github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/provider/openstack/config.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package openstack
     5  
     6  import (
     7  	"fmt"
     8  	"net/url"
     9  
    10  	"github.com/juju/schema"
    11  	"gopkg.in/goose.v1/identity"
    12  	"gopkg.in/juju/environschema.v1"
    13  
    14  	"github.com/juju/juju/environs/config"
    15  )
    16  
    17  var configSchema = environschema.Fields{
    18  	"username": {
    19  		Description: "The user name  to use when auth-mode is userpass.",
    20  		Type:        environschema.Tstring,
    21  		EnvVars:     identity.CredEnvUser,
    22  		Group:       environschema.AccountGroup,
    23  	},
    24  	"password": {
    25  		Description: "The password to use when auth-mode is userpass.",
    26  		Type:        environschema.Tstring,
    27  		EnvVars:     identity.CredEnvSecrets,
    28  		Group:       environschema.AccountGroup,
    29  		Secret:      true,
    30  	},
    31  	"tenant-name": {
    32  		Description: "The openstack tenant name.",
    33  		Type:        environschema.Tstring,
    34  		EnvVars:     identity.CredEnvTenantName,
    35  		Group:       environschema.AccountGroup,
    36  	},
    37  	"auth-url": {
    38  		Description: "The keystone URL for authentication.",
    39  		Type:        environschema.Tstring,
    40  		EnvVars:     identity.CredEnvAuthURL,
    41  		Example:     "https://yourkeystoneurl:443/v2.0/",
    42  		Group:       environschema.AccountGroup,
    43  	},
    44  	"auth-mode": {
    45  		Description: "The authentication mode to use. When set to keypair, the access-key and secret-key parameters should be set; when set to userpass or legacy, the username and password parameters should be set.",
    46  		Type:        environschema.Tstring,
    47  		Values:      []interface{}{AuthKeyPair, AuthLegacy, AuthUserPass},
    48  		Group:       environschema.AccountGroup,
    49  	},
    50  	"access-key": {
    51  		Description: "The access key to use when auth-mode is set to keypair.",
    52  		Type:        environschema.Tstring,
    53  		EnvVars:     identity.CredEnvUser,
    54  		Group:       environschema.AccountGroup,
    55  		Secret:      true,
    56  	},
    57  	"secret-key": {
    58  		Description: "The secret key to use when auth-mode is set to keypair.",
    59  		EnvVars:     identity.CredEnvSecrets,
    60  		Group:       environschema.AccountGroup,
    61  		Type:        environschema.Tstring,
    62  		Secret:      true,
    63  	},
    64  	"region": {
    65  		Description: "The openstack region.",
    66  		Type:        environschema.Tstring,
    67  		EnvVars:     identity.CredEnvRegion,
    68  	},
    69  	"control-bucket": {
    70  		Description: "The name to use for the control bucket (do not set unless you know what you are doing!).",
    71  		Type:        environschema.Tstring,
    72  	},
    73  	"use-floating-ip": {
    74  		Description: "Whether a floating IP address is required to give the nodes a public IP address. Some installations assign public IP addresses by default without requiring a floating IP address.",
    75  		Type:        environschema.Tbool,
    76  	},
    77  	"use-default-secgroup": {
    78  		Description: `Whether new machine instances should have the "default" Openstack security group assigned.`,
    79  		Type:        environschema.Tbool,
    80  	},
    81  	"network": {
    82  		Description: "The network label or UUID to bring machines up on when multiple networks exist.",
    83  		Type:        environschema.Tstring,
    84  	},
    85  }
    86  
    87  var configFields = func() schema.Fields {
    88  	fs, _, err := configSchema.ValidationSchema()
    89  	if err != nil {
    90  		panic(err)
    91  	}
    92  	return fs
    93  }()
    94  
    95  var configDefaults = schema.Defaults{
    96  	"username":             "",
    97  	"password":             "",
    98  	"tenant-name":          "",
    99  	"auth-url":             "",
   100  	"auth-mode":            string(AuthUserPass),
   101  	"access-key":           "",
   102  	"secret-key":           "",
   103  	"region":               "",
   104  	"control-bucket":       "",
   105  	"use-floating-ip":      false,
   106  	"use-default-secgroup": false,
   107  	"network":              "",
   108  }
   109  
   110  type environConfig struct {
   111  	*config.Config
   112  	attrs map[string]interface{}
   113  }
   114  
   115  func (c *environConfig) region() string {
   116  	return c.attrs["region"].(string)
   117  }
   118  
   119  func (c *environConfig) username() string {
   120  	return c.attrs["username"].(string)
   121  }
   122  
   123  func (c *environConfig) password() string {
   124  	return c.attrs["password"].(string)
   125  }
   126  
   127  func (c *environConfig) tenantName() string {
   128  	return c.attrs["tenant-name"].(string)
   129  }
   130  
   131  func (c *environConfig) authURL() string {
   132  	return c.attrs["auth-url"].(string)
   133  }
   134  
   135  func (c *environConfig) authMode() AuthMode {
   136  	return AuthMode(c.attrs["auth-mode"].(string))
   137  }
   138  
   139  func (c *environConfig) accessKey() string {
   140  	return c.attrs["access-key"].(string)
   141  }
   142  
   143  func (c *environConfig) secretKey() string {
   144  	return c.attrs["secret-key"].(string)
   145  }
   146  
   147  func (c *environConfig) controlBucket() string {
   148  	return c.attrs["control-bucket"].(string)
   149  }
   150  
   151  func (c *environConfig) useFloatingIP() bool {
   152  	return c.attrs["use-floating-ip"].(bool)
   153  }
   154  
   155  func (c *environConfig) useDefaultSecurityGroup() bool {
   156  	return c.attrs["use-default-secgroup"].(bool)
   157  }
   158  
   159  func (c *environConfig) network() string {
   160  	return c.attrs["network"].(string)
   161  }
   162  
   163  func (p environProvider) newConfig(cfg *config.Config) (*environConfig, error) {
   164  	valid, err := p.Validate(cfg, nil)
   165  	if err != nil {
   166  		return nil, err
   167  	}
   168  	return &environConfig{valid, valid.UnknownAttrs()}, nil
   169  }
   170  
   171  type AuthMode string
   172  
   173  const (
   174  	AuthKeyPair  AuthMode = "keypair"
   175  	AuthLegacy   AuthMode = "legacy"
   176  	AuthUserPass AuthMode = "userpass"
   177  )
   178  
   179  // Schema returns the configuration schema for an environment.
   180  func (environProvider) Schema() environschema.Fields {
   181  	fields, err := config.Schema(configSchema)
   182  	if err != nil {
   183  		panic(err)
   184  	}
   185  	return fields
   186  }
   187  
   188  func (p environProvider) Validate(cfg, old *config.Config) (valid *config.Config, err error) {
   189  	// Check for valid changes for the base config values.
   190  	if err := config.Validate(cfg, old); err != nil {
   191  		return nil, err
   192  	}
   193  
   194  	validated, err := cfg.ValidateUnknownAttrs(configFields, configDefaults)
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  
   199  	// Add Openstack specific defaults.
   200  	providerDefaults := make(map[string]interface{})
   201  
   202  	// Storage.
   203  	if _, ok := cfg.StorageDefaultBlockSource(); !ok {
   204  		providerDefaults[config.StorageDefaultBlockSourceKey] = CinderProviderType
   205  	}
   206  	if len(providerDefaults) > 0 {
   207  		if cfg, err = cfg.Apply(providerDefaults); err != nil {
   208  			return nil, err
   209  		}
   210  	}
   211  
   212  	ecfg := &environConfig{cfg, validated}
   213  
   214  	if ecfg.authURL() != "" {
   215  		parts, err := url.Parse(ecfg.authURL())
   216  		if err != nil || parts.Host == "" || parts.Scheme == "" {
   217  			return nil, fmt.Errorf("invalid auth-url value %q", ecfg.authURL())
   218  		}
   219  	}
   220  	cred := identity.CredentialsFromEnv()
   221  	format := "required environment variable not set for credentials attribute: %s"
   222  	switch ecfg.authMode() {
   223  	case AuthUserPass, AuthLegacy:
   224  		if ecfg.username() == "" {
   225  			if cred.User == "" {
   226  				return nil, fmt.Errorf(format, "User")
   227  			}
   228  			ecfg.attrs["username"] = cred.User
   229  		}
   230  		if ecfg.password() == "" {
   231  			if cred.Secrets == "" {
   232  				return nil, fmt.Errorf(format, "Secrets")
   233  			}
   234  			ecfg.attrs["password"] = cred.Secrets
   235  		}
   236  	case AuthKeyPair:
   237  		if ecfg.accessKey() == "" {
   238  			if cred.User == "" {
   239  				return nil, fmt.Errorf(format, "User")
   240  			}
   241  			ecfg.attrs["access-key"] = cred.User
   242  		}
   243  		if ecfg.secretKey() == "" {
   244  			if cred.Secrets == "" {
   245  				return nil, fmt.Errorf(format, "Secrets")
   246  			}
   247  			ecfg.attrs["secret-key"] = cred.Secrets
   248  		}
   249  	default:
   250  		return nil, fmt.Errorf("unexpected authentication mode %q", ecfg.authMode())
   251  	}
   252  	if ecfg.authURL() == "" {
   253  		if cred.URL == "" {
   254  			return nil, fmt.Errorf(format, "URL")
   255  		}
   256  		ecfg.attrs["auth-url"] = cred.URL
   257  	}
   258  	if ecfg.tenantName() == "" {
   259  		if cred.TenantName == "" {
   260  			return nil, fmt.Errorf(format, "TenantName")
   261  		}
   262  		ecfg.attrs["tenant-name"] = cred.TenantName
   263  	}
   264  	if ecfg.region() == "" {
   265  		if cred.Region == "" {
   266  			return nil, fmt.Errorf(format, "Region")
   267  		}
   268  		ecfg.attrs["region"] = cred.Region
   269  	}
   270  
   271  	if old != nil {
   272  		attrs := old.UnknownAttrs()
   273  		if region, _ := attrs["region"].(string); ecfg.region() != region {
   274  			return nil, fmt.Errorf("cannot change region from %q to %q", region, ecfg.region())
   275  		}
   276  		if controlBucket, _ := attrs["control-bucket"].(string); ecfg.controlBucket() != controlBucket {
   277  			return nil, fmt.Errorf("cannot change control-bucket from %q to %q", controlBucket, ecfg.controlBucket())
   278  		}
   279  	}
   280  
   281  	// Check for deprecated fields and log a warning. We also print to stderr to ensure the user sees the message
   282  	// even if they are not running with --debug.
   283  	cfgAttrs := cfg.AllAttrs()
   284  	if defaultImageId := cfgAttrs["default-image-id"]; defaultImageId != nil && defaultImageId.(string) != "" {
   285  		msg := fmt.Sprintf(
   286  			"Config attribute %q (%v) is deprecated and ignored.\n"+
   287  				"Your cloud provider should have set up image metadata to provide the correct image id\n"+
   288  				"for your chosen series and archietcure. If this is a private Openstack deployment without\n"+
   289  				"existing image metadata, please run 'juju-metadata help' to see how suitable image"+
   290  				"metadata can be generated.",
   291  			"default-image-id", defaultImageId)
   292  		logger.Warningf(msg)
   293  	}
   294  	if defaultInstanceType := cfgAttrs["default-instance-type"]; defaultInstanceType != nil && defaultInstanceType.(string) != "" {
   295  		msg := fmt.Sprintf(
   296  			"Config attribute %q (%v) is deprecated and ignored.\n"+
   297  				"The correct instance flavor is determined using constraints, globally specified\n"+
   298  				"when an environment is bootstrapped, or individually when a charm is deployed.\n"+
   299  				"See 'juju help bootstrap' or 'juju help deploy'.",
   300  			"default-instance-type", defaultInstanceType)
   301  		logger.Warningf(msg)
   302  	}
   303  	// Construct a new config with the deprecated attributes removed.
   304  	for _, attr := range []string{"default-image-id", "default-instance-type"} {
   305  		delete(cfgAttrs, attr)
   306  		delete(ecfg.attrs, attr)
   307  	}
   308  	for k, v := range ecfg.attrs {
   309  		cfgAttrs[k] = v
   310  	}
   311  	return config.New(config.NoDefaults, cfgAttrs)
   312  }