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
138 changes: 138 additions & 0 deletions plugins/scaleway/api_key.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
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
}

// TODO : Handle multiple profiles
accraw marked this conversation as resolved.
Show resolved Hide resolved

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

fields := make(map[sdk.FieldName]string)
fields[fieldname.AccessKeyID] = config.AccessKey
fields[fieldname.SecretAccessKey] = config.SecretKey
fields[fieldname.DefaultOrganization] = config.DefaultOrganizationID
accraw marked this conversation as resolved.
Show resolved Hide resolved
accraw marked this conversation as resolved.
Show resolved Hide resolved
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"`
DefaultRegion string `yaml:"default_region"`
DefaultZone string `yaml:"default_zone"`
ActiveProfile string `yaml:"active_profile,omitempty"`
Profiles map[string]Profil `yaml:"profiles,omitempty"`
accraw marked this conversation as resolved.
Show resolved Hide resolved
}

type Profil struct {
accraw marked this conversation as resolved.
Show resolved Hide resolved
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?

DefaultZone string `yaml:"default_zone"`
DefaultRegion string `yaml:"default_region"`
}
79 changes: 79 additions & 0 deletions plugins/scaleway/api_key_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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
},
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 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
},
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",
},
},
},
})
}
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