github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/cmd/snap/cmd_validate.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2020 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  	"strconv"
    25  	"strings"
    26  
    27  	"github.com/jessevdk/go-flags"
    28  
    29  	"github.com/snapcore/snapd/asserts"
    30  	"github.com/snapcore/snapd/client"
    31  	"github.com/snapcore/snapd/i18n"
    32  )
    33  
    34  type cmdValidate struct {
    35  	clientMixin
    36  	Monitor bool `long:"monitor"`
    37  	// XXX: enforce mode is not supported yet
    38  	Enforce    bool `long:"enforce" hidden:"yes"`
    39  	Forget     bool `long:"forget"`
    40  	Positional struct {
    41  		ValidationSet string `positional-arg-name:"<validation-set>"`
    42  	} `positional-args:"yes"`
    43  	colorMixin
    44  }
    45  
    46  var shortValidateHelp = i18n.G("List or apply validation sets")
    47  var longValidateHelp = i18n.G(`
    48  The validate command lists or applies validations sets
    49  `)
    50  
    51  func init() {
    52  	cmd := addCommand("validate", shortValidateHelp, longValidateHelp, func() flags.Commander { return &cmdValidate{} }, colorDescs.also(map[string]string{
    53  		// TRANSLATORS: This should not start with a lowercase letter.
    54  		"monitor": i18n.G("Monitor the given validations set"),
    55  		// TRANSLATORS: This should not start with a lowercase letter.
    56  		"enforce": i18n.G("Enforce the given validation set"),
    57  		// TRANSLATORS: This should not start with a lowercase letter.
    58  		"forget": i18n.G("Forget the given validation set"),
    59  	}), []argDesc{{
    60  		// TRANSLATORS: This needs to begin with < and end with >
    61  		name: i18n.G("<validation-set>"),
    62  		// TRANSLATORS: This should not start with a lowercase letter.
    63  		desc: i18n.G("Validation set with an optional pinned sequence point, i.e. account-id/name[=seq]"),
    64  	}})
    65  	// XXX: remove once api has landed
    66  	cmd.hidden = true
    67  }
    68  
    69  func splitValidationSetArg(arg string) (account, name string, seq int, err error) {
    70  	parts := strings.Split(arg, "=")
    71  	if len(parts) > 2 {
    72  		return "", "", 0, fmt.Errorf("cannot parse validation set, expected account/name=seq")
    73  	}
    74  	if len(parts) == 2 {
    75  		seq, err = strconv.Atoi(parts[1])
    76  		if err != nil {
    77  			return "", "", 0, err
    78  		}
    79  	}
    80  
    81  	parts = strings.Split(parts[0], "/")
    82  	if len(parts) != 2 {
    83  		return "", "", 0, fmt.Errorf("expected a single account/name")
    84  	}
    85  
    86  	account = parts[0]
    87  	name = parts[1]
    88  	if !asserts.IsValidAccountID(account) {
    89  		return "", "", 0, fmt.Errorf("invalid account ID %q", account)
    90  	}
    91  	if !asserts.IsValidValidationSetName(name) {
    92  		return "", "", 0, fmt.Errorf("invalid validation set name %q", name)
    93  	}
    94  
    95  	return account, name, seq, nil
    96  }
    97  
    98  func fmtValid(res *client.ValidationSetResult) string {
    99  	if res.Valid {
   100  		return "valid"
   101  	}
   102  	return "invalid"
   103  }
   104  
   105  func fmtValidationSet(res *client.ValidationSetResult) string {
   106  	if res.PinnedAt == 0 {
   107  		return fmt.Sprintf("%s/%s", res.AccountID, res.Name)
   108  	}
   109  	return fmt.Sprintf("%s/%s=%d", res.AccountID, res.Name, res.PinnedAt)
   110  }
   111  
   112  func (cmd *cmdValidate) Execute(args []string) error {
   113  	// check that only one action is used at a time
   114  	var action string
   115  	for _, a := range []struct {
   116  		name string
   117  		set  bool
   118  	}{
   119  		{"monitor", cmd.Monitor},
   120  		{"enforce", cmd.Enforce},
   121  		{"forget", cmd.Forget},
   122  	} {
   123  		if a.set {
   124  			if action != "" {
   125  				return fmt.Errorf("cannot use --%s and --%s together", action, a.name)
   126  			}
   127  			action = a.name
   128  		}
   129  	}
   130  
   131  	if cmd.Positional.ValidationSet == "" && action != "" {
   132  		return fmt.Errorf("missing validation set argument")
   133  	}
   134  
   135  	var accountID, name string
   136  	var seq int
   137  	var err error
   138  	if cmd.Positional.ValidationSet != "" {
   139  		accountID, name, seq, err = splitValidationSetArg(cmd.Positional.ValidationSet)
   140  		if err != nil {
   141  			return fmt.Errorf("cannot parse validation set %q: %v", cmd.Positional.ValidationSet, err)
   142  		}
   143  	}
   144  
   145  	if action != "" {
   146  		// forget
   147  		if cmd.Forget {
   148  			return cmd.client.ForgetValidationSet(accountID, name, seq)
   149  		}
   150  		// apply
   151  		opts := &client.ValidateApplyOptions{
   152  			Mode:     action,
   153  			Sequence: seq,
   154  		}
   155  		return cmd.client.ApplyValidationSet(accountID, name, opts)
   156  	}
   157  
   158  	// no validation set argument, print list with extended info
   159  	if cmd.Positional.ValidationSet == "" {
   160  		vsets, err := cmd.client.ListValidationsSets()
   161  		if err != nil {
   162  			return err
   163  		}
   164  		if len(vsets) == 0 {
   165  			fmt.Fprintln(Stderr, i18n.G("No validations are available"))
   166  			return nil
   167  		}
   168  
   169  		esc := cmd.getEscapes()
   170  		w := tabWriter()
   171  
   172  		// TRANSLATORS: the %s is to insert a filler escape sequence (please keep it flush to the column header, with no extra spaces)
   173  		fmt.Fprintf(w, i18n.G("Validation\tMode\tSeq\tCurrent\t%s\tNotes\n"), fillerPublisher(esc))
   174  		for _, res := range vsets {
   175  			// TODO: fill notes when've clarity about them
   176  			var notes string
   177  			// doing it this way because otherwise it's a sea of %s\t%s\t%s
   178  			line := []string{
   179  				fmtValidationSet(res),
   180  				res.Mode,
   181  				fmt.Sprintf("%d", res.Sequence),
   182  				fmtValid(res),
   183  				notes,
   184  			}
   185  			fmt.Fprintln(w, strings.Join(line, "\t"))
   186  		}
   187  		w.Flush()
   188  	} else {
   189  		vset, err := cmd.client.ValidationSet(accountID, name, seq)
   190  		if err != nil {
   191  			return err
   192  		}
   193  		fmt.Fprintf(Stdout, fmtValid(vset))
   194  		// XXX: exit status 1 if invalid?
   195  	}
   196  
   197  	return nil
   198  }