diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 612af53..819bef2 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -7,14 +7,14 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: - go-version: "1.21" + go-version-file: 'go.mod' - name: Import GPG key id: import_gpg diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index abbc635..d2c600a 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -8,12 +8,12 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - - name: Set up Go 1.21 - uses: actions/setup-go@v2 + - name: Set up Go + uses: actions/setup-go@v5 with: - go-version: "1.21" + go-version-file: 'go.mod' - name: golangci-lint continue-on-error: true @@ -43,7 +43,7 @@ jobs: fetch-depth: 0 - name: Prepare release - uses: labd/changie-release-action@v0.2.0 + uses: labd/changie-release-action@v0.3.2 with: github-token: ${{ secrets.GITHUB_TOKEN }} release-workflow: 'release.yaml' diff --git a/contentful/errors.go b/contentful/errors.go new file mode 100644 index 0000000..5f227e4 --- /dev/null +++ b/contentful/errors.go @@ -0,0 +1,34 @@ +package contentful + +import ( + "errors" + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/labd/contentful-go" + "strings" +) + +func parseError(err error) diag.Diagnostics { + if !errors.As(err, &contentful.ErrorResponse{}) { + return diag.FromErr(err) + } + + var warnings []diag.Diagnostic + for _, e := range err.(contentful.ErrorResponse).Details.Errors { + var path []string + if e.Path != nil { + for _, p := range e.Path.([]interface{}) { + path = append(path, fmt.Sprintf("%v", p)) + } + } + warnings = append(warnings, diag.Diagnostic{ + Severity: diag.Warning, + Summary: fmt.Sprintf("%s (%s)", e.Details, strings.Join(path, ".")), + }) + } + + return append(warnings, diag.Diagnostic{ + Severity: diag.Error, + Summary: err.(contentful.ErrorResponse).Message, + }) +} diff --git a/contentful/errors_test.go b/contentful/errors_test.go new file mode 100644 index 0000000..c3fd36f --- /dev/null +++ b/contentful/errors_test.go @@ -0,0 +1,69 @@ +package contentful + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/labd/contentful-go" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestParseError_Nil(t *testing.T) { + d := parseError(nil) + assert.Nil(t, d) +} + +func TestParseError_RegularErr(t *testing.T) { + d := parseError(fmt.Errorf("regular error")) + assert.True(t, d.HasError()) + assert.Equal(t, d[0].Summary, "regular error") +} + +func TestParseError_WithoutWarning(t *testing.T) { + d := parseError(&contentful.ErrorResponse{ + Message: "error message", + }) + assert.True(t, d.HasError()) + assert.Equal(t, len(d), 1) + assert.Equal(t, d[0].Summary, "error message") + assert.Equal(t, d[0].Severity, diag.Error) +} + +func TestParseError_WithWarning_WithoutPath(t *testing.T) { + d := parseError(contentful.ErrorResponse{ + Message: "error message", + Details: &contentful.ErrorDetails{ + Errors: []*contentful.ErrorDetail{ + { + Details: "error detail", + }, + }, + }, + }) + assert.True(t, d.HasError()) + assert.Equal(t, len(d), 2) + assert.Equal(t, d[0].Summary, "error detail ()") + assert.Equal(t, d[0].Severity, diag.Warning) + assert.Equal(t, d[1].Summary, "error message") + assert.Equal(t, d[1].Severity, diag.Error) +} + +func TestParseError_WithWarning_WithPath(t *testing.T) { + d := parseError(contentful.ErrorResponse{ + Message: "error message", + Details: &contentful.ErrorDetails{ + Errors: []*contentful.ErrorDetail{ + { + Path: []interface{}{"path", "to", "error"}, + Details: "error detail", + }, + }, + }, + }) + assert.True(t, d.HasError()) + assert.Equal(t, len(d), 2) + assert.Equal(t, d[0].Summary, "error detail (path.to.error)") + assert.Equal(t, d[0].Severity, diag.Warning) + assert.Equal(t, d[1].Summary, "error message") + assert.Equal(t, d[1].Severity, diag.Error) +} diff --git a/contentful/provider_test.go b/contentful/provider_test.go index c9bd83e..dbd9a77 100644 --- a/contentful/provider_test.go +++ b/contentful/provider_test.go @@ -27,11 +27,14 @@ func TestProvider_impl(t *testing.T) { } func testAccPreCheck(t *testing.T) { - var cmaToken, organizationID string + var cmaToken, organizationID, sId string if cmaToken = CMAToken; cmaToken == "" { t.Fatal("CONTENTFUL_MANAGEMENT_TOKEN must set with a valid Contentful Content Management API Token for acceptance tests") } if organizationID = orgID; organizationID == "" { t.Fatal("CONTENTFUL_ORGANIZATION_ID must set with a valid Contentful Organization ID for acceptance tests") } + if sId = spaceID; sId == "" { + t.Fatal("SPACE_ID must set with a valid Space ID for acceptance tests") + } } diff --git a/contentful/resource_contentful_apikey.go b/contentful/resource_contentful_apikey.go index 1a9eda5..52d0456 100644 --- a/contentful/resource_contentful_apikey.go +++ b/contentful/resource_contentful_apikey.go @@ -1,6 +1,9 @@ package contentful import ( + "context" + "errors" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/labd/contentful-go" ) @@ -9,10 +12,10 @@ func resourceContentfulAPIKey() *schema.Resource { return &schema.Resource{ Description: "A Contentful API Key represents a token that can be used to authenticate against the Contentful Content Delivery API and Content Preview API.", - Create: resourceCreateAPIKey, - Read: resourceReadAPIKey, - Update: resourceUpdateAPIKey, - Delete: resourceDeleteAPIKey, + CreateContext: resourceCreateAPIKey, + ReadContext: resourceReadAPIKey, + UpdateContext: resourceUpdateAPIKey, + DeleteContext: resourceDeleteAPIKey, Schema: map[string]*schema.Schema{ "version": { @@ -39,7 +42,7 @@ func resourceContentfulAPIKey() *schema.Resource { } } -func resourceCreateAPIKey(d *schema.ResourceData, m interface{}) (err error) { +func resourceCreateAPIKey(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) apiKey := &contentful.APIKey{ @@ -47,13 +50,13 @@ func resourceCreateAPIKey(d *schema.ResourceData, m interface{}) (err error) { Description: d.Get("description").(string), } - err = client.APIKeys.Upsert(d.Get("space_id").(string), apiKey) + err := client.APIKeys.Upsert(d.Get("space_id").(string), apiKey) if err != nil { - return err + return parseError(err) } if err := setAPIKeyProperties(d, apiKey); err != nil { - return err + return parseError(err) } d.SetId(apiKey.Sys.ID) @@ -61,14 +64,14 @@ func resourceCreateAPIKey(d *schema.ResourceData, m interface{}) (err error) { return nil } -func resourceUpdateAPIKey(d *schema.ResourceData, m interface{}) (err error) { +func resourceUpdateAPIKey(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) apiKeyID := d.Id() apiKey, err := client.APIKeys.Get(spaceID, apiKeyID) if err != nil { - return err + return parseError(err) } apiKey.Name = d.Get("name").(string) @@ -76,11 +79,11 @@ func resourceUpdateAPIKey(d *schema.ResourceData, m interface{}) (err error) { err = client.APIKeys.Upsert(spaceID, apiKey) if err != nil { - return err + return parseError(err) } if err := setAPIKeyProperties(d, apiKey); err != nil { - return err + return parseError(err) } d.SetId(apiKey.Sys.ID) @@ -88,31 +91,42 @@ func resourceUpdateAPIKey(d *schema.ResourceData, m interface{}) (err error) { return nil } -func resourceReadAPIKey(d *schema.ResourceData, m interface{}) (err error) { +func resourceReadAPIKey(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) apiKeyID := d.Id() apiKey, err := client.APIKeys.Get(spaceID, apiKeyID) - if _, ok := err.(contentful.NotFoundError); ok { + var notFoundError contentful.NotFoundError + if errors.As(err, ¬FoundError) { d.SetId("") return nil } - return setAPIKeyProperties(d, apiKey) + err = setAPIKeyProperties(d, apiKey) + if err != nil { + return parseError(err) + } + + return nil } -func resourceDeleteAPIKey(d *schema.ResourceData, m interface{}) (err error) { +func resourceDeleteAPIKey(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) apiKeyID := d.Id() apiKey, err := client.APIKeys.Get(spaceID, apiKeyID) if err != nil { - return err + return parseError(err) } - return client.APIKeys.Delete(spaceID, apiKey) + err = client.APIKeys.Delete(spaceID, apiKey) + if err != nil { + return parseError(err) + } + + return nil } func setAPIKeyProperties(d *schema.ResourceData, apiKey *contentful.APIKey) error { diff --git a/contentful/resource_contentful_asset.go b/contentful/resource_contentful_asset.go index f3e3617..016b7fc 100644 --- a/contentful/resource_contentful_asset.go +++ b/contentful/resource_contentful_asset.go @@ -1,7 +1,9 @@ package contentful import ( - "fmt" + "context" + "errors" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "time" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -10,11 +12,11 @@ import ( func resourceContentfulAsset() *schema.Resource { return &schema.Resource{ - Description: "A Contentful Asset represents a file that can be used in entries.", - Create: resourceCreateAsset, - Read: resourceReadAsset, - Update: resourceUpdateAsset, - Delete: resourceDeleteAsset, + Description: "A Contentful Asset represents a file that can be used in entries.", + CreateContext: resourceCreateAsset, + ReadContext: resourceReadAsset, + UpdateContext: resourceUpdateAsset, + DeleteContext: resourceDeleteAsset, Schema: map[string]*schema.Schema{ "asset_id": { @@ -142,7 +144,7 @@ func resourceContentfulAsset() *schema.Resource { } } -func resourceCreateAsset(d *schema.ResourceData, m interface{}) (err error) { +func resourceCreateAsset(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) fields := d.Get("fields").([]interface{})[0].(map[string]interface{}) @@ -164,7 +166,7 @@ func resourceCreateAsset(d *schema.ResourceData, m interface{}) (err error) { files := fields["file"].([]interface{}) if len(files) == 0 { - return fmt.Errorf("file block not defined in asset") + return diag.Errorf("file block not defined in asset") } file := files[0].(map[string]interface{}) @@ -207,37 +209,37 @@ func resourceCreateAsset(d *schema.ResourceData, m interface{}) (err error) { } } - if err = client.Assets.Upsert(d.Get("space_id").(string), asset); err != nil { - return err + if err := client.Assets.Upsert(d.Get("space_id").(string), asset); err != nil { + return parseError(err) } - if err = client.Assets.Process(d.Get("space_id").(string), asset); err != nil { - return err + if err := client.Assets.Process(d.Get("space_id").(string), asset); err != nil { + return parseError(err) } d.SetId(asset.Sys.ID) if err := setAssetProperties(d, asset); err != nil { - return err + return parseError(err) } time.Sleep(1 * time.Second) // avoid race conditions with version mismatches - if err = setAssetState(d, m); err != nil { - return err + if err := setAssetState(d, m); err != nil { + return parseError(err) } - return err + return nil } -func resourceUpdateAsset(d *schema.ResourceData, m interface{}) (err error) { +func resourceUpdateAsset(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) assetID := d.Id() - _, err = client.Assets.Get(spaceID, assetID) + _, err := client.Assets.Get(spaceID, assetID) if err != nil { - return err + return parseError(err) } fields := d.Get("fields").([]interface{})[0].(map[string]interface{}) @@ -259,7 +261,7 @@ func resourceUpdateAsset(d *schema.ResourceData, m interface{}) (err error) { files := fields["file"].([]interface{}) if len(files) == 0 { - return fmt.Errorf("file block not defined in asset") + return diag.Errorf("file block not defined in asset") } file := files[0].(map[string]interface{}) @@ -303,24 +305,24 @@ func resourceUpdateAsset(d *schema.ResourceData, m interface{}) (err error) { } if err := client.Assets.Upsert(d.Get("space_id").(string), asset); err != nil { - return err + return parseError(err) } if err = client.Assets.Process(d.Get("space_id").(string), asset); err != nil { - return err + return parseError(err) } d.SetId(asset.Sys.ID) if err := setAssetProperties(d, asset); err != nil { - return err + return parseError(err) } if err = setAssetState(d, m); err != nil { - return err + return parseError(err) } - return err + return nil } func setAssetState(d *schema.ResourceData, m interface{}) (err error) { @@ -354,31 +356,42 @@ func setAssetState(d *schema.ResourceData, m interface{}) (err error) { return err } -func resourceReadAsset(d *schema.ResourceData, m interface{}) (err error) { +func resourceReadAsset(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) assetID := d.Id() asset, err := client.Assets.Get(spaceID, assetID) - if _, ok := err.(contentful.NotFoundError); ok { + var notFoundError contentful.NotFoundError + if errors.As(err, ¬FoundError) { d.SetId("") return nil } - return setAssetProperties(d, asset) + err = setAssetProperties(d, asset) + if err != nil { + return parseError(err) + } + + return nil } -func resourceDeleteAsset(d *schema.ResourceData, m interface{}) (err error) { +func resourceDeleteAsset(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) assetID := d.Id() asset, err := client.Assets.Get(spaceID, assetID) if err != nil { - return err + return parseError(err) + } + + err = client.Assets.Delete(spaceID, asset) + if err != nil { + return parseError(err) } - return client.Assets.Delete(spaceID, asset) + return nil } func setAssetProperties(d *schema.ResourceData, asset *contentful.Asset) (err error) { diff --git a/contentful/resource_contentful_contenttype.go b/contentful/resource_contentful_contenttype.go index 0051a0a..198fc3a 100644 --- a/contentful/resource_contentful_contenttype.go +++ b/contentful/resource_contentful_contenttype.go @@ -1,6 +1,8 @@ package contentful import ( + "context" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/labd/contentful-go" ) @@ -9,10 +11,10 @@ func resourceContentfulContentType() *schema.Resource { return &schema.Resource{ Description: "A Contentful Content Type represents a structure for entries.", - Create: resourceContentTypeCreate, - Read: resourceContentTypeRead, - Update: resourceContentTypeUpdate, - Delete: resourceContentTypeDelete, + CreateContext: resourceContentTypeCreate, + ReadContext: resourceContentTypeRead, + UpdateContext: resourceContentTypeUpdate, + DeleteContext: resourceContentTypeDelete, Schema: map[string]*schema.Schema{ "space_id": { @@ -111,7 +113,7 @@ func resourceContentfulContentType() *schema.Resource { } } -func resourceContentTypeCreate(d *schema.ResourceData, m interface{}) (err error) { +func resourceContentTypeCreate(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) @@ -146,7 +148,7 @@ func resourceContentTypeCreate(d *schema.ResourceData, m interface{}) (err error if validations, ok := field["validations"].([]interface{}); ok { parsedValidations, err := contentful.ParseValidations(validations) if err != nil { - return err + return parseError(err) } contentfulField.Validations = parsedValidations @@ -159,16 +161,16 @@ func resourceContentTypeCreate(d *schema.ResourceData, m interface{}) (err error ct.Fields = append(ct.Fields, contentfulField) } - if err = client.ContentTypes.Upsert(spaceID, ct); err != nil { - return err + if err := client.ContentTypes.Upsert(spaceID, ct); err != nil { + return parseError(err) } - if err = client.ContentTypes.Activate(spaceID, ct); err != nil { - return err + if err := client.ContentTypes.Activate(spaceID, ct); err != nil { + return parseError(err) } - if err = setContentTypeProperties(d, ct); err != nil { - return err + if err := setContentTypeProperties(d, ct); err != nil { + return parseError(err) } d.SetId(ct.Sys.ID) @@ -176,16 +178,19 @@ func resourceContentTypeCreate(d *schema.ResourceData, m interface{}) (err error return nil } -func resourceContentTypeRead(d *schema.ResourceData, m interface{}) (err error) { +func resourceContentTypeRead(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) - _, err = client.ContentTypes.Get(spaceID, d.Id()) + _, err := client.ContentTypes.Get(spaceID, d.Id()) + if err != nil { + return parseError(err) + } - return err + return nil } -func resourceContentTypeUpdate(d *schema.ResourceData, m interface{}) (err error) { +func resourceContentTypeUpdate(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { var existingFields []*contentful.Field var deletedFields []*contentful.Field @@ -194,7 +199,7 @@ func resourceContentTypeUpdate(d *schema.ResourceData, m interface{}) (err error ct, err := client.ContentTypes.Get(spaceID, d.Id()) if err != nil { - return err + return parseError(err) } ct.Name = d.Get("name").(string) @@ -220,44 +225,48 @@ func resourceContentTypeUpdate(d *schema.ResourceData, m interface{}) (err error // Omit the removed fields and publish the new version of the content type, // followed by the field removal and final publish. if err = client.ContentTypes.Upsert(spaceID, ct); err != nil { - return err + return parseError(err) } if err = client.ContentTypes.Activate(spaceID, ct); err != nil { - return err + return parseError(err) } if deletedFields != nil { ct.Fields = existingFields if err = client.ContentTypes.Upsert(spaceID, ct); err != nil { - return err + return parseError(err) } if err = client.ContentTypes.Activate(spaceID, ct); err != nil { - return err + return parseError(err) } } - return setContentTypeProperties(d, ct) + if err = setContentTypeProperties(d, ct); err != nil { + return parseError(err) + } + + return nil } -func resourceContentTypeDelete(d *schema.ResourceData, m interface{}) (err error) { +func resourceContentTypeDelete(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) ct, err := client.ContentTypes.Get(spaceID, d.Id()) if err != nil { - return err + return parseError(err) } err = client.ContentTypes.Deactivate(spaceID, ct) if err != nil { - return err + return parseError(err) } if err = client.ContentTypes.Delete(spaceID, ct); err != nil { - return err + return parseError(err) } return nil diff --git a/contentful/resource_contentful_entry.go b/contentful/resource_contentful_entry.go index f302f9e..6f63af7 100644 --- a/contentful/resource_contentful_entry.go +++ b/contentful/resource_contentful_entry.go @@ -1,6 +1,10 @@ package contentful import ( + "context" + "encoding/json" + "errors" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/labd/contentful-go" ) @@ -9,10 +13,10 @@ func resourceContentfulEntry() *schema.Resource { return &schema.Resource{ Description: "A Contentful Entry represents a piece of content in a space.", - Create: resourceCreateEntry, - Read: resourceReadEntry, - Update: resourceUpdateEntry, - Delete: resourceDeleteEntry, + CreateContext: resourceCreateEntry, + ReadContext: resourceReadEntry, + UpdateContext: resourceUpdateEntry, + DeleteContext: resourceDeleteEntry, Schema: map[string]*schema.Schema{ "entry_id": { @@ -45,8 +49,9 @@ func resourceContentfulEntry() *schema.Resource { Required: true, }, "content": { - Type: schema.TypeString, - Required: true, + Type: schema.TypeString, + Required: true, + Description: "The content of the field. If the field type is Richtext the content can be passed as stringified JSON (see example).", }, "locale": { Type: schema.TypeString, @@ -67,7 +72,7 @@ func resourceContentfulEntry() *schema.Resource { } } -func resourceCreateEntry(d *schema.ResourceData, m interface{}) (err error) { +func resourceCreateEntry(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) fieldProperties := map[string]interface{}{} @@ -75,7 +80,7 @@ func resourceCreateEntry(d *schema.ResourceData, m interface{}) (err error) { for i := 0; i < len(rawField); i++ { field := rawField[i].(map[string]interface{}) fieldProperties[field["id"].(string)] = map[string]interface{}{} - fieldProperties[field["id"].(string)].(map[string]interface{})[field["locale"].(string)] = field["content"].(string) + fieldProperties[field["id"].(string)].(map[string]interface{})[field["locale"].(string)] = parseContentValue(field["content"].(string)) } entry := &contentful.Entry{ @@ -86,32 +91,32 @@ func resourceCreateEntry(d *schema.ResourceData, m interface{}) (err error) { }, } - err = client.Entries.Upsert(d.Get("space_id").(string), d.Get("contenttype_id").(string), entry) + err := client.Entries.Upsert(d.Get("space_id").(string), d.Get("contenttype_id").(string), entry) if err != nil { - return err + return parseError(err) } if err := setEntryProperties(d, entry); err != nil { - return err + return parseError(err) } d.SetId(entry.Sys.ID) if err := setEntryState(d, m); err != nil { - return err + return parseError(err) } - return err + return parseError(err) } -func resourceUpdateEntry(d *schema.ResourceData, m interface{}) (err error) { +func resourceUpdateEntry(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) entryID := d.Id() entry, err := client.Entries.Get(spaceID, entryID) if err != nil { - return err + return parseError(err) } fieldProperties := map[string]interface{}{} @@ -119,7 +124,7 @@ func resourceUpdateEntry(d *schema.ResourceData, m interface{}) (err error) { for i := 0; i < len(rawField); i++ { field := rawField[i].(map[string]interface{}) fieldProperties[field["id"].(string)] = map[string]interface{}{} - fieldProperties[field["id"].(string)].(map[string]interface{})[field["locale"].(string)] = field["content"].(string) + fieldProperties[field["id"].(string)].(map[string]interface{})[field["locale"].(string)] = parseContentValue(field["content"].(string)) } entry.Fields = fieldProperties @@ -127,20 +132,20 @@ func resourceUpdateEntry(d *schema.ResourceData, m interface{}) (err error) { err = client.Entries.Upsert(d.Get("space_id").(string), d.Get("contenttype_id").(string), entry) if err != nil { - return err + return parseError(err) } d.SetId(entry.Sys.ID) if err := setEntryProperties(d, entry); err != nil { - return err + return parseError(err) } if err := setEntryState(d, m); err != nil { - return err + return parseError(err) } - return err + return nil } func setEntryState(d *schema.ResourceData, m interface{}) (err error) { @@ -165,31 +170,42 @@ func setEntryState(d *schema.ResourceData, m interface{}) (err error) { return err } -func resourceReadEntry(d *schema.ResourceData, m interface{}) (err error) { +func resourceReadEntry(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) entryID := d.Id() entry, err := client.Entries.Get(spaceID, entryID) - if _, ok := err.(contentful.NotFoundError); ok { + var notFoundError contentful.NotFoundError + if errors.As(err, ¬FoundError) { d.SetId("") return nil } - return setEntryProperties(d, entry) + err = setEntryProperties(d, entry) + if err != nil { + return parseError(err) + } + + return nil } -func resourceDeleteEntry(d *schema.ResourceData, m interface{}) (err error) { +func resourceDeleteEntry(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) entryID := d.Id() - _, err = client.Entries.Get(spaceID, entryID) + _, err := client.Entries.Get(spaceID, entryID) if err != nil { - return err + return parseError(err) + } + + err = client.Entries.Delete(spaceID, entryID) + if err != nil { + return parseError(err) } - return client.Entries.Delete(spaceID, entryID) + return nil } func setEntryProperties(d *schema.ResourceData, entry *contentful.Entry) (err error) { @@ -207,3 +223,13 @@ func setEntryProperties(d *schema.ResourceData, entry *contentful.Entry) (err er return err } + +func parseContentValue(value interface{}) interface{} { + var content interface{} + err := json.Unmarshal([]byte(value.(string)), &content) + if err != nil { + content = value + } + + return content +} diff --git a/contentful/resource_contentful_entry_test.go b/contentful/resource_contentful_entry_test.go index 30c7cb7..b4cfb86 100644 --- a/contentful/resource_contentful_entry_test.go +++ b/contentful/resource_contentful_entry_test.go @@ -2,6 +2,7 @@ package contentful import ( "fmt" + "github.com/stretchr/testify/assert" "testing" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" @@ -9,6 +10,16 @@ import ( contentful "github.com/labd/contentful-go" ) +func TestParseContentValue_String(t *testing.T) { + value := "hello" + assert.Equal(t, parseContentValue(value), value) +} + +func TestParseContentValue_Json(t *testing.T) { + value := `{"foo": "bar", "baz": [1, 2, 3]}` + assert.Equal(t, parseContentValue(value), map[string]interface{}{"foo": "bar", "baz": []interface{}{float64(1), float64(2), float64(3)}}) +} + func TestAccContentfulEntry_Basic(t *testing.T) { var entry contentful.Entry @@ -136,6 +147,11 @@ resource "contentful_contenttype" "mycontenttype" { required = true type = "Text" } + field { + id = "field3" + name = "Field 3" + type = "RichText" + } } resource "contentful_entry" "myentry" { @@ -153,6 +169,29 @@ resource "contentful_entry" "myentry" { content = "Bacon is healthy!" locale = "en-US" } + + field { + id = "field3" + locale = "en-US" + content = jsonencode({ + data= {}, + content= [ + { + nodeType= "paragraph", + content= [ + { + nodeType= "text", + marks= [], + value= "This is another paragraph.", + data= {}, + }, + ], + data= {}, + } + ], + nodeType= "document" + }) + } published = true archived = false depends_on = [contentful_contenttype.mycontenttype] @@ -183,6 +222,11 @@ resource "contentful_contenttype" "mycontenttype" { required = true type = "Text" } + field { + id = "field3" + name = "Field 3" + type = "RichText" + } } resource "contentful_entry" "myentry" { @@ -200,6 +244,28 @@ resource "contentful_entry" "myentry" { content = "Bacon is healthy!" locale = "en-US" } + field { + id = "field3" + locale = "en-US" + content = jsonencode({ + data= {}, + content= [ + { + nodeType= "paragraph", + content= [ + { + nodeType= "text", + marks= [], + value= "This is another paragraph.", + data= {}, + }, + ], + data= {}, + } + ], + nodeType= "document" + }) + } published = false archived = false depends_on = [contentful_contenttype.mycontenttype] diff --git a/contentful/resource_contentful_environment.go b/contentful/resource_contentful_environment.go index 097cc5e..fbf64d7 100644 --- a/contentful/resource_contentful_environment.go +++ b/contentful/resource_contentful_environment.go @@ -1,17 +1,20 @@ package contentful import ( + "context" + "errors" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/labd/contentful-go" ) func resourceContentfulEnvironment() *schema.Resource { return &schema.Resource{ - Description: "A Contentful Environment represents a space environment.", - Create: resourceCreateEnvironment, - Read: resourceReadEnvironment, - Update: resourceUpdateEnvironment, - Delete: resourceDeleteEnvironment, + Description: "A Contentful Environment represents a space environment.", + CreateContext: resourceCreateEnvironment, + ReadContext: resourceReadEnvironment, + UpdateContext: resourceUpdateEnvironment, + DeleteContext: resourceDeleteEnvironment, Schema: map[string]*schema.Schema{ "version": { @@ -30,20 +33,20 @@ func resourceContentfulEnvironment() *schema.Resource { } } -func resourceCreateEnvironment(d *schema.ResourceData, m interface{}) (err error) { +func resourceCreateEnvironment(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) environment := &contentful.Environment{ Name: d.Get("name").(string), } - err = client.Environments.Upsert(d.Get("space_id").(string), environment) + err := client.Environments.Upsert(d.Get("space_id").(string), environment) if err != nil { - return err + return parseError(err) } if err := setEnvironmentProperties(d, environment); err != nil { - return err + return parseError(err) } d.SetId(environment.Name) @@ -51,25 +54,25 @@ func resourceCreateEnvironment(d *schema.ResourceData, m interface{}) (err error return nil } -func resourceUpdateEnvironment(d *schema.ResourceData, m interface{}) (err error) { +func resourceUpdateEnvironment(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) environmentID := d.Id() environment, err := client.Environments.Get(spaceID, environmentID) if err != nil { - return err + return parseError(err) } environment.Name = d.Get("name").(string) err = client.Environments.Upsert(spaceID, environment) if err != nil { - return err + return parseError(err) } if err := setEnvironmentProperties(d, environment); err != nil { - return err + return parseError(err) } d.SetId(environment.Sys.ID) @@ -77,31 +80,42 @@ func resourceUpdateEnvironment(d *schema.ResourceData, m interface{}) (err error return nil } -func resourceReadEnvironment(d *schema.ResourceData, m interface{}) (err error) { +func resourceReadEnvironment(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) environmentID := d.Id() environment, err := client.Environments.Get(spaceID, environmentID) - if _, ok := err.(contentful.NotFoundError); ok { + var notFoundError contentful.NotFoundError + if errors.As(err, ¬FoundError) { d.SetId("") return nil } - return setEnvironmentProperties(d, environment) + err = setEnvironmentProperties(d, environment) + if err != nil { + return parseError(err) + } + + return nil } -func resourceDeleteEnvironment(d *schema.ResourceData, m interface{}) (err error) { +func resourceDeleteEnvironment(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) environmentID := d.Id() environment, err := client.Environments.Get(spaceID, environmentID) if err != nil { - return err + return parseError(err) } - return client.Environments.Delete(spaceID, environment) + err = client.Environments.Delete(spaceID, environment) + if err != nil { + return parseError(err) + } + + return nil } func setEnvironmentProperties(d *schema.ResourceData, environment *contentful.Environment) error { diff --git a/contentful/resource_contentful_locale.go b/contentful/resource_contentful_locale.go index 755bc46..f6d8104 100644 --- a/contentful/resource_contentful_locale.go +++ b/contentful/resource_contentful_locale.go @@ -1,6 +1,9 @@ package contentful import ( + "context" + "errors" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/labd/contentful-go" ) @@ -9,10 +12,10 @@ func resourceContentfulLocale() *schema.Resource { return &schema.Resource{ Description: "A Contentful Locale represents a language and region combination.", - Create: resourceCreateLocale, - Read: resourceReadLocale, - Update: resourceUpdateLocale, - Delete: resourceDeleteLocale, + CreateContext: resourceCreateLocale, + ReadContext: resourceReadLocale, + UpdateContext: resourceUpdateLocale, + DeleteContext: resourceDeleteLocale, Schema: map[string]*schema.Schema{ "version": { @@ -55,7 +58,7 @@ func resourceContentfulLocale() *schema.Resource { } } -func resourceCreateLocale(d *schema.ResourceData, m interface{}) (err error) { +func resourceCreateLocale(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) @@ -68,14 +71,14 @@ func resourceCreateLocale(d *schema.ResourceData, m interface{}) (err error) { CMA: d.Get("cma").(bool), } - err = client.Locales.Upsert(spaceID, locale) + err := client.Locales.Upsert(spaceID, locale) if err != nil { - return err + return parseError(err) } err = setLocaleProperties(d, locale) if err != nil { - return err + return parseError(err) } d.SetId(locale.Sys.ID) @@ -83,32 +86,38 @@ func resourceCreateLocale(d *schema.ResourceData, m interface{}) (err error) { return nil } -func resourceReadLocale(d *schema.ResourceData, m interface{}) error { +func resourceReadLocale(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) localeID := d.Id() locale, err := client.Locales.Get(spaceID, localeID) - if _, ok := err.(*contentful.NotFoundError); ok { + var notFoundError *contentful.NotFoundError + if errors.As(err, ¬FoundError) { d.SetId("") return nil } if err != nil { - return err + return parseError(err) + } + + err = setLocaleProperties(d, locale) + if err != nil { + return parseError(err) } - return setLocaleProperties(d, locale) + return nil } -func resourceUpdateLocale(d *schema.ResourceData, m interface{}) (err error) { +func resourceUpdateLocale(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) localeID := d.Id() locale, err := client.Locales.Get(spaceID, localeID) if err != nil { - return err + return parseError(err) } locale.Name = d.Get("name").(string) @@ -120,34 +129,35 @@ func resourceUpdateLocale(d *schema.ResourceData, m interface{}) (err error) { err = client.Locales.Upsert(spaceID, locale) if err != nil { - return err + return parseError(err) } err = setLocaleProperties(d, locale) if err != nil { - return err + return parseError(err) } return nil } -func resourceDeleteLocale(d *schema.ResourceData, m interface{}) (err error) { +func resourceDeleteLocale(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) localeID := d.Id() locale, err := client.Locales.Get(spaceID, localeID) if err != nil { - return err + return parseError(err) } err = client.Locales.Delete(spaceID, locale) - if _, ok := err.(*contentful.NotFoundError); ok { + var notFoundError *contentful.NotFoundError + if errors.As(err, ¬FoundError) { return nil } if err != nil { - return err + return parseError(err) } return nil diff --git a/contentful/resource_contentful_space.go b/contentful/resource_contentful_space.go index 36bc188..c159bb5 100644 --- a/contentful/resource_contentful_space.go +++ b/contentful/resource_contentful_space.go @@ -1,6 +1,9 @@ package contentful import ( + "context" + "errors" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/labd/contentful-go" ) @@ -9,10 +12,10 @@ func resourceContentfulSpace() *schema.Resource { return &schema.Resource{ Description: "A Contentful Space represents a space in Contentful.", - Create: resourceSpaceCreate, - Read: resourceSpaceRead, - Update: resourceSpaceUpdate, - Delete: resourceSpaceDelete, + CreateContext: resourceSpaceCreate, + ReadContext: resourceSpaceRead, + UpdateContext: resourceSpaceUpdate, + DeleteContext: resourceSpaceDelete, Schema: map[string]*schema.Schema{ "version": { @@ -33,7 +36,7 @@ func resourceContentfulSpace() *schema.Resource { } } -func resourceSpaceCreate(d *schema.ResourceData, m interface{}) (err error) { +func resourceSpaceCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) space := &contentful.Space{ @@ -41,14 +44,14 @@ func resourceSpaceCreate(d *schema.ResourceData, m interface{}) (err error) { DefaultLocale: d.Get("default_locale").(string), } - err = client.Spaces.Upsert(space) + err := client.Spaces.Upsert(space) if err != nil { - return err + return parseError(err) } err = updateSpaceProperties(d, space) if err != nil { - return err + return parseError(err) } d.SetId(space.Sys.ID) @@ -56,45 +59,51 @@ func resourceSpaceCreate(d *schema.ResourceData, m interface{}) (err error) { return nil } -func resourceSpaceRead(d *schema.ResourceData, m interface{}) error { +func resourceSpaceRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Id() _, err := client.Spaces.Get(spaceID) - if _, ok := err.(contentful.NotFoundError); ok { + var notFoundError contentful.NotFoundError + if errors.As(err, ¬FoundError) { d.SetId("") return nil } - return err + return parseError(err) } -func resourceSpaceUpdate(d *schema.ResourceData, m interface{}) (err error) { +func resourceSpaceUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Id() space, err := client.Spaces.Get(spaceID) if err != nil { - return err + return parseError(err) } space.Name = d.Get("name").(string) err = client.Spaces.Upsert(space) if err != nil { - return err + return parseError(err) } - return updateSpaceProperties(d, space) + err = updateSpaceProperties(d, space) + if err != nil { + return parseError(err) + } + + return nil } -func resourceSpaceDelete(d *schema.ResourceData, m interface{}) (err error) { +func resourceSpaceDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Id() space, err := client.Spaces.Get(spaceID) if err != nil { - return err + return parseError(err) } err = client.Spaces.Delete(space) @@ -102,7 +111,7 @@ func resourceSpaceDelete(d *schema.ResourceData, m interface{}) (err error) { return nil } - return err + return parseError(err) } func updateSpaceProperties(d *schema.ResourceData, space *contentful.Space) error { diff --git a/contentful/resource_contentful_webhook.go b/contentful/resource_contentful_webhook.go index e74b749..49b500d 100644 --- a/contentful/resource_contentful_webhook.go +++ b/contentful/resource_contentful_webhook.go @@ -1,6 +1,9 @@ package contentful import ( + "context" + "errors" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/labd/contentful-go" ) @@ -9,10 +12,10 @@ func resourceContentfulWebhook() *schema.Resource { return &schema.Resource{ Description: "A Contentful Webhook represents a webhook that can be used to notify external services of changes in a space.", - Create: resourceCreateWebhook, - Read: resourceReadWebhook, - Update: resourceUpdateWebhook, - Delete: resourceDeleteWebhook, + CreateContext: resourceCreateWebhook, + ReadContext: resourceReadWebhook, + UpdateContext: resourceUpdateWebhook, + DeleteContext: resourceDeleteWebhook, Schema: map[string]*schema.Schema{ "version": { @@ -58,7 +61,7 @@ func resourceContentfulWebhook() *schema.Resource { } } -func resourceCreateWebhook(d *schema.ResourceData, m interface{}) (err error) { +func resourceCreateWebhook(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) @@ -71,14 +74,14 @@ func resourceCreateWebhook(d *schema.ResourceData, m interface{}) (err error) { HTTPBasicPassword: d.Get("http_basic_auth_password").(string), } - err = client.Webhooks.Upsert(spaceID, webhook) + err := client.Webhooks.Upsert(spaceID, webhook) if err != nil { - return err + return parseError(err) } err = setWebhookProperties(d, webhook) if err != nil { - return err + return parseError(err) } d.SetId(webhook.Sys.ID) @@ -86,14 +89,14 @@ func resourceCreateWebhook(d *schema.ResourceData, m interface{}) (err error) { return nil } -func resourceUpdateWebhook(d *schema.ResourceData, m interface{}) (err error) { +func resourceUpdateWebhook(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) webhookID := d.Id() webhook, err := client.Webhooks.Get(spaceID, webhookID) if err != nil { - return err + return parseError(err) } webhook.Name = d.Get("name").(string) @@ -105,12 +108,12 @@ func resourceUpdateWebhook(d *schema.ResourceData, m interface{}) (err error) { err = client.Webhooks.Upsert(spaceID, webhook) if err != nil { - return err + return parseError(err) } err = setWebhookProperties(d, webhook) if err != nil { - return err + return parseError(err) } d.SetId(webhook.Sys.ID) @@ -118,32 +121,38 @@ func resourceUpdateWebhook(d *schema.ResourceData, m interface{}) (err error) { return nil } -func resourceReadWebhook(d *schema.ResourceData, m interface{}) error { +func resourceReadWebhook(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) webhookID := d.Id() webhook, err := client.Webhooks.Get(spaceID, webhookID) - if _, ok := err.(contentful.NotFoundError); ok { + var notFoundError contentful.NotFoundError + if errors.As(err, ¬FoundError) { d.SetId("") return nil } if err != nil { - return err + return parseError(err) } - return setWebhookProperties(d, webhook) + err = setWebhookProperties(d, webhook) + if err != nil { + return parseError(err) + } + + return nil } -func resourceDeleteWebhook(d *schema.ResourceData, m interface{}) (err error) { +func resourceDeleteWebhook(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { client := m.(*contentful.Client) spaceID := d.Get("space_id").(string) webhookID := d.Id() webhook, err := client.Webhooks.Get(spaceID, webhookID) if err != nil { - return err + return parseError(err) } err = client.Webhooks.Delete(spaceID, webhook) @@ -151,7 +160,7 @@ func resourceDeleteWebhook(d *schema.ResourceData, m interface{}) (err error) { return nil } - return err + return parseError(err) } func setWebhookProperties(d *schema.ResourceData, webhook *contentful.Webhook) (err error) { diff --git a/docs/resources/entry.md b/docs/resources/entry.md index 61233ec..73c72ef 100644 --- a/docs/resources/entry.md +++ b/docs/resources/entry.md @@ -28,6 +28,28 @@ resource "contentful_entry" "example_entry" { content = "Lettuce is healthy!" locale = "en-US" } + field { + id = "content" + locale = "en-US" + content = jsonencode({ + data = {}, + content = [ + { + nodeType = "paragraph", + content = [ + { + nodeType = "text", + marks = [], + value = "This is a paragraph", + data = {}, + }, + ], + data = {}, + } + ], + nodeType = "document" + }) + } published = false archived = false depends_on = [contentful_contenttype.mycontenttype] @@ -57,7 +79,7 @@ resource "contentful_entry" "example_entry" { Required: -- `content` (String) +- `content` (String) The content of the field. If the field type is Richtext the content can be passed as stringified JSON (see example). - `locale` (String) Read-Only: diff --git a/examples/resources/contentful_entry/resource.tf b/examples/resources/contentful_entry/resource.tf index c1ebf51..c556074 100644 --- a/examples/resources/contentful_entry/resource.tf +++ b/examples/resources/contentful_entry/resource.tf @@ -13,7 +13,29 @@ resource "contentful_entry" "example_entry" { content = "Lettuce is healthy!" locale = "en-US" } + field { + id = "content" + locale = "en-US" + content = jsonencode({ + data = {}, + content = [ + { + nodeType = "paragraph", + content = [ + { + nodeType = "text", + marks = [], + value = "This is a paragraph", + data = {}, + }, + ], + data = {}, + } + ], + nodeType = "document" + }) + } published = false archived = false depends_on = [contentful_contenttype.mycontenttype] -} \ No newline at end of file +} diff --git a/go.mod b/go.mod index dbdc91a..ca92569 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ go 1.21 require ( github.com/hashicorp/terraform-plugin-docs v0.19.0 github.com/labd/contentful-go v0.5.3 + github.com/stretchr/testify v1.8.4 ) require ( @@ -21,6 +22,7 @@ require ( github.com/bgentry/speakeasy v0.1.0 // indirect github.com/bmatcuk/doublestar/v4 v4.6.1 // indirect github.com/cloudflare/circl v1.3.7 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/cli v1.1.6 // indirect github.com/hashicorp/hc-install v0.6.4 // indirect @@ -30,6 +32,7 @@ require ( github.com/huandu/xstrings v1.4.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect github.com/posener/complete v1.2.3 // indirect github.com/rivo/uniseg v0.4.7 // indirect github.com/shopspring/decimal v1.4.0 // indirect