Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ jobs:
- name: Validate Examples (skip provider directory)
run: |
for dir in examples/*/; do
if [[ "$(basename "$dir")" == "provider" ]]; then
echo "Skipping provider directory (requires published provider)"
if [[ "$(basename "$dir")" == "provider" || "$(basename "$dir")" == "ephemeral-credentials" ]]; then
echo "Skipping $(basename "$dir") directory (requires published provider)"
continue
fi
echo "Validating $dir"
Expand Down
2 changes: 2 additions & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ provider "eon" {
client_id = var.eon_client_id
client_secret = var.eon_client_secret
project_id = var.eon_project_id
token = var.eon_token
}
```

Expand Down Expand Up @@ -96,3 +97,4 @@ provider "eon" {
- `client_secret` (String, Sensitive) Eon API client secret for authentication. Can also be set with the `EON_CLIENT_SECRET` environment variable.
- `endpoint` (String) Eon API base URL in the format `https://<your-domain>.console.eon.io` (no trailing slash). Can also be set with the `EON_ENDPOINT` environment variable.
- `project_id` (String) Eon project ID. Can also be set with the `EON_PROJECT_ID` environment variable.
- `token` (String, Sensitive) Eon API token for authentication. Can also be set with the `EON_TOKEN` environment variable.
6 changes: 3 additions & 3 deletions docs/resources/backup_policy.md
Original file line number Diff line number Diff line change
Expand Up @@ -577,11 +577,11 @@ Required:

Optional:

- `daily_config` (Attributes) Daily configuration (see [below for nested schema](#nestedatt--backup_plan--standard_plan--backup_schedules--schedule_config--daily_config))
- `weekly_config` (Attributes) Weekly configuration (see [below for nested schema](#nestedatt--backup_plan--standard_plan--backup_schedules--schedule_config--weekly_config))
- `monthly_config` (Attributes) Monthly configuration (see [below for nested schema](#nestedatt--backup_plan--standard_plan--backup_schedules--schedule_config--monthly_config))
- `annually_config` (Attributes) Annually configuration (see [below for nested schema](#nestedatt--backup_plan--standard_plan--backup_schedules--schedule_config--annually_config))
- `daily_config` (Attributes) Daily configuration (see [below for nested schema](#nestedatt--backup_plan--standard_plan--backup_schedules--schedule_config--daily_config))
- `interval_config` (Attributes) Interval configuration (see [below for nested schema](#nestedatt--backup_plan--standard_plan--backup_schedules--schedule_config--interval_config))
- `monthly_config` (Attributes) Monthly configuration (see [below for nested schema](#nestedatt--backup_plan--standard_plan--backup_schedules--schedule_config--monthly_config))
- `weekly_config` (Attributes) Weekly configuration (see [below for nested schema](#nestedatt--backup_plan--standard_plan--backup_schedules--schedule_config--weekly_config))

<a id="nestedatt--backup_plan--standard_plan--backup_schedules--schedule_config--annually_config"></a>
### Nested Schema for `backup_plan.standard_plan.backup_schedules.schedule_config.annually_config`
Expand Down
20 changes: 20 additions & 0 deletions examples/ephemeral-credentials/provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
variable "client_id" {
type = string
default = "Eon API client ID"
sensitive = true
ephemeral = true
}

variable "client_secret" {
type = string
default = "Eon API client secret"
sensitive = true
ephemeral = true
}

provider "eon" {
endpoint = "https://your-eon-endpoint.co"
client_id = var.client_id
client_secret = var.client_secret
project_id = "Eon project ID"
}
1 change: 1 addition & 0 deletions examples/provider/provider.tf
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ provider "eon" {
client_id = var.eon_client_id
client_secret = var.eon_client_secret
project_id = var.eon_project_id
token = var.eon_token
}
8 changes: 7 additions & 1 deletion examples/provider/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,10 @@ variable "eon_client_secret" {
variable "eon_project_id" {
description = "Eon project ID"
type = string
}
}

variable "eon_token" {
description = "Eon API token"
type = string
sensitive = true
}
22 changes: 22 additions & 0 deletions internal/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,28 @@ type EonClient struct {
endpoint string
}

func NewEonClientWithToken(endpoint, projectID, token string) (*EonClient, error) {
config := externalEonSdkAPI.NewConfiguration()
config.Servers = []externalEonSdkAPI.ServerConfiguration{
{
URL: fmt.Sprintf("%s/api", endpoint),
},
}

client := &EonClient{
client: externalEonSdkAPI.NewAPIClient(config),
ProjectID: projectID,
authToken: token,
endpoint: endpoint,
}

client.tokenExpiry = time.Now().Add(time.Duration(60 * 60 * 24 * time.Second)) // 1 day

client.client.GetConfig().DefaultHeader["Authorization"] = "Bearer " + token

return client, nil
}

// NewEonClient creates a new Eon API client with the provided configuration
func NewEonClient(endpoint, clientID, clientSecret, projectID string) (*EonClient, error) {
config := externalEonSdkAPI.NewConfiguration()
Expand Down
9 changes: 6 additions & 3 deletions internal/provider/basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ func TestEonProvider_Schema(t *testing.T) {
assert.Contains(t, resp.Schema.Attributes, "client_id")
assert.Contains(t, resp.Schema.Attributes, "client_secret")
assert.Contains(t, resp.Schema.Attributes, "project_id")
assert.Contains(t, resp.Schema.Attributes, "token")
}

// TestEonProvider_Resources tests the provider resources registration
Expand Down Expand Up @@ -83,12 +84,14 @@ func TestEonProviderModel(t *testing.T) {
ClientId: types.StringValue("test-client-id"),
ClientSecret: types.StringValue("test-client-secret"),
ProjectId: types.StringValue("test-project-id"),
Token: types.StringValue("test-token"),
}

assert.Equal(t, "https://test.eon.io", model.Endpoint.ValueString())
assert.Equal(t, "test-client-id", model.ClientId.ValueString())
assert.Equal(t, "test-client-secret", model.ClientSecret.ValueString())
assert.Equal(t, "test-project-id", model.ProjectId.ValueString())
assert.Equal(t, "test-token", model.Token.ValueString())
}

// TestEonProvider_StringValues tests string value handling
Expand Down Expand Up @@ -210,11 +213,11 @@ func TestEonProvider_ProviderSchema(t *testing.T) {
require.NotNil(t, resp.Schema)
assert.False(t, resp.Diagnostics.HasError())

// Test that we have exactly 4 attributes
assert.Equal(t, 4, len(resp.Schema.Attributes))
// Test that we have exactly 5 attributes
assert.Equal(t, 5, len(resp.Schema.Attributes))

// Test attribute names
expectedAttributes := []string{"endpoint", "client_id", "client_secret", "project_id"}
expectedAttributes := []string{"endpoint", "client_id", "client_secret", "project_id", "token"}
for _, attr := range expectedAttributes {
assert.Contains(t, resp.Schema.Attributes, attr)
}
Expand Down
47 changes: 33 additions & 14 deletions internal/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type EonProviderModel struct {
ClientId types.String `tfsdk:"client_id"`
ClientSecret types.String `tfsdk:"client_secret"`
ProjectId types.String `tfsdk:"project_id"`
Token types.String `tfsdk:"token"`
}

// New creates a new provider instance.
Expand Down Expand Up @@ -68,6 +69,11 @@ func (p *EonProvider) Schema(ctx context.Context, req provider.SchemaRequest, re
MarkdownDescription: "Eon project ID. Can also be set with the `EON_PROJECT_ID` environment variable.",
Optional: true,
},
"token": schema.StringAttribute{
MarkdownDescription: "Eon API token for authentication. Can also be set with the `EON_TOKEN` environment variable.",
Optional: true,
Sensitive: true,
},
},
}
}
Expand All @@ -85,6 +91,7 @@ func (p *EonProvider) Configure(ctx context.Context, req provider.ConfigureReque
clientId := os.Getenv("EON_CLIENT_ID")
clientSecret := os.Getenv("EON_CLIENT_SECRET")
projectId := os.Getenv("EON_PROJECT_ID")
token := os.Getenv("EON_TOKEN")

if !data.Endpoint.IsNull() {
endpoint = data.Endpoint.ValueString()
Expand All @@ -102,6 +109,10 @@ func (p *EonProvider) Configure(ctx context.Context, req provider.ConfigureReque
projectId = data.ProjectId.ValueString()
}

if !data.Token.IsNull() {
token = data.Token.ValueString()
}

// Validate required fields
if endpoint == "" {
resp.Diagnostics.AddAttributeError(
Expand All @@ -111,20 +122,22 @@ func (p *EonProvider) Configure(ctx context.Context, req provider.ConfigureReque
)
}

if clientId == "" {
resp.Diagnostics.AddAttributeError(
path.Root("client_id"),
"Missing Eon Client ID",
"The provider requires a client ID. Set the client_id value in the configuration or use the `EON_CLIENT_ID` environment variable.",
)
}
if token == "" {
if clientId == "" {
resp.Diagnostics.AddAttributeError(
path.Root("client_id"),
"Missing Eon Client ID",
"The provider requires either a token or a client ID. Set the client_id value in the configuration or use the `EON_CLIENT_ID` environment variable.",
)
}

if clientSecret == "" {
resp.Diagnostics.AddAttributeError(
path.Root("client_secret"),
"Missing Eon Client Secret",
"The provider requires a client secret. Set the client_secret value in the configuration or use the `EON_CLIENT_SECRET` environment variable.",
)
if clientSecret == "" {
resp.Diagnostics.AddAttributeError(
path.Root("client_secret"),
"Missing Eon Client Secret",
"The provider requires either a token or a client secret. Set the client_secret value in the configuration or use the `EON_CLIENT_SECRET` environment variable.",
)
}
}

if projectId == "" {
Expand All @@ -140,7 +153,13 @@ func (p *EonProvider) Configure(ctx context.Context, req provider.ConfigureReque
}

// Create Eon client
eonClient, err := client.NewEonClient(endpoint, clientId, clientSecret, projectId)
var eonClient *client.EonClient
var err error
if token == "" {
eonClient, err = client.NewEonClient(endpoint, clientId, clientSecret, projectId)
} else {
eonClient, err = client.NewEonClientWithToken(endpoint, projectId, token)
}
if err != nil {
resp.Diagnostics.AddError(
"Unable to Create Eon API Client",
Expand Down
143 changes: 143 additions & 0 deletions internal/provider/provider_token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package provider

import (
"context"
"os"
"testing"

"github.com/hashicorp/terraform-plugin-framework/provider"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

// TestEonProvider_TokenFromConfig tests that token is properly read from provider config
func TestEonProvider_TokenFromConfig(t *testing.T) {
// Create a config with token
model := EonProviderModel{
Endpoint: types.StringValue("https://test.eon.io"),
ProjectId: types.StringValue("test-project-id"),
Token: types.StringValue("test-token-value"),
}

assert.Equal(t, "test-token-value", model.Token.ValueString())
assert.False(t, model.Token.IsNull())
}

// TestEonProvider_TokenFromEnvironment tests that token is read from EON_TOKEN env var
func TestEonProvider_TokenFromEnvironment(t *testing.T) {
// Set environment variable
originalToken := os.Getenv("EON_TOKEN")
defer os.Setenv("EON_TOKEN", originalToken)

os.Setenv("EON_TOKEN", "env-token-value")

// Verify we can read it
token := os.Getenv("EON_TOKEN")
assert.Equal(t, "env-token-value", token)
}

// TestEonProvider_TokenInSchema tests that token attribute is properly defined in schema
func TestEonProvider_TokenInSchema(t *testing.T) {
p := &EonProvider{}

req := provider.SchemaRequest{}
resp := &provider.SchemaResponse{}

p.Schema(context.Background(), req, resp)

require.NotNil(t, resp.Schema)
assert.Contains(t, resp.Schema.Attributes, "token")

tokenAttr := resp.Schema.Attributes["token"]
assert.NotNil(t, tokenAttr)
}

// TestEonProvider_TokenSensitivity tests that token is marked as sensitive
func TestEonProvider_TokenSensitivity(t *testing.T) {
p := &EonProvider{}

req := provider.SchemaRequest{}
resp := &provider.SchemaResponse{}

p.Schema(context.Background(), req, resp)

require.NotNil(t, resp.Schema)
require.Contains(t, resp.Schema.Attributes, "token")

// The token attribute should exist
// In terraform-plugin-framework, sensitive is a property of StringAttribute
// We're just verifying it exists and is accessible
assert.NotNil(t, resp.Schema.Attributes["token"])
}

// TestEonProvider_AllAuthenticationMethods tests different authentication methods
func TestEonProvider_AllAuthenticationMethods(t *testing.T) {
testCases := []struct {
name string
endpoint string
clientId string
clientSecret string
projectId string
token string
description string
}{
{
name: "token_only",
endpoint: "https://test.eon.io",
projectId: "project-123",
token: "token-value",
description: "Authentication with token only",
},
{
name: "client_credentials_only",
endpoint: "https://test.eon.io",
clientId: "client-id",
clientSecret: "client-secret",
projectId: "project-123",
description: "Authentication with client credentials only",
},
{
name: "token_preferred_when_both",
endpoint: "https://test.eon.io",
clientId: "client-id",
clientSecret: "client-secret",
projectId: "project-123",
token: "token-value",
description: "Token should be preferred when both methods provided",
},
}

for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
model := EonProviderModel{
Endpoint: types.StringValue(tc.endpoint),
ProjectId: types.StringValue(tc.projectId),
}

if tc.clientId != "" {
model.ClientId = types.StringValue(tc.clientId)
}
if tc.clientSecret != "" {
model.ClientSecret = types.StringValue(tc.clientSecret)
}
if tc.token != "" {
model.Token = types.StringValue(tc.token)
}

// Verify the model was constructed correctly
assert.Equal(t, tc.endpoint, model.Endpoint.ValueString())
assert.Equal(t, tc.projectId, model.ProjectId.ValueString())

if tc.token != "" {
assert.Equal(t, tc.token, model.Token.ValueString())
}
if tc.clientId != "" {
assert.Equal(t, tc.clientId, model.ClientId.ValueString())
}
if tc.clientSecret != "" {
assert.Equal(t, tc.clientSecret, model.ClientSecret.ValueString())
}
})
}
}
Loading