diff --git a/builder/openstack/access_config.go b/builder/openstack/access_config.go index 377a0c3a7..ad63a396c 100644 --- a/builder/openstack/access_config.go +++ b/builder/openstack/access_config.go @@ -16,22 +16,25 @@ import ( // AccessConfig is for common configuration related to openstack access type AccessConfig struct { - Username string `mapstructure:"username"` - UserID string `mapstructure:"user_id"` - Password string `mapstructure:"password"` - IdentityEndpoint string `mapstructure:"identity_endpoint"` - TenantID string `mapstructure:"tenant_id"` - TenantName string `mapstructure:"tenant_name"` - DomainID string `mapstructure:"domain_id"` - DomainName string `mapstructure:"domain_name"` - Insecure bool `mapstructure:"insecure"` - Region string `mapstructure:"region"` - EndpointType string `mapstructure:"endpoint_type"` - CACertFile string `mapstructure:"cacert"` - ClientCertFile string `mapstructure:"cert"` - ClientKeyFile string `mapstructure:"key"` - Token string `mapstructure:"token"` - Cloud string `mapstructure:"cloud"` + Username string `mapstructure:"username"` + UserID string `mapstructure:"user_id"` + Password string `mapstructure:"password"` + IdentityEndpoint string `mapstructure:"identity_endpoint"` + TenantID string `mapstructure:"tenant_id"` + TenantName string `mapstructure:"tenant_name"` + DomainID string `mapstructure:"domain_id"` + DomainName string `mapstructure:"domain_name"` + Insecure bool `mapstructure:"insecure"` + Region string `mapstructure:"region"` + EndpointType string `mapstructure:"endpoint_type"` + CACertFile string `mapstructure:"cacert"` + ClientCertFile string `mapstructure:"cert"` + ClientKeyFile string `mapstructure:"key"` + Token string `mapstructure:"token"` + ApplicationCredentialName string `mapstructure:"application_credential_name"` + ApplicationCredentialID string `mapstructure:"application_credential_id"` + ApplicationCredentialSecret string `mapstructure:"application_credential_secret"` + Cloud string `mapstructure:"cloud"` osClient *gophercloud.ProviderClient } @@ -126,6 +129,9 @@ func (c *AccessConfig) Prepare(ctx *interpolate.Context) []error { {&c.DomainID, &ao.DomainID}, {&c.DomainName, &ao.DomainName}, {&c.Token, &ao.TokenID}, + {&c.ApplicationCredentialName, &ao.ApplicationCredentialName}, + {&c.ApplicationCredentialID, &ao.ApplicationCredentialID}, + {&c.ApplicationCredentialSecret, &ao.ApplicationCredentialSecret}, } for _, s := range overrides { if *s.From != "" { diff --git a/go.mod b/go.mod index 5abf32ecd..4795d2067 100644 --- a/go.mod +++ b/go.mod @@ -64,8 +64,8 @@ require ( github.com/google/go-querystring v0.0.0-20151028211038-2a60fc2ba6c1 // indirect github.com/google/shlex v0.0.0-20150127133951-6f45313302b9 github.com/google/uuid v0.0.0-20171129191014-dec09d789f3d - github.com/gophercloud/gophercloud v0.0.0-20180815020510-83835c772d1a - github.com/gophercloud/utils v0.0.0-20180806215700-d6e28a8b3199 + github.com/gophercloud/gophercloud v0.0.0-20180903124057-ea7289ebdf06 + github.com/gophercloud/utils v0.0.0-20190124192022-a5c25e7a53a6 github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e // indirect github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777 // indirect github.com/gotestyourself/gotestyourself v2.2.0+incompatible // indirect diff --git a/go.sum b/go.sum index dcee98dcb..b01531a30 100644 --- a/go.sum +++ b/go.sum @@ -143,10 +143,10 @@ github.com/google/shlex v0.0.0-20150127133951-6f45313302b9 h1:JM174NTeGNJ2m/oLH3 github.com/google/shlex v0.0.0-20150127133951-6f45313302b9/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= github.com/google/uuid v0.0.0-20171129191014-dec09d789f3d h1:rXQlD9GXkjA/PQZhmEaF/8Pj/sJfdZJK7GJG0gkS8I0= github.com/google/uuid v0.0.0-20171129191014-dec09d789f3d/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gophercloud/gophercloud v0.0.0-20180815020510-83835c772d1a h1:BYGFl3ozKqWP2FnV4hyr8pNvBBLvyoREM4H6Un75wQ4= -github.com/gophercloud/gophercloud v0.0.0-20180815020510-83835c772d1a/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= -github.com/gophercloud/utils v0.0.0-20180806215700-d6e28a8b3199 h1:mmwryCmmFkCxL3t5r6syrbk1eyP6tP9q/whDdAiM9Mw= -github.com/gophercloud/utils v0.0.0-20180806215700-d6e28a8b3199/go.mod h1:wjDF8z83zTeg5eMLml5EBSlAhbF7G8DobyI1YsMuyzw= +github.com/gophercloud/gophercloud v0.0.0-20180903124057-ea7289ebdf06 h1:m7Rt/8En7PLrM7PQpykdZBPKUdgZWN6MwiA/ChVIoxs= +github.com/gophercloud/gophercloud v0.0.0-20180903124057-ea7289ebdf06/go.mod h1:3WdhXV3rUYy9p6AUW8d94kr+HS62Y4VL9mBnFxsD8q4= +github.com/gophercloud/utils v0.0.0-20190124192022-a5c25e7a53a6 h1:Cw/B8Bu7Rryomxf7bjc8zNfIyLgjxsDd91n0eGRWpuo= +github.com/gophercloud/utils v0.0.0-20190124192022-a5c25e7a53a6/go.mod h1:wjDF8z83zTeg5eMLml5EBSlAhbF7G8DobyI1YsMuyzw= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e h1:JKmoR8x90Iww1ks85zJ1lfDGgIiMDuIptTOhJq+zKyg= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v0.0.0-20170319172727-a91eba7f9777 h1:JIM+OacoOJRU30xpjMf8sulYqjr0ViA3WDrTX6j/yDI= diff --git a/vendor/github.com/gophercloud/gophercloud/README.md b/vendor/github.com/gophercloud/gophercloud/README.md index 8c5bfce79..ad29041d9 100644 --- a/vendor/github.com/gophercloud/gophercloud/README.md +++ b/vendor/github.com/gophercloud/gophercloud/README.md @@ -140,7 +140,7 @@ See the [contributing guide](./.github/CONTRIBUTING.md). ## Help and feedback If you're struggling with something or have spotted a potential bug, feel free -to submit an issue to our [bug tracker](/issues). +to submit an issue to our [bug tracker](https://github.com/gophercloud/gophercloud/issues). ## Thank You diff --git a/vendor/github.com/gophercloud/gophercloud/auth_options.go b/vendor/github.com/gophercloud/gophercloud/auth_options.go index 5e693585c..f28c8d2ad 100644 --- a/vendor/github.com/gophercloud/gophercloud/auth_options.go +++ b/vendor/github.com/gophercloud/gophercloud/auth_options.go @@ -84,6 +84,12 @@ type AuthOptions struct { // Scope determines the scoping of the authentication request. Scope *AuthScope `json:"-"` + + // Authentication through Application Credentials requires supplying name, project and secret + // For project we can use TenantID + ApplicationCredentialID string `json:"-"` + ApplicationCredentialName string `json:"-"` + ApplicationCredentialSecret string `json:"-"` } // AuthScope allows a created token to be limited to a specific domain or project. @@ -142,7 +148,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s type userReq struct { ID *string `json:"id,omitempty"` Name *string `json:"name,omitempty"` - Password string `json:"password"` + Password string `json:"password,omitempty"` Domain *domainReq `json:"domain,omitempty"` } @@ -154,10 +160,18 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s ID string `json:"id"` } + type applicationCredentialReq struct { + ID *string `json:"id,omitempty"` + Name *string `json:"name,omitempty"` + User *userReq `json:"user,omitempty"` + Secret *string `json:"secret,omitempty"` + } + type identityReq struct { - Methods []string `json:"methods"` - Password *passwordReq `json:"password,omitempty"` - Token *tokenReq `json:"token,omitempty"` + Methods []string `json:"methods"` + Password *passwordReq `json:"password,omitempty"` + Token *tokenReq `json:"token,omitempty"` + ApplicationCredential *applicationCredentialReq `json:"application_credential,omitempty"` } type authReq struct { @@ -171,6 +185,7 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s // Populate the request structure based on the provided arguments. Create and return an error // if insufficient or incompatible information is present. var req request + var userRequest userReq if opts.Password == "" { if opts.TokenID != "" { @@ -194,8 +209,49 @@ func (opts *AuthOptions) ToTokenV3CreateMap(scope map[string]interface{}) (map[s req.Auth.Identity.Token = &tokenReq{ ID: opts.TokenID, } + + } else if opts.ApplicationCredentialID != "" { + // Configure the request for ApplicationCredentialID authentication. + // https://github.com/openstack/keystoneauth/blob/stable/rocky/keystoneauth1/identity/v3/application_credential.py#L48-L67 + // There are three kinds of possible application_credential requests + // 1. application_credential id + secret + // 2. application_credential name + secret + user_id + // 3. application_credential name + secret + username + domain_id / domain_name + if opts.ApplicationCredentialSecret == "" { + return nil, ErrAppCredMissingSecret{} + } + req.Auth.Identity.Methods = []string{"application_credential"} + req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{ + ID: &opts.ApplicationCredentialID, + Secret: &opts.ApplicationCredentialSecret, + } + } else if opts.ApplicationCredentialName != "" { + if opts.ApplicationCredentialSecret == "" { + return nil, ErrAppCredMissingSecret{} + } + // make sure that only one of DomainName or DomainID were provided + if opts.DomainID == "" && opts.DomainName == "" { + return nil, ErrDomainIDOrDomainName{} + } + req.Auth.Identity.Methods = []string{"application_credential"} + if opts.DomainID != "" { + userRequest = userReq{ + Name: &opts.Username, + Domain: &domainReq{ID: &opts.DomainID}, + } + } else if opts.DomainName != "" { + userRequest = userReq{ + Name: &opts.Username, + Domain: &domainReq{Name: &opts.DomainName}, + } + } + req.Auth.Identity.ApplicationCredential = &applicationCredentialReq{ + Name: &opts.ApplicationCredentialName, + User: &userRequest, + Secret: &opts.ApplicationCredentialSecret, + } } else { - // If no password or token ID are available, authentication can't continue. + // If no password or token ID or ApplicationCredential are available, authentication can't continue. return nil, ErrMissingPassword{} } } else { diff --git a/vendor/github.com/gophercloud/gophercloud/errors.go b/vendor/github.com/gophercloud/gophercloud/errors.go index a5fa68d6d..4bf102468 100644 --- a/vendor/github.com/gophercloud/gophercloud/errors.go +++ b/vendor/github.com/gophercloud/gophercloud/errors.go @@ -451,3 +451,10 @@ type ErrScopeEmpty struct{ BaseError } func (e ErrScopeEmpty) Error() string { return "You must provide either a Project or Domain in a Scope" } + +// ErrAppCredMissingSecret indicates that no Application Credential Secret was provided with Application Credential ID or Name +type ErrAppCredMissingSecret struct{ BaseError } + +func (e ErrAppCredMissingSecret) Error() string { + return "You must provide an Application Credential Secret" +} diff --git a/vendor/github.com/gophercloud/gophercloud/provider_client.go b/vendor/github.com/gophercloud/gophercloud/provider_client.go index 17e451274..95fa11a78 100644 --- a/vendor/github.com/gophercloud/gophercloud/provider_client.go +++ b/vendor/github.com/gophercloud/gophercloud/provider_client.go @@ -295,7 +295,11 @@ func (client *ProviderClient) Request(method, url string, options *RequestOpts) seeker.Seek(0, 0) } } + // make a new call to request with a nil reauth func in order to avoid infinite loop + reauthFunc := client.ReauthFunc + client.ReauthFunc = nil resp, err = client.Request(method, url, options) + client.ReauthFunc = reauthFunc if err != nil { switch err.(type) { case *ErrUnexpectedResponseCode: @@ -378,7 +382,7 @@ func defaultOkCodes(method string) []int { case method == "PUT": return []int{201, 202} case method == "PATCH": - return []int{200, 204} + return []int{200, 202, 204} case method == "DELETE": return []int{202, 204} } diff --git a/vendor/github.com/gophercloud/gophercloud/results.go b/vendor/github.com/gophercloud/gophercloud/results.go index fdd4830ec..30d3b1559 100644 --- a/vendor/github.com/gophercloud/gophercloud/results.go +++ b/vendor/github.com/gophercloud/gophercloud/results.go @@ -89,23 +89,45 @@ func (r Result) extractIntoPtr(to interface{}, label string) error { if typeOfV.Kind() == reflect.Struct { if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous { newSlice := reflect.MakeSlice(reflect.SliceOf(typeOfV), 0, 0) - newType := reflect.New(typeOfV).Elem() for _, v := range m[label].([]interface{}) { + // For each iteration of the slice, we create a new struct. + // This is to work around a bug where elements of a slice + // are reused and not overwritten when the same copy of the + // struct is used: + // + // https://github.com/golang/go/issues/21092 + // https://github.com/golang/go/issues/24155 + // https://play.golang.org/p/NHo3ywlPZli + newType := reflect.New(typeOfV).Elem() + b, err := json.Marshal(v) if err != nil { return err } + // This is needed for structs with an UnmarshalJSON method. + // Technically this is just unmarshalling the response into + // a struct that is never used, but it's good enough to + // trigger the UnmarshalJSON method. for i := 0; i < newType.NumField(); i++ { s := newType.Field(i).Addr().Interface() - err = json.NewDecoder(bytes.NewReader(b)).Decode(s) + + // Unmarshal is used rather than NewDecoder to also work + // around the above-mentioned bug. + err = json.Unmarshal(b, s) if err != nil { return err } } + newSlice = reflect.Append(newSlice, newType) } + + // "to" should now be properly modeled to receive the + // JSON response body and unmarshal into all the correct + // fields of the struct or composed extension struct + // at the end of this method. toValue.Set(newSlice) } } @@ -366,6 +388,27 @@ func (jt *JSONRFC3339ZNoT) UnmarshalJSON(data []byte) error { return nil } +// RFC3339ZNoTNoZ is another time format used in Zun (Containers Service). +const RFC3339ZNoTNoZ = "2006-01-02 15:04:05" + +type JSONRFC3339ZNoTNoZ time.Time + +func (jt *JSONRFC3339ZNoTNoZ) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + if s == "" { + return nil + } + t, err := time.Parse(RFC3339ZNoTNoZ, s) + if err != nil { + return err + } + *jt = JSONRFC3339ZNoTNoZ(t) + return nil +} + /* Link is an internal type to be used in packages of collection resources that are paginated in a certain way. diff --git a/vendor/github.com/gophercloud/utils/openstack/clientconfig/requests.go b/vendor/github.com/gophercloud/utils/openstack/clientconfig/requests.go index 508f1ab13..007939013 100644 --- a/vendor/github.com/gophercloud/utils/openstack/clientconfig/requests.go +++ b/vendor/github.com/gophercloud/utils/openstack/clientconfig/requests.go @@ -9,7 +9,7 @@ import ( "github.com/gophercloud/gophercloud" "github.com/gophercloud/gophercloud/openstack" - "gopkg.in/yaml.v2" + yaml "gopkg.in/yaml.v2" ) // AuthType respresents a valid method of authentication. @@ -30,6 +30,9 @@ const ( AuthV3Password AuthType = "v3password" // AuthV3Token defines version 3 of the token AuthV3Token AuthType = "v3token" + + // AuthV3ApplicationCredential defines version 3 of the application credential + AuthV3ApplicationCredential AuthType = "v3applicationcredential" ) // ClientOpts represents options to customize the way a client is @@ -333,6 +336,8 @@ func determineIdentityAPI(cloud *Cloud, opts *ClientOpts) string { identityAPI = "3" case AuthV3Token: identityAPI = "3" + case AuthV3ApplicationCredential: + identityAPI = "3" } } @@ -353,40 +358,52 @@ func v2auth(cloud *Cloud, opts *ClientOpts) (*gophercloud.AuthOptions, error) { envPrefix = opts.EnvPrefix } - if v := os.Getenv(envPrefix + "AUTH_URL"); v != "" { - cloud.AuthInfo.AuthURL = v + if cloud.AuthInfo.AuthURL == "" { + if v := os.Getenv(envPrefix + "AUTH_URL"); v != "" { + cloud.AuthInfo.AuthURL = v + } } - if v := os.Getenv(envPrefix + "TOKEN"); v != "" { - cloud.AuthInfo.Token = v - } + if cloud.AuthInfo.Token == "" { + if v := os.Getenv(envPrefix + "TOKEN"); v != "" { + cloud.AuthInfo.Token = v + } - if v := os.Getenv(envPrefix + "AUTH_TOKEN"); v != "" { - cloud.AuthInfo.Token = v + if v := os.Getenv(envPrefix + "AUTH_TOKEN"); v != "" { + cloud.AuthInfo.Token = v + } } - if v := os.Getenv(envPrefix + "USERNAME"); v != "" { - cloud.AuthInfo.Username = v + if cloud.AuthInfo.Username == "" { + if v := os.Getenv(envPrefix + "USERNAME"); v != "" { + cloud.AuthInfo.Username = v + } } - if v := os.Getenv(envPrefix + "PASSWORD"); v != "" { - cloud.AuthInfo.Password = v + if cloud.AuthInfo.Password == "" { + if v := os.Getenv(envPrefix + "PASSWORD"); v != "" { + cloud.AuthInfo.Password = v + } } - if v := os.Getenv(envPrefix + "TENANT_ID"); v != "" { - cloud.AuthInfo.ProjectID = v - } + if cloud.AuthInfo.ProjectID == "" { + if v := os.Getenv(envPrefix + "TENANT_ID"); v != "" { + cloud.AuthInfo.ProjectID = v + } - if v := os.Getenv(envPrefix + "PROJECT_ID"); v != "" { - cloud.AuthInfo.ProjectID = v + if v := os.Getenv(envPrefix + "PROJECT_ID"); v != "" { + cloud.AuthInfo.ProjectID = v + } } - if v := os.Getenv(envPrefix + "TENANT_NAME"); v != "" { - cloud.AuthInfo.ProjectName = v - } + if cloud.AuthInfo.ProjectName == "" { + if v := os.Getenv(envPrefix + "TENANT_NAME"); v != "" { + cloud.AuthInfo.ProjectName = v + } - if v := os.Getenv(envPrefix + "PROJECT_NAME"); v != "" { - cloud.AuthInfo.ProjectName = v + if v := os.Getenv(envPrefix + "PROJECT_NAME"); v != "" { + cloud.AuthInfo.ProjectName = v + } } ao := &gophercloud.AuthOptions{ @@ -409,109 +426,161 @@ func v3auth(cloud *Cloud, opts *ClientOpts) (*gophercloud.AuthOptions, error) { envPrefix = opts.EnvPrefix } - if v := os.Getenv(envPrefix + "AUTH_URL"); v != "" { - cloud.AuthInfo.AuthURL = v + if cloud.AuthInfo.AuthURL == "" { + if v := os.Getenv(envPrefix + "AUTH_URL"); v != "" { + cloud.AuthInfo.AuthURL = v + } } - if v := os.Getenv(envPrefix + "TOKEN"); v != "" { - cloud.AuthInfo.Token = v + if cloud.AuthInfo.Token == "" { + if v := os.Getenv(envPrefix + "TOKEN"); v != "" { + cloud.AuthInfo.Token = v + } + + if v := os.Getenv(envPrefix + "AUTH_TOKEN"); v != "" { + cloud.AuthInfo.Token = v + } } - if v := os.Getenv(envPrefix + "AUTH_TOKEN"); v != "" { - cloud.AuthInfo.Token = v + if cloud.AuthInfo.Username == "" { + if v := os.Getenv(envPrefix + "USERNAME"); v != "" { + cloud.AuthInfo.Username = v + } } - if v := os.Getenv(envPrefix + "USERNAME"); v != "" { - cloud.AuthInfo.Username = v + if cloud.AuthInfo.UserID == "" { + if v := os.Getenv(envPrefix + "USER_ID"); v != "" { + cloud.AuthInfo.UserID = v + } } - if v := os.Getenv(envPrefix + "USER_ID"); v != "" { - cloud.AuthInfo.UserID = v + if cloud.AuthInfo.Password == "" { + if v := os.Getenv(envPrefix + "PASSWORD"); v != "" { + cloud.AuthInfo.Password = v + } } - if v := os.Getenv(envPrefix + "PASSWORD"); v != "" { - cloud.AuthInfo.Password = v + if cloud.AuthInfo.ProjectID == "" { + if v := os.Getenv(envPrefix + "TENANT_ID"); v != "" { + cloud.AuthInfo.ProjectID = v + } + + if v := os.Getenv(envPrefix + "PROJECT_ID"); v != "" { + cloud.AuthInfo.ProjectID = v + } } - if v := os.Getenv(envPrefix + "TENANT_ID"); v != "" { - cloud.AuthInfo.ProjectID = v + if cloud.AuthInfo.ProjectName == "" { + if v := os.Getenv(envPrefix + "TENANT_NAME"); v != "" { + cloud.AuthInfo.ProjectName = v + } + + if v := os.Getenv(envPrefix + "PROJECT_NAME"); v != "" { + cloud.AuthInfo.ProjectName = v + } } - if v := os.Getenv(envPrefix + "PROJECT_ID"); v != "" { - cloud.AuthInfo.ProjectID = v + if cloud.AuthInfo.DomainID == "" { + if v := os.Getenv(envPrefix + "DOMAIN_ID"); v != "" { + cloud.AuthInfo.DomainID = v + } } - if v := os.Getenv(envPrefix + "TENANT_NAME"); v != "" { - cloud.AuthInfo.ProjectName = v + if cloud.AuthInfo.DomainName == "" { + if v := os.Getenv(envPrefix + "DOMAIN_NAME"); v != "" { + cloud.AuthInfo.DomainName = v + } } - if v := os.Getenv(envPrefix + "PROJECT_NAME"); v != "" { - cloud.AuthInfo.ProjectName = v + if cloud.AuthInfo.DefaultDomain == "" { + if v := os.Getenv(envPrefix + "DEFAULT_DOMAIN"); v != "" { + cloud.AuthInfo.DefaultDomain = v + } } - if v := os.Getenv(envPrefix + "DOMAIN_ID"); v != "" { - cloud.AuthInfo.DomainID = v + if cloud.AuthInfo.ProjectDomainID == "" { + if v := os.Getenv(envPrefix + "PROJECT_DOMAIN_ID"); v != "" { + cloud.AuthInfo.ProjectDomainID = v + } } - if v := os.Getenv(envPrefix + "DOMAIN_NAME"); v != "" { - cloud.AuthInfo.DomainName = v + if cloud.AuthInfo.ProjectDomainName == "" { + if v := os.Getenv(envPrefix + "PROJECT_DOMAIN_NAME"); v != "" { + cloud.AuthInfo.ProjectDomainName = v + } } - if v := os.Getenv(envPrefix + "DEFAULT_DOMAIN"); v != "" { - cloud.AuthInfo.DefaultDomain = v + if cloud.AuthInfo.UserDomainID == "" { + if v := os.Getenv(envPrefix + "USER_DOMAIN_ID"); v != "" { + cloud.AuthInfo.UserDomainID = v + } } - if v := os.Getenv(envPrefix + "PROJECT_DOMAIN_ID"); v != "" { - cloud.AuthInfo.ProjectDomainID = v + if cloud.AuthInfo.UserDomainName == "" { + if v := os.Getenv(envPrefix + "USER_DOMAIN_NAME"); v != "" { + cloud.AuthInfo.UserDomainName = v + } } - if v := os.Getenv(envPrefix + "PROJECT_DOMAIN_NAME"); v != "" { - cloud.AuthInfo.ProjectDomainName = v + if cloud.AuthInfo.ApplicationCredentialID == "" { + if v := os.Getenv(envPrefix + "APPLICATION_CREDENTIAL_ID"); v != "" { + cloud.AuthInfo.ApplicationCredentialID = v + } } - if v := os.Getenv(envPrefix + "USER_DOMAIN_ID"); v != "" { - cloud.AuthInfo.UserDomainID = v + if cloud.AuthInfo.ApplicationCredentialName == "" { + if v := os.Getenv(envPrefix + "APPLICATION_CREDENTIAL_NAME"); v != "" { + cloud.AuthInfo.ApplicationCredentialName = v + } } - if v := os.Getenv(envPrefix + "USER_DOMAIN_NAME"); v != "" { - cloud.AuthInfo.UserDomainName = v + if cloud.AuthInfo.ApplicationCredentialSecret == "" { + if v := os.Getenv(envPrefix + "APPLICATION_CREDENTIAL_SECRET"); v != "" { + cloud.AuthInfo.ApplicationCredentialSecret = v + } } // Build a scope and try to do it correctly. // https://github.com/openstack/os-client-config/blob/master/os_client_config/config.py#L595 scope := new(gophercloud.AuthScope) - if !isProjectScoped(cloud.AuthInfo) { - if cloud.AuthInfo.DomainID != "" { - scope.DomainID = cloud.AuthInfo.DomainID - } else if cloud.AuthInfo.DomainName != "" { - scope.DomainName = cloud.AuthInfo.DomainName - } - } else { - // If Domain* is set, but UserDomain* or ProjectDomain* aren't, - // then use Domain* as the default setting. - cloud = setDomainIfNeeded(cloud) - - if cloud.AuthInfo.ProjectID != "" { - scope.ProjectID = cloud.AuthInfo.ProjectID + // Application credentials don't support scope + if !isApplicationCredential(cloud.AuthInfo) { + if !isProjectScoped(cloud.AuthInfo) { + if cloud.AuthInfo.DomainID != "" { + scope.DomainID = cloud.AuthInfo.DomainID + } else if cloud.AuthInfo.DomainName != "" { + scope.DomainName = cloud.AuthInfo.DomainName + } } else { - scope.ProjectName = cloud.AuthInfo.ProjectName - scope.DomainID = cloud.AuthInfo.ProjectDomainID - scope.DomainName = cloud.AuthInfo.ProjectDomainName + // If Domain* is set, but UserDomain* or ProjectDomain* aren't, + // then use Domain* as the default setting. + cloud = setDomainIfNeeded(cloud) + + if cloud.AuthInfo.ProjectID != "" { + scope.ProjectID = cloud.AuthInfo.ProjectID + } else { + scope.ProjectName = cloud.AuthInfo.ProjectName + scope.DomainID = cloud.AuthInfo.ProjectDomainID + scope.DomainName = cloud.AuthInfo.ProjectDomainName + } } } ao := &gophercloud.AuthOptions{ - Scope: scope, - IdentityEndpoint: cloud.AuthInfo.AuthURL, - TokenID: cloud.AuthInfo.Token, - Username: cloud.AuthInfo.Username, - UserID: cloud.AuthInfo.UserID, - Password: cloud.AuthInfo.Password, - TenantID: cloud.AuthInfo.ProjectID, - TenantName: cloud.AuthInfo.ProjectName, - DomainID: cloud.AuthInfo.UserDomainID, - DomainName: cloud.AuthInfo.UserDomainName, + Scope: scope, + IdentityEndpoint: cloud.AuthInfo.AuthURL, + TokenID: cloud.AuthInfo.Token, + Username: cloud.AuthInfo.Username, + UserID: cloud.AuthInfo.UserID, + Password: cloud.AuthInfo.Password, + TenantID: cloud.AuthInfo.ProjectID, + TenantName: cloud.AuthInfo.ProjectName, + DomainID: cloud.AuthInfo.UserDomainID, + DomainName: cloud.AuthInfo.UserDomainName, + ApplicationCredentialID: cloud.AuthInfo.ApplicationCredentialID, + ApplicationCredentialName: cloud.AuthInfo.ApplicationCredentialName, + ApplicationCredentialSecret: cloud.AuthInfo.ApplicationCredentialSecret, } // If an auth_type of "token" was specified, then make sure @@ -591,21 +660,20 @@ func NewServiceClient(service string, opts *ClientOpts) (*gophercloud.ServiceCli } // Determine the region to use. - // First, see if the cloud entry has one. + // First, check if the REGION_NAME environment variable is set. var region string - if v := cloud.RegionName; v != "" { + if v := os.Getenv(envPrefix + "REGION_NAME"); v != "" { region = v } - // Next, see if one was specified in the ClientOpts. - // If so, this takes precedence. - if v := opts.RegionName; v != "" { + // Next, check if the cloud entry sets a region. + if v := cloud.RegionName; v != "" { region = v } - // Finally, see if there's an environment variable. - // This should always override prior settings. - if v := os.Getenv(envPrefix + "REGION_NAME"); v != "" { + // Finally, see if one was specified in the ClientOpts. + // If so, this takes precedence. + if v := opts.RegionName; v != "" { region = v } @@ -722,3 +790,11 @@ func setDomainIfNeeded(cloud *Cloud) *Cloud { return cloud } + +// isApplicationCredential determines if an application credential is used to auth. +func isApplicationCredential(authInfo *AuthInfo) bool { + if authInfo.ApplicationCredentialID == "" && authInfo.ApplicationCredentialName == "" && authInfo.ApplicationCredentialSecret == "" { + return false + } + return true +} diff --git a/vendor/github.com/gophercloud/utils/openstack/clientconfig/results.go b/vendor/github.com/gophercloud/utils/openstack/clientconfig/results.go index b49367c3c..3dc849c6c 100644 --- a/vendor/github.com/gophercloud/utils/openstack/clientconfig/results.go +++ b/vendor/github.com/gophercloud/utils/openstack/clientconfig/results.go @@ -61,6 +61,15 @@ type AuthInfo struct { // Password is the password of the user. Password string `yaml:"password"` + // Application Credential ID to login with. + ApplicationCredentialID string `yaml:"application_credential_id"` + + // Application Credential name to login with. + ApplicationCredentialName string `yaml:"application_credential_name"` + + // Application Credential secret to login with. + ApplicationCredentialSecret string `yaml:"application_credential_secret"` + // ProjectName is the common/human-readable name of a project. // Users can be scoped to a project. // ProjectName on its own is not enough to ensure a unique scope. It must diff --git a/vendor/github.com/gophercloud/utils/openstack/clientconfig/utils.go b/vendor/github.com/gophercloud/utils/openstack/clientconfig/utils.go index 3403e5355..884e5644e 100644 --- a/vendor/github.com/gophercloud/utils/openstack/clientconfig/utils.go +++ b/vendor/github.com/gophercloud/utils/openstack/clientconfig/utils.go @@ -128,16 +128,13 @@ func findAndReadYAML(yamlFile string) ([]byte, error) { } // unix user config directory: ~/.config/openstack. - currentUser, err := user.Current() - if err != nil { - return nil, fmt.Errorf("unable to get current user: %s", err) - } - - homeDir := currentUser.HomeDir - if homeDir != "" { - filename := filepath.Join(homeDir, ".config/openstack/"+yamlFile) - if ok := fileExists(filename); ok { - return ioutil.ReadFile(filename) + if currentUser, err := user.Current(); err == nil { + homeDir := currentUser.HomeDir + if homeDir != "" { + filename := filepath.Join(homeDir, ".config/openstack/"+yamlFile) + if ok := fileExists(filename); ok { + return ioutil.ReadFile(filename) + } } } diff --git a/vendor/vendor.json b/vendor/vendor.json index 0defc2d99..0d0e5a2f6 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -811,10 +811,10 @@ "revisionTime": "2017-11-29T19:10:14Z" }, { - "checksumSHA1": "qduT9GZUhXc00XoHEwLx16Xn9gM=", + "checksumSHA1": "90HfuOdlP9yK6xUnJCAnCrL73DQ=", "path": "github.com/gophercloud/gophercloud", - "revision": "7112fcd50da4ea27e8d4d499b30f04eea143bec2", - "revisionTime": "2018-05-31T02:06:30Z" + "revision": "ea7289ebdf06687b792c087e2516317579d3003b", + "revisionTime": "2018-09-03T13:40:57Z" }, { "checksumSHA1": "b7g9TcU1OmW7e2UySYeOAmcfHpY=", @@ -961,10 +961,16 @@ "revisionTime": "2018-05-31T02:06:30Z" }, { - "checksumSHA1": "BYHuEArNKnTCbp/LTCwQSlaIY4Y=", + "checksumSHA1": "df+06zNEC3V7qgnTaVLtH0uktmI=", + "path": "github.com/gophercloud/utils", + "revision": "a5c25e7a53a63b89622852e35d7200c85f7cbe56", + "revisionTime": "2019-01-24T19:20:22Z" + }, + { + "checksumSHA1": "+lG+bluykADYk0Zzq8sdh7KIyxY=", "path": "github.com/gophercloud/utils/openstack/clientconfig", - "revision": "d6e28a8b3199a79da5e74e3dde1eb878ff525f1a", - "revisionTime": "2018-08-06T21:57:00Z" + "revision": "a5c25e7a53a63b89622852e35d7200c85f7cbe56", + "revisionTime": "2019-01-24T19:20:22Z" }, { "checksumSHA1": "xSmii71kfQASGNG2C8ttmHx9KTE=", diff --git a/website/source/docs/builders/openstack.html.md b/website/source/docs/builders/openstack.html.md index 837409e9f..17bffb820 100644 --- a/website/source/docs/builders/openstack.html.md +++ b/website/source/docs/builders/openstack.html.md @@ -78,15 +78,28 @@ builder. - `username` or `user_id` (string) - The username or id used to connect to the OpenStack service. If not specified, Packer will use the environment variable `OS_USERNAME` or `OS_USERID`, if set. This is not required if - using access token instead of password or if using `cloud.yaml`. + using access token or application credential instead of password, or if using + `cloud.yaml`. - `password` (string) - The password used to connect to the OpenStack service. If not specified, Packer will use the environment variables - `OS_PASSWORD`, if set. This is not required if using access token instead - of password or if using `cloud.yaml`. + `OS_PASSWORD`, if set. This is not required if using access token or + application credential instead of password, or if using `cloud.yaml`. ### Optional: +- `application_credential_id` (string) - The application credential id to + use with application credential based authorization. Packer will use the + environment variable `OS_APPLICATION_CREDENTIAL_ID`, if set. + +- `application_credential_name` (string) - The application credential name to + use with application credential based authorization. Packer will use the + environment variable `OS_APPLICATION_CREDENTIAL_NAME`, if set. + +- `application_credential_secret` (string) - The application credential secret + to use with application credential based authorization. Packer will use the + environment variable `OS_APPLICATION_CREDENTIAL_SECRET`, if set. + - `availability_zone` (string) - The availability zone to launch the server in. If this isn't specified, the default enforced by your OpenStack cluster will be used. This may be required for some OpenStack clusters. @@ -432,3 +445,13 @@ Or use the following environment variables: - `OS_AUTH_URL` - `OS_TOKEN` - One of `OS_TENANT_NAME` or `OS_TENANT_ID` + +### Authorize Using Application Credential + +To authorize with an application credential, only `identity_endpoint`, +`application_credential_id`, and `application_credential_secret` are needed. +Or use the following environment variables: + +- `OS_AUTH_URL` +- `OS_APPLICATION_CREDENTIAL_ID` +- `OS_APPLICATION_CREDENTIAL_SECRET`