gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/patch/patch4.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 patch
    21  
    22  import (
    23  	"encoding/json"
    24  	"errors"
    25  	"fmt"
    26  
    27  	"github.com/snapcore/snapd/overlord/state"
    28  	"github.com/snapcore/snapd/snap"
    29  )
    30  
    31  func init() {
    32  	patches[4] = []PatchFunc{patch4}
    33  }
    34  
    35  type patch4Flags int
    36  
    37  const (
    38  	patch4FlagDevMode = 1 << iota
    39  	patch4FlagTryMode
    40  	patch4FlagJailMode
    41  )
    42  
    43  const patch4FlagRevert = patch4Flags(0x40000000)
    44  
    45  func (f patch4Flags) DevMode() bool {
    46  	return f&patch4FlagDevMode != 0
    47  }
    48  
    49  func (f patch4Flags) TryMode() bool {
    50  	return f&patch4FlagTryMode != 0
    51  }
    52  
    53  func (f patch4Flags) JailMode() bool {
    54  	return f&patch4FlagJailMode != 0
    55  }
    56  
    57  func (f patch4Flags) Revert() bool {
    58  	return f&patch4FlagRevert != 0
    59  }
    60  
    61  type patch4DownloadInfo struct {
    62  	AnonDownloadURL string `json:"anon-download-url,omitempty"`
    63  	DownloadURL     string `json:"download-url,omitempty"`
    64  
    65  	Size     int64  `json:"size,omitempty"`
    66  	Sha3_384 string `json:"sha3-384,omitempty"`
    67  }
    68  
    69  type patch4SideInfo struct {
    70  	RealName          string        `yaml:"name,omitempty" json:"name,omitempty"`
    71  	SnapID            string        `yaml:"snap-id" json:"snap-id"`
    72  	Revision          snap.Revision `yaml:"revision" json:"revision"`
    73  	Channel           string        `yaml:"channel,omitempty" json:"channel,omitempty"`
    74  	DeveloperID       string        `yaml:"developer-id,omitempty" json:"developer-id,omitempty"`
    75  	Developer         string        `yaml:"developer,omitempty" json:"developer,omitempty"`
    76  	EditedSummary     string        `yaml:"summary,omitempty" json:"summary,omitempty"`
    77  	EditedDescription string        `yaml:"description,omitempty" json:"description,omitempty"`
    78  	Private           bool          `yaml:"private,omitempty" json:"private,omitempty"`
    79  }
    80  
    81  type patch4SnapSetup struct {
    82  	Channel      string              `json:"channel,omitempty"`
    83  	UserID       int                 `json:"user-id,omitempty"`
    84  	Flags        patch4Flags         `json:"flags,omitempty"`
    85  	SnapPath     string              `json:"snap-path,omitempty"`
    86  	DownloadInfo *patch4DownloadInfo `json:"download-info,omitempty"`
    87  	SideInfo     *patch4SideInfo     `json:"side-info,omitempty"`
    88  }
    89  
    90  func (snapsup *patch4SnapSetup) Name() string {
    91  	if snapsup.SideInfo.RealName == "" {
    92  		panic("SnapSetup.SideInfo.RealName not set")
    93  	}
    94  	return snapsup.SideInfo.RealName
    95  }
    96  
    97  func (snapsup *patch4SnapSetup) Revision() snap.Revision {
    98  	return snapsup.SideInfo.Revision
    99  }
   100  
   101  type patch4SnapState struct {
   102  	SnapType string            `json:"type"` // Use Type and SetType
   103  	Sequence []*patch4SideInfo `json:"sequence"`
   104  	Active   bool              `json:"active,omitempty"`
   105  	Current  snap.Revision     `json:"current"`
   106  	Channel  string            `json:"channel,omitempty"`
   107  	Flags    patch4Flags       `json:"flags,omitempty"`
   108  }
   109  
   110  func (snapst *patch4SnapState) LastIndex(revision snap.Revision) int {
   111  	for i := len(snapst.Sequence) - 1; i >= 0; i-- {
   112  		if snapst.Sequence[i].Revision == revision {
   113  			return i
   114  		}
   115  	}
   116  	return -1
   117  }
   118  
   119  type patch4T struct{} // for namespacing of the helpers
   120  
   121  func (p4 patch4T) taskSnapSetup(task *state.Task) (*patch4SnapSetup, error) {
   122  	var snapsup patch4SnapSetup
   123  
   124  	switch err := p4.getMaybe(task, "snap-setup", &snapsup); err {
   125  	case state.ErrNoState:
   126  		// continue below
   127  	case nil:
   128  		return &snapsup, nil
   129  	default:
   130  		return nil, err
   131  	}
   132  
   133  	var id string
   134  	if err := p4.get(task, "snap-setup-task", &id); err != nil {
   135  		return nil, err
   136  	}
   137  
   138  	if err := p4.get(task.State().Task(id), "snap-setup", &snapsup); err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	return &snapsup, nil
   143  }
   144  
   145  var errNoSnapState = errors.New("no snap state")
   146  
   147  func (p4 patch4T) snapSetupAndState(task *state.Task) (*patch4SnapSetup, *patch4SnapState, error) {
   148  	var snapst patch4SnapState
   149  
   150  	snapsup, err := p4.taskSnapSetup(task)
   151  	if err != nil {
   152  		return nil, nil, err
   153  	}
   154  
   155  	var snaps map[string]*json.RawMessage
   156  	err = task.State().Get("snaps", &snaps)
   157  	if err != nil {
   158  		return nil, nil, errNoSnapState
   159  	}
   160  	raw, ok := snaps[snapsup.Name()]
   161  	if !ok {
   162  		return nil, nil, errNoSnapState
   163  	}
   164  	err = json.Unmarshal([]byte(*raw), &snapst)
   165  	if err != nil {
   166  		return nil, nil, fmt.Errorf("cannot get state for snap %q: %v", snapsup.Name(), err)
   167  	}
   168  
   169  	return snapsup, &snapst, err
   170  }
   171  
   172  // getMaybe calls task.Get and wraps any non-ErrNoState error in an informative message
   173  func (p4 patch4T) getMaybe(task *state.Task, key string, value interface{}) error {
   174  	return p4.gget(task, key, true, value)
   175  }
   176  
   177  // get calls task.Get and wraps any error in an informative message
   178  func (p4 patch4T) get(task *state.Task, key string, value interface{}) error {
   179  	return p4.gget(task, key, false, value)
   180  }
   181  
   182  // gget does the actual work of get and getMaybe
   183  func (patch4T) gget(task *state.Task, key string, passThroughMissing bool, value interface{}) error {
   184  	err := task.Get(key, value)
   185  	if err == nil || (passThroughMissing && err == state.ErrNoState) {
   186  		return err
   187  	}
   188  	change := task.Change()
   189  
   190  	return fmt.Errorf("cannot get %q from task %s (%s) of change %s (%s): %v",
   191  		key, task.ID(), task.Kind(), change.ID(), change.Kind(), err)
   192  }
   193  
   194  func (p4 patch4T) addCleanup(task *state.Task) error {
   195  	// NOTE we could check for the status of the change itself, but
   196  	// copy-snap-data is the one creating the trash, so if it's run there's
   197  	// no sense in fiddling with the change.
   198  	if task.Status().Ready() {
   199  		return nil
   200  	}
   201  
   202  	snapsup, err := p4.taskSnapSetup(task)
   203  	if err != nil {
   204  		return err
   205  	}
   206  
   207  	var tid string
   208  	if err := p4.get(task, "snap-setup-task", &tid); err != nil {
   209  		return err
   210  	}
   211  
   212  	change := task.Change()
   213  	revisionStr := ""
   214  	if snapsup.SideInfo != nil {
   215  		revisionStr = fmt.Sprintf(" (%s)", snapsup.Revision())
   216  	}
   217  
   218  	tasks := change.Tasks()
   219  	last := tasks[len(tasks)-1]
   220  	newTask := task.State().NewTask("cleanup", fmt.Sprintf("Clean up %q%s install", snapsup.Name(), revisionStr))
   221  	newTask.Set("snap-setup-task", tid)
   222  	newTask.WaitFor(last)
   223  	change.AddTask(newTask)
   224  
   225  	return nil
   226  }
   227  
   228  func (p4 patch4T) mangle(task *state.Task) error {
   229  	snapsup, snapst, err := p4.snapSetupAndState(task)
   230  	if err == errNoSnapState {
   231  		change := task.Change()
   232  		if change.Kind() != "install-snap" {
   233  			return fmt.Errorf("cannot get snap state for task %s (%s) of change %s (%s != install-snap)", task.ID(), task.Kind(), change.ID(), change.Kind())
   234  		}
   235  		// we expect pending/in-progress install changes
   236  		// possibly not to have reached link-sanp yet and so
   237  		// have no snap state yet, nothing to do
   238  		return nil
   239  	}
   240  	if err != nil {
   241  		return err
   242  	}
   243  
   244  	var hadCandidate bool
   245  	if err := p4.getMaybe(task, "had-candidate", &hadCandidate); err != nil && err != state.ErrNoState {
   246  		return err
   247  	}
   248  
   249  	if hadCandidate {
   250  		change := task.Change()
   251  		if change.Kind() != "revert-snap" {
   252  			return fmt.Errorf("had-candidate true for task %s (%s) of non-revert change %s (%s)",
   253  				task.ID(), task.Kind(), change.ID(), change.Kind())
   254  		}
   255  	}
   256  
   257  	task.Clear("had-candidate")
   258  
   259  	task.Set("old-candidate-index", snapst.LastIndex(snapsup.SideInfo.Revision))
   260  
   261  	return nil
   262  }
   263  
   264  func (p4 patch4T) addRevertFlag(task *state.Task) error {
   265  	var snapsup patch4SnapSetup
   266  	err := p4.getMaybe(task, "snap-setup", &snapsup)
   267  	switch err {
   268  	case nil:
   269  		snapsup.Flags |= patch4FlagRevert
   270  
   271  		// save it back
   272  		task.Set("snap-setup", &snapsup)
   273  		return nil
   274  	case state.ErrNoState:
   275  		return nil
   276  	default:
   277  		return err
   278  	}
   279  }
   280  
   281  // patch4:
   282  //  - add Revert flag to in-progress revert-snap changes
   283  //  - move from had-candidate to old-candidate-index in link-snap tasks
   284  //  - add cleanup task to in-progress changes that have a copy-snap-data task
   285  func patch4(s *state.State) error {
   286  	p4 := patch4T{}
   287  	for _, change := range s.Changes() {
   288  		// change is full done, take it easy
   289  		if change.Status().Ready() {
   290  			continue
   291  		}
   292  
   293  		if change.Kind() != "revert-snap" {
   294  			continue
   295  		}
   296  		for _, task := range change.Tasks() {
   297  			if err := p4.addRevertFlag(task); err != nil {
   298  				return err
   299  			}
   300  		}
   301  	}
   302  
   303  	for _, task := range s.Tasks() {
   304  		// change is full done, take it easy
   305  		if task.Change().Status().Ready() {
   306  			continue
   307  		}
   308  
   309  		switch task.Kind() {
   310  		case "link-snap":
   311  			if err := p4.mangle(task); err != nil {
   312  				return err
   313  			}
   314  		case "copy-snap-data":
   315  			if err := p4.addCleanup(task); err != nil {
   316  				return err
   317  			}
   318  		}
   319  	}
   320  
   321  	return nil
   322  }