Skip to content
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

Added Scaleway Shell Plugin #314

Open
wants to merge 14 commits into
base: main
Choose a base branch
from
127 changes: 127 additions & 0 deletions plugins/scaleway/api_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package scaleway

import (
"context"
"os"

"github.com/1Password/shell-plugins/sdk"
"github.com/1Password/shell-plugins/sdk/importer"
"github.com/1Password/shell-plugins/sdk/provision"
"github.com/1Password/shell-plugins/sdk/schema"
"github.com/1Password/shell-plugins/sdk/schema/credname"
"github.com/1Password/shell-plugins/sdk/schema/fieldname"
)

func APIKey() schema.CredentialType {
return schema.CredentialType{
Name: credname.APIKey,
DocsURL: sdk.URL("https://www.scaleway.com/en/docs/identity-and-access-management/iam/how-to/create-api-keys"),
ManagementURL: sdk.URL("https://console.scaleway.com/iam/api-keys"),
Fields: []schema.CredentialField{
{
Name: fieldname.AccessKeyID,
MarkdownDescription: "The ID of the API Key used to authenticate to Scaleway.",
Secret: false,
Composition: &schema.ValueComposition{
Length: 20,
Prefix: "SCW",
Charset: schema.Charset{
Uppercase: true,
Digits: true,
},
},
},
{
Name: fieldname.SecretAccessKey,
MarkdownDescription: "The secret access key used to authenticate to Scaleway.",
Secret: true,
Composition: &schema.ValueComposition{
Length: 36,
Charset: schema.Charset{
Lowercase: true,
Digits: true,
Specific: []rune{'-'},
},
},
},
{
Name: fieldname.DefaultOrganization,
MarkdownDescription: "The default organization ID to use for this access key.",
Secret: false,
Composition: &schema.ValueComposition{
Length: 36,
Charset: schema.Charset{
Lowercase: true,
Digits: true,
Specific: []rune{'-'},
},
},
},
{
Name: fieldname.DefaultRegion,
MarkdownDescription: "The default region to use for this access key.",
Optional: true,
},
{
Name: fieldname.DefaultZone,
MarkdownDescription: "The default zone to use for this access key.",
Optional: true,
},
},
DefaultProvisioner: provision.EnvVars(defaultEnvVarMapping),
Importer: importer.TryAll(
importer.TryEnvVarPair(defaultEnvVarMapping),
TryScalewayConfigFile(),
)}
}

var defaultEnvVarMapping = map[string]sdk.FieldName{
"SCW_ACCESS_KEY": fieldname.AccessKeyID,
"SCW_SECRET_KEY": fieldname.SecretAccessKey,
"SCW_DEFAULT_ORGANIZATION_ID": fieldname.DefaultOrganization,
"SCW_DEFAULT_REGION": fieldname.DefaultRegion,
"SCW_DEFAULT_ZONE": fieldname.DefaultZone,
arunsathiya marked this conversation as resolved.
Show resolved Hide resolved
}

func TryScalewayConfigFile() sdk.Importer {
file := os.Getenv("SCW_CONFIG_PATH")
if file == "" {
file = "~/.config/scw/config.yaml"
}
return importer.TryFile(file, func(ctx context.Context, contents importer.FileContents, in sdk.ImportInput, out *sdk.ImportAttempt) {
var config Config
if err := contents.ToYAML(&config); err != nil {
out.AddError(err)
return
}

if config.AccessKey == "" || config.SecretKey == "" {
return
}

fields := make(map[sdk.FieldName]string)
fields[fieldname.AccessKeyID] = config.AccessKey
fields[fieldname.SecretAccessKey] = config.SecretKey
if config.DefaultOrganizationID != "" {
fields[fieldname.DefaultOrganization] = config.DefaultOrganizationID
}
if config.DefaultRegion != "" {
fields[fieldname.DefaultRegion] = config.DefaultRegion
}
if config.DefaultZone != "" {
fields[fieldname.DefaultZone] = config.DefaultZone
}
out.AddCandidate(sdk.ImportCandidate{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems that Scaleway config file supports the concepts of profiles. Could we import the profile name as a Namehint? You'll need to set up at least two profiles to test the importer. Example:

profiles:
  first:
    access_key: redacted
    secret_key: redacted
    default_organization_id: redacted
    default_project_id: redacted
    default_zone: fr-par-1
    default_region: fr-par
    # api_url: https://api.scaleway.com
    # insecure: false

  second:
    access_key: redacted
    secret_key: redacted
    default_organization_id: redacted
    default_project_id: redacted
    default_zone: fr-par-1
    default_region: fr-par
    # api_url: https://api.scaleway.com
    # insecure: false

There's an example of how to import multiple profiles in the Fastly shell plugin.

Fields: fields,
})
})
}

