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

Onboard object storage credentials group #74

Merged
merged 22 commits into from
Oct 12, 2023
Merged
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 docs/data-sources/objectstorage_bucket.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@
page_title: "stackit_objectstorage_bucket Data Source - stackit"
subcategory: ""
description: |-
ObjectStorage credential data source schema.
ObjectStorage bucket data source schema.
---

# stackit_objectstorage_bucket (Data Source)

ObjectStorage credential data source schema.
ObjectStorage bucket data source schema.



Expand Down
30 changes: 30 additions & 0 deletions docs/data-sources/objectstorage_credentials_group.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackit_objectstorage_credentials_group Data Source - stackit"
subcategory: ""
description: |-
ObjectStorage credentials group data source schema.
---

# stackit_objectstorage_credentials_group (Data Source)

ObjectStorage credentials group data source schema.



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `project_id` (String) Object Storage Project ID to which the credentials group is associated.

### Optional

- `credentials_group_id` (String) Terraform's internal data source identifier. It is structured as "`project_id`,`credentials_group_id`".
- `name` (String) The credentials group's display name.

### Read-Only

- `id` (String) Terraform's internal data source identifier. It is structured as "`project_id`,`credentials_group_id`".
- `urn` (String) Credentials group uniform resource name (URN)
27 changes: 27 additions & 0 deletions docs/resources/objectstorage_credentials_group.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "stackit_objectstorage_credentials_group Resource - stackit"
subcategory: ""
description: |-
ObjectStorage credentials group resource schema.
---

# stackit_objectstorage_credentials_group (Resource)

ObjectStorage credentials group resource schema.



<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `name` (String) The credentials group's display name.
- `project_id` (String) Project ID to which the credentials group is associated.

### Read-Only

