github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/manual/provider.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package manual 5 6 import ( 7 "strings" 8 9 "github.com/juju/errors" 10 "github.com/juju/jsonschema" 11 12 "github.com/juju/juju/cloud" 13 "github.com/juju/juju/environs" 14 "github.com/juju/juju/environs/config" 15 "github.com/juju/juju/environs/context" 16 "github.com/juju/juju/environs/manual/sshprovisioner" 17 ) 18 19 // ManualProvider contains the logic for using a random ubuntu machine as a 20 // controller, connected via SSH. 21 type ManualProvider struct { 22 environProviderCredentials 23 ping func(endpoint string) error 24 } 25 26 // Verify that we conform to the interface. 27 var _ environs.EnvironProvider = (*ManualProvider)(nil) 28 29 var initUbuntuUser = sshprovisioner.InitUbuntuUser 30 31 func ensureBootstrapUbuntuUser(ctx environs.BootstrapContext, host, user string, cfg *environConfig) error { 32 err := initUbuntuUser(host, user, cfg.AuthorizedKeys(), ctx.GetStdin(), ctx.GetStdout()) 33 if err != nil { 34 logger.Errorf("initializing ubuntu user: %v", err) 35 return err 36 } 37 logger.Infof("initialized ubuntu user") 38 return nil 39 } 40 41 // DetectRegions is specified in the environs.CloudRegionDetector interface. 42 func (p ManualProvider) DetectRegions() ([]cloud.Region, error) { 43 return nil, errors.NotFoundf("regions") 44 } 45 46 var cloudSchema = &jsonschema.Schema{ 47 Type: []jsonschema.Type{jsonschema.ObjectType}, 48 Required: []string{cloud.EndpointKey}, 49 Properties: map[string]*jsonschema.Schema{ 50 cloud.EndpointKey: { 51 Singular: "the controller's hostname or IP address", 52 Type: []jsonschema.Type{jsonschema.StringType}, 53 Format: jsonschema.FormatURI, 54 }, 55 }, 56 } 57 58 // CloudSchema returns the schema for verifying the cloud configuration. 59 func (p ManualProvider) CloudSchema() *jsonschema.Schema { 60 return cloudSchema 61 } 62 63 // Ping tests the connection to the cloud, to verify the endpoint is valid. 64 func (p ManualProvider) Ping(ctx context.ProviderCallContext, endpoint string) error { 65 if p.ping != nil { 66 return p.ping(endpoint) 67 } 68 return pingMachine(endpoint) 69 } 70 71 // pingMachine is what is used in production by ManualProvider.Ping(). 72 // It does nothing at the moment. 73 func pingMachine(endpoint string) error { 74 // (anastasiamac 2017-03-30) This method was introduced to verify 75 // manual endpoint by attempting to SSH into it. 76 // However, what we really wanted to do was to determine if 77 // we could connect to the endpoint not whether we could authenticate. 78 // In other words, we wanted to ignore authentication errors. 79 // These errors, at verification stage, when adding cloud details, are meaningless 80 // since authentication is configurable at bootstrap. 81 // With OpenSSH and crypto/ssh, both underlying current SSH client implementations, it is not 82 // possible to cleanly distinguish between authentication and connection failures 83 // without examining error string and looking for various matches. 84 // This feels dirty and flaky as the error messages can easily change 85 // between different libraries and their versions. 86 // So, it has been decided to just accept endpoint. 87 // If this ping(..) call will be used for other purposes, this decision may 88 // need to be re-visited. 89 return nil 90 } 91 92 // PrepareConfig is specified in the EnvironProvider interface. 93 func (p ManualProvider) PrepareConfig(args environs.PrepareConfigParams) (*config.Config, error) { 94 if err := validateCloudSpec(args.Cloud); err != nil { 95 return nil, errors.Trace(err) 96 } 97 envConfig, err := p.validate(args.Config, nil) 98 if err != nil { 99 return nil, err 100 } 101 return args.Config.Apply(envConfig.attrs) 102 } 103 104 // Version is part of the EnvironProvider interface. 105 func (ManualProvider) Version() int { 106 return 0 107 } 108 109 func (p ManualProvider) Open(args environs.OpenParams) (environs.Environ, error) { 110 if err := validateCloudSpec(args.Cloud); err != nil { 111 return nil, errors.Trace(err) 112 } 113 _, err := p.validate(args.Config, nil) 114 if err != nil { 115 return nil, err 116 } 117 // validate adds missing manual-specific config attributes 118 // with their defaults in the result; we don't want that in 119 // Open. 120 envConfig := newModelConfig(args.Config, args.Config.UnknownAttrs()) 121 host, user := args.Cloud.Endpoint, "" 122 if i := strings.IndexRune(host, '@'); i >= 0 { 123 user, host = host[:i], host[i+1:] 124 } 125 return p.open(host, user, envConfig) 126 } 127 128 func validateCloudSpec(spec environs.CloudSpec) error { 129 if spec.Endpoint == "" { 130 return errors.Errorf( 131 "missing address of host to bootstrap: " + 132 `please specify "juju bootstrap manual/[user@]<host>"`, 133 ) 134 } 135 return nil 136 } 137 138 func (p ManualProvider) open(host, user string, cfg *environConfig) (environs.Environ, error) { 139 env := &manualEnviron{host: host, user: user, cfg: cfg} 140 // Need to call SetConfig to initialise storage. 141 if err := env.SetConfig(cfg.Config); err != nil { 142 return nil, err 143 } 144 return env, nil 145 } 146 147 func (p ManualProvider) validate(cfg, old *config.Config) (*environConfig, error) { 148 // Check for valid changes for the base config values. 149 if err := config.Validate(cfg, old); err != nil { 150 return nil, err 151 } 152 validated, err := cfg.ValidateUnknownAttrs(configFields, configDefaults) 153 if err != nil { 154 return nil, err 155 } 156 envConfig := newModelConfig(cfg, validated) 157 158 // If the user hasn't already specified a value, set it to the 159 // given value. 160 defineIfNot := func(keyName string, value interface{}) { 161 if _, defined := cfg.AllAttrs()[keyName]; !defined { 162 logger.Infof("%s was not defined. Defaulting to %v.", keyName, value) 163 envConfig.attrs[keyName] = value 164 } 165 } 166 167 // If the user hasn't specified a value, refresh the 168 // available updates, but don't upgrade. 169 defineIfNot("enable-os-refresh-update", true) 170 defineIfNot("enable-os-upgrade", false) 171 172 return envConfig, nil 173 } 174 175 func (p ManualProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) { 176 envConfig, err := p.validate(cfg, old) 177 if err != nil { 178 return nil, err 179 } 180 return cfg.Apply(envConfig.attrs) 181 }