gopkg.in/ubuntu-core/snappy.v0@v0.0.0-20210902073436-25a8614f10a6/overlord/configstate/configstate.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 configstate implements the manager and state aspects responsible for
    21  // the configuration of snaps.
    22  package configstate
    23  
    24  import (
    25  	"fmt"
    26  	"os"
    27  	"time"
    28  
    29  	"github.com/snapcore/snapd/gadget"
    30  	"github.com/snapcore/snapd/i18n"
    31  	"github.com/snapcore/snapd/overlord/configstate/config"
    32  	"github.com/snapcore/snapd/overlord/configstate/configcore"
    33  	"github.com/snapcore/snapd/overlord/devicestate"
    34  	"github.com/snapcore/snapd/overlord/hookstate"
    35  	"github.com/snapcore/snapd/overlord/snapstate"
    36  	"github.com/snapcore/snapd/overlord/state"
    37  	"github.com/snapcore/snapd/snap"
    38  	"github.com/snapcore/snapd/sysconfig"
    39  )
    40  
    41  func init() {
    42  	snapstate.Configure = Configure
    43  }
    44  
    45  func ConfigureHookTimeout() time.Duration {
    46  	timeout := 5 * time.Minute
    47  	if s := os.Getenv("SNAPD_CONFIGURE_HOOK_TIMEOUT"); s != "" {
    48  		if to, err := time.ParseDuration(s); err == nil {
    49  			timeout = to
    50  		}
    51  	}
    52  	return timeout
    53  }
    54  
    55  func canConfigure(st *state.State, snapName string) error {
    56  	// the "core" snap/pseudonym can always be configured as it
    57  	// is handled internally
    58  	if snapName == "core" {
    59  		return nil
    60  	}
    61  
    62  	var snapst snapstate.SnapState
    63  	err := snapstate.Get(st, snapName, &snapst)
    64  	if err != nil && err != state.ErrNoState {
    65  		return err
    66  	}
    67  
    68  	if !snapst.IsInstalled() {
    69  		return &snap.NotInstalledError{Snap: snapName}
    70  	}
    71  
    72  	// the "snapd" snap cannot be configured yet
    73  	typ, err := snapst.Type()
    74  	if err != nil {
    75  		return err
    76  	}
    77  	if typ == snap.TypeSnapd {
    78  		return fmt.Errorf(`cannot configure the "snapd" snap, please use "system" instead`)
    79  	}
    80  
    81  	// bases cannot be configured for now
    82  	typ, err = snapst.Type()
    83  	if err != nil {
    84  		return err
    85  	}
    86  	if typ == snap.TypeBase {
    87  		return fmt.Errorf("cannot configure snap %q because it is of type 'base'", snapName)
    88  	}
    89  
    90  	return snapstate.CheckChangeConflict(st, snapName, nil)
    91  }
    92  
    93  // ConfigureInstalled returns a taskset to apply the given
    94  // configuration patch for an installed snap. It returns
    95  // snap.NotInstalledError if the snap is not installed.
    96  func ConfigureInstalled(st *state.State, snapName string, patch map[string]interface{}, flags int) (*state.TaskSet, error) {
    97  	if err := canConfigure(st, snapName); err != nil {
    98  		return nil, err
    99  	}
   100  
   101  	taskset := Configure(st, snapName, patch, flags)
   102  	return taskset, nil
   103  }
   104  
   105  // Configure returns a taskset to apply the given configuration patch.
   106  func Configure(st *state.State, snapName string, patch map[string]interface{}, flags int) *state.TaskSet {
   107  	summary := fmt.Sprintf(i18n.G("Run configure hook of %q snap"), snapName)
   108  	// regular configuration hook
   109  	hooksup := &hookstate.HookSetup{
   110  		Snap:        snapName,
   111  		Hook:        "configure",
   112  		Optional:    len(patch) == 0,
   113  		IgnoreError: flags&snapstate.IgnoreHookError != 0,
   114  		TrackError:  flags&snapstate.TrackHookError != 0,
   115  		// all configure hooks must finish within this timeout
   116  		Timeout: ConfigureHookTimeout(),
   117  	}
   118  	var contextData map[string]interface{}
   119  	if flags&snapstate.UseConfigDefaults != 0 {
   120  		contextData = map[string]interface{}{"use-defaults": true}
   121  	} else if len(patch) > 0 {
   122  		contextData = map[string]interface{}{"patch": patch}
   123  	}
   124  
   125  	if hooksup.Optional {
   126  		summary = fmt.Sprintf(i18n.G("Run configure hook of %q snap if present"), snapName)
   127  	}
   128  
   129  	task := hookstate.HookTask(st, summary, hooksup, contextData)
   130  	return state.NewTaskSet(task)
   131  }
   132  
   133  // RemapSnapFromRequest renames a snap as received from an API request
   134  func RemapSnapFromRequest(snapName string) string {
   135  	if snapName == "system" {
   136  		return "core"
   137  	}
   138  	return snapName
   139  }
   140  
   141  // RemapSnapToResponse renames a snap as about to be sent from an API response
   142  func RemapSnapToResponse(snapName string) string {
   143  	if snapName == "core" {
   144  		return "system"
   145  	}
   146  	return snapName
   147  }
   148  
   149  func delayedCrossMgrInit() {
   150  	devicestate.EarlyConfig = EarlyConfig
   151  }
   152  
   153  var (
   154  	configcoreExportExperimentalFlags = configcore.ExportExperimentalFlags
   155  	configcoreEarly                   = configcore.Early
   156  )
   157  
   158  // EarlyConfig performs any needed early configuration handling during
   159  // managers' startup, it is exposed as a hook to devicestate for invocation.
   160  // preloadGadget if set will be invoked if the system is not yet seeded
   161  // or configured, it should either return ErrNoState, or return
   162  // the gadget.Info for the to-be-seeded gadget and details about
   163  // the model/device as sysconfig.Device.
   164  func EarlyConfig(st *state.State, preloadGadget func() (sysconfig.Device, *gadget.Info, error)) error {
   165  	// already configured
   166  	configed, err := systemAlreadyConfigured(st)
   167  	if err != nil {
   168  		return err
   169  	}
   170  	tr := config.NewTransaction(st)
   171  	if configed {
   172  		if err := configcoreExportExperimentalFlags(tr); err != nil {
   173  			return fmt.Errorf("cannot export experimental config flags: %v", err)
   174  		}
   175  		return nil
   176  	}
   177  	if preloadGadget != nil {
   178  		dev, gi, err := preloadGadget()
   179  		if err != nil {
   180  			if err == state.ErrNoState {
   181  				// nothing to do
   182  				return nil
   183  			}
   184  			return err
   185  		}
   186  		values := gadget.SystemDefaults(gi.Defaults)
   187  		if err := configcoreEarly(dev, tr, values); err != nil {
   188  			return err
   189  		}
   190  		tr.Commit()
   191  	}
   192  	return nil
   193  }
   194  
   195  func systemAlreadyConfigured(st *state.State) (bool, error) {
   196  	var seeded bool
   197  	if err := st.Get("seeded", &seeded); err != nil && err != state.ErrNoState {
   198  		return false, err
   199  	}
   200  	if seeded {
   201  		return true, nil
   202  	}
   203  	cfg, err := config.GetSnapConfig(st, "core")
   204  	if cfg != nil {
   205  		return true, nil
   206  	}
   207  	return false, err
   208  }