github.com/qri-io/qri@v0.10.1-0.20220104210721-c771715036cb/cmd/save.go (about)

     1  package cmd
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/qri-io/ioes"
     9  	"github.com/qri-io/qri/dsref"
    10  	"github.com/qri-io/qri/lib"
    11  	"github.com/qri-io/qri/repo"
    12  	"github.com/spf13/cobra"
    13  )
    14  
    15  // NewSaveCommand creates a `qri save` cobra command used for saving changes
    16  // to datasets
    17  func NewSaveCommand(f Factory, ioStreams ioes.IOStreams) *cobra.Command {
    18  	o := &SaveOptions{IOStreams: ioStreams}
    19  	cmd := &cobra.Command{
    20  		Use:     "save [DATASET]",
    21  		Aliases: []string{"commit"},
    22  		Short:   "save changes to a dataset",
    23  		Long: `
    24  Save is how you change a dataset, updating one or more dataset components. Every
    25  time you run save, an entry is added to your dataset’s log (which you can see by
    26  running `[1:] + "`qri log <dataset_reference>`" + `).
    27  
    28  Dataset changes can be automated with a transform component adn the --apply flag
    29  For more on transforms see https://qri.io/docs/transforms/overview
    30  If the dataset you're changing has a transform, running ` + "`qri save --apply`" +
    31  			`
    32  will re-execute it to produce a new version
    33  
    34  Every time you save, you can provide a message about what you changed and why. 
    35  If you don’t provide a message Qri will automatically generate one for you.
    36  The ` + "`--message`" + `" and ` + "`--title`" + ` flags allow you to add a 
    37  commit message and title to the save.
    38  
    39  When you make an update and save a dataset that you originally added from a 
    40  different peer, the dataset gets renamed from ` + "`peers_name/dataset_name`" +
    41  			` to
    42  ` + "`my_name/dataset_name`" + `.`,
    43  		Example: `  # Save updated data to dataset annual_pop:
    44    $ qri save --body /path/to/data.csv me/annual_pop
    45  
    46    # Save updated dataset (no data) to annual_pop:
    47    $ qri save --file /path/to/dataset.yaml me/annual_pop
    48    
    49    # Re-execute the latest transform from history:
    50    $ qri save --apply me/tf_dataset`,
    51  		Annotations: map[string]string{
    52  			"group": "dataset",
    53  		},
    54  		Args: cobra.MaximumNArgs(1),
    55  		RunE: func(cmd *cobra.Command, args []string) error {
    56  			if err := o.Complete(f, args); err != nil {
    57  				return err
    58  			}
    59  			if err := o.Validate(); err != nil {
    60  				return err
    61  			}
    62  			return o.Run()
    63  		},
    64  	}
    65  
    66  	cmd.Flags().StringSliceVarP(&o.FilePaths, "file", "f", nil, "dataset or component file (yaml or json)")
    67  	cmd.MarkFlagFilename("file", "yaml", "yml", "json")
    68  	cmd.Flags().StringVarP(&o.Title, "title", "t", "", "title of commit message for save")
    69  	cmd.Flags().StringVarP(&o.Message, "message", "m", "", "commit message for save")
    70  	cmd.Flags().StringVarP(&o.BodyPath, "body", "", "", "path to file or url of data to add as dataset contents")
    71  	cmd.MarkFlagFilename("body")
    72  	// cmd.Flags().BoolVarP(&o.ShowValidation, "show-validation", "s", false, "display a list of validation errors upon adding")
    73  	cmd.Flags().BoolVar(&o.Apply, "apply", false, "apply a transformation and save the result")
    74  	cmd.Flags().BoolVar(&o.NoApply, "no-apply", false, "don't apply any transforms that are added")
    75  	cmd.Flags().StringSliceVar(&o.Secrets, "secrets", nil, "transform secrets as comma separated key,value,key,value,... sequence")
    76  	cmd.Flags().BoolVar(&o.DeprecatedDryRun, "dry-run", false, "deprecated: use `qri apply` instead")
    77  	cmd.Flags().BoolVar(&o.Force, "force", false, "force a new commit, even if no changes are detected")
    78  	cmd.Flags().BoolVarP(&o.KeepFormat, "keep-format", "k", false, "convert incoming data to stored data format")
    79  	// TODO(dustmop): --no-render is deprecated, viz are being phased out, in favor of readme.
    80  	cmd.Flags().BoolVar(&o.NoRender, "no-render", false, "don't store a rendered version of the the visualization")
    81  	cmd.Flags().BoolVarP(&o.NewName, "new", "n", false, "save a new dataset only, using an available name")
    82  	cmd.Flags().StringVar(&o.Drop, "drop", "", "comma-separated list of components to remove")
    83  
    84  	return cmd
    85  }
    86  
    87  // SaveOptions encapsulates state for the save command
    88  type SaveOptions struct {
    89  	ioes.IOStreams
    90  
    91  	Refs      *RefSelect
    92  	FilePaths []string
    93  	BodyPath  string
    94  	Drop      string
    95  
    96  	Title   string
    97  	Message string
    98  
    99  	Apply            bool
   100  	NoApply          bool
   101  	DeprecatedDryRun bool
   102  	Secrets          []string
   103  
   104  	Replace        bool
   105  	ShowValidation bool
   106  	KeepFormat     bool
   107  	Force          bool
   108  	NoRender       bool
   109  	NewName        bool
   110  	UseDscache     bool
   111  
   112  	inst *lib.Instance
   113  }
   114  
   115  // Complete adds any missing configuration that can only be added just before calling Run
   116  func (o *SaveOptions) Complete(f Factory, args []string) (err error) {
   117  	if o.DeprecatedDryRun {
   118  		return fmt.Errorf("--dry-run has been removed, use `qri apply` command instead")
   119  	}
   120  
   121  	if o.inst, err = f.Instance(); err != nil {
   122  		return
   123  	}
   124  
   125  	if o.Refs, err = GetCurrentRefSelect(f, args, BadUpperCaseOkayWhenSavingExistingDataset); err != nil {
   126  		// Not an error to use an empty reference, it will be inferred later on.
   127  		if err != repo.ErrEmptyRef {
   128  			return
   129  		}
   130  	}
   131  
   132  	return nil
   133  }
   134  
   135  // Validate checks that all user input is valid
   136  func (o *SaveOptions) Validate() error {
   137  	return nil
   138  }
   139  
   140  // Run executes the save command
   141  func (o *SaveOptions) Run() (err error) {
   142  	p := &lib.SaveParams{
   143  		Ref:      o.Refs.Ref(),
   144  		BodyPath: o.BodyPath,
   145  		Title:    o.Title,
   146  		Message:  o.Message,
   147  
   148  		ScriptOutput: o.ErrOut,
   149  		FilePaths:    o.FilePaths,
   150  		Private:      false,
   151  		Apply:        o.Apply,
   152  		Drop:         o.Drop,
   153  
   154  		ConvertFormatToPrev: o.KeepFormat,
   155  		Force:               o.Force,
   156  
   157  		ShouldRender: !o.NoRender,
   158  		NewName:      o.NewName,
   159  	}
   160  
   161  	// Check if file ends in '.star'. If so, either Apply or NoApply is required.
   162  	// Apply is passed down to the lib level, NoApply ends here. NoApply's only purpose
   163  	// is to ensure that the user wants to add a transform without running it, and explicitly
   164  	// agrees that they're not expecting the old behavior: wherein adding a transform
   165  	// would always run it.
   166  	for _, file := range o.FilePaths {
   167  		if strings.HasSuffix(file, ".star") {
   168  			if !o.Apply && !o.NoApply {
   169  				return fmt.Errorf("saving with a new transform requires either --apply or --no-apply flag")
   170  			}
   171  		}
   172  	}
   173  
   174  	// TODO(dustmop): Support component files, like .json and .yaml, which can contain
   175  	// transform scripts.
   176  
   177  	if o.Secrets != nil {
   178  		if !confirm(o.ErrOut, o.In, `
   179  Warning: You are providing secrets to a dataset transformation.
   180  Never provide secrets to a transformation you do not trust.
   181  continue?`, true) {
   182  			return
   183  		}
   184  		if p.Secrets, err = parseSecrets(o.Secrets...); err != nil {
   185  			return err
   186  		}
   187  	}
   188  
   189  	ctx := context.TODO()
   190  	res, err := o.inst.Dataset().Save(ctx, p)
   191  	if err != nil {
   192  		return err
   193  	}
   194  
   195  	ref := dsref.ConvertDatasetToVersionInfo(res).SimpleRef()
   196  	printSuccess(o.ErrOut, "dataset saved: %s", refString(ref))
   197  	if res.Structure != nil && res.Structure.ErrCount > 0 {
   198  		printWarning(o.ErrOut, fmt.Sprintf("this dataset has %d validation errors", res.Structure.ErrCount))
   199  	}
   200  
   201  	return nil
   202  }