- `credentials_group_id` (String) The credentials group ID
- `id` (String) Terraform's internal data source identifier. It is structured as "`project_id`,`credentials_group_id`".
- `urn` (String) Credentials group uniform resource name (URN)
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
data "stackit_objectstorage_bucket" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
bucket_name = "example-name"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
data "stackit_objectstorage_credentials_group" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
credentials_group_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
}
4 changes: 4 additions & 0 deletions examples/resources/stackit_object_storage_bucket/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
resource "stackit_object_storage_credentials_group" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
name = "example-credentials-group"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
resource "stackit_object_storage_bucket" "example" {
project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
bucket_name = "example-bucket"
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func (r *bucketDataSource) Configure(ctx context.Context, req datasource.Configu
// Schema defines the schema for the data source.
func (r *bucketDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
descriptions := map[string]string{
"main": "ObjectStorage credential data source schema.",
"main": "ObjectStorage bucket data source schema.",
"id": "Terraform's internal data source identifier. It is structured as \"`project_id`,`bucket_name`\".",
"bucket_name": "The bucket name. It must be DNS conform.",
"project_id": "STACKIT Project ID to which the bucket is associated.",
Expand Down
27 changes: 25 additions & 2 deletions stackit/internal/services/objectstorage/bucket/resource.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,8 +155,15 @@ func (r *bucketResource) Create(ctx context.Context, req resource.CreateRequest,
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "bucket_name", bucketName)

// Create new recordset
_, err := r.client.CreateBucket(ctx, projectId, bucketName).Execute()
// Handle project init
err := enableProject(ctx, &model, r.client)
if resp.Diagnostics.HasError() {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating bucket", fmt.Sprintf("Enabling object storage project before creation: %v", err))
return
}

// Create new bucket
_, err = r.client.CreateBucket(ctx, projectId, bucketName).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating bucket", fmt.Sprintf("Calling API: %v", err))
return
Expand Down Expand Up @@ -294,3 +301,19 @@ func mapFields(bucketResp *objectstorage.GetBucketResponse, model *Model) error
model.URLVirtualHostedStyle = types.StringPointerValue(bucket.UrlVirtualHostedStyle)
return nil
}

type objectStorageClient interface {
CreateProjectExecute(ctx context.Context, projectId string) (*objectstorage.GetProjectResponse, error)
}

// enableProject enables object storage for the specified project. If the project is already enabled, nothing happens
func enableProject(ctx context.Context, model *Model, client objectStorageClient) error {
projectId := model.ProjectId.ValueString()

// From the object storage OAS: Creation will also be successful if the project is already enabled, but will not create a duplicate
_, err := client.CreateProjectExecute(ctx, projectId)
if err != nil {
return fmt.Errorf("failed to create object storage project: %w", err)
}
return nil
}
49 changes: 49 additions & 0 deletions stackit/internal/services/objectstorage/bucket/resource_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package objectstorage

import (
"context"
"fmt"
"testing"

"github.com/google/go-cmp/cmp"
Expand All @@ -9,6 +11,20 @@ import (
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
)

type objectStorageClientMocked struct {
returnError bool
}

func (c *objectStorageClientMocked) CreateProjectExecute(_ context.Context, projectId string) (*objectstorage.GetProjectResponse, error) {
if c.returnError {
return nil, fmt.Errorf("create project failed")
}

return &objectstorage.GetProjectResponse{
Project: utils.Ptr(projectId),
}, nil
}

func TestMapFields(t *testing.T) {
tests := []struct {
description string
Expand Down Expand Up @@ -99,3 +115,36 @@ func TestMapFields(t *testing.T) {
})
}
}

func TestEnableProject(t *testing.T) {
tests := []struct {
description string
enableFails bool
isValid bool
}{
{
"default_values",
false,
true,
},
{
"error_response",
true,
false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
client := &objectStorageClientMocked{
returnError: tt.enableFails,
}
err := enableProject(context.Background(), &Model{}, client)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
if tt.isValid && err != nil {
t.Fatalf("Should not have failed: %v", err)
}
})
}
}
144 changes: 144 additions & 0 deletions stackit/internal/services/objectstorage/credentialsgroup/datasource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
package objectstorage

import (
"context"
"fmt"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"

"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
"github.com/stackitcloud/stackit-sdk-go/core/config"
"github.com/stackitcloud/stackit-sdk-go/services/objectstorage"
)

// Ensure the implementation satisfies the expected interfaces.
var (
_ datasource.DataSource = &credentialsGroupDataSource{}
)

// NewCredentialsGroupDataSource is a helper function to simplify the provider implementation.
func NewCredentialsGroupDataSource() datasource.DataSource {
return &credentialsGroupDataSource{}
}

// credentialsGroupDataSource is the data source implementation.
type credentialsGroupDataSource struct {
client *objectstorage.APIClient
}

// Metadata returns the data source type name.
func (r *credentialsGroupDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_objectstorage_credentials_group"
}

// Configure adds the provider configured client to the data source.
func (r *credentialsGroupDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
// Prevent panic if the provider has not been configured.
if req.ProviderData == nil {
return
}

providerData, ok := req.ProviderData.(core.ProviderData)
if !ok {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring API client", fmt.Sprintf("Expected configure type stackit.ProviderData, got %T", req.ProviderData))
return
}

var apiClient *objectstorage.APIClient
var err error
if providerData.ObjectStorageCustomEndpoint != "" {
apiClient, err = objectstorage.NewAPIClient(
config.WithCustomAuth(providerData.RoundTripper),
config.WithEndpoint(providerData.ObjectStorageCustomEndpoint),
)
} else {
apiClient, err = objectstorage.NewAPIClient(
config.WithCustomAuth(providerData.RoundTripper),
config.WithRegion(providerData.Region),
)
}

if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring API client", fmt.Sprintf("Configuring client: %v", err))
return
}

r.client = apiClient
tflog.Info(ctx, "ObjectStorage credentials group client configured")
}

// Schema defines the schema for the data source.
func (r *credentialsGroupDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
descriptions := map[string]string{
"main": "ObjectStorage credentials group data source schema.",
"id": "Terraform's internal data source identifier. It is structured as \"`project_id`,`credentials_group_id`\".",
"credentials_group_id": "The credentials group ID",
"name": "The credentials group's display name.",
"project_id": "Object Storage Project ID to which the credentials group is associated.",
"urn": "Credentials group uniform resource name (URN)",
}

resp.Schema = schema.Schema{
Description: descriptions["main"],
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Description: descriptions["id"],
Computed: true,
},
"credentials_group_id": schema.StringAttribute{
Description: descriptions["id"],
Optional: true,
Computed: true,
},
"project_id": schema.StringAttribute{
Description: descriptions["project_id"],
Required: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"name": schema.StringAttribute{
Description: descriptions["name"],
Optional: true,
Computed: true,
},
"urn": schema.StringAttribute{
Computed: true,
Description: descriptions["urn"],
},
},
}
}

// Read refreshes the Terraform state with the latest data.
func (r *credentialsGroupDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
var model Model
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
credentialsGroupId := model.CredentialsGroupId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "credentials_group_id", credentialsGroupId)

err := readCredentialsGroups(ctx, &model, r.client)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading credentialsGroup", fmt.Sprintf("getting credential group from list of credentials groups: %v", err))
return
}

// Set refreshed state
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "ObjectStorage credentials group read")
}
Loading