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 }