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 }