type Config struct {
AccessKey string `yaml:"access_key"`
SecretKey string `yaml:"secret_key"`
DefaultOrganizationID string `yaml:"default_organization_id"`
DefaultProjectID string `yaml:"default_project_id"`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this attribute used anywhere?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I noticed the same thing. @parthiv11, let's remove this since this is not being used anywhere. But before we do, would you know if the Scaleway CLI needs both the organization ID and the project ID to function?

DefaultRegion string `yaml:"default_region"`
DefaultZone string `yaml:"default_zone"`
}
87 changes: 87 additions & 0 deletions plugins/scaleway/api_key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package scaleway

import (
"testing"

"github.com/1Password/shell-plugins/sdk"
"github.com/1Password/shell-plugins/sdk/plugintest"
"github.com/1Password/shell-plugins/sdk/schema/fieldname"
)

func TestAPIKeyImporter(t *testing.T) {
plugintest.TestImporter(t, APIKey().Importer, map[string]plugintest.ImportCase{
"Environment variables": {
Environment: map[string]string{
"SCW_ACCESS_KEY": "SCWSYXTFI97NSEXAMPLE",
"SCW_SECRET_KEY": "d9b67b48-873c-8ece-8270-e1e15example",
"SCW_DEFAULT_ORGANIZATION_ID": "11111111-2222-3333-4444-55555example",
arunsathiya marked this conversation as resolved.
Show resolved Hide resolved
"SCW_DEFAULT_REGION": "fr-par",
"SCW_DEFAULT_ZONE": "fr-par-1",
},
ExpectedCandidates: []sdk.ImportCandidate{
{
Fields: map[sdk.FieldName]string{
fieldname.AccessKeyID: "SCWSYXTFI97NSEXAMPLE",
fieldname.SecretAccessKey: "d9b67b48-873c-8ece-8270-e1e15example",
fieldname.DefaultOrganization: "11111111-2222-3333-4444-55555example",
fieldname.DefaultRegion: "fr-par",
fieldname.DefaultZone: "fr-par-1",
},
},
},
},
"SCW default config file location": {
Files: map[string]string{
"~/.config/scw/config.yaml": plugintest.LoadFixture(t, "simple.yaml"),
},
ExpectedCandidates: []sdk.ImportCandidate{
{
Fields: map[sdk.FieldName]string{
fieldname.AccessKeyID: "SCWSYXTFI97NSEXAMPLE",
fieldname.SecretAccessKey: "d9b67b48-873c-8ece-8270-e1e15example",
fieldname.DefaultOrganization: "11111111-2222-3333-4444-55555example",
},
},
},
},
"SCW config file with optional settings": {
Files: map[string]string{
"~/.config/scw/config.yaml": plugintest.LoadFixture(t, "optional.yaml"),
},
ExpectedCandidates: []sdk.ImportCandidate{
{
Fields: map[sdk.FieldName]string{
fieldname.AccessKeyID: "SCWSYXTFI97NSEXAMPLE",
fieldname.SecretAccessKey: "d9b67b48-873c-8ece-8270-e1e15example",
fieldname.DefaultOrganization: "11111111-2222-3333-4444-55555example",
fieldname.DefaultRegion: "fr-par",
fieldname.DefaultZone: "fr-par-1",
},
},
},
},
})
}

