Updated to latest gophercloud/utils with app cred support.
This commit is contained in:
parent
4025d1da2e
commit
947a172a80
2
go.mod
2
go.mod
|
@ -62,7 +62,7 @@ require (
|
|||
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-20180903124057-ea7289ebdf06
|
||||
github.com/gophercloud/utils v0.0.0-20180806215700-d6e28a8b3199
|
||||
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
|
||||
|
|
4
go.sum
4
go.sum
|
@ -135,8 +135,8 @@ github.com/google/uuid v0.0.0-20171129191014-dec09d789f3d h1:rXQlD9GXkjA/PQZhmEa
|
|||
github.com/google/uuid v0.0.0-20171129191014-dec09d789f3d/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
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-20180806215700-d6e28a8b3199 h1:mmwryCmmFkCxL3t5r6syrbk1eyP6tP9q/whDdAiM9Mw=
|
||||
github.com/gophercloud/utils v0.0.0-20180806215700-d6e28a8b3199/go.mod h1:wjDF8z83zTeg5eMLml5EBSlAhbF7G8DobyI1YsMuyzw=
|
||||
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=
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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=",
|
||||
|
|
Loading…
Reference in New Issue