github.com/vchain-us/vcn@v0.9.11-0.20210921212052-a2484d23c0b3/pkg/cmd/inspect/inspect.go (about)

     1  /*
     2   * Copyright (c) 2018-2020 vChain, Inc. All Rights Reserved.
     3   * This software is released under GPL3.
     4   * The full license information can be found under:
     5   * https://www.gnu.org/licenses/gpl-3.0.en.html
     6   *
     7   */
     8  
     9  package inspect
    10  
    11  import (
    12  	"fmt"
    13  	"github.com/vchain-us/vcn/internal/errors"
    14  	"strings"
    15  
    16  	"github.com/spf13/viper"
    17  	"github.com/vchain-us/vcn/internal/assert"
    18  	"github.com/vchain-us/vcn/pkg/meta"
    19  
    20  	"github.com/vchain-us/vcn/pkg/cmd/internal/cli"
    21  
    22  	"github.com/spf13/cobra"
    23  	"github.com/vchain-us/vcn/pkg/api"
    24  	"github.com/vchain-us/vcn/pkg/cmd/internal/types"
    25  	"github.com/vchain-us/vcn/pkg/extractor"
    26  	"github.com/vchain-us/vcn/pkg/store"
    27  )
    28  
    29  // NewCommand returns the cobra command for `vcn inspect`
    30  func NewCommand() *cobra.Command {
    31  	cmd := &cobra.Command{
    32  		Use:     "inspect",
    33  		Aliases: []string{"i"},
    34  		Short:   "Returns the asset history with low-level information",
    35  		Long: `
    36  Returns the asset history with low-level information
    37  
    38  Environment variables:
    39  VCN_USER=
    40  VCN_PASSWORD=
    41  VCN_NOTARIZATION_PASSWORD=
    42  VCN_NOTARIZATION_PASSWORD_EMPTY=
    43  VCN_OTP=
    44  VCN_OTP_EMPTY=
    45  VCN_LC_HOST=
    46  VCN_LC_PORT=
    47  VCN_LC_CERT=
    48  VCN_LC_SKIP_TLS_VERIFY=false
    49  VCN_LC_NO_TLS=false
    50  VCN_LC_API_KEY=
    51  VCN_LC_LEDGER=
    52  `,
    53  		PreRunE: func(cmd *cobra.Command, args []string) error {
    54  			return viper.BindPFlags(cmd.Flags())
    55  		},
    56  		RunE: runInspect,
    57  		Args: func(cmd *cobra.Command, args []string) error {
    58  			if hash, _ := cmd.Flags().GetString("hash"); hash != "" {
    59  				if len(args) > 0 {
    60  					return fmt.Errorf("cannot use ARG(s) with --hash")
    61  				}
    62  				return nil
    63  			}
    64  
    65  			first, _ := cmd.Flags().GetUint64("first")
    66  			last, _ := cmd.Flags().GetUint64("last")
    67  			start, _ := cmd.Flags().GetString("start")
    68  			end, _ := cmd.Flags().GetString("end")
    69  
    70  			if (first > 0 || last > 0 || start != "" || end != "") &&
    71  				store.Config().CurrentContext.LcHost == "" {
    72  				return fmt.Errorf("time range filter is available only in Ledger Compliance environment")
    73  			}
    74  
    75  			if first > 0 && last > 0 {
    76  				return fmt.Errorf("--first and --last are mutual exclusive")
    77  			}
    78  			return cobra.MinimumNArgs(1)(cmd, args)
    79  		},
    80  		Example: `
    81  vcn inspect document.pdf --last 1
    82  vcn inspect document.pdf --first 1
    83  vcn inspect document.pdf --start 2020/10/28-08:00:00 --end 2020/10/28-17:00:00 --first 10
    84  vcn inspect document.pdf --signerID CygBE_zb8XnprkkO6ncIrbbwYoUq5T1zfyEF6DhqcAI= --start 2020/10/28-16:00:00 --end 2020/10/28-17:10:00 --last 3
    85  `,
    86  	}
    87  
    88  	cmd.SetUsageTemplate(
    89  		strings.Replace(cmd.UsageTemplate(), "{{.UseLine}}", "{{.UseLine}} ARG", 1),
    90  	)
    91  
    92  	cmd.Flags().String("hash", "", "specify a hash to inspect, if set no ARG can be used")
    93  	cmd.Flags().Bool("extract-only", false, "if set, print only locally extracted info")
    94  	// ledger compliance flags
    95  	cmd.Flags().String("lc-host", "", meta.VcnLcHostFlagDesc)
    96  	cmd.Flags().String("lc-port", "443", meta.VcnLcPortFlagDesc)
    97  	cmd.Flags().String("lc-cert", "", meta.VcnLcCertPathDesc)
    98  	cmd.Flags().Bool("lc-skip-tls-verify", false, meta.VcnLcSkipTlsVerifyDesc)
    99  	cmd.Flags().Bool("lc-no-tls", false, meta.VcnLcNoTlsDesc)
   100  	cmd.Flags().String("lc-api-key", "", meta.VcnLcApiKeyDesc)
   101  	cmd.Flags().String("lc-ledger", "", meta.VcnLcLedgerDesc)
   102  
   103  	cmd.Flags().String("signerID", "", "specify a signerID to refine inspection result on ledger compliance")
   104  
   105  	cmd.Flags().Uint64("first", 0, "set the limit for the first elements filter")
   106  	cmd.Flags().Uint64("last", 0, "set the limit for the last elements filter")
   107  
   108  	cmd.Flags().String("start", "", "set the start of date and time range filter. Example 2020/10/28-16:00:00")
   109  	cmd.Flags().String("end", "", "set the end of date and time range filter. Example 2020/10/28-16:00:00")
   110  
   111  	return cmd
   112  }
   113  
   114  func runInspect(cmd *cobra.Command, args []string) error {
   115  
   116  	output, err := cmd.Flags().GetString("output")
   117  	if err != nil {
   118  		return err
   119  	}
   120  	hash, err := cmd.Flags().GetString("hash")
   121  	if err != nil {
   122  		return err
   123  	}
   124  	hash = strings.ToLower(hash)
   125  
   126  	extractOnly, err := cmd.Flags().GetBool("extract-only")
   127  	if err != nil {
   128  		return err
   129  	}
   130  	cmd.SilenceUsage = true
   131  
   132  	if hash == "" {
   133  		if len(args) < 1 {
   134  			return fmt.Errorf("no argument")
   135  		}
   136  		if hash, err = extractInfo(args[0], output); err != nil {
   137  			return err
   138  		}
   139  		if output == "" {
   140  			fmt.Print("\n\n")
   141  		}
   142  	}
   143  
   144  	if extractOnly {
   145  		return nil
   146  	}
   147  
   148  	signerID, err := cmd.Flags().GetString("signerID")
   149  	if err != nil {
   150  		return err
   151  	}
   152  
   153  	lcHost := viper.GetString("lc-host")
   154  	lcPort := viper.GetString("lc-port")
   155  	lcCert := viper.GetString("lc-cert")
   156  	skipTlsVerify := viper.GetBool("lc-skip-tls-verify")
   157  	noTls := viper.GetBool("lc-no-tls")
   158  	lcApiKey := viper.GetString("lc-api-key")
   159  	lcLedger := viper.GetString("lc-ledger")
   160  
   161  	if lcApiKey == "" && lcHost != "" {
   162  		return errors.ErrNoLcApiKeyEnv
   163  	}
   164  
   165  	//check if an lcUser is present inside the context
   166  	var lcUser *api.LcUser
   167  	uif, err := api.GetUserFromContext(store.Config().CurrentContext, lcApiKey, lcLedger, nil)
   168  	if err != nil {
   169  		return err
   170  	}
   171  	if lctmp, ok := uif.(*api.LcUser); ok {
   172  		lcUser = lctmp
   173  	}
   174  
   175  	// It uses flags for CNC context client constructor if at least host and apikey are provided
   176  	if lcHost != "" && lcApiKey != "" {
   177  		// client from context could be override by the one created from local flags
   178  		lcUser, err = api.NewLcUser(lcApiKey, lcLedger, lcHost, lcPort, lcCert, skipTlsVerify, noTls, nil)
   179  		if err != nil {
   180  			return err
   181  		} // Store the new config
   182  		if err := store.SaveConfig(); err != nil {
   183  			return err
   184  		}
   185  	}
   186  
   187  	if lcUser != nil {
   188  		err = lcUser.Client.Connect()
   189  		if err != nil {
   190  			return err
   191  		}
   192  		first, err := cmd.Flags().GetUint64("first")
   193  		if err != nil {
   194  			return err
   195  		}
   196  		last, err := cmd.Flags().GetUint64("last")
   197  		if err != nil {
   198  			return err
   199  		}
   200  		start, err := cmd.Flags().GetString("start")
   201  		if err != nil {
   202  			return err
   203  		}
   204  		end, err := cmd.Flags().GetString("end")
   205  		if err != nil {
   206  			return err
   207  		}
   208  
   209  		if first == 0 && last == 0 {
   210  			last = 100
   211  			fmt.Printf("no filter is specified. At maximum last 100 items will be returned\n")
   212  		}
   213  		return lcInspect(hash, signerID, lcUser, first, last, start, end, output)
   214  	}
   215  
   216  	// User
   217  	if err := assert.UserLogin(); err != nil {
   218  		return err
   219  	}
   220  	u, ok := uif.(*api.User)
   221  	if !ok {
   222  		return fmt.Errorf("cannot load the current user")
   223  	}
   224  
   225  	if hasAuth, _ := u.IsAuthenticated(); hasAuth && output == "" {
   226  		fmt.Printf("Current user: %s\n", u.Email())
   227  	}
   228  
   229  	return inspect(hash, u, output)
   230  }
   231  
   232  func extractInfo(arg string, output string) (hash string, err error) {
   233  	a, err := extractor.Extract([]string{arg})
   234  	if err != nil {
   235  		return "", err
   236  	}
   237  	if len(a) == 0 {
   238  		return "", fmt.Errorf("unable to process the input asset provided: %s", arg)
   239  	}
   240  	if len(a) == 1 {
   241  		hash = a[0].Hash
   242  	}
   243  	if len(a) > 1 {
   244  		return "", fmt.Errorf("info extraction on multiple items is not yet supported")
   245  	}
   246  	if output == "" {
   247  		fmt.Printf("Extracted info from: %s\n\n", arg)
   248  	}
   249  	cli.Print(output, types.NewResult(a[0], nil, nil))
   250  	return
   251  }
   252  
   253  func inspect(hash string, u *api.User, output string) error {
   254  	results, err := GetResults(hash, u)
   255  	if err != nil {
   256  		return err
   257  	}
   258  
   259  	if output == "" {
   260  		fmt.Printf(
   261  			`%d notarizations found for "%s"
   262  
   263  `,
   264  			len(results), hash)
   265  	}
   266  
   267  	return cli.PrintSlice(output, results)
   268  }
   269  
   270  func GetResults(hash string, u *api.User) ([]types.Result, error) {
   271  	verifications, err := api.BlockChainInspect(hash)
   272  	if err != nil {
   273  		return nil, err
   274  	}
   275  	l := len(verifications)
   276  
   277  	results := make([]types.Result, l)
   278  	for i, v := range verifications {
   279  		ar, err := api.LoadArtifact(u, hash, v.MetaHash())
   280  		results[i] = *types.NewResult(nil, ar, &v)
   281  		if err != nil {
   282  			results[i].AddError(err)
   283  		}
   284  		// check if artifact is synced, if any
   285  		if ar != nil {
   286  			if v.Status.String() != ar.Status {
   287  				results[i].AddError(fmt.Errorf(
   288  					"status not in sync (blockchain: %s, platform: %s)", v.Status.String(), ar.Status,
   289  				))
   290  			}
   291  			if int64(v.Level) != ar.Level {
   292  				results[i].AddError(fmt.Errorf(
   293  					"level not in sync (blockchain: %d, platform: %d)", v.Level, ar.Level,
   294  				))
   295  			}
   296  		}
   297  	}
   298  	return results, nil
   299  }