github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/asserts/repair.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  
     3  /*
     4   * Copyright (C) 2017 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 asserts
    21  
    22  import (
    23  	"fmt"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/snapcore/snapd/snap/naming"
    28  	"github.com/snapcore/snapd/strutil"
    29  )
    30  
    31  // Repair holds an repair assertion which allows running repair
    32  // code to fixup broken systems. It can be limited by series and models, as well
    33  // as by bases and modes.
    34  type Repair struct {
    35  	assertionBase
    36  
    37  	series        []string
    38  	architectures []string
    39  	models        []string
    40  
    41  	modes []string
    42  	bases []string
    43  
    44  	id int
    45  
    46  	disabled  bool
    47  	timestamp time.Time
    48  }
    49  
    50  // BrandID returns the brand identifier that signed this assertion.
    51  func (r *Repair) BrandID() string {
    52  	return r.HeaderString("brand-id")
    53  }
    54  
    55  // RepairID returns the sequential id of the repair. There
    56  // should be a public place to look up details about the repair
    57  // by brand-id and repair-id.
    58  // (e.g. the snapcraft forum).
    59  func (r *Repair) RepairID() int {
    60  	return r.id
    61  }
    62  
    63  // Sequence implements SequenceMember, it returns the same as RepairID.
    64  func (r *Repair) Sequence() int {
    65  	return r.RepairID()
    66  }
    67  
    68  // Summary returns the mandatory summary description of the repair.
    69  func (r *Repair) Summary() string {
    70  	return r.HeaderString("summary")
    71  }
    72  
    73  // Architectures returns the architectures that this assertions applies to.
    74  func (r *Repair) Architectures() []string {
    75  	return r.architectures
    76  }
    77  
    78  // Series returns the series that this assertion is valid for.
    79  func (r *Repair) Series() []string {
    80  	return r.series
    81  }
    82  
    83  // Modes returns the modes that this assertion is valid for. It is either a list
    84  // of "run", "recover", or "install", or it is the empty list. The empty list
    85  // is interpreted to mean only "run" mode.
    86  func (r *Repair) Modes() []string {
    87  	return r.modes
    88  }
    89  
    90  // Bases returns the bases that this assertion is valid for. It is either a list
    91  // of valid base snaps that Ubuntu Core systems can have or it is the empty
    92  // list. The empty list effectively means all Ubuntu Core systems while "core"
    93  // means Ubuntu Core 16, "core18" means Ubuntu Core 18, etc.
    94  func (r *Repair) Bases() []string {
    95  	return r.bases
    96  }
    97  
    98  // Models returns the models that this assertion is valid for.
    99  // It is a list of "brand-id/model-name" strings.
   100  func (r *Repair) Models() []string {
   101  	return r.models
   102  }
   103  
   104  // Disabled returns true if the repair has been disabled.
   105  func (r *Repair) Disabled() bool {
   106  	return r.disabled
   107  }
   108  
   109  // Timestamp returns the time when the repair was issued.
   110  func (r *Repair) Timestamp() time.Time {
   111  	return r.timestamp
   112  }
   113  
   114  // Implement further consistency checks.
   115  func (r *Repair) checkConsistency(db RODatabase, acck *AccountKey) error {
   116  	// Do the cross-checks when this assertion is actually used,
   117  	// i.e. in the future repair code
   118  
   119  	return nil
   120  }
   121  
   122  // sanity
   123  var _ consistencyChecker = (*Repair)(nil)
   124  
   125  func assembleRepair(assert assertionBase) (Assertion, error) {
   126  	err := checkAuthorityMatchesBrand(&assert)
   127  	if err != nil {
   128  		return nil, err
   129  	}
   130  
   131  	repairID, err := checkSequence(assert.headers, "repair-id")
   132  	if err != nil {
   133  		return nil, err
   134  	}
   135  
   136  	summary, err := checkNotEmptyString(assert.headers, "summary")
   137  	if err != nil {
   138  		return nil, err
   139  	}
   140  	if strings.ContainsAny(summary, "\n\r") {
   141  		return nil, fmt.Errorf(`"summary" header cannot have newlines`)
   142  	}
   143  
   144  	series, err := checkStringList(assert.headers, "series")
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  	models, err := checkStringList(assert.headers, "models")
   149  	if err != nil {
   150  		return nil, err
   151  	}
   152  	architectures, err := checkStringList(assert.headers, "architectures")
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  	modes, err := checkStringList(assert.headers, "modes")
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  	bases, err := checkStringList(assert.headers, "bases")
   161  	if err != nil {
   162  		return nil, err
   163  	}
   164  
   165  	// validate that all base snap names are valid snap names
   166  	for _, b := range bases {
   167  		if err := naming.ValidateSnap(b); err != nil {
   168  			return nil, fmt.Errorf("invalid snap name %q in \"bases\"", b)
   169  		}
   170  	}
   171  
   172  	// verify that modes is a list of only "run" and "recover"
   173  	if len(modes) != 0 {
   174  		for _, m := range modes {
   175  			// note that we could import boot here to use i.e. boot.ModeRun, but
   176  			// that is rather a heavy package considering that this package is
   177  			// used in many places, so instead just use the values directly,
   178  			// they're unlikely to change now
   179  			if !strutil.ListContains([]string{"run", "recover"}, m) {
   180  				return nil, fmt.Errorf("header \"modes\" contains an invalid element: %q (valid values are run and recover)", m)
   181  			}
   182  		}
   183  
   184  		// if modes is non-empty, then bases must be core2X, i.e. core20+
   185  		// however, we don't know what future bases could be UC20-like and named
   186  		// differently yet, so we just fail on bases that we know as of today
   187  		// are _not_ UC20: core and core18
   188  
   189  		for _, b := range bases {
   190  			// fail on uc16 and uc18 base snaps
   191  			if b == "core" || b == "core18" || b == "core16" {
   192  				return nil, fmt.Errorf("in the presence of a non-empty \"modes\" header, \"bases\" must only contain base snaps supporting recovery modes")
   193  			}
   194  		}
   195  	}
   196  
   197  	disabled, err := checkOptionalBool(assert.headers, "disabled")
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  
   202  	timestamp, err := checkRFC3339Date(assert.headers, "timestamp")
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	return &Repair{
   208  		assertionBase: assert,
   209  		series:        series,
   210  		architectures: architectures,
   211  		models:        models,
   212  		modes:         modes,
   213  		bases:         bases,
   214  		id:            repairID,
   215  		disabled:      disabled,
   216  		timestamp:     timestamp,
   217  	}, nil
   218  }