diff --git a/docs/resources/cdn.md b/docs/resources/cdn.md index 0ec013c..90d7730 100644 --- a/docs/resources/cdn.md +++ b/docs/resources/cdn.md @@ -156,3 +156,15 @@ Optional: - `port` (Number) - `protocol` (String) - `query_key` (String) + +## Import + +Import is supported using the following syntax: + +```shell +$ terraform import cdn77_cdn.example + +# must be the ID (unsigned integer) of the CDN +# Example: +$ terraform import cdn77_cdn.example 1837865409 +``` diff --git a/docs/resources/origin.md b/docs/resources/origin.md index 47785f3..fc6cd08 100644 --- a/docs/resources/origin.md +++ b/docs/resources/origin.md @@ -48,3 +48,36 @@ resource "cdn77_origin" "example" { - `access_key_id` (String) Access key to your Object Storage bucket - `access_key_secret` (String, Sensitive) Access secret to your Object Storage bucket - `id` (String) Origin ID (UUID) + +## Import + +Import is supported using the following syntax: + +```shell +$ terraform import cdn77_origin.example ,[,type-specific parameters,...] + +# must be the ID (UUID) of the Origin +# must be a type of the Origin (one of: "aws", "object-storage", "url") +# Depending on the type of the origin there may be other required parameters: + +# URL type doesn't need other parameters +$ terraform import cdn77_origin.example ,url + +# AWS type requires access key secret +# must be the secret of the AWS access key that you provided when creating the Origin +$ terraform import cdn77_origin.example ,aws, + +# Object Storage type requires ACL, cluster ID, access key ID and access key secret +# must be ACL type (one of: "authenticated-read", "private", "public-read", "public-read-write") +# must be an ID (UUID) of the Object Storage cluster +# must be the ID of the access key that was returned when creating the Origin +# must be the secret of the access key that was returned when creating the Origin +$ terraform import cdn77_origin.example ,object-storage,,,, + +# Examples: +$ terraform import cdn77_origin.example_url 4cd2378b-dec8-49e2-aa17-bf7561452998,url +$ terraform import cdn77_origin.example_aws 4cd2378b-dec8-49e2-aa17-bf7561452998,aws,\ +VWK92izmd7zpY8Khs/Dllv4yLYc4sFWNyg2XtuNF +$ terraform import cdn77_origin.example_object_storage 4cd2378b-dec8-49e2-aa17-bf7561452998,object-storage,\ +private,842b5641-b641-4723-ac81-f8cc286e288f,I17DXFE00GNJZVQUTQPW,7UG7WbcIz4VhZnVxV4XQcDR2X0APApuvthyATf2v +``` diff --git a/docs/resources/ssl.md b/docs/resources/ssl.md index bb39550..55ea725 100644 --- a/docs/resources/ssl.md +++ b/docs/resources/ssl.md @@ -32,3 +32,24 @@ resource "cdn77_ssl" "example" { - `expires_at` (String) Date and time of the SNI certificate expiration - `id` (String) ID (UUID) of the SSL certificate - `subjects` (Set of String) Subjects (domain names) of the certificate + +## Import + +Import is supported using the following syntax: + +```shell +$ terraform import cdn77_ssl.example , + +# must be the ID (UUID) of the SSL certificate +# must be an entire private key (including PEM headers) encoded via base64. +# Example: +$ key=$(base64 --wrap=0 key.pem < + +# must be the ID (unsigned integer) of the CDN +# Example: +$ terraform import cdn77_cdn.example 1837865409 diff --git a/examples/resources/cdn77_origin/import.sh b/examples/resources/cdn77_origin/import.sh new file mode 100644 index 0000000..3e4a5f9 --- /dev/null +++ b/examples/resources/cdn77_origin/import.sh @@ -0,0 +1,26 @@ +$ terraform import cdn77_origin.example ,[,type-specific parameters,...] + +# must be the ID (UUID) of the Origin +# must be a type of the Origin (one of: "aws", "object-storage", "url") +# Depending on the type of the origin there may be other required parameters: + +# URL type doesn't need other parameters +$ terraform import cdn77_origin.example ,url + +# AWS type requires access key secret +# must be the secret of the AWS access key that you provided when creating the Origin +$ terraform import cdn77_origin.example ,aws, + +# Object Storage type requires ACL, cluster ID, access key ID and access key secret +# must be ACL type (one of: "authenticated-read", "private", "public-read", "public-read-write") +# must be an ID (UUID) of the Object Storage cluster +# must be the ID of the access key that was returned when creating the Origin +# must be the secret of the access key that was returned when creating the Origin +$ terraform import cdn77_origin.example ,object-storage,,,, + +# Examples: +$ terraform import cdn77_origin.example_url 4cd2378b-dec8-49e2-aa17-bf7561452998,url +$ terraform import cdn77_origin.example_aws 4cd2378b-dec8-49e2-aa17-bf7561452998,aws,\ +VWK92izmd7zpY8Khs/Dllv4yLYc4sFWNyg2XtuNF +$ terraform import cdn77_origin.example_object_storage 4cd2378b-dec8-49e2-aa17-bf7561452998,object-storage,\ +private,842b5641-b641-4723-ac81-f8cc286e288f,I17DXFE00GNJZVQUTQPW,7UG7WbcIz4VhZnVxV4XQcDR2X0APApuvthyATf2v diff --git a/examples/resources/cdn77_ssl/import.sh b/examples/resources/cdn77_ssl/import.sh new file mode 100644 index 0000000..e51fac4 --- /dev/null +++ b/examples/resources/cdn77_ssl/import.sh @@ -0,0 +1,14 @@ +$ terraform import cdn77_ssl.example , + +# must be the ID (UUID) of the SSL certificate +# must be an entire private key (including PEM headers) encoded via base64. +# Example: +$ key=$(base64 --wrap=0 key.pem <,url"\n\t",aws," \n\t`+ + `",object-storage,,,,")\nGot: %q`, + req.ID, + ), + ) + } + + id := idParts[0] + originType := idParts[1] + + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("type"), originType)...) + + switch originType { + case OriginTypeAws: + if len(idParts) != 3 { + resp.Diagnostics.AddError( + "Invalid AWS Origin Import Identifier", + fmt.Sprintf(`Expected ",aws,"; got: %q`, req.ID), + ) + + return + } + + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("aws_access_key_secret"), idParts[2])...) + case OriginTypeObjectStorage: + if len(idParts) != 6 { + resp.Diagnostics.AddError( + "Invalid Object Storage Origin Import Identifier", + fmt.Sprintf( + `Expected ",object-storage,,,,"; got: %q`, + req.ID, + ), + ) + + return + } + + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("acl"), idParts[2])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("cluster_id"), idParts[3])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("access_key_id"), idParts[4])...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("access_key_secret"), idParts[5])...) + case OriginTypeUrl: + if len(idParts) != 2 { + resp.Diagnostics.AddError( + "Invalid URL Origin Import Identifier", + fmt.Sprintf(`Expected ",url"; got: %q`, req.ID), + ) + + return + } + default: + addUnknownOriginTypeError(&resp.Diagnostics, OriginModel{Type: types.StringValue(originType)}) + } +} + func (r *OriginResource) createAws( ctx context.Context, diags *diag.Diagnostics, diff --git a/internal/provider/origin_resource_test.go b/internal/provider/origin_resource_test.go index ea9b6ef..980291e 100644 --- a/internal/provider/origin_resource_test.go +++ b/internal/provider/origin_resource_test.go @@ -244,6 +244,44 @@ func TestAccOriginResource_Aws(t *testing.T) { }) } +func TestAccOriginResourceImport_Aws(t *testing.T) { + client := acctest.GetClient(t) + rsc := "cdn77_origin.aws" + var originId string + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acctest.GetProviderFactories(), + CheckDestroy: checkOriginsDestroyed(client), + Steps: []resource.TestStep{ + { + Config: `resource "cdn77_origin" "aws" { + type = "aws" + label = "some label" + note = "some note" + aws_access_key_id = "keyid" + aws_access_key_secret = "keysecret" + aws_region = "eu" + scheme = "http" + host = "my-totally-random-custom-host.com" + }`, + Check: resource.TestCheckResourceAttrWith(rsc, "id", func(value string) error { + originId = value + + return acctest.NotEqual(value, "") + }), + }, + { + ResourceName: rsc, + ImportState: true, + ImportStateIdFunc: func(*terraform.State) (string, error) { + return fmt.Sprintf("%s,aws,keysecret", originId), nil + }, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccOriginResource_ObjectStorage(t *testing.T) { client := acctest.GetClient(t) bucketName := "my-bucket-" + uuid.New().String() @@ -251,16 +289,6 @@ func TestAccOriginResource_ObjectStorage(t *testing.T) { var originId string var clusterId string - const objectStoragesDataSourceConfig = ` - data "cdn77_object_storages" "all" { - } - - locals { - eu_cluster_id = one([for os in data.cdn77_object_storages.all.clusters : os.id if os.label == "EU"]) - us_cluster_id = one([for os in data.cdn77_object_storages.all.clusters : os.id if os.label == "US"]) - } - ` - resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acctest.GetProviderFactories(), CheckDestroy: checkOriginsDestroyed(client), @@ -598,6 +626,63 @@ func TestAccOriginResource_ObjectStorage(t *testing.T) { }) } +func TestAccOriginResourceImport_ObjectStorage(t *testing.T) { + client := acctest.GetClient(t) + rsc := "cdn77_origin.os" + bucketName := "my-bucket-" + uuid.New().String() + var originId, clusterId, accessKeyId, accessKeySecret string + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acctest.GetProviderFactories(), + CheckDestroy: checkOriginsDestroyed(client), + Steps: []resource.TestStep{ + { + Config: acctest.Config(objectStoragesDataSourceConfig+`resource "cdn77_origin" "os" { + type = "object-storage" + label = "some label" + note = "some note" + acl = "private" + cluster_id = local.eu_cluster_id + bucket_name = "{bucketName}" + }`, "bucketName", bucketName), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttrWith(rsc, "id", func(value string) error { + originId = value + + return acctest.NotEqual(value, "") + }), + resource.TestCheckResourceAttrWith(rsc, "cluster_id", func(value string) error { + clusterId = value + + return acctest.NotEqual(value, "") + }), + resource.TestCheckResourceAttrWith(rsc, "access_key_id", func(value string) error { + accessKeyId = value + + return acctest.NotEqual(value, "") + }), + resource.TestCheckResourceAttrWith(rsc, "access_key_secret", func(value string) error { + accessKeySecret = value + + return acctest.NotEqual(value, "") + }), + ), + }, + { + ResourceName: rsc, + ImportState: true, + ImportStateIdFunc: func(*terraform.State) (string, error) { + return fmt.Sprintf( + "%s,object-storage,private,%s,%s,%s", + originId, clusterId, accessKeyId, accessKeySecret, + ), nil + }, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccOriginResource_Url(t *testing.T) { client := acctest.GetClient(t) var originId string @@ -821,12 +906,47 @@ func TestAccOriginResource_Url(t *testing.T) { }) } +func TestAccOriginResourceImport_Url(t *testing.T) { + client := acctest.GetClient(t) + rsc := "cdn77_origin.url" + var originId string + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acctest.GetProviderFactories(), + CheckDestroy: checkOriginsDestroyed(client), + Steps: []resource.TestStep{ + { + Config: `resource "cdn77_origin" "url" { + type = "url" + label = "some label" + note = "some note" + scheme = "http" + host = "my-totally-random-custom-host.com" + }`, + Check: resource.TestCheckResourceAttrWith(rsc, "id", func(value string) error { + originId = value + + return acctest.NotEqual(value, "") + }), + }, + { + ResourceName: rsc, + ImportState: true, + ImportStateIdFunc: func(*terraform.State) (string, error) { + return fmt.Sprintf("%s,url", originId), nil + }, + ImportStateVerify: true, + }, + }, + }) +} + func checkAwsOrigin( client cdn77.ClientWithResponsesInterface, originId *string, fn func(o *cdn77.S3OriginDetail) error, ) func(*terraform.State) error { - return func(_ *terraform.State) error { + return func(*terraform.State) error { response, err := client.OriginDetailAwsWithResponse(context.Background(), *originId) message := fmt.Sprintf("failed to get Origin[id=%s]: %%s", *originId) @@ -843,7 +963,7 @@ func checkObjectStorageOrigin( originId *string, fn func(o *cdn77.ObjectStorageOriginDetail) error, ) func(*terraform.State) error { - return func(_ *terraform.State) error { + return func(*terraform.State) error { response, err := client.OriginDetailObjectStorageWithResponse(context.Background(), *originId) message := fmt.Sprintf("failed to get Origin[id=%s]: %%s", *originId) @@ -860,7 +980,7 @@ func checkUrlOrigin( originId *string, fn func(o *cdn77.UrlOriginDetail) error, ) func(*terraform.State) error { - return func(_ *terraform.State) error { + return func(*terraform.State) error { response, err := client.OriginDetailUrlWithResponse(context.Background(), *originId) message := fmt.Sprintf("failed to get Origin[id=%s]: %%s", *originId) diff --git a/internal/provider/ssl_resource.go b/internal/provider/ssl_resource.go index 9dcb898..5824922 100644 --- a/internal/provider/ssl_resource.go +++ b/internal/provider/ssl_resource.go @@ -2,14 +2,21 @@ package provider import ( "context" + "encoding/base64" + "fmt" + "strings" "github.com/cdn77/cdn77-client-go" "github.com/cdn77/terraform-provider-cdn77/internal/util" + "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/types" ) -var _ resource.ResourceWithConfigure = &SslResource{} +var ( + _ resource.ResourceWithConfigure = &SslResource{} + _ resource.ResourceWithImportState = &SslResource{} +) func NewSslResource() resource.Resource { return &SslResource{} @@ -132,3 +139,38 @@ func (r *SslResource) Delete(ctx context.Context, req resource.DeleteRequest, re return } } + +func (*SslResource) ImportState( + ctx context.Context, + req resource.ImportStateRequest, + resp *resource.ImportStateResponse, +) { + idParts := strings.Split(req.ID, ",") + + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf( + "Expected import identifier with format: ,. "+ + " must be the whole PEM file (including headers) encoded via base64. Got: %q", + req.ID, + ), + ) + + return + } + + id, privateKeyBase64 := idParts[0], idParts[1] + + privateKey, err := base64.StdEncoding.DecodeString(privateKeyBase64) + if err != nil { + resp.Diagnostics.AddError( + "Invalid Private Key", + "Private Key must be base64 encoded key (including the PEM headers)") + + return + } + + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), id)...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("private_key"), string(privateKey))...) +} diff --git a/internal/provider/ssl_resource_test.go b/internal/provider/ssl_resource_test.go index 71b086c..0da8e74 100644 --- a/internal/provider/ssl_resource_test.go +++ b/internal/provider/ssl_resource_test.go @@ -2,6 +2,7 @@ package provider_test import ( "context" + "encoding/base64" "errors" "fmt" "sort" @@ -91,12 +92,41 @@ func TestAccSslResource(t *testing.T) { }) } +func TestAccSslResourceImport(t *testing.T) { + client := acctest.GetClient(t) + rsc := "cdn77_ssl.crt" + var sslId string + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acctest.GetProviderFactories(), + CheckDestroy: checkSslsDestroyed(client), + Steps: []resource.TestStep{ + { + Config: acctest.Config(SslResourceConfig, "cert", sslTestCert1, "key", sslTestKey), + Check: resource.TestCheckResourceAttrWith(rsc, "id", func(value string) error { + sslId = value + + return acctest.NotEqual(value, "") + }), + }, + { + ResourceName: rsc, + ImportState: true, + ImportStateVerify: true, + ImportStateIdFunc: func(*terraform.State) (string, error) { + return fmt.Sprintf("%s,%s", sslId, base64.StdEncoding.EncodeToString([]byte(sslTestKey))), nil + }, + }, + }, + }) +} + func checkSsl( client cdn77.ClientWithResponsesInterface, sslId *string, fn func(o *cdn77.Ssl) error, ) func(*terraform.State) error { - return func(_ *terraform.State) error { + return func(*terraform.State) error { response, err := client.SslSniDetailWithResponse(context.Background(), *sslId) message := fmt.Sprintf("failed to get SSL[id=%s]: %%s", *sslId)