func TestAPIKeyProvisioner(t *testing.T) {
plugintest.TestProvisioner(t, APIKey().DefaultProvisioner, map[string]plugintest.ProvisionCase{
"default": {
ItemFields: map[sdk.FieldName]string{
fieldname.AccessKeyID: "SCWSYXTFI97NSEXAMPLE",
fieldname.SecretAccessKey: "d9b67b48-873c-8ece-8270-e1e15example",
fieldname.DefaultOrganization: "11111111-2222-3333-4444-55555example",
arunsathiya marked this conversation as resolved.
Show resolved Hide resolved
fieldname.DefaultRegion: "fr-par",
fieldname.DefaultZone: "fr-par-1",
},
ExpectedOutput: sdk.ProvisionOutput{
Environment: map[string]string{
"SCW_ACCESS_KEY": "SCWSYXTFI97NSEXAMPLE",
"SCW_SECRET_KEY": "d9b67b48-873c-8ece-8270-e1e15example",
"SCW_DEFAULT_ORGANIZATION_ID": "11111111-2222-3333-4444-55555example",
"SCW_DEFAULT_REGION": "fr-par",
"SCW_DEFAULT_ZONE": "fr-par-1",
},
},
},
})
}
22 changes: 22 additions & 0 deletions plugins/scaleway/plugin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package scaleway

import (
"github.com/1Password/shell-plugins/sdk"
"github.com/1Password/shell-plugins/sdk/schema"
)

func New() schema.Plugin {
return schema.Plugin{
Name: "scaleway",
Platform: schema.PlatformInfo{
Name: "Scaleway",
Homepage: sdk.URL("https://scaleway.com"),
},
Credentials: []schema.CredentialType{
APIKey(),
},
Executables: []schema.Executable{
ScalewayCLI(),
},
}
}
27 changes: 27 additions & 0 deletions plugins/scaleway/scw.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package scaleway

import (
"github.com/1Password/shell-plugins/sdk"
"github.com/1Password/shell-plugins/sdk/needsauth"
"github.com/1Password/shell-plugins/sdk/schema"
"github.com/1Password/shell-plugins/sdk/schema/credname"
)

func ScalewayCLI() schema.Executable {
return schema.Executable{
Name: "Scaleway CLI",
Runs: []string{"scw"},
DocsURL: sdk.URL("https://www.scaleway.com/en/cli"),
NeedsAuth: needsauth.IfAll(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's skip for -p as well, because that applies only when the configuration file exists on the computer. So, it's not relevant when a shell plugin is being used.

needsauth.NotForHelpOrVersion(),
needsauth.NotWithoutArgs(),
needsauth.NotWhenContainsArgs("-c"),
needsauth.NotWhenContainsArgs("--config"),
),
Uses: []schema.CredentialUsage{
{
Name: credname.APIKey,
},
},
}
}
6 changes: 6 additions & 0 deletions plugins/scaleway/test-fixtures/optional.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
access_key: SCWSYXTFI97NSEXAMPLE
secret_key: d9b67b48-873c-8ece-8270-e1e15example
default_organization_id: 11111111-2222-3333-4444-55555example
default_region: fr-par
default_zone: fr-par-1
3 changes: 3 additions & 0 deletions plugins/scaleway/test-fixtures/simple.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
access_key: SCWSYXTFI97NSEXAMPLE
secret_key: d9b67b48-873c-8ece-8270-e1e15example
default_organization_id: 11111111-2222-3333-4444-55555example
99 changes: 52 additions & 47 deletions sdk/schema/fieldname/names.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,53 +4,55 @@ import "github.com/1Password/shell-plugins/sdk"

