github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/environs/bootstrap/prepare.go (about) 1 // Copyright 2011-2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package bootstrap 5 6 import ( 7 "github.com/juju/errors" 8 "github.com/juju/featureflag" 9 "github.com/juju/names/v5" 10 11 "github.com/juju/juju/caas" 12 "github.com/juju/juju/controller" 13 "github.com/juju/juju/core/model" 14 "github.com/juju/juju/core/permission" 15 "github.com/juju/juju/environs" 16 environscloudspec "github.com/juju/juju/environs/cloudspec" 17 "github.com/juju/juju/environs/config" 18 "github.com/juju/juju/feature" 19 "github.com/juju/juju/jujuclient" 20 ) 21 22 const ( 23 // ControllerModelName is the name of the admin model in each controller. 24 ControllerModelName = "controller" 25 26 // ControllerCharmName is the name of the controller charm. 27 ControllerCharmName = "juju-controller" 28 29 // ControllerApplicationName is the name of the controller application. 30 ControllerApplicationName = "controller" 31 32 // ControllerCharmArchive is the name of the controller charm archive. 33 ControllerCharmArchive = "controller.charm" 34 ) 35 36 // PrepareParams contains the parameters for preparing a controller Environ 37 // for bootstrapping. 38 type PrepareParams struct { 39 // ModelConfig contains the base configuration for the controller model. 40 // 41 // This includes the model name, cloud type, any user-supplied 42 // configuration, config inherited from controller, and any defaults. 43 ModelConfig map[string]interface{} 44 45 // ControllerConfig is the configuration of the controller being prepared. 46 ControllerConfig controller.Config 47 48 // ControllerName is the name of the controller being prepared. 49 ControllerName string 50 51 // Cloud is the specification of the cloud that the controller is 52 // being prepared for. 53 Cloud environscloudspec.CloudSpec 54 55 // CredentialName is the name of the credential to use to bootstrap. 56 // This will be empty for auto-detected credentials. 57 CredentialName string 58 59 // AdminSecret contains the password for the admin user. 60 AdminSecret string 61 } 62 63 // Validate validates the PrepareParams. 64 func (p PrepareParams) Validate() error { 65 if err := p.ControllerConfig.Validate(); err != nil { 66 return errors.Annotate(err, "validating controller config") 67 } 68 if p.ControllerName == "" { 69 return errors.NotValidf("empty controller name") 70 } 71 if p.Cloud.Name == "" { 72 return errors.NotValidf("empty cloud name") 73 } 74 if p.AdminSecret == "" { 75 return errors.NotValidf("empty admin-secret") 76 } 77 return nil 78 } 79 80 // PrepareController prepares a new controller based on the provided configuration. 81 // It is an error to prepare a controller if there already exists an 82 // entry in the client store with the same name. 83 // 84 // Upon success, Prepare will update the ClientStore with the details of 85 // the controller, admin account, and admin model. 86 func PrepareController( 87 isCAASController bool, 88 ctx environs.BootstrapContext, 89 store jujuclient.ClientStore, 90 args PrepareParams, 91 ) (environs.BootstrapEnviron, error) { 92 93 if err := args.Validate(); err != nil { 94 return nil, errors.Trace(err) 95 } 96 97 _, err := store.ControllerByName(args.ControllerName) 98 if err == nil { 99 return nil, errors.AlreadyExistsf("controller %q", args.ControllerName) 100 } else if !errors.IsNotFound(err) { 101 return nil, errors.Annotatef(err, "error reading controller %q info", args.ControllerName) 102 } 103 104 cloudType, ok := args.ModelConfig["type"].(string) 105 if !ok { 106 return nil, errors.NotFoundf("cloud type in base configuration") 107 } 108 109 p, err := environs.Provider(cloudType) 110 if err != nil { 111 return nil, errors.Trace(err) 112 } 113 114 cfg, details, err := prepare(ctx, p, args) 115 if err != nil { 116 return nil, errors.Trace(err) 117 } 118 119 do := func() error { 120 if err := decorateAndWriteInfo( 121 store, details, args.ControllerName, cfg.Name(), 122 ); err != nil { 123 return errors.Annotatef(err, "cannot create controller %q info", args.ControllerName) 124 } 125 return nil 126 } 127 128 var env environs.BootstrapEnviron 129 openParams := environs.OpenParams{ 130 ControllerUUID: args.ControllerConfig.ControllerUUID(), 131 Cloud: args.Cloud, 132 Config: cfg, 133 } 134 if isCAASController { 135 details.ModelType = model.CAAS 136 env, err = caas.Open(ctx.Context(), p, openParams) 137 } else { 138 details.ModelType = model.IAAS 139 env, err = environs.Open(ctx.Context(), p, openParams) 140 } 141 if err != nil { 142 return nil, errors.Trace(err) 143 } 144 if err := env.PrepareForBootstrap(ctx, args.ControllerName); err != nil { 145 return nil, errors.Trace(err) 146 } 147 if err := do(); err != nil { 148 return nil, errors.Trace(err) 149 } 150 return env, nil 151 } 152 153 // decorateAndWriteInfo decorates the info struct with information 154 // from the given cfg, and the writes that out to the filesystem. 155 func decorateAndWriteInfo( 156 store jujuclient.ClientStore, 157 details prepareDetails, 158 controllerName, modelName string, 159 ) error { 160 qualifiedModelName := jujuclient.JoinOwnerModelName( 161 names.NewUserTag(details.AccountDetails.User), 162 modelName, 163 ) 164 if err := store.AddController(controllerName, details.ControllerDetails); err != nil { 165 return errors.Trace(err) 166 } 167 if err := store.UpdateBootstrapConfig(controllerName, details.BootstrapConfig); err != nil { 168 return errors.Trace(err) 169 } 170 if err := store.UpdateAccount(controllerName, details.AccountDetails); err != nil { 171 return errors.Trace(err) 172 } 173 if err := store.UpdateModel(controllerName, qualifiedModelName, details.ModelDetails); err != nil { 174 return errors.Trace(err) 175 } 176 if err := store.SetCurrentModel(controllerName, qualifiedModelName); err != nil { 177 return errors.Trace(err) 178 } 179 return nil 180 } 181 182 func prepare( 183 ctx environs.BootstrapContext, 184 p environs.EnvironProvider, 185 args PrepareParams, 186 ) (*config.Config, prepareDetails, error) { 187 var details prepareDetails 188 189 cfg, err := config.New(config.NoDefaults, args.ModelConfig) 190 if err != nil { 191 return cfg, details, errors.Trace(err) 192 } 193 194 cfg, err = p.PrepareConfig(environs.PrepareConfigParams{Cloud: args.Cloud, Config: cfg}) 195 if err != nil { 196 return cfg, details, errors.Trace(err) 197 } 198 199 // We store the base configuration only; we don't want the 200 // default attributes, generated secrets/certificates, or 201 // UUIDs stored in the bootstrap config. Make a copy, so 202 // we don't disturb the caller's config map. 203 details.Config = make(map[string]interface{}) 204 for k, v := range args.ModelConfig { 205 details.Config[k] = v 206 } 207 delete(details.Config, config.UUIDKey) 208 209 // TODO(axw) change signature of CACert() to not return a bool. 210 // It's no longer possible to have a controller config without 211 // a CA certificate. 212 caCert, ok := args.ControllerConfig.CACert() 213 if !ok { 214 return cfg, details, errors.New("controller config is missing CA certificate") 215 } 216 217 // We want to store attributes describing how a controller has been configured. 218 // These do not include the CACert or UUID since they will be replaced with new 219 // values when/if we need to use this configuration. 220 details.ControllerConfig = make(controller.Config) 221 for k, v := range args.ControllerConfig { 222 if k == controller.CACertKey || k == controller.ControllerUUIDKey { 223 continue 224 } 225 details.ControllerConfig[k] = v 226 } 227 for k, v := range args.ControllerConfig { 228 if k == controller.CACertKey || k == controller.ControllerUUIDKey { 229 continue 230 } 231 details.ControllerConfig[k] = v 232 } 233 details.CACert = caCert 234 details.ControllerUUID = args.ControllerConfig.ControllerUUID() 235 details.ControllerModelUUID = args.ModelConfig[config.UUIDKey].(string) 236 details.User = environs.AdminUser 237 details.Password = args.AdminSecret 238 details.LastKnownAccess = string(permission.SuperuserAccess) 239 details.ModelUUID = cfg.UUID() 240 if featureflag.Enabled(feature.Branches) || featureflag.Enabled(feature.Generations) { 241 details.ActiveBranch = model.GenerationMaster 242 } 243 details.ControllerDetails.Cloud = args.Cloud.Name 244 details.ControllerDetails.CloudRegion = args.Cloud.Region 245 details.ControllerDetails.CloudType = args.Cloud.Type 246 details.BootstrapConfig.CloudType = args.Cloud.Type 247 details.BootstrapConfig.Cloud = args.Cloud.Name 248 details.BootstrapConfig.CloudRegion = args.Cloud.Region 249 details.BootstrapConfig.CloudCACertificates = args.Cloud.CACertificates 250 details.BootstrapConfig.SkipTLSVerify = args.Cloud.SkipTLSVerify 251 details.CloudEndpoint = args.Cloud.Endpoint 252 details.CloudIdentityEndpoint = args.Cloud.IdentityEndpoint 253 details.CloudStorageEndpoint = args.Cloud.StorageEndpoint 254 details.Credential = args.CredentialName 255 256 if args.Cloud.SkipTLSVerify { 257 if len(args.Cloud.CACertificates) > 0 && args.Cloud.CACertificates[0] != "" { 258 return cfg, details, errors.NotValidf("cloud with both skip-TLS-verify=true and CA certificates") 259 } 260 logger.Warningf("controller %v is configured to skip validity checks on the server's certificate", args.ControllerName) 261 } 262 263 return cfg, details, nil 264 } 265 266 type prepareDetails struct { 267 jujuclient.ControllerDetails 268 jujuclient.BootstrapConfig 269 jujuclient.AccountDetails 270 jujuclient.ModelDetails 271 }