From 31e62438f896620643e3b04953d2c3a5a6878f9f Mon Sep 17 00:00:00 2001 From: xcezx <111251+xcezx@users.noreply.github.com> Date: Sun, 5 Jul 2026 06:50:36 +0900 Subject: [PATCH] Add Octopus Deploy shell plugin Add a shell plugin for the Octopus Deploy CLI (octopus) that provisions OCTOPUS_API_KEY, OCTOPUS_URL, and OCTOPUS_SPACE from 1Password. Supports importing existing credentials from environment variables and from the octopus login config file (~/.config/octopus/cli_config.json). Add the "Space" field name to the SDK for OCTOPUS_SPACE. --- plugins/octopus/api_key.go | 90 +++++++++++++++++++ plugins/octopus/api_key_test.go | 63 +++++++++++++ plugins/octopus/octopus.go | 27 ++++++ plugins/octopus/plugin.go | 22 +++++ plugins/octopus/test-fixtures/cli_config.json | 6 ++ sdk/schema/fieldname/names.go | 2 + 6 files changed, 210 insertions(+) create mode 100644 plugins/octopus/api_key.go create mode 100644 plugins/octopus/api_key_test.go create mode 100644 plugins/octopus/octopus.go create mode 100644 plugins/octopus/plugin.go create mode 100644 plugins/octopus/test-fixtures/cli_config.json diff --git a/plugins/octopus/api_key.go b/plugins/octopus/api_key.go new file mode 100644 index 00000000..0dd4deae --- /dev/null +++ b/plugins/octopus/api_key.go @@ -0,0 +1,90 @@ +package octopus + +import ( + "context" + + "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://octopus.com/docs/octopus-rest-api/how-to-create-an-api-key"), + ManagementURL: nil, + Fields: []schema.CredentialField{ + { + Name: fieldname.APIKey, + MarkdownDescription: "API Key used to authenticate to Octopus Deploy.", + Secret: true, + Composition: &schema.ValueComposition{ + Charset: schema.Charset{ + Uppercase: true, + Digits: true, + }, + Prefix: "API-", + }, + }, + { + Name: fieldname.URL, + MarkdownDescription: "URL of the Octopus Deploy server.", + Optional: true, + }, + { + Name: fieldname.Space, + MarkdownDescription: "Space to use for the Octopus Deploy CLI.", + Optional: true, + }, + }, + DefaultProvisioner: provision.EnvVars(defaultEnvVarMapping), + Importer: importer.TryAll( + importer.TryEnvVarPair(defaultEnvVarMapping), + TryOctopusDeployConfigFile(), + )} +} + +var defaultEnvVarMapping = map[string]sdk.FieldName{ + "OCTOPUS_API_KEY": fieldname.APIKey, + "OCTOPUS_URL": fieldname.URL, + "OCTOPUS_SPACE": fieldname.Space, +} + +func TryOctopusDeployConfigFile() sdk.Importer { + return importer.TryFile("~/.config/octopus/cli_config.json", func(ctx context.Context, contents importer.FileContents, in sdk.ImportInput, out *sdk.ImportAttempt) { + var config Config + if err := contents.ToJSON(&config); err != nil { + out.AddError(err) + return + } + + if config.APIKey == "" { + return + } + + fields := map[sdk.FieldName]string{ + fieldname.APIKey: config.APIKey, + } + + if config.URL != "" { + fields[fieldname.URL] = config.URL + } + + if config.Space != "" { + fields[fieldname.Space] = config.Space + } + + out.AddCandidate(sdk.ImportCandidate{ + Fields: fields, + }) + }) +} + +type Config struct { + APIKey string `json:"apikey"` + URL string `json:"url"` + Space string `json:"space"` +} diff --git a/plugins/octopus/api_key_test.go b/plugins/octopus/api_key_test.go new file mode 100644 index 00000000..b7cb7bf0 --- /dev/null +++ b/plugins/octopus/api_key_test.go @@ -0,0 +1,63 @@ +package octopus + +import ( + "testing" + + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/plugintest" + "github.com/1Password/shell-plugins/sdk/schema/fieldname" +) + +func TestAPIKeyProvisioner(t *testing.T) { + plugintest.TestProvisioner(t, APIKey().DefaultProvisioner, map[string]plugintest.ProvisionCase{ + "default": { + ItemFields: map[sdk.FieldName]string{ + fieldname.APIKey: "API-PFIPLPKUTCYAWJXUREGWTUYNYEEXAMPLE", + fieldname.URL: "https://my.octopus.app", + fieldname.Space: "Default", + }, + ExpectedOutput: sdk.ProvisionOutput{ + Environment: map[string]string{ + "OCTOPUS_API_KEY": "API-PFIPLPKUTCYAWJXUREGWTUYNYEEXAMPLE", + "OCTOPUS_URL": "https://my.octopus.app", + "OCTOPUS_SPACE": "Default", + }, + }, + }, + }) +} + +func TestAPIKeyImporter(t *testing.T) { + plugintest.TestImporter(t, APIKey().Importer, map[string]plugintest.ImportCase{ + "environment": { + Environment: map[string]string{ + "OCTOPUS_API_KEY": "API-PFIPLPKUTCYAWJXUREGWTUYNYEEXAMPLE", + "OCTOPUS_URL": "https://my.octopus.app", + "OCTOPUS_SPACE": "Default", + }, + ExpectedCandidates: []sdk.ImportCandidate{ + { + Fields: map[sdk.FieldName]string{ + fieldname.APIKey: "API-PFIPLPKUTCYAWJXUREGWTUYNYEEXAMPLE", + fieldname.URL: "https://my.octopus.app", + fieldname.Space: "Default", + }, + }, + }, + }, + "config file": { + Files: map[string]string{ + "~/.config/octopus/cli_config.json": plugintest.LoadFixture(t, "cli_config.json"), + }, + ExpectedCandidates: []sdk.ImportCandidate{ + { + Fields: map[sdk.FieldName]string{ + fieldname.APIKey: "API-PFIPLPKUTCYAWJXUREGWTUYNYEEXAMPLE", + fieldname.URL: "https://my.octopus.app", + fieldname.Space: "Default", + }, + }, + }, + }, + }) +} diff --git a/plugins/octopus/octopus.go b/plugins/octopus/octopus.go new file mode 100644 index 00000000..b194616c --- /dev/null +++ b/plugins/octopus/octopus.go @@ -0,0 +1,27 @@ +package octopus + +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 OctopusDeployCLI() schema.Executable { + return schema.Executable{ + Name: "Octopus CLI", + Runs: []string{"octopus"}, + DocsURL: sdk.URL("https://octopus.com/docs/octopus-rest-api/cli"), + NeedsAuth: needsauth.IfAll( + needsauth.NotForHelpOrVersion(), + needsauth.NotWhenContainsArgs("login"), + needsauth.NotWhenContainsArgs("logout"), + needsauth.NotWhenContainsArgs("config"), + ), + Uses: []schema.CredentialUsage{ + { + Name: credname.APIKey, + }, + }, + } +} diff --git a/plugins/octopus/plugin.go b/plugins/octopus/plugin.go new file mode 100644 index 00000000..ae978c69 --- /dev/null +++ b/plugins/octopus/plugin.go @@ -0,0 +1,22 @@ +package octopus + +import ( + "github.com/1Password/shell-plugins/sdk" + "github.com/1Password/shell-plugins/sdk/schema" +) + +func New() schema.Plugin { + return schema.Plugin{ + Name: "octopus", + Platform: schema.PlatformInfo{ + Name: "Octopus Deploy", + Homepage: sdk.URL("https://octopus.com"), + }, + Credentials: []schema.CredentialType{ + APIKey(), + }, + Executables: []schema.Executable{ + OctopusDeployCLI(), + }, + } +} diff --git a/plugins/octopus/test-fixtures/cli_config.json b/plugins/octopus/test-fixtures/cli_config.json new file mode 100644 index 00000000..dbd1d878 --- /dev/null +++ b/plugins/octopus/test-fixtures/cli_config.json @@ -0,0 +1,6 @@ +{ + "accesstoken": "", + "apikey": "API-PFIPLPKUTCYAWJXUREGWTUYNYEEXAMPLE", + "space": "Default", + "url": "https://my.octopus.app" +} diff --git a/sdk/schema/fieldname/names.go b/sdk/schema/fieldname/names.go index 4aac615a..67019d97 100644 --- a/sdk/schema/fieldname/names.go +++ b/sdk/schema/fieldname/names.go @@ -53,6 +53,7 @@ const ( Region = sdk.FieldName("Region") Secret = sdk.FieldName("Secret") SecretAccessKey = sdk.FieldName("Secret Access Key") + Space = sdk.FieldName("Space") Subdomain = sdk.FieldName("Subdomain") Token = sdk.FieldName("Token") URL = sdk.FieldName("URL") @@ -110,6 +111,7 @@ func ListAll() []sdk.FieldName { Region, Secret, SecretAccessKey, + Space, Token, URL, User,