github.com/ubuntu-core/snappy@v0.0.0-20210827154228-9e584df982bb/cmd/snap/cmd_set.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2016 Canonical Ltd
     5   *
     6   * This program is free software: you can redistribute it and/or modify
     7   * it under the terms of the GNU General Public License version 3 as
     8   * published by the Free Software Foundation.
     9   *
    10   * This program is distributed in the hope that it will be useful,
    11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    13   * GNU General Public License for more details.
    14   *
    15   * You should have received a copy of the GNU General Public License
    16   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    17   *
    18   */
    19  
    20  package main
    21  
    22  import (
    23  	"fmt"
    24  	"strings"
    25  
    26  	"github.com/jessevdk/go-flags"
    27  
    28  	"github.com/snapcore/snapd/i18n"
    29  	"github.com/snapcore/snapd/jsonutil"
    30  )
    31  
    32  var shortSetHelp = i18n.G("Change configuration options")
    33  var longSetHelp = i18n.G(`
    34  The set command changes the provided configuration options as requested.
    35  
    36      $ snap set snap-name username=frank password=$PASSWORD
    37  
    38  All configuration changes are persisted at once, and only after the
    39  snap's configuration hook returns successfully.
    40  
    41  Nested values may be modified via a dotted path:
    42  
    43      $ snap set snap-name author.name=frank
    44  
    45  Configuration option may be unset with exclamation mark:
    46      $ snap set snap-name author!
    47  `)
    48  
    49  type cmdSet struct {
    50  	waitMixin
    51  	Positional struct {
    52  		Snap       installedSnapName
    53  		ConfValues []string `required:"1"`
    54  	} `positional-args:"yes" required:"yes"`
    55  
    56  	Typed  bool `short:"t"`
    57  	String bool `short:"s"`
    58  }
    59  
    60  func init() {
    61  	addCommand("set", shortSetHelp, longSetHelp, func() flags.Commander { return &cmdSet{} },
    62  		waitDescs.also(map[string]string{
    63  			// TRANSLATORS: This should not start with a lowercase letter.
    64  			"t": i18n.G("Parse the value strictly as JSON document"),
    65  			// TRANSLATORS: This should not start with a lowercase letter.
    66  			"s": i18n.G("Parse the value as a string"),
    67  		}), []argDesc{
    68  			{
    69  				name: "<snap>",
    70  				// TRANSLATORS: This should not start with a lowercase letter.
    71  				desc: i18n.G("The snap to configure (e.g. hello-world)"),
    72  			}, {
    73  				// TRANSLATORS: This needs to begin with < and end with >
    74  				name: i18n.G("<conf value>"),
    75  				// TRANSLATORS: This should not start with a lowercase letter.
    76  				desc: i18n.G("Set (key=value) or unset (key!) configuration value"),
    77  			},
    78  		})
    79  }
    80  
    81  func (x *cmdSet) Execute(args []string) error {
    82  	if x.String && x.Typed {
    83  		return fmt.Errorf(i18n.G("cannot use -t and -s together"))
    84  	}
    85  
    86  	patchValues := make(map[string]interface{})
    87  	for _, patchValue := range x.Positional.ConfValues {
    88  		parts := strings.SplitN(patchValue, "=", 2)
    89  		if len(parts) == 1 && strings.HasSuffix(patchValue, "!") {
    90  			patchValues[strings.TrimSuffix(patchValue, "!")] = nil
    91  			continue
    92  		}
    93  		if len(parts) != 2 {
    94  			return fmt.Errorf(i18n.G("invalid configuration: %q (want key=value)"), patchValue)
    95  		}
    96  
    97  		if x.String {
    98  			patchValues[parts[0]] = parts[1]
    99  		} else {
   100  			var value interface{}
   101  			if err := jsonutil.DecodeWithNumber(strings.NewReader(parts[1]), &value); err != nil {
   102  				if x.Typed {
   103  					return fmt.Errorf("failed to parse JSON: %w", err)
   104  				}
   105  
   106  				// Not valid JSON-- just save the string as-is.
   107  				patchValues[parts[0]] = parts[1]
   108  			} else {
   109  				patchValues[parts[0]] = value
   110  			}
   111  		}
   112  	}
   113  
   114  	snapName := string(x.Positional.Snap)
   115  	id, err := x.client.SetConf(snapName, patchValues)
   116  	if err != nil {
   117  		return err
   118  	}
   119  
   120  	if _, err := x.wait(id); err != nil {
   121  		if err == noWait {
   122  			return nil
   123  		}
   124  		return err
   125  	}
   126  
   127  	return nil
   128  }