Skip to content

Commit

Permalink
GIN-341: add teams certificates to Gateway and to Gateway account con…
Browse files Browse the repository at this point in the history
…figuration
  • Loading branch information
alyssamw committed Sep 4, 2024
1 parent 26eebcf commit a869693
Show file tree
Hide file tree
Showing 12 changed files with 776 additions and 7 deletions.
3 changes: 3 additions & 0 deletions .changelog/3547.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:new-resource
resource/teams-certificate: add teams certificates to Gateway and Gateway account configurations
```
16 changes: 16 additions & 0 deletions internal/framework/service/gateway_certificates/model.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package gateway_app_types

import (
"github.com/hashicorp/terraform-plugin-framework/types"
)

type GatewayCertificateModel struct {
ID types.String `tfsdk:"id"`
Custom types.Bool `tfsdk:"custom"`
GatewayManaged types.Bool `tfsdk:"gateway_managed"`
ValidityPeriodDays types.Int64 `tfsdk:"validity_period_days"`
Activate types.Bool `tfsdk:"activate"`
Enabled types.Bool `tfsdk:"enabled"`
BindingStatus types.String `tfsdk:"binding_status"`
ExpiresOn types.String `tfsdk:"expires_on"`
}
138 changes: 138 additions & 0 deletions internal/framework/service/gateway_certificates/resource.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package r2_bucket

import (
"context"
"fmt"
"strings"

cfv1 "github.com/cloudflare/cloudflare-go"
"github.com/cloudflare/terraform-provider-cloudflare/internal/framework/muxclient"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
)

// Ensure provider defined types fully satisfy framework interfaces.
var _ resource.Resource = &GatewayCertificateModel{}
var _ resource.ResourceWithImportState = &GatewayCertificateModel{}

func NewResource() resource.Resource {
return &GatewayCertificateModel{}
}

// GatewayCertificateResource defines the resource implementation.
type GatewayCertificateResource struct {
client *muxclient.Client
}

func (r *GatewayCertificateResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_zero_trust_gateway_certificate"
}

func (r *GatewayCertificateResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}

client, ok := req.ProviderData.(*muxclient.Client)

if !ok {
resp.Diagnostics.AddError(
"unexpected resource configure type",
fmt.Sprintf("Expected *muxclient.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData),
)

return
}

r.client = client
}

func (r *GatewayCertificateResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data *GatewayCertificateModel

resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

cert, err := r.client.V1.TeamsGenerateCertificate(ctx, cfv1.AccountIdentifier(data.AccountID.ValueString()),
cfv1.TeamsCertificateCreateRequest{
ValidityPeriodDays: data.ValidityPeriodDays.ValueString(),
},
)
if err != nil {
resp.Diagnostics.AddError("failed to generate Gateway certificate", err.Error())
return
}
data.ID = types.StringValue(cert.ID)
data.BindingStatus = types.StringValue(cert.BindingStatus)
data.Activate = types.Bool(false)
data.Enabled = types.Bool(cert.Enabled)
data.ExpiresOn = types.StringValue(cert.ExpiresOn)
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *GatewayCertificateResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data *GatewayCertificateModel

resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

r2Bucket, err := r.client.V1.GetR2Bucket(ctx, cfv1.AccountIdentifier(data.AccountID.ValueString()), data.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError("failed reading R2 bucket", err.Error())
return
}
data.ID = types.StringValue(r2Bucket.Name)
data.Name = types.StringValue(r2Bucket.Name)
data.Location = types.StringValue(r2Bucket.Location)
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *GatewayCertificateResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data *GatewayCertificateModel

resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

resp.Diagnostics.AddError("failed to update R2 bucket", "Not implemented")
}

func (r *GatewayCertificateResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data *GatewayCertificateModel

resp.Diagnostics.Append(req.State.Get(ctx, &data)...)

if resp.Diagnostics.HasError() {
return
}

err := r.client.V1.DeleteR2Bucket(ctx, cfv1.AccountIdentifier(data.AccountID.ValueString()), data.ID.ValueString())

if err != nil {
resp.Diagnostics.AddError("failed to delete R2 bucket", err.Error())
return
}
}

func (r *GatewayCertificateResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
idparts := strings.Split(req.ID, "/")
if len(idparts) != 2 {
resp.Diagnostics.AddError("error importing R2 bucket", "invalid ID specified. Please specify the ID as \"account_id/name\"")
return
}
resp.Diagnostics.Append(resp.State.SetAttribute(
ctx, path.Root("account_id"), idparts[0],
)...)
resp.Diagnostics.Append(resp.State.SetAttribute(
ctx, path.Root("id"), idparts[1],
)...)
}
191 changes: 191 additions & 0 deletions internal/framework/service/gateway_certificates/resource_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package r2_bucket_test

import (
"context"
"errors"
"fmt"
"os"
"regexp"
"testing"

"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/credentials"
"github.com/aws/aws-sdk-go-v2/service/s3"
cfv1 "github.com/cloudflare/cloudflare-go"
"github.com/cloudflare/terraform-provider-cloudflare/internal/acctest"
"github.com/cloudflare/terraform-provider-cloudflare/internal/utils"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
)

func TestMain(m *testing.M) {
resource.TestMain(m)
}

func init() {
resource.AddTestSweepers("cloudflare_r2_bucket", &resource.Sweeper{
Name: "cloudflare_r2_bucket",
F: func(region string) error {
client, err := acctest.SharedV1Client()
accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID")

accessKeyId := os.Getenv("CLOUDFLARE_R2_ACCESS_KEY_ID")
accessKeySecret := os.Getenv("CLOUDFLARE_R2_ACCESS_KEY_SECRET")

if accessKeyId == "" {
return errors.New("CLOUDFLARE_R2_ACCESS_KEY_ID must be set for this acceptance test")
}

if accessKeyId == "" {
return errors.New("CLOUDFLARE_R2_ACCESS_KEY_SECRET must be set for this acceptance test")
}

if err != nil {
return fmt.Errorf("error establishing client: %w", err)
}

ctx := context.Background()
buckets, err := client.ListR2Buckets(ctx, cfv1.AccountIdentifier(accountID), cfv1.ListR2BucketsParams{})
if err != nil {
return fmt.Errorf("failed to fetch R2 buckets: %w", err)
}

for _, bucket := range buckets {
// hard coded bucket name for Worker script acceptance tests
// until we can break out the packages without cyclic errors.
if bucket.Name == "bnfywlzwpt" {
continue
}

r2Resolver := aws.EndpointResolverWithOptionsFunc(func(service, region string, options ...interface{}) (aws.Endpoint, error) {
return aws.Endpoint{
URL: fmt.Sprintf("https://%s.r2.cloudflarestorage.com", accountID),
}, nil
})

cfg, err := config.LoadDefaultConfig(context.TODO(),
config.WithEndpointResolverWithOptions(r2Resolver),
config.WithCredentialsProvider(credentials.NewStaticCredentialsProvider(accessKeyId, accessKeySecret, "")),
config.WithRegion("auto"),
)
if err != nil {
return err
}

s3client := s3.NewFromConfig(cfg)
listObjectsOutput, err := s3client.ListObjectsV2(context.TODO(), &s3.ListObjectsV2Input{
Bucket: &bucket.Name,
})
if err != nil {
return err
}

for _, object := range listObjectsOutput.Contents {
_, err = s3client.DeleteObject(context.TODO(), &s3.DeleteObjectInput{
Bucket: &bucket.Name,
Key: object.Key,
})
if err != nil {
return err
}
}

err = client.DeleteR2Bucket(ctx, cfv1.AccountIdentifier(accountID), bucket.Name)
if err != nil {
return fmt.Errorf("failed to delete R2 bucket %q: %w", bucket.Name, err)
}
}

return nil
},
})
}

func TestAccCloudflareR2Bucket_Basic(t *testing.T) {
rnd := utils.GenerateRandomResourceName()
accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID")
resourceName := "cloudflare_r2_bucket." + rnd

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.TestAccPreCheck(t) },
ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccCheckCloudflareR2BucketBasic(rnd, accountID),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "name", rnd),
resource.TestCheckResourceAttr(resourceName, "id", rnd),
resource.TestCheckResourceAttr(resourceName, "location", "ENAM"),
),
},
{
ResourceName: resourceName,
ImportStateIdPrefix: fmt.Sprintf("%s/", accountID),
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAccCloudflareR2Bucket_Minimum(t *testing.T) {
rnd := utils.GenerateRandomResourceName()
accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID")
resourceName := "cloudflare_r2_bucket." + rnd

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.TestAccPreCheck(t) },
ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccCheckCloudflareR2BucketMinimum(rnd, accountID),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(resourceName, "name", rnd),
resource.TestCheckResourceAttr(resourceName, "id", rnd),
),
},
},
})
}

func TestAccCloudflareR2Bucket_InvalidLocation(t *testing.T) {
rnd := utils.GenerateRandomResourceName()
accountID := os.Getenv("CLOUDFLARE_ACCOUNT_ID")

resource.ParallelTest(t, resource.TestCase{
PreCheck: func() { acctest.TestAccPreCheck(t) },
ProtoV6ProviderFactories: acctest.TestAccProtoV6ProviderFactories,
Steps: []resource.TestStep{
{
Config: testAccCheckCloudflareR2BucketInvalidLocation(rnd, accountID),
ExpectError: regexp.MustCompile("Error: Invalid Attribute Value Match"),
},
},
})
}

func testAccCheckCloudflareR2BucketMinimum(rnd, accountID string) string {
return fmt.Sprintf(`
resource "cloudflare_r2_bucket" "%[1]s" {
account_id = "%[2]s"
name = "%[1]s"
}`, rnd, accountID)
}

func testAccCheckCloudflareR2BucketBasic(rnd, accountID string) string {
return fmt.Sprintf(`
resource "cloudflare_r2_bucket" "%[1]s" {
account_id = "%[2]s"
name = "%[1]s"
location = "ENAM"
}`, rnd, accountID)
}

func testAccCheckCloudflareR2BucketInvalidLocation(rnd, accountID string) string {
return fmt.Sprintf(`
resource "cloudflare_r2_bucket" "%[1]s" {
account_id = "%[2]s"
name = "%[1]s"
location = "foo"
}`, rnd, accountID)
}
44 changes: 44 additions & 0 deletions internal/framework/service/gateway_certificates/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package gateway_app_types

import (
"context"

"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/datasource/schema"
)

func (d *CloudflareGatewayAppTypesDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Use this data source to retrieve all Gateway application types for an account.",
Attributes: map[string]schema.Attribute{
"account_id": schema.StringAttribute{
Required: true,
Description: "The account ID to fetch Gateway App Types from.",
},
"app_types": schema.ListNestedAttribute{
Computed: true,
Description: "A list of Gateway App Types.",
NestedObject: schema.NestedAttributeObject{
Attributes: map[string]schema.Attribute{
"id": schema.Int64Attribute{
Computed: true,
Description: "The identifier for this app type. There is only one app type per ID.",
},
"application_type_id": schema.Int64Attribute{
Computed: true,
Description: "The identifier for the application type of this app.",
},
"name": schema.StringAttribute{
Computed: true,
Description: "The name of the app type.",
},
"description": schema.StringAttribute{
Computed: true,
Description: "A short summary of the app type.",
},
},
},
},
},
}
}
Loading

0 comments on commit a869693

Please sign in to comment.