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