Merge pull request #7300 from kmbulebu/openstack_app_creds
OpenStack: Support Application Credential Authentication
This commit is contained in:
commit
5aeab4ec06
|
@ -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 != "" {
|
||||
|
|
4
go.mod
4
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
|
||||
|
|
8
go.sum
8
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=
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -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}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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 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.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_ID"); v != "" {
|
||||
cloud.AuthInfo.ProjectID = v
|
||||
}
|
||||
|
||||
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,24 +660,23 @@ 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 := os.Getenv(envPrefix + "REGION_NAME"); v != "" {
|
||||
region = v
|
||||
}
|
||||
|
||||
// Next, check if the cloud entry sets a region.
|
||||
if v := cloud.RegionName; v != "" {
|
||||
region = v
|
||||
}
|
||||
|
||||
// Next, see if one was specified in the ClientOpts.
|
||||
// Finally, see if one was specified in the ClientOpts.
|
||||
// If so, this takes precedence.
|
||||
if v := opts.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 != "" {
|
||||
region = v
|
||||
}
|
||||
|
||||
eo := gophercloud.EndpointOpts{
|
||||
Region: region,
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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=",
|
||||
|
|
|
@ -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`
|
||||
|
|
Loading…
Reference in New Issue