-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add OpenStack Swift scaler #1462
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,210 @@ | ||
package openstack | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"net/http" | ||
"net/url" | ||
"path" | ||
"time" | ||
|
||
kedautil "github.com/kedacore/keda/v2/pkg/util" | ||
) | ||
|
||
const tokensEndpoint = "/auth/tokens" | ||
|
||
// KeystoneAuthMetadata contains all the necessary metadata for Keystone authentication | ||
type KeystoneAuthMetadata struct { | ||
AuthURL string `json:"-"` | ||
AuthToken string `json:"-"` | ||
HTTPClient *http.Client `json:"-"` | ||
Properties *authProps `json:"auth"` | ||
} | ||
|
||
type authProps struct { | ||
Identity *identityProps `json:"identity"` | ||
Scope *scopeProps `json:"scope,omitempty"` | ||
} | ||
|
||
type identityProps struct { | ||
Methods []string `json:"methods"` | ||
Password *passwordProps `json:"password,omitempty"` | ||
AppCredential *appCredentialProps `json:"application_credential,omitempty"` | ||
} | ||
|
||
type passwordProps struct { | ||
User *userProps `json:"user"` | ||
} | ||
|
||
type appCredentialProps struct { | ||
ID string `json:"id"` | ||
Secret string `json:"secret"` | ||
} | ||
|
||
type scopeProps struct { | ||
Project *projectProps `json:"project"` | ||
} | ||
|
||
type userProps struct { | ||
ID string `json:"id"` | ||
Password string `json:"password"` | ||
} | ||
|
||
type projectProps struct { | ||
ID string `json:"id"` | ||
} | ||
|
||
// GetToken retrieves a token from Keystone | ||
func (authProps *KeystoneAuthMetadata) GetToken() (string, error) { | ||
jsonBody, jsonError := json.Marshal(authProps) | ||
|
||
if jsonError != nil { | ||
return "", jsonError | ||
} | ||
|
||
body := bytes.NewReader(jsonBody) | ||
|
||
tokenURL, err := url.Parse(authProps.AuthURL) | ||
|
||
if err != nil { | ||
return "", fmt.Errorf("the authURL is invalid: %s", err.Error()) | ||
} | ||
|
||
tokenURL.Path = path.Join(tokenURL.Path, tokensEndpoint) | ||
|
||
getTokenRequest, getTokenRequestError := http.NewRequest("POST", tokenURL.String(), body) | ||
|
||
getTokenRequest.Header.Set("Content-Type", "application/json") | ||
|
||
if getTokenRequestError != nil { | ||
return "", getTokenRequestError | ||
} | ||
|
||
resp, requestError := authProps.HTTPClient.Do(getTokenRequest) | ||
|
||
if requestError != nil { | ||
return "", requestError | ||
} | ||
|
||
defer resp.Body.Close() | ||
|
||
if resp.StatusCode >= 200 && resp.StatusCode < 300 { | ||
authProps.AuthToken = resp.Header["X-Subject-Token"][0] | ||
return resp.Header["X-Subject-Token"][0], nil | ||
} | ||
|
||
errBody, readBodyErr := ioutil.ReadAll(resp.Body) | ||
|
||
if readBodyErr != nil { | ||
return "", readBodyErr | ||
} | ||
|
||
return "", fmt.Errorf(string(errBody)) | ||
} | ||
|
||
// IsTokenValid checks if a authentication token is valid | ||
func IsTokenValid(authProps KeystoneAuthMetadata) (bool, error) { | ||
token := authProps.AuthToken | ||
|
||
tokenURL, err := url.Parse(authProps.AuthURL) | ||
|
||
if err != nil { | ||
return false, fmt.Errorf("the authURL is invalid: %s", err.Error()) | ||
} | ||
|
||
tokenURL.Path = path.Join(tokenURL.Path, tokensEndpoint) | ||
|
||
checkTokenRequest, checkRequestError := http.NewRequest("HEAD", tokenURL.String(), nil) | ||
checkTokenRequest.Header.Set("X-Subject-Token", token) | ||
checkTokenRequest.Header.Set("X-Auth-Token", token) | ||
|
||
if checkRequestError != nil { | ||
return false, checkRequestError | ||
} | ||
|
||
checkResp, requestError := authProps.HTTPClient.Do(checkTokenRequest) | ||
|
||
if requestError != nil { | ||
return false, requestError | ||
} | ||
|
||
defer checkResp.Body.Close() | ||
|
||
if checkResp.StatusCode >= 400 { | ||
return false, nil | ||
} | ||
|
||
return true, nil | ||
} | ||
|
||
// NewPasswordAuth creates a struct containing metadata for authentication using password method | ||
func NewPasswordAuth(authURL string, userID string, userPassword string, projectID string, httpTimeout int) (*KeystoneAuthMetadata, error) { | ||
var tokenError error | ||
|
||
passAuth := new(KeystoneAuthMetadata) | ||
|
||
passAuth.Properties = new(authProps) | ||
|
||
passAuth.Properties.Scope = new(scopeProps) | ||
passAuth.Properties.Scope.Project = new(projectProps) | ||
|
||
passAuth.Properties.Identity = new(identityProps) | ||
passAuth.Properties.Identity.Password = new(passwordProps) | ||
passAuth.Properties.Identity.Password.User = new(userProps) | ||
|
||
url, err := url.Parse(authURL) | ||
|
||
if err != nil { | ||
return nil, fmt.Errorf("authURL is invalid: %s", err.Error()) | ||
} | ||
|
||
url.Path = path.Join(url.Path, "") | ||
|
||
passAuth.AuthURL = url.String() | ||
|
||
passAuth.HTTPClient = kedautil.CreateHTTPClient(time.Duration(httpTimeout) * time.Second) | ||
|
||
passAuth.Properties.Identity.Methods = []string{"password"} | ||
passAuth.Properties.Identity.Password.User.ID = userID | ||
passAuth.Properties.Identity.Password.User.Password = userPassword | ||
|
||
passAuth.Properties.Scope.Project.ID = projectID | ||
|
||
passAuth.AuthToken, tokenError = passAuth.GetToken() | ||
|
||
return passAuth, tokenError | ||
} | ||
|
||
// NewAppCredentialsAuth creates a struct containing metadata for authentication using application credentials method | ||
func NewAppCredentialsAuth(authURL string, id string, secret string, httpTimeout int) (*KeystoneAuthMetadata, error) { | ||
var tokenError error | ||
|
||
appAuth := new(KeystoneAuthMetadata) | ||
|
||
appAuth.Properties = new(authProps) | ||
|
||
appAuth.Properties.Identity = new(identityProps) | ||
|
||
url, err := url.Parse(authURL) | ||
|
||
if err != nil { | ||
return nil, fmt.Errorf("authURL is invalid: %s", err.Error()) | ||
} | ||
|
||
url.Path = path.Join(url.Path, "") | ||
|
||
appAuth.AuthURL = url.String() | ||
|
||
appAuth.HTTPClient = kedautil.CreateHTTPClient(time.Duration(httpTimeout) * time.Second) | ||
|
||
appAuth.Properties.Identity.AppCredential = new(appCredentialProps) | ||
appAuth.Properties.Identity.Methods = []string{"application_credential"} | ||
appAuth.Properties.Identity.AppCredential.ID = id | ||
appAuth.Properties.Identity.AppCredential.Secret = secret | ||
|
||
appAuth.AuthToken, tokenError = appAuth.GetToken() | ||
|
||
return appAuth, tokenError | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it seems to me that you don't need to make
passAuth
,passAuth.Properties
,passAuth.Properties.Identity
,passAuth.Properties.Identity.Password
,passAuth.Properties.Identity.Password.User
, orpassAuth.Properties.Scope
references, and then you'll get a default object for all of them just by doingright?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As far as I know (and tried), since
KeystoneAuthMetadata
is a struct that has other structs inside that are pointers, they need to be initialized somehow first. Thus,is not sufficient to get a proper
KeystoneAuthMetadata
default object. Assigning values to it without manually making those references will result in:Maybe there is a more elegant way to do or another way to initialize these "inner structs", but my limited experience in Go does not allow me to find it out 😅
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess what I meant is maybe non of these pointers need to be a pointer since you never expect them to actually be nil, right? So
KeystoreAuthMetadata
would beand in turn
authProps
would beand so on.
I think the only difference is in the
json.Marshal()
behavior where a nil is an empty object but any non-nil object is not empty, but I don't think it makes a difference really. Just thought it would make the code a bit easier to read since you're already initializing all the pointers with new() anyway. No worries if it gives you trouble.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly! They do not need to be pointers. The only reason we did that way was because of
json.Marshal()
as you noticed. Since we use these structs to build a JSON payload for our token request, some of them need to be omitted (and thus be pointers, and then benil
) in order to make it work properly.Also, we plan to submit new scalers for OpenStack projects soon. As they come, we can study better ways to do it and, eventually, updated the way we originally structured this.
That is nice feedback, anyway @ahmelsayed. Thanks! We'll keep an eye on it 😄