diff --git a/docs/guides/2-provider-configuration.md b/docs/guides/2-provider-configuration.md index aee44cb92..158c59df3 100644 --- a/docs/guides/2-provider-configuration.md +++ b/docs/guides/2-provider-configuration.md @@ -7,6 +7,8 @@ subcategory: "Guides" ## Example usage +### API Key + `main.tf` ```hcl @@ -25,13 +27,49 @@ provider "octopusdeploy" { } ``` +### Access Token (via Environment Variable) +OIDC Access Tokens are short-lived and typically generated per-run of an automated pipeline, such as GitHub Actions. +If you use the Access Token approach, we recommend sourcing the token from environment variable. + +The environment variable fallback values that the Terraform Provider search for correspond to the values that pipeline steps like our [GitHub Login action](https://github.com/OctopusDeploy/login?tab=readme-ov-file#outputs) set in the pipeline context, so the provider will automatically pick up the value from environment variable. + +`main.tf` + +```hcl +terraform { + required_providers { + octopusdeploy = { + source = OctopusDeployLabs/octopusdeploy + } + } +} + +provider "octopusdeploy" { + space_id = "..." +} +``` + ## Schema ### Required -* `address` (String) The Octopus Deploy server URL. This can also be set using the `OCTOPUS_URL` environment variable. -* `api_key` (String) The Octopus Deploy server API key. This can also be set using the `OCTOPUS_APIKEY` environment variable. +* `address` (String) The Octopus Deploy server URL. + +and one of either +* `api_key` (String) The Octopus Deploy server API key. + +OR +* `access_token` (String) The OIDC Access Token from an OIDC exchange. ### Optional * `space_id` (String) The ID of the space to create the resources in. -**If `space_id` is not specified the default space will be used.** \ No newline at end of file +**If `space_id` is not specified the default space will be used.** + +### Environment Variable fallback +The following priority order will be used to calculate the final value for these configuration items: + +| Configuration Item | Priority Order | +|--------------------|--------------------------------------------------------------------------------------------------| +| `address` | 1. Provider Configuration Block
2. env: `OCTOPUS_URL` | +| `api_key` | 1. Provider Configuration Block
2. env: `OCTOPUS_APIKEY`
3. env: `OCTOPUS_API_KEY` | +| `access_token` | 1. Provider Configuration Block
2. env: `OCTOPUS_ACCESS_TOKEN` | diff --git a/docs/index.md b/docs/index.md index e0fadfb5f..634c4f849 100644 --- a/docs/index.md +++ b/docs/index.md @@ -17,6 +17,13 @@ This provider is used to configure resources in Octopus Deploy. The provider mus ## Configuration +### Authentication Methods +The provider supports authenticating to an Octopus Server instance via either: +* API Key +* OIDC Access Token + +These are mutually exclusive options - use either, not both. For backward compatibility, API Key will always be preferred over OIDC, when an API Key is present. + ### Default Space Octopus Deploy supports the concept of a Default Space. This is the first space that is automatically created on server setup. If you do not specify a Space when configuring the Octopus Deploy Terraform provider it will use the Default Space. @@ -81,6 +88,7 @@ resource "octopusdeploy_environment" "Env3" { ### Optional +- `access_token` (String) The OIDC Access Token to use with the Octopus REST API - `address` (String) The endpoint of the Octopus REST API - `api_key` (String) The API key to use with the Octopus REST API - `space_id` (String) The space ID to target \ No newline at end of file diff --git a/octopusdeploy/config.go b/octopusdeploy/config.go index e084da8eb..037171ed1 100644 --- a/octopusdeploy/config.go +++ b/octopusdeploy/config.go @@ -1,6 +1,7 @@ package octopusdeploy import ( + "fmt" "net/url" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" @@ -10,19 +11,15 @@ import ( // Config holds Address and the APIKey of the Octopus Deploy server type Config struct { - Address string - APIKey string - SpaceID string + Address string + APIKey string + AccessToken string + SpaceID string } // Client returns a new Octopus Deploy client func (c *Config) Client() (*client.Client, diag.Diagnostics) { - apiURL, err := url.Parse(c.Address) - if err != nil { - return nil, diag.FromErr(err) - } - - octopus, err := client.NewClient(nil, apiURL, c.APIKey, "") + octopus, err := getClientForDefaultSpace(c) if err != nil { return nil, diag.FromErr(err) } @@ -33,7 +30,7 @@ func (c *Config) Client() (*client.Client, diag.Diagnostics) { return nil, diag.FromErr(err) } - octopus, err = client.NewClient(nil, apiURL, c.APIKey, space.GetID()) + octopus, err = getClientForSpace(c, space.GetID()) if err != nil { return nil, diag.FromErr(err) } @@ -41,3 +38,43 @@ func (c *Config) Client() (*client.Client, diag.Diagnostics) { return octopus, nil } + +func getClientForDefaultSpace(c *Config) (*client.Client, error) { + return getClientForSpace(c, "") +} + +func getClientForSpace(c *Config, spaceID string) (*client.Client, error) { + apiURL, err := url.Parse(c.Address) + if err != nil { + return nil, err + } + + credential, err := getApiCredential(c) + if err != nil { + return nil, err + } + + return client.NewClientWithCredentials(nil, apiURL, credential, spaceID, "TerraformProvider") +} + +func getApiCredential(c *Config) (client.ICredential, error) { + if c.APIKey != "" { + credential, err := client.NewApiKey(c.APIKey) + if err != nil { + return nil, err + } + + return credential, nil + } + + if c.AccessToken != "" { + credential, err := client.NewAccessToken(c.AccessToken) + if err != nil { + return nil, err + } + + return credential, nil + } + + return nil, fmt.Errorf("either an APIKey or an AccessToken is required to connect to the Octopus Server instance") +} diff --git a/octopusdeploy/provider.go b/octopusdeploy/provider.go index 5f1a0822f..3b281bfbb 100644 --- a/octopusdeploy/provider.go +++ b/octopusdeploy/provider.go @@ -76,11 +76,17 @@ func Provider() *schema.Provider { Type: schema.TypeString, }, "api_key": { - DefaultFunc: schema.EnvDefaultFunc("OCTOPUS_APIKEY", nil), + DefaultFunc: schema.MultiEnvDefaultFunc([]string{"OCTOPUS_APIKEY", "OCTOPUS_API_KEY"}, nil), Description: "The API key to use with the Octopus REST API", Optional: true, Type: schema.TypeString, }, + "access_token": { + DefaultFunc: schema.EnvDefaultFunc("OCTOPUS_ACCESS_TOKEN", nil), + Description: "The OIDC Access Token to use with the Octopus REST API", + Optional: true, + Type: schema.TypeString, + }, "space_id": { Description: "The space ID to target", Optional: true, @@ -94,8 +100,9 @@ func Provider() *schema.Provider { func providerConfigure(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { config := Config{ - Address: d.Get("address").(string), - APIKey: d.Get("api_key").(string), + AccessToken: d.Get("access_token").(string), + Address: d.Get("address").(string), + APIKey: d.Get("api_key").(string), } if spaceID, ok := d.GetOk("space_id"); ok { config.SpaceID = spaceID.(string) diff --git a/octopusdeploy_framework/config.go b/octopusdeploy_framework/config.go index 96bf2da70..ef106320d 100644 --- a/octopusdeploy_framework/config.go +++ b/octopusdeploy_framework/config.go @@ -12,20 +12,17 @@ import ( ) type Config struct { - Address string - ApiKey string - SpaceID string - Client *client.Client + Address string + ApiKey string + AccessToken string + SpaceID string + Client *client.Client } func (c *Config) GetClient(ctx context.Context) error { tflog.Debug(ctx, "GetClient") - apiURL, err := url.Parse(c.Address) - if err != nil { - return err - } - octopus, err := client.NewClient(nil, apiURL, c.ApiKey, "") + octopus, err := getClientForDefaultSpace(c, ctx) if err != nil { return err } @@ -36,7 +33,7 @@ func (c *Config) GetClient(ctx context.Context) error { return err } - octopus, err = client.NewClient(nil, apiURL, c.ApiKey, space.GetID()) + octopus, err = getClientForSpace(c, ctx, space.GetID()) if err != nil { return err } @@ -49,6 +46,54 @@ func (c *Config) GetClient(ctx context.Context) error { return nil } +func getClientForDefaultSpace(c *Config, ctx context.Context) (*client.Client, error) { + return getClientForSpace(c, ctx, "") +} + +func getClientForSpace(c *Config, ctx context.Context, spaceID string) (*client.Client, error) { + apiURL, err := url.Parse(c.Address) + if err != nil { + return nil, err + } + + credential, err := getApiCredential(c, ctx) + if err != nil { + return nil, err + } + + return client.NewClientWithCredentials(nil, apiURL, credential, spaceID, "TerraformProvider") +} + +func getApiCredential(c *Config, ctx context.Context) (client.ICredential, error) { + tflog.Debug(ctx, "GetClient: Trying the following auth methods in order of priority - APIKey, AccessToken") + + if c.ApiKey != "" { + tflog.Debug(ctx, "GetClient: Attempting to authenticate with API Key") + credential, err := client.NewApiKey(c.ApiKey) + if err != nil { + return nil, err + } + + return credential, nil + } else { + tflog.Debug(ctx, "GetClient: No API Key found") + } + + if c.AccessToken != "" { + tflog.Debug(ctx, "GetClient: Attempting to authenticate with Access Token") + credential, err := client.NewAccessToken(c.AccessToken) + if err != nil { + return nil, err + } + + return credential, nil + } else { + tflog.Debug(ctx, "GetClient: No Access Token found") + } + + return nil, fmt.Errorf("either an APIKey or an AccessToken is required to connect to the Octopus Server instance") +} + func DataSourceConfiguration(req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) *Config { if req.ProviderData == nil { return nil diff --git a/octopusdeploy_framework/framework_provider.go b/octopusdeploy_framework/framework_provider.go index 265eeac96..6cce7bc07 100644 --- a/octopusdeploy_framework/framework_provider.go +++ b/octopusdeploy_framework/framework_provider.go @@ -13,9 +13,10 @@ import ( ) type octopusDeployFrameworkProvider struct { - Address types.String `tfsdk:"address"` - ApiKey types.String `tfsdk:"api_key"` - SpaceID types.String `tfsdk:"space_id"` + Address types.String `tfsdk:"address"` + ApiKey types.String `tfsdk:"api_key"` + AccessToken types.String `tfsdk:"access_token"` + SpaceID types.String `tfsdk:"space_id"` } var _ provider.Provider = (*octopusDeployFrameworkProvider)(nil) @@ -45,6 +46,12 @@ func (p *octopusDeployFrameworkProvider) Configure(ctx context.Context, req prov if config.ApiKey == "" { config.ApiKey = os.Getenv("OCTOPUS_APIKEY") } + if config.ApiKey == "" { + config.ApiKey = os.Getenv("OCTOPUS_API_KEY") + } + if config.AccessToken == "" { + config.AccessToken = os.Getenv("OCTOPUS_ACCESS_TOKEN") + } config.Address = providerData.Address.ValueString() if config.Address == "" { config.Address = os.Getenv("OCTOPUS_URL") @@ -118,6 +125,10 @@ func (p *octopusDeployFrameworkProvider) Schema(_ context.Context, req provider. Optional: true, Description: "The API key to use with the Octopus REST API", }, + "access_token": schema.StringAttribute{ + Optional: true, + Description: "The OIDC Access Token to use with the Octopus REST API", + }, "space_id": schema.StringAttribute{ Optional: true, Description: "The space ID to target", diff --git a/templates/guides/2-provider-configuration.md.tmpl b/templates/guides/2-provider-configuration.md.tmpl index aee44cb92..158c59df3 100644 --- a/templates/guides/2-provider-configuration.md.tmpl +++ b/templates/guides/2-provider-configuration.md.tmpl @@ -7,6 +7,8 @@ subcategory: "Guides" ## Example usage +### API Key + `main.tf` ```hcl @@ -25,13 +27,49 @@ provider "octopusdeploy" { } ``` +### Access Token (via Environment Variable) +OIDC Access Tokens are short-lived and typically generated per-run of an automated pipeline, such as GitHub Actions. +If you use the Access Token approach, we recommend sourcing the token from environment variable. + +The environment variable fallback values that the Terraform Provider search for correspond to the values that pipeline steps like our [GitHub Login action](https://github.com/OctopusDeploy/login?tab=readme-ov-file#outputs) set in the pipeline context, so the provider will automatically pick up the value from environment variable. + +`main.tf` + +```hcl +terraform { + required_providers { + octopusdeploy = { + source = OctopusDeployLabs/octopusdeploy + } + } +} + +provider "octopusdeploy" { + space_id = "..." +} +``` + ## Schema ### Required -* `address` (String) The Octopus Deploy server URL. This can also be set using the `OCTOPUS_URL` environment variable. -* `api_key` (String) The Octopus Deploy server API key. This can also be set using the `OCTOPUS_APIKEY` environment variable. +* `address` (String) The Octopus Deploy server URL. + +and one of either +* `api_key` (String) The Octopus Deploy server API key. + +OR +* `access_token` (String) The OIDC Access Token from an OIDC exchange. ### Optional * `space_id` (String) The ID of the space to create the resources in. -**If `space_id` is not specified the default space will be used.** \ No newline at end of file +**If `space_id` is not specified the default space will be used.** + +### Environment Variable fallback +The following priority order will be used to calculate the final value for these configuration items: + +| Configuration Item | Priority Order | +|--------------------|--------------------------------------------------------------------------------------------------| +| `address` | 1. Provider Configuration Block
2. env: `OCTOPUS_URL` | +| `api_key` | 1. Provider Configuration Block
2. env: `OCTOPUS_APIKEY`
3. env: `OCTOPUS_API_KEY` | +| `access_token` | 1. Provider Configuration Block
2. env: `OCTOPUS_ACCESS_TOKEN` | diff --git a/templates/index.md.tmpl b/templates/index.md.tmpl index 16c88cd8d..2f907216f 100644 --- a/templates/index.md.tmpl +++ b/templates/index.md.tmpl @@ -17,6 +17,13 @@ This provider is used to configure resources in Octopus Deploy. The provider mus ## Configuration +### Authentication Methods +The provider supports authenticating to an Octopus Server instance via either: +* API Key +* OIDC Access Token + +These are mutually exclusive options - use either, not both. For backward compatibility, API Key will always be preferred over OIDC, when an API Key is present. + ### Default Space Octopus Deploy supports the concept of a Default Space. This is the first space that is automatically created on server setup. If you do not specify a Space when configuring the Octopus Deploy Terraform provider it will use the Default Space.