// Credential field names.
const (
APIHost = sdk.FieldName("API Host")
APIKey = sdk.FieldName("API Key")
APIKeyID = sdk.FieldName("API Key ID")
APISecret = sdk.FieldName("API Secret")
AccessKeyID = sdk.FieldName("Access Key ID")
AccessToken = sdk.FieldName("Access Token")
Account = sdk.FieldName("Account")
AccountID = sdk.FieldName("Account ID")
AccountSID = sdk.FieldName("Account SID")
Address = sdk.FieldName("Address")
AppKey = sdk.FieldName("App Key")
AppSecret = sdk.FieldName("App Secret")
AppToken = sdk.FieldName("App Token")
AuthToken = sdk.FieldName("Auth Token")
Authtoken = sdk.FieldName("Authtoken")
Cert = sdk.FieldName("Cert")
Certificate = sdk.FieldName("Certificate")
ClientSecret = sdk.FieldName("Client Secret")
ClientToken = sdk.FieldName("Client Token")
Credential = sdk.FieldName("Credential")
Credentials = sdk.FieldName("Credentials")
Database = sdk.FieldName("Database")
DefaultRegion = sdk.FieldName("Default Region")
Email = sdk.FieldName("Email")
Endpoint = sdk.FieldName("Endpoint")
Host = sdk.FieldName("Host")
HostAddress = sdk.FieldName("Host Address")
Key = sdk.FieldName("Key")
MFASerial = sdk.FieldName("MFA Serial")
Mode = sdk.FieldName("Mode")
Namespace = sdk.FieldName("Namespace")
OneTimePassword = sdk.FieldName("One-Time Password")
OrgURL = sdk.FieldName("Org URL")
Organization = sdk.FieldName("Organization")
Password = sdk.FieldName("Password")
Port = sdk.FieldName("Port")
PublicKey = sdk.FieldName("Public Key")
PrivateKey = sdk.FieldName("Private Key")
Region = sdk.FieldName("Region")
Secret = sdk.FieldName("Secret")
SecretAccessKey = sdk.FieldName("Secret Access Key")
Subdomain = sdk.FieldName("Subdomain")
Token = sdk.FieldName("Token")
URL = sdk.FieldName("URL")
User = sdk.FieldName("User")
Username = sdk.FieldName("Username")
Website = sdk.FieldName("Website")
APIHost = sdk.FieldName("API Host")
APIKey = sdk.FieldName("API Key")
APIKeyID = sdk.FieldName("API Key ID")
APISecret = sdk.FieldName("API Secret")
AccessKeyID = sdk.FieldName("Access Key ID")
AccessToken = sdk.FieldName("Access Token")
Account = sdk.FieldName("Account")
AccountID = sdk.FieldName("Account ID")
AccountSID = sdk.FieldName("Account SID")
Address = sdk.FieldName("Address")
AppKey = sdk.FieldName("App Key")
AppSecret = sdk.FieldName("App Secret")
AppToken = sdk.FieldName("App Token")
AuthToken = sdk.FieldName("Auth Token")
Authtoken = sdk.FieldName("Authtoken")
Cert = sdk.FieldName("Cert")
Certificate = sdk.FieldName("Certificate")
ClientSecret = sdk.FieldName("Client Secret")
ClientToken = sdk.FieldName("Client Token")
Credential = sdk.FieldName("Credential")
Credentials = sdk.FieldName("Credentials")
Database = sdk.FieldName("Database")
DefaultRegion = sdk.FieldName("Default Region")
DefaultOrganization = sdk.FieldName("Default Organization")
DefaultZone = sdk.FieldName("Default Zone")
Email = sdk.FieldName("Email")
Endpoint = sdk.FieldName("Endpoint")
Host = sdk.FieldName("Host")
HostAddress = sdk.FieldName("Host Address")
Key = sdk.FieldName("Key")
MFASerial = sdk.FieldName("MFA Serial")
Mode = sdk.FieldName("Mode")
Namespace = sdk.FieldName("Namespace")
OneTimePassword = sdk.FieldName("One-Time Password")
OrgURL = sdk.FieldName("Org URL")
Organization = sdk.FieldName("Organization")
Password = sdk.FieldName("Password")
Port = sdk.FieldName("Port")
PublicKey = sdk.FieldName("Public Key")
PrivateKey = sdk.FieldName("Private Key")
Region = sdk.FieldName("Region")
Secret = sdk.FieldName("Secret")
SecretAccessKey = sdk.FieldName("Secret Access Key")
Subdomain = sdk.FieldName("Subdomain")
Token = sdk.FieldName("Token")
URL = sdk.FieldName("URL")
User = sdk.FieldName("User")
Username = sdk.FieldName("Username")
Website = sdk.FieldName("Website")
)

func ListAll() []sdk.FieldName {
Expand Down Expand Up @@ -78,6 +80,8 @@ func ListAll() []sdk.FieldName {
Credentials,
Database,
DefaultRegion,
DefaultOrganization,
DefaultZone,
Endpoint,
Host,
HostAddress,
Expand All @@ -101,4 +105,5 @@ func ListAll() []sdk.FieldName {
Username,
Website,
}

}