diff --git a/.changes/header.tpl.md b/.changes/header.tpl.md new file mode 100644 index 0000000..df8faa7 --- /dev/null +++ b/.changes/header.tpl.md @@ -0,0 +1,6 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), +and is generated by [Changie](https://github.com/miniscruff/changie). diff --git a/.changes/unreleased/.gitkeep b/.changes/unreleased/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/.changes/unreleased/Added-20250324-081735.yaml b/.changes/unreleased/Added-20250324-081735.yaml new file mode 100644 index 0000000..9c7be5f --- /dev/null +++ b/.changes/unreleased/Added-20250324-081735.yaml @@ -0,0 +1,3 @@ +kind: Added +body: Merge all improvements/fixes and new features from the flaconi version +time: 2025-03-24T08:17:35.614551+01:00 diff --git a/.changes/v0.3.1.md b/.changes/v0.3.1.md new file mode 100644 index 0000000..a19058c --- /dev/null +++ b/.changes/v0.3.1.md @@ -0,0 +1,24 @@ +0.3.1 (2021-04-30) +================== +# Fixes + +* Fixed a bug causing validations in fields to not be saved properly to the models + +0.3.0 (2021-04-29) +================== +Re-release of 0.1.0 to fix registry. +# Features + +Create, update and delete Contentful resources: +- [x] Spaces +- [x] Content Types +- [x] API Keys +- [x] Webhooks +- [x] Locales +- [x] Environments +- [x] Entries +- [x] Assets + +0.1.0 (2020-10-26) +================== +Initial release \ No newline at end of file diff --git a/.changes/v0.4.0.md b/.changes/v0.4.0.md new file mode 100644 index 0000000..51f75be --- /dev/null +++ b/.changes/v0.4.0.md @@ -0,0 +1,3 @@ +## v0.4.0 - 2023-09-15 +### Added +* Updated to go 1.21, updated documentation and metadata files diff --git a/.changes/v0.5.0.md b/.changes/v0.5.0.md new file mode 100644 index 0000000..69be05f --- /dev/null +++ b/.changes/v0.5.0.md @@ -0,0 +1,7 @@ +## v0.5.0 - 2024-04-19 +### Added +* Added option to set different base url for API +### Fixed +* Fixed documentation +### Security +* Updated dependencies diff --git a/.changes/v0.5.1.md b/.changes/v0.5.1.md new file mode 100644 index 0000000..885386e --- /dev/null +++ b/.changes/v0.5.1.md @@ -0,0 +1,4 @@ +## v0.5.1 - 2024-06-21 +### Fixed +* Added support for richtext +* Added better error handling diff --git a/.changes/v0.5.2.md b/.changes/v0.5.2.md new file mode 100644 index 0000000..0bbf1c7 --- /dev/null +++ b/.changes/v0.5.2.md @@ -0,0 +1,3 @@ +## v0.5.2 - 2023-10-25 +### Fixed +* use action correct diff --git a/.changes/v1.0.0.md b/.changes/v1.0.0.md new file mode 100644 index 0000000..83ce37a --- /dev/null +++ b/.changes/v1.0.0.md @@ -0,0 +1,3 @@ +## v1.0.0 - 2023-11-06 +### Changed +* Extended ContentType to support enviroments, more validations, locales in environments diff --git a/.changes/v1.1.0.md b/.changes/v1.1.0.md new file mode 100644 index 0000000..7cca132 --- /dev/null +++ b/.changes/v1.1.0.md @@ -0,0 +1,3 @@ +## v1.1.0 - 2023-11-08 +### Added +* Added support for bulk_editing and tracking_field_id in settings; removed call to get contentful environment diff --git a/.changes/v1.1.1.md b/.changes/v1.1.1.md new file mode 100644 index 0000000..1181aed --- /dev/null +++ b/.changes/v1.1.1.md @@ -0,0 +1,3 @@ +## v1.1.1 - 2023-11-10 +### Fixed +* Fixed issue that default values are not set diff --git a/.changes/v1.2.0.md b/.changes/v1.2.0.md new file mode 100644 index 0000000..040dbca --- /dev/null +++ b/.changes/v1.2.0.md @@ -0,0 +1,3 @@ +## v1.2.0 - 2023-11-13 +### Added +* Prevent field type change with error diff --git a/.changes/v1.2.1.md b/.changes/v1.2.1.md new file mode 100644 index 0000000..7be6808 --- /dev/null +++ b/.changes/v1.2.1.md @@ -0,0 +1,3 @@ +## v1.2.1 - 2023-11-14 +### Fixed +* Fixed field type validation to allow new fields in the middle diff --git a/.changes/v1.3.0.md b/.changes/v1.3.0.md new file mode 100644 index 0000000..14b01e5 --- /dev/null +++ b/.changes/v1.3.0.md @@ -0,0 +1,5 @@ +## v1.3.0 - 2023-12-08 +### Added +* Add support app configuration in the organization +### Fixed +* Fixed wrong behaviour when fields where sorted diff --git a/.changes/v1.4.0.md b/.changes/v1.4.0.md new file mode 100644 index 0000000..ee5d398 --- /dev/null +++ b/.changes/v1.4.0.md @@ -0,0 +1,3 @@ +## v1.4.0 - 2023-12-15 +### Added +* Add support for app_installations diff --git a/.changes/v2.0.0.md b/.changes/v2.0.0.md new file mode 100644 index 0000000..dfbd8e4 --- /dev/null +++ b/.changes/v2.0.0.md @@ -0,0 +1,7 @@ +## v2.0.0 - 2023-12-19 +### Added +* Add support for preview api keys +### Changed +* Use flaconi fork of contentful lib +* Replace labd with flaconi in the whole code base +* Use for app installation new client version diff --git a/.changes/v3.0.0.md b/.changes/v3.0.0.md new file mode 100644 index 0000000..3dcfd3e --- /dev/null +++ b/.changes/v3.0.0.md @@ -0,0 +1,6 @@ +## v3.0.0 - 2024-03-11 +### Added +* added for content types unique validation +* switch to latest version of the contentful go lib +### Changed +* environment for content type is now required diff --git a/.changie.yaml b/.changie.yaml new file mode 100644 index 0000000..704932a --- /dev/null +++ b/.changie.yaml @@ -0,0 +1,28 @@ +changesDir: .changes +unreleasedDir: unreleased +headerPath: header.tpl.md +changelogPath: CHANGELOG.md +versionExt: md +versionFormat: '## {{.Version}} - {{.Time.Format "2006-01-02"}}' +kindFormat: '### {{.Kind}}' +changeFormat: '* {{.Body}}' +kinds: + - label: Added + auto: minor + - label: Changed + auto: major + - label: Deprecated + auto: minor + - label: Removed + auto: major + - label: Fixed + auto: patch + - label: Security + auto: patch + - label: Dependency + auto: patch +newlines: + afterChangelogHeader: 1 + beforeChangelogVersion: 1 + endOfVersion: 1 +envPrefix: CHANGIE_ diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000..00c946f --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1 @@ +* @labd/go diff --git a/.github/CODE_OF_CONDUCT.md b/.github/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..18c9147 --- /dev/null +++ b/.github/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md new file mode 100644 index 0000000..043c105 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -0,0 +1,27 @@ +--- +name: Bug report +about: When you are running into an issue +title: '' +labels: bug, triage +assignees: '' + +--- + +### Version information +- **terraform**: _Please specify the version of Terraform you are using._ +- **terraform provider**: _Please specify the version of the provider you are using._ + +### Describe the bug +A clear and concise description of what the bug is. + +### To Reproduce +Steps to reproduce the behavior. + +### Expected behavior +A clear and concise description of what you expected to happen. + +### Screenshots +If applicable, add screenshots to help explain your problem. + +### Additional context +Add any other context about the problem here. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..3ba13e0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md new file mode 100644 index 0000000..9ccf42b --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -0,0 +1,12 @@ +--- +name: Feature request +about: When you are missing a specific feature +title: '' +labels: enhancement, triage +assignees: '' + +--- + +Describe the feature you would like to see implemented. Please provide as much +detail as possible. If you have a specific use case, please provide that as +well. diff --git a/.github/ISSUE_TEMPLATE/support-request.md b/.github/ISSUE_TEMPLATE/support-request.md new file mode 100644 index 0000000..7744c64 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/support-request.md @@ -0,0 +1,12 @@ +--- +name: Support request +about: When you have a generic question +title: '' +labels: question, triage +assignees: '' + +--- + +Describe your question here. Please provide as much detail as possible. If you +have a specific use case, please provide that as well. +``` diff --git a/.github/dependabot.yaml b/.github/dependabot.yaml new file mode 100644 index 0000000..44db557 --- /dev/null +++ b/.github/dependabot.yaml @@ -0,0 +1,25 @@ +version: 2 +updates: + - package-ecosystem: "gomod" + directory: "/" + schedule: + interval: "monthly" + day: tuesday + commit-message: + prefix: "chore(deps)" + groups: + go: + patterns: + - "*" + + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "monthly" + day: tuesday + commit-message: + prefix: "chore(deps)" + groups: + github-actions: + patterns: + - "*" diff --git a/.github/workflows/dependabot-changie.yaml b/.github/workflows/dependabot-changie.yaml new file mode 100644 index 0000000..cb0c658 --- /dev/null +++ b/.github/workflows/dependabot-changie.yaml @@ -0,0 +1,36 @@ +name: Dependabot add changie file +on: + pull_request: + types: [opened] + +permissions: + pull-requests: write + issues: write + repository-projects: write + contents: write + +jobs: + dependabot-changie: + runs-on: ubuntu-latest + if: github.actor == 'dependabot[bot]' + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Fetch Dependabot metadata + id: dependabot-metadata + uses: dependabot/fetch-metadata@v2 + with: + github-token: "${{ secrets.GITHUB_TOKEN }}" + + - name: Create change file + uses: miniscruff/changie-action@v2 + with: + version: latest + args: new --body "${{ github.event.pull_request.title }}" --kind Dependency + + - uses: stefanzweifel/git-auto-commit-action@v5 + with: + commit_message: "chore(deps): add changelog for dependabot updates" + commit_user_name: "dependabot[bot]" + commit_user_email: "dependabot[bot]@users.noreply.github.com" diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 0000000..4c3ccf6 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,33 @@ +name: release +on: + workflow_dispatch: + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + + - name: Import GPG key + id: import_gpg + uses: crazy-max/ghaction-import-gpg@v6.0.0 + with: + gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.PASSPHRASE }} + + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@v5 + with: + version: latest + args: release --clean + env: + GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 0000000..573324c --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,49 @@ +name: Run Tests + +on: [ push ] + +jobs: + + test: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Go + uses: actions/setup-go@v5 + with: + go-version-file: 'go.mod' + + - name: golangci-lint + continue-on-error: true + uses: golangci/golangci-lint-action@v3 + with: + args: --issues-exit-code=0 --timeout=5m + + - name: Run tests + run: go test -race -coverprofile=coverage.out -covermode=atomic -coverpkg=./... -v ./... + + - name: Upload to codecov + uses: codecov/codecov-action@v3 + with: + verbose: true + + changie: + runs-on: ubuntu-latest + needs: test + if: github.ref == 'refs/heads/main' && github.event_name != 'pull_request' + permissions: + contents: write + pull-requests: write + actions: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Prepare release + uses: labd/changie-release-action@v0.3.2 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + release-workflow: 'release.yaml' diff --git a/.github/workflows/triage.yaml b/.github/workflows/triage.yaml new file mode 100644 index 0000000..0dc162e --- /dev/null +++ b/.github/workflows/triage.yaml @@ -0,0 +1,27 @@ +name: Triage + +on: + pull_request: + types: + - opened + issues: + types: + - opened + +jobs: + add_to_project: + name: Push issue or PR to board + runs-on: ubuntu-latest + steps: + - name: get app token + id: get-app-token + uses: labd/action-gh-app-token@main + with: + app-id: ${{ secrets.RD_APP_ID }} + private-key: ${{ secrets.RD_APP_PRIVATE_KEY }} + installation-id: ${{ secrets.RD_APP_INSTALLATION_ID }} + - name: set to project board + uses: actions/add-to-project@v1.0.2 + with: + project-url: https://github.com/orgs/labd/projects/3 + github-token: ${{ steps.get-app-token.outputs.app-token }} diff --git a/.gitignore b/.gitignore index 62b43e7..92303c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,22 @@ terraform-provider-contentful .envrc +.terraform* +NOTES.md +main.tf + +*.backup +/dist/* +coverage.txt + +.terraform/ +terraform.* +crash.log + +.vscode +.idea + +local/ +!/local/*.example + +vendor/ +coverage.out diff --git a/.goreleaser.yml b/.goreleaser.yml new file mode 100644 index 0000000..5307fc7 --- /dev/null +++ b/.goreleaser.yml @@ -0,0 +1,55 @@ +project_name: terraform-provider-contentful + +builds: + - env: + # goreleaser does not work with CGO, it could also complicate + # usage by users in CI/CD systems like Terraform Cloud where + # they are unable to install libraries. + - CGO_ENABLED=0 + mod_timestamp: '{{ .CommitTimestamp }}' + flags: + - -trimpath + ldflags: + - '-s -w -X main.version={{.Version}} -X main.commit={{.ShortCommit}}' + goos: + - freebsd + - windows + - linux + - darwin + goarch: + - amd64 + - '386' + - arm + - arm64 + ignore: + - goos: darwin + goarch: '386' + binary: '{{ .ProjectName }}_v{{ .Version }}' + +archives: + - format: zip + name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' + +checksum: + name_template: '{{ .ProjectName }}_{{ .Version }}_SHA256SUMS' + algorithm: sha256 + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" + +signs: + - artifacts: checksum + args: + # if you are using this is a GitHub action or some other automated pipeline, you + # need to pass the batch flag to indicate its not interactive. + - "--batch" + - "--local-user" + - "{{ .Env.GPG_FINGERPRINT }}" # set this environment variable for your signing key + - "--output" + - "${signature}" + - "--detach-sign" + - "${artifact}" diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..edf6921 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,49 @@ +# Changelog +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html), +and is generated by [Changie](https://github.com/miniscruff/changie). + + +## v0.5.1 - 2024-06-21 +### Fixed +* Added support for richtext +* Added better error handling + +## v0.5.0 - 2024-04-19 +### Added +* Added option to set different base url for API +### Fixed +* Fixed documentation +### Security +* Updated dependencies + +## v0.4.0 - 2023-09-15 +### Added +* Updated to go 1.21, updated documentation and metadata files + +0.3.1 (2021-04-30) +================== +# Fixes + +* Fixed a bug causing validations in fields to not be saved properly to the models + +0.3.0 (2021-04-29) +================== +Re-release of 0.1.0 to fix registry. +# Features + +Create, update and delete Contentful resources: +- [x] Spaces +- [x] Content Types +- [x] API Keys +- [x] Webhooks +- [x] Locales +- [x] Environments +- [x] Entries +- [x] Assets + +0.1.0 (2020-10-26) +================== +Initial release \ No newline at end of file diff --git a/Dockerfile-test b/Dockerfile-test deleted file mode 100644 index 5e5a912..0000000 --- a/Dockerfile-test +++ /dev/null @@ -1,11 +0,0 @@ -FROM golang:1.9 - -WORKDIR /go/src/github.com/danihodovic/contentful-terraform - -# http://stackoverflow.com/questions/39278756/cache-go-get-in-docker-build -COPY ./install-dependencies.sh /go/src/github.com/danihodovic/contentful-terraform/ -RUN ./install-dependencies.sh - -COPY . /go/src/github.com/danihodovic/contentful-terraform - -CMD go test -v diff --git a/LICENSE b/LICENSE index d8b47e8..38e31bc 100644 --- a/LICENSE +++ b/LICENSE @@ -14,7 +14,7 @@ copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE diff --git a/Makefile b/Makefile index bce3756..46a67b4 100644 --- a/Makefile +++ b/Makefile @@ -1,31 +1,25 @@ -.PHONY: build +.PHONY: build, test-unit, interactive, testacc + build: - docker build -t contentful-terraform-test -f Dockerfile-test . + go build -.PHONY: test-unit test-unit: build - docker run \ - contentful-terraform-test \ - go test -v - -# Runs an end-to-end integration test using Contentful. -# Requires that the following environment variables are set: -# - CONTENTFUL_MANAGEMENT_TOKEN -# - CONTENTFUL_ORGANIZATION_ID -.PHONY: test-integration -test-integration: build - docker run \ - -e CONTENTFUL_MANAGEMENT_TOKEN \ - -e CONTENTFUL_ORGANIZATION_ID \ + sudo docker run \ + -e CONTENTFUL_MANAGEMENT_TOKEN=${CONTENTFUL_MANAGEMENT_TOKEN} \ + -e CONTENTFUL_ORGANIZATION_ID=${CONTENTFUL_ORGANIZATION_ID} \ + -e SPACE_ID=${SPACE_ID} \ -e "TF_ACC=true" \ - contentful-terraform-test \ - go test -v + terraform-provider-contentful \ + go test ./... -v -.PHONY: interactive interactive: - docker run -it \ - -v $(shell pwd):/go/src/github.com/danihodovic/contentful-terraform \ - -e CONTENTFUL_MANAGEMENT_TOKEN \ - -e CONTENTFUL_ORGANIZATION_ID \ - contentful-terraform-test \ + sudo -S docker run -it \ + -v $(shell pwd):/go/src/github.com/labd/terraform-provider-contentful \ + -e CONTENTFUL_MANAGEMENT_TOKEN=${CONTENTFUL_MANAGEMENT_TOKEN} \ + -e CONTENTFUL_ORGANIZATION_ID=${CONTENTFUL_ORGANIZATION_ID} \ + -e SPACE_ID=${SPACE_ID} \ + terraform-provider-contentful \ bash + +testacc: + TF_ACC=1 go test -v ./... diff --git a/README.md b/README.md index 4ddae03..6a8ea6b 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,10 @@ -[![Build Status](https://img.shields.io/circleci/project/github/contentful-labs/terraform-contentful.svg?branch=master)](https://circleci.com/gh/contentful-labs/terraform-contentful) -[![license](https://img.shields.io/github/license/contentful-labs/terraform-contentful.svg)](https://github.com/contentful-labs/terraform-contentful/blob/master/LICENSE) +![Go](https://github.com/labd/terraform-provider-contentful/workflows/Go/badge.svg?branch=master) +[![license](https://img.shields.io/github/license/labd/terraform-provider-contentful.svg)](https://github.com/labd/terraform-provider-contentful/blob/master/LICENSE) Terraform Provider for [Contentful's](https://www.contentful.com) Content Management API # About - [Contentful](https://www.contentful.com) provides a content infrastructure for digital teams to power content in websites, apps, and devices. Unlike a CMS, Contentful was built to integrate with the modern software stack. It offers a central hub for structured content, powerful management and delivery APIs, and a customizable web app that enable developers and content creators to ship digital products faster. [Terraform](https://www.terraform.io) is a tool for building, changing, and versioning infrastructure safely and efficiently. Terraform can manage existing and popular service providers as well as custom in-house solutions. @@ -14,10 +13,13 @@ Terraform Provider for [Contentful's](https://www.contentful.com) Content Manage Create, update and delete Contentful resources such as: - [x] Spaces -- [ ] Content Types +- [x] Content Types - [x] API Keys - [x] Webhooks -- [ ] Locales +- [x] Locales +- [x] Environments +- [x] Entries +- [x] Assets # Getting started @@ -50,6 +52,10 @@ Build the binary $ go build -o terraform-provider-contentful +*Or using make command* + + $ make build + Add it to your ~/.terraformrc (or %APPDATA%/terraform.rc for Windows) $ cat ~/.terraformrc @@ -69,7 +75,7 @@ Use the provider by creating a main.tf file with: Run the terraform plan - terraform plan -out=contentful.plan + $ terraform plan -out=contentful.plan Check the changes ``` @@ -98,6 +104,10 @@ Plan: 1 to add, 0 to change, 0 to destroy. ``` Apply the plan + + $ terraform apply + +Check the Terraform output for warnings and errors ``` contentful_space.test: Creating... default_locale: "" => "en" @@ -117,11 +127,15 @@ State path: ## Testing - TF_ACC=1 go test -v + $ TF_ACC=1 go test -v + +To enable higher verbose mode: + + $ TF_LOG=debug TF_ACC=1 go test -v -To enable higher verbose mode +For testing, you can also make use of the make command: - TF_LOG=debug TF_ACC=1 go test -v + $ make test-unit ## Documentation/References @@ -135,7 +149,7 @@ Julien Fabre: [Writing a Terraform provider](http://blog.jfabre.net/2017/01/22/w ## Support -If you have a problem with this provider, please file an [issue](https://github.com/contentful-labs/terraform-contentful/issues/new) here on Github. +If you have a problem with this provider, please file an [issue](https://github.com/labd/terraform-provider-contentful/issues/new) here on Github. ## License diff --git a/Taskfile.yml b/Taskfile.yml new file mode 100644 index 0000000..8195122 --- /dev/null +++ b/Taskfile.yml @@ -0,0 +1,45 @@ +version: '3' + +tasks: + format: + cmds: + - go fmt ./... + + test: + cmds: + - go test -v ./... + + generate: + cmds: + - go run github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen --config=oapi-config.yaml ./openapi.yaml + - go generate ./... + + build-local: + cmds: + - go build -o terraform-provider-contentful_{{ .VERSION }} + - mkdir -p ~/.terraform.d/plugins/registry.terraform.io/labd/contentful/{{ .VERSION }}/{{ .PLATFORM }}/ + - mv terraform-provider-contentful_{{ .VERSION }} ~/.terraform.d/plugins/registry.terraform.io/labd/contentful/{{ .VERSION }}/{{ .PLATFORM }}/terraform-provider-contentful_v{{ .VERSION }} + - cmd: codesign --deep --force -s - ~/.terraform.d/plugins/registry.terraform.io/labd/contentful/{{ .VERSION }}/{{ .PLATFORM }}/terraform-provider-contentful_v{{ .VERSION }} + platforms: [ darwin ] + vars: + VERSION: 99.0.0 + PLATFORM: + sh: echo "$(go env GOOS)_$(go env GOARCH)" + + coverage-html: + cmds: + - go test -race -coverprofile=coverage.txt -covermode=atomic -coverpkg=./... ./... + - go tool cover -html=coverage.txt + + coverage: + cmds: + - go test -race -coverprofile=coverage.txt -covermode=atomic -coverpkg=./... ./... + - go tool cover -func=coverage.txt + + testacc: + cmds: + - TF_ACC=1 go test -v ./... + + testacct: + cmds: + - TF_ACC=1 go test -race -coverprofile=coverage.txt -covermode=atomic -coverpkg=./... -v ./... diff --git a/circle.yml b/circle.yml deleted file mode 100644 index 1959fde..0000000 --- a/circle.yml +++ /dev/null @@ -1,15 +0,0 @@ -machine: - pre: - # Install docker 1.10.0, can be removed when circle ships it - # https://discuss.circleci.com/t/docker-1-10-0-is-available-beta/2100 - - curl -sSL https://s3.amazonaws.com/circle-downloads/install-circleci-docker.sh | bash -s -- 1.10.0 - services: - - docker - -dependencies: - override: - - make build - -test: - override: - - make test-unit diff --git a/constants.go b/constants.go deleted file mode 100644 index 5c8f977..0000000 --- a/constants.go +++ /dev/null @@ -1,16 +0,0 @@ -package main - -import ( - "errors" -) - -var ( - baseURL = "https://api.contentful.com" - contentfulContentType = "application/vnd.contentful.management.v1+json" - // User friendly errors we return - errorUnauthorized = errors.New("401 Unauthorized. Is the CMA token valid?") - errorSpaceNotFound = errors.New("Space not found") - errorOrganizationNotFound = errors.New("Organization not found") - errorLocaleNotFound = errors.New("Locale not found") - errorWebhookNotFound = errors.New("The webhook could not be found") -) diff --git a/contentful/constants.go b/contentful/constants.go new file mode 100644 index 0000000..cff5289 --- /dev/null +++ b/contentful/constants.go @@ -0,0 +1,15 @@ +package contentful + +import ( + "os" +) + +var ( + // Environment variables + spaceID = os.Getenv("SPACE_ID") + CMAToken = os.Getenv("CONTENTFUL_MANAGEMENT_TOKEN") + orgID = os.Getenv("CONTENTFUL_ORGANIZATION_ID") + + // Terraform configuration values + logBoolean = os.Getenv("TF_LOG") +) diff --git a/contentful/errors.go b/contentful/errors.go new file mode 100644 index 0000000..e12fa8d --- /dev/null +++ b/contentful/errors.go @@ -0,0 +1,35 @@ +package contentful + +import ( + "errors" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/labd/contentful-go/pkgs/common" +) + +func parseError(err error) diag.Diagnostics { + if !errors.As(err, &common.ErrorResponse{}) { + return diag.FromErr(err) + } + + var warnings []diag.Diagnostic + for _, e := range err.(common.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.(common.ErrorResponse).Message, + }) +} diff --git a/contentful/errors_test.go b/contentful/errors_test.go new file mode 100644 index 0000000..ffd2f79 --- /dev/null +++ b/contentful/errors_test.go @@ -0,0 +1,70 @@ +package contentful + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/labd/contentful-go/pkgs/common" + "github.com/stretchr/testify/assert" +) + +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(&common.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(common.ErrorResponse{ + Message: "error message", + Details: &common.ErrorDetails{ + Errors: []*common.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(common.ErrorResponse{ + Message: "error message", + Details: &common.ErrorDetails{ + Errors: []*common.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.go b/contentful/provider.go new file mode 100644 index 0000000..4911b70 --- /dev/null +++ b/contentful/provider.go @@ -0,0 +1,99 @@ +package contentful + +import ( + "context" + "net/http" + + "github.com/deepmap/oapi-codegen/pkg/securityprovider" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/labd/terraform-provider-contentful/internal/sdk" + "github.com/labd/terraform-provider-contentful/internal/utils" +) + +// Provider returns the Terraform Provider as a scheme and makes resources reachable +func Provider() func() *schema.Provider { + return func() *schema.Provider { + p := &schema.Provider{ + Schema: map[string]*schema.Schema{ + "cma_token": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + DefaultFunc: schema.EnvDefaultFunc("CONTENTFUL_MANAGEMENT_TOKEN", nil), + Description: "The Contentful Management API token", + }, + "organization_id": { + Type: schema.TypeString, + Optional: true, + Sensitive: true, + DefaultFunc: schema.EnvDefaultFunc("CONTENTFUL_ORGANIZATION_ID", nil), + Description: "The organization ID", + }, + "base_url": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("CONTENTFUL_BASE_URL", "https://api.contentful.com"), + Description: "The base url to use for the Contentful API. Defaults to https://api.contentful.com", + }, + "environment": { + Type: schema.TypeString, + Optional: true, + DefaultFunc: schema.EnvDefaultFunc("CONTENTFUL_ENVIRONMENT", "master"), + Description: "The environment to use for the Contentful API. Defaults to master", + }, + }, + ResourcesMap: map[string]*schema.Resource{ + "contentful_space": resourceContentfulSpace(), + "contentful_webhook": resourceContentfulWebhook(), + "contentful_locale": resourceContentfulLocale(), + "contentful_environment": resourceContentfulEnvironment(), + "contentful_entry": resourceContentfulEntry(), + "contentful_asset": resourceContentfulAsset(), + }, + ConfigureContextFunc: providerConfigure, + } + + return p + } + +} + +// providerConfigure sets the configuration for the Terraform Provider +func providerConfigure(_ context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { + baseURL := d.Get("base_url").(string) + token := d.Get("cma_token").(string) + + authProvider, err := securityprovider.NewSecurityProviderBearerToken(token) + if err != nil { + return nil, diag.Errorf("Unable to Create Storyblok API Client", err.Error()) + } + + httpClient := &http.Client{ + Transport: http.DefaultTransport, + } + httpClient.Transport = utils.NewDebugTransport(httpClient.Transport) + + setVersionHeader := func(ctx context.Context, req *http.Request) error { + req.Header.Set("Content-Type", "application/vnd.contentful.management.v1+json") + return nil + } + + client, err := sdk.NewClientWithResponses( + baseURL, + sdk.WithHTTPClient(httpClient), + sdk.WithRequestEditorFn(setVersionHeader), + sdk.WithRequestEditorFn(authProvider.Intercept)) + + if err != nil { + return nil, diag.FromErr(err) + } + + data := utils.ProviderData{ + Client: client, + OrganizationId: d.Get("organization_id").(string), + } + + return data, nil +} diff --git a/contentful/provider_test.go b/contentful/provider_test.go new file mode 100644 index 0000000..b7c3f42 --- /dev/null +++ b/contentful/provider_test.go @@ -0,0 +1,40 @@ +package contentful + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var testAccProviders map[string]*schema.Provider +var testAccProvider *schema.Provider + +func init() { + testAccProvider = Provider()() + testAccProviders = map[string]*schema.Provider{ + "contentful": testAccProvider, + } +} + +func TestProvider(t *testing.T) { + if err := Provider()().InternalValidate(); err != nil { + t.Fatalf("err: %s", err) + } +} + +func TestProvider_impl(t *testing.T) { + var _ = Provider() +} + +func testAccPreCheck(t *testing.T) { + 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_asset.go b/contentful/resource_contentful_asset.go new file mode 100644 index 0000000..a7d63dd --- /dev/null +++ b/contentful/resource_contentful_asset.go @@ -0,0 +1,381 @@ +package contentful + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/labd/contentful-go/pkgs/common" + "github.com/labd/contentful-go/pkgs/model" + + "github.com/labd/terraform-provider-contentful/internal/utils" +) + +func resourceContentfulAsset() *schema.Resource { + return &schema.Resource{ + Create: resourceCreateAsset, + Read: resourceReadAsset, + Update: resourceUpdateAsset, + Delete: resourceDeleteAsset, + + Schema: map[string]*schema.Schema{ + "asset_id": { + Type: schema.TypeString, + Required: true, + }, + "version": { + Type: schema.TypeInt, + Computed: true, + }, + "space_id": { + Type: schema.TypeString, + Required: true, + }, + "fields": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "title": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "content": { + Type: schema.TypeString, + Required: true, + }, + "locale": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "description": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "content": { + Type: schema.TypeString, + Required: true, + }, + "locale": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + "file": { + Type: schema.TypeList, + Required: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locale": { + Type: schema.TypeString, + Required: true, + }, + "url": { + Type: schema.TypeString, + Computed: true, + }, + "upload": { + Type: schema.TypeString, + Required: true, + }, + "details": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "size": { + Type: schema.TypeInt, + Required: true, + }, + "image": { + Type: schema.TypeSet, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "width": { + Type: schema.TypeInt, + Required: true, + }, + "height": { + Type: schema.TypeInt, + Required: true, + }, + }, + }, + }, + }, + }, + }, + "upload_from": { + Type: schema.TypeString, + Computed: true, + }, + "file_name": { + Type: schema.TypeString, + Required: true, + }, + "content_type": { + Type: schema.TypeString, + Required: true, + }, + }, + }, + }, + }, + }, + }, + "published": { + Type: schema.TypeBool, + Required: true, + }, + "environment": { + Type: schema.TypeString, + Required: true, + }, + "archived": { + Type: schema.TypeBool, + Required: true, + }, + }, + } +} + +func resourceCreateAsset(d *schema.ResourceData, m interface{}) (err error) { + client := m.(utils.ProviderData).CMAClient + spaceID := d.Get("space_id").(string) + + assetClient := client.WithSpaceId(spaceID).WithEnvironment(d.Get("environment").(string)).Assets() + + asset, err := buildAsset(d) + + if err != nil { + return err + } + + if err = assetClient.Upsert(context.Background(), asset); err != nil { + return err + } + + if err = assetClient.Process(context.Background(), asset); err != nil { + return err + } + + d.SetId(asset.Sys.ID) + + if err := setAssetProperties(d, asset); err != nil { + return err + } + + time.Sleep(1 * time.Second) // avoid race conditions with version mismatches + + if err = setAssetState(d, m); err != nil { + return err + } + + return err +} + +func resourceUpdateAsset(d *schema.ResourceData, m interface{}) (err error) { + client := m.(utils.ProviderData).CMAClient + spaceID := d.Get("space_id").(string) + assetID := d.Id() + + assetClient := client.WithSpaceId(spaceID).WithEnvironment(d.Get("environment").(string)).Assets() + + _, err = assetClient.Get(context.Background(), assetID) + if err != nil { + return err + } + + asset, err := buildAsset(d) + + if err != nil { + return err + } + + if err := assetClient.Upsert(context.Background(), asset); err != nil { + return err + } + + if err = assetClient.Process(context.Background(), asset); err != nil { + return err + } + + d.SetId(asset.Sys.ID) + + if err := setAssetProperties(d, asset); err != nil { + return err + } + + if err = setAssetState(d, m); err != nil { + return err + } + + return err +} + +func buildAsset(d *schema.ResourceData) (*model.Asset, error) { + fields := d.Get("fields").([]interface{})[0].(map[string]interface{}) + + localizedTitle := map[string]string{} + rawTitle := fields["title"].([]interface{}) + for i := 0; i < len(rawTitle); i++ { + field := rawTitle[i].(map[string]interface{}) + localizedTitle[field["locale"].(string)] = field["content"].(string) + } + + localizedDescription := map[string]string{} + rawDescription := fields["description"].([]interface{}) + for i := 0; i < len(rawDescription); i++ { + field := rawDescription[i].(map[string]interface{}) + localizedDescription[field["locale"].(string)] = field["content"].(string) + } + + files := fields["file"].([]interface{}) + + if len(files) == 0 { + return nil, fmt.Errorf("file block not defined in asset") + } + + fileData := map[string]*model.File{} + for _, file := range files { + fileLocale := file.(map[string]any) + + fileData[fileLocale["locale"].(string)] = &model.File{ + URL: "", + UploadURL: "", + UploadFrom: nil, + Details: nil, + FileName: fileLocale["file_name"].(string), + ContentType: fileLocale["content_type"].(string), + } + + if url, ok := fileLocale["url"].(string); ok && url != "" { + fileData[fileLocale["locale"].(string)].URL = url + } + + if upload, ok := fileLocale["upload"].(string); ok && upload != "" { + fileData[fileLocale["locale"].(string)].UploadURL = upload + } + + if details, ok := fileLocale["file_details"].(*model.FileDetails); ok { + fileData[fileLocale["locale"].(string)].Details = details + } + + if uploadFrom, ok := fileLocale["upload_from"].(string); ok && uploadFrom != "" { + fileData[fileLocale["locale"].(string)].UploadFrom = &model.UploadFrom{ + Sys: &model.BaseSys{ + ID: uploadFrom, + }, + } + } + + } + + return &model.Asset{ + Sys: &model.PublishSys{ + EnvironmentSys: model.EnvironmentSys{ + SpaceSys: model.SpaceSys{ + CreatedSys: model.CreatedSys{ + BaseSys: model.BaseSys{ + ID: d.Get("asset_id").(string), + Version: d.Get("version").(int), + }, + }, + }, + }, + }, + Fields: &model.AssetFields{ + Title: localizedTitle, + Description: localizedDescription, + File: fileData, + }, + }, nil +} + +func setAssetState(d *schema.ResourceData, m interface{}) (err error) { + client := m.(utils.ProviderData).CMAClient + spaceID := d.Get("space_id").(string) + assetID := d.Id() + + assetClient := client.WithSpaceId(spaceID).WithEnvironment(d.Get("environment").(string)).Assets() + + ctx := context.Background() + + asset, _ := assetClient.Get(ctx, assetID) + + if d.Get("published").(bool) && asset.Sys.PublishedAt == "" { + if err = assetClient.Publish(ctx, asset); err != nil { + return err + } + } else if !d.Get("published").(bool) && asset.Sys.PublishedAt != "" { + if err = assetClient.Unpublish(ctx, asset); err != nil { + return err + } + } + + if d.Get("archived").(bool) && asset.Sys.ArchivedAt == "" { + if err = assetClient.Archive(ctx, asset); err != nil { + return err + } + } else if !d.Get("archived").(bool) && asset.Sys.ArchivedAt != "" { + if err = assetClient.Unarchive(ctx, asset); err != nil { + return err + } + } + + err = setAssetProperties(d, asset) + return err +} + +func resourceReadAsset(d *schema.ResourceData, m interface{}) (err error) { + client := m.(utils.ProviderData).CMAClient + spaceID := d.Get("space_id").(string) + assetID := d.Id() + + assetClient := client.WithSpaceId(spaceID).WithEnvironment(d.Get("environment").(string)).Assets() + + asset, err := assetClient.Get(context.Background(), assetID) + if _, ok := err.(common.NotFoundError); ok { + d.SetId("") + return nil + } + + return setAssetProperties(d, asset) +} + +func resourceDeleteAsset(d *schema.ResourceData, m interface{}) (err error) { + client := m.(utils.ProviderData).CMAClient + spaceID := d.Get("space_id").(string) + assetID := d.Id() + + assetClient := client.WithSpaceId(spaceID).WithEnvironment(d.Get("environment").(string)).Assets() + + asset, err := assetClient.Get(context.Background(), assetID) + if err != nil { + return err + } + + return assetClient.Delete(context.Background(), asset) +} + +func setAssetProperties(d *schema.ResourceData, asset *model.Asset) (err error) { + if err = d.Set("space_id", asset.Sys.Space.Sys.ID); err != nil { + return err + } + + if err = d.Set("version", asset.Sys.Version); err != nil { + return err + } + + return err +} diff --git a/contentful/resource_contentful_asset_test.go b/contentful/resource_contentful_asset_test.go new file mode 100644 index 0000000..0de7c15 --- /dev/null +++ b/contentful/resource_contentful_asset_test.go @@ -0,0 +1,164 @@ +package contentful + +import ( + "context" + "fmt" + "testing" + + "github.com/labd/contentful-go/pkgs/model" + "github.com/labd/terraform-provider-contentful/internal/acctest" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccContentfulAsset_Basic(t *testing.T) { + var asset model.Asset + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccContentfulAssetDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContentfulAssetConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckContentfulAssetExists("contentful_asset.myasset", &asset), + testAccCheckContentfulAssetAttributes(&asset, map[string]interface{}{ + "space_id": spaceID, + }), + ), + }, + { + Config: testAccContentfulAssetUpdateConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckContentfulAssetExists("contentful_asset.myasset", &asset), + testAccCheckContentfulAssetAttributes(&asset, map[string]interface{}{ + "space_id": spaceID, + }), + ), + }, + }, + }) +} + +func testAccCheckContentfulAssetExists(n string, asset *model.Asset) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("not Found: %s", n) + } + + spaceID := rs.Primary.Attributes["space_id"] + if spaceID == "" { + return fmt.Errorf("no space_id is set") + } + + client := acctest.GetCMA() + + contentfulAsset, err := client.WithSpaceId(spaceID).WithEnvironment("master").Assets().Get(context.Background(), rs.Primary.ID) + if err != nil { + return err + } + + *asset = *contentfulAsset + + return nil + } +} + +func testAccCheckContentfulAssetAttributes(asset *model.Asset, attrs map[string]interface{}) resource.TestCheckFunc { + return func(s *terraform.State) error { + + spaceIDCheck := attrs["space_id"].(string) + if asset.Sys.Space.Sys.ID != spaceIDCheck { + return fmt.Errorf("space id does not match: %s, %s", asset.Sys.Space.Sys.ID, spaceIDCheck) + } + + return nil + } +} + +func testAccContentfulAssetDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "contentful_entry" { + continue + } + + // get space id from resource data + spaceID := rs.Primary.Attributes["space_id"] + if spaceID == "" { + return fmt.Errorf("no space_id is set") + } + + // check webhook resource id + if rs.Primary.ID == "" { + return fmt.Errorf("no asset ID is set") + } + + // sdk client + client := acctest.GetCMA() + + asset, _ := client.WithSpaceId(spaceID).WithEnvironment("master").Assets().Get(context.Background(), rs.Primary.ID) + if asset == nil { + return nil + } + + return fmt.Errorf("asset still exists with id: %s", rs.Primary.ID) + } + + return nil +} + +var testAccContentfulAssetConfig = ` +resource "contentful_asset" "myasset" { + asset_id = "test_asset" + + environment = "master" + space_id = "` + spaceID + `" + fields { + title { + locale = "en-US" + content = "Asset title" + } + description { + locale = "en-US" + content = "Asset description" + } + file { + upload = "https://images.ctfassets.net/fo9twyrwpveg/2VQx7vz73aMEYi20MMgCk0/66e502115b1f1f973a944b4bd2cc536f/IC-1H_Modern_Stack_Website.svg" + file_name = "example.jpeg" + content_type = "image/jpeg" + locale = "en-US" + } + } + published = true + archived = false +} +` + +var testAccContentfulAssetUpdateConfig = ` +resource "contentful_asset" "myasset" { + asset_id = "test_asset" + environment = "master" + space_id = "` + spaceID + `" + fields { + title { + locale = "en-US" + content = "Updated asset title" + } + description { + locale = "en-US" + content = "Updated asset description" + } + file { + upload = "https://images.ctfassets.net/fo9twyrwpveg/2VQx7vz73aMEYi20MMgCk0/66e502115b1f1f973a944b4bd2cc536f/IC-1H_Modern_Stack_Website.svg" + file_name = "example.jpeg" + content_type = "image/jpeg" + locale = "en-US" + } + } + published = false + archived = false +} +` diff --git a/contentful/resource_contentful_entry.go b/contentful/resource_contentful_entry.go new file mode 100644 index 0000000..f185b5e --- /dev/null +++ b/contentful/resource_contentful_entry.go @@ -0,0 +1,292 @@ +package contentful + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/labd/terraform-provider-contentful/internal/sdk" + "github.com/labd/terraform-provider-contentful/internal/utils" +) + +func resourceContentfulEntry() *schema.Resource { + return &schema.Resource{ + Description: "A Contentful Entry represents a piece of content in a space.", + + CreateContext: resourceCreateEntry, + ReadContext: resourceReadEntry, + UpdateContext: resourceUpdateEntry, + DeleteContext: resourceDeleteEntry, + + Schema: map[string]*schema.Schema{ + "entry_id": { + Type: schema.TypeString, + Required: true, + }, + "version": { + Type: schema.TypeInt, + Computed: true, + }, + "space_id": { + Type: schema.TypeString, + Required: true, + }, + "environment": { + Type: schema.TypeString, + Required: true, + }, + "contenttype_id": { + Type: schema.TypeString, + Required: true, + }, + "field": { + Type: schema.TypeList, + Required: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Required: true, + }, + "content": { + 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, + Required: true, + }, + }, + }, + }, + "published": { + Type: schema.TypeBool, + Required: true, + }, + "archived": { + Type: schema.TypeBool, + Required: true, + }, + }, + } +} + +func resourceCreateEntry(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).Client + spaceID := d.Get("space_id").(string) + environmentID := d.Get("environment").(string) + + fieldProperties := map[string]interface{}{} + rawField := d.Get("field").([]interface{}) + 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)] = parseContentValue(field["content"].(string)) + } + + body := sdk.EntryCreate{ + Fields: &fieldProperties, + } + + resp, err := client.CreateEntryWithResponse(ctx, spaceID, environmentID, nil, body) + if err != nil { + return parseError(err) + } + + if resp.StatusCode() != 201 { + return diag.Errorf("Failed to create entry") + } + + entry := resp.JSON201 + if err := setEntryProperties(d, entry); err != nil { + return parseError(err) + } + + d.SetId(*entry.Sys.Id) + + if err := setEntryState(ctx, d, m); err != nil { + return parseError(err) + } + + return parseError(err) +} + +func resourceUpdateEntry(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).Client + spaceID := d.Get("space_id").(string) + environmentID := d.Get("environment").(string) + entryID := d.Id() + + fieldProperties := map[string]any{} + rawField := d.Get("field").([]any) + for i := 0; i < len(rawField); i++ { + field := rawField[i].(map[string]any) + fieldProperties[field["id"].(string)] = map[string]any{} + fieldProperties[field["id"].(string)].(map[string]any)[field["locale"].(string)] = parseContentValue(field["content"].(string)) + } + + body := sdk.EntryUpdate{ + Fields: &fieldProperties, + } + + params := &sdk.UpdateEntryParams{ + XContentfulVersion: d.Get("version").(int), + } + + resp, err := client.UpdateEntryWithResponse(ctx, spaceID, environmentID, entryID, params, body) + if err != nil { + return parseError(err) + } + + if resp.StatusCode() != 200 { + return diag.Errorf("Failed to update entry") + } + + entry := resp.JSON200 + d.SetId(*entry.Sys.Id) + + if err := setEntryProperties(d, entry); err != nil { + return parseError(err) + } + + if err := setEntryState(ctx, d, m); err != nil { + return parseError(err) + } + + return nil +} + +func setEntryState(ctx context.Context, d *schema.ResourceData, m interface{}) (err error) { + client := m.(utils.ProviderData).Client + spaceID := d.Get("space_id").(string) + entryID := d.Id() + environmentID := d.Get("environment").(string) + + published := d.Get("published").(bool) + archived := d.Get("archived").(bool) + + resp, err := client.GetEntryWithResponse(ctx, spaceID, environmentID, entryID) + if err != nil { + return err + } + + if resp.StatusCode() != 200 { + return fmt.Errorf("Failed to get entry") + } + + entry := resp.JSON200 + + if published && entry.Sys.PublishedAt == nil { + resp, err := client.PublishEntryWithResponse(ctx, spaceID, environmentID, entryID) + if err != nil { + return err + } + if resp.StatusCode() != 200 { + return fmt.Errorf("Failed to publish entry") + } + } else if !published && entry.Sys.PublishedAt != nil { + resp, err := client.UnpublishEntryWithResponse(ctx, spaceID, environmentID, entryID) + if err != nil { + return err + } + if resp.StatusCode() != 200 { + return fmt.Errorf("Failed to unpublish entry") + } + } + + if archived && entry.Sys.ArchivedAt == nil { + resp, err := client.ArchiveEntryWithResponse(ctx, spaceID, environmentID, entryID) + if err != nil { + return err + } + if resp.StatusCode() != 200 { + return fmt.Errorf("Failed to archive entry") + } + + } else if !archived && entry.Sys.ArchivedAt != nil { + resp, err := client.UnarchiveEntryWithResponse(ctx, spaceID, environmentID, entryID) + if err != nil { + return err + } + if resp.StatusCode() != 200 { + return fmt.Errorf("Failed to unarchive entry") + } + } + + return err +} + +func resourceReadEntry(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).Client + spaceID := d.Get("space_id").(string) + entryID := d.Id() + environmentID := d.Get("environment").(string) + + resp, err := client.GetEntryWithResponse(ctx, spaceID, environmentID, entryID) + + if err != nil { + return parseError(err) + } + + if resp.StatusCode() != 200 { + d.SetId("") + return nil + } + + entry := resp.JSON200 + err = setEntryProperties(d, entry) + if err != nil { + return parseError(err) + } + + return nil +} +func resourceDeleteEntry(_ context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).CMAClient + spaceID := d.Get("space_id").(string) + entryID := d.Id() + + entryClient := client.WithSpaceId(spaceID).WithEnvironment(d.Get("environment").(string)).Entries() + + entry, err := entryClient.Get(context.Background(), entryID) + if err != nil { + return parseError(err) + } + + err = entryClient.Delete(context.Background(), entry) + if err != nil { + return parseError(err) + } + + return nil +} + +func setEntryProperties(d *schema.ResourceData, entry *sdk.Entry) (err error) { + if err = d.Set("space_id", entry.Sys.Space.Sys.Id); err != nil { + return err + } + + if err = d.Set("version", entry.Sys.Version); err != nil { + return err + } + + if err = d.Set("contenttype_id", entry.Sys.ContentType.Sys.Id); err != nil { + return err + } + + 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 new file mode 100644 index 0000000..7e4dc42 --- /dev/null +++ b/contentful/resource_contentful_entry_test.go @@ -0,0 +1,282 @@ +package contentful + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/labd/contentful-go/pkgs/model" + "github.com/labd/terraform-provider-contentful/internal/acctest" + "github.com/stretchr/testify/assert" +) + +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) { + //todo remove skip when entry is moved to new sdk style as content type already moved + t.Skip() + var entry model.Entry + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccContentfulEntryDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContentfulEntryConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckContentfulEntryExists("contentful_entry.myentry", &entry), + testAccCheckContentfulEntryAttributes(&entry, map[string]interface{}{ + "space_id": spaceID, + }), + ), + }, + { + Config: testAccContentfulEntryUpdateConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckContentfulEntryExists("contentful_entry.myentry", &entry), + testAccCheckContentfulEntryAttributes(&entry, map[string]interface{}{ + "space_id": spaceID, + }), + ), + }, + }, + }) +} + +func testAccCheckContentfulEntryExists(n string, entry *model.Entry) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("not Found: %s", n) + } + + spaceID := rs.Primary.Attributes["space_id"] + if spaceID == "" { + return fmt.Errorf("no space_id is set") + } + + contenttypeID := rs.Primary.Attributes["contenttype_id"] + if contenttypeID == "" { + return fmt.Errorf("no contenttype_id is set") + } + + client := acctest.GetCMA() + + contentfulEntry, err := client.WithSpaceId(os.Getenv("CONTENTFUL_SPACE_ID")).WithEnvironment("master").Entries().Get(context.Background(), rs.Primary.ID) + if err != nil { + return err + } + + *entry = *contentfulEntry + + return nil + } +} + +func testAccCheckContentfulEntryAttributes(entry *model.Entry, attrs map[string]interface{}) resource.TestCheckFunc { + return func(s *terraform.State) error { + + spaceIDCheck := attrs["space_id"].(string) + if entry.Sys.Space.Sys.ID != spaceIDCheck { + return fmt.Errorf("space id does not match: %s, %s", entry.Sys.Space.Sys.ID, spaceIDCheck) + } + + return nil + } +} + +func testAccContentfulEntryDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "contentful_entry" { + continue + } + + // get space id from resource data + spaceID := rs.Primary.Attributes["space_id"] + if spaceID == "" { + return fmt.Errorf("no space_id is set") + } + + // check webhook resource id + if rs.Primary.ID == "" { + return fmt.Errorf("no entry ID is set") + } + + // sdk client + client := acctest.GetCMA() + + entry, _ := client.WithSpaceId(os.Getenv("CONTENTFUL_SPACE_ID")).WithEnvironment("master").Entries().Get(context.Background(), rs.Primary.ID) + if entry == nil { + return nil + } + + return fmt.Errorf("entry still exists with id: %s", rs.Primary.ID) + } + + return nil +} + +var testAccContentfulEntryConfig = ` +resource "contentful_contenttype" "mycontenttype" { + space_id = "` + spaceID + `" + name = "tf_test_1" + environment = "master" + description = "Terraform Acc Test Content Type" + display_field = "field1" + field { + disabled = false + id = "field1" + localized = false + name = "Field 1" + omitted = false + required = true + type = "Text" + } + field { + disabled = false + id = "field2" + localized = false + name = "Field 2" + omitted = false + required = true + type = "Text" + } + field { + id = "field3" + name = "Field 3" + type = "RichText" + } +} + +resource "contentful_entry" "myentry" { + entry_id = "mytestentry" + space_id = "` + spaceID + `" + environment = "master" + contenttype_id = "tf_test_1" + locale = "en-US" + field { + id = "field1" + content = "Hello, World!" + locale = "en-US" + } + field { + id = "field2" + 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] +} +` + +var testAccContentfulEntryUpdateConfig = ` +resource "contentful_contenttype" "mycontenttype" { + space_id = "` + spaceID + `" + environment = "master" + name = "tf_test_1" + description = "Terraform Acc Test Content Type" + display_field = "field1" + field { + disabled = false + id = "field1" + localized = false + name = "Field 1" + omitted = false + required = true + type = "Text" + } + field { + disabled = false + id = "field2" + localized = false + name = "Field 2" + omitted = false + required = true + type = "Text" + } + field { + id = "field3" + name = "Field 3" + type = "RichText" + } +} + +resource "contentful_entry" "myentry" { + entry_id = "mytestentry" + space_id = "` + spaceID + `" + environment = "master" + contenttype_id = "tf_test_1" + locale = "en-US" + field { + id = "field1" + content = "Hello, World!" + locale = "en-US" + } + field { + id = "field2" + 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 new file mode 100644 index 0000000..c525ae2 --- /dev/null +++ b/contentful/resource_contentful_environment.go @@ -0,0 +1,163 @@ +package contentful + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/labd/terraform-provider-contentful/internal/sdk" + "github.com/labd/terraform-provider-contentful/internal/utils" +) + +func resourceContentfulEnvironment() *schema.Resource { + return &schema.Resource{ + Description: "A Contentful Environment represents a space environment.", + CreateContext: resourceCreateEnvironment, + ReadContext: resourceReadEnvironment, + UpdateContext: resourceUpdateEnvironment, + DeleteContext: resourceDeleteEnvironment, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "version": { + Type: schema.TypeInt, + Computed: true, + }, + "space_id": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceCreateEnvironment(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).Client + spaceID := d.Get("space_id").(string) + + body := sdk.EnvironmentCreate{ + Name: d.Get("name").(string), + } + + resp, err := client.CreateEnvironmentWithResponse(ctx, spaceID, body) + if err != nil { + return parseError(err) + } + + if resp.StatusCode() != 201 { + return diag.Errorf("Failed to create environment") + } + + environment := resp.JSON201 + if err := setEnvironmentProperties(d, environment); err != nil { + return parseError(err) + } + + d.SetId(*environment.Sys.Id) + + return nil +} + +func resourceUpdateEnvironment(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).Client + spaceID := d.Get("space_id").(string) + environmentID := d.Id() + + body := sdk.EnvironmentUpdate{ + Name: d.Get("name").(string), + } + + params := &sdk.UpdateEnvironmentParams{ + XContentfulVersion: d.Get("version").(int), + } + + resp, err := client.UpdateEnvironmentWithResponse(ctx, spaceID, environmentID, params, body) + if err != nil { + return parseError(err) + } + + if resp.StatusCode() != 200 { + return diag.Errorf("Failed to update environment") + } + + environment := resp.JSON200 + if err := setEnvironmentProperties(d, environment); err != nil { + return parseError(err) + } + + d.SetId(*environment.Sys.Id) + + return nil +} + +func resourceReadEnvironment(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).Client + spaceID := d.Get("space_id").(string) + environmentID := d.Id() + + resp, err := client.GetEnvironmentWithResponse(ctx, spaceID, environmentID) + if err != nil { + return parseError(err) + } + + if resp.StatusCode() == 404 { + d.SetId("") + return nil + } + + if resp.StatusCode() != 200 { + return diag.Errorf("Failed to read environment") + } + + environment := resp.JSON200 + err = setEnvironmentProperties(d, environment) + if err != nil { + return parseError(err) + } + + return nil +} + +func resourceDeleteEnvironment(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).Client + spaceID := d.Get("space_id").(string) + environmentID := d.Id() + + params := &sdk.DeleteEnvironmentParams{ + XContentfulVersion: d.Get("version").(int), + } + + resp, err := client.DeleteEnvironmentWithResponse(ctx, spaceID, environmentID, params) + if err != nil { + return parseError(err) + } + + if resp.StatusCode() != 204 { + return diag.Errorf("Failed to delete environment") + } + + return nil +} + +func setEnvironmentProperties(d *schema.ResourceData, environment *sdk.Environment) error { + if err := d.Set("space_id", environment.Sys.Space.Sys.Id); err != nil { + return err + } + + if err := d.Set("version", environment.Sys.Version); err != nil { + return err + } + + if err := d.Set("name", environment.Name); err != nil { + return err + } + + return nil +} diff --git a/contentful/resource_contentful_environment_test.go b/contentful/resource_contentful_environment_test.go new file mode 100644 index 0000000..1875cf3 --- /dev/null +++ b/contentful/resource_contentful_environment_test.go @@ -0,0 +1,132 @@ +package contentful + +import ( + "context" + "fmt" + "os" + "testing" + + "github.com/labd/contentful-go/pkgs/common" + "github.com/labd/contentful-go/pkgs/model" + "github.com/labd/terraform-provider-contentful/internal/acctest" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccContentfulEnvironment_Basic(t *testing.T) { + var environment model.Environment + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccContentfulEnvironmentDestroy, + Steps: []resource.TestStep{ + { + Config: testAccContentfulEnvironmentConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckContentfulEnvironmentExists("contentful_environment.myenvironment", &environment), + testAccCheckContentfulEnvironmentAttributes(&environment, map[string]interface{}{ + "space_id": spaceID, + "name": "provider-test", + }), + ), + }, + { + Config: testAccContentfulEnvironmentUpdateConfig, + Check: resource.ComposeTestCheckFunc( + testAccCheckContentfulEnvironmentExists("contentful_environment.myenvironment", &environment), + testAccCheckContentfulEnvironmentAttributes(&environment, map[string]interface{}{ + "space_id": spaceID, + "name": "provider-test-updated", + }), + ), + }, + }, + }) +} + +func testAccCheckContentfulEnvironmentExists(n string, environment *model.Environment) resource.TestCheckFunc { + return func(s *terraform.State) error { + rs, ok := s.RootModule().Resources[n] + if !ok { + return fmt.Errorf("not Found: %s", n) + } + + spaceID := rs.Primary.Attributes["space_id"] + if spaceID == "" { + return fmt.Errorf("no space_id is set") + } + + name := rs.Primary.Attributes["name"] + if name == "" { + return fmt.Errorf("no name is set") + } + + client := acctest.GetCMA() + + contentfulEnvironment, err := client.WithSpaceId(os.Getenv("CONTENTFUL_SPACE_ID")).Environments().Get(context.Background(), rs.Primary.ID) + + if err != nil { + return err + } + + *environment = *contentfulEnvironment + + return nil + } +} + +func testAccCheckContentfulEnvironmentAttributes(environment *model.Environment, attrs map[string]interface{}) resource.TestCheckFunc { + return func(s *terraform.State) error { + name := attrs["name"].(string) + if environment.Name != name { + return fmt.Errorf("environment name does not match: %s, %s", environment.Name, name) + } + + return nil + } +} + +func testAccContentfulEnvironmentDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "contentful_environment" { + continue + } + spaceID := rs.Primary.Attributes["space_id"] + if spaceID == "" { + return fmt.Errorf("no space_id is set") + } + + environmentID := rs.Primary.ID + if environmentID == "" { + return fmt.Errorf("no environment ID is set") + } + + client := acctest.GetCMA() + + _, err := client.WithSpaceId(os.Getenv("CONTENTFUL_SPACE_ID")).Environments().Get(context.Background(), environmentID) + + if _, ok := err.(common.NotFoundError); ok { + return nil + } + + return fmt.Errorf("environment still exists with id: %s", environmentID) + } + + return nil +} + +var testAccContentfulEnvironmentConfig = ` +resource "contentful_environment" "myenvironment" { + space_id = "` + spaceID + `" + name = "provider-test" +} +` + +var testAccContentfulEnvironmentUpdateConfig = ` +resource "contentful_environment" "myenvironment" { + space_id = "` + spaceID + `" + name = "provider-test-updated" +} +` diff --git a/contentful/resource_contentful_locale.go b/contentful/resource_contentful_locale.go new file mode 100644 index 0000000..62b3f65 --- /dev/null +++ b/contentful/resource_contentful_locale.go @@ -0,0 +1,276 @@ +package contentful + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/labd/terraform-provider-contentful/internal/sdk" + "github.com/labd/terraform-provider-contentful/internal/utils" +) + +func resourceContentfulLocaleParseImportId(id string) (string, string, string, error) { + parts := strings.SplitN(id, ":", 3) + + if len(parts) != 3 || parts[0] == "" || parts[1] == "" || parts[2] == "" { + return "", "", "", fmt.Errorf("unexpected format of ID (%s), expected localeId:env:spaceId", id) + } + + return parts[0], parts[1], parts[2], nil +} + +func resourceContentfulLocale() *schema.Resource { + return &schema.Resource{ + Description: "A Contentful Locale represents a language and region combination.", + + CreateContext: resourceCreateLocale, + ReadContext: resourceReadLocale, + UpdateContext: resourceUpdateLocale, + DeleteContext: resourceDeleteLocale, + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + id, environment, spaceId, err := resourceContentfulLocaleParseImportId(d.Id()) + + if err != nil { + return nil, err + } + + err = d.Set("environment", environment) + if err != nil { + return nil, err + } + + err = d.Set("space_id", spaceId) + if err != nil { + return nil, err + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil + }, + }, + + Schema: map[string]*schema.Schema{ + "version": { + Type: schema.TypeInt, + Computed: true, + }, + "space_id": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + "code": { + Type: schema.TypeString, + Required: true, + }, + "fallback_code": { + Type: schema.TypeString, + Optional: true, + }, + "optional": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "cda": { + Type: schema.TypeBool, + Optional: true, + Default: true, + }, + "cma": { + Type: schema.TypeBool, + Optional: true, + Default: false, + }, + "environment": { + Type: schema.TypeString, + Required: true, + }, + }, + } +} + +func resourceCreateLocale(ctx context.Context, d *schema.ResourceData, m any) diag.Diagnostics { + client := m.(utils.ProviderData).Client + spaceID := d.Get("space_id").(string) + environmentID := d.Get("environment").(string) + + body := sdk.LocaleCreate{ + Name: d.Get("name").(string), + Code: d.Get("code").(string), + FallbackCode: nil, + Optional: utils.Pointer(d.Get("optional").(bool)), + ContentDeliveryApi: utils.Pointer(d.Get("cda").(bool)), + ContentManagementApi: utils.Pointer(d.Get("cma").(bool)), + } + + if fallbackCode, ok := d.GetOk("fallback_code"); ok { + fallbackCodeStr := fallbackCode.(string) + body.FallbackCode = &fallbackCodeStr + } + + resp, err := client.CreateLocaleWithResponse(ctx, spaceID, environmentID, body) + if err != nil { + return parseError(err) + } + + if resp.StatusCode() != 201 { + return diag.Errorf("Failed to create locale") + } + + locale := resp.JSON201 + diagErr := setLocaleProperties(d, locale) + if diagErr != nil { + return diagErr + } + + d.SetId(*locale.Sys.Id) + + return nil +} + +func resourceReadLocale(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).Client + spaceID := d.Get("space_id").(string) + environmentId := d.Get("environment").(string) + localeId := d.Id() + + locale, err := getLocale(ctx, client, spaceID, environmentId, localeId) + if err != nil { + return parseError(err) + } + + if locale == nil { + d.SetId("") + return nil + } + + diagErr := setLocaleProperties(d, locale) + if diagErr != nil { + return diagErr + } + + return nil +} + +func resourceUpdateLocale(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).Client + spaceID := d.Get("space_id").(string) + environmentId := d.Get("environment").(string) + localeId := d.Id() + + params := &sdk.UpdateLocaleParams{ + XContentfulVersion: d.Get("version").(int), + } + body := sdk.LocaleUpdate{ + Name: d.Get("name").(string), + Code: d.Get("code").(string), + FallbackCode: nil, + Optional: utils.Pointer(d.Get("optional").(bool)), + ContentDeliveryApi: utils.Pointer(d.Get("cda").(bool)), + ContentManagementApi: utils.Pointer(d.Get("cma").(bool)), + } + + if fallbackCode, ok := d.GetOk("fallback_code"); ok { + fallbackCodeStr := fallbackCode.(string) + body.FallbackCode = &fallbackCodeStr + } + + resp, err := client.UpdateLocaleWithResponse(ctx, spaceID, environmentId, localeId, params, body) + if err != nil { + return parseError(err) + } + + if resp.StatusCode() != 200 { + return diag.Errorf("Failed to update locale") + } + + locale := resp.JSON200 + diagErr := setLocaleProperties(d, locale) + if diagErr != nil { + return diagErr + } + + return nil +} + +func resourceDeleteLocale(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).Client + spaceID := d.Get("space_id").(string) + environmentId := d.Get("environment").(string) + localeId := d.Id() + + resp, err := client.DeleteLocaleWithResponse(ctx, spaceID, environmentId, localeId) + if err != nil { + return parseError(err) + } + + if resp.StatusCode() != 204 { + return diag.Errorf("Failed to delete locale") + } + + return nil +} + +func setLocaleProperties(d *schema.ResourceData, locale *sdk.Locale) diag.Diagnostics { + err := d.Set("name", locale.Name) + if err != nil { + return diag.FromErr(err) + } + + err = d.Set("code", locale.Code) + if err != nil { + return diag.FromErr(err) + } + + err = d.Set("fallback_code", locale.FallbackCode) + if err != nil { + return diag.FromErr(err) + } + + err = d.Set("optional", locale.Optional) + if err != nil { + return diag.FromErr(err) + } + + err = d.Set("cda", locale.ContentDeliveryApi) + if err != nil { + return diag.FromErr(err) + } + + err = d.Set("cma", locale.ContentManagementApi) + if err != nil { + return diag.FromErr(err) + } + + err = d.Set("version", locale.Sys.Version) + if err != nil { + return diag.FromErr(err) + } + + return nil +} + +func getLocale(ctx context.Context, client *sdk.ClientWithResponses, spaceID, environmentId, localeId string) (*sdk.Locale, error) { + resp, err := client.GetLocaleWithResponse(ctx, spaceID, environmentId, localeId) + if err != nil { + return nil, err + } + + if resp.StatusCode() == 404 { + return nil, nil + } + + if resp.StatusCode() != 200 { + return nil, fmt.Errorf("Failed to read locale") + } + + return resp.JSON200, nil +} diff --git a/resource_contentful_locale_test.go b/contentful/resource_contentful_locale_test.go similarity index 50% rename from resource_contentful_locale_test.go rename to contentful/resource_contentful_locale_test.go index f6e06aa..0aac96b 100644 --- a/resource_contentful_locale_test.go +++ b/contentful/resource_contentful_locale_test.go @@ -1,32 +1,35 @@ -package main +package contentful import ( + "context" + "errors" "fmt" + "os" "testing" - "github.com/hashicorp/terraform/helper/acctest" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" - contentful "github.com/tolgaakyuz/contentful-go" + "github.com/labd/contentful-go/pkgs/common" + "github.com/labd/contentful-go/pkgs/model" + "github.com/labd/terraform-provider-contentful/internal/acctest" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" ) func TestAccContentfulLocales_Basic(t *testing.T) { - var locale contentful.Locale - - spaceName := fmt.Sprintf("space-name-%s", acctest.RandString(3)) - name := fmt.Sprintf("locale-name-%s", acctest.RandString(3)) + var locale model.Locale resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccContentfulLocaleDestroy, Steps: []resource.TestStep{ - resource.TestStep{ - Config: testAccContentfulLocaleConfig(spaceName, name), + { + Config: testAccContentfulLocaleConfig, Check: resource.ComposeTestCheckFunc( testAccCheckContentfulLocaleExists("contentful_locale.mylocale", &locale), testAccCheckContentfulLocaleAttributes(&locale, map[string]interface{}{ - "name": name, + "space_id": spaceID, + "name": "locale-name", "code": "de", "fallback_code": "en-US", "optional": false, @@ -35,12 +38,13 @@ func TestAccContentfulLocales_Basic(t *testing.T) { }), ), }, - resource.TestStep{ - Config: testAccContentfulLocaleUpdateConfig(spaceName, name), + { + Config: testAccContentfulLocaleUpdateConfig, Check: resource.ComposeTestCheckFunc( testAccCheckContentfulLocaleExists("contentful_locale.mylocale", &locale), testAccCheckContentfulLocaleAttributes(&locale, map[string]interface{}{ - "name": fmt.Sprintf("%s-updated", name), + "space_id": spaceID, + "name": "locale-name-updated", "code": "es", "fallback_code": "en-US", "optional": true, @@ -53,26 +57,27 @@ func TestAccContentfulLocales_Basic(t *testing.T) { }) } -func testAccCheckContentfulLocaleExists(n string, locale *contentful.Locale) resource.TestCheckFunc { +func testAccCheckContentfulLocaleExists(n string, locale *model.Locale) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not Found: %s", n) + return fmt.Errorf("not Found: %s", n) } spaceID := rs.Primary.Attributes["space_id"] if spaceID == "" { - return fmt.Errorf("No space_id is set") + return fmt.Errorf("no space_id is set") } localeID := rs.Primary.ID if localeID == "" { - return fmt.Errorf("No locale ID is set") + return fmt.Errorf("no locale ID is set") } - client := testAccProvider.Meta().(*contentful.Contentful) + client := acctest.GetCMA() + + contentfulLocale, err := client.WithSpaceId(os.Getenv("CONTENTFUL_SPACE_ID")).WithEnvironment("master").Locales().Get(context.Background(), localeID) - contentfulLocale, err := client.Locales.Get(spaceID, localeID) if err != nil { return err } @@ -83,36 +88,36 @@ func testAccCheckContentfulLocaleExists(n string, locale *contentful.Locale) res } } -func testAccCheckContentfulLocaleAttributes(locale *contentful.Locale, attrs map[string]interface{}) resource.TestCheckFunc { +func testAccCheckContentfulLocaleAttributes(locale *model.Locale, attrs map[string]interface{}) resource.TestCheckFunc { return func(s *terraform.State) error { name := attrs["name"].(string) if locale.Name != name { - return fmt.Errorf("Locale name does not match: %s, %s", locale.Name, name) + return fmt.Errorf("locale name does not match: %s, %s", locale.Name, name) } code := attrs["code"].(string) if locale.Code != code { - return fmt.Errorf("Locale code does not match: %s, %s", locale.Code, code) + return fmt.Errorf("locale code does not match: %s, %s", locale.Code, code) } fallbackCode := attrs["fallback_code"].(string) - if locale.FallbackCode != fallbackCode { - return fmt.Errorf("Locale fallback code does not match: %s, %s", locale.FallbackCode, fallbackCode) + if *locale.FallbackCode != fallbackCode { + return fmt.Errorf("locale fallback code does not match: %s, %s", *locale.FallbackCode, fallbackCode) } isOptional := attrs["optional"].(bool) if locale.Optional != isOptional { - return fmt.Errorf("Locale options value does not match: %t, %t", locale.Optional, isOptional) + return fmt.Errorf("locale options value does not match: %t, %t", locale.Optional, isOptional) } isCDA := attrs["cda"].(bool) if locale.CDA != isCDA { - return fmt.Errorf("Locale cda does not match: %t, %t", locale.CDA, isCDA) + return fmt.Errorf("locale cda does not match: %t, %t", locale.CDA, isCDA) } isCMA := attrs["cma"].(bool) if locale.CMA != isCMA { - return fmt.Errorf("Locale cma does not match: %t, %t", locale.CMA, isCMA) + return fmt.Errorf("locale cma does not match: %t, %t", locale.CMA, isCMA) } return nil @@ -127,63 +132,51 @@ func testAccContentfulLocaleDestroy(s *terraform.State) error { spaceID := rs.Primary.Attributes["space_id"] if spaceID == "" { - return fmt.Errorf("No space_id is set") + return fmt.Errorf("no space_id is set") } localeID := rs.Primary.ID if localeID == "" { - return fmt.Errorf("No locale ID is set") + return fmt.Errorf("no locale ID is set") } - client := testAccProvider.Meta().(*contentful.Contentful) + client := acctest.GetCMA() - _, err := client.Locales.Get(spaceID, localeID) - if _, ok := err.(contentful.NotFoundError); ok { + _, err := client.WithSpaceId(os.Getenv("CONTENTFUL_SPACE_ID")).WithEnvironment("master").Locales().Get(context.Background(), localeID) + + var notFoundError common.NotFoundError + if errors.As(err, ¬FoundError) { return nil } - return fmt.Errorf("Locale still exists with id: %s", localeID) + return fmt.Errorf("locale still exists with id: %s", localeID) } return nil } -func testAccContentfulLocaleConfig(spaceName, name string) string { - return fmt.Sprintf(` -resource "contentful_space" "myspace" { - name = "%s" - default_locale = "en-US" -} - +var testAccContentfulLocaleConfig = ` resource "contentful_locale" "mylocale" { - space_id = "${contentful_space.myspace.id}" - - name = "%s" + space_id = "` + spaceID + `" + environment = "master" + name = "locale-name" code = "de" fallback_code = "en-US" optional = false cda = false cma = true } -`, spaceName, name) -} - -func testAccContentfulLocaleUpdateConfig(spaceName, name string) string { - return fmt.Sprintf(` -resource "contentful_space" "myspace" { - name = "%s" - default_locale = "en-US" -} +` +var testAccContentfulLocaleUpdateConfig = ` resource "contentful_locale" "mylocale" { - space_id = "${contentful_space.myspace.id}" - - name = "%s-updated" + space_id = "` + spaceID + `" + environment = "master" + name = "locale-name-updated" code = "es" fallback_code = "en-US" optional = true cda = true cma = false } -`, spaceName, name) -} +` diff --git a/contentful/resource_contentful_space.go b/contentful/resource_contentful_space.go new file mode 100644 index 0000000..6a74129 --- /dev/null +++ b/contentful/resource_contentful_space.go @@ -0,0 +1,186 @@ +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/pkgs/common" + + "github.com/labd/terraform-provider-contentful/internal/sdk" + "github.com/labd/terraform-provider-contentful/internal/utils" +) + +func resourceContentfulSpace() *schema.Resource { + return &schema.Resource{ + Description: "A Contentful Space represents a space in Contentful.", + + CreateContext: resourceSpaceCreate, + ReadContext: resourceSpaceRead, + UpdateContext: resourceSpaceUpdate, + DeleteContext: resourceSpaceDelete, + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + + Schema: map[string]*schema.Schema{ + "version": { + Type: schema.TypeInt, + Computed: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + // Space specific props + "default_locale": { + Type: schema.TypeString, + Optional: true, + Default: "en", + }, + }, + } +} + +func resourceSpaceCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).Client + + body := sdk.SpaceCreate{ + Name: d.Get("name").(string), + DefaultLocale: utils.Pointer(d.Get("default_locale").(string)), + } + + resp, err := client.CreateSpaceWithResponse(ctx, nil, body) + if err != nil { + return parseError(err) + } + + if resp.StatusCode() != 201 { + return diag.Errorf("Failed to create space") + } + + space := resp.JSON201 + + err = updateSpaceProperties(d, space) + if err != nil { + return parseError(err) + } + + d.SetId(*space.Sys.Id) + + return nil +} + +func resourceSpaceRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).Client + spaceID := d.Id() + + resp, err := client.GetSpaceWithResponse(ctx, spaceID) + if err != nil { + return parseError(err) + } + + if resp.StatusCode() == 404 { + d.SetId("") + return nil + } + + if resp.StatusCode() != 200 { + return diag.Errorf("Failed to retrieve space") + } + + err = updateSpaceProperties(d, resp.JSON200) + if err != nil { + return parseError(err) + } + + return nil +} + +func resourceSpaceUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).Client + spaceID := d.Id() + + getResp, err := client.GetSpaceWithResponse(ctx, spaceID) + if err != nil { + return parseError(err) + } + + if getResp.StatusCode() != 200 { + return diag.Errorf("Failed to retrieve space") + } + + space := getResp.JSON200 + + // TODO: we can't update the default locale here, we need to do that via the + // locales endpoint, searching for the default locale + update := sdk.SpaceUpdate{ + Name: d.Get("name").(string), + } + + params := &sdk.UpdateSpaceParams{ + XContentfulVersion: d.Get("version").(int), + } + + resp, err := client.UpdateSpaceWithResponse(ctx, *space.Sys.Id, params, update) + if err != nil { + return parseError(err) + } + + if resp.StatusCode() != 200 { + return diag.Errorf("Failed to update space") + } + + space = resp.JSON200 + + err = updateSpaceProperties(d, space) + if err != nil { + return parseError(err) + } + + return nil +} + +func resourceSpaceDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).Client + spaceID := d.Id() + + getResp, err := client.GetSpaceWithResponse(ctx, spaceID) + if err != nil { + return parseError(err) + } + + if getResp.StatusCode() != 200 { + return diag.Errorf("Failed to retrieve space") + } + + params := &sdk.DeleteSpaceParams{ + XContentfulVersion: d.Get("version").(int), + } + + space := getResp.JSON200 + resp, err := client.DeleteSpaceWithResponse(ctx, *space.Sys.Id, params) + if _, ok := err.(common.NotFoundError); ok { + return nil + } + + if resp.StatusCode() != 204 { + return diag.Errorf("Failed to delete space") + } + + return parseError(err) +} + +func updateSpaceProperties(d *schema.ResourceData, space *sdk.Space) error { + err := d.Set("version", space.Sys.Version) + if err != nil { + return err + } + + err = d.Set("name", space.Name) + if err != nil { + return err + } + + return nil +} diff --git a/resource_contentful_space_test.go b/contentful/resource_contentful_space_test.go similarity index 71% rename from resource_contentful_space_test.go rename to contentful/resource_contentful_space_test.go index da1e9e5..0f51d30 100644 --- a/resource_contentful_space_test.go +++ b/contentful/resource_contentful_space_test.go @@ -1,27 +1,27 @@ -package main +package contentful import ( "fmt" "testing" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" - contentful "github.com/tolgaakyuz/contentful-go" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + contentful "github.com/labd/contentful-go" ) func TestAccContentfulSpace_Basic(t *testing.T) { - t.Skip() + t.Skip() // Space resource can only be tested when user has the rights to do so, if not, skip this test! resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccCheckContentfulSpaceDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccContentfulSpaceConfig, Check: resource.TestCheckResourceAttr( "contentful_space.myspace", "name", "TF Acc Test Space"), }, - resource.TestStep{ + { Config: testAccContentfulSpaceUpdateConfig, Check: resource.TestCheckResourceAttr( "contentful_space.myspace", "name", "TF Acc Test Changed Space"), @@ -31,7 +31,7 @@ func TestAccContentfulSpace_Basic(t *testing.T) { } func testAccCheckContentfulSpaceDestroy(s *terraform.State) error { - client := testAccProvider.Meta().(*contentful.Contentful) + client := testAccProvider.Meta().(*contentful.Client) for _, rs := range s.RootModule().Resources { if rs.Type != "contentful_space" { @@ -40,7 +40,7 @@ func testAccCheckContentfulSpaceDestroy(s *terraform.State) error { space, err := client.Spaces.Get(rs.Primary.ID) if err == nil { - return fmt.Errorf("Space %s still exists after destroy", space.Sys.ID) + return fmt.Errorf("space %s still exists after destroy", space.Sys.ID) } } @@ -49,7 +49,7 @@ func testAccCheckContentfulSpaceDestroy(s *terraform.State) error { var testAccContentfulSpaceConfig = ` resource "contentful_space" "myspace" { - name = "TF Acc Test Space" + name = "Playground" } ` diff --git a/contentful/resource_contentful_webhook.go b/contentful/resource_contentful_webhook.go new file mode 100644 index 0000000..f5accf7 --- /dev/null +++ b/contentful/resource_contentful_webhook.go @@ -0,0 +1,260 @@ +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/pkgs/common" + + "github.com/labd/terraform-provider-contentful/internal/sdk" + "github.com/labd/terraform-provider-contentful/internal/utils" +) + +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.", + + CreateContext: resourceCreateWebhook, + ReadContext: resourceReadWebhook, + UpdateContext: resourceUpdateWebhook, + DeleteContext: resourceDeleteWebhook, + + Schema: map[string]*schema.Schema{ + "version": { + Type: schema.TypeInt, + Computed: true, + }, + "space_id": { + Type: schema.TypeString, + Required: true, + }, + "name": { + Type: schema.TypeString, + Required: true, + }, + // Webhook specific props + "url": { + Type: schema.TypeString, + Required: true, + }, + "http_basic_auth_username": { + Type: schema.TypeString, + Optional: true, + Default: "", + }, + "http_basic_auth_password": { + Type: schema.TypeString, + Optional: true, + Default: "", + }, + "headers": { + Type: schema.TypeMap, + Optional: true, + }, + "topics": { + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + MinItems: 1, + Required: true, + }, + }, + } +} + +func resourceCreateWebhook(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).Client + spaceID := d.Get("space_id").(string) + + body := sdk.WebhookCreate{ + Name: d.Get("name").(string), + Url: d.Get("url").(string), + Topics: transformTopicsToContentfulFormat(d.Get("topics").([]any)), + Headers: transformHeadersToContentfulFormat(d.Get("headers")), + HttpBasicUsername: utils.Pointer(d.Get("http_basic_auth_username").(string)), + HttpBasicPassword: utils.Pointer(d.Get("http_basic_auth_password").(string)), + } + + resp, err := client.CreateWebhookWithResponse(ctx, spaceID, body) + if err != nil { + return parseError(err) + } + + if resp.StatusCode() != 201 { + return diag.Errorf("Failed to create webhook") + } + + webhook := resp.JSON201 + err = setWebhookProperties(d, webhook) + if err != nil { + return parseError(err) + } + + d.SetId(*webhook.Sys.Id) + + return nil +} + +func resourceUpdateWebhook(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).Client + spaceID := d.Get("space_id").(string) + webhookID := d.Id() + + resp, err := client.GetWebhookWithResponse(ctx, spaceID, webhookID) + if err != nil { + return parseError(err) + } + + if resp.StatusCode() != 200 { + return diag.Errorf("Webhook not found") + } + + params := &sdk.UpdateWebhookParams{ + XContentfulVersion: d.Get("version").(int), + } + + update := sdk.WebhookUpdate{ + Name: d.Get("name").(string), + Url: d.Get("url").(string), + Topics: transformTopicsToContentfulFormat(d.Get("topics").([]any)), + Headers: transformHeadersToContentfulFormat(d.Get("headers")), + HttpBasicUsername: utils.Pointer(d.Get("http_basic_auth_username").(string)), + HttpBasicPassword: utils.Pointer(d.Get("http_basic_auth_password").(string)), + } + + updateResp, err := client.UpdateWebhookWithResponse(ctx, spaceID, webhookID, params, update) + if err != nil { + return parseError(err) + } + + if updateResp.StatusCode() != 200 { + return diag.Errorf("Failed to update webhook") + } + + webhook := updateResp.JSON200 + err = setWebhookProperties(d, webhook) + if err != nil { + return parseError(err) + } + + d.SetId(*webhook.Sys.Id) + + return nil +} + +func resourceReadWebhook(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).Client + spaceID := d.Get("space_id").(string) + webhookID := d.Id() + + resp, err := client.GetWebhookWithResponse(ctx, spaceID, webhookID) + if _, ok := err.(common.NotFoundError); ok { + d.SetId("") + return nil + } + + if err != nil { + return parseError(err) + } + + webhook := resp.JSON200 + err = setWebhookProperties(d, webhook) + if err != nil { + return parseError(err) + } + + return nil +} + +func resourceDeleteWebhook(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + client := m.(utils.ProviderData).Client + spaceID := d.Get("space_id").(string) + webhookID := d.Id() + + params := &sdk.DeleteWebhookParams{ + XContentfulVersion: d.Get("version").(int), + } + + resp, err := client.DeleteWebhookWithResponse(ctx, spaceID, webhookID, params) + if err != nil { + return parseError(err) + } + + if resp.StatusCode() != 204 { + return diag.Errorf("Failed to delete webhook") + } + + return nil +} + +func setWebhookProperties(d *schema.ResourceData, webhook *sdk.Webhook) (err error) { + headers := make(map[string]string) + for _, entry := range webhook.Headers { + headers[entry.Key] = entry.Value + } + + err = d.Set("headers", headers) + if err != nil { + return err + } + + err = d.Set("space_id", webhook.Sys.Space.Sys.Id) + if err != nil { + return err + } + + err = d.Set("version", webhook.Sys.Version) + if err != nil { + return err + } + + err = d.Set("name", webhook.Name) + if err != nil { + return err + } + + err = d.Set("url", webhook.Url) + if err != nil { + return err + } + + err = d.Set("http_basic_auth_username", webhook.HttpBasicUsername) + if err != nil { + return err + } + + err = d.Set("topics", webhook.Topics) + if err != nil { + return err + } + + return nil +} + +func transformHeadersToContentfulFormat(headersTerraform any) *[]sdk.WebhookHeader { + var headers []sdk.WebhookHeader + + for k, v := range headersTerraform.(map[string]interface{}) { + headers = append(headers, sdk.WebhookHeader{ + Key: k, + Value: v.(string), + }) + } + if len(headers) == 0 { + return nil + } + + return &headers +} + +func transformTopicsToContentfulFormat(topicsTerraform []interface{}) []string { + var topics []string + + for _, v := range topicsTerraform { + topics = append(topics, v.(string)) + } + + return topics +} diff --git a/resource_contentful_webhook_test.go b/contentful/resource_contentful_webhook_test.go similarity index 71% rename from resource_contentful_webhook_test.go rename to contentful/resource_contentful_webhook_test.go index c35e59f..9baac31 100644 --- a/resource_contentful_webhook_test.go +++ b/contentful/resource_contentful_webhook_test.go @@ -1,12 +1,15 @@ -package main +package contentful import ( "fmt" "testing" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" - contentful "github.com/tolgaakyuz/contentful-go" + "github.com/labd/contentful-go/pkgs/common" + "github.com/labd/terraform-provider-contentful/internal/acctest" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + contentful "github.com/labd/contentful-go" ) func TestAccContentfulWebhook_Basic(t *testing.T) { @@ -17,24 +20,26 @@ func TestAccContentfulWebhook_Basic(t *testing.T) { Providers: testAccProviders, CheckDestroy: testAccContentfulWebhookDestroy, Steps: []resource.TestStep{ - resource.TestStep{ + { Config: testAccContentfulWebhookConfig, Check: resource.ComposeTestCheckFunc( testAccCheckContentfulWebhookExists("contentful_webhook.mywebhook", &webhook), testAccCheckContentfulWebhookAttributes(&webhook, map[string]interface{}{ - "name": "webhook-name", - "url": "https://www.example.com/test", + "space_id": spaceID, + "name": "webhook-name", + "url": "https://www.example.com/test", "http_basic_auth_username": "username", }), ), }, - resource.TestStep{ + { Config: testAccContentfulWebhookUpdateConfig, Check: resource.ComposeTestCheckFunc( testAccCheckContentfulWebhookExists("contentful_webhook.mywebhook", &webhook), testAccCheckContentfulWebhookAttributes(&webhook, map[string]interface{}{ - "name": "webhook-name-updated", - "url": "https://www.example.com/test-updated", + "space_id": spaceID, + "name": "webhook-name-updated", + "url": "https://www.example.com/test-updated", "http_basic_auth_username": "username-updated", }), ), @@ -47,21 +52,21 @@ func testAccCheckContentfulWebhookExists(n string, webhook *contentful.Webhook) return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] if !ok { - return fmt.Errorf("Not Found: %s", n) + return fmt.Errorf("not Found: %s", n) } // get space id from resource data spaceID := rs.Primary.Attributes["space_id"] if spaceID == "" { - return fmt.Errorf("No space_id is set") + return fmt.Errorf("no space_id is set") } // check webhook resource id if rs.Primary.ID == "" { - return fmt.Errorf("No webhook ID is set") + return fmt.Errorf("no webhook ID is set") } - client := testAccProvider.Meta().(*contentful.Contentful) + client := acctest.GetClient() contentfulWebhook, err := client.Webhooks.Get(spaceID, rs.Primary.ID) if err != nil { @@ -78,12 +83,12 @@ func testAccCheckContentfulWebhookAttributes(webhook *contentful.Webhook, attrs return func(s *terraform.State) error { name := attrs["name"].(string) if webhook.Name != name { - return fmt.Errorf("Webhook name does not match: %s, %s", webhook.Name, name) + return fmt.Errorf("webhook name does not match: %s, %s", webhook.Name, name) } url := attrs["url"].(string) if webhook.URL != url { - return fmt.Errorf("Webhook url does not match: %s, %s", webhook.URL, url) + return fmt.Errorf("webhook url does not match: %s, %s", webhook.URL, url) } /* topics := attrs["topics"].([]string) @@ -92,7 +97,7 @@ func testAccCheckContentfulWebhookAttributes(webhook *contentful.Webhook, attrs httpBasicAuthUsername := attrs["http_basic_auth_username"].(string) if webhook.HTTPBasicUsername != httpBasicAuthUsername { - return fmt.Errorf("Webhook http_basic_auth_username does not match: %s, %s", webhook.HTTPBasicUsername, httpBasicAuthUsername) + return fmt.Errorf("webhook http_basic_auth_username does not match: %s, %s", webhook.HTTPBasicUsername, httpBasicAuthUsername) } return nil @@ -108,36 +113,31 @@ func testAccContentfulWebhookDestroy(s *terraform.State) error { // get space id from resource data spaceID := rs.Primary.Attributes["space_id"] if spaceID == "" { - return fmt.Errorf("No space_id is set") + return fmt.Errorf("no space_id is set") } // check webhook resource id if rs.Primary.ID == "" { - return fmt.Errorf("No webhook ID is set") + return fmt.Errorf("no webhook ID is set") } // sdk client - client := testAccProvider.Meta().(*contentful.Contentful) + client := acctest.GetClient() _, err := client.Webhooks.Get(spaceID, rs.Primary.ID) - if _, ok := err.(contentful.NotFoundError); ok { + if _, ok := err.(common.NotFoundError); ok { return nil } - return fmt.Errorf("Webhook still exists with id: %s", rs.Primary.ID) + return fmt.Errorf("webhook still exists with id: %s", rs.Primary.ID) } return nil } var testAccContentfulWebhookConfig = ` -resource "contentful_space" "myspace" { - name = "space-name" -} - resource "contentful_webhook" "mywebhook" { - space_id = "${contentful_space.myspace.id}" - depends_on = ["contentful_space.myspace"] + space_id = "` + spaceID + `" name = "webhook-name" url= "https://www.example.com/test" @@ -145,7 +145,7 @@ resource "contentful_webhook" "mywebhook" { "Entry.create", "ContentType.create", ] - headers { + headers = { header1 = "header1-value" header2 = "header2-value" } @@ -155,13 +155,9 @@ resource "contentful_webhook" "mywebhook" { ` var testAccContentfulWebhookUpdateConfig = ` -resource "contentful_space" "myspace" { - name = "space-name" -} - resource "contentful_webhook" "mywebhook" { - depends_on = ["contentful_space.myspace"] - space_id = "${contentful_space.myspace.id}" + space_id = "` + spaceID + `" + name = "webhook-name-updated" url= "https://www.example.com/test-updated" diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..73d2994 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,30 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "contentful Provider" +subcategory: "" +description: |- + +--- + +# contentful Provider + + + +## Example Usage + +```terraform +provider "contentful" { + cma_token = "" + organization_id = "" +} +``` + + +## Schema + +### Optional + +- `base_url` (String) The base url to use for the Contentful API. Defaults to https://api.contentful.com +- `cma_token` (String, Sensitive) The Contentful Management API token +- `environment` (String) The environment to use for the Contentful API. Defaults to master +- `organization_id` (String, Sensitive) The organization ID diff --git a/docs/resources/apikey.md b/docs/resources/apikey.md new file mode 100644 index 0000000..26cc8a3 --- /dev/null +++ b/docs/resources/apikey.md @@ -0,0 +1,43 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "contentful_apikey Resource - terraform-provider-contentful" +subcategory: "" +description: |- + Todo for explaining apikey +--- + +# contentful_apikey (Resource) + +Todo for explaining apikey + +## Example Usage + +```terraform +resource "contentful_apikey" "myapikey" { + space_id = "space-id" + + name = "api-key-name" + description = "a-great-key" +} +``` + + +## Schema + +### Required + +- `name` (String) +- `space_id` (String) space id + +### Optional + +- `description` (String) +- `environments` (List of String) List of needed environments if not added then master is used + +### Read-Only + +- `access_token` (String, Sensitive) +- `id` (String) api key id +- `preview_id` (String) preview api key id +- `preview_token` (String, Sensitive) +- `version` (Number) diff --git a/docs/resources/app_definition.md b/docs/resources/app_definition.md new file mode 100644 index 0000000..810a28a --- /dev/null +++ b/docs/resources/app_definition.md @@ -0,0 +1,85 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "contentful_app_definition Resource - terraform-provider-contentful" +subcategory: "" +description: |- + Todo for explaining app definition +--- + +# contentful_app_definition (Resource) + +Todo for explaining app definition + +## Example Usage + +```terraform +resource "contentful_app_definition" "example_app_definition" { + name = "test_app_definition" + use_bundle = false + src = "http://localhost:3000" + locations = [{ location = "app-config" }, { location = "dialog" }, { location = "entry-editor" }] +} +``` + + +## Schema + +### Required + +- `locations` (Attributes List) (see [below for nested schema](#nestedatt--locations)) +- `name` (String) +- `use_bundle` (Boolean) + +### Optional + +- `src` (String) + +### Read-Only + +- `bundle_id` (String) +- `id` (String) app definition id + + +### Nested Schema for `locations` + +Required: + +- `location` (String) + +Optional: + +- `field_types` (Attributes List) (see [below for nested schema](#nestedatt--locations--field_types)) +- `navigation_item` (Attributes) (see [below for nested schema](#nestedatt--locations--navigation_item)) + + +### Nested Schema for `locations.field_types` + +Required: + +- `type` (String) + +Optional: + +- `items` (Attributes) (see [below for nested schema](#nestedatt--locations--field_types--items)) +- `link_type` (String) + + +### Nested Schema for `locations.field_types.items` + +Required: + +- `type` (String) + +Optional: + +- `link_type` (String) + + + + +### Nested Schema for `locations.navigation_item` + +Required: + +- `name` (String) +- `path` (String) diff --git a/docs/resources/app_installation.md b/docs/resources/app_installation.md new file mode 100644 index 0000000..605359e --- /dev/null +++ b/docs/resources/app_installation.md @@ -0,0 +1,47 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "contentful_app_installation Resource - terraform-provider-contentful" +subcategory: "" +description: |- + Todo for explaining app installation +--- + +# contentful_app_installation (Resource) + +Todo for explaining app installation + +## Example Usage + +```terraform +resource "contentful_app_installation" "example_app_installation" { + space_id = "space-id" + environment = "master" + + app_definition_id = contentful_app_definition.example_app_definition.id + + parameters = jsonencode({ + "example" : "one", + "nested" : { + "example" : "two" + } + }) +} +``` + + +## Schema + +### Required + +- `app_definition_id` (String) app definition id +- `environment` (String) +- `parameters` (String) Parameters needed for the installation of the app in the given space, like credentials or other configuration parameters +- `space_id` (String) space id + +### Optional + +- `accepted_terms` (List of String) List of needed terms to accept to install the app + +### Read-Only + +- `id` (String) The ID of this resource. diff --git a/docs/resources/asset.md b/docs/resources/asset.md new file mode 100644 index 0000000..6a04121 --- /dev/null +++ b/docs/resources/asset.md @@ -0,0 +1,120 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "contentful_asset Resource - terraform-provider-contentful" +subcategory: "" +description: |- + +--- + +# contentful_asset (Resource) + + + +## Example Usage + +```terraform +resource "contentful_asset" "example_asset" { + asset_id = "test_asset" + environment = "master" + space_id = "space-id" + + fields { + title { + locale = "en-US" + content = "asset title" + } + description { + locale = "en-US" + content = "asset description" + } + file { + upload = "https://images.ctfassets.net/fo9twyrwpveg/2VQx7vz73aMEYi20MMgCk0/66e502115b1f1f973a944b4bd2cc536f/IC-1H_Modern_Stack_Website.svg" + fileName = "example.jpeg" + contentType = "image/jpeg" + } + } + published = false + archived = false +} +``` + + +## Schema + +### Required + +- `archived` (Boolean) +- `asset_id` (String) +- `environment` (String) +- `fields` (Block List, Min: 1) (see [below for nested schema](#nestedblock--fields)) +- `published` (Boolean) +- `space_id` (String) + +### Read-Only + +- `id` (String) The ID of this resource. +- `version` (Number) + + +### Nested Schema for `fields` + +Required: + +- `description` (Block List, Min: 1) (see [below for nested schema](#nestedblock--fields--description)) +- `file` (Block List, Min: 1, Max: 1) (see [below for nested schema](#nestedblock--fields--file)) +- `title` (Block List, Min: 1) (see [below for nested schema](#nestedblock--fields--title)) + + +### Nested Schema for `fields.description` + +Required: + +- `content` (String) +- `locale` (String) + + + +### Nested Schema for `fields.file` + +Required: + +- `content_type` (String) +- `file_name` (String) +- `locale` (String) +- `upload` (String) + +Optional: + +- `details` (Block Set) (see [below for nested schema](#nestedblock--fields--file--details)) + +Read-Only: + +- `upload_from` (String) +- `url` (String) + + +### Nested Schema for `fields.file.details` + +Required: + +- `image` (Block Set, Min: 1) (see [below for nested schema](#nestedblock--fields--file--details--image)) +- `size` (Number) + + +### Nested Schema for `fields.file.details.image` + +Required: + +- `height` (Number) +- `width` (Number) + + + + + +### Nested Schema for `fields.title` + +Required: + +- `content` (String) +- `locale` (String) diff --git a/docs/resources/contenttype.md b/docs/resources/contenttype.md new file mode 100644 index 0000000..b35adc0 --- /dev/null +++ b/docs/resources/contenttype.md @@ -0,0 +1,280 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "contentful_contenttype Resource - terraform-provider-contentful" +subcategory: "" +description: |- + Todo for explaining contenttype +--- + +# contentful_contenttype (Resource) + +Todo for explaining contenttype + +## Example Usage + +```terraform +resource "contentful_contenttype" "example_contenttype" { + space_id = "space-id" + environment = "master" + id = "tf_linked" + name = "tf_linked" + description = "content type description" + display_field = "asset_field" + manage_field_controls = true + fields = [{ + id = "asset_field" + name = "Asset Field" + type = "Array" + items = { + type = "Link" + link_type = "Asset" + } + required = true + }, { + id = "entry_link_field" + name = "Entry Link Field" + type = "Link" + link_type = "Entry" + validations = [{ + link_content_type = [ + contentful_contenttype.some_other_content_type.id + ] + } + ] + required = false + }, { + id = "select", + name = "Select Field", + type = "Symbol", + required = true, + validations = [ + { + in = [ + "choice 1", + "choice 2", + "choice 3", + "choice 4" + ] + } + ] + control = { + widget_id = "radio" + widget_namespace = "builtin" + } + }] +} +``` + + +## Schema + +### Required + +- `display_field` (String) +- `environment` (String) +- `fields` (Attributes List) (see [below for nested schema](#nestedatt--fields)) +- `name` (String) +- `space_id` (String) space id + +### Optional + +- `description` (String) +- `id` (String) content type id +- `manage_field_controls` (Boolean) +- `sidebar` (Attributes List) (see [below for nested schema](#nestedatt--sidebar)) + +### Read-Only + +- `version` (Number) +- `version_controls` (Number) + + +### Nested Schema for `fields` + +Required: + +- `id` (String) +- `name` (String) +- `type` (String) + +Optional: + +- `control` (Attributes) (see [below for nested schema](#nestedatt--fields--control)) +- `default_value` (Attributes) (see [below for nested schema](#nestedatt--fields--default_value)) +- `disabled` (Boolean) +- `items` (Attributes) (see [below for nested schema](#nestedatt--fields--items)) +- `link_type` (String) +- `localized` (Boolean) +- `omitted` (Boolean) +- `required` (Boolean) +- `validations` (Attributes List) (see [below for nested schema](#nestedatt--fields--validations)) + + +### Nested Schema for `fields.control` + +Required: + +- `widget_id` (String) +- `widget_namespace` (String) + +Optional: + +- `settings` (Attributes) (see [below for nested schema](#nestedatt--fields--control--settings)) + + +### Nested Schema for `fields.control.settings` + +Optional: + +- `ampm` (String) +- `bulk_editing` (Boolean) +- `false_label` (String) +- `format` (String) +- `help_text` (String) +- `stars` (Number) +- `tracking_field_id` (String) +- `true_label` (String) + + + + +### Nested Schema for `fields.default_value` + +Optional: + +- `bool` (Map of Boolean) +- `string` (Map of String) + + + +### Nested Schema for `fields.items` + +Required: + +- `type` (String) + +Optional: + +- `link_type` (String) +- `validations` (Attributes List) (see [below for nested schema](#nestedatt--fields--items--validations)) + + +### Nested Schema for `fields.items.validations` + +Optional: + +- `asset_file_size` (Attributes) (see [below for nested schema](#nestedatt--fields--items--validations--asset_file_size)) +- `enabled_marks` (List of String) +- `enabled_node_types` (List of String) +- `in` (List of String) +- `link_content_type` (List of String) +- `link_mimetype_group` (List of String) +- `message` (String) +- `range` (Attributes) (see [below for nested schema](#nestedatt--fields--items--validations--range)) +- `regexp` (Attributes) (see [below for nested schema](#nestedatt--fields--items--validations--regexp)) +- `size` (Attributes) (see [below for nested schema](#nestedatt--fields--items--validations--size)) +- `unique` (Boolean) + + +### Nested Schema for `fields.items.validations.asset_file_size` + +Optional: + +- `max` (Number) +- `min` (Number) + + + +### Nested Schema for `fields.items.validations.range` + +Optional: + +- `max` (Number) +- `min` (Number) + + + +### Nested Schema for `fields.items.validations.regexp` + +Optional: + +- `pattern` (String) + + + +### Nested Schema for `fields.items.validations.size` + +Optional: + +- `max` (Number) +- `min` (Number) + + + + + +### Nested Schema for `fields.validations` + +Optional: + +- `asset_file_size` (Attributes) (see [below for nested schema](#nestedatt--fields--validations--asset_file_size)) +- `enabled_marks` (List of String) +- `enabled_node_types` (List of String) +- `in` (List of String) +- `link_content_type` (List of String) +- `link_mimetype_group` (List of String) +- `message` (String) +- `range` (Attributes) (see [below for nested schema](#nestedatt--fields--validations--range)) +- `regexp` (Attributes) (see [below for nested schema](#nestedatt--fields--validations--regexp)) +- `size` (Attributes) (see [below for nested schema](#nestedatt--fields--validations--size)) +- `unique` (Boolean) + + +### Nested Schema for `fields.validations.asset_file_size` + +Optional: + +- `max` (Number) +- `min` (Number) + + + +### Nested Schema for `fields.validations.range` + +Optional: + +- `max` (Number) +- `min` (Number) + + + +### Nested Schema for `fields.validations.regexp` + +Optional: + +- `pattern` (String) + + + +### Nested Schema for `fields.validations.size` + +Optional: + +- `max` (Number) +- `min` (Number) + + + + + +### Nested Schema for `sidebar` + +Required: + +- `widget_id` (String) +- `widget_namespace` (String) + +Optional: + +- `disabled` (Boolean) +- `settings` (String) diff --git a/docs/resources/entry.md b/docs/resources/entry.md new file mode 100644 index 0000000..90ea009 --- /dev/null +++ b/docs/resources/entry.md @@ -0,0 +1,87 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "contentful_entry Resource - terraform-provider-contentful" +subcategory: "" +description: |- + A Contentful Entry represents a piece of content in a space. +--- + +# contentful_entry (Resource) + +A Contentful Entry represents a piece of content in a space. + +## Example Usage + +```terraform +resource "contentful_entry" "example_entry" { + entry_id = "mytestentry" + space_id = "space-id" + contenttype_id = "type-id" + environment = "master" + field { + id = "field1" + content = "Hello, World!" + locale = "en-US" + } + field { + id = "field2" + 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] +} +``` + + +## Schema + +### Required + +- `archived` (Boolean) +- `contenttype_id` (String) +- `entry_id` (String) +- `environment` (String) +- `field` (Block List, Min: 1) (see [below for nested schema](#nestedblock--field)) +- `published` (Boolean) +- `space_id` (String) + +### Read-Only + +- `id` (String) The ID of this resource. +- `version` (Number) + + +### Nested Schema for `field` + +Required: + +- `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: + +- `id` (String) The ID of this resource. diff --git a/docs/resources/environment.md b/docs/resources/environment.md new file mode 100644 index 0000000..b863063 --- /dev/null +++ b/docs/resources/environment.md @@ -0,0 +1,33 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "contentful_environment Resource - terraform-provider-contentful" +subcategory: "" +description: |- + A Contentful Environment represents a space environment. +--- + +# contentful_environment (Resource) + +A Contentful Environment represents a space environment. + +## Example Usage + +```terraform +resource "contentful_environment" "example_environment" { + space_id = "spaced-id" + name = "environment-name" +} +``` + + +## Schema + +### Required + +- `name` (String) +- `space_id` (String) + +### Read-Only + +- `id` (String) The ID of this resource. +- `version` (Number) diff --git a/docs/resources/locale.md b/docs/resources/locale.md new file mode 100644 index 0000000..5a1dbb4 --- /dev/null +++ b/docs/resources/locale.md @@ -0,0 +1,49 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "contentful_locale Resource - terraform-provider-contentful" +subcategory: "" +description: |- + A Contentful Locale represents a language and region combination. +--- + +# contentful_locale (Resource) + +A Contentful Locale represents a language and region combination. + +## Example Usage + +```terraform +resource "contentful_locale" "example_locale" { + space_id = "spaced-id" + environment = "master" + + name = "locale-name" + code = "de" + fallback_code = "en-US" + optional = false + cda = false + cma = true +} +``` + + +## Schema + +### Required + +- `code` (String) +- `environment` (String) +- `name` (String) +- `space_id` (String) + +### Optional + +- `cda` (Boolean) +- `cma` (Boolean) +- `fallback_code` (String) +- `optional` (Boolean) + +### Read-Only + +- `id` (String) The ID of this resource. +- `version` (Number) diff --git a/docs/resources/space.md b/docs/resources/space.md new file mode 100644 index 0000000..95f4139 --- /dev/null +++ b/docs/resources/space.md @@ -0,0 +1,35 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "contentful_space Resource - terraform-provider-contentful" +subcategory: "" +description: |- + A Contentful Space represents a space in Contentful. +--- + +# contentful_space (Resource) + +A Contentful Space represents a space in Contentful. + +## Example Usage + +```terraform +resource "contentful_space" "example_space" { + name = "example_space_name" +} +``` + + +## Schema + +### Required + +- `name` (String) + +### Optional + +- `default_locale` (String) + +### Read-Only + +- `id` (String) The ID of this resource. +- `version` (Number) diff --git a/docs/resources/webhook.md b/docs/resources/webhook.md new file mode 100644 index 0000000..5036223 --- /dev/null +++ b/docs/resources/webhook.md @@ -0,0 +1,53 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "contentful_webhook Resource - terraform-provider-contentful" +subcategory: "" +description: |- + A Contentful Webhook represents a webhook that can be used to notify external services of changes in a space. +--- + +# contentful_webhook (Resource) + +A Contentful Webhook represents a webhook that can be used to notify external services of changes in a space. + +## Example Usage + +```terraform +resource "contentful_webhook" "example_webhook" { + space_id = "space-id" + + name = "webhook-name" + url = "https://www.example.com/test" + topics = [ + "Entry.create", + "ContentType.create", + ] + headers = { + header1 = "header1-value" + header2 = "header2-value" + } + http_basic_auth_username = "username" + http_basic_auth_password = "password" +} +``` + + +## Schema + +### Required + +- `name` (String) +- `space_id` (String) +- `topics` (List of String) +- `url` (String) + +### Optional + +- `headers` (Map of String) +- `http_basic_auth_password` (String) +- `http_basic_auth_username` (String) + +### Read-Only + +- `id` (String) The ID of this resource. +- `version` (Number) diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf new file mode 100644 index 0000000..87a6940 --- /dev/null +++ b/examples/provider/provider.tf @@ -0,0 +1,4 @@ +provider "contentful" { + cma_token = "" + organization_id = "" +} diff --git a/examples/resources/contentful_apikey/resource.tf b/examples/resources/contentful_apikey/resource.tf new file mode 100644 index 0000000..7b00576 --- /dev/null +++ b/examples/resources/contentful_apikey/resource.tf @@ -0,0 +1,6 @@ +resource "contentful_apikey" "myapikey" { + space_id = "space-id" + + name = "api-key-name" + description = "a-great-key" +} \ No newline at end of file diff --git a/examples/resources/contentful_app_definition/resource.tf b/examples/resources/contentful_app_definition/resource.tf new file mode 100644 index 0000000..2339e01 --- /dev/null +++ b/examples/resources/contentful_app_definition/resource.tf @@ -0,0 +1,6 @@ +resource "contentful_app_definition" "example_app_definition" { + name = "test_app_definition" + use_bundle = false + src = "http://localhost:3000" + locations = [{ location = "app-config" }, { location = "dialog" }, { location = "entry-editor" }] +} \ No newline at end of file diff --git a/examples/resources/contentful_app_installation/resource.tf b/examples/resources/contentful_app_installation/resource.tf new file mode 100644 index 0000000..3a33913 --- /dev/null +++ b/examples/resources/contentful_app_installation/resource.tf @@ -0,0 +1,13 @@ +resource "contentful_app_installation" "example_app_installation" { + space_id = "space-id" + environment = "master" + + app_definition_id = contentful_app_definition.example_app_definition.id + + parameters = jsonencode({ + "example" : "one", + "nested" : { + "example" : "two" + } + }) +} \ No newline at end of file diff --git a/examples/resources/contentful_asset/resource.tf b/examples/resources/contentful_asset/resource.tf new file mode 100644 index 0000000..c750e8d --- /dev/null +++ b/examples/resources/contentful_asset/resource.tf @@ -0,0 +1,23 @@ +resource "contentful_asset" "example_asset" { + asset_id = "test_asset" + environment = "master" + space_id = "space-id" + + fields { + title { + locale = "en-US" + content = "asset title" + } + description { + locale = "en-US" + content = "asset description" + } + file { + upload = "https://images.ctfassets.net/fo9twyrwpveg/2VQx7vz73aMEYi20MMgCk0/66e502115b1f1f973a944b4bd2cc536f/IC-1H_Modern_Stack_Website.svg" + fileName = "example.jpeg" + contentType = "image/jpeg" + } + } + published = false + archived = false +} diff --git a/examples/resources/contentful_contenttype/resource.tf b/examples/resources/contentful_contenttype/resource.tf new file mode 100644 index 0000000..08cedf2 --- /dev/null +++ b/examples/resources/contentful_contenttype/resource.tf @@ -0,0 +1,50 @@ +resource "contentful_contenttype" "example_contenttype" { + space_id = "space-id" + environment = "master" + id = "tf_linked" + name = "tf_linked" + description = "content type description" + display_field = "asset_field" + manage_field_controls = true + fields = [{ + id = "asset_field" + name = "Asset Field" + type = "Array" + items = { + type = "Link" + link_type = "Asset" + } + required = true + }, { + id = "entry_link_field" + name = "Entry Link Field" + type = "Link" + link_type = "Entry" + validations = [{ + link_content_type = [ + contentful_contenttype.some_other_content_type.id + ] + } + ] + required = false + }, { + id = "select", + name = "Select Field", + type = "Symbol", + required = true, + validations = [ + { + in = [ + "choice 1", + "choice 2", + "choice 3", + "choice 4" + ] + } + ] + control = { + widget_id = "radio" + widget_namespace = "builtin" + } + }] +} \ No newline at end of file diff --git a/examples/resources/contentful_entry/resource.tf b/examples/resources/contentful_entry/resource.tf new file mode 100644 index 0000000..b9159f3 --- /dev/null +++ b/examples/resources/contentful_entry/resource.tf @@ -0,0 +1,41 @@ +resource "contentful_entry" "example_entry" { + entry_id = "mytestentry" + space_id = "space-id" + contenttype_id = "type-id" + environment = "master" + field { + id = "field1" + content = "Hello, World!" + locale = "en-US" + } + field { + id = "field2" + 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] +} diff --git a/examples/resources/contentful_environment/resource.tf b/examples/resources/contentful_environment/resource.tf new file mode 100644 index 0000000..336b3b6 --- /dev/null +++ b/examples/resources/contentful_environment/resource.tf @@ -0,0 +1,4 @@ +resource "contentful_environment" "example_environment" { + space_id = "spaced-id" + name = "environment-name" +} \ No newline at end of file diff --git a/examples/resources/contentful_locale/resource.tf b/examples/resources/contentful_locale/resource.tf new file mode 100644 index 0000000..ea6a0c0 --- /dev/null +++ b/examples/resources/contentful_locale/resource.tf @@ -0,0 +1,11 @@ +resource "contentful_locale" "example_locale" { + space_id = "spaced-id" + environment = "master" + + name = "locale-name" + code = "de" + fallback_code = "en-US" + optional = false + cda = false + cma = true +} \ No newline at end of file diff --git a/examples/resources/contentful_space/resource.tf b/examples/resources/contentful_space/resource.tf new file mode 100644 index 0000000..7c3568e --- /dev/null +++ b/examples/resources/contentful_space/resource.tf @@ -0,0 +1,3 @@ +resource "contentful_space" "example_space" { + name = "example_space_name" +} \ No newline at end of file diff --git a/examples/resources/contentful_webhook/resource.tf b/examples/resources/contentful_webhook/resource.tf new file mode 100644 index 0000000..f95d46e --- /dev/null +++ b/examples/resources/contentful_webhook/resource.tf @@ -0,0 +1,16 @@ +resource "contentful_webhook" "example_webhook" { + space_id = "space-id" + + name = "webhook-name" + url = "https://www.example.com/test" + topics = [ + "Entry.create", + "ContentType.create", + ] + headers = { + header1 = "header1-value" + header2 = "header2-value" + } + http_basic_auth_username = "username" + http_basic_auth_password = "password" +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1338e12 --- /dev/null +++ b/go.mod @@ -0,0 +1,116 @@ +module github.com/labd/terraform-provider-contentful + +go 1.23.0 + +toolchain go1.23.7 + +require ( + github.com/deepmap/oapi-codegen v1.16.3 + github.com/elliotchance/pie/v2 v2.9.1 + github.com/hashicorp/terraform-plugin-docs v0.19.0 + github.com/hashicorp/terraform-plugin-framework v1.14.1 + github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.17.0 + github.com/hashicorp/terraform-plugin-log v0.9.0 + github.com/hashicorp/terraform-plugin-mux v0.18.0 + github.com/hashicorp/terraform-plugin-testing v1.12.0 + github.com/labd/contentful-go v0.5.4-0.20250324140149-2fdab88b69c2 + github.com/oapi-codegen/nullable v1.1.0 + github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 + github.com/oapi-codegen/runtime v1.1.1 + github.com/stretchr/testify v1.9.0 +) + +require ( + github.com/BurntSushi/toml v1.3.2 // indirect + github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.1 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect + github.com/ProtonMail/go-crypto v1.1.3 // indirect + github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/armon/go-radix v1.0.0 // indirect + 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/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 // indirect + github.com/getkin/kin-openapi v0.127.0 // indirect + github.com/go-openapi/jsonpointer v0.21.0 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/hashicorp/cli v1.1.6 // indirect + github.com/hashicorp/go-retryablehttp v0.7.7 // indirect + github.com/hashicorp/hc-install v0.9.1 // indirect + github.com/hashicorp/terraform-registry-address v0.2.4 // indirect + github.com/hashicorp/terraform-svchost v0.1.1 // indirect + github.com/huandu/xstrings v1.4.0 // indirect + github.com/imdario/mergo v0.3.16 // indirect + github.com/invopop/yaml v0.3.1 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/perimeterx/marshmallow v1.1.5 // 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 + github.com/speakeasy-api/openapi-overlay v0.9.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect + github.com/yuin/goldmark v1.7.1 // indirect + github.com/yuin/goldmark-meta v1.1.0 // indirect + go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect + golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f // indirect + golang.org/x/sync v0.12.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +require ( + github.com/agext/levenshtein v1.2.3 // indirect + github.com/fatih/color v1.16.0 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-checkpoint v0.5.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-cty v1.5.0 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-plugin v1.6.2 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/hcl/v2 v2.23.0 // indirect + github.com/hashicorp/logutils v1.0.0 // indirect + github.com/hashicorp/terraform-exec v0.22.0 // indirect + github.com/hashicorp/terraform-json v0.24.0 // indirect + github.com/hashicorp/terraform-plugin-go v0.26.0 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 + github.com/hashicorp/yamux v0.1.1 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/oklog/run v1.1.0 // indirect + github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect + github.com/zclconf/go-cty v1.16.2 // indirect + golang.org/x/crypto v0.36.0 // indirect + golang.org/x/mod v0.22.0 // indirect + golang.org/x/net v0.37.0 // indirect + golang.org/x/sys v0.31.0 // indirect + golang.org/x/text v0.23.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/grpc v1.69.4 // indirect + google.golang.org/protobuf v1.36.3 // indirect + moul.io/http2curl v1.0.1-0.20190925090545-5cd742060b0e // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..7c17a59 --- /dev/null +++ b/go.sum @@ -0,0 +1,432 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0= +github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/semver/v3 v3.2.1 h1:RN9w6+7QoMeJVGyfmbcgs28Br8cvmnucEXnY0rYXWg0= +github.com/Masterminds/semver/v3 v3.2.1/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk= +github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= +github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/apapsch/go-jsonmerge/v2 v2.0.0 h1:axGnT1gRIfimI7gJifB699GoE/oq+F2MU7Dml6nw9rQ= +github.com/apapsch/go-jsonmerge/v2 v2.0.0/go.mod h1:lvDnEdqiQrp0O42VQGgmlKpxL1AP2+08jFMw88y4klk= +github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= +github.com/bmatcuk/doublestar/v4 v4.6.1 h1:FH9SifrbvJhnlQpztAx++wlkk70QBf0iBWDwNy7PA4I= +github.com/bmatcuk/doublestar/v4 v4.6.1/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= +github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= +github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= +github.com/cyphar/filepath-securejoin v0.2.5 h1:6iR5tXJ/e6tJZzzdMc1km3Sa7RRIVBKAK32O2s7AYfo= +github.com/cyphar/filepath-securejoin v0.2.5/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/deepmap/oapi-codegen v1.16.3 h1:GT9G86SbQtT1r8ZB+4Cybi9VGdu1P5ieNvNdEoCSbrA= +github.com/deepmap/oapi-codegen v1.16.3/go.mod h1:JD6ErqeX0nYnhdciLc61Konj3NBASREMlkHOgHn8WAM= +github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936 h1:PRxIJD8XjimM5aTknUK9w6DHLDox2r2M3DI4i2pnd3w= +github.com/dprotaso/go-yit v0.0.0-20220510233725-9ba8df137936/go.mod h1:ttYvX5qlB+mlV1okblJqcSMtR4c52UKxDiX9GRBS8+Q= +github.com/elliotchance/pie/v2 v2.9.1 h1:v7TdC6ZdNZJ1HACofpLXvGKHUk307AjY/bttwDPWKEQ= +github.com/elliotchance/pie/v2 v2.9.1/go.mod h1:18t0dgGFH006g4eVdDtWfgFZPQEgl10IoEO8YWEq3Og= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/getkin/kin-openapi v0.127.0 h1:Mghqi3Dhryf3F8vR370nN67pAERW+3a95vomb3MAREY= +github.com/getkin/kin-openapi v0.127.0/go.mod h1:OZrfXzUfGrNbsKj+xmFBx6E5c6yH3At/tAKSc2UszXM= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8= +github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM= +github.com/go-git/go-git/v5 v5.13.0 h1:vLn5wlGIh/X78El6r3Jr+30W16Blk0CTcxTYcYPWi5E= +github.com/go-git/go-git/v5 v5.13.0/go.mod h1:Wjo7/JyVKtQgUNdXYXIepzWfJQkUEIGvkvVkiXRR/zw= +github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= +github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= +github.com/go-openapi/jsonpointer v0.21.0/go.mod h1:IUyH9l/+uyhIYQ/PXVA41Rexl+kOkAPDdXEYns6fzUY= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= +github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/cli v1.1.6 h1:CMOV+/LJfL1tXCOKrgAX0uRKnzjj/mpmqNXloRSy2K8= +github.com/hashicorp/cli v1.1.6/go.mod h1:MPon5QYlgjjo0BSoAiN0ESeT5fRzDjVRp+uioJ0piz4= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= +github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-cty v1.5.0 h1:EkQ/v+dDNUqnuVpmS5fPqyY71NXVgT5gf32+57xY8g0= +github.com/hashicorp/go-cty v1.5.0/go.mod h1:lFUCG5kd8exDobgSfyj4ONE/dc822kiYMguVKdHGMLM= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.6.2 h1:zdGAEd0V1lCaU0u+MxWQhtSDQmahpkwOun8U8EiRVog= +github.com/hashicorp/go-plugin v1.6.2/go.mod h1:CkgLQ5CZqNmdL9U9JzM532t8ZiYQ35+pj3b1FD37R0Q= +github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU= +github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= +github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hc-install v0.9.1 h1:gkqTfE3vVbafGQo6VZXcy2v5yoz2bE0+nhZXruCuODQ= +github.com/hashicorp/hc-install v0.9.1/go.mod h1:pWWvN/IrfeBK4XPeXXYkL6EjMufHkCK5DvwxeLKuBf0= +github.com/hashicorp/hcl/v2 v2.23.0 h1:Fphj1/gCylPxHutVSEOf2fBOh1VE4AuLV7+kbJf3qos= +github.com/hashicorp/hcl/v2 v2.23.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/terraform-exec v0.22.0 h1:G5+4Sz6jYZfRYUCg6eQgDsqTzkNXV+fP8l+uRmZHj64= +github.com/hashicorp/terraform-exec v0.22.0/go.mod h1:bjVbsncaeh8jVdhttWYZuBGj21FcYw6Ia/XfHcNO7lQ= +github.com/hashicorp/terraform-json v0.24.0 h1:rUiyF+x1kYawXeRth6fKFm/MdfBS6+lW4NbeATsYz8Q= +github.com/hashicorp/terraform-json v0.24.0/go.mod h1:Nfj5ubo9xbu9uiAoZVBsNOjvNKB66Oyrvtit74kC7ow= +github.com/hashicorp/terraform-plugin-docs v0.19.0 h1:ufXLte5Kx20LazYmGN2UZG2bN4aF0PmlDyuS1iKWSXo= +github.com/hashicorp/terraform-plugin-docs v0.19.0/go.mod h1:NPfKCSfzTtq+YCFHr2qTAMknWUxR8C4KgTbGkHULSV8= +github.com/hashicorp/terraform-plugin-framework v1.14.1 h1:jaT1yvU/kEKEsxnbrn4ZHlgcxyIfjvZ41BLdlLk52fY= +github.com/hashicorp/terraform-plugin-framework v1.14.1/go.mod h1:xNUKmvTs6ldbwTuId5euAtg37dTxuyj3LHS3uj7BHQ4= +github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0 h1:SJXL5FfJJm17554Kpt9jFXngdM6fXbnUnZ6iT2IeiYA= +github.com/hashicorp/terraform-plugin-framework-jsontypes v0.2.0/go.mod h1:p0phD0IYhsu9bR4+6OetVvvH59I6LwjXGnTVEr8ox6E= +github.com/hashicorp/terraform-plugin-framework-validators v0.17.0 h1:0uYQcqqgW3BMyyve07WJgpKorXST3zkpzvrOnf3mpbg= +github.com/hashicorp/terraform-plugin-framework-validators v0.17.0/go.mod h1:VwdfgE/5Zxm43flraNa0VjcvKQOGVrcO4X8peIri0T0= +github.com/hashicorp/terraform-plugin-go v0.26.0 h1:cuIzCv4qwigug3OS7iKhpGAbZTiypAfFQmw8aE65O2M= +github.com/hashicorp/terraform-plugin-go v0.26.0/go.mod h1:+CXjuLDiFgqR+GcrM5a2E2Kal5t5q2jb0E3D57tTdNY= +github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= +github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= +github.com/hashicorp/terraform-plugin-mux v0.18.0 h1:7491JFSpWyAe0v9YqBT+kel7mzHAbO5EpxxT0cUL/Ms= +github.com/hashicorp/terraform-plugin-mux v0.18.0/go.mod h1:Ho1g4Rr8qv0qTJlcRKfjjXTIO67LNbDtM6r+zHUNHJQ= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1 h1:WNMsTLkZf/3ydlgsuXePa3jvZFwAJhruxTxP/c1Viuw= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.36.1/go.mod h1:P6o64QS97plG44iFzSM6rAn6VJIC/Sy9a9IkEtl79K4= +github.com/hashicorp/terraform-plugin-testing v1.12.0 h1:tpIe+T5KBkA1EO6aT704SPLedHUo55RenguLHcaSBdI= +github.com/hashicorp/terraform-plugin-testing v1.12.0/go.mod h1:jbDQUkT9XRjAh1Bvyufq+PEH1Xs4RqIdpOQumSgSXBM= +github.com/hashicorp/terraform-registry-address v0.2.4 h1:JXu/zHB2Ymg/TGVCRu10XqNa4Sh2bWcqCNyKWjnCPJA= +github.com/hashicorp/terraform-registry-address v0.2.4/go.mod h1:tUNYTVyCtU4OIGXXMDp7WNcJ+0W1B4nmstVDgHMjfAU= +github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= +github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc= +github.com/hashicorp/yamux v0.1.1 h1:yrQxtgseBDrq9Y652vSRDvsKCJKOUD+GzTS4Y0Y8pvE= +github.com/hashicorp/yamux v0.1.1/go.mod h1:CtWFDAQgb7dxtzFs4tWbplKIe2jSi3+5vKbgIO0SLnQ= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huandu/xstrings v1.3.3/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.4.0 h1:D17IlohoQq4UcpqD7fDk80P7l+lwAmlFaBHgOipl2FU= +github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= +github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/invopop/yaml v0.3.1 h1:f0+ZpmhfBSS4MhG+4HYseMdJhoeeopbSKbq5Rpeelso= +github.com/invopop/yaml v0.3.1/go.mod h1:PMOp3nn4/12yEZUFfmOuNHJsZToEEOwoWsT+D81KkeA= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labd/contentful-go v0.5.4-0.20250324140149-2fdab88b69c2 h1:Tz4faVF5rrjzHtOo7bR2fhEpooCXCbwFE42zyn6IFpQ= +github.com/labd/contentful-go v0.5.4-0.20250324140149-2fdab88b69c2/go.mod h1:SrC2m8PCbpFiMkVJjODPI6e/8Nne6/Q27sxsXOiXs5A= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= +github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/oapi-codegen/nullable v1.1.0 h1:eAh8JVc5430VtYVnq00Hrbpag9PFRGWLjxR1/3KntMs= +github.com/oapi-codegen/nullable v1.1.0/go.mod h1:KUZ3vUzkmEKY90ksAmit2+5juDIhIZhfDl+0PwOQlFY= +github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 h1:ykgG34472DWey7TSjd8vIfNykXgjOgYJZoQbKfEeY/Q= +github.com/oapi-codegen/oapi-codegen/v2 v2.4.1/go.mod h1:N5+lY1tiTDV3V1BeHtOxeWXHoPVeApvsvjJqegfoaz8= +github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= +github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= +github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA= +github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.10.2/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.4 h1:29JGrr5oVBm5ulCWet69zQkzWipVXIol6ygQUe/EzNc= +github.com/onsi/ginkgo v1.16.4/go.mod h1:dX+/inL/fNMqNlz0e9LfyB9TswhZpCVdJM/Z6Vvnwo0= +github.com/onsi/ginkgo/v2 v2.1.3/go.mod h1:vw5CSIxN1JObi/U8gcbwft7ZxR2dgaR70JSE3/PpL4c= +github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY= +github.com/onsi/gomega v1.19.0/go.mod h1:LY+I3pBVzYsTBU1AnDwOSxaYi9WoWiqgwooUqq9yPro= +github.com/onsi/gomega v1.27.6 h1:ENqfyGeS5AX/rlXDd/ETokDz93u0YufY1Pgxuy/PvWE= +github.com/onsi/gomega v1.27.6/go.mod h1:PIQNjfQwkP3aQAH7lf7j87O/5FiNr+ZR8+ipb+qQlhg= +github.com/perimeterx/marshmallow v1.1.5 h1:a2LALqQ1BlHM8PZblsDdidgv1mWi1DgC2UmX50IvK2s= +github.com/perimeterx/marshmallow v1.1.5/go.mod h1:dsXbUu8CRzfYP5a87xpp0xq9S3u0Vchtcl8we9tYaXw= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= +github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY= +github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M= +github.com/speakeasy-api/openapi-overlay v0.9.0 h1:Wrz6NO02cNlLzx1fB093lBlYxSI54VRhy1aSutx0PQg= +github.com/speakeasy-api/openapi-overlay v0.9.0/go.mod h1:f5FloQrHA7MsxYg9djzMD5h6dxrHjVVByWKh7an8TRc= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= +github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= +github.com/vmware-labs/yaml-jsonpath v0.3.2/go.mod h1:U6whw1z03QyqgWdgXxvVnQ90zN1BWz5V+51Ewf8k+rQ= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.1 h1:3bajkSilaCbjdKVsKdZjZCLBNPL9pYzrCakKaf4U49U= +github.com/yuin/goldmark v1.7.1/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= +github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= +github.com/zclconf/go-cty v1.16.2 h1:LAJSwc3v81IRBZyUVQDUdZ7hs3SYs9jv0eZJDWHD/70= +github.com/zclconf/go-cty v1.16.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= +go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= +go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= +go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY= +go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE= +go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE= +go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY= +go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk= +go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0= +go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc= +go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8= +go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys= +go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= +golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f h1:99ci1mjWVBWwJiEKYY6jWa4d2nTQVIEhZIptnrVb1XY= +golang.org/x/exp v0.0.0-20240416160154-fe59bbe5cc7f/go.mod h1:/lliqkxwWAhPjf5oSOIJup2XcqJaw8RGS6k3TGEc7GI= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.22.0 h1:D4nJWe9zXqHOmWqj4VMOJhvzj7bEZg4wEYa759z1pH4= +golang.org/x/mod v0.22.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= +golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= +golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= +golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= +golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= +golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53 h1:X58yt85/IXCx0Y3ZwN6sEIKZzQtDEYaBWrDvErdXrRE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241015192408-796eee8c2d53/go.mod h1:GX3210XPVPUjJbTUbvwI8f2IpZDMZuPJWDzDuebbviI= +google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A= +google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU= +google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20191026110619-0b21df46bc1d/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +moul.io/http2curl v1.0.1-0.20190925090545-5cd742060b0e h1:C7q+e9M5nggAvWfVg9Nl66kebKeuJlP3FD58V4RR5wo= +moul.io/http2curl v1.0.1-0.20190925090545-5cd742060b0e/go.mod h1:nejbQVfXh96n9dSF6cH3Jsk/QI1Z2oEL7sSI2ifXFNA= diff --git a/install-dependencies.sh b/install-dependencies.sh deleted file mode 100755 index 656d26a..0000000 --- a/install-dependencies.sh +++ /dev/null @@ -1,7 +0,0 @@ -#!/usr/bin/env bash -# Let's avoid rebuilding the dependencies each time a source file changes. -# See: http://stackoverflow.com/questions/39278756/cache-go-get-in-docker-build -go get github.com/hashicorp/terraform/terraform -go get github.com/hashicorp/terraform/helper/resource -go get github.com/hashicorp/terraform/helper/schema -go get github.com/tolgaakyuz/contentful-go diff --git a/internal/acctest/acctest.go b/internal/acctest/acctest.go new file mode 100644 index 0000000..fdbfab6 --- /dev/null +++ b/internal/acctest/acctest.go @@ -0,0 +1,19 @@ +package acctest + +import ( + "os" + "testing" +) + +func TestAccPreCheck(t *testing.T) { + requiredEnvs := []string{ + "CONTENTFUL_MANAGEMENT_TOKEN", + "CONTENTFUL_ORGANIZATION_ID", + "CONTENTFUL_SPACE_ID", + } + for _, val := range requiredEnvs { + if os.Getenv(val) == "" { + t.Fatalf("%v must be set for acceptance tests", val) + } + } +} diff --git a/internal/acctest/client.go b/internal/acctest/client.go new file mode 100644 index 0000000..4d0b0ac --- /dev/null +++ b/internal/acctest/client.go @@ -0,0 +1,33 @@ +package acctest + +import ( + "os" + + "github.com/labd/contentful-go" + client2 "github.com/labd/contentful-go/pkgs/client" + "github.com/labd/contentful-go/pkgs/util" + "github.com/labd/contentful-go/service/cma" +) + +func GetClient() *contentful.Client { + cmaToken := os.Getenv("CONTENTFUL_MANAGEMENT_TOKEN") + organizationId := os.Getenv("CONTENTFUL_ORGANIZATION_ID") + cma := contentful.NewCMA(cmaToken) + cma.SetOrganization(organizationId) + + return cma +} + +func GetCMA() cma.SpaceIdClientBuilder { + client, err := contentful.NewCMAV2(client2.ClientConfig{ + Debug: false, + UserAgent: util.ToPointer("terraform-provider-contentful-test"), + Token: os.Getenv("CONTENTFUL_MANAGEMENT_TOKEN"), + }) + + if err != nil { + panic(err) + } + + return client +} diff --git a/internal/custommodifier/booldefault.go b/internal/custommodifier/booldefault.go new file mode 100644 index 0000000..25b0c23 --- /dev/null +++ b/internal/custommodifier/booldefault.go @@ -0,0 +1,40 @@ +package custommodifier + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// From https://developer.hashicorp.com/terraform/plugin/framework/resources/plan-modification#creating-attribute-plan-modifiers + +var _ planmodifier.Bool = boolDefaultModifier{} +var _ planmodifier.Describer = boolDefaultModifier{} + +type boolDefaultModifier struct { + Default bool +} + +// Description returns a human-readable description of the plan modifier. +func (m boolDefaultModifier) Description(_ context.Context) string { + return fmt.Sprintf("If value is not configured, defaults to %t", m.Default) +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (m boolDefaultModifier) MarkdownDescription(ctx context.Context) string { + return m.Description(ctx) +} + +func (m boolDefaultModifier) PlanModifyBool(_ context.Context, _ planmodifier.BoolRequest, resp *planmodifier.BoolResponse) { + // If the value is unknown or known, do not set default value. + if resp.PlanValue.IsNull() || resp.PlanValue.IsUnknown() { + resp.PlanValue = types.BoolValue(m.Default) + } +} + +func BoolDefault(defaultValue bool) planmodifier.Bool { + return boolDefaultModifier{ + Default: defaultValue, + } +} diff --git a/internal/custommodifier/fieldtypechangeprohibited.go b/internal/custommodifier/fieldtypechangeprohibited.go new file mode 100644 index 0000000..bca0757 --- /dev/null +++ b/internal/custommodifier/fieldtypechangeprohibited.go @@ -0,0 +1,54 @@ +package custommodifier + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// From https://developer.hashicorp.com/terraform/plugin/framework/resources/plan-modification#creating-attribute-plan-modifiers + +var _ planmodifier.Object = fieldTypeChangeProhibited{} +var _ planmodifier.Object = fieldTypeChangeProhibited{} + +type fieldTypeChangeProhibited struct{} + +func (s fieldTypeChangeProhibited) Description(_ context.Context) string { + return fmt.Sprint("If value is already configured, prevent any change") +} + +func (s fieldTypeChangeProhibited) MarkdownDescription(ctx context.Context) string { + return s.Description(ctx) +} + +func (s fieldTypeChangeProhibited) PlanModifyObject(ctx context.Context, request planmodifier.ObjectRequest, response *planmodifier.ObjectResponse) { + if request.StateValue.IsNull() { + return + } + + var fieldsList basetypes.ListValue + + diags := request.State.GetAttribute(ctx, request.Path.ParentPath(), &fieldsList) + + response.Diagnostics.Append(diags...) + + for _, value := range fieldsList.Elements() { + + stateFields := value.(basetypes.ObjectValue).Attributes() + planFields := request.PlanValue.Attributes() + + if planFields["id"].Equal(stateFields["id"]) { + if !planFields["type"].Equal(stateFields["type"]) { + response.Diagnostics.AddError( + fmt.Sprintf("Content Type Field Type Change for Field %s", planFields["id"].String()), "Changing a field type in contentful is not possible. Pls follow this faq: "+ + "https://www.contentful.com/faq/best-practices/#how-to-change-field-type", + ) + } + } + } +} + +func FieldTypeChangeProhibited() planmodifier.Object { + return fieldTypeChangeProhibited{} +} diff --git a/internal/custommodifier/int64default.go b/internal/custommodifier/int64default.go new file mode 100644 index 0000000..1de7e23 --- /dev/null +++ b/internal/custommodifier/int64default.go @@ -0,0 +1,40 @@ +package custommodifier + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// From https://developer.hashicorp.com/terraform/plugin/framework/resources/plan-modification#creating-attribute-plan-modifiers + +var _ planmodifier.Int64 = int64DefaultModifier{} +var _ planmodifier.Describer = int64DefaultModifier{} + +type int64DefaultModifier struct { + Default int64 +} + +// Description returns a human-readable description of the plan modifier. +func (m int64DefaultModifier) Description(_ context.Context) string { + return fmt.Sprintf("If value is not configured, defaults to %d", m.Default) +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (m int64DefaultModifier) MarkdownDescription(ctx context.Context) string { + return m.Description(ctx) +} + +func (m int64DefaultModifier) PlanModifyInt64(_ context.Context, _ planmodifier.Int64Request, resp *planmodifier.Int64Response) { + // If the value is unknown or known, do not set default value. + if resp.PlanValue.IsNull() || resp.PlanValue.IsUnknown() { + resp.PlanValue = types.Int64Value(m.Default) + } +} + +func Int64Default(defaultValue int64) planmodifier.Int64 { + return int64DefaultModifier{ + Default: defaultValue, + } +} diff --git a/internal/custommodifier/listdefault.go b/internal/custommodifier/listdefault.go new file mode 100644 index 0000000..f1d73bc --- /dev/null +++ b/internal/custommodifier/listdefault.go @@ -0,0 +1,47 @@ +package custommodifier + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// From https://developer.hashicorp.com/terraform/plugin/framework/resources/plan-modification#creating-attribute-plan-modifiers + +var _ planmodifier.List = listDefaultModifier{} +var _ planmodifier.Describer = listDefaultModifier{} + +type listDefaultModifier struct { + Default []attr.Value +} + +func (l listDefaultModifier) Description(_ context.Context) string { + return fmt.Sprintf("If value is not configured, defaults to %s", l.Default) +} + +func (l listDefaultModifier) MarkdownDescription(ctx context.Context) string { + return l.Description(ctx) +} + +func (l listDefaultModifier) PlanModifyList(ctx context.Context, _ planmodifier.ListRequest, resp *planmodifier.ListResponse) { + // If the value is unknown or known, do not set default value. + if resp.PlanValue.IsNull() || resp.PlanValue.IsUnknown() { + planValue, diags := types.ListValue(resp.PlanValue.ElementType(ctx), l.Default) + + resp.Diagnostics.Append(diags...) + + if resp.Diagnostics.HasError() { + return + } + + resp.PlanValue = planValue + } +} + +func ListDefault(defaultValue []attr.Value) planmodifier.List { + return listDefaultModifier{ + Default: defaultValue, + } +} diff --git a/internal/custommodifier/stringdefault.go b/internal/custommodifier/stringdefault.go new file mode 100644 index 0000000..73ed7d6 --- /dev/null +++ b/internal/custommodifier/stringdefault.go @@ -0,0 +1,40 @@ +package custommodifier + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// From https://developer.hashicorp.com/terraform/plugin/framework/resources/plan-modification#creating-attribute-plan-modifiers + +var _ planmodifier.String = stringDefaultModifier{} +var _ planmodifier.Describer = stringDefaultModifier{} + +type stringDefaultModifier struct { + Default string +} + +// Description returns a human-readable description of the plan modifier. +func (m stringDefaultModifier) Description(_ context.Context) string { + return fmt.Sprintf("If value is not configured, defaults to %s", m.Default) +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (m stringDefaultModifier) MarkdownDescription(ctx context.Context) string { + return m.Description(ctx) +} + +func (m stringDefaultModifier) PlanModifyString(_ context.Context, _ planmodifier.StringRequest, resp *planmodifier.StringResponse) { + // If the value is unknown or known, do not set default value. + if resp.PlanValue.IsNull() || resp.PlanValue.IsUnknown() { + resp.PlanValue = types.StringValue(m.Default) + } +} + +func StringDefault(defaultValue string) planmodifier.String { + return stringDefaultModifier{ + Default: defaultValue, + } +} diff --git a/internal/customvalidator/attributeneedstobesetvalidator.go b/internal/customvalidator/attributeneedstobesetvalidator.go new file mode 100644 index 0000000..c66d14e --- /dev/null +++ b/internal/customvalidator/attributeneedstobesetvalidator.go @@ -0,0 +1,84 @@ +package customvalidator + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.String = &attributeNeedsToBeSetValidator{} + +type attributeNeedsToBeSetValidator struct { + pathToBeSet path.Expression + value string +} + +func (s attributeNeedsToBeSetValidator) Description(_ context.Context) string { + finalStep, _ := s.pathToBeSet.Steps().LastStep() + return fmt.Sprintf("Attribute \"%s\" needs to be set when value of the current field is \"%s\"", finalStep.String(), s.value) +} + +func (s attributeNeedsToBeSetValidator) MarkdownDescription(ctx context.Context) string { + return s.Description(ctx) +} + +func (s attributeNeedsToBeSetValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + // If the current attribute configuration is null or unknown, there + // cannot be any value comparisons, so exit early without error. + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + // field is not set to the expected value, so we skip + if request.ConfigValue.ValueString() != s.value { + return + } + + // Combine the given path expressions with the current attribute path + // expression. This call automatically handles relative and absolute + // expressions. + expression := request.PathExpression.Merge(s.pathToBeSet) + + // Find paths matching the expression in the configuration data. + matchedPaths, diags := request.Config.PathMatches(ctx, expression) + + response.Diagnostics.Append(diags...) + + // Collect all errors + if diags.HasError() { + return + } + + for _, matchedPath := range matchedPaths { + var matchedPathValue attr.Value + + diags = request.Config.GetAttribute(ctx, matchedPath, &matchedPathValue) + + response.Diagnostics.Append(diags...) + + // Collect all errors + if diags.HasError() { + continue + } + + // If the matched path value is null or unknown, we cannot compare + // values, so continue to other matched paths. + if matchedPathValue.IsNull() || matchedPathValue.IsUnknown() { + finalStep, _ := expression.Steps().LastStep() + response.Diagnostics.AddAttributeError( + request.Path, + "Attribute Value Is Not Set", + fmt.Sprintf("The attribute \"%s\" needs to be set as the value of \"%s\" is \"%s\".", finalStep.String(), request.Path.String(), s.value), + ) + } + } +} + +func AttributeNeedsToBeSetValidator(pathToBeSet path.Expression, value string) validator.String { + return attributeNeedsToBeSetValidator{ + pathToBeSet: pathToBeSet, + value: value, + } +} diff --git a/internal/customvalidator/int64allowedwhensetvalidator.go b/internal/customvalidator/int64allowedwhensetvalidator.go new file mode 100644 index 0000000..0e44d0e --- /dev/null +++ b/internal/customvalidator/int64allowedwhensetvalidator.go @@ -0,0 +1,101 @@ +package customvalidator + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ validator.Int64 = &int64AllowedWhenSetValidator{} + +type int64AllowedWhenSetValidator struct { + expression path.Expression + widgetType string +} + +func (i int64AllowedWhenSetValidator) Description(_ context.Context) string { + return fmt.Sprintf("Can only be configured for widget.id of type %s", i.widgetType) +} + +func (i int64AllowedWhenSetValidator) MarkdownDescription(ctx context.Context) string { + return i.Description(ctx) +} + +func (i int64AllowedWhenSetValidator) ValidateInt64(ctx context.Context, request validator.Int64Request, response *validator.Int64Response) { + // If the current attribute configuration is null or unknown, there + // cannot be any value comparisons, so exit early without error. + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + // Combine the given path expressions with the current attribute path + // expression. This call automatically handles relative and absolute + // expressions. + expression := request.PathExpression.Merge(i.expression) + + // Find paths matching the expression in the configuration data. + matchedPaths, diags := request.Config.PathMatches(ctx, expression) + + response.Diagnostics.Append(diags...) + + // Collect all errors + if diags.HasError() { + return + } + + for _, matchedPath := range matchedPaths { + var matchedPathValue attr.Value + + diags = request.Config.GetAttribute(ctx, matchedPath, &matchedPathValue) + + response.Diagnostics.Append(diags...) + + // Collect all errors + if diags.HasError() { + continue + } + + // If the matched path value is null or unknown, we cannot compare + // values, so continue to other matched paths. + if matchedPathValue.IsNull() || matchedPathValue.IsUnknown() { + continue + } + + // Now that we know the matched path value is not null or unknown, + // it is safe to attempt converting it to the intended attr.Value + // implementation, in this case a types.Int64 value. + var matchedPathConfig types.String + + diags = tfsdk.ValueAs(ctx, matchedPathValue, &matchedPathConfig) + + response.Diagnostics.Append(diags...) + + // If the matched path value was not able to be converted from + // attr.Value to the intended types.Int64 implementation, it most + // likely means that the path expression was not pointing at a + // types.Int64Type attribute. Collect the error and continue to + // other matched paths. + if diags.HasError() { + continue + } + + if matchedPathConfig.ValueString() != i.widgetType { + response.Diagnostics.AddAttributeError( + request.Path, + "Invalid Attribute Value", + fmt.Sprintf("This value can only be set if the widget_id is \"%s\" but it is \"%s\". Path: %s", i.widgetType, matchedPathConfig.ValueString(), request.Path.String()), + ) + } + } +} + +func Int64AllowedWhenSetValidator(expression path.Expression, widgetType string) validator.Int64 { + return int64AllowedWhenSetValidator{ + expression: expression, + widgetType: widgetType, + } +} diff --git a/internal/customvalidator/stringallowedwhensetvalidator.go b/internal/customvalidator/stringallowedwhensetvalidator.go new file mode 100644 index 0000000..8598074 --- /dev/null +++ b/internal/customvalidator/stringallowedwhensetvalidator.go @@ -0,0 +1,101 @@ +package customvalidator + +import ( + "context" + "fmt" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +var _ validator.String = &stringAllowedWhenSetValidator{} + +type stringAllowedWhenSetValidator struct { + expression path.Expression + widgetType string +} + +func (s stringAllowedWhenSetValidator) Description(_ context.Context) string { + return fmt.Sprintf("Can only be configured for widget.id of type %s", s.widgetType) +} + +func (s stringAllowedWhenSetValidator) MarkdownDescription(ctx context.Context) string { + return s.Description(ctx) +} + +func (s stringAllowedWhenSetValidator) ValidateString(ctx context.Context, request validator.StringRequest, response *validator.StringResponse) { + // If the current attribute configuration is null or unknown, there + // cannot be any value comparisons, so exit early without error. + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + // Combine the given path expressions with the current attribute path + // expression. This call automatically handles relative and absolute + // expressions. + expression := request.PathExpression.Merge(s.expression) + + // Find paths matching the expression in the configuration data. + matchedPaths, diags := request.Config.PathMatches(ctx, expression) + + response.Diagnostics.Append(diags...) + + // Collect all errors + if diags.HasError() { + return + } + + for _, matchedPath := range matchedPaths { + var matchedPathValue attr.Value + + diags = request.Config.GetAttribute(ctx, matchedPath, &matchedPathValue) + + response.Diagnostics.Append(diags...) + + // Collect all errors + if diags.HasError() { + continue + } + + // If the matched path value is null or unknown, we cannot compare + // values, so continue to other matched paths. + if matchedPathValue.IsNull() || matchedPathValue.IsUnknown() { + continue + } + + // Now that we know the matched path value is not null or unknown, + // it is safe to attempt converting it to the intended attr.Value + // implementation, in this case a types.Int64 value. + var matchedPathConfig types.String + + diags = tfsdk.ValueAs(ctx, matchedPathValue, &matchedPathConfig) + + response.Diagnostics.Append(diags...) + + // If the matched path value was not able to convert from + // attr.Value to the intended types.Int64 implementation, it most + // likely means that the path expression was not pointing at a + // types.Int64Type attribute. Collect the error and continue to + // other matched paths. + if diags.HasError() { + continue + } + + if matchedPathConfig.ValueString() != s.widgetType { + response.Diagnostics.AddAttributeError( + request.Path, + "Invalid Attribute Value", + fmt.Sprintf("This value can only be set if the widget_id is \"%s\" but it is \"%s\". Path: %s", s.widgetType, matchedPathConfig.ValueString(), request.Path.String()), + ) + } + } +} + +func StringAllowedWhenSetValidator(expression path.Expression, widgetType string) validator.String { + return stringAllowedWhenSetValidator{ + expression: expression, + widgetType: widgetType, + } +} diff --git a/internal/customvalidator/whenothervalueexistlistvalidator.go b/internal/customvalidator/whenothervalueexistlistvalidator.go new file mode 100644 index 0000000..14283a3 --- /dev/null +++ b/internal/customvalidator/whenothervalueexistlistvalidator.go @@ -0,0 +1,76 @@ +package customvalidator + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.List = &whenOtherValueExistListValidator{} + +type whenOtherValueExistListValidator struct { + expression path.Expression + listValidator validator.List +} + +func (l whenOtherValueExistListValidator) Description(_ context.Context) string { + return "Validates a list when a given property is set" +} + +func (l whenOtherValueExistListValidator) MarkdownDescription(ctx context.Context) string { + return l.Description(ctx) +} + +func (l whenOtherValueExistListValidator) ValidateList(ctx context.Context, request validator.ListRequest, response *validator.ListResponse) { + // If the current attribute configuration is null or unknown, there + // cannot be any value comparisons, so exit early without error. + if request.ConfigValue.IsNull() || request.ConfigValue.IsUnknown() { + return + } + + // Combine the given path expressions with the current attribute path + // expression. This call automatically handles relative and absolute + // expressions. + expression := request.PathExpression.Merge(l.expression) + + // Find paths matching the expression in the configuration data. + matchedPaths, diags := request.Config.PathMatches(ctx, expression) + + response.Diagnostics.Append(diags...) + + // Collect all errors + if diags.HasError() { + return + } + + for _, matchedPath := range matchedPaths { + var matchedPathValue attr.Value + + diags = request.Config.GetAttribute(ctx, matchedPath, &matchedPathValue) + + response.Diagnostics.Append(diags...) + + // Collect all errors + if diags.HasError() { + continue + } + + // If the matched path value is null or unknown, we cannot compare + // values, so continue to other matched paths. + if matchedPathValue.IsNull() || matchedPathValue.IsUnknown() { + continue + } + + // when the matched path has a value we will execute the given list validation + + l.listValidator.ValidateList(ctx, request, response) + } +} + +func WhenOtherValueExistListValidator(expression path.Expression, listValidator validator.List) validator.List { + return whenOtherValueExistListValidator{ + expression: expression, + listValidator: listValidator, + } +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go new file mode 100644 index 0000000..d492a1d --- /dev/null +++ b/internal/provider/provider.go @@ -0,0 +1,160 @@ +package provider + +import ( + "context" + "os" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/labd/contentful-go" + client2 "github.com/labd/contentful-go/pkgs/client" + "github.com/labd/contentful-go/pkgs/util" + + "github.com/labd/terraform-provider-contentful/internal/resources/api_key" + "github.com/labd/terraform-provider-contentful/internal/resources/app_definition" + "github.com/labd/terraform-provider-contentful/internal/resources/app_installation" + "github.com/labd/terraform-provider-contentful/internal/resources/contenttype" + "github.com/labd/terraform-provider-contentful/internal/utils" +) + +var ( + _ provider.Provider = &contentfulProvider{} +) + +func New(version string, debug bool) provider.Provider { + return &contentfulProvider{ + version: version, + debug: debug, + } +} + +type contentfulProvider struct { + version string + debug bool +} + +// Provider schema struct +type contentfulProviderModel struct { + CmaToken types.String `tfsdk:"cma_token"` + OrganizationId types.String `tfsdk:"organization_id"` + BaseURL types.String `tfsdk:"base_url"` + Environment types.String `tfsdk:"environment"` +} + +func (c contentfulProvider) Metadata(_ context.Context, _ provider.MetadataRequest, response *provider.MetadataResponse) { + response.TypeName = "contentful" +} + +func (c contentfulProvider) Schema(_ context.Context, _ provider.SchemaRequest, response *provider.SchemaResponse) { + response.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "cma_token": schema.StringAttribute{ + Optional: true, + Sensitive: true, + Description: "The Contentful Management API token", + }, + "organization_id": schema.StringAttribute{ + Optional: true, + Sensitive: true, + Description: "The organization ID", + }, + "base_url": schema.StringAttribute{ + Optional: true, + Description: "The base url to use for the Contentful API. Defaults to https://api.contentful.com", + }, + "environment": schema.StringAttribute{ + Optional: true, + Description: "The environment to use for the Contentful API. Defaults to master", + }, + }, + } +} + +func (c contentfulProvider) Configure(ctx context.Context, request provider.ConfigureRequest, response *provider.ConfigureResponse) { + var config contentfulProviderModel + + diags := request.Config.Get(ctx, &config) + response.Diagnostics.Append(diags...) + + if response.Diagnostics.HasError() { + return + } + + var cmaToken string + + if config.CmaToken.IsUnknown() || config.CmaToken.IsNull() { + cmaToken = os.Getenv("CONTENTFUL_MANAGEMENT_TOKEN") + } else { + cmaToken = config.CmaToken.ValueString() + } + + var organizationId string + + if config.OrganizationId.IsUnknown() || config.OrganizationId.IsNull() { + organizationId = os.Getenv("CONTENTFUL_ORGANIZATION_ID") + } else { + organizationId = config.OrganizationId.ValueString() + } + + cma := contentful.NewCMA(cmaToken) + cma.SetOrganization(organizationId) + + var baseURL string + if config.BaseURL.IsUnknown() || config.BaseURL.IsNull() { + value, isSet := os.LookupEnv("CONTENTFUL_BASE_URL") + if isSet { + baseURL = value + } else { + baseURL = "https://api.contentful.com" + } + } else { + baseURL = config.BaseURL.ValueString() + } + + debug := c.debug + + if os.Getenv("TF_LOG") != "" { + debug = true + } + + cma.Debug = debug + + client, err := contentful.NewCMAV2(client2.ClientConfig{ + URL: &baseURL, + Debug: debug, + UserAgent: util.ToPointer("terraform-provider-contentful"), + Token: cmaToken, + }) + + if err != nil { + response.Diagnostics.AddError( + "error during creation of cma client", + err.Error(), + ) + return + } + + data := utils.ProviderData{ + CMAClient: client, + OrganizationId: organizationId, + } + + response.ResourceData = data + response.DataSourceData = data +} + +func (c contentfulProvider) DataSources(_ context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{} +} + +func (c contentfulProvider) Resources(_ context.Context) []func() resource.Resource { + return []func() resource.Resource{ + contenttype.NewContentTypeResource, + app_definition.NewAppDefinitionResource, + app_installation.NewAppInstallationResource, + api_key.NewApiKeyResource, + } +} diff --git a/internal/resources/api_key/model.go b/internal/resources/api_key/model.go new file mode 100644 index 0000000..f652107 --- /dev/null +++ b/internal/resources/api_key/model.go @@ -0,0 +1,65 @@ +package api_key + +import ( + "github.com/elliotchance/pie/v2" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/labd/contentful-go/pkgs/model" +) + +// ApiKey is the main resource schema data +type ApiKey struct { + ID types.String `tfsdk:"id"` + PreviewID types.String `tfsdk:"preview_id"` + SpaceId types.String `tfsdk:"space_id"` + Name types.String `tfsdk:"name"` + Description types.String `tfsdk:"description"` + Version types.Int64 `tfsdk:"version"` + AccessToken types.String `tfsdk:"access_token"` + PreviewToken types.String `tfsdk:"preview_token"` + Environments []types.String `tfsdk:"environments"` +} + +func (a *ApiKey) Import(n *model.APIKey) { + a.ID = types.StringValue(n.Sys.ID) + a.PreviewID = types.StringValue(n.PreviewAPIKey.Sys.ID) + a.SpaceId = types.StringValue(n.Sys.Space.Sys.ID) + a.Version = types.Int64Value(int64(n.Sys.Version)) + a.Name = types.StringValue(n.Name) + a.Description = types.StringNull() + if n.Description != "" { + a.Description = types.StringValue(n.Description) + } + + a.Environments = pie.Map(n.Environments, func(t model.Environments) types.String { + return types.StringValue(t.Sys.ID) + }) + + a.AccessToken = types.StringValue(n.AccessToken) +} + +func (a *ApiKey) Draft() *model.APIKey { + draft := &model.APIKey{} + + if !a.ID.IsUnknown() || !a.ID.IsNull() { + draft.Sys = &model.SpaceSys{CreatedSys: model.CreatedSys{BaseSys: model.BaseSys{ID: a.ID.ValueString(), Version: int(a.Version.ValueInt64())}}} + + } + + if !a.Description.IsNull() && !a.Description.IsUnknown() { + draft.Description = a.Description.ValueString() + } + + draft.Environments = pie.Map(a.Environments, func(t types.String) model.Environments { + return model.Environments{ + Sys: model.BaseSys{ + ID: t.ValueString(), + Type: "Link", + LinkType: "Environment", + }, + } + }) + + draft.Name = a.Name.ValueString() + + return draft +} diff --git a/internal/resources/api_key/resource.go b/internal/resources/api_key/resource.go new file mode 100644 index 0000000..3db14ab --- /dev/null +++ b/internal/resources/api_key/resource.go @@ -0,0 +1,270 @@ +package api_key + +import ( + "context" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/labd/contentful-go/pkgs/model" + "github.com/labd/contentful-go/service/cma" + "github.com/labd/terraform-provider-contentful/internal/custommodifier" + "github.com/labd/terraform-provider-contentful/internal/utils" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &apiKeyResource{} + _ resource.ResourceWithConfigure = &apiKeyResource{} + _ resource.ResourceWithImportState = &apiKeyResource{} +) + +func NewApiKeyResource() resource.Resource { + return &apiKeyResource{} +} + +// apiKeyResource is the resource implementation. +type apiKeyResource struct { + client cma.SpaceIdClientBuilder +} + +func (e *apiKeyResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = request.ProviderTypeName + "_apikey" +} + +func (e *apiKeyResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) { + + response.Schema = schema.Schema{ + Description: "Todo for explaining apikey", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "api key id", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "preview_id": schema.StringAttribute{ + Computed: true, + Description: "preview api key id", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "version": schema.Int64Attribute{ + Computed: true, + }, + "space_id": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Description: "space id", + }, + "access_token": schema.StringAttribute{ + Computed: true, + Sensitive: true, + }, + "name": schema.StringAttribute{ + Required: true, + }, + "description": schema.StringAttribute{ + Optional: true, + }, + "preview_token": schema.StringAttribute{ + Computed: true, + Sensitive: true, + }, + "environments": schema.ListAttribute{ + Optional: true, + Computed: true, + Description: "List of needed environments if not added then master is used", + ElementType: types.StringType, + PlanModifiers: []planmodifier.List{ + custommodifier.ListDefault([]attr.Value{types.StringValue("master")}), + }, + }, + }, + } +} + +func (e *apiKeyResource) Configure(_ context.Context, request resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if request.ProviderData == nil { + return + } + + data := request.ProviderData.(utils.ProviderData) + e.client = data.CMAClient +} + +func (e *apiKeyResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var plan *ApiKey + response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...) + if response.Diagnostics.HasError() { + return + } + + draft := plan.Draft() + + if err := e.client.WithSpaceId(plan.SpaceId.ValueString()).ApiKeys().Upsert(ctx, draft); err != nil { + response.Diagnostics.AddError("Error creating api_key", err.Error()) + return + } + + plan.Import(draft) + + previewApiKeyContentful, err := e.getPreviewApiKey(ctx, plan) + if err != nil { + response.Diagnostics.AddError( + "Error reading preview api key", + "Could not retrieve preview api key, unexpected error: "+err.Error(), + ) + return + } + + plan.PreviewToken = types.StringValue(previewApiKeyContentful.AccessToken) + + // Set state to fully populated data + response.Diagnostics.Append(response.State.Set(ctx, plan)...) + if response.Diagnostics.HasError() { + return + } +} + +func (e *apiKeyResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + // Get current state + var state *ApiKey + diags := request.State.Get(ctx, &state) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + e.doRead(ctx, state, &response.State, &response.Diagnostics) +} + +func (e *apiKeyResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + // Retrieve values from plan + var plan *ApiKey + diags := request.Plan.Get(ctx, &plan) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + // Get current state + var state *ApiKey + diags = request.State.Get(ctx, &state) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + draft := plan.Draft() + + if err := e.client.WithSpaceId(state.SpaceId.ValueString()).ApiKeys().Upsert(ctx, draft); err != nil { + response.Diagnostics.AddError( + "Error updating api key", + "Could not update api key, unexpected error: "+err.Error(), + ) + return + } + + plan.Import(draft) + + previewApiKeyContentful, err := e.getPreviewApiKey(ctx, plan) + if err != nil { + response.Diagnostics.AddError( + "Error reading preview api key", + "Could not retrieve preview api key, unexpected error: "+err.Error(), + ) + return + } + + plan.PreviewToken = types.StringValue(previewApiKeyContentful.AccessToken) + + // Set state to fully populated data + response.Diagnostics.Append(response.State.Set(ctx, plan)...) + if response.Diagnostics.HasError() { + return + } +} + +func (e *apiKeyResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + // Get current state + var state *ApiKey + response.Diagnostics.Append(request.State.Get(ctx, &state)...) + + if err := e.client.WithSpaceId(state.SpaceId.ValueString()).ApiKeys().Delete(ctx, state.Draft()); err != nil { + response.Diagnostics.AddError( + "Error deleting api_key", + "Could not delete api_key, unexpected error: "+err.Error(), + ) + return + } +} + +func (e *apiKeyResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + idParts := strings.SplitN(request.ID, ":", 2) + + if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { + response.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: apiKeyId:spaceId. Got: %q", request.ID), + ) + return + } + + futureState := &ApiKey{ + ID: types.StringValue(idParts[0]), + SpaceId: types.StringValue(idParts[1]), + } + + e.doRead(ctx, futureState, &response.State, &response.Diagnostics) +} + +func (e *apiKeyResource) doRead(ctx context.Context, apiKey *ApiKey, state *tfsdk.State, d *diag.Diagnostics) { + + apiKeyContentful, err := e.getApiKey(ctx, apiKey) + if err != nil { + d.AddError( + "Error reading api key", + "Could not retrieve api key, unexpected error: "+err.Error(), + ) + return + } + + apiKey.Import(apiKeyContentful) + + previewApiKeyContentful, err := e.getPreviewApiKey(ctx, apiKey) + if err != nil { + d.AddError( + "Error reading preview api key", + "Could not retrieve preview api key, unexpected error: "+err.Error(), + ) + return + } + + apiKey.PreviewToken = types.StringValue(previewApiKeyContentful.AccessToken) + + // Set refreshed state + d.Append(state.Set(ctx, &apiKey)...) + if d.HasError() { + return + } +} + +func (e *apiKeyResource) getApiKey(ctx context.Context, apiKey *ApiKey) (*model.APIKey, error) { + return e.client.WithSpaceId(apiKey.SpaceId.ValueString()).ApiKeys().Get(ctx, apiKey.ID.ValueString()) +} + +func (e *apiKeyResource) getPreviewApiKey(ctx context.Context, apiKey *ApiKey) (*model.PreviewAPIKey, error) { + return e.client.WithSpaceId(apiKey.SpaceId.ValueString()).PreviewApiKeys().Get(ctx, apiKey.PreviewID.ValueString()) +} diff --git a/internal/resources/api_key/resource_test.go b/internal/resources/api_key/resource_test.go new file mode 100644 index 0000000..596c7bd --- /dev/null +++ b/internal/resources/api_key/resource_test.go @@ -0,0 +1,173 @@ +package api_key_test + +import ( + "context" + "errors" + "fmt" + "os" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + hashicor_acctest "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/labd/contentful-go/pkgs/common" + "github.com/labd/contentful-go/pkgs/model" + "github.com/labd/terraform-provider-contentful/internal/acctest" + "github.com/labd/terraform-provider-contentful/internal/provider" + "github.com/labd/terraform-provider-contentful/internal/utils" + "github.com/stretchr/testify/assert" +) + +type assertFunc func(*testing.T, *model.APIKey) + +func TestApiKeyResource_Create(t *testing.T) { + name := fmt.Sprintf("apikey-name-%s", hashicor_acctest.RandString(3)) + description := fmt.Sprintf("apikey-description-%s", hashicor_acctest.RandString(3)) + resourceName := "contentful_apikey.myapikey" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + CheckDestroy: testAccCheckContentfulApiKeyDestroy, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "contentful": providerserver.NewProtocol6WithError(provider.New("test", true)), + }, + Steps: []resource.TestStep{ + { + Config: testApiKey(os.Getenv("CONTENTFUL_SPACE_ID"), name, description), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "description", description), + resource.TestMatchResourceAttr(resourceName, "id", regexp.MustCompile(`^[a-zA-Z0-9-_.]{1,64}$`)), + resource.TestMatchResourceAttr(resourceName, "preview_id", regexp.MustCompile(`^[a-zA-Z0-9-_.]{1,64}$`)), + testAccCheckContentfulApiKeyExists(t, resourceName, func(t *testing.T, apiKey *model.APIKey) { + assert.NotEmpty(t, apiKey.AccessToken) + assert.EqualValues(t, name, apiKey.Name) + assert.EqualValues(t, description, apiKey.Description) + assert.Regexp(t, regexp.MustCompile(`^[a-zA-Z0-9-_.]{1,64}$`), apiKey.Sys.ID) + assert.EqualValues(t, "master", apiKey.Environments[0].Sys.ID) + }), + ), + }, + { + Config: testApiKeyUpdate(os.Getenv("CONTENTFUL_SPACE_ID"), name, description), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", fmt.Sprintf("%s-updated", name)), + resource.TestCheckResourceAttr(resourceName, "description", fmt.Sprintf("%s-updated", description)), + resource.TestMatchResourceAttr(resourceName, "id", regexp.MustCompile(`^[a-zA-Z0-9-_.]{1,64}$`)), + resource.TestMatchResourceAttr(resourceName, "preview_id", regexp.MustCompile(`^[a-zA-Z0-9-_.]{1,64}$`)), + testAccCheckContentfulApiKeyExists(t, resourceName, func(t *testing.T, apiKey *model.APIKey) { + assert.NotEmpty(t, apiKey.AccessToken) + assert.EqualValues(t, fmt.Sprintf("%s-updated", name), apiKey.Name) + assert.EqualValues(t, fmt.Sprintf("%s-updated", description), apiKey.Description) + assert.Regexp(t, regexp.MustCompile(`^[a-zA-Z0-9-_.]{1,64}$`), apiKey.Sys.ID) + }), + ), + }, + }, + }) +} + +func TestApiKeyResource_CreateWithEnvironmentSet(t *testing.T) { + name := fmt.Sprintf("apikey-name-%s", hashicor_acctest.RandString(3)) + description := fmt.Sprintf("apikey-description-%s", hashicor_acctest.RandString(3)) + resourceName := "contentful_apikey.myapikey" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + CheckDestroy: testAccCheckContentfulApiKeyDestroy, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "contentful": providerserver.NewProtocol6WithError(provider.New("test", true)), + }, + Steps: []resource.TestStep{ + { + Config: testApiKeyWithEnvironment(os.Getenv("CONTENTFUL_SPACE_ID"), name, description), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", name), + resource.TestCheckResourceAttr(resourceName, "description", description), + resource.TestMatchResourceAttr(resourceName, "id", regexp.MustCompile(`^[a-zA-Z0-9-_.]{1,64}$`)), + resource.TestMatchResourceAttr(resourceName, "preview_id", regexp.MustCompile(`^[a-zA-Z0-9-_.]{1,64}$`)), + testAccCheckContentfulApiKeyExists(t, resourceName, func(t *testing.T, apiKey *model.APIKey) { + assert.NotEmpty(t, apiKey.AccessToken) + assert.EqualValues(t, name, apiKey.Name) + assert.EqualValues(t, description, apiKey.Description) + assert.Regexp(t, regexp.MustCompile(`^[a-zA-Z0-9-_.]{1,64}$`), apiKey.Sys.ID) + assert.EqualValues(t, "notexisting", apiKey.Environments[0].Sys.ID) + }), + ), + }, + }, + }) +} + +func testAccCheckContentfulApiKeyExists(t *testing.T, resourceName string, assertFunc assertFunc) resource.TestCheckFunc { + return func(s *terraform.State) error { + definition, err := getApiKeyFromState(s, resourceName) + if err != nil { + return err + } + + assertFunc(t, definition) + return nil + } +} + +func getApiKeyFromState(s *terraform.State, resourceName string) (*model.APIKey, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return nil, fmt.Errorf("api key not found") + } + + if rs.Primary.ID == "" { + return nil, fmt.Errorf("no api key ID found") + } + + client := acctest.GetCMA() + + return client.WithSpaceId(os.Getenv("CONTENTFUL_SPACE_ID")).ApiKeys().Get(context.Background(), rs.Primary.ID) +} + +func testAccCheckContentfulApiKeyDestroy(s *terraform.State) error { + client := acctest.GetCMA() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "contentful_apikey" { + continue + } + + _, err := client.WithSpaceId(os.Getenv("CONTENTFUL_SPACE_ID")).ApiKeys().Get(context.Background(), rs.Primary.ID) + var notFoundError common.NotFoundError + if errors.As(err, ¬FoundError) { + return nil + } + + return fmt.Errorf("api key still exists with id: %s", rs.Primary.ID) + } + + return nil +} + +func testApiKey(spaceId string, name string, description string) string { + return utils.HCLTemplateFromPath("test_resources/create.tf", map[string]any{ + "spaceId": spaceId, + "name": name, + "description": description, + }) +} + +func testApiKeyWithEnvironment(spaceId string, name string, description string) string { + return utils.HCLTemplateFromPath("test_resources/create.tf", map[string]any{ + "spaceId": spaceId, + "name": name, + "description": description, + "environments": []string{"notexisting"}, + }) +} + +func testApiKeyUpdate(spaceId string, name string, description string) string { + return utils.HCLTemplateFromPath("test_resources/update.tf", map[string]any{ + "spaceId": spaceId, + "name": name, + "description": description, + }) +} diff --git a/internal/resources/api_key/test_resources/create.tf b/internal/resources/api_key/test_resources/create.tf new file mode 100644 index 0000000..758395e --- /dev/null +++ b/internal/resources/api_key/test_resources/create.tf @@ -0,0 +1,13 @@ +resource "contentful_apikey" "myapikey" { + space_id = "{{ .spaceId }}" + + name = "{{ .name }}" + description = "{{ .description }}" + + {{if .environments}} + + environments = [ {{range .environments}}"{{.}}",{{end}}] + + {{end}} + +} \ No newline at end of file diff --git a/internal/resources/api_key/test_resources/update.tf b/internal/resources/api_key/test_resources/update.tf new file mode 100644 index 0000000..1cb8387 --- /dev/null +++ b/internal/resources/api_key/test_resources/update.tf @@ -0,0 +1,6 @@ +resource "contentful_apikey" "myapikey" { + space_id = "{{ .spaceId }}" + + name = "{{ .name }}-updated" + description = "{{ .description }}-updated" +} \ No newline at end of file diff --git a/internal/resources/app_definition/bundle.zip b/internal/resources/app_definition/bundle.zip new file mode 100644 index 0000000..d273e66 Binary files /dev/null and b/internal/resources/app_definition/bundle.zip differ diff --git a/internal/resources/app_definition/model.go b/internal/resources/app_definition/model.go new file mode 100644 index 0000000..cd0f45e --- /dev/null +++ b/internal/resources/app_definition/model.go @@ -0,0 +1,192 @@ +package app_definition + +import ( + "github.com/elliotchance/pie/v2" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/labd/contentful-go/pkgs/model" + "github.com/labd/terraform-provider-contentful/internal/utils" +) + +// AppDefinition is the main resource schema data +type AppDefinition struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Src types.String `tfsdk:"src"` + BundleId types.String `tfsdk:"bundle_id"` + UseBundle types.Bool `tfsdk:"use_bundle"` + Locations []Location `tfsdk:"locations"` +} + +func (a AppDefinition) Draft() *model.AppDefinition { + + app := &model.AppDefinition{} + + if !a.ID.IsUnknown() || !a.ID.IsNull() { + app.Sys = &model.CreatedSys{BaseSys: model.BaseSys{ID: a.ID.ValueString()}} + } + + if !a.Src.IsNull() && !a.Src.IsUnknown() { + app.SRC = a.Src.ValueStringPointer() + } + + app.Name = a.Name.ValueString() + + app.Locations = pie.Map(a.Locations, func(t Location) model.Locations { + return t.Draft() + }) + + if a.UseBundle.ValueBool() && !a.BundleId.IsNull() && !a.BundleId.IsUnknown() { + app.Bundle = &model.Bundle{Sys: &model.BaseSys{ + ID: a.BundleId.ValueString(), + Type: "Link", + LinkType: "AppBundle", + }} + } + + return app +} + +func (a *AppDefinition) Equal(n *model.AppDefinition) bool { + + if a.Name.ValueString() != n.Name { + return false + } + + if !utils.CompareStringPointer(a.Src, n.SRC) { + return false + } + + if len(a.Locations) != len(n.Locations) { + return false + } + + for _, location := range a.Locations { + idx := pie.FindFirstUsing(n.Locations, func(f model.Locations) bool { + return f.Location == location.Location.ValueString() + }) + + if idx == -1 { + return false + } + } + + return true +} + +func (a *AppDefinition) Import(n *model.AppDefinition) { + a.ID = types.StringValue(n.Sys.ID) + a.UseBundle = types.BoolValue(false) + a.BundleId = types.StringNull() + + a.Name = types.StringValue(n.Name) + + a.Src = types.StringPointerValue(n.SRC) + + fields := []Location{} + + for _, location := range n.Locations { + field := &Location{} + field.Import(location) + fields = append(fields, *field) + } + + if n.Bundle != nil { + a.BundleId = types.StringValue(n.Bundle.Sys.ID) + a.UseBundle = types.BoolValue(true) + } + + a.Locations = fields +} + +type Location struct { + Location types.String `tfsdk:"location"` + FieldTypes []FieldType `tfsdk:"field_types"` + NavigationItem *NavigationItem `tfsdk:"navigation_item"` +} + +func (l *Location) Import(n model.Locations) { + l.Location = types.StringValue(n.Location) + + if n.NavigationItem != nil { + l.NavigationItem = &NavigationItem{} + l.NavigationItem.Import(n.NavigationItem) + } + + l.FieldTypes = pie.Map(n.FieldTypes, func(t model.FieldType) FieldType { + field := &FieldType{} + field.Import(t) + return *field + }) +} + +func (l *Location) Draft() model.Locations { + location := model.Locations{ + Location: l.Location.ValueString(), + } + + if l.NavigationItem != nil { + location.NavigationItem = &model.NavigationItem{ + Name: l.NavigationItem.Name.ValueString(), + Path: l.NavigationItem.Path.String(), + } + } + + location.FieldTypes = pie.Map(l.FieldTypes, func(t FieldType) model.FieldType { + return t.Draft() + }) + + return location +} + +type FieldType struct { + Type types.String `tfsdk:"type"` + LinkType types.String `tfsdk:"link_type"` + Items *Items `tfsdk:"items"` +} + +func (f *FieldType) Draft() model.FieldType { + fieldType := model.FieldType{ + Type: f.Type.ValueString(), + LinkType: f.LinkType.ValueStringPointer(), + } + + if f.Items != nil { + fieldType.Items = f.Items.Draft() + } + + return fieldType +} + +func (f *FieldType) Import(n model.FieldType) { + f.Type = types.StringValue(n.Type) + f.LinkType = types.StringPointerValue(n.LinkType) + + if n.Items != nil { + f.Items = &Items{ + Type: types.StringValue(n.Items.Type), + LinkType: types.StringPointerValue(n.Items.LinkType), + } + } +} + +type Items struct { + Type types.String `tfsdk:"type"` + LinkType types.String `tfsdk:"link_type"` +} + +func (i *Items) Draft() *model.Items { + return &model.Items{ + Type: i.Type.ValueString(), + LinkType: i.LinkType.ValueStringPointer(), + } +} + +type NavigationItem struct { + Name types.String `tfsdk:"name"` + Path types.String `tfsdk:"path"` +} + +func (l *NavigationItem) Import(n *model.NavigationItem) { + l.Name = types.StringValue(n.Name) + l.Path = types.StringValue(n.Path) +} diff --git a/internal/resources/app_definition/resource.go b/internal/resources/app_definition/resource.go new file mode 100644 index 0000000..cd001db --- /dev/null +++ b/internal/resources/app_definition/resource.go @@ -0,0 +1,336 @@ +package app_definition + +import ( + "context" + _ "embed" + + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/labd/contentful-go" + "github.com/labd/contentful-go/pkgs/model" + "github.com/labd/contentful-go/service/cma" + + "github.com/labd/terraform-provider-contentful/internal/custommodifier" + "github.com/labd/terraform-provider-contentful/internal/customvalidator" + "github.com/labd/terraform-provider-contentful/internal/utils" +) + +//go:embed bundle.zip +var defaultDummyBundle []byte + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &appDefinitionResource{} + _ resource.ResourceWithConfigure = &appDefinitionResource{} + _ resource.ResourceWithImportState = &appDefinitionResource{} +) + +func NewAppDefinitionResource() resource.Resource { + return &appDefinitionResource{} +} + +// appDefinitionResource is the resource implementation. +type appDefinitionResource struct { + client cma.OrganizationIdClient + clientAppUpload *contentful.Client + organizationId string +} + +func (e *appDefinitionResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = request.ProviderTypeName + "_app_definition" +} + +func (e *appDefinitionResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Description: "Todo for explaining app definition", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + Description: "app definition id", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + stringplanmodifier.RequiresReplace(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + }, + "src": schema.StringAttribute{ + Optional: true, + }, + "use_bundle": schema.BoolAttribute{ + Required: true, + PlanModifiers: []planmodifier.Bool{ + custommodifier.BoolDefault(false), + }, + }, + "bundle_id": schema.StringAttribute{ + Computed: true, + }, + "locations": schema.ListNestedAttribute{ + Validators: []validator.List{ + customvalidator.WhenOtherValueExistListValidator(path.MatchRelative().AtParent().AtName("src"), listvalidator.SizeAtLeast(1)), + }, + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "location": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("entry-field", "entry-sidebar", "entry-editor", "dialog", "app-config", "page", "home"), + customvalidator.AttributeNeedsToBeSetValidator(path.MatchRelative().AtParent().AtName("field_types"), "entry-field"), + }, + }, + "navigation_item": schema.SingleNestedAttribute{ + Optional: true, + // only be valid when type = page + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + }, + "path": schema.StringAttribute{ + Required: true, + }, + }, + }, + // needs to be set when location is entry-field + "field_types": schema.ListNestedAttribute{ + Optional: true, + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + }, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf(utils.GetAppFieldTypes()...), + customvalidator.AttributeNeedsToBeSetValidator(path.MatchRelative().AtParent().AtName("link_type"), "Link"), + customvalidator.AttributeNeedsToBeSetValidator(path.MatchRelative().AtParent().AtName("items"), "Array"), + }, + }, + "link_type": schema.StringAttribute{ + Optional: true, + // needs to be set when type is link + Validators: []validator.String{ + customvalidator.StringAllowedWhenSetValidator(path.MatchRelative().AtParent().AtName("type"), "Link"), + stringvalidator.OneOf(utils.GetLinkTypes()...), + }, + }, + "items": schema.SingleNestedAttribute{ + Optional: true, + //needs to be set when type is Array + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("Symbol", "Link"), + customvalidator.AttributeNeedsToBeSetValidator(path.MatchRelative().AtParent().AtName("link_type"), "Link"), + }, + }, + "link_type": schema.StringAttribute{ + Optional: true, + // needs to be set when type is link + Validators: []validator.String{ + stringvalidator.OneOf(utils.GetLinkTypes()...), + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } +} + +func (e *appDefinitionResource) Configure(_ context.Context, request resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if request.ProviderData == nil { + return + } + + data := request.ProviderData.(utils.ProviderData) + e.client = data.CMAClient.WithOrganizationId(data.OrganizationId) + e.organizationId = data.OrganizationId + e.clientAppUpload = data.ClientOld + +} + +func (e *appDefinitionResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var plan AppDefinition + response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...) + if response.Diagnostics.HasError() { + return + } + + if e.setDefaultBundle(ctx, &plan, response.Diagnostics) { + return + } + + draft := plan.Draft() + + if err := e.client.AppDefinitions().Upsert(ctx, draft); err != nil { + response.Diagnostics.AddError("Error creating app_definition definition", err.Error()) + return + } + + plan.ID = types.StringValue(draft.Sys.ID) + + // Set state to fully populated data + response.Diagnostics.Append(response.State.Set(ctx, plan)...) + if response.Diagnostics.HasError() { + return + } +} + +func (e *appDefinitionResource) setDefaultBundle(ctx context.Context, plan *AppDefinition, diagnostics diag.Diagnostics) bool { + if plan.UseBundle.ValueBool() && (plan.BundleId.IsNull() || plan.BundleId.IsUnknown()) { + + draft := plan.Draft() + + locations := draft.Locations + + draft.Locations = []model.Locations{} + + if err := e.client.AppDefinitions().Upsert(ctx, draft); err != nil { + diagnostics.AddError("Error upsert app_definition definition", err.Error()) + return true + } + + upload, err := e.clientAppUpload.AppUpload.Create(e.organizationId, defaultDummyBundle) + + if err != nil { + diagnostics.AddError("Error uploading default bundle", err.Error()) + return true + } + + draft.Locations = locations + + bundle, err := e.clientAppUpload.AppBundle.Create(e.organizationId, draft.Sys.ID, "Default Terraform BundleId", upload.Sys.ID) + if err != nil { + diagnostics.AddError("Error creating app_bundle for app definition", err.Error()) + return true + } + + plan.ID = types.StringValue(draft.Sys.ID) + plan.BundleId = types.StringValue(bundle.Sys.ID) + } + return false +} + +func (e *appDefinitionResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + // Get current state + var state *AppDefinition + diags := request.State.Get(ctx, &state) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + e.doRead(ctx, state, &response.State, &response.Diagnostics) +} + +func (e *appDefinitionResource) doRead(ctx context.Context, app *AppDefinition, state *tfsdk.State, d *diag.Diagnostics) { + + appDefinition, err := e.getApp(ctx, app) + if err != nil { + d.AddError( + "Error reading app definition", + "Could not retrieve app definition, unexpected error: "+err.Error(), + ) + return + } + + app.Import(appDefinition) + + // Set refreshed state + d.Append(state.Set(ctx, &app)...) + if d.HasError() { + return + } +} + +func (e *appDefinitionResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + // Retrieve values from plan + var plan *AppDefinition + diags := request.Plan.Get(ctx, &plan) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + // Get current state + var state *AppDefinition + diags = request.State.Get(ctx, &state) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + appDefinition, err := e.getApp(ctx, plan) + if err != nil { + response.Diagnostics.AddError( + "Error reading app definition", + "Could not retrieve app definition, unexpected error: "+err.Error(), + ) + return + } + + if !plan.Equal(appDefinition) { + + if e.setDefaultBundle(ctx, plan, response.Diagnostics) { + return + } + + draft := plan.Draft() + + if err = e.client.AppDefinitions().Upsert(ctx, draft); err != nil { + response.Diagnostics.AddError( + "Error updating app definition", + "Could not update app definition, unexpected error: "+err.Error(), + ) + return + } + } + + e.doRead(ctx, plan, &response.State, &response.Diagnostics) +} + +func (e *appDefinitionResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + // Get current state + var state *AppDefinition + response.Diagnostics.Append(request.State.Get(ctx, &state)...) + + if err := e.client.AppDefinitions().Delete(ctx, state.Draft()); err != nil { + response.Diagnostics.AddError( + "Error deleting app_definition definition", + "Could not delete app_definition definition, unexpected error: "+err.Error(), + ) + return + } + +} + +func (e *appDefinitionResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + futureState := &AppDefinition{ + ID: types.StringValue(request.ID), + } + + e.doRead(ctx, futureState, &response.State, &response.Diagnostics) +} + +func (e *appDefinitionResource) getApp(ctx context.Context, app *AppDefinition) (*model.AppDefinition, error) { + return e.client.AppDefinitions().Get(ctx, app.ID.ValueString()) +} diff --git a/internal/resources/app_definition/resource_test.go b/internal/resources/app_definition/resource_test.go new file mode 100644 index 0000000..84c2803 --- /dev/null +++ b/internal/resources/app_definition/resource_test.go @@ -0,0 +1,156 @@ +package app_definition_test + +import ( + "context" + "errors" + "fmt" + "os" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/labd/contentful-go/pkgs/common" + "github.com/labd/contentful-go/pkgs/model" + "github.com/labd/terraform-provider-contentful/internal/acctest" + "github.com/labd/terraform-provider-contentful/internal/provider" + "github.com/labd/terraform-provider-contentful/internal/utils" + "github.com/stretchr/testify/assert" +) + +type assertFunc func(*testing.T, *model.AppDefinition) + +func TestAppDefinitionResource_Create(t *testing.T) { + resourceName := "contentful_app_definition.acctest_app_definition" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + CheckDestroy: testAccCheckContentfulAppDefinitionDestroy, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "contentful": providerserver.NewProtocol6WithError(provider.New("test", true)), + }, + Steps: []resource.TestStep{ + { + Config: testAppDefinition("acctest_app_definition"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", "tf_test1"), + resource.TestMatchResourceAttr(resourceName, "id", regexp.MustCompile(`^[a-zA-Z0-9-_.]{1,64}$`)), + resource.TestMatchResourceAttr(resourceName, "bundle_id", regexp.MustCompile(`^[a-zA-Z0-9-_.]{1,64}$`)), + testAccCheckContentfulAppDefinitionExists(t, resourceName, func(t *testing.T, definition *model.AppDefinition) { + assert.Nil(t, definition.SRC) + assert.EqualValues(t, definition.Name, "tf_test1") + assert.Len(t, definition.Locations, 1) + assert.EqualValues(t, definition.Locations[0].Location, "entry-field") + assert.Len(t, definition.Locations[0].FieldTypes, 1) + assert.EqualValues(t, definition.Locations[0].FieldTypes[0].Type, "Symbol") + assert.NotNil(t, definition.Bundle) + assert.Regexp(t, regexp.MustCompile(`^[a-zA-Z0-9-_.]{1,64}$`), definition.Bundle.Sys.ID) + }), + ), + }, + { + Config: testAppDefinitionUpdateLocation("acctest_app_definition"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", "tf_test1"), + resource.TestMatchResourceAttr(resourceName, "id", regexp.MustCompile(`^[a-zA-Z0-9-_.]{1,64}$`)), + resource.TestMatchResourceAttr(resourceName, "bundle_id", regexp.MustCompile(`^[a-zA-Z0-9-_.]{1,64}$`)), + testAccCheckContentfulAppDefinitionExists(t, resourceName, func(t *testing.T, definition *model.AppDefinition) { + assert.Nil(t, definition.SRC) + assert.EqualValues(t, definition.Name, "tf_test1") + assert.Len(t, definition.Locations, 2) + assert.EqualValues(t, definition.Locations[0].Location, "entry-field") + assert.Len(t, definition.Locations[0].FieldTypes, 1) + assert.EqualValues(t, definition.Locations[1].Location, "dialog") + assert.EqualValues(t, definition.Locations[0].FieldTypes[0].Type, "Symbol") + assert.NotNil(t, definition.Bundle) + assert.Regexp(t, regexp.MustCompile(`^[a-zA-Z0-9-_.]{1,64}$`), definition.Bundle.Sys.ID) + }), + ), + }, + { + Config: testAppDefinitionUpdateToSrc("acctest_app_definition"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", "tf_test1"), + resource.TestCheckResourceAttr(resourceName, "src", "http://localhost"), + resource.TestCheckNoResourceAttr(resourceName, "bundle_id"), + testAccCheckContentfulAppDefinitionExists(t, resourceName, func(t *testing.T, definition *model.AppDefinition) { + assert.Equal(t, *definition.SRC, "http://localhost") + assert.EqualValues(t, definition.Name, "tf_test1") + assert.Len(t, definition.Locations, 1) + assert.EqualValues(t, definition.Locations[0].Location, "entry-field") + assert.Len(t, definition.Locations[0].FieldTypes, 1) + assert.EqualValues(t, definition.Locations[0].FieldTypes[0].Type, "Symbol") + assert.Nil(t, definition.Bundle) + }), + ), + }, + }, + }) +} + +func testAccCheckContentfulAppDefinitionExists(t *testing.T, resourceName string, assertFunc assertFunc) resource.TestCheckFunc { + return func(s *terraform.State) error { + definition, err := getAppDefinitionFromState(s, resourceName) + if err != nil { + return err + } + + assertFunc(t, definition) + return nil + } +} + +func getAppDefinitionFromState(s *terraform.State, resourceName string) (*model.AppDefinition, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return nil, fmt.Errorf("app definition not found") + } + + if rs.Primary.ID == "" { + return nil, fmt.Errorf("no app definition ID found") + } + + client := acctest.GetCMA() + + return client.WithOrganizationId(os.Getenv("CONTENTFUL_ORGANIZATION_ID")).AppDefinitions().Get(context.Background(), rs.Primary.ID) +} + +func testAccCheckContentfulAppDefinitionDestroy(s *terraform.State) error { + client := acctest.GetCMA() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "contentful_app_definition" { + continue + } + + _, err := client.WithOrganizationId(os.Getenv("CONTENTFUL_ORGANIZATION_ID")).AppDefinitions().Get(context.Background(), rs.Primary.ID) + + var notFoundError common.NotFoundError + if errors.As(err, ¬FoundError) { + return nil + } + + return fmt.Errorf("app definition still exists with id: %s", rs.Primary.ID) + } + + return nil +} + +func testAppDefinition(identifier string) string { + return utils.HCLTemplateFromPath("test_resources/create.tf", map[string]any{ + "identifier": identifier, + }) +} + +func testAppDefinitionUpdateLocation(identifier string) string { + return utils.HCLTemplateFromPath("test_resources/update.tf", map[string]any{ + "identifier": identifier, + }) +} + +func testAppDefinitionUpdateToSrc(identifier string) string { + return utils.HCLTemplateFromPath("test_resources/update_to_src.tf", map[string]any{ + "identifier": identifier, + }) +} diff --git a/internal/resources/app_definition/test_resources/create.tf b/internal/resources/app_definition/test_resources/create.tf new file mode 100644 index 0000000..54be05f --- /dev/null +++ b/internal/resources/app_definition/test_resources/create.tf @@ -0,0 +1,5 @@ +resource "contentful_app_definition" "{{ .identifier }}" { + name = "tf_test1" + use_bundle = true + locations = [{ location = "entry-field", "field_types" = [{ "type" = "Symbol" }] }] +} \ No newline at end of file diff --git a/internal/resources/app_definition/test_resources/update.tf b/internal/resources/app_definition/test_resources/update.tf new file mode 100644 index 0000000..227573d --- /dev/null +++ b/internal/resources/app_definition/test_resources/update.tf @@ -0,0 +1,5 @@ +resource "contentful_app_definition" "{{ .identifier }}" { + name = "tf_test1" + use_bundle = true + locations = [{ location = "entry-field", "field_types" = [{ "type" = "Symbol" }] }, { location = "dialog" }] +} \ No newline at end of file diff --git a/internal/resources/app_definition/test_resources/update_to_src.tf b/internal/resources/app_definition/test_resources/update_to_src.tf new file mode 100644 index 0000000..c2ccae7 --- /dev/null +++ b/internal/resources/app_definition/test_resources/update_to_src.tf @@ -0,0 +1,6 @@ +resource "contentful_app_definition" "{{ .identifier }}" { + name = "tf_test1" + use_bundle = false + locations = [{ location = "entry-field", "field_types" = [{ "type" = "Symbol" }] }] + src = "http://localhost" +} \ No newline at end of file diff --git a/internal/resources/app_installation/model.go b/internal/resources/app_installation/model.go new file mode 100644 index 0000000..1e544e7 --- /dev/null +++ b/internal/resources/app_installation/model.go @@ -0,0 +1,69 @@ +package app_installation + +import ( + "encoding/json" + + "github.com/elliotchance/pie/v2" + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/labd/contentful-go/pkgs/model" +) + +// AppInstallation is the main resource schema data +type AppInstallation struct { + ID types.String `tfsdk:"id"` + AppDefinitionID types.String `tfsdk:"app_definition_id"` + SpaceId types.String `tfsdk:"space_id"` + Environment types.String `tfsdk:"environment"` + Parameters jsontypes.Normalized `tfsdk:"parameters"` + AcceptedTerms []types.String `tfsdk:"accepted_terms"` +} + +func (a *AppInstallation) Draft() *model.AppInstallation { + + app := &model.AppInstallation{} + + parameters := make(map[string]any) + + a.Parameters.Unmarshal(¶meters) + + if !a.AppDefinitionID.IsUnknown() || !a.AppDefinitionID.IsNull() { + + appDefinitionSys := struct { + Sys model.BaseSys + }{ + Sys: model.BaseSys{ + ID: a.AppDefinitionID.ValueString(), + }, + } + + app.Sys = &model.AppInstallationSys{AppDefinition: (*struct { + Sys model.BaseSys `json:"sys,omitempty"` + })(&appDefinitionSys)} + } + + app.Terms = pie.Map(a.AcceptedTerms, func(t types.String) string { + return t.ValueString() + }) + app.Parameters = parameters + + return app +} + +func (a *AppInstallation) Equal(n *model.AppInstallation) bool { + + data, _ := json.Marshal(n.Parameters) + + if a.Parameters.ValueString() != string(data) { + return false + } + + return true +} + +func (a *AppInstallation) Import(n *model.AppInstallation) { + a.AppDefinitionID = types.StringValue(n.Sys.AppDefinition.Sys.ID) + a.ID = types.StringValue(n.Sys.AppDefinition.Sys.ID) + data, _ := json.Marshal(n.Parameters) + a.Parameters = jsontypes.NewNormalizedValue(string(data)) +} diff --git a/internal/resources/app_installation/resource.go b/internal/resources/app_installation/resource.go new file mode 100644 index 0000000..ca7ca7d --- /dev/null +++ b/internal/resources/app_installation/resource.go @@ -0,0 +1,238 @@ +package app_installation + +import ( + "context" + _ "embed" + "errors" + "fmt" + "strings" + + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/labd/contentful-go/pkgs/common" + "github.com/labd/contentful-go/pkgs/model" + "github.com/labd/contentful-go/service/cma" + "github.com/labd/terraform-provider-contentful/internal/utils" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &appInstallationResource{} + _ resource.ResourceWithConfigure = &appInstallationResource{} + _ resource.ResourceWithImportState = &appInstallationResource{} +) + +func NewAppInstallationResource() resource.Resource { + return &appInstallationResource{} +} + +// appInstallationResource is the resource implementation. +type appInstallationResource struct { + client cma.SpaceIdClientBuilder + organizationId string +} + +func (e *appInstallationResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = request.ProviderTypeName + "_app_installation" +} + +func (e *appInstallationResource) Schema(_ context.Context, _ resource.SchemaRequest, response *resource.SchemaResponse) { + response.Schema = schema.Schema{ + Description: "Todo for explaining app installation", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + }, + "app_definition_id": schema.StringAttribute{ + Required: true, + Description: "app definition id", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "space_id": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Description: "space id", + }, + "environment": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "parameters": schema.StringAttribute{ + CustomType: jsontypes.NormalizedType{}, + Description: "Parameters needed for the installation of the app in the given space, like credentials or other configuration parameters", + Required: true, + }, + "accepted_terms": schema.ListAttribute{ + Optional: true, + Description: "List of needed terms to accept to install the app", + ElementType: types.StringType, + }, + }, + } +} + +func (e *appInstallationResource) Configure(_ context.Context, request resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if request.ProviderData == nil { + return + } + + data := request.ProviderData.(utils.ProviderData) + e.client = data.CMAClient + e.organizationId = data.OrganizationId + +} + +func (e *appInstallationResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var plan *AppInstallation + response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...) + if response.Diagnostics.HasError() { + return + } + + draft := plan.Draft() + + if err := e.client.WithSpaceId(plan.SpaceId.ValueString()).WithEnvironment(plan.Environment.ValueString()).AppInstallations().Upsert(ctx, draft); err != nil { + var errorResponse common.ErrorResponse + if errors.As(err, &errorResponse) { + if errorResponse.Error() == "Forbidden" { + response.Diagnostics.AddError("Error creating app_installation", fmt.Sprintf("%s: %s", errorResponse.Error(), errorResponse.Details.Reasons)) + } + } + + response.Diagnostics.AddError("Error creating app_installation", err.Error()) + return + } + + plan.ID = plan.AppDefinitionID + + // Set state to fully populated data + response.Diagnostics.Append(response.State.Set(ctx, plan)...) + if response.Diagnostics.HasError() { + return + } +} + +func (e *appInstallationResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + // Get current state + var state *AppInstallation + diags := request.State.Get(ctx, &state) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + e.doRead(ctx, state, &response.State, &response.Diagnostics) +} + +func (e *appInstallationResource) doRead(ctx context.Context, app *AppInstallation, state *tfsdk.State, d *diag.Diagnostics) { + + appDefinition, err := e.getAppInstallation(ctx, app) + if err != nil { + d.AddError( + "Error reading app installation", + "Could not retrieve app installation, unexpected error: "+err.Error(), + ) + return + } + + app.Import(appDefinition) + + // Set refreshed state + d.Append(state.Set(ctx, &app)...) + if d.HasError() { + return + } +} + +func (e *appInstallationResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + // Retrieve values from plan + var plan *AppInstallation + diags := request.Plan.Get(ctx, &plan) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + // Get current state + var state *AppInstallation + diags = request.State.Get(ctx, &state) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + appDefinition, err := e.getAppInstallation(ctx, plan) + if err != nil { + response.Diagnostics.AddError( + "Error reading app installation", + "Could not retrieve app installation, unexpected error: "+err.Error(), + ) + return + } + + if !plan.Equal(appDefinition) { + + draft := plan.Draft() + + if err = e.client.WithSpaceId(state.SpaceId.ValueString()).WithEnvironment(state.Environment.ValueString()).AppInstallations().Upsert(ctx, draft); err != nil { + response.Diagnostics.AddError( + "Error updating app installation", + "Could not update app installation, unexpected error: "+err.Error(), + ) + return + } + } + + e.doRead(ctx, plan, &response.State, &response.Diagnostics) +} + +func (e *appInstallationResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + // Get current state + var state *AppInstallation + response.Diagnostics.Append(request.State.Get(ctx, &state)...) + + if err := e.client.WithSpaceId(state.SpaceId.ValueString()).WithEnvironment(state.Environment.ValueString()).AppInstallations().Delete(ctx, state.Draft()); err != nil { + response.Diagnostics.AddError( + "Error deleting app_installation", + "Could not delete app_installation, unexpected error: "+err.Error(), + ) + return + } + +} + +func (e *appInstallationResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + idParts := strings.SplitN(request.ID, ":", 3) + + if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" { + response.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: appDefinitionID:env:spaceId. Got: %q", request.ID), + ) + return + } + + futureState := &AppInstallation{ + AppDefinitionID: types.StringValue(idParts[0]), + SpaceId: types.StringValue(idParts[2]), + Environment: types.StringValue(idParts[1]), + } + + e.doRead(ctx, futureState, &response.State, &response.Diagnostics) +} + +func (e *appInstallationResource) getAppInstallation(ctx context.Context, app *AppInstallation) (*model.AppInstallation, error) { + return e.client.WithSpaceId(app.SpaceId.ValueString()).WithEnvironment(app.Environment.ValueString()).AppInstallations().Get(ctx, app.AppDefinitionID.ValueString()) +} diff --git a/internal/resources/app_installation/resource_test.go b/internal/resources/app_installation/resource_test.go new file mode 100644 index 0000000..6615e12 --- /dev/null +++ b/internal/resources/app_installation/resource_test.go @@ -0,0 +1,127 @@ +package app_installation_test + +import ( + "context" + "errors" + "fmt" + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/labd/contentful-go/pkgs/common" + "github.com/labd/contentful-go/pkgs/model" + "github.com/labd/terraform-provider-contentful/internal/acctest" + "github.com/labd/terraform-provider-contentful/internal/provider" + "github.com/labd/terraform-provider-contentful/internal/utils" + "github.com/stretchr/testify/assert" +) + +type assertFunc func(*testing.T, *model.AppInstallation) + +func TestAppInstallation_Create(t *testing.T) { + resourceName := "contentful_app_installation.acctest_app_installation" + // merge app + appInstallationId := "cQeaauOu1yUCYVhQ00atE" + //graphql playground + otherId := "66frtrAqmWSowDJzQNDiD" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + CheckDestroy: testAccCheckContentfulAppInstallationDestroy, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "contentful": providerserver.NewProtocol6WithError(provider.New("test", true)), + }, + Steps: []resource.TestStep{ + { + Config: testAppInstallation("acctest_app_installation", os.Getenv("CONTENTFUL_SPACE_ID"), "master", appInstallationId), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "app_definition_id", appInstallationId), + testAccCheckContentfulAppInstallationExists(t, resourceName, func(t *testing.T, appInstallation *model.AppInstallation) { + assert.IsType(t, map[string]any{}, appInstallation.Parameters) + assert.Len(t, appInstallation.Parameters, 0) + assert.EqualValues(t, appInstallationId, appInstallation.Sys.AppDefinition.Sys.ID) + }), + ), + }, + { + Config: testAppInstallationWithParameter("acctest_app_installation_2", os.Getenv("CONTENTFUL_SPACE_ID"), "master", otherId), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("contentful_app_installation.acctest_app_installation_2", "app_definition_id", otherId), + testAccCheckContentfulAppInstallationExists(t, "contentful_app_installation.acctest_app_installation_2", func(t *testing.T, appInstallation *model.AppInstallation) { + assert.IsType(t, map[string]any{}, appInstallation.Parameters) + assert.Len(t, appInstallation.Parameters, 1) + assert.EqualValues(t, "not-working-ever", appInstallation.Parameters["cpaToken"]) + assert.EqualValues(t, otherId, appInstallation.Sys.AppDefinition.Sys.ID) + }), + ), + }, + }, + }) +} + +func getAppInstallationFromState(s *terraform.State, resourceName string) (*model.AppInstallation, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return nil, fmt.Errorf("app installation not found") + } + + if rs.Primary.ID == "" { + return nil, fmt.Errorf("no app installation ID found") + } + + client := acctest.GetCMA() + + return client.WithSpaceId(os.Getenv("CONTENTFUL_SPACE_ID")).WithEnvironment("master").AppInstallations().Get(context.Background(), rs.Primary.ID) +} + +func testAccCheckContentfulAppInstallationExists(t *testing.T, resourceName string, assertFunc assertFunc) resource.TestCheckFunc { + return func(s *terraform.State) error { + result, err := getAppInstallationFromState(s, resourceName) + if err != nil { + return err + } + + assertFunc(t, result) + return nil + } +} + +func testAccCheckContentfulAppInstallationDestroy(s *terraform.State) error { + client := acctest.GetCMA() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "contentful_app_installation" { + continue + } + + _, err := client.WithSpaceId(os.Getenv("CONTENTFUL_SPACE_ID")).WithEnvironment("master").AppInstallations().Get(context.Background(), rs.Primary.ID) + var notFoundError common.NotFoundError + if errors.As(err, ¬FoundError) { + return nil + } + + return fmt.Errorf("app installation still exists with id: %s", rs.Primary.ID) + } + + return nil +} + +func testAppInstallation(identifier string, spaceId string, environment string, appDefinitionId string) string { + return utils.HCLTemplateFromPath("test_resources/without_terms.tf", map[string]any{ + "identifier": identifier, + "spaceId": spaceId, + "environment": environment, + "appDefinitionId": appDefinitionId, + }) +} + +func testAppInstallationWithParameter(identifier string, spaceId string, environment string, appDefinitionId string) string { + return utils.HCLTemplateFromPath("test_resources/with_terms.tf", map[string]any{ + "identifier": identifier, + "spaceId": spaceId, + "environment": environment, + "appDefinitionId": appDefinitionId, + }) +} diff --git a/internal/resources/app_installation/test_resources/with_terms.tf b/internal/resources/app_installation/test_resources/with_terms.tf new file mode 100644 index 0000000..05c56ec --- /dev/null +++ b/internal/resources/app_installation/test_resources/with_terms.tf @@ -0,0 +1,14 @@ +resource "contentful_app_installation" "{{ .identifier }}" { + + space_id = "{{ .spaceId }}" + environment = "{{ .environment }}" + + app_definition_id = "{{ .appDefinitionId }}" + + parameters = jsonencode({ + cpaToken = "not-working-ever" + }) + + accepted_terms = ["i-accept-end-user-license-agreement", "i-accept-marketplace-terms-of-service", "i-accept-privacy-policy"] + +} \ No newline at end of file diff --git a/internal/resources/app_installation/test_resources/without_terms.tf b/internal/resources/app_installation/test_resources/without_terms.tf new file mode 100644 index 0000000..776d44b --- /dev/null +++ b/internal/resources/app_installation/test_resources/without_terms.tf @@ -0,0 +1,10 @@ +resource "contentful_app_installation" "{{ .identifier }}" { + + space_id = "{{ .spaceId }}" + environment = "{{ .environment }}" + + app_definition_id = "{{ .appDefinitionId }}" + + parameters = jsonencode({}) + +} \ No newline at end of file diff --git a/internal/resources/contenttype/model.go b/internal/resources/contenttype/model.go new file mode 100644 index 0000000..66a8213 --- /dev/null +++ b/internal/resources/contenttype/model.go @@ -0,0 +1,866 @@ +package contenttype + +import ( + "encoding/json" + "fmt" + "reflect" + + "github.com/elliotchance/pie/v2" + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/labd/contentful-go" + "github.com/labd/contentful-go/pkgs/model" + "github.com/labd/terraform-provider-contentful/internal/utils" +) + +// ContentType is the main resource schema data +type ContentType struct { + ID types.String `tfsdk:"id"` + SpaceId types.String `tfsdk:"space_id"` + Environment types.String `tfsdk:"environment"` + Name types.String `tfsdk:"name"` + DisplayField types.String `tfsdk:"display_field"` + Description types.String `tfsdk:"description"` + Version types.Int64 `tfsdk:"version"` + VersionControls types.Int64 `tfsdk:"version_controls"` + Fields []Field `tfsdk:"fields"` + ManageFieldControls types.Bool `tfsdk:"manage_field_controls"` + Sidebar []Sidebar `tfsdk:"sidebar"` +} + +type Sidebar struct { + WidgetId types.String `tfsdk:"widget_id"` + WidgetNamespace types.String `tfsdk:"widget_namespace"` + Settings jsontypes.Normalized `tfsdk:"settings"` + Disabled types.Bool `tfsdk:"disabled"` +} + +type Field struct { + Id types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + Type types.String `tfsdk:"type"` + LinkType types.String `tfsdk:"link_type"` + Required types.Bool `tfsdk:"required"` + Localized types.Bool `tfsdk:"localized"` + Disabled types.Bool `tfsdk:"disabled"` + Omitted types.Bool `tfsdk:"omitted"` + Validations []Validation `tfsdk:"validations"` + Items *Items `tfsdk:"items"` + Control *Control `tfsdk:"control"` + DefaultValue *DefaultValue `tfsdk:"default_value"` +} + +type DefaultValue struct { + Bool types.Map `tfsdk:"bool"` + String types.Map `tfsdk:"string"` +} + +func (d *DefaultValue) Draft() map[string]any { + var defaultValues = map[string]any{} + + if !d.String.IsNull() && !d.String.IsUnknown() { + + for k, v := range d.String.Elements() { + defaultValues[k] = v.(types.String).ValueString() + } + } + + if !d.Bool.IsNull() && !d.Bool.IsUnknown() { + + for k, v := range d.Bool.Elements() { + defaultValues[k] = v.(types.Bool).ValueBool() + } + } + + return defaultValues +} + +type Control struct { + WidgetId types.String `tfsdk:"widget_id"` + WidgetNamespace types.String `tfsdk:"widget_namespace"` + Settings *Settings `tfsdk:"settings"` +} + +type Validation struct { + Unique types.Bool `tfsdk:"unique"` + Size *Size `tfsdk:"size"` + Range *Size `tfsdk:"range"` + AssetFileSize *Size `tfsdk:"asset_file_size"` + Regexp *Regexp `tfsdk:"regexp"` + LinkContentType []types.String `tfsdk:"link_content_type"` + LinkMimetypeGroup []types.String `tfsdk:"link_mimetype_group"` + In []types.String `tfsdk:"in"` + EnabledMarks []types.String `tfsdk:"enabled_marks"` + EnabledNodeTypes []types.String `tfsdk:"enabled_node_types"` + Message types.String `tfsdk:"message"` +} + +func (v Validation) Draft() model.FieldValidation { + + if !v.Unique.IsUnknown() && !v.Unique.IsNull() { + return model.FieldValidationUnique{ + Unique: v.Unique.ValueBool(), + } + } + + if v.Size != nil { + return model.FieldValidationSize{ + Size: &model.MinMax{ + Min: v.Size.Min.ValueFloat64Pointer(), + Max: v.Size.Max.ValueFloat64Pointer(), + }, + ErrorMessage: v.Message.ValueStringPointer(), + } + } + + if v.Range != nil { + return model.FieldValidationRange{ + Range: &model.MinMax{ + Min: v.Range.Min.ValueFloat64Pointer(), + Max: v.Range.Max.ValueFloat64Pointer(), + }, + ErrorMessage: v.Message.ValueString(), + } + } + + if v.AssetFileSize != nil { + return model.FieldValidationFileSize{ + Size: &model.MinMax{ + Min: v.AssetFileSize.Min.ValueFloat64Pointer(), + Max: v.AssetFileSize.Max.ValueFloat64Pointer(), + }, + } + } + + if v.Regexp != nil { + return model.FieldValidationRegex{ + Regex: &model.Regex{ + Pattern: v.Regexp.Pattern.ValueString(), + }, + ErrorMessage: v.Message.ValueStringPointer(), + } + } + + if len(v.LinkContentType) > 0 { + return model.FieldValidationLink{ + LinkContentType: pie.Map(v.LinkContentType, func(t types.String) string { + return t.ValueString() + }), + } + } + + if len(v.LinkMimetypeGroup) > 0 { + return model.FieldValidationMimeType{ + MimeTypes: pie.Map(v.LinkMimetypeGroup, func(t types.String) string { + return t.ValueString() + }), + ErrorMessage: v.Message.ValueStringPointer(), + } + } + + if len(v.In) > 0 { + return model.FieldValidationPredefinedValues{ + In: pie.Map(v.In, func(t types.String) any { + return t.ValueString() + }), + } + } + + if len(v.EnabledMarks) > 0 { + return model.FieldValidationEnabledMarks{ + Marks: pie.Map(v.EnabledMarks, func(t types.String) string { + return t.ValueString() + }), + ErrorMessage: v.Message.ValueStringPointer(), + } + } + + if len(v.EnabledNodeTypes) > 0 { + return model.FieldValidationEnabledNodeTypes{ + NodeTypes: pie.Map(v.EnabledNodeTypes, func(t types.String) string { + return t.ValueString() + }), + ErrorMessage: v.Message.ValueStringPointer(), + } + } + + return nil +} + +type Size struct { + Min types.Float64 `tfsdk:"min"` + Max types.Float64 `tfsdk:"max"` +} + +type Regexp struct { + Pattern types.String `tfsdk:"pattern"` +} + +func (f *Field) Equal(n *model.Field) bool { + + if n.Type != f.Type.ValueString() { + return false + } + + if n.ID != f.Id.ValueString() { + return false + } + + if n.Name != f.Name.ValueString() { + return false + } + + if n.LinkType != f.LinkType.ValueString() { + return false + } + + if n.Required != f.Required.ValueBool() { + return false + } + + if n.Omitted != f.Omitted.ValueBool() { + return false + } + + if n.Disabled != f.Disabled.ValueBool() { + return false + } + + if n.Localized != f.Localized.ValueBool() { + return false + } + + if f.Items == nil && n.Items != nil { + return false + } + + if f.Items != nil && !f.Items.Equal(n.Items) { + return false + } + + if len(f.Validations) != len(n.Validations) { + return false + } + + for idx, validation := range pie.Map(f.Validations, func(t Validation) model.FieldValidation { + return t.Draft() + }) { + cfVal := n.Validations[idx] + + if !reflect.DeepEqual(validation, cfVal) { + return false + } + + } + + if f.DefaultValue != nil && !reflect.DeepEqual(f.DefaultValue.Draft(), n.DefaultValue) { + return false + } + + return true +} + +func (f *Field) ToNative() (*model.Field, error) { + + contentfulField := &model.Field{ + ID: f.Id.ValueString(), + Name: f.Name.ValueString(), + Type: f.Type.ValueString(), + Localized: f.Localized.ValueBool(), + Required: f.Required.ValueBool(), + Disabled: f.Disabled.ValueBool(), + Omitted: f.Omitted.ValueBool(), + Validations: pie.Map(f.Validations, func(t Validation) model.FieldValidation { + return t.Draft() + }), + } + + if !f.LinkType.IsNull() && !f.LinkType.IsUnknown() { + contentfulField.LinkType = f.LinkType.ValueString() + } + + if contentfulField.Type == model.FieldTypeArray { + items, errItem := f.Items.ToNative() + + if errItem != nil { + return nil, errItem + } + + contentfulField.Items = items + } + + if f.DefaultValue != nil { + contentfulField.DefaultValue = f.DefaultValue.Draft() + } + + return contentfulField, nil +} + +func getTypeOfMap(mapValues map[string]any) (*string, error) { + for _, v := range mapValues { + switch c := v.(type) { + case string: + t := "string" + return &t, nil + case bool: + t := "bool" + return &t, nil + default: + return nil, fmt.Errorf("The default type %T is not supported by the provider", c) + } + } + + return nil, nil +} + +func (f *Field) Import(n *model.Field, c []contentful.Controls) error { + f.Id = types.StringValue(n.ID) + f.Name = types.StringValue(n.Name) + f.Type = types.StringValue(n.Type) + f.LinkType = utils.FromOptionalString(n.LinkType) + f.Required = types.BoolValue(n.Required) + f.Omitted = types.BoolValue(n.Omitted) + f.Localized = types.BoolValue(n.Localized) + f.Disabled = types.BoolValue(n.Disabled) + + defaultValueType, err := getTypeOfMap(n.DefaultValue) + + if err != nil { + return err + } + + if defaultValueType != nil { + + f.DefaultValue = &DefaultValue{ + Bool: types.MapNull(types.BoolType), + String: types.MapNull(types.StringType), + } + + switch *defaultValueType { + case "string": + stringMap := map[string]attr.Value{} + + for k, v := range n.DefaultValue { + stringMap[k] = types.StringValue(v.(string)) + } + + f.DefaultValue.String = types.MapValueMust(types.StringType, stringMap) + case "bool": + boolMap := map[string]attr.Value{} + + for k, v := range n.DefaultValue { + boolMap[k] = types.BoolValue(v.(bool)) + } + + f.DefaultValue.Bool = types.MapValueMust(types.BoolType, boolMap) + } + + } + + validations, err := getValidations(n.Validations) + + if err != nil { + return err + } + + f.Validations = validations + + if n.Type == model.FieldTypeArray { + + itemValidations, err := getValidations(n.Items.Validations) + + if err != nil { + return err + } + + f.Items = &Items{ + Type: types.StringValue(n.Items.Type), + LinkType: types.StringPointerValue(n.Items.LinkType), + Validations: itemValidations, + } + } + + idx := pie.FindFirstUsing(c, func(control contentful.Controls) bool { + return n.ID == control.FieldID + }) + + if idx != -1 && c[idx].WidgetID != nil { + + var settings *Settings + + if c[idx].Settings != nil { + settings = &Settings{} + + settings.Import(c[idx].Settings) + } + + f.Control = &Control{ + WidgetId: types.StringPointerValue(c[idx].WidgetID), + WidgetNamespace: types.StringPointerValue(c[idx].WidgetNameSpace), + Settings: settings, + } + } + + return nil +} + +type Settings struct { + HelpText types.String `tfsdk:"help_text"` + TrueLabel types.String `tfsdk:"true_label"` + FalseLabel types.String `tfsdk:"false_label"` + Stars types.Int64 `tfsdk:"stars"` + Format types.String `tfsdk:"format"` + TimeFormat types.String `tfsdk:"ampm"` + BulkEditing types.Bool `tfsdk:"bulk_editing"` + TrackingFieldId types.String `tfsdk:"tracking_field_id"` +} + +func (s *Settings) Import(settings *contentful.Settings) { + s.HelpText = types.StringPointerValue(settings.HelpText) + s.TrueLabel = types.StringPointerValue(settings.TrueLabel) + s.FalseLabel = types.StringPointerValue(settings.FalseLabel) + s.Stars = types.Int64PointerValue(settings.Stars) + s.Format = types.StringPointerValue(settings.Format) + s.TimeFormat = types.StringPointerValue(settings.AMPM) + s.BulkEditing = types.BoolPointerValue(settings.BulkEditing) + s.TrackingFieldId = types.StringPointerValue(settings.TrackingFieldId) +} + +func (s *Settings) Draft() *contentful.Settings { + settings := &contentful.Settings{} + + settings.HelpText = s.HelpText.ValueStringPointer() + settings.TrueLabel = s.TrueLabel.ValueStringPointer() + settings.FalseLabel = s.FalseLabel.ValueStringPointer() + settings.Stars = s.Stars.ValueInt64Pointer() + settings.Format = s.Format.ValueStringPointer() + settings.AMPM = s.TimeFormat.ValueStringPointer() + settings.BulkEditing = s.BulkEditing.ValueBoolPointer() + settings.TrackingFieldId = s.TrackingFieldId.ValueStringPointer() + return settings +} + +type Items struct { + Type types.String `tfsdk:"type"` + LinkType types.String `tfsdk:"link_type"` + Validations []Validation `tfsdk:"validations"` +} + +func (i *Items) ToNative() (*model.FieldTypeArrayItem, error) { + + return &model.FieldTypeArrayItem{ + Type: i.Type.ValueString(), + Validations: pie.Map(i.Validations, func(t Validation) model.FieldValidation { + return t.Draft() + }), + LinkType: i.LinkType.ValueStringPointer(), + }, nil +} + +func (i *Items) Equal(n *model.FieldTypeArrayItem) bool { + + if n == nil { + return false + } + + if i.Type.ValueString() != n.Type { + return false + } + + if !utils.CompareStringPointer(i.LinkType, n.LinkType) { + return false + } + + if len(i.Validations) != len(n.Validations) { + return false + } + + for idx, validation := range pie.Map(i.Validations, func(t Validation) model.FieldValidation { + return t.Draft() + }) { + cfVal := n.Validations[idx] + + if !reflect.DeepEqual(validation, cfVal) { + return false + } + + } + + return true +} + +func (c *ContentType) Draft() (*model.ContentType, error) { + + var fields []*model.Field + + for _, field := range c.Fields { + + nativeField, err := field.ToNative() + if err != nil { + return nil, err + } + + fields = append(fields, nativeField) + } + + contentfulType := &model.ContentType{ + Name: c.Name.ValueString(), + DisplayField: c.DisplayField.ValueString(), + Fields: fields, + } + + if !c.ID.IsUnknown() || !c.ID.IsNull() { + contentfulType.Sys = &model.EnvironmentSys{ + SpaceSys: model.SpaceSys{ + CreatedSys: model.CreatedSys{ + BaseSys: model.BaseSys{ + ID: c.ID.ValueString(), + }, + }, + }, + } + } + + if !c.Description.IsNull() && !c.Description.IsUnknown() { + contentfulType.Description = c.Description.ValueStringPointer() + } + + return contentfulType, nil + +} + +func (c *ContentType) Import(n *model.ContentType, e *contentful.EditorInterface) error { + c.ID = types.StringValue(n.Sys.ID) + c.Version = types.Int64Value(int64(n.Sys.Version)) + + c.Description = types.StringPointerValue(n.Description) + + c.Name = types.StringValue(n.Name) + c.DisplayField = types.StringValue(n.DisplayField) + + var fields []Field + + var controls []contentful.Controls + var sidebar []contentful.Sidebar + c.VersionControls = types.Int64Value(0) + if e != nil { + controls = e.Controls + sidebar = e.SideBar + c.VersionControls = types.Int64Value(int64(e.Sys.Version)) + } + + for _, nf := range n.Fields { + field := &Field{} + err := field.Import(nf, controls) + if err != nil { + return err + } + fields = append(fields, *field) + } + + c.Sidebar = pie.Map(sidebar, func(t contentful.Sidebar) Sidebar { + + settings := jsontypes.NewNormalizedValue("{}") + + if t.Settings != nil { + data, _ := json.Marshal(t.Settings) + settings = jsontypes.NewNormalizedValue(string(data)) + } + return Sidebar{ + WidgetId: types.StringValue(t.WidgetID), + WidgetNamespace: types.StringValue(t.WidgetNameSpace), + Settings: settings, + Disabled: types.BoolValue(t.Disabled), + } + }) + + c.Fields = fields + + return nil + +} + +func (c *ContentType) Equal(n *model.ContentType) bool { + + if !utils.CompareStringPointer(c.Description, n.Description) { + return false + } + + if c.Name.ValueString() != n.Name { + return false + } + + if c.DisplayField.ValueString() != n.DisplayField { + return false + } + + if len(c.Fields) != len(n.Fields) { + return false + } + + for idxOrg, field := range c.Fields { + idx := pie.FindFirstUsing(n.Fields, func(f *model.Field) bool { + return f.ID == field.Id.ValueString() + }) + + if idx == -1 { + return false + } + + if !field.Equal(n.Fields[idx]) { + return false + } + + // field was moved, it is the same as before but different position + if idxOrg != idx { + return false + } + } + + return true +} + +func (c *ContentType) EqualEditorInterface(n *contentful.EditorInterface) bool { + + if len(c.Fields) != len(n.Controls) { + return false + } + + filteredControls := pie.Filter(n.Controls, func(c contentful.Controls) bool { + return c.WidgetID != nil || c.WidgetNameSpace != nil || c.Settings != nil + }) + + filteredFields := pie.Filter(c.Fields, func(f Field) bool { + return f.Control != nil + }) + + if len(filteredControls) != len(filteredFields) { + return false + } + + for _, field := range filteredFields { + idx := pie.FindFirstUsing(filteredControls, func(t contentful.Controls) bool { + return t.FieldID == field.Id.ValueString() + }) + + if idx == -1 { + return false + } + control := filteredControls[idx] + + if field.Control.WidgetId.ValueString() != *control.WidgetID { + return false + } + + if field.Control.WidgetNamespace.ValueString() != *control.WidgetNameSpace { + return false + } + + if field.Control.Settings == nil && control.Settings != nil { + return false + } + + if field.Control.Settings != nil && !reflect.DeepEqual(field.Control.Settings.Draft(), control.Settings) { + return false + } + } + + if len(c.Sidebar) != len(n.SideBar) { + return false + } + + for idxOrg, s := range c.Sidebar { + idx := pie.FindFirstUsing(n.SideBar, func(t contentful.Sidebar) bool { + return t.WidgetID == s.WidgetId.ValueString() + }) + + if idx == -1 { + return false + } + + // field was moved, it is the same as before but different position + if idxOrg != idx { + return false + } + + sidebar := n.SideBar[idx] + + if sidebar.Disabled != s.Disabled.ValueBool() { + return false + } + + if sidebar.WidgetID != s.WidgetId.ValueString() { + return false + } + + if sidebar.WidgetNameSpace != s.WidgetNamespace.ValueString() { + return false + } + + a := make(map[string]string) + + s.Settings.Unmarshal(a) + + if !reflect.DeepEqual(sidebar.Settings, a) { + return false + } + } + + return true +} + +func (c *ContentType) DraftEditorInterface(n *contentful.EditorInterface) { + n.Controls = pie.Map(c.Fields, func(field Field) contentful.Controls { + + control := contentful.Controls{ + FieldID: field.Id.ValueString(), + } + + if field.Control != nil { + control.WidgetID = field.Control.WidgetId.ValueStringPointer() + control.WidgetNameSpace = field.Control.WidgetNamespace.ValueStringPointer() + + if field.Control.Settings != nil { + control.Settings = field.Control.Settings.Draft() + } + } + + return control + + }) + + n.SideBar = pie.Map(c.Sidebar, func(t Sidebar) contentful.Sidebar { + + sidebar := contentful.Sidebar{ + WidgetNameSpace: t.WidgetNamespace.ValueString(), + WidgetID: t.WidgetId.ValueString(), + Disabled: t.Disabled.ValueBool(), + } + + if !sidebar.Disabled { + settings := make(map[string]string) + + t.Settings.Unmarshal(settings) + sidebar.Settings = settings + } + + return sidebar + }) +} + +func getValidations(contentfulValidations []model.FieldValidation) ([]Validation, error) { + var validations []Validation + + for _, validation := range contentfulValidations { + + val, err := getValidation(validation) + + if err != nil { + return nil, err + } + + validations = append(validations, *val) + } + + return validations, nil +} + +func getValidation(cfVal model.FieldValidation) (*Validation, error) { + + if v, ok := cfVal.(model.FieldValidationPredefinedValues); ok { + + return &Validation{ + In: pie.Map(v.In, func(t any) types.String { + return types.StringValue(t.(string)) + }), + }, nil + } + + if v, ok := cfVal.(model.FieldValidationUnique); ok { + + return &Validation{ + Unique: types.BoolValue(v.Unique), + }, nil + } + + if v, ok := cfVal.(model.FieldValidationRegex); ok { + + return &Validation{ + Regexp: &Regexp{ + Pattern: types.StringValue(v.Regex.Pattern), + }, + Message: types.StringPointerValue(v.ErrorMessage), + }, nil + } + + if v, ok := cfVal.(model.FieldValidationSize); ok { + + return &Validation{ + Size: &Size{ + Max: types.Float64PointerValue(v.Size.Max), + Min: types.Float64PointerValue(v.Size.Min), + }, + }, nil + } + + if v, ok := cfVal.(model.FieldValidationLink); ok { + + return &Validation{ + LinkContentType: pie.Map(v.LinkContentType, func(t string) types.String { + return types.StringValue(t) + }), + }, nil + } + + if v, ok := cfVal.(model.FieldValidationMimeType); ok { + + return &Validation{ + LinkMimetypeGroup: pie.Map(v.MimeTypes, func(t string) types.String { + return types.StringValue(t) + }), + Message: types.StringPointerValue(v.ErrorMessage), + }, nil + } + + if v, ok := cfVal.(model.FieldValidationRange); ok { + + return &Validation{ + Range: &Size{ + Max: types.Float64PointerValue(v.Range.Max), + Min: types.Float64PointerValue(v.Range.Min), + }, + }, nil + } + + if v, ok := cfVal.(model.FieldValidationEnabledNodeTypes); ok { + + return &Validation{ + EnabledNodeTypes: pie.Map(v.NodeTypes, func(t string) types.String { + return types.StringValue(t) + }), + Message: types.StringPointerValue(v.ErrorMessage), + }, nil + } + + if v, ok := cfVal.(model.FieldValidationEnabledMarks); ok { + + return &Validation{ + EnabledMarks: pie.Map(v.Marks, func(t string) types.String { + return types.StringValue(t) + }), + Message: types.StringPointerValue(v.ErrorMessage), + }, nil + } + + if v, ok := cfVal.(model.FieldValidationFileSize); ok { + return &Validation{ + AssetFileSize: &Size{ + Max: types.Float64PointerValue(v.Size.Max), + Min: types.Float64PointerValue(v.Size.Min), + }, + }, nil + } + + return nil, fmt.Errorf("unsupported validation used, %s. Please implement", reflect.TypeOf(cfVal).String()) +} diff --git a/internal/resources/contenttype/resource.go b/internal/resources/contenttype/resource.go new file mode 100644 index 0000000..7f0e7d0 --- /dev/null +++ b/internal/resources/contenttype/resource.go @@ -0,0 +1,702 @@ +package contenttype + +import ( + "context" + "fmt" + "reflect" + "strings" + + "github.com/elliotchance/pie/v2" + "github.com/hashicorp/terraform-plugin-framework-jsontypes/jsontypes" + "github.com/hashicorp/terraform-plugin-framework-validators/listvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/objectvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/objectplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/labd/contentful-go" + "github.com/labd/contentful-go/pkgs/model" + "github.com/labd/contentful-go/service/cma" + + "github.com/labd/terraform-provider-contentful/internal/custommodifier" + "github.com/labd/terraform-provider-contentful/internal/customvalidator" + "github.com/labd/terraform-provider-contentful/internal/utils" +) + +type key int + +const ( + OnlyControlVersion key = iota +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &contentTypeResource{} + _ resource.ResourceWithConfigure = &contentTypeResource{} + _ resource.ResourceWithImportState = &contentTypeResource{} +) + +func NewContentTypeResource() resource.Resource { + return &contentTypeResource{} +} + +// contentTypeResource is the resource implementation. +type contentTypeResource struct { + client cma.SpaceIdClientBuilder + clientOld *contentful.Client +} + +func (e *contentTypeResource) Metadata(_ context.Context, request resource.MetadataRequest, response *resource.MetadataResponse) { + response.TypeName = request.ProviderTypeName + "_contenttype" +} + +var resourceLinkTypes = []string{"Contentful:Entry"} +var arrayItemTypes = []string{"Symbol", "Link", "ResourceLink"} + +//https://www.contentful.com/developers/docs/extensibility/app-framework/editor-interfaces/ + +func (e *contentTypeResource) Schema(ctx context.Context, request resource.SchemaRequest, response *resource.SchemaResponse) { + + sizeSchema := schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "min": schema.Float64Attribute{ + Optional: true, + }, + "max": schema.Float64Attribute{ + Optional: true, + }, + }, + } + + validationsSchema := schema.ListNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "range": sizeSchema, + "size": sizeSchema, + "unique": schema.BoolAttribute{ + Optional: true, + }, + "asset_file_size": sizeSchema, + "regexp": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "pattern": schema.StringAttribute{ + Optional: true, + }, + }, + }, + "link_mimetype_group": schema.ListAttribute{ + Optional: true, + ElementType: types.StringType, + }, + "link_content_type": schema.ListAttribute{ + Optional: true, + ElementType: types.StringType, + }, + "in": schema.ListAttribute{ + Optional: true, + ElementType: types.StringType, + }, + "enabled_marks": schema.ListAttribute{ + Optional: true, + ElementType: types.StringType, + }, + "enabled_node_types": schema.ListAttribute{ + Optional: true, + ElementType: types.StringType, + }, + "message": schema.StringAttribute{ + Optional: true, + }, + }, + Validators: []validator.Object{ + objectvalidator.AtLeastOneOf(path.MatchRelative().AtName("range")), + }, + }, + } + + widgetIdPath := path.MatchRelative().AtParent().AtParent().AtName("widget_id") + + response.Schema = schema.Schema{ + Description: "Todo for explaining contenttype", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: "content type id", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + stringplanmodifier.RequiresReplace(), + }, + }, + "version": schema.Int64Attribute{ + Computed: true, + }, + "version_controls": schema.Int64Attribute{ + Computed: true, + }, + "space_id": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Description: "space id", + }, + "environment": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "name": schema.StringAttribute{ + Required: true, + }, + "display_field": schema.StringAttribute{ + Required: true, + }, + "description": schema.StringAttribute{ + Optional: true, + }, + "manage_field_controls": schema.BoolAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + custommodifier.BoolDefault(false), + }, + }, + "fields": schema.ListNestedAttribute{ + Validators: []validator.List{ + listvalidator.SizeAtLeast(1), + listvalidator.UniqueValues(), + }, + Required: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Required: true, + }, + "name": schema.StringAttribute{ + Required: true, + }, + "type": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf(utils.GetContentTypes()...), + customvalidator.AttributeNeedsToBeSetValidator(path.MatchRelative().AtParent().AtName("link_type"), "Link"), + customvalidator.AttributeNeedsToBeSetValidator(path.MatchRelative().AtParent().AtName("items"), "Array"), + }, + }, + "link_type": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf(utils.GetLinkTypes()...), + }, + }, + "required": schema.BoolAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + custommodifier.BoolDefault(false), + }, + }, + "localized": schema.BoolAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + custommodifier.BoolDefault(false), + }, + }, + "disabled": schema.BoolAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + custommodifier.BoolDefault(false), + }, + }, + "omitted": schema.BoolAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + custommodifier.BoolDefault(false), + }, + }, + "validations": validationsSchema, + "items": schema.SingleNestedAttribute{ + Optional: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf(arrayItemTypes...), + customvalidator.AttributeNeedsToBeSetValidator(path.MatchRelative().AtParent().AtName("link_type"), "Link"), + }, + }, + "link_type": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf(utils.GetLinkTypes()...), + }, + }, + "validations": validationsSchema, + }, + }, + "default_value": schema.SingleNestedAttribute{ + Optional: true, + Attributes: map[string]schema.Attribute{ + "bool": schema.MapAttribute{ + ElementType: types.BoolType, + Optional: true, + }, + "string": schema.MapAttribute{ + ElementType: types.StringType, + Optional: true, + }, + }, + }, + "control": schema.SingleNestedAttribute{ + Optional: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, + + Attributes: map[string]schema.Attribute{ + "widget_id": schema.StringAttribute{ + Required: true, + }, + "widget_namespace": schema.StringAttribute{ + Required: true, + Validators: []validator.String{ + stringvalidator.OneOf("builtin", "extension", "app"), + }, + }, + "settings": schema.SingleNestedAttribute{ + Attributes: map[string]schema.Attribute{ + "help_text": schema.StringAttribute{ + Optional: true, + }, + "true_label": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + customvalidator.StringAllowedWhenSetValidator(widgetIdPath, "boolean"), + }, + }, + "false_label": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + customvalidator.StringAllowedWhenSetValidator(widgetIdPath, "boolean"), + }, + }, + "stars": schema.Int64Attribute{ + Optional: true, + Validators: []validator.Int64{ + customvalidator.Int64AllowedWhenSetValidator(widgetIdPath, "rating"), + }, + }, + "format": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf("dateonly", "time", "timeZ"), + customvalidator.StringAllowedWhenSetValidator(widgetIdPath, "datepicker"), + }, + }, + "ampm": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.OneOf("12", "24"), + customvalidator.StringAllowedWhenSetValidator(widgetIdPath, "datepicker"), + }, + }, + /** (only for References, many) Select whether to enable Bulk Editing mode */ + "bulk_editing": schema.BoolAttribute{ + Optional: true, + }, + "tracking_field_id": schema.StringAttribute{ + Optional: true, + Validators: []validator.String{ + customvalidator.StringAllowedWhenSetValidator(widgetIdPath, "slugEditor"), + }, + }, + }, + Optional: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.UseStateForUnknown(), + }, + }, + }, + }, + }, + PlanModifiers: []planmodifier.Object{ + custommodifier.FieldTypeChangeProhibited(), + }, + }, + }, + + "sidebar": schema.ListNestedAttribute{ + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "disabled": schema.BoolAttribute{ + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + custommodifier.BoolDefault(false), + }, + }, + "widget_id": schema.StringAttribute{ + Required: true, + }, + "widget_namespace": schema.StringAttribute{ + Required: true, + }, + "settings": schema.StringAttribute{ + CustomType: jsontypes.NormalizedType{}, + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.String{ + custommodifier.StringDefault("{}"), + }, + }, + }, + }, + }, + }, + } +} + +func (e *contentTypeResource) Configure(_ context.Context, request resource.ConfigureRequest, _ *resource.ConfigureResponse) { + if request.ProviderData == nil { + return + } + + data := request.ProviderData.(utils.ProviderData) + e.client = data.CMAClient + e.clientOld = data.ClientOld + +} + +func (e *contentTypeResource) Create(ctx context.Context, request resource.CreateRequest, response *resource.CreateResponse) { + var plan ContentType + response.Diagnostics.Append(request.Plan.Get(ctx, &plan)...) + if response.Diagnostics.HasError() { + return + } + + draft, err := plan.Draft() + + if err != nil { + response.Diagnostics.AddError("Error creating contenttype", err.Error()) + return + } + + envClient := e.client.WithSpaceId(plan.SpaceId.ValueString()).WithEnvironment(plan.Environment.ValueString()) + + if err = envClient.ContentTypes().Upsert(ctx, draft); err != nil { + response.Diagnostics.AddError("Error creating contenttype", err.Error()) + return + } + + if err = envClient.ContentTypes().Activate(ctx, draft); err != nil { + response.Diagnostics.AddError("Error activating contenttype", err.Error()) + return + } + + plan.Version = types.Int64Value(int64(draft.Sys.Version)) + plan.VersionControls = types.Int64Value(0) + plan.ID = types.StringValue(draft.Sys.ID) + + // Set state to fully populated data + response.Diagnostics.Append(response.State.Set(ctx, plan)...) + if response.Diagnostics.HasError() { + return + } +} + +func (e *contentTypeResource) Read(ctx context.Context, request resource.ReadRequest, response *resource.ReadResponse) { + // Get current state + var state *ContentType + diags := request.State.Get(ctx, &state) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + e.doRead(ctx, state, &response.State, &response.Diagnostics) +} + +func (e *contentTypeResource) doRead(ctx context.Context, contentType *ContentType, state *tfsdk.State, d *diag.Diagnostics) { + + contentfulContentType, err := e.getContentType(ctx, contentType) + if err != nil { + d.AddError( + "Error reading contenttype", + "Could not retrieve contenttype, unexpected error: "+err.Error(), + ) + return + } + + var editorInterface *contentful.EditorInterface + + if contentType.ManageFieldControls.ValueBool() { + editorInterface, err = e.getEditorInterface(contentType) + if err != nil { + d.AddError( + "Error reading contenttype", + "Could not retrieve contenttype, unexpected error: "+err.Error(), + ) + return + } + + if u, ok := ctx.Value(OnlyControlVersion).(bool); ok && u { + contentType.VersionControls = types.Int64Value(int64(editorInterface.Sys.Version)) + + ei := &contentful.EditorInterface{} + contentType.DraftEditorInterface(ei) + // remove all controls which are not in the plan for an easier import + editorInterface.Controls = pie.Filter(editorInterface.Controls, func(c contentful.Controls) bool { + return pie.Any(ei.Controls, func(value contentful.Controls) bool { + return value.FieldID == c.FieldID && value.WidgetID != nil && reflect.DeepEqual(value.WidgetID, c.WidgetID) + }) + }) + } + } + + err = contentType.Import(contentfulContentType, editorInterface) + + if err != nil { + d.AddError( + "Error importing contenttype to state", + "Could not import contenttype to state, unexpected error: "+err.Error(), + ) + return + } + + // Set refreshed state + d.Append(state.Set(ctx, &contentType)...) + if d.HasError() { + return + } +} + +func (e *contentTypeResource) Update(ctx context.Context, request resource.UpdateRequest, response *resource.UpdateResponse) { + // Retrieve values from plan + var plan *ContentType + diags := request.Plan.Get(ctx, &plan) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + // Get current state + var state *ContentType + diags = request.State.Get(ctx, &state) + response.Diagnostics.Append(diags...) + if response.Diagnostics.HasError() { + return + } + + contentfulContentType, err := e.getContentType(ctx, plan) + if err != nil { + response.Diagnostics.AddError( + "Error reading contenttype", + "Could not retrieve contenttype, unexpected error: "+err.Error(), + ) + return + } + + deletedFields := pie.Of(pie.FilterNot(contentfulContentType.Fields, func(cf *model.Field) bool { + return pie.FindFirstUsing(plan.Fields, func(f Field) bool { + return cf.ID == f.Id.ValueString() + }) != -1 + + })).Map(func(f *model.Field) *model.Field { + f.Omitted = true + + return f + }).Result + + draft, err := plan.Draft() + + if err != nil { + response.Diagnostics.AddError("Error updating contenttype", err.Error()) + return + } + + draft.Sys = contentfulContentType.Sys + + if len(deletedFields) > 0 { + draft.Fields = append(draft.Fields, deletedFields...) + } + + // To remove a field from a content type 4 API calls need to be made. + // Omit the removed fields and publish the new version of the content type, + // followed by the field removal and final publish. + + if !plan.Equal(contentfulContentType) { + err = e.doUpdate(ctx, plan, draft) + if err != nil { + response.Diagnostics.AddError( + "Error updating contenttype", + "Could not update contenttype, unexpected error: "+err.Error(), + ) + return + } + + if len(deletedFields) > 0 { + sys := draft.Sys + draft, err = plan.Draft() + + if err != nil { + response.Diagnostics.AddError("Error updating contenttype", err.Error()) + return + } + + draft.Sys = sys + + err = e.doUpdate(ctx, plan, draft) + if err != nil { + response.Diagnostics.AddError( + "Error updating contenttype", + "Could not update contenttype, unexpected error: "+err.Error(), + ) + return + } + } + } + + ctxControls := e.updateEditorInterface(ctx, state, plan, &response.Diagnostics) + + e.doRead(ctxControls, plan, &response.State, &response.Diagnostics) +} + +func (e *contentTypeResource) updateEditorInterface(ctx context.Context, state *ContentType, plan *ContentType, d *diag.Diagnostics) context.Context { + if plan.ManageFieldControls.ValueBool() { + // first import of editorInterface to the state just get the editorInterface version + if state.VersionControls.IsNull() { + return context.WithValue(ctx, OnlyControlVersion, true) + } + + editorInterface, err := e.getEditorInterface(plan) + if err != nil { + d.AddError( + "Error reading contenttype", + "Could not retrieve contenttype editorInterface, unexpected error: "+err.Error(), + ) + return ctx + } + + if !plan.EqualEditorInterface(editorInterface) { + + plan.DraftEditorInterface(editorInterface) + e.clientOld.SetEnvironment(plan.Environment.ValueString()) + + if err = e.clientOld.EditorInterfaces.Update(plan.SpaceId.ValueString(), plan.ID.ValueString(), editorInterface); err != nil { + d.AddError( + "Error updating contenttype editorInterface", + "Could not update contenttype editorInterface, unexpected error: "+err.Error(), + ) + + return ctx + } + } + + } + + return ctx +} + +func (e *contentTypeResource) doUpdate(ctx context.Context, plan *ContentType, draft *model.ContentType) error { + envClient := e.client.WithSpaceId(plan.SpaceId.ValueString()).WithEnvironment(plan.Environment.ValueString()) + + if err := envClient.ContentTypes().Upsert(ctx, draft); err != nil { + return err + } + + return envClient.ContentTypes().Activate(ctx, draft) +} + +func (e *contentTypeResource) Delete(ctx context.Context, request resource.DeleteRequest, response *resource.DeleteResponse) { + // Get current state + var state *ContentType + response.Diagnostics.Append(request.State.Get(ctx, &state)...) + + contentfulContentType, err := e.getContentType(ctx, state) + + if err != nil { + response.Diagnostics.AddError( + "Error reading contenttype", + "Could not retrieve contenttype, unexpected error: "+err.Error(), + ) + return + } + + err = e.doDelete(ctx, state, contentfulContentType) + if err != nil { + response.Diagnostics.AddError( + "Error deleting contenttype", + "Could not delete contenttype, unexpected error: "+err.Error(), + ) + return + } + +} + +func (e *contentTypeResource) doDelete(ctx context.Context, data *ContentType, draft *model.ContentType) error { + envClient := e.client.WithSpaceId(data.SpaceId.ValueString()).WithEnvironment(data.Environment.ValueString()) + + if err := envClient.ContentTypes().Deactivate(ctx, draft); err != nil { + return err + } + + return envClient.ContentTypes().Delete(ctx, draft) +} + +func (e *contentTypeResource) ImportState(ctx context.Context, request resource.ImportStateRequest, response *resource.ImportStateResponse) { + idParts := strings.SplitN(request.ID, ":", 3) + + if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" { + response.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: localeId:env:spaceId. Got: %q", request.ID), + ) + return + } + + futureState := &ContentType{ + ID: types.StringValue(idParts[0]), + SpaceId: types.StringValue(idParts[2]), + Environment: types.StringValue(idParts[1]), + } + + e.doRead(ctx, futureState, &response.State, &response.Diagnostics) +} + +func (e *contentTypeResource) getContentType(ctx context.Context, editor *ContentType) (*model.ContentType, error) { + envClient := e.client.WithSpaceId(editor.SpaceId.ValueString()).WithEnvironment(editor.Environment.ValueString()) + + return envClient.ContentTypes().Get(ctx, editor.ID.ValueString()) +} + +func (e *contentTypeResource) getEditorInterface(editor *ContentType) (*contentful.EditorInterface, error) { + + env := &contentful.Environment{Sys: &contentful.Sys{ + ID: editor.Environment.ValueString(), + Space: &contentful.Space{ + Sys: &contentful.Sys{ID: editor.SpaceId.ValueString()}, + }, + }} + + return e.clientOld.EditorInterfaces.GetWithEnv(env, editor.ID.ValueString()) + +} diff --git a/internal/resources/contenttype/resource_test.go b/internal/resources/contenttype/resource_test.go new file mode 100644 index 0000000..a9f78e8 --- /dev/null +++ b/internal/resources/contenttype/resource_test.go @@ -0,0 +1,387 @@ +package contenttype_test + +import ( + "context" + "errors" + "fmt" + "os" + "regexp" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/labd/contentful-go" + "github.com/labd/contentful-go/pkgs/common" + "github.com/labd/contentful-go/pkgs/model" + "github.com/labd/terraform-provider-contentful/internal/acctest" + "github.com/labd/terraform-provider-contentful/internal/provider" + "github.com/labd/terraform-provider-contentful/internal/utils" + "github.com/stretchr/testify/assert" + + "testing" +) + +type assertFunc func(*testing.T, *model.ContentType) +type assertEditorInterfaceFunc func(*testing.T, *contentful.EditorInterface) + +func TestContentTypeResource_Create(t *testing.T) { + resourceName := "contentful_contenttype.acctest_content_type" + linkedResourceName := "contentful_contenttype.linked_content_type" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + CheckDestroy: testAccCheckContentfulContentTypeDestroy, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "contentful": providerserver.NewProtocol6WithError(provider.New("test", false)), + }, + Steps: []resource.TestStep{ + { + Config: testContentType("acctest_content_type", os.Getenv("CONTENTFUL_SPACE_ID")), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", "tf_test1"), + resource.TestCheckResourceAttr(resourceName, "id", "tf_test1"), + resource.TestCheckResourceAttr(resourceName, "version", "2"), + resource.TestCheckResourceAttr(resourceName, "version_controls", "0"), + testAccCheckContentfulContentTypeExists(t, resourceName, func(t *testing.T, contentType *model.ContentType) { + assert.EqualValues(t, "tf_test1", contentType.Name) + assert.Equal(t, 2, contentType.Sys.Version) + assert.EqualValues(t, "tf_test1", contentType.Sys.ID) + assert.EqualValues(t, "none", *contentType.Description) + assert.EqualValues(t, "field1", contentType.DisplayField) + assert.Len(t, contentType.Fields, 2) + assert.Equal(t, &model.Field{ + ID: "field1", + Name: "Field 1 name change", + Type: "Text", + LinkType: "", + Items: nil, + Required: true, + Localized: false, + Disabled: false, + Omitted: false, + Validations: nil, + DefaultValue: nil, + }, contentType.Fields[0]) + assert.Equal(t, &model.Field{ + ID: "field3", + Name: "Field 3 new field", + Type: "Integer", + LinkType: "", + Items: nil, + Required: true, + Localized: false, + Disabled: false, + Omitted: false, + Validations: nil, + DefaultValue: nil, + }, contentType.Fields[1]) + }), + ), + }, + { + Config: testContentTypeUpdateWithDifferentOrderOfFields("acctest_content_type", os.Getenv("CONTENTFUL_SPACE_ID")), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", "tf_test1"), + resource.TestCheckResourceAttr(resourceName, "version", "4"), + resource.TestCheckResourceAttr(resourceName, "version_controls", "0"), + testAccCheckContentfulContentTypeExists(t, resourceName, func(t *testing.T, contentType *model.ContentType) { + assert.EqualValues(t, "tf_test1", contentType.Name) + assert.Equal(t, 4, contentType.Sys.Version) + assert.EqualValues(t, "tf_test1", contentType.Sys.ID) + assert.EqualValues(t, "Terraform Acc Test Content Type description change", *contentType.Description) + assert.EqualValues(t, "field1", contentType.DisplayField) + assert.Len(t, contentType.Fields, 2) + assert.Equal(t, &model.Field{ + ID: "field1", + Name: "Field 1 name change", + Type: "Text", + LinkType: "", + Required: true, + Localized: false, + Disabled: false, + Omitted: false, + }, contentType.Fields[1]) + assert.Equal(t, &model.Field{ + ID: "field3", + Name: "Field 3 new field", + Type: "Integer", + LinkType: "", + Required: true, + Localized: false, + Disabled: false, + Omitted: false, + }, contentType.Fields[0]) + }), + ), + }, + { + Config: testContentTypeUpdate("acctest_content_type", os.Getenv("CONTENTFUL_SPACE_ID")), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", "tf_test1"), + resource.TestCheckResourceAttr(resourceName, "version", "6"), + resource.TestCheckResourceAttr(resourceName, "version_controls", "4"), + testAccCheckContentfulContentTypeExists(t, resourceName, func(t *testing.T, contentType *model.ContentType) { + assert.EqualValues(t, "tf_test1", contentType.Name) + assert.Equal(t, 6, contentType.Sys.Version) + assert.EqualValues(t, "tf_test1", contentType.Sys.ID) + assert.EqualValues(t, "Terraform Acc Test Content Type description change", *contentType.Description) + assert.EqualValues(t, "field1", contentType.DisplayField) + assert.Len(t, contentType.Fields, 2) + assert.Equal(t, &model.Field{ + ID: "field1", + Name: "Field 1 name change", + Type: "Text", + LinkType: "", + Required: true, + Localized: false, + Disabled: false, + Omitted: false, + }, contentType.Fields[0]) + assert.Equal(t, &model.Field{ + ID: "field3", + Name: "Field 3 new field", + Type: "Integer", + LinkType: "", + Required: true, + Localized: false, + Disabled: false, + Omitted: false, + }, contentType.Fields[1]) + }), + testAccCheckEditorInterfaceExists(t, "tf_test1", func(t *testing.T, editorInterface *contentful.EditorInterface) { + assert.Len(t, editorInterface.Controls, 2) + assert.Equal(t, contentful.Controls{ + FieldID: "field1", + }, editorInterface.Controls[0]) + assert.Equal(t, contentful.Controls{ + FieldID: "field3", + WidgetNameSpace: toPointer("builtin"), + WidgetID: toPointer("numberEditor"), + Settings: &contentful.Settings{ + BulkEditing: toPointer(true), + HelpText: toPointer("blabla"), + }, + }, editorInterface.Controls[1]) + }), + ), + }, + { + Config: testContentTypeLinkConfig("acctest_content_type", os.Getenv("CONTENTFUL_SPACE_ID"), "linked_content_type"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(linkedResourceName, "name", "tf_linked"), + testAccCheckContentfulContentTypeExists(t, linkedResourceName, func(t *testing.T, contentType *model.ContentType) { + assert.EqualValues(t, "tf_linked", contentType.Name) + assert.Equal(t, 2, contentType.Sys.Version) + assert.EqualValues(t, "tf_linked", contentType.Sys.ID) + assert.EqualValues(t, "Terraform Acc Test Content Type with links", *contentType.Description) + assert.EqualValues(t, "asset_field", contentType.DisplayField) + assert.Len(t, contentType.Fields, 2) + assert.Equal(t, &model.Field{ + ID: "asset_field", + Name: "Asset Field", + Type: "Array", + LinkType: "", + Items: &model.FieldTypeArrayItem{ + Type: "Link", + LinkType: toPointer("Asset"), + }, + Required: true, + Localized: false, + Disabled: false, + Omitted: false, + }, contentType.Fields[0]) + assert.Equal(t, &model.Field{ + ID: "entry_link_field", + Name: "Entry Link Field", + Type: "Link", + LinkType: "Entry", + Required: false, + Localized: false, + Disabled: false, + Omitted: false, + Validations: []model.FieldValidation{ + model.FieldValidationLink{ + LinkContentType: []string{"tf_test1"}, + }, + }, + }, contentType.Fields[1]) + }), + ), + }, + { + Config: testContentTypeWithId("acctest_content_type", os.Getenv("CONTENTFUL_SPACE_ID")), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", "tf_test1"), + resource.TestCheckResourceAttr(resourceName, "id", "tf_test2"), + testAccCheckContentfulContentTypeExists(t, resourceName, func(t *testing.T, contentType *model.ContentType) { + assert.EqualValues(t, "tf_test1", contentType.Name) + assert.Equal(t, 2, contentType.Sys.Version) + assert.EqualValues(t, "tf_test2", contentType.Sys.ID) + assert.EqualValues(t, "Terraform Acc Test Content Type description change", *contentType.Description) + assert.EqualValues(t, "field1", contentType.DisplayField) + assert.Len(t, contentType.Fields, 2) + assert.Equal(t, &model.Field{ + ID: "field1", + Name: "Field 1 name change", + Type: "Text", + LinkType: "", + Required: true, + Localized: false, + Disabled: false, + Omitted: false, + }, contentType.Fields[0]) + assert.Equal(t, &model.Field{ + ID: "field3", + Name: "Field 3 new field", + Type: "Integer", + LinkType: "", + Required: true, + Localized: false, + Disabled: false, + Omitted: false, + }, contentType.Fields[1]) + }), + ), + }, + }, + }) +} + +func TestContentTypeResource_WithDuplicateField(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acctest.TestAccPreCheck(t) }, + CheckDestroy: testAccCheckContentfulContentTypeDestroy, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "contentful": providerserver.NewProtocol6WithError(provider.New("test", false)), + }, + Steps: []resource.TestStep{ + { + Config: testContentTypeDuplicateFields("acctest_content_type", os.Getenv("CONTENTFUL_SPACE_ID")), + PlanOnly: true, + ExpectError: regexp.MustCompile("Error: Duplicate List Value"), + }, + }, + }) +} + +func getContentTypeFromState(s *terraform.State, resourceName string) (*model.ContentType, error) { + rs, ok := s.RootModule().Resources[resourceName] + if !ok { + return nil, fmt.Errorf("content type not found") + } + + if rs.Primary.ID == "" { + return nil, fmt.Errorf("no content type ID found") + } + + client := acctest.GetCMA() + + return client.WithSpaceId(os.Getenv("CONTENTFUL_SPACE_ID")).WithEnvironment("master").ContentTypes().Get(context.Background(), rs.Primary.ID) +} + +func getEditorInterfaceFromState(id string) (*contentful.EditorInterface, error) { + client := acctest.GetClient() + + return client.EditorInterfaces.Get(os.Getenv("CONTENTFUL_SPACE_ID"), id) +} + +func testAccCheckContentfulContentTypeExists(t *testing.T, resourceName string, assertFunc assertFunc) resource.TestCheckFunc { + return func(s *terraform.State) error { + result, err := getContentTypeFromState(s, resourceName) + if err != nil { + return err + } + + assertFunc(t, result) + return nil + } +} + +func testAccCheckEditorInterfaceExists(t *testing.T, id string, assertFunc assertEditorInterfaceFunc) resource.TestCheckFunc { + return func(s *terraform.State) error { + result, err := getEditorInterfaceFromState(id) + if err != nil { + return err + } + + assertFunc(t, result) + return nil + } +} + +func testAccCheckContentfulContentTypeDestroy(s *terraform.State) (err error) { + client := acctest.GetCMA() + + for _, rs := range s.RootModule().Resources { + if rs.Type != "contentful_contenttype" { + continue + } + + spaceID := rs.Primary.Attributes["space_id"] + if spaceID == "" { + return fmt.Errorf("no space_id is set") + } + + _, err := client.WithSpaceId(spaceID).WithEnvironment("master").ContentTypes().Get(context.Background(), rs.Primary.ID) + var notFoundError common.NotFoundError + if errors.As(err, ¬FoundError) { + return nil + } + + return fmt.Errorf("content type still exists with id: %s", rs.Primary.ID) + } + + return nil +} + +func testContentType(identifier string, spaceId string) string { + return utils.HCLTemplateFromPath("test_resources/create.tf", map[string]any{ + "identifier": identifier, + "spaceId": spaceId, + "id_definition": "", + "desc": "none", + }) +} + +func testContentTypeWithId(identifier string, spaceId string) string { + return utils.HCLTemplateFromPath("test_resources/create.tf", map[string]any{ + "identifier": identifier, + "spaceId": spaceId, + "id_definition": "id = \"tf_test2\"", + "desc": "Terraform Acc Test Content Type description change", + }) +} + +func testContentTypeUpdateWithDifferentOrderOfFields(identifier string, spaceId string) string { + return utils.HCLTemplateFromPath("test_resources/changed_order.tf", map[string]any{ + "identifier": identifier, + "spaceId": spaceId, + }) +} + +func testContentTypeUpdate(identifier string, spaceId string) string { + return utils.HCLTemplateFromPath("test_resources/update.tf", map[string]any{ + "identifier": identifier, + "spaceId": spaceId, + }) +} + +func testContentTypeDuplicateFields(identifier string, spaceId string) string { + return utils.HCLTemplateFromPath("test_resources/update_duplicate_field.tf", map[string]any{ + "identifier": identifier, + "spaceId": spaceId, + }) +} + +func testContentTypeLinkConfig(identifier string, spaceId string, linkIdentifier string) string { + return utils.HCLTemplateFromPath("test_resources/link_config.tf", map[string]any{ + "identifier": identifier, + "linkIdentifier": linkIdentifier, + "spaceId": spaceId, + }) + +} + +func toPointer[T string | bool](value T) *T { + return &value +} diff --git a/internal/resources/contenttype/test_resources/changed_order.tf b/internal/resources/contenttype/test_resources/changed_order.tf new file mode 100644 index 0000000..b84f2d7 --- /dev/null +++ b/internal/resources/contenttype/test_resources/changed_order.tf @@ -0,0 +1,18 @@ +resource "contentful_contenttype" "{{ .identifier }}" { + space_id = "{{ .spaceId }}" + environment = "master" + name = "tf_test1" + description = "Terraform Acc Test Content Type description change" + display_field = "field1" + fields = [{ + id = "field3" + name = "Field 3 new field" + required = true + type = "Integer" + }, { + id = "field1" + name = "Field 1 name change" + required = true + type = "Text" + }] +} \ No newline at end of file diff --git a/internal/resources/contenttype/test_resources/create.tf b/internal/resources/contenttype/test_resources/create.tf new file mode 100644 index 0000000..60d27a2 --- /dev/null +++ b/internal/resources/contenttype/test_resources/create.tf @@ -0,0 +1,19 @@ +resource "contentful_contenttype" "{{ .identifier }}" { + space_id = "{{ .spaceId }}" + environment = "master" +{{.id_definition}} + name = "tf_test1" + description = "{{.desc}}" + display_field = "field1" + fields = [{ + id = "field1" + name = "Field 1 name change" + required = true + type = "Text" + }, { + id = "field3" + name = "Field 3 new field" + required = true + type = "Integer" + }] +} \ No newline at end of file diff --git a/internal/resources/contenttype/test_resources/link_config.tf b/internal/resources/contenttype/test_resources/link_config.tf new file mode 100644 index 0000000..8212a92 --- /dev/null +++ b/internal/resources/contenttype/test_resources/link_config.tf @@ -0,0 +1,53 @@ +resource "contentful_contenttype" "{{ .identifier }}" { + space_id = "{{ .spaceId }}" + environment = "master" + name = "tf_test1" + description = "Terraform Acc Test Content Type description change" + display_field = "field1" + manage_field_controls = true + fields = [{ + id = "field1" + name = "Field 1 name change" + required = true + type = "Text" + }, { + id = "field3" + name = "Field 3 new field" + required = true + type = "Integer" + control = { + widget_id = "numberEditor" + widget_namespace = "builtin" + settings = { + help_text = "blabla" + bulk_editing = false + } + } + }] +} + +resource "contentful_contenttype" "{{ .linkIdentifier }}" { + space_id = "{{ .spaceId }}" + name = "tf_linked" + environment = "master" + description = "Terraform Acc Test Content Type with links" + display_field = "asset_field" + fields =[{ + id = "asset_field" + name = "Asset Field" + type = "Array" + items = { + type = "Link" + link_type = "Asset" + } + required = true + },{ + id = "entry_link_field" + name = "Entry Link Field" + type = "Link" + link_type = "Entry" + validations = [{ + link_content_type = [contentful_contenttype.{{ .identifier }}.id ] + }] + }] +} \ No newline at end of file diff --git a/internal/resources/contenttype/test_resources/update.tf b/internal/resources/contenttype/test_resources/update.tf new file mode 100644 index 0000000..2da5d2a --- /dev/null +++ b/internal/resources/contenttype/test_resources/update.tf @@ -0,0 +1,27 @@ +resource "contentful_contenttype" "{{ .identifier }}" { + space_id = "{{ .spaceId }}" + environment = "master" + name = "tf_test1" + description = "Terraform Acc Test Content Type description change" + display_field = "field1" + manage_field_controls = true + fields = [{ + id = "field1" + name = "Field 1 name change" + required = true + type = "Text" + }, { + id = "field3" + name = "Field 3 new field" + required = true + type = "Integer" + control = { + widget_id = "numberEditor" + widget_namespace = "builtin" + settings = { + help_text = "blabla" + bulk_editing = true + } + } + }] +} \ No newline at end of file diff --git a/internal/resources/contenttype/test_resources/update_duplicate_field.tf b/internal/resources/contenttype/test_resources/update_duplicate_field.tf new file mode 100644 index 0000000..4ea677b --- /dev/null +++ b/internal/resources/contenttype/test_resources/update_duplicate_field.tf @@ -0,0 +1,34 @@ +resource "contentful_contenttype" "{{ .identifier }}" { + space_id = "{{ .spaceId }}" + environment = "master" + name = "tf_test1" + description = "Terraform Acc Test Content Type description change" + display_field = "field1" + manage_field_controls = true + fields = [{ + id = "field1" + name = "Field 1 name change" + required = true + type = "Text" + }, + { + id = "field1" + name = "Field 1 name change" + required = true + type = "Text" + }, + { + id = "field3" + name = "Field 3 new field" + required = true + type = "Integer" + control = { + widget_id = "numberEditor" + widget_namespace = "builtin" + settings = { + help_text = "blabla" + bulk_editing = true + } + }, + }] +} \ No newline at end of file diff --git a/internal/sdk/main.gen.go b/internal/sdk/main.gen.go new file mode 100644 index 0000000..1180f44 --- /dev/null +++ b/internal/sdk/main.gen.go @@ -0,0 +1,5575 @@ +// Package sdk provides primitives to interact with the openapi HTTP API. +// +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT. +package sdk + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "github.com/oapi-codegen/runtime" +) + +const ( + BearerAuthScopes = "bearerAuth.Scopes" +) + +// Defines values for AssetCollectionSysType. +const ( + AssetCollectionSysTypeArray AssetCollectionSysType = "Array" +) + +// Defines values for ContentTypeCollectionSysType. +const ( + ContentTypeCollectionSysTypeArray ContentTypeCollectionSysType = "Array" +) + +// Defines values for EntryCollectionSysType. +const ( + EntryCollectionSysTypeArray EntryCollectionSysType = "Array" +) + +// Defines values for EnvironmentCollectionSysType. +const ( + EnvironmentCollectionSysTypeArray EnvironmentCollectionSysType = "Array" +) + +// Defines values for EnvironmentSystemPropertiesSysLinkType. +const ( + EnvironmentSystemPropertiesSysLinkTypeEnvironment EnvironmentSystemPropertiesSysLinkType = "Environment" +) + +// Defines values for EnvironmentSystemPropertiesSysType. +const ( + EnvironmentSystemPropertiesSysTypeLink EnvironmentSystemPropertiesSysType = "Link" +) + +// Defines values for FieldLinkType. +const ( + FieldLinkTypeAsset FieldLinkType = "Asset" + FieldLinkTypeEntry FieldLinkType = "Entry" +) + +// Defines values for FieldType. +const ( + FieldTypeArray FieldType = "Array" + FieldTypeBoolean FieldType = "Boolean" + FieldTypeDate FieldType = "Date" + FieldTypeInteger FieldType = "Integer" + FieldTypeLink FieldType = "Link" + FieldTypeLocation FieldType = "Location" + FieldTypeNumber FieldType = "Number" + FieldTypeObject FieldType = "Object" + FieldTypeSymbol FieldType = "Symbol" + FieldTypeText FieldType = "Text" +) + +// Defines values for LocaleCollectionSysType. +const ( + LocaleCollectionSysTypeArray LocaleCollectionSysType = "Array" +) + +// Defines values for SpaceCollectionSysType. +const ( + SpaceCollectionSysTypeArray SpaceCollectionSysType = "Array" +) + +// Defines values for SystemPropertiesContentTypeSysLinkType. +const ( + SystemPropertiesContentTypeSysLinkTypeContentType SystemPropertiesContentTypeSysLinkType = "ContentType" +) + +// Defines values for SystemPropertiesContentTypeSysType. +const ( + SystemPropertiesContentTypeSysTypeLink SystemPropertiesContentTypeSysType = "Link" +) + +// Defines values for SystemPropertiesEnvironmentSysLinkType. +const ( + SystemPropertiesEnvironmentSysLinkTypeEnvironment SystemPropertiesEnvironmentSysLinkType = "Environment" +) + +// Defines values for SystemPropertiesEnvironmentSysType. +const ( + SystemPropertiesEnvironmentSysTypeLink SystemPropertiesEnvironmentSysType = "Link" +) + +// Defines values for WebhookCollectionSysType. +const ( + Array WebhookCollectionSysType = "Array" +) + +// Asset defines model for Asset. +type Asset struct { + Fields *struct { + // Description Asset description by locale + Description *map[string]string `json:"description,omitempty"` + + // File Asset file details by locale + File *map[string]struct { + ContentType *string `json:"contentType,omitempty"` + Details *struct { + Image *struct { + Height *int `json:"height,omitempty"` + Width *int `json:"width,omitempty"` + } `json:"image,omitempty"` + Size *int `json:"size,omitempty"` + } `json:"details,omitempty"` + FileName *string `json:"fileName,omitempty"` + Url *string `json:"url,omitempty"` + } `json:"file,omitempty"` + + // Title Asset title by locale + Title *map[string]string `json:"title,omitempty"` + } `json:"fields,omitempty"` + Sys *SystemProperties `json:"sys,omitempty"` +} + +// AssetCollection defines model for AssetCollection. +type AssetCollection struct { + Items *[]Asset `json:"items,omitempty"` + + // Limit Maximum number of assets returned + Limit *int `json:"limit,omitempty"` + + // Skip Number of assets skipped + Skip *int `json:"skip,omitempty"` + Sys *struct { + Type *AssetCollectionSysType `json:"type,omitempty"` + } `json:"sys,omitempty"` + + // Total Total number of assets + Total *int `json:"total,omitempty"` +} + +// AssetCollectionSysType defines model for AssetCollection.Sys.Type. +type AssetCollectionSysType string + +// AssetCreate defines model for AssetCreate. +type AssetCreate struct { + Fields *struct { + // Description Asset description by locale + Description *map[string]string `json:"description,omitempty"` + + // File Asset file details by locale + File *map[string]struct { + ContentType *string `json:"contentType,omitempty"` + FileName *string `json:"fileName,omitempty"` + + // Upload Upload URL + Upload *string `json:"upload,omitempty"` + } `json:"file,omitempty"` + + // Title Asset title by locale + Title *map[string]string `json:"title,omitempty"` + } `json:"fields,omitempty"` +} + +// ContentType defines model for ContentType. +type ContentType struct { + // Description Description of the content type + Description *string `json:"description,omitempty"` + + // DisplayField ID of the field to use as the display field + DisplayField *string `json:"displayField,omitempty"` + Fields *[]Field `json:"fields,omitempty"` + + // Name Name of the content type + Name *string `json:"name,omitempty"` + Sys *SystemProperties `json:"sys,omitempty"` +} + +// ContentTypeCollection defines model for ContentTypeCollection. +type ContentTypeCollection struct { + Items *[]ContentType `json:"items,omitempty"` + + // Limit Maximum number of content types returned + Limit *int `json:"limit,omitempty"` + + // Skip Number of content types skipped + Skip *int `json:"skip,omitempty"` + Sys *struct { + Type *ContentTypeCollectionSysType `json:"type,omitempty"` + } `json:"sys,omitempty"` + + // Total Total number of content types + Total *int `json:"total,omitempty"` +} + +// ContentTypeCollectionSysType defines model for ContentTypeCollection.Sys.Type. +type ContentTypeCollectionSysType string + +// ContentTypeCreate defines model for ContentTypeCreate. +type ContentTypeCreate struct { + // Description Description of the content type + Description *string `json:"description,omitempty"` + + // DisplayField ID of the field to use as the display field + DisplayField *string `json:"displayField,omitempty"` + Fields []Field `json:"fields"` + + // Name Name of the content type + Name string `json:"name"` +} + +// Entry defines model for Entry. +type Entry struct { + // Fields Content fields with values by locale + Fields *map[string]interface{} `json:"fields,omitempty"` + Sys *SystemProperties `json:"sys,omitempty"` +} + +// EntryCollection defines model for EntryCollection. +type EntryCollection struct { + Items *[]Entry `json:"items,omitempty"` + + // Limit Maximum number of entries returned + Limit *int `json:"limit,omitempty"` + + // Skip Number of entries skipped + Skip *int `json:"skip,omitempty"` + Sys *struct { + Type *EntryCollectionSysType `json:"type,omitempty"` + } `json:"sys,omitempty"` + + // Total Total number of entries + Total *int `json:"total,omitempty"` +} + +// EntryCollectionSysType defines model for EntryCollection.Sys.Type. +type EntryCollectionSysType string + +// EntryCreate defines model for EntryCreate. +type EntryCreate struct { + // Fields Content fields with values by locale + Fields *map[string]interface{} `json:"fields,omitempty"` +} + +// EntryUpdate defines model for EntryUpdate. +type EntryUpdate struct { + // Fields Content fields with values by locale + Fields *map[string]interface{} `json:"fields,omitempty"` +} + +// Environment defines model for Environment. +type Environment struct { + // Name Name of the environment + Name *string `json:"name,omitempty"` + Sys *SystemProperties `json:"sys,omitempty"` +} + +// EnvironmentCollection defines model for EnvironmentCollection. +type EnvironmentCollection struct { + Items *[]Environment `json:"items,omitempty"` + + // Limit Maximum number of environments returned + Limit *int `json:"limit,omitempty"` + + // Skip Number of environments skipped + Skip *int `json:"skip,omitempty"` + Sys *struct { + Type *EnvironmentCollectionSysType `json:"type,omitempty"` + } `json:"sys,omitempty"` + + // Total Total number of environments + Total *int `json:"total,omitempty"` +} + +// EnvironmentCollectionSysType defines model for EnvironmentCollection.Sys.Type. +type EnvironmentCollectionSysType string + +// EnvironmentCreate defines model for EnvironmentCreate. +type EnvironmentCreate struct { + // Name Name of the environment to create + Name string `json:"name"` +} + +// EnvironmentSystemProperties defines model for EnvironmentSystemProperties. +type EnvironmentSystemProperties struct { + Sys *struct { + // Id Environment ID + Id *string `json:"id,omitempty"` + LinkType *EnvironmentSystemPropertiesSysLinkType `json:"linkType,omitempty"` + Type *EnvironmentSystemPropertiesSysType `json:"type,omitempty"` + } `json:"sys,omitempty"` +} + +// EnvironmentSystemPropertiesSysLinkType defines model for EnvironmentSystemProperties.Sys.LinkType. +type EnvironmentSystemPropertiesSysLinkType string + +// EnvironmentSystemPropertiesSysType defines model for EnvironmentSystemProperties.Sys.Type. +type EnvironmentSystemPropertiesSysType string + +// EnvironmentUpdate defines model for EnvironmentUpdate. +type EnvironmentUpdate struct { + // Name Updated name for the environment + Name string `json:"name"` +} + +// Field defines model for Field. +type Field struct { + // Id ID of the field + Id string `json:"id"` + + // Items Used for Array fields to define the items type + Items *map[string]interface{} `json:"items,omitempty"` + + // LinkType For Link fields, defines what type of resource it links to + LinkType *FieldLinkType `json:"linkType,omitempty"` + + // Localized Whether the field is localized + Localized *bool `json:"localized,omitempty"` + + // Name Name of the field + Name string `json:"name"` + + // Required Whether the field is required + Required *bool `json:"required,omitempty"` + + // Type Type of the field + Type FieldType `json:"type"` +} + +// FieldLinkType For Link fields, defines what type of resource it links to +type FieldLinkType string + +// FieldType Type of the field +type FieldType string + +// Locale defines model for Locale. +type Locale struct { + // Code Locale code (e.g., en-US, de-DE) + Code *string `json:"code,omitempty"` + + // ContentDeliveryApi Whether this locale is available in the content delivery API + ContentDeliveryApi *bool `json:"contentDeliveryApi,omitempty"` + + // ContentManagementApi Whether this locale is available in the content management API + ContentManagementApi *bool `json:"contentManagementApi,omitempty"` + + // Default Whether this is the default locale + Default *bool `json:"default,omitempty"` + + // FallbackCode Code of the locale to use as a fallback + FallbackCode *string `json:"fallbackCode,omitempty"` + + // Name Human readable name of the locale + Name *string `json:"name,omitempty"` + + // Optional Whether this locale is optional for content + Optional *bool `json:"optional,omitempty"` + Sys *SystemProperties `json:"sys,omitempty"` +} + +// LocaleCollection defines model for LocaleCollection. +type LocaleCollection struct { + Items *[]Locale `json:"items,omitempty"` + + // Limit Maximum number of locales returned + Limit *int `json:"limit,omitempty"` + + // Skip Number of locales skipped + Skip *int `json:"skip,omitempty"` + Sys *struct { + Type *LocaleCollectionSysType `json:"type,omitempty"` + } `json:"sys,omitempty"` + + // Total Total number of locales + Total *int `json:"total,omitempty"` +} + +// LocaleCollectionSysType defines model for LocaleCollection.Sys.Type. +type LocaleCollectionSysType string + +// LocaleCreate defines model for LocaleCreate. +type LocaleCreate struct { + // Code Locale code (e.g., en-US, de-DE) + Code string `json:"code"` + + // ContentDeliveryApi Whether this locale is available in the content delivery API + ContentDeliveryApi *bool `json:"contentDeliveryApi,omitempty"` + + // ContentManagementApi Whether this locale is available in the content management API + ContentManagementApi *bool `json:"contentManagementApi,omitempty"` + + // Default Whether this is the default locale + Default *bool `json:"default,omitempty"` + + // FallbackCode Code of the locale to use as a fallback + FallbackCode *string `json:"fallbackCode"` + + // Name Human readable name of the locale + Name string `json:"name"` + + // Optional Whether this locale is optional for content + Optional *bool `json:"optional,omitempty"` +} + +// LocaleUpdate defines model for LocaleUpdate. +type LocaleUpdate struct { + // Code Locale code (e.g., en-US, de-DE) + Code string `json:"code"` + + // ContentDeliveryApi Whether this locale is available in the content delivery API + ContentDeliveryApi *bool `json:"contentDeliveryApi,omitempty"` + + // ContentManagementApi Whether this locale is available in the content management API + ContentManagementApi *bool `json:"contentManagementApi,omitempty"` + + // FallbackCode Code of the locale to use as a fallback + FallbackCode *string `json:"fallbackCode"` + + // Name Human readable name of the locale + Name string `json:"name"` + + // Optional Whether this locale is optional for content + Optional *bool `json:"optional,omitempty"` +} + +// Space defines model for Space. +type Space struct { + // DefaultLocale Default locale of the space + DefaultLocale *string `json:"defaultLocale,omitempty"` + + // Name Name of the space + Name *string `json:"name,omitempty"` + Sys *SystemProperties `json:"sys,omitempty"` +} + +// SpaceCollection defines model for SpaceCollection. +type SpaceCollection struct { + Items *[]Space `json:"items,omitempty"` + + // Limit Maximum number of spaces returned + Limit *int `json:"limit,omitempty"` + + // Skip Number of spaces skipped + Skip *int `json:"skip,omitempty"` + Sys *struct { + Type *SpaceCollectionSysType `json:"type,omitempty"` + } `json:"sys,omitempty"` + + // Total Total number of spaces + Total *int `json:"total,omitempty"` +} + +// SpaceCollectionSysType defines model for SpaceCollection.Sys.Type. +type SpaceCollectionSysType string + +// SpaceCreate defines model for SpaceCreate. +type SpaceCreate struct { + // DefaultLocale Default locale for the space + DefaultLocale *string `json:"defaultLocale,omitempty"` + + // Name Name of the space to create + Name string `json:"name"` +} + +// SpaceUpdate defines model for SpaceUpdate. +type SpaceUpdate struct { + // Name Updated name for the space + Name string `json:"name"` +} + +// SystemProperties defines model for SystemProperties. +type SystemProperties struct { + // ArchivedAt Archival timestamp + ArchivedAt *time.Time `json:"archivedAt,omitempty"` + ContentType *struct { + Sys *struct { + // Id Content type ID + Id *string `json:"id,omitempty"` + LinkType *SystemPropertiesContentTypeSysLinkType `json:"linkType,omitempty"` + Type *SystemPropertiesContentTypeSysType `json:"type,omitempty"` + } `json:"sys,omitempty"` + } `json:"contentType,omitempty"` + + // CreatedAt Creation timestamp + CreatedAt *time.Time `json:"createdAt,omitempty"` + Environment *struct { + Sys *struct { + // Id Environment ID + Id *string `json:"id,omitempty"` + LinkType *SystemPropertiesEnvironmentSysLinkType `json:"linkType,omitempty"` + Type *SystemPropertiesEnvironmentSysType `json:"type,omitempty"` + } `json:"sys,omitempty"` + } `json:"environment,omitempty"` + + // Id Resource ID + Id *string `json:"id,omitempty"` + + // PublishedAt Publication timestamp + PublishedAt *time.Time `json:"publishedAt,omitempty"` + Space *EnvironmentSystemProperties `json:"space,omitempty"` + + // Type Resource type + Type *string `json:"type,omitempty"` + + // UpdatedAt Last update timestamp + UpdatedAt *time.Time `json:"updatedAt,omitempty"` + + // Version Resource version + Version *int `json:"version,omitempty"` +} + +// SystemPropertiesContentTypeSysLinkType defines model for SystemProperties.ContentType.Sys.LinkType. +type SystemPropertiesContentTypeSysLinkType string + +// SystemPropertiesContentTypeSysType defines model for SystemProperties.ContentType.Sys.Type. +type SystemPropertiesContentTypeSysType string + +// SystemPropertiesEnvironmentSysLinkType defines model for SystemProperties.Environment.Sys.LinkType. +type SystemPropertiesEnvironmentSysLinkType string + +// SystemPropertiesEnvironmentSysType defines model for SystemProperties.Environment.Sys.Type. +type SystemPropertiesEnvironmentSysType string + +// Webhook defines model for Webhook. +type Webhook struct { + // Headers HTTP headers to send with the webhook request + Headers []WebhookHeader `json:"headers"` + + // HttpBasicPassword Password for HTTP basic authentication + HttpBasicPassword *string `json:"httpBasicPassword,omitempty"` + + // HttpBasicUsername Username for HTTP basic authentication + HttpBasicUsername *string `json:"httpBasicUsername,omitempty"` + + // Name Name of the webhook + Name string `json:"name"` + Sys *SystemProperties `json:"sys,omitempty"` + + // Topics Events that trigger the webhook + Topics []string `json:"topics"` + + // Url URL to call when the webhook is triggered + Url string `json:"url"` +} + +// WebhookCollection defines model for WebhookCollection. +type WebhookCollection struct { + Items *[]Webhook `json:"items,omitempty"` + + // Limit Maximum number of webhooks returned + Limit *int `json:"limit,omitempty"` + + // Skip Number of webhooks skipped + Skip *int `json:"skip,omitempty"` + Sys *struct { + Type *WebhookCollectionSysType `json:"type,omitempty"` + } `json:"sys,omitempty"` + + // Total Total number of webhooks + Total *int `json:"total,omitempty"` +} + +// WebhookCollectionSysType defines model for WebhookCollection.Sys.Type. +type WebhookCollectionSysType string + +// WebhookCreate defines model for WebhookCreate. +type WebhookCreate struct { + // Headers HTTP headers to send with the webhook request + Headers *[]WebhookHeader `json:"headers,omitempty"` + + // HttpBasicPassword Password for HTTP basic authentication + HttpBasicPassword *string `json:"httpBasicPassword,omitempty"` + + // HttpBasicUsername Username for HTTP basic authentication + HttpBasicUsername *string `json:"httpBasicUsername,omitempty"` + + // Name Name of the webhook + Name string `json:"name"` + + // Topics Events that trigger the webhook + Topics []string `json:"topics"` + + // Url URL to call when the webhook is triggered + Url string `json:"url"` +} + +// WebhookHeader defines model for WebhookHeader. +type WebhookHeader struct { + Key string `json:"key"` + Value string `json:"value"` +} + +// WebhookUpdate defines model for WebhookUpdate. +type WebhookUpdate struct { + // Headers HTTP headers to send with the webhook request + Headers *[]WebhookHeader `json:"headers,omitempty"` + + // HttpBasicPassword Password for HTTP basic authentication + HttpBasicPassword *string `json:"httpBasicPassword,omitempty"` + + // HttpBasicUsername Username for HTTP basic authentication + HttpBasicUsername *string `json:"httpBasicUsername,omitempty"` + + // Name Name of the webhook + Name string `json:"name"` + + // Topics Events that trigger the webhook + Topics []string `json:"topics"` + + // Url URL to call when the webhook is triggered + Url string `json:"url"` +} + +// ContentTypeHeader defines model for contentTypeHeader. +type ContentTypeHeader = string + +// EntryId defines model for entryId. +type EntryId = string + +// EnvironmentId defines model for environmentId. +type EnvironmentId = string + +// Limit defines model for limit. +type Limit = int + +// LocaleId defines model for localeId. +type LocaleId = string + +// ResourceVersion defines model for resourceVersion. +type ResourceVersion = int + +// Skip defines model for skip. +type Skip = int + +// SpaceId defines model for spaceId. +type SpaceId = string + +// WebhookId defines model for webhookId. +type WebhookId = string + +// GetAllSpacesParams defines parameters for GetAllSpaces. +type GetAllSpacesParams struct { + // Limit Maximum number of items to return + Limit *Limit `form:"limit,omitempty" json:"limit,omitempty"` + + // Skip Number of items to skip + Skip *Skip `form:"skip,omitempty" json:"skip,omitempty"` +} + +// CreateSpaceParams defines parameters for CreateSpace. +type CreateSpaceParams struct { + // ContentType Contentful Management API version header + ContentType ContentTypeHeader `json:"Content-Type"` +} + +// DeleteSpaceParams defines parameters for DeleteSpace. +type DeleteSpaceParams struct { + // XContentfulVersion The version of the locale to update. + XContentfulVersion ResourceVersion `json:"X-Contentful-Version"` +} + +// UpdateSpaceParams defines parameters for UpdateSpace. +type UpdateSpaceParams struct { + // XContentfulVersion The version of the locale to update. + XContentfulVersion ResourceVersion `json:"X-Contentful-Version"` +} + +// GetAllEnvironmentsParams defines parameters for GetAllEnvironments. +type GetAllEnvironmentsParams struct { + // Limit Maximum number of items to return + Limit *Limit `form:"limit,omitempty" json:"limit,omitempty"` + + // Skip Number of items to skip + Skip *Skip `form:"skip,omitempty" json:"skip,omitempty"` +} + +// DeleteEnvironmentParams defines parameters for DeleteEnvironment. +type DeleteEnvironmentParams struct { + // XContentfulVersion The version of the locale to update. + XContentfulVersion ResourceVersion `json:"X-Contentful-Version"` +} + +// UpdateEnvironmentParams defines parameters for UpdateEnvironment. +type UpdateEnvironmentParams struct { + // XContentfulVersion The version of the locale to update. + XContentfulVersion ResourceVersion `json:"X-Contentful-Version"` +} + +// GetAllAssetsParams defines parameters for GetAllAssets. +type GetAllAssetsParams struct { + // Limit Maximum number of items to return + Limit *Limit `form:"limit,omitempty" json:"limit,omitempty"` + + // Skip Number of items to skip + Skip *Skip `form:"skip,omitempty" json:"skip,omitempty"` +} + +// GetAllContentTypesParams defines parameters for GetAllContentTypes. +type GetAllContentTypesParams struct { + // Limit Maximum number of items to return + Limit *Limit `form:"limit,omitempty" json:"limit,omitempty"` + + // Skip Number of items to skip + Skip *Skip `form:"skip,omitempty" json:"skip,omitempty"` +} + +// GetAllEntriesParams defines parameters for GetAllEntries. +type GetAllEntriesParams struct { + // Limit Maximum number of items to return + Limit *Limit `form:"limit,omitempty" json:"limit,omitempty"` + + // Skip Number of items to skip + Skip *Skip `form:"skip,omitempty" json:"skip,omitempty"` + + // ContentType Filter by content type + ContentType *string `form:"content_type,omitempty" json:"content_type,omitempty"` +} + +// CreateEntryParams defines parameters for CreateEntry. +type CreateEntryParams struct { + // ContentType Content type ID for the new entry + ContentType string `form:"content_type" json:"content_type"` +} + +// DeleteEntryParams defines parameters for DeleteEntry. +type DeleteEntryParams struct { + // XContentfulVersion The version of the locale to update. + XContentfulVersion ResourceVersion `json:"X-Contentful-Version"` +} + +// UpdateEntryParams defines parameters for UpdateEntry. +type UpdateEntryParams struct { + // XContentfulVersion The version of the locale to update. + XContentfulVersion ResourceVersion `json:"X-Contentful-Version"` +} + +// GetAllLocalesParams defines parameters for GetAllLocales. +type GetAllLocalesParams struct { + // Limit Maximum number of items to return + Limit *Limit `form:"limit,omitempty" json:"limit,omitempty"` + + // Skip Number of items to skip + Skip *Skip `form:"skip,omitempty" json:"skip,omitempty"` +} + +// UpdateLocaleParams defines parameters for UpdateLocale. +type UpdateLocaleParams struct { + // XContentfulVersion The version of the locale to update. + XContentfulVersion ResourceVersion `json:"X-Contentful-Version"` +} + +// GetAllWebhooksParams defines parameters for GetAllWebhooks. +type GetAllWebhooksParams struct { + // Limit Maximum number of items to return + Limit *Limit `form:"limit,omitempty" json:"limit,omitempty"` + + // Skip Number of items to skip + Skip *Skip `form:"skip,omitempty" json:"skip,omitempty"` +} + +// DeleteWebhookParams defines parameters for DeleteWebhook. +type DeleteWebhookParams struct { + // XContentfulVersion The version of the locale to update. + XContentfulVersion ResourceVersion `json:"X-Contentful-Version"` +} + +// UpdateWebhookParams defines parameters for UpdateWebhook. +type UpdateWebhookParams struct { + // XContentfulVersion The version of the locale to update. + XContentfulVersion ResourceVersion `json:"X-Contentful-Version"` +} + +// CreateSpaceJSONRequestBody defines body for CreateSpace for application/json ContentType. +type CreateSpaceJSONRequestBody = SpaceCreate + +// UpdateSpaceJSONRequestBody defines body for UpdateSpace for application/json ContentType. +type UpdateSpaceJSONRequestBody = SpaceUpdate + +// CreateEnvironmentJSONRequestBody defines body for CreateEnvironment for application/json ContentType. +type CreateEnvironmentJSONRequestBody = EnvironmentCreate + +// UpdateEnvironmentJSONRequestBody defines body for UpdateEnvironment for application/json ContentType. +type UpdateEnvironmentJSONRequestBody = EnvironmentUpdate + +// CreateAssetJSONRequestBody defines body for CreateAsset for application/json ContentType. +type CreateAssetJSONRequestBody = AssetCreate + +// CreateContentTypeJSONRequestBody defines body for CreateContentType for application/json ContentType. +type CreateContentTypeJSONRequestBody = ContentTypeCreate + +// CreateEntryJSONRequestBody defines body for CreateEntry for application/json ContentType. +type CreateEntryJSONRequestBody = EntryCreate + +// UpdateEntryJSONRequestBody defines body for UpdateEntry for application/json ContentType. +type UpdateEntryJSONRequestBody = EntryUpdate + +// CreateLocaleJSONRequestBody defines body for CreateLocale for application/json ContentType. +type CreateLocaleJSONRequestBody = LocaleCreate + +// UpdateLocaleJSONRequestBody defines body for UpdateLocale for application/json ContentType. +type UpdateLocaleJSONRequestBody = LocaleUpdate + +// CreateWebhookJSONRequestBody defines body for CreateWebhook for application/json ContentType. +type CreateWebhookJSONRequestBody = WebhookCreate + +// UpdateWebhookJSONRequestBody defines body for UpdateWebhook for application/json ContentType. +type UpdateWebhookJSONRequestBody = WebhookUpdate + +// RequestEditorFn is the function signature for the RequestEditor callback function +type RequestEditorFn func(ctx context.Context, req *http.Request) error + +// Doer performs HTTP requests. +// +// The standard http.Client implements this interface. +type HttpRequestDoer interface { + Do(req *http.Request) (*http.Response, error) +} + +// Client which conforms to the OpenAPI3 specification for this service. +type Client struct { + // The endpoint of the server conforming to this interface, with scheme, + // https://api.deepmap.com for example. This can contain a path relative + // to the server, such as https://api.deepmap.com/dev-test, and all the + // paths in the swagger spec will be appended to the server. + Server string + + // Doer for performing requests, typically a *http.Client with any + // customized settings, such as certificate chains. + Client HttpRequestDoer + + // A list of callbacks for modifying requests which are generated before sending over + // the network. + RequestEditors []RequestEditorFn +} + +// ClientOption allows setting custom parameters during construction +type ClientOption func(*Client) error + +// Creates a new Client, with reasonable defaults +func NewClient(server string, opts ...ClientOption) (*Client, error) { + // create a client with sane default values + client := Client{ + Server: server, + } + // mutate client and add all optional params + for _, o := range opts { + if err := o(&client); err != nil { + return nil, err + } + } + // ensure the server URL always has a trailing slash + if !strings.HasSuffix(client.Server, "/") { + client.Server += "/" + } + // create httpClient, if not already present + if client.Client == nil { + client.Client = &http.Client{} + } + return &client, nil +} + +// WithHTTPClient allows overriding the default Doer, which is +// automatically created using http.Client. This is useful for tests. +func WithHTTPClient(doer HttpRequestDoer) ClientOption { + return func(c *Client) error { + c.Client = doer + return nil + } +} + +// WithRequestEditorFn allows setting up a callback function, which will be +// called right before sending the request. This can be used to mutate the request. +func WithRequestEditorFn(fn RequestEditorFn) ClientOption { + return func(c *Client) error { + c.RequestEditors = append(c.RequestEditors, fn) + return nil + } +} + +// The interface specification for the client above. +type ClientInterface interface { + // GetAllSpaces request + GetAllSpaces(ctx context.Context, params *GetAllSpacesParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateSpaceWithBody request with any body + CreateSpaceWithBody(ctx context.Context, params *CreateSpaceParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateSpace(ctx context.Context, params *CreateSpaceParams, body CreateSpaceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteSpace request + DeleteSpace(ctx context.Context, spaceId SpaceId, params *DeleteSpaceParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetSpace request + GetSpace(ctx context.Context, spaceId SpaceId, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateSpaceWithBody request with any body + UpdateSpaceWithBody(ctx context.Context, spaceId SpaceId, params *UpdateSpaceParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateSpace(ctx context.Context, spaceId SpaceId, params *UpdateSpaceParams, body UpdateSpaceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetAllEnvironments request + GetAllEnvironments(ctx context.Context, spaceId SpaceId, params *GetAllEnvironmentsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateEnvironmentWithBody request with any body + CreateEnvironmentWithBody(ctx context.Context, spaceId SpaceId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateEnvironment(ctx context.Context, spaceId SpaceId, body CreateEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteEnvironment request + DeleteEnvironment(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *DeleteEnvironmentParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetEnvironment request + GetEnvironment(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateEnvironmentWithBody request with any body + UpdateEnvironmentWithBody(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *UpdateEnvironmentParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateEnvironment(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *UpdateEnvironmentParams, body UpdateEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetAllAssets request + GetAllAssets(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *GetAllAssetsParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateAssetWithBody request with any body + CreateAssetWithBody(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateAsset(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, body CreateAssetJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetAllContentTypes request + GetAllContentTypes(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *GetAllContentTypesParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateContentTypeWithBody request with any body + CreateContentTypeWithBody(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateContentType(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, body CreateContentTypeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetAllEntries request + GetAllEntries(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *GetAllEntriesParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateEntryWithBody request with any body + CreateEntryWithBody(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *CreateEntryParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateEntry(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *CreateEntryParams, body CreateEntryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteEntry request + DeleteEntry(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, params *DeleteEntryParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetEntry request + GetEntry(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateEntryWithBody request with any body + UpdateEntryWithBody(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, params *UpdateEntryParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateEntry(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, params *UpdateEntryParams, body UpdateEntryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UnarchiveEntry request + UnarchiveEntry(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*http.Response, error) + + // ArchiveEntry request + ArchiveEntry(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UnpublishEntry request + UnpublishEntry(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*http.Response, error) + + // PublishEntry request + PublishEntry(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetAllLocales request + GetAllLocales(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *GetAllLocalesParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateLocaleWithBody request with any body + CreateLocaleWithBody(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateLocale(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, body CreateLocaleJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteLocale request + DeleteLocale(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetLocale request + GetLocale(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateLocaleWithBody request with any body + UpdateLocaleWithBody(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId, params *UpdateLocaleParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateLocale(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId, params *UpdateLocaleParams, body UpdateLocaleJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetAllWebhooks request + GetAllWebhooks(ctx context.Context, spaceId SpaceId, params *GetAllWebhooksParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // CreateWebhookWithBody request with any body + CreateWebhookWithBody(ctx context.Context, spaceId SpaceId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + CreateWebhook(ctx context.Context, spaceId SpaceId, body CreateWebhookJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) + + // DeleteWebhook request + DeleteWebhook(ctx context.Context, spaceId SpaceId, webhookId WebhookId, params *DeleteWebhookParams, reqEditors ...RequestEditorFn) (*http.Response, error) + + // GetWebhook request + GetWebhook(ctx context.Context, spaceId SpaceId, webhookId WebhookId, reqEditors ...RequestEditorFn) (*http.Response, error) + + // UpdateWebhookWithBody request with any body + UpdateWebhookWithBody(ctx context.Context, spaceId SpaceId, webhookId WebhookId, params *UpdateWebhookParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) + + UpdateWebhook(ctx context.Context, spaceId SpaceId, webhookId WebhookId, params *UpdateWebhookParams, body UpdateWebhookJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) +} + +func (c *Client) GetAllSpaces(ctx context.Context, params *GetAllSpacesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetAllSpacesRequest(c.Server, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateSpaceWithBody(ctx context.Context, params *CreateSpaceParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateSpaceRequestWithBody(c.Server, params, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateSpace(ctx context.Context, params *CreateSpaceParams, body CreateSpaceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateSpaceRequest(c.Server, params, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteSpace(ctx context.Context, spaceId SpaceId, params *DeleteSpaceParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteSpaceRequest(c.Server, spaceId, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetSpace(ctx context.Context, spaceId SpaceId, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetSpaceRequest(c.Server, spaceId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateSpaceWithBody(ctx context.Context, spaceId SpaceId, params *UpdateSpaceParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateSpaceRequestWithBody(c.Server, spaceId, params, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateSpace(ctx context.Context, spaceId SpaceId, params *UpdateSpaceParams, body UpdateSpaceJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateSpaceRequest(c.Server, spaceId, params, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetAllEnvironments(ctx context.Context, spaceId SpaceId, params *GetAllEnvironmentsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetAllEnvironmentsRequest(c.Server, spaceId, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateEnvironmentWithBody(ctx context.Context, spaceId SpaceId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateEnvironmentRequestWithBody(c.Server, spaceId, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateEnvironment(ctx context.Context, spaceId SpaceId, body CreateEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateEnvironmentRequest(c.Server, spaceId, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteEnvironment(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *DeleteEnvironmentParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteEnvironmentRequest(c.Server, spaceId, environmentId, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetEnvironment(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetEnvironmentRequest(c.Server, spaceId, environmentId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateEnvironmentWithBody(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *UpdateEnvironmentParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateEnvironmentRequestWithBody(c.Server, spaceId, environmentId, params, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateEnvironment(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *UpdateEnvironmentParams, body UpdateEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateEnvironmentRequest(c.Server, spaceId, environmentId, params, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetAllAssets(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *GetAllAssetsParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetAllAssetsRequest(c.Server, spaceId, environmentId, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateAssetWithBody(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateAssetRequestWithBody(c.Server, spaceId, environmentId, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateAsset(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, body CreateAssetJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateAssetRequest(c.Server, spaceId, environmentId, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetAllContentTypes(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *GetAllContentTypesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetAllContentTypesRequest(c.Server, spaceId, environmentId, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateContentTypeWithBody(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateContentTypeRequestWithBody(c.Server, spaceId, environmentId, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateContentType(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, body CreateContentTypeJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateContentTypeRequest(c.Server, spaceId, environmentId, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetAllEntries(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *GetAllEntriesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetAllEntriesRequest(c.Server, spaceId, environmentId, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateEntryWithBody(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *CreateEntryParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateEntryRequestWithBody(c.Server, spaceId, environmentId, params, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateEntry(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *CreateEntryParams, body CreateEntryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateEntryRequest(c.Server, spaceId, environmentId, params, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteEntry(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, params *DeleteEntryParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteEntryRequest(c.Server, spaceId, environmentId, entryId, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetEntry(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetEntryRequest(c.Server, spaceId, environmentId, entryId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateEntryWithBody(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, params *UpdateEntryParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateEntryRequestWithBody(c.Server, spaceId, environmentId, entryId, params, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateEntry(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, params *UpdateEntryParams, body UpdateEntryJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateEntryRequest(c.Server, spaceId, environmentId, entryId, params, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UnarchiveEntry(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUnarchiveEntryRequest(c.Server, spaceId, environmentId, entryId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) ArchiveEntry(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewArchiveEntryRequest(c.Server, spaceId, environmentId, entryId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UnpublishEntry(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUnpublishEntryRequest(c.Server, spaceId, environmentId, entryId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) PublishEntry(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewPublishEntryRequest(c.Server, spaceId, environmentId, entryId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetAllLocales(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *GetAllLocalesParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetAllLocalesRequest(c.Server, spaceId, environmentId, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateLocaleWithBody(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateLocaleRequestWithBody(c.Server, spaceId, environmentId, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateLocale(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, body CreateLocaleJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateLocaleRequest(c.Server, spaceId, environmentId, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteLocale(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteLocaleRequest(c.Server, spaceId, environmentId, localeId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetLocale(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetLocaleRequest(c.Server, spaceId, environmentId, localeId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateLocaleWithBody(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId, params *UpdateLocaleParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateLocaleRequestWithBody(c.Server, spaceId, environmentId, localeId, params, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateLocale(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId, params *UpdateLocaleParams, body UpdateLocaleJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateLocaleRequest(c.Server, spaceId, environmentId, localeId, params, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetAllWebhooks(ctx context.Context, spaceId SpaceId, params *GetAllWebhooksParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetAllWebhooksRequest(c.Server, spaceId, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateWebhookWithBody(ctx context.Context, spaceId SpaceId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateWebhookRequestWithBody(c.Server, spaceId, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) CreateWebhook(ctx context.Context, spaceId SpaceId, body CreateWebhookJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewCreateWebhookRequest(c.Server, spaceId, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) DeleteWebhook(ctx context.Context, spaceId SpaceId, webhookId WebhookId, params *DeleteWebhookParams, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewDeleteWebhookRequest(c.Server, spaceId, webhookId, params) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) GetWebhook(ctx context.Context, spaceId SpaceId, webhookId WebhookId, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewGetWebhookRequest(c.Server, spaceId, webhookId) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateWebhookWithBody(ctx context.Context, spaceId SpaceId, webhookId WebhookId, params *UpdateWebhookParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateWebhookRequestWithBody(c.Server, spaceId, webhookId, params, contentType, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +func (c *Client) UpdateWebhook(ctx context.Context, spaceId SpaceId, webhookId WebhookId, params *UpdateWebhookParams, body UpdateWebhookJSONRequestBody, reqEditors ...RequestEditorFn) (*http.Response, error) { + req, err := NewUpdateWebhookRequest(c.Server, spaceId, webhookId, params, body) + if err != nil { + return nil, err + } + req = req.WithContext(ctx) + if err := c.applyEditors(ctx, req, reqEditors); err != nil { + return nil, err + } + return c.Client.Do(req) +} + +// NewGetAllSpacesRequest generates requests for GetAllSpaces +func NewGetAllSpacesRequest(server string, params *GetAllSpacesParams) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Limit != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "limit", runtime.ParamLocationQuery, *params.Limit); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Skip != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "skip", runtime.ParamLocationQuery, *params.Skip); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateSpaceRequest calls the generic CreateSpace builder with application/json body +func NewCreateSpaceRequest(server string, params *CreateSpaceParams, body CreateSpaceJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateSpaceRequestWithBody(server, params, "application/json", bodyReader) +} + +// NewCreateSpaceRequestWithBody generates requests for CreateSpace with any type of body +func NewCreateSpaceRequestWithBody(server string, params *CreateSpaceParams, contentType string, body io.Reader) (*http.Request, error) { + var err error + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces") + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "Content-Type", runtime.ParamLocationHeader, params.ContentType) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", headerParam0) + + } + + return req, nil +} + +// NewDeleteSpaceRequest generates requests for DeleteSpace +func NewDeleteSpaceRequest(server string, spaceId SpaceId, params *DeleteSpaceParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "X-Contentful-Version", runtime.ParamLocationHeader, params.XContentfulVersion) + if err != nil { + return nil, err + } + + req.Header.Set("X-Contentful-Version", headerParam0) + + } + + return req, nil +} + +// NewGetSpaceRequest generates requests for GetSpace +func NewGetSpaceRequest(server string, spaceId SpaceId) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewUpdateSpaceRequest calls the generic UpdateSpace builder with application/json body +func NewUpdateSpaceRequest(server string, spaceId SpaceId, params *UpdateSpaceParams, body UpdateSpaceJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateSpaceRequestWithBody(server, spaceId, params, "application/json", bodyReader) +} + +// NewUpdateSpaceRequestWithBody generates requests for UpdateSpace with any type of body +func NewUpdateSpaceRequestWithBody(server string, spaceId SpaceId, params *UpdateSpaceParams, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "X-Contentful-Version", runtime.ParamLocationHeader, params.XContentfulVersion) + if err != nil { + return nil, err + } + + req.Header.Set("X-Contentful-Version", headerParam0) + + } + + return req, nil +} + +// NewGetAllEnvironmentsRequest generates requests for GetAllEnvironments +func NewGetAllEnvironmentsRequest(server string, spaceId SpaceId, params *GetAllEnvironmentsParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Limit != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "limit", runtime.ParamLocationQuery, *params.Limit); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Skip != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "skip", runtime.ParamLocationQuery, *params.Skip); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateEnvironmentRequest calls the generic CreateEnvironment builder with application/json body +func NewCreateEnvironmentRequest(server string, spaceId SpaceId, body CreateEnvironmentJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateEnvironmentRequestWithBody(server, spaceId, "application/json", bodyReader) +} + +// NewCreateEnvironmentRequestWithBody generates requests for CreateEnvironment with any type of body +func NewCreateEnvironmentRequestWithBody(server string, spaceId SpaceId, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewDeleteEnvironmentRequest generates requests for DeleteEnvironment +func NewDeleteEnvironmentRequest(server string, spaceId SpaceId, environmentId EnvironmentId, params *DeleteEnvironmentParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "X-Contentful-Version", runtime.ParamLocationHeader, params.XContentfulVersion) + if err != nil { + return nil, err + } + + req.Header.Set("X-Contentful-Version", headerParam0) + + } + + return req, nil +} + +// NewGetEnvironmentRequest generates requests for GetEnvironment +func NewGetEnvironmentRequest(server string, spaceId SpaceId, environmentId EnvironmentId) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewUpdateEnvironmentRequest calls the generic UpdateEnvironment builder with application/json body +func NewUpdateEnvironmentRequest(server string, spaceId SpaceId, environmentId EnvironmentId, params *UpdateEnvironmentParams, body UpdateEnvironmentJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateEnvironmentRequestWithBody(server, spaceId, environmentId, params, "application/json", bodyReader) +} + +// NewUpdateEnvironmentRequestWithBody generates requests for UpdateEnvironment with any type of body +func NewUpdateEnvironmentRequestWithBody(server string, spaceId SpaceId, environmentId EnvironmentId, params *UpdateEnvironmentParams, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "X-Contentful-Version", runtime.ParamLocationHeader, params.XContentfulVersion) + if err != nil { + return nil, err + } + + req.Header.Set("X-Contentful-Version", headerParam0) + + } + + return req, nil +} + +// NewGetAllAssetsRequest generates requests for GetAllAssets +func NewGetAllAssetsRequest(server string, spaceId SpaceId, environmentId EnvironmentId, params *GetAllAssetsParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s/assets", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Limit != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "limit", runtime.ParamLocationQuery, *params.Limit); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Skip != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "skip", runtime.ParamLocationQuery, *params.Skip); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateAssetRequest calls the generic CreateAsset builder with application/json body +func NewCreateAssetRequest(server string, spaceId SpaceId, environmentId EnvironmentId, body CreateAssetJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateAssetRequestWithBody(server, spaceId, environmentId, "application/json", bodyReader) +} + +// NewCreateAssetRequestWithBody generates requests for CreateAsset with any type of body +func NewCreateAssetRequestWithBody(server string, spaceId SpaceId, environmentId EnvironmentId, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s/assets", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewGetAllContentTypesRequest generates requests for GetAllContentTypes +func NewGetAllContentTypesRequest(server string, spaceId SpaceId, environmentId EnvironmentId, params *GetAllContentTypesParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s/content_types", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Limit != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "limit", runtime.ParamLocationQuery, *params.Limit); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Skip != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "skip", runtime.ParamLocationQuery, *params.Skip); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateContentTypeRequest calls the generic CreateContentType builder with application/json body +func NewCreateContentTypeRequest(server string, spaceId SpaceId, environmentId EnvironmentId, body CreateContentTypeJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateContentTypeRequestWithBody(server, spaceId, environmentId, "application/json", bodyReader) +} + +// NewCreateContentTypeRequestWithBody generates requests for CreateContentType with any type of body +func NewCreateContentTypeRequestWithBody(server string, spaceId SpaceId, environmentId EnvironmentId, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s/content_types", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewGetAllEntriesRequest generates requests for GetAllEntries +func NewGetAllEntriesRequest(server string, spaceId SpaceId, environmentId EnvironmentId, params *GetAllEntriesParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s/entries", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Limit != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "limit", runtime.ParamLocationQuery, *params.Limit); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Skip != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "skip", runtime.ParamLocationQuery, *params.Skip); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.ContentType != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "content_type", runtime.ParamLocationQuery, *params.ContentType); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateEntryRequest calls the generic CreateEntry builder with application/json body +func NewCreateEntryRequest(server string, spaceId SpaceId, environmentId EnvironmentId, params *CreateEntryParams, body CreateEntryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateEntryRequestWithBody(server, spaceId, environmentId, params, "application/json", bodyReader) +} + +// NewCreateEntryRequestWithBody generates requests for CreateEntry with any type of body +func NewCreateEntryRequestWithBody(server string, spaceId SpaceId, environmentId EnvironmentId, params *CreateEntryParams, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s/entries", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "content_type", runtime.ParamLocationQuery, params.ContentType); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewDeleteEntryRequest generates requests for DeleteEntry +func NewDeleteEntryRequest(server string, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, params *DeleteEntryParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "entryId", runtime.ParamLocationPath, entryId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s/entries/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "X-Contentful-Version", runtime.ParamLocationHeader, params.XContentfulVersion) + if err != nil { + return nil, err + } + + req.Header.Set("X-Contentful-Version", headerParam0) + + } + + return req, nil +} + +// NewGetEntryRequest generates requests for GetEntry +func NewGetEntryRequest(server string, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "entryId", runtime.ParamLocationPath, entryId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s/entries/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewUpdateEntryRequest calls the generic UpdateEntry builder with application/json body +func NewUpdateEntryRequest(server string, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, params *UpdateEntryParams, body UpdateEntryJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateEntryRequestWithBody(server, spaceId, environmentId, entryId, params, "application/json", bodyReader) +} + +// NewUpdateEntryRequestWithBody generates requests for UpdateEntry with any type of body +func NewUpdateEntryRequestWithBody(server string, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, params *UpdateEntryParams, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "entryId", runtime.ParamLocationPath, entryId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s/entries/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "X-Contentful-Version", runtime.ParamLocationHeader, params.XContentfulVersion) + if err != nil { + return nil, err + } + + req.Header.Set("X-Contentful-Version", headerParam0) + + } + + return req, nil +} + +// NewUnarchiveEntryRequest generates requests for UnarchiveEntry +func NewUnarchiveEntryRequest(server string, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "entryId", runtime.ParamLocationPath, entryId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s/entries/%s/archived", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewArchiveEntryRequest generates requests for ArchiveEntry +func NewArchiveEntryRequest(server string, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "entryId", runtime.ParamLocationPath, entryId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s/entries/%s/archived", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewUnpublishEntryRequest generates requests for UnpublishEntry +func NewUnpublishEntryRequest(server string, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "entryId", runtime.ParamLocationPath, entryId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s/entries/%s/published", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewPublishEntryRequest generates requests for PublishEntry +func NewPublishEntryRequest(server string, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "entryId", runtime.ParamLocationPath, entryId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s/entries/%s/published", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetAllLocalesRequest generates requests for GetAllLocales +func NewGetAllLocalesRequest(server string, spaceId SpaceId, environmentId EnvironmentId, params *GetAllLocalesParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s/locales", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Limit != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "limit", runtime.ParamLocationQuery, *params.Limit); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Skip != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "skip", runtime.ParamLocationQuery, *params.Skip); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateLocaleRequest calls the generic CreateLocale builder with application/json body +func NewCreateLocaleRequest(server string, spaceId SpaceId, environmentId EnvironmentId, body CreateLocaleJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateLocaleRequestWithBody(server, spaceId, environmentId, "application/json", bodyReader) +} + +// NewCreateLocaleRequestWithBody generates requests for CreateLocale with any type of body +func NewCreateLocaleRequestWithBody(server string, spaceId SpaceId, environmentId EnvironmentId, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s/locales", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewDeleteLocaleRequest generates requests for DeleteLocale +func NewDeleteLocaleRequest(server string, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "localeId", runtime.ParamLocationPath, localeId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s/locales/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewGetLocaleRequest generates requests for GetLocale +func NewGetLocaleRequest(server string, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "localeId", runtime.ParamLocationPath, localeId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s/locales/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewUpdateLocaleRequest calls the generic UpdateLocale builder with application/json body +func NewUpdateLocaleRequest(server string, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId, params *UpdateLocaleParams, body UpdateLocaleJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateLocaleRequestWithBody(server, spaceId, environmentId, localeId, params, "application/json", bodyReader) +} + +// NewUpdateLocaleRequestWithBody generates requests for UpdateLocale with any type of body +func NewUpdateLocaleRequestWithBody(server string, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId, params *UpdateLocaleParams, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "environmentId", runtime.ParamLocationPath, environmentId) + if err != nil { + return nil, err + } + + var pathParam2 string + + pathParam2, err = runtime.StyleParamWithLocation("simple", false, "localeId", runtime.ParamLocationPath, localeId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/environments/%s/locales/%s", pathParam0, pathParam1, pathParam2) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "X-Contentful-Version", runtime.ParamLocationHeader, params.XContentfulVersion) + if err != nil { + return nil, err + } + + req.Header.Set("X-Contentful-Version", headerParam0) + + } + + return req, nil +} + +// NewGetAllWebhooksRequest generates requests for GetAllWebhooks +func NewGetAllWebhooksRequest(server string, spaceId SpaceId, params *GetAllWebhooksParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/webhook_definitions", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + if params != nil { + queryValues := queryURL.Query() + + if params.Limit != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "limit", runtime.ParamLocationQuery, *params.Limit); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + if params.Skip != nil { + + if queryFrag, err := runtime.StyleParamWithLocation("form", true, "skip", runtime.ParamLocationQuery, *params.Skip); err != nil { + return nil, err + } else if parsed, err := url.ParseQuery(queryFrag); err != nil { + return nil, err + } else { + for k, v := range parsed { + for _, v2 := range v { + queryValues.Add(k, v2) + } + } + } + + } + + queryURL.RawQuery = queryValues.Encode() + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewCreateWebhookRequest calls the generic CreateWebhook builder with application/json body +func NewCreateWebhookRequest(server string, spaceId SpaceId, body CreateWebhookJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewCreateWebhookRequestWithBody(server, spaceId, "application/json", bodyReader) +} + +// NewCreateWebhookRequestWithBody generates requests for CreateWebhook with any type of body +func NewCreateWebhookRequestWithBody(server string, spaceId SpaceId, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/webhook_definitions", pathParam0) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("POST", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + return req, nil +} + +// NewDeleteWebhookRequest generates requests for DeleteWebhook +func NewDeleteWebhookRequest(server string, spaceId SpaceId, webhookId WebhookId, params *DeleteWebhookParams) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "webhookId", runtime.ParamLocationPath, webhookId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/webhook_definitions/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("DELETE", queryURL.String(), nil) + if err != nil { + return nil, err + } + + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "X-Contentful-Version", runtime.ParamLocationHeader, params.XContentfulVersion) + if err != nil { + return nil, err + } + + req.Header.Set("X-Contentful-Version", headerParam0) + + } + + return req, nil +} + +// NewGetWebhookRequest generates requests for GetWebhook +func NewGetWebhookRequest(server string, spaceId SpaceId, webhookId WebhookId) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "webhookId", runtime.ParamLocationPath, webhookId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/webhook_definitions/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("GET", queryURL.String(), nil) + if err != nil { + return nil, err + } + + return req, nil +} + +// NewUpdateWebhookRequest calls the generic UpdateWebhook builder with application/json body +func NewUpdateWebhookRequest(server string, spaceId SpaceId, webhookId WebhookId, params *UpdateWebhookParams, body UpdateWebhookJSONRequestBody) (*http.Request, error) { + var bodyReader io.Reader + buf, err := json.Marshal(body) + if err != nil { + return nil, err + } + bodyReader = bytes.NewReader(buf) + return NewUpdateWebhookRequestWithBody(server, spaceId, webhookId, params, "application/json", bodyReader) +} + +// NewUpdateWebhookRequestWithBody generates requests for UpdateWebhook with any type of body +func NewUpdateWebhookRequestWithBody(server string, spaceId SpaceId, webhookId WebhookId, params *UpdateWebhookParams, contentType string, body io.Reader) (*http.Request, error) { + var err error + + var pathParam0 string + + pathParam0, err = runtime.StyleParamWithLocation("simple", false, "spaceId", runtime.ParamLocationPath, spaceId) + if err != nil { + return nil, err + } + + var pathParam1 string + + pathParam1, err = runtime.StyleParamWithLocation("simple", false, "webhookId", runtime.ParamLocationPath, webhookId) + if err != nil { + return nil, err + } + + serverURL, err := url.Parse(server) + if err != nil { + return nil, err + } + + operationPath := fmt.Sprintf("/spaces/%s/webhook_definitions/%s", pathParam0, pathParam1) + if operationPath[0] == '/' { + operationPath = "." + operationPath + } + + queryURL, err := serverURL.Parse(operationPath) + if err != nil { + return nil, err + } + + req, err := http.NewRequest("PUT", queryURL.String(), body) + if err != nil { + return nil, err + } + + req.Header.Add("Content-Type", contentType) + + if params != nil { + + var headerParam0 string + + headerParam0, err = runtime.StyleParamWithLocation("simple", false, "X-Contentful-Version", runtime.ParamLocationHeader, params.XContentfulVersion) + if err != nil { + return nil, err + } + + req.Header.Set("X-Contentful-Version", headerParam0) + + } + + return req, nil +} + +func (c *Client) applyEditors(ctx context.Context, req *http.Request, additionalEditors []RequestEditorFn) error { + for _, r := range c.RequestEditors { + if err := r(ctx, req); err != nil { + return err + } + } + for _, r := range additionalEditors { + if err := r(ctx, req); err != nil { + return err + } + } + return nil +} + +// ClientWithResponses builds on ClientInterface to offer response payloads +type ClientWithResponses struct { + ClientInterface +} + +// NewClientWithResponses creates a new ClientWithResponses, which wraps +// Client with return type handling +func NewClientWithResponses(server string, opts ...ClientOption) (*ClientWithResponses, error) { + client, err := NewClient(server, opts...) + if err != nil { + return nil, err + } + return &ClientWithResponses{client}, nil +} + +// WithBaseURL overrides the baseURL. +func WithBaseURL(baseURL string) ClientOption { + return func(c *Client) error { + newBaseURL, err := url.Parse(baseURL) + if err != nil { + return err + } + c.Server = newBaseURL.String() + return nil + } +} + +// ClientWithResponsesInterface is the interface specification for the client with responses above. +type ClientWithResponsesInterface interface { + // GetAllSpacesWithResponse request + GetAllSpacesWithResponse(ctx context.Context, params *GetAllSpacesParams, reqEditors ...RequestEditorFn) (*GetAllSpacesResponse, error) + + // CreateSpaceWithBodyWithResponse request with any body + CreateSpaceWithBodyWithResponse(ctx context.Context, params *CreateSpaceParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateSpaceResponse, error) + + CreateSpaceWithResponse(ctx context.Context, params *CreateSpaceParams, body CreateSpaceJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateSpaceResponse, error) + + // DeleteSpaceWithResponse request + DeleteSpaceWithResponse(ctx context.Context, spaceId SpaceId, params *DeleteSpaceParams, reqEditors ...RequestEditorFn) (*DeleteSpaceResponse, error) + + // GetSpaceWithResponse request + GetSpaceWithResponse(ctx context.Context, spaceId SpaceId, reqEditors ...RequestEditorFn) (*GetSpaceResponse, error) + + // UpdateSpaceWithBodyWithResponse request with any body + UpdateSpaceWithBodyWithResponse(ctx context.Context, spaceId SpaceId, params *UpdateSpaceParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateSpaceResponse, error) + + UpdateSpaceWithResponse(ctx context.Context, spaceId SpaceId, params *UpdateSpaceParams, body UpdateSpaceJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateSpaceResponse, error) + + // GetAllEnvironmentsWithResponse request + GetAllEnvironmentsWithResponse(ctx context.Context, spaceId SpaceId, params *GetAllEnvironmentsParams, reqEditors ...RequestEditorFn) (*GetAllEnvironmentsResponse, error) + + // CreateEnvironmentWithBodyWithResponse request with any body + CreateEnvironmentWithBodyWithResponse(ctx context.Context, spaceId SpaceId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateEnvironmentResponse, error) + + CreateEnvironmentWithResponse(ctx context.Context, spaceId SpaceId, body CreateEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateEnvironmentResponse, error) + + // DeleteEnvironmentWithResponse request + DeleteEnvironmentWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *DeleteEnvironmentParams, reqEditors ...RequestEditorFn) (*DeleteEnvironmentResponse, error) + + // GetEnvironmentWithResponse request + GetEnvironmentWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, reqEditors ...RequestEditorFn) (*GetEnvironmentResponse, error) + + // UpdateEnvironmentWithBodyWithResponse request with any body + UpdateEnvironmentWithBodyWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *UpdateEnvironmentParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateEnvironmentResponse, error) + + UpdateEnvironmentWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *UpdateEnvironmentParams, body UpdateEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateEnvironmentResponse, error) + + // GetAllAssetsWithResponse request + GetAllAssetsWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *GetAllAssetsParams, reqEditors ...RequestEditorFn) (*GetAllAssetsResponse, error) + + // CreateAssetWithBodyWithResponse request with any body + CreateAssetWithBodyWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateAssetResponse, error) + + CreateAssetWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, body CreateAssetJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateAssetResponse, error) + + // GetAllContentTypesWithResponse request + GetAllContentTypesWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *GetAllContentTypesParams, reqEditors ...RequestEditorFn) (*GetAllContentTypesResponse, error) + + // CreateContentTypeWithBodyWithResponse request with any body + CreateContentTypeWithBodyWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateContentTypeResponse, error) + + CreateContentTypeWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, body CreateContentTypeJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateContentTypeResponse, error) + + // GetAllEntriesWithResponse request + GetAllEntriesWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *GetAllEntriesParams, reqEditors ...RequestEditorFn) (*GetAllEntriesResponse, error) + + // CreateEntryWithBodyWithResponse request with any body + CreateEntryWithBodyWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *CreateEntryParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateEntryResponse, error) + + CreateEntryWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *CreateEntryParams, body CreateEntryJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateEntryResponse, error) + + // DeleteEntryWithResponse request + DeleteEntryWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, params *DeleteEntryParams, reqEditors ...RequestEditorFn) (*DeleteEntryResponse, error) + + // GetEntryWithResponse request + GetEntryWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*GetEntryResponse, error) + + // UpdateEntryWithBodyWithResponse request with any body + UpdateEntryWithBodyWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, params *UpdateEntryParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateEntryResponse, error) + + UpdateEntryWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, params *UpdateEntryParams, body UpdateEntryJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateEntryResponse, error) + + // UnarchiveEntryWithResponse request + UnarchiveEntryWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*UnarchiveEntryResponse, error) + + // ArchiveEntryWithResponse request + ArchiveEntryWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*ArchiveEntryResponse, error) + + // UnpublishEntryWithResponse request + UnpublishEntryWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*UnpublishEntryResponse, error) + + // PublishEntryWithResponse request + PublishEntryWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*PublishEntryResponse, error) + + // GetAllLocalesWithResponse request + GetAllLocalesWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *GetAllLocalesParams, reqEditors ...RequestEditorFn) (*GetAllLocalesResponse, error) + + // CreateLocaleWithBodyWithResponse request with any body + CreateLocaleWithBodyWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateLocaleResponse, error) + + CreateLocaleWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, body CreateLocaleJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateLocaleResponse, error) + + // DeleteLocaleWithResponse request + DeleteLocaleWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId, reqEditors ...RequestEditorFn) (*DeleteLocaleResponse, error) + + // GetLocaleWithResponse request + GetLocaleWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId, reqEditors ...RequestEditorFn) (*GetLocaleResponse, error) + + // UpdateLocaleWithBodyWithResponse request with any body + UpdateLocaleWithBodyWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId, params *UpdateLocaleParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateLocaleResponse, error) + + UpdateLocaleWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId, params *UpdateLocaleParams, body UpdateLocaleJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateLocaleResponse, error) + + // GetAllWebhooksWithResponse request + GetAllWebhooksWithResponse(ctx context.Context, spaceId SpaceId, params *GetAllWebhooksParams, reqEditors ...RequestEditorFn) (*GetAllWebhooksResponse, error) + + // CreateWebhookWithBodyWithResponse request with any body + CreateWebhookWithBodyWithResponse(ctx context.Context, spaceId SpaceId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateWebhookResponse, error) + + CreateWebhookWithResponse(ctx context.Context, spaceId SpaceId, body CreateWebhookJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateWebhookResponse, error) + + // DeleteWebhookWithResponse request + DeleteWebhookWithResponse(ctx context.Context, spaceId SpaceId, webhookId WebhookId, params *DeleteWebhookParams, reqEditors ...RequestEditorFn) (*DeleteWebhookResponse, error) + + // GetWebhookWithResponse request + GetWebhookWithResponse(ctx context.Context, spaceId SpaceId, webhookId WebhookId, reqEditors ...RequestEditorFn) (*GetWebhookResponse, error) + + // UpdateWebhookWithBodyWithResponse request with any body + UpdateWebhookWithBodyWithResponse(ctx context.Context, spaceId SpaceId, webhookId WebhookId, params *UpdateWebhookParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateWebhookResponse, error) + + UpdateWebhookWithResponse(ctx context.Context, spaceId SpaceId, webhookId WebhookId, params *UpdateWebhookParams, body UpdateWebhookJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateWebhookResponse, error) +} + +type GetAllSpacesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *SpaceCollection +} + +// Status returns HTTPResponse.Status +func (r GetAllSpacesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetAllSpacesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateSpaceResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *Space +} + +// Status returns HTTPResponse.Status +func (r CreateSpaceResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateSpaceResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeleteSpaceResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r DeleteSpaceResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteSpaceResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetSpaceResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Space +} + +// Status returns HTTPResponse.Status +func (r GetSpaceResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetSpaceResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UpdateSpaceResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Space +} + +// Status returns HTTPResponse.Status +func (r UpdateSpaceResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateSpaceResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetAllEnvironmentsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *EnvironmentCollection +} + +// Status returns HTTPResponse.Status +func (r GetAllEnvironmentsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetAllEnvironmentsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateEnvironmentResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *Environment +} + +// Status returns HTTPResponse.Status +func (r CreateEnvironmentResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateEnvironmentResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeleteEnvironmentResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r DeleteEnvironmentResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteEnvironmentResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetEnvironmentResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Environment +} + +// Status returns HTTPResponse.Status +func (r GetEnvironmentResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetEnvironmentResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UpdateEnvironmentResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Environment +} + +// Status returns HTTPResponse.Status +func (r UpdateEnvironmentResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateEnvironmentResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetAllAssetsResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *AssetCollection +} + +// Status returns HTTPResponse.Status +func (r GetAllAssetsResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetAllAssetsResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateAssetResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *Asset +} + +// Status returns HTTPResponse.Status +func (r CreateAssetResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateAssetResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetAllContentTypesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *ContentTypeCollection +} + +// Status returns HTTPResponse.Status +func (r GetAllContentTypesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetAllContentTypesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateContentTypeResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *ContentType +} + +// Status returns HTTPResponse.Status +func (r CreateContentTypeResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateContentTypeResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetAllEntriesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *EntryCollection +} + +// Status returns HTTPResponse.Status +func (r GetAllEntriesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetAllEntriesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateEntryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *Entry +} + +// Status returns HTTPResponse.Status +func (r CreateEntryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateEntryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeleteEntryResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r DeleteEntryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteEntryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetEntryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Entry +} + +// Status returns HTTPResponse.Status +func (r GetEntryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetEntryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UpdateEntryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Entry +} + +// Status returns HTTPResponse.Status +func (r UpdateEntryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateEntryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UnarchiveEntryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Entry +} + +// Status returns HTTPResponse.Status +func (r UnarchiveEntryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UnarchiveEntryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type ArchiveEntryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Entry +} + +// Status returns HTTPResponse.Status +func (r ArchiveEntryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r ArchiveEntryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UnpublishEntryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Entry +} + +// Status returns HTTPResponse.Status +func (r UnpublishEntryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UnpublishEntryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type PublishEntryResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Entry +} + +// Status returns HTTPResponse.Status +func (r PublishEntryResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r PublishEntryResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetAllLocalesResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *LocaleCollection +} + +// Status returns HTTPResponse.Status +func (r GetAllLocalesResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetAllLocalesResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateLocaleResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *Locale +} + +// Status returns HTTPResponse.Status +func (r CreateLocaleResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateLocaleResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeleteLocaleResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r DeleteLocaleResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteLocaleResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetLocaleResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Locale +} + +// Status returns HTTPResponse.Status +func (r GetLocaleResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetLocaleResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UpdateLocaleResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Locale +} + +// Status returns HTTPResponse.Status +func (r UpdateLocaleResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateLocaleResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetAllWebhooksResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *WebhookCollection +} + +// Status returns HTTPResponse.Status +func (r GetAllWebhooksResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetAllWebhooksResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type CreateWebhookResponse struct { + Body []byte + HTTPResponse *http.Response + JSON201 *Webhook +} + +// Status returns HTTPResponse.Status +func (r CreateWebhookResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r CreateWebhookResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type DeleteWebhookResponse struct { + Body []byte + HTTPResponse *http.Response +} + +// Status returns HTTPResponse.Status +func (r DeleteWebhookResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r DeleteWebhookResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type GetWebhookResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Webhook +} + +// Status returns HTTPResponse.Status +func (r GetWebhookResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r GetWebhookResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +type UpdateWebhookResponse struct { + Body []byte + HTTPResponse *http.Response + JSON200 *Webhook +} + +// Status returns HTTPResponse.Status +func (r UpdateWebhookResponse) Status() string { + if r.HTTPResponse != nil { + return r.HTTPResponse.Status + } + return http.StatusText(0) +} + +// StatusCode returns HTTPResponse.StatusCode +func (r UpdateWebhookResponse) StatusCode() int { + if r.HTTPResponse != nil { + return r.HTTPResponse.StatusCode + } + return 0 +} + +// GetAllSpacesWithResponse request returning *GetAllSpacesResponse +func (c *ClientWithResponses) GetAllSpacesWithResponse(ctx context.Context, params *GetAllSpacesParams, reqEditors ...RequestEditorFn) (*GetAllSpacesResponse, error) { + rsp, err := c.GetAllSpaces(ctx, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetAllSpacesResponse(rsp) +} + +// CreateSpaceWithBodyWithResponse request with arbitrary body returning *CreateSpaceResponse +func (c *ClientWithResponses) CreateSpaceWithBodyWithResponse(ctx context.Context, params *CreateSpaceParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateSpaceResponse, error) { + rsp, err := c.CreateSpaceWithBody(ctx, params, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateSpaceResponse(rsp) +} + +func (c *ClientWithResponses) CreateSpaceWithResponse(ctx context.Context, params *CreateSpaceParams, body CreateSpaceJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateSpaceResponse, error) { + rsp, err := c.CreateSpace(ctx, params, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateSpaceResponse(rsp) +} + +// DeleteSpaceWithResponse request returning *DeleteSpaceResponse +func (c *ClientWithResponses) DeleteSpaceWithResponse(ctx context.Context, spaceId SpaceId, params *DeleteSpaceParams, reqEditors ...RequestEditorFn) (*DeleteSpaceResponse, error) { + rsp, err := c.DeleteSpace(ctx, spaceId, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteSpaceResponse(rsp) +} + +// GetSpaceWithResponse request returning *GetSpaceResponse +func (c *ClientWithResponses) GetSpaceWithResponse(ctx context.Context, spaceId SpaceId, reqEditors ...RequestEditorFn) (*GetSpaceResponse, error) { + rsp, err := c.GetSpace(ctx, spaceId, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetSpaceResponse(rsp) +} + +// UpdateSpaceWithBodyWithResponse request with arbitrary body returning *UpdateSpaceResponse +func (c *ClientWithResponses) UpdateSpaceWithBodyWithResponse(ctx context.Context, spaceId SpaceId, params *UpdateSpaceParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateSpaceResponse, error) { + rsp, err := c.UpdateSpaceWithBody(ctx, spaceId, params, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateSpaceResponse(rsp) +} + +func (c *ClientWithResponses) UpdateSpaceWithResponse(ctx context.Context, spaceId SpaceId, params *UpdateSpaceParams, body UpdateSpaceJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateSpaceResponse, error) { + rsp, err := c.UpdateSpace(ctx, spaceId, params, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateSpaceResponse(rsp) +} + +// GetAllEnvironmentsWithResponse request returning *GetAllEnvironmentsResponse +func (c *ClientWithResponses) GetAllEnvironmentsWithResponse(ctx context.Context, spaceId SpaceId, params *GetAllEnvironmentsParams, reqEditors ...RequestEditorFn) (*GetAllEnvironmentsResponse, error) { + rsp, err := c.GetAllEnvironments(ctx, spaceId, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetAllEnvironmentsResponse(rsp) +} + +// CreateEnvironmentWithBodyWithResponse request with arbitrary body returning *CreateEnvironmentResponse +func (c *ClientWithResponses) CreateEnvironmentWithBodyWithResponse(ctx context.Context, spaceId SpaceId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateEnvironmentResponse, error) { + rsp, err := c.CreateEnvironmentWithBody(ctx, spaceId, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateEnvironmentResponse(rsp) +} + +func (c *ClientWithResponses) CreateEnvironmentWithResponse(ctx context.Context, spaceId SpaceId, body CreateEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateEnvironmentResponse, error) { + rsp, err := c.CreateEnvironment(ctx, spaceId, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateEnvironmentResponse(rsp) +} + +// DeleteEnvironmentWithResponse request returning *DeleteEnvironmentResponse +func (c *ClientWithResponses) DeleteEnvironmentWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *DeleteEnvironmentParams, reqEditors ...RequestEditorFn) (*DeleteEnvironmentResponse, error) { + rsp, err := c.DeleteEnvironment(ctx, spaceId, environmentId, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteEnvironmentResponse(rsp) +} + +// GetEnvironmentWithResponse request returning *GetEnvironmentResponse +func (c *ClientWithResponses) GetEnvironmentWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, reqEditors ...RequestEditorFn) (*GetEnvironmentResponse, error) { + rsp, err := c.GetEnvironment(ctx, spaceId, environmentId, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetEnvironmentResponse(rsp) +} + +// UpdateEnvironmentWithBodyWithResponse request with arbitrary body returning *UpdateEnvironmentResponse +func (c *ClientWithResponses) UpdateEnvironmentWithBodyWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *UpdateEnvironmentParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateEnvironmentResponse, error) { + rsp, err := c.UpdateEnvironmentWithBody(ctx, spaceId, environmentId, params, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateEnvironmentResponse(rsp) +} + +func (c *ClientWithResponses) UpdateEnvironmentWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *UpdateEnvironmentParams, body UpdateEnvironmentJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateEnvironmentResponse, error) { + rsp, err := c.UpdateEnvironment(ctx, spaceId, environmentId, params, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateEnvironmentResponse(rsp) +} + +// GetAllAssetsWithResponse request returning *GetAllAssetsResponse +func (c *ClientWithResponses) GetAllAssetsWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *GetAllAssetsParams, reqEditors ...RequestEditorFn) (*GetAllAssetsResponse, error) { + rsp, err := c.GetAllAssets(ctx, spaceId, environmentId, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetAllAssetsResponse(rsp) +} + +// CreateAssetWithBodyWithResponse request with arbitrary body returning *CreateAssetResponse +func (c *ClientWithResponses) CreateAssetWithBodyWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateAssetResponse, error) { + rsp, err := c.CreateAssetWithBody(ctx, spaceId, environmentId, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateAssetResponse(rsp) +} + +func (c *ClientWithResponses) CreateAssetWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, body CreateAssetJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateAssetResponse, error) { + rsp, err := c.CreateAsset(ctx, spaceId, environmentId, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateAssetResponse(rsp) +} + +// GetAllContentTypesWithResponse request returning *GetAllContentTypesResponse +func (c *ClientWithResponses) GetAllContentTypesWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *GetAllContentTypesParams, reqEditors ...RequestEditorFn) (*GetAllContentTypesResponse, error) { + rsp, err := c.GetAllContentTypes(ctx, spaceId, environmentId, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetAllContentTypesResponse(rsp) +} + +// CreateContentTypeWithBodyWithResponse request with arbitrary body returning *CreateContentTypeResponse +func (c *ClientWithResponses) CreateContentTypeWithBodyWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateContentTypeResponse, error) { + rsp, err := c.CreateContentTypeWithBody(ctx, spaceId, environmentId, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateContentTypeResponse(rsp) +} + +func (c *ClientWithResponses) CreateContentTypeWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, body CreateContentTypeJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateContentTypeResponse, error) { + rsp, err := c.CreateContentType(ctx, spaceId, environmentId, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateContentTypeResponse(rsp) +} + +// GetAllEntriesWithResponse request returning *GetAllEntriesResponse +func (c *ClientWithResponses) GetAllEntriesWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *GetAllEntriesParams, reqEditors ...RequestEditorFn) (*GetAllEntriesResponse, error) { + rsp, err := c.GetAllEntries(ctx, spaceId, environmentId, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetAllEntriesResponse(rsp) +} + +// CreateEntryWithBodyWithResponse request with arbitrary body returning *CreateEntryResponse +func (c *ClientWithResponses) CreateEntryWithBodyWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *CreateEntryParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateEntryResponse, error) { + rsp, err := c.CreateEntryWithBody(ctx, spaceId, environmentId, params, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateEntryResponse(rsp) +} + +func (c *ClientWithResponses) CreateEntryWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *CreateEntryParams, body CreateEntryJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateEntryResponse, error) { + rsp, err := c.CreateEntry(ctx, spaceId, environmentId, params, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateEntryResponse(rsp) +} + +// DeleteEntryWithResponse request returning *DeleteEntryResponse +func (c *ClientWithResponses) DeleteEntryWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, params *DeleteEntryParams, reqEditors ...RequestEditorFn) (*DeleteEntryResponse, error) { + rsp, err := c.DeleteEntry(ctx, spaceId, environmentId, entryId, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteEntryResponse(rsp) +} + +// GetEntryWithResponse request returning *GetEntryResponse +func (c *ClientWithResponses) GetEntryWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*GetEntryResponse, error) { + rsp, err := c.GetEntry(ctx, spaceId, environmentId, entryId, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetEntryResponse(rsp) +} + +// UpdateEntryWithBodyWithResponse request with arbitrary body returning *UpdateEntryResponse +func (c *ClientWithResponses) UpdateEntryWithBodyWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, params *UpdateEntryParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateEntryResponse, error) { + rsp, err := c.UpdateEntryWithBody(ctx, spaceId, environmentId, entryId, params, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateEntryResponse(rsp) +} + +func (c *ClientWithResponses) UpdateEntryWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, params *UpdateEntryParams, body UpdateEntryJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateEntryResponse, error) { + rsp, err := c.UpdateEntry(ctx, spaceId, environmentId, entryId, params, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateEntryResponse(rsp) +} + +// UnarchiveEntryWithResponse request returning *UnarchiveEntryResponse +func (c *ClientWithResponses) UnarchiveEntryWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*UnarchiveEntryResponse, error) { + rsp, err := c.UnarchiveEntry(ctx, spaceId, environmentId, entryId, reqEditors...) + if err != nil { + return nil, err + } + return ParseUnarchiveEntryResponse(rsp) +} + +// ArchiveEntryWithResponse request returning *ArchiveEntryResponse +func (c *ClientWithResponses) ArchiveEntryWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*ArchiveEntryResponse, error) { + rsp, err := c.ArchiveEntry(ctx, spaceId, environmentId, entryId, reqEditors...) + if err != nil { + return nil, err + } + return ParseArchiveEntryResponse(rsp) +} + +// UnpublishEntryWithResponse request returning *UnpublishEntryResponse +func (c *ClientWithResponses) UnpublishEntryWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*UnpublishEntryResponse, error) { + rsp, err := c.UnpublishEntry(ctx, spaceId, environmentId, entryId, reqEditors...) + if err != nil { + return nil, err + } + return ParseUnpublishEntryResponse(rsp) +} + +// PublishEntryWithResponse request returning *PublishEntryResponse +func (c *ClientWithResponses) PublishEntryWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, entryId EntryId, reqEditors ...RequestEditorFn) (*PublishEntryResponse, error) { + rsp, err := c.PublishEntry(ctx, spaceId, environmentId, entryId, reqEditors...) + if err != nil { + return nil, err + } + return ParsePublishEntryResponse(rsp) +} + +// GetAllLocalesWithResponse request returning *GetAllLocalesResponse +func (c *ClientWithResponses) GetAllLocalesWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, params *GetAllLocalesParams, reqEditors ...RequestEditorFn) (*GetAllLocalesResponse, error) { + rsp, err := c.GetAllLocales(ctx, spaceId, environmentId, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetAllLocalesResponse(rsp) +} + +// CreateLocaleWithBodyWithResponse request with arbitrary body returning *CreateLocaleResponse +func (c *ClientWithResponses) CreateLocaleWithBodyWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateLocaleResponse, error) { + rsp, err := c.CreateLocaleWithBody(ctx, spaceId, environmentId, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateLocaleResponse(rsp) +} + +func (c *ClientWithResponses) CreateLocaleWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, body CreateLocaleJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateLocaleResponse, error) { + rsp, err := c.CreateLocale(ctx, spaceId, environmentId, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateLocaleResponse(rsp) +} + +// DeleteLocaleWithResponse request returning *DeleteLocaleResponse +func (c *ClientWithResponses) DeleteLocaleWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId, reqEditors ...RequestEditorFn) (*DeleteLocaleResponse, error) { + rsp, err := c.DeleteLocale(ctx, spaceId, environmentId, localeId, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteLocaleResponse(rsp) +} + +// GetLocaleWithResponse request returning *GetLocaleResponse +func (c *ClientWithResponses) GetLocaleWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId, reqEditors ...RequestEditorFn) (*GetLocaleResponse, error) { + rsp, err := c.GetLocale(ctx, spaceId, environmentId, localeId, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetLocaleResponse(rsp) +} + +// UpdateLocaleWithBodyWithResponse request with arbitrary body returning *UpdateLocaleResponse +func (c *ClientWithResponses) UpdateLocaleWithBodyWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId, params *UpdateLocaleParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateLocaleResponse, error) { + rsp, err := c.UpdateLocaleWithBody(ctx, spaceId, environmentId, localeId, params, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateLocaleResponse(rsp) +} + +func (c *ClientWithResponses) UpdateLocaleWithResponse(ctx context.Context, spaceId SpaceId, environmentId EnvironmentId, localeId LocaleId, params *UpdateLocaleParams, body UpdateLocaleJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateLocaleResponse, error) { + rsp, err := c.UpdateLocale(ctx, spaceId, environmentId, localeId, params, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateLocaleResponse(rsp) +} + +// GetAllWebhooksWithResponse request returning *GetAllWebhooksResponse +func (c *ClientWithResponses) GetAllWebhooksWithResponse(ctx context.Context, spaceId SpaceId, params *GetAllWebhooksParams, reqEditors ...RequestEditorFn) (*GetAllWebhooksResponse, error) { + rsp, err := c.GetAllWebhooks(ctx, spaceId, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetAllWebhooksResponse(rsp) +} + +// CreateWebhookWithBodyWithResponse request with arbitrary body returning *CreateWebhookResponse +func (c *ClientWithResponses) CreateWebhookWithBodyWithResponse(ctx context.Context, spaceId SpaceId, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*CreateWebhookResponse, error) { + rsp, err := c.CreateWebhookWithBody(ctx, spaceId, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateWebhookResponse(rsp) +} + +func (c *ClientWithResponses) CreateWebhookWithResponse(ctx context.Context, spaceId SpaceId, body CreateWebhookJSONRequestBody, reqEditors ...RequestEditorFn) (*CreateWebhookResponse, error) { + rsp, err := c.CreateWebhook(ctx, spaceId, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseCreateWebhookResponse(rsp) +} + +// DeleteWebhookWithResponse request returning *DeleteWebhookResponse +func (c *ClientWithResponses) DeleteWebhookWithResponse(ctx context.Context, spaceId SpaceId, webhookId WebhookId, params *DeleteWebhookParams, reqEditors ...RequestEditorFn) (*DeleteWebhookResponse, error) { + rsp, err := c.DeleteWebhook(ctx, spaceId, webhookId, params, reqEditors...) + if err != nil { + return nil, err + } + return ParseDeleteWebhookResponse(rsp) +} + +// GetWebhookWithResponse request returning *GetWebhookResponse +func (c *ClientWithResponses) GetWebhookWithResponse(ctx context.Context, spaceId SpaceId, webhookId WebhookId, reqEditors ...RequestEditorFn) (*GetWebhookResponse, error) { + rsp, err := c.GetWebhook(ctx, spaceId, webhookId, reqEditors...) + if err != nil { + return nil, err + } + return ParseGetWebhookResponse(rsp) +} + +// UpdateWebhookWithBodyWithResponse request with arbitrary body returning *UpdateWebhookResponse +func (c *ClientWithResponses) UpdateWebhookWithBodyWithResponse(ctx context.Context, spaceId SpaceId, webhookId WebhookId, params *UpdateWebhookParams, contentType string, body io.Reader, reqEditors ...RequestEditorFn) (*UpdateWebhookResponse, error) { + rsp, err := c.UpdateWebhookWithBody(ctx, spaceId, webhookId, params, contentType, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateWebhookResponse(rsp) +} + +func (c *ClientWithResponses) UpdateWebhookWithResponse(ctx context.Context, spaceId SpaceId, webhookId WebhookId, params *UpdateWebhookParams, body UpdateWebhookJSONRequestBody, reqEditors ...RequestEditorFn) (*UpdateWebhookResponse, error) { + rsp, err := c.UpdateWebhook(ctx, spaceId, webhookId, params, body, reqEditors...) + if err != nil { + return nil, err + } + return ParseUpdateWebhookResponse(rsp) +} + +// ParseGetAllSpacesResponse parses an HTTP response from a GetAllSpacesWithResponse call +func ParseGetAllSpacesResponse(rsp *http.Response) (*GetAllSpacesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetAllSpacesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest SpaceCollection + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCreateSpaceResponse parses an HTTP response from a CreateSpaceWithResponse call +func ParseCreateSpaceResponse(rsp *http.Response) (*CreateSpaceResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateSpaceResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Space + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseDeleteSpaceResponse parses an HTTP response from a DeleteSpaceWithResponse call +func ParseDeleteSpaceResponse(rsp *http.Response) (*DeleteSpaceResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteSpaceResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetSpaceResponse parses an HTTP response from a GetSpaceWithResponse call +func ParseGetSpaceResponse(rsp *http.Response) (*GetSpaceResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetSpaceResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Space + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseUpdateSpaceResponse parses an HTTP response from a UpdateSpaceWithResponse call +func ParseUpdateSpaceResponse(rsp *http.Response) (*UpdateSpaceResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateSpaceResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Space + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseGetAllEnvironmentsResponse parses an HTTP response from a GetAllEnvironmentsWithResponse call +func ParseGetAllEnvironmentsResponse(rsp *http.Response) (*GetAllEnvironmentsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetAllEnvironmentsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest EnvironmentCollection + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCreateEnvironmentResponse parses an HTTP response from a CreateEnvironmentWithResponse call +func ParseCreateEnvironmentResponse(rsp *http.Response) (*CreateEnvironmentResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateEnvironmentResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Environment + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseDeleteEnvironmentResponse parses an HTTP response from a DeleteEnvironmentWithResponse call +func ParseDeleteEnvironmentResponse(rsp *http.Response) (*DeleteEnvironmentResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteEnvironmentResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetEnvironmentResponse parses an HTTP response from a GetEnvironmentWithResponse call +func ParseGetEnvironmentResponse(rsp *http.Response) (*GetEnvironmentResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetEnvironmentResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Environment + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseUpdateEnvironmentResponse parses an HTTP response from a UpdateEnvironmentWithResponse call +func ParseUpdateEnvironmentResponse(rsp *http.Response) (*UpdateEnvironmentResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateEnvironmentResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Environment + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseGetAllAssetsResponse parses an HTTP response from a GetAllAssetsWithResponse call +func ParseGetAllAssetsResponse(rsp *http.Response) (*GetAllAssetsResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetAllAssetsResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest AssetCollection + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCreateAssetResponse parses an HTTP response from a CreateAssetWithResponse call +func ParseCreateAssetResponse(rsp *http.Response) (*CreateAssetResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateAssetResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Asset + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseGetAllContentTypesResponse parses an HTTP response from a GetAllContentTypesWithResponse call +func ParseGetAllContentTypesResponse(rsp *http.Response) (*GetAllContentTypesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetAllContentTypesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest ContentTypeCollection + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCreateContentTypeResponse parses an HTTP response from a CreateContentTypeWithResponse call +func ParseCreateContentTypeResponse(rsp *http.Response) (*CreateContentTypeResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateContentTypeResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest ContentType + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseGetAllEntriesResponse parses an HTTP response from a GetAllEntriesWithResponse call +func ParseGetAllEntriesResponse(rsp *http.Response) (*GetAllEntriesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetAllEntriesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest EntryCollection + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCreateEntryResponse parses an HTTP response from a CreateEntryWithResponse call +func ParseCreateEntryResponse(rsp *http.Response) (*CreateEntryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateEntryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Entry + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseDeleteEntryResponse parses an HTTP response from a DeleteEntryWithResponse call +func ParseDeleteEntryResponse(rsp *http.Response) (*DeleteEntryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteEntryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetEntryResponse parses an HTTP response from a GetEntryWithResponse call +func ParseGetEntryResponse(rsp *http.Response) (*GetEntryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetEntryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Entry + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseUpdateEntryResponse parses an HTTP response from a UpdateEntryWithResponse call +func ParseUpdateEntryResponse(rsp *http.Response) (*UpdateEntryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateEntryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Entry + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseUnarchiveEntryResponse parses an HTTP response from a UnarchiveEntryWithResponse call +func ParseUnarchiveEntryResponse(rsp *http.Response) (*UnarchiveEntryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UnarchiveEntryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Entry + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseArchiveEntryResponse parses an HTTP response from a ArchiveEntryWithResponse call +func ParseArchiveEntryResponse(rsp *http.Response) (*ArchiveEntryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &ArchiveEntryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Entry + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseUnpublishEntryResponse parses an HTTP response from a UnpublishEntryWithResponse call +func ParseUnpublishEntryResponse(rsp *http.Response) (*UnpublishEntryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UnpublishEntryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Entry + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParsePublishEntryResponse parses an HTTP response from a PublishEntryWithResponse call +func ParsePublishEntryResponse(rsp *http.Response) (*PublishEntryResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &PublishEntryResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Entry + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseGetAllLocalesResponse parses an HTTP response from a GetAllLocalesWithResponse call +func ParseGetAllLocalesResponse(rsp *http.Response) (*GetAllLocalesResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetAllLocalesResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest LocaleCollection + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCreateLocaleResponse parses an HTTP response from a CreateLocaleWithResponse call +func ParseCreateLocaleResponse(rsp *http.Response) (*CreateLocaleResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateLocaleResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Locale + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseDeleteLocaleResponse parses an HTTP response from a DeleteLocaleWithResponse call +func ParseDeleteLocaleResponse(rsp *http.Response) (*DeleteLocaleResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteLocaleResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetLocaleResponse parses an HTTP response from a GetLocaleWithResponse call +func ParseGetLocaleResponse(rsp *http.Response) (*GetLocaleResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetLocaleResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Locale + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseUpdateLocaleResponse parses an HTTP response from a UpdateLocaleWithResponse call +func ParseUpdateLocaleResponse(rsp *http.Response) (*UpdateLocaleResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateLocaleResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Locale + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseGetAllWebhooksResponse parses an HTTP response from a GetAllWebhooksWithResponse call +func ParseGetAllWebhooksResponse(rsp *http.Response) (*GetAllWebhooksResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetAllWebhooksResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest WebhookCollection + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseCreateWebhookResponse parses an HTTP response from a CreateWebhookWithResponse call +func ParseCreateWebhookResponse(rsp *http.Response) (*CreateWebhookResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &CreateWebhookResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 201: + var dest Webhook + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON201 = &dest + + } + + return response, nil +} + +// ParseDeleteWebhookResponse parses an HTTP response from a DeleteWebhookWithResponse call +func ParseDeleteWebhookResponse(rsp *http.Response) (*DeleteWebhookResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &DeleteWebhookResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + return response, nil +} + +// ParseGetWebhookResponse parses an HTTP response from a GetWebhookWithResponse call +func ParseGetWebhookResponse(rsp *http.Response) (*GetWebhookResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &GetWebhookResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Webhook + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} + +// ParseUpdateWebhookResponse parses an HTTP response from a UpdateWebhookWithResponse call +func ParseUpdateWebhookResponse(rsp *http.Response) (*UpdateWebhookResponse, error) { + bodyBytes, err := io.ReadAll(rsp.Body) + defer func() { _ = rsp.Body.Close() }() + if err != nil { + return nil, err + } + + response := &UpdateWebhookResponse{ + Body: bodyBytes, + HTTPResponse: rsp, + } + + switch { + case strings.Contains(rsp.Header.Get("Content-Type"), "json") && rsp.StatusCode == 200: + var dest Webhook + if err := json.Unmarshal(bodyBytes, &dest); err != nil { + return nil, err + } + response.JSON200 = &dest + + } + + return response, nil +} diff --git a/internal/utils/container.go b/internal/utils/container.go new file mode 100644 index 0000000..f785826 --- /dev/null +++ b/internal/utils/container.go @@ -0,0 +1,15 @@ +package utils + +import ( + "github.com/labd/contentful-go" + "github.com/labd/contentful-go/service/cma" + + "github.com/labd/terraform-provider-contentful/internal/sdk" +) + +type ProviderData struct { + Client *sdk.ClientWithResponses + ClientOld *contentful.Client + CMAClient cma.SpaceIdClientBuilder + OrganizationId string +} diff --git a/internal/utils/fields.go b/internal/utils/fields.go new file mode 100644 index 0000000..8306c8b --- /dev/null +++ b/internal/utils/fields.go @@ -0,0 +1,32 @@ +package utils + +import ( + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +func FromOptionalString(value string) basetypes.StringValue { + if value == "" { + return types.StringNull() + } + return types.StringValue(value) +} + +func CompareStringPointer(tfPointer types.String, stringPointer *string) bool { + if stringPointer != nil && tfPointer.ValueStringPointer() == nil { + return false + } + + if tfPointer.ValueStringPointer() != nil { + + if stringPointer == nil { + return false + } + + if tfPointer.ValueString() != *stringPointer { + return false + } + } + + return true +} diff --git a/internal/utils/hcl.go b/internal/utils/hcl.go new file mode 100644 index 0000000..7d8b808 --- /dev/null +++ b/internal/utils/hcl.go @@ -0,0 +1,26 @@ +package utils + +import ( + "bytes" + "os" + "text/template" +) + +func HCLTemplate(data string, params map[string]any) string { + var out bytes.Buffer + tmpl := template.Must(template.New("hcl").Parse(data)) + err := tmpl.Execute(&out, params) + if err != nil { + panic(err) + } + return out.String() +} + +func HCLTemplateFromPath(path string, params map[string]any) string { + data, err := os.ReadFile(path) + if err != nil { + panic(err) + } + + return HCLTemplate(string(data), params) +} diff --git a/internal/utils/http_debug.go b/internal/utils/http_debug.go new file mode 100644 index 0000000..2528c4f --- /dev/null +++ b/internal/utils/http_debug.go @@ -0,0 +1,63 @@ +package utils + +import ( + "context" + "fmt" + "net/http" + "net/http/httputil" + + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +func NewDebugTransport(innerTransport http.RoundTripper) http.RoundTripper { + return &LogTransport{ + transport: innerTransport, + } +} + +type LogTransport struct { + transport http.RoundTripper +} + +var DebugTransport = &LogTransport{ + transport: http.DefaultTransport, +} + +func (c *LogTransport) RoundTrip(request *http.Request) (*http.Response, error) { + logRequest(request.Context(), request) + response, err := c.transport.RoundTrip(request) + logResponse(request.Context(), response, err) + return response, err +} + +const logRequestTemplate = `DEBUG: +---[ REQUEST ]-------------------------------------------------------- +%s +---------------------------------------------------------------------- +` + +const logResponseTemplate = `DEBUG: +---[ RESPONSE ]------------------------------------------------------- +%s +---------------------------------------------------------------------- +` + +func logRequest(ctx context.Context, r *http.Request) { + body, err := httputil.DumpRequestOut(r, true) + if err != nil { + return + } + tflog.Debug(ctx, fmt.Sprintf(logRequestTemplate, body)) +} + +func logResponse(ctx context.Context, r *http.Response, err error) { + if err != nil { + tflog.Debug(ctx, fmt.Sprintf(logResponseTemplate, err)) + return + } + body, err := httputil.DumpResponse(r, true) + if err != nil { + return + } + tflog.Debug(ctx, fmt.Sprintf(logResponseTemplate, body)) +} diff --git a/internal/utils/types.go b/internal/utils/types.go new file mode 100644 index 0000000..3c1aeb1 --- /dev/null +++ b/internal/utils/types.go @@ -0,0 +1,32 @@ +package utils + +var baseContentTypes = []string{ + "Symbol", + "Text", + "RichText", + "Integer", + "Number", + "Date", + "Boolean", + "Object", + "Location", + "Array", + "Link", + "ResourceLink", +} + +func GetContentTypes() []string { + return append(baseContentTypes, "ResourceLink") +} + +func GetAppFieldTypes() []string { + return baseContentTypes +} + +func GetLinkTypes() []string { + return []string{"Asset", "Entry"} +} + +func Pointer[T any](v T) *T { + return &v +} diff --git a/main.go b/main.go index 5ecf31e..692021a 100644 --- a/main.go +++ b/main.go @@ -1,14 +1,83 @@ package main import ( - "github.com/hashicorp/terraform/plugin" - "github.com/hashicorp/terraform/terraform" + "context" + "flag" + "fmt" + "log" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tfprotov6/tf6server" + "github.com/hashicorp/terraform-plugin-mux/tf5to6server" + "github.com/hashicorp/terraform-plugin-mux/tf6muxserver" + + "github.com/labd/terraform-provider-contentful/contentful" + "github.com/labd/terraform-provider-contentful/internal/provider" +) + +// Run "go generate" to format example terraform files and generate the docs for the registry/website + +// If you do not have terraform installed, you can remove the formatting command, but its suggested to +// ensure the documentation is formatted properly. +//go:generate terraform fmt -recursive ./examples/ + +// Run the docs generation tool, check its repository for more information on how it works and how docs +// can be customized. +// +//go:generate go run github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs + +var ( + // these will be set by the goreleaser configuration + // to appropriate values for the compiled binary + version string = "dev" + commit string = "snapshot" ) func main() { - plugin.Serve(&plugin.ServeOpts{ - ProviderFunc: func() terraform.ResourceProvider { - return Provider() + + debugFlag := flag.Bool("debug", false, "Start provider in debug mode.") + flag.Parse() + + fullVersion := fmt.Sprintf("%s (%s)", version, commit) + + sdkProvider := contentful.Provider() + + ctx := context.Background() + + upgradedSdkProvider, err := tf5to6server.UpgradeServer( + ctx, + sdkProvider().GRPCProvider, + ) + + if err != nil { + log.Fatal(err) + } + + providers := []func() tfprotov6.ProviderServer{ + providerserver.NewProtocol6(provider.New(fullVersion, *debugFlag)), + func() tfprotov6.ProviderServer { + return upgradedSdkProvider }, - }) + } + + muxServer, err := tf6muxserver.NewMuxServer(ctx, providers...) + + if err != nil { + log.Fatal(err) + } + + var serveOpts []tf6server.ServeOpt + if *debugFlag { + serveOpts = append(serveOpts, tf6server.WithManagedDebug()) + } + + err = tf6server.Serve( + "registry.terraform.io/labd/contentful", + muxServer.ProviderServer, + serveOpts..., + ) + if err != nil { + log.Fatal(err) + } } diff --git a/oapi-config.yaml b/oapi-config.yaml new file mode 100644 index 0000000..b8382d8 --- /dev/null +++ b/oapi-config.yaml @@ -0,0 +1,7 @@ +package: sdk +generate: + models: true + client: true +output: internal/sdk/main.gen.go +output-options: + skip-prune: true diff --git a/openapi.yaml b/openapi.yaml new file mode 100644 index 0000000..f92e36a --- /dev/null +++ b/openapi.yaml @@ -0,0 +1,1307 @@ +openapi: 3.0.3 +info: + title: Contentful Content Management API + description: | + The Contentful Content Management API (CMA) is a RESTful API for managing content in Contentful. + It allows you to create, update, delete and retrieve content from your Contentful spaces. + version: "1.0.0" + contact: + name: Contentful Support + url: https://www.contentful.com/support/ + +servers: + - url: https://api.contentful.com + description: Contentful Content Management API + +security: + - bearerAuth: [] + +paths: + /spaces: + get: + summary: Get all spaces + description: Retrieves all spaces the authorized user has access to + operationId: getAllSpaces + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/skip" + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/SpaceCollection" + post: + summary: Create a space + description: Creates a new space + operationId: createSpace + parameters: + - $ref: "#/components/parameters/contentTypeHeader" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/SpaceCreate" + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/Space" + + /spaces/{spaceId}: + parameters: + - $ref: "#/components/parameters/spaceId" + get: + summary: Get a space + description: Retrieves a specific space by ID + operationId: getSpace + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Space" + put: + summary: Update a space + description: Updates a space + operationId: updateSpace + parameters: + - $ref: "#/components/parameters/resourceVersion" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/SpaceUpdate" + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Space" + delete: + summary: Delete a space + description: Deletes a space + operationId: deleteSpace + parameters: + - $ref: "#/components/parameters/resourceVersion" + responses: + "204": + description: No Content + + /spaces/{spaceId}/environments: + parameters: + - $ref: "#/components/parameters/spaceId" + get: + summary: Get all environments + description: Retrieves all environments in a space + operationId: getAllEnvironments + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/skip" + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/EnvironmentCollection" + post: + summary: Create an environment + description: Creates a new environment in a space + operationId: createEnvironment + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/EnvironmentCreate" + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/Environment" + + /spaces/{spaceId}/environments/{environmentId}: + parameters: + - $ref: "#/components/parameters/spaceId" + - $ref: "#/components/parameters/environmentId" + get: + summary: Get an environment + description: Retrieves a specific environment by ID + operationId: getEnvironment + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Environment" + put: + summary: Update an environment + description: Updates an environment + operationId: updateEnvironment + parameters: + - $ref: "#/components/parameters/resourceVersion" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/EnvironmentUpdate" + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Environment" + delete: + summary: Delete an environment + description: Deletes an environment + operationId: deleteEnvironment + parameters: + - $ref: "#/components/parameters/resourceVersion" + responses: + "204": + description: No Content + + /spaces/{spaceId}/environments/{environmentId}/content_types: + parameters: + - $ref: "#/components/parameters/spaceId" + - $ref: "#/components/parameters/environmentId" + get: + summary: Get all content types + description: Retrieves all content types in an environment + operationId: getAllContentTypes + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/skip" + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/ContentTypeCollection" + post: + summary: Create a content type + description: Creates a new content type + operationId: createContentType + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/ContentTypeCreate" + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/ContentType" + + /spaces/{spaceId}/environments/{environmentId}/entries: + parameters: + - $ref: "#/components/parameters/spaceId" + - $ref: "#/components/parameters/environmentId" + get: + summary: Get all entries + description: Retrieves all entries in an environment + operationId: getAllEntries + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/skip" + - name: content_type + in: query + schema: + type: string + description: Filter by content type + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/EntryCollection" + post: + summary: Create an entry + description: Creates a new entry + operationId: createEntry + parameters: + - name: content_type + in: query + required: true + schema: + type: string + description: Content type ID for the new entry + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/EntryCreate" + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/Entry" + + /spaces/{spaceId}/environments/{environmentId}/entries/{entryId}: + parameters: + - $ref: "#/components/parameters/spaceId" + - $ref: "#/components/parameters/environmentId" + - $ref: "#/components/parameters/entryId" + + get: + summary: Get an entry + description: Retrieves a specific entry by ID + operationId: getEntry + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Entry" + put: + summary: Update an entry + description: Updates an entry + operationId: updateEntry + parameters: + - $ref: "#/components/parameters/resourceVersion" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/EntryUpdate" + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Entry" + delete: + summary: Delete an entry + description: Deletes an entry + operationId: deleteEntry + parameters: + - $ref: "#/components/parameters/resourceVersion" + responses: + "204": + description: No Content + + /spaces/{spaceId}/environments/{environmentId}/entries/{entryId}/published: + parameters: + - $ref: "#/components/parameters/spaceId" + - $ref: "#/components/parameters/environmentId" + - $ref: "#/components/parameters/entryId" + put: + summary: Publish an entry + description: Publishes an entry + operationId: publishEntry + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Entry" + delete: + summary: Unpublish an entry + description: Unpublishes an entry + operationId: unpublishEntry + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Entry" + + /spaces/{spaceId}/environments/{environmentId}/entries/{entryId}/archived: + parameters: + - $ref: "#/components/parameters/spaceId" + - $ref: "#/components/parameters/environmentId" + - $ref: "#/components/parameters/entryId" + put: + summary: Archive an entry + description: Archives an entry + operationId: archiveEntry + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Entry" + delete: + summary: Unarchive an entry + description: Unarchives an entry + operationId: unarchiveEntry + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Entry" + + /spaces/{spaceId}/environments/{environmentId}/assets: + parameters: + - $ref: "#/components/parameters/spaceId" + - $ref: "#/components/parameters/environmentId" + get: + summary: Get all assets + description: Retrieves all assets in an environment + operationId: getAllAssets + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/skip" + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/AssetCollection" + post: + summary: Create an asset + description: Creates a new asset + operationId: createAsset + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/AssetCreate" + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/Asset" + + /spaces/{spaceId}/webhook_definitions: + parameters: + - $ref: "#/components/parameters/spaceId" + get: + summary: Get all webhooks + description: Retrieves all webhooks in a space + operationId: getAllWebhooks + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/skip" + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/WebhookCollection" + post: + summary: Create a webhook + description: Creates a new webhook in a space + operationId: createWebhook + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/WebhookCreate" + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/Webhook" + + /spaces/{spaceId}/webhook_definitions/{webhookId}: + parameters: + - $ref: "#/components/parameters/spaceId" + - $ref: "#/components/parameters/webhookId" + get: + summary: Get a webhook + description: Retrieves a specific webhook by ID + operationId: getWebhook + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Webhook" + put: + summary: Update a webhook + description: Updates a webhook + operationId: updateWebhook + parameters: + - $ref: "#/components/parameters/resourceVersion" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/WebhookUpdate" + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Webhook" + delete: + summary: Delete a webhook + description: Deletes a webhook + operationId: deleteWebhook + parameters: + - $ref: "#/components/parameters/resourceVersion" + responses: + "204": + description: No Content + + /spaces/{spaceId}/environments/{environmentId}/locales: + parameters: + - $ref: "#/components/parameters/spaceId" + - $ref: "#/components/parameters/environmentId" + get: + summary: Get all locales + description: Retrieves all locales in an environment + operationId: getAllLocales + parameters: + - $ref: "#/components/parameters/limit" + - $ref: "#/components/parameters/skip" + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/LocaleCollection" + post: + summary: Create a locale + description: Creates a new locale in an environment + operationId: createLocale + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/LocaleCreate" + responses: + "201": + description: Created + content: + application/json: + schema: + $ref: "#/components/schemas/Locale" + + /spaces/{spaceId}/environments/{environmentId}/locales/{localeId}: + parameters: + - $ref: "#/components/parameters/spaceId" + - $ref: "#/components/parameters/environmentId" + - $ref: "#/components/parameters/localeId" + get: + summary: Get a locale + description: Retrieves a specific locale by ID + operationId: getLocale + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Locale" + put: + summary: Update a locale + description: Updates a locale + operationId: updateLocale + parameters: + - $ref: "#/components/parameters/resourceVersion" + requestBody: + required: true + content: + application/json: + schema: + $ref: "#/components/schemas/LocaleUpdate" + responses: + "200": + description: Success + content: + application/json: + schema: + $ref: "#/components/schemas/Locale" + delete: + summary: Delete a locale + description: Deletes a locale + operationId: deleteLocale + responses: + "204": + description: No Content + +components: + parameters: + spaceId: + name: spaceId + in: path + required: true + schema: + type: string + description: ID of the space + environmentId: + name: environmentId + in: path + required: true + schema: + type: string + description: ID of the environment + limit: + name: limit + in: query + required: false + schema: + type: integer + minimum: 1 + maximum: 1000 + default: 100 + description: Maximum number of items to return + skip: + name: skip + in: query + required: false + schema: + type: integer + minimum: 0 + default: 0 + description: Number of items to skip + localeId: + name: localeId + in: path + required: true + schema: + type: string + description: ID of the locale + webhookId: + name: webhookId + in: path + required: true + schema: + type: string + description: ID of the webhook + contentTypeHeader: + name: Content-Type + in: header + required: true + schema: + type: string + default: application/vnd.contentful.management.v1+json + description: Contentful Management API version header + entryId: + name: entryId + in: path + required: true + schema: + type: string + description: ID of the environment + resourceVersion: + name: X-Contentful-Version + in: header + required: true + description: The version of the locale to update. + schema: + type: integer + + securitySchemes: + bearerAuth: + type: http + scheme: bearer + bearerFormat: JWT + description: Use your Contentful personal access token + + schemas: + Asset: + type: object + properties: + fields: + properties: + description: + additionalProperties: + type: string + description: Asset description by locale + type: object + file: + additionalProperties: + properties: + contentType: + type: string + details: + properties: + image: + properties: + height: + type: integer + width: + type: integer + type: object + size: + type: integer + type: object + fileName: + type: string + url: + type: string + type: object + description: Asset file details by locale + type: object + title: + additionalProperties: + type: string + description: Asset title by locale + type: object + type: object + sys: + $ref: '#/components/schemas/SystemProperties' + + AssetCollection: + type: object + properties: + items: + items: + $ref: '#/components/schemas/Asset' + type: array + limit: + description: Maximum number of assets returned + type: integer + skip: + description: Number of assets skipped + type: integer + sys: + properties: + type: + enum: [Array] + type: string + type: object + total: + description: Total number of assets + type: integer + + AssetCreate: + type: object + properties: + fields: + properties: + description: + additionalProperties: + type: string + description: Asset description by locale + type: object + file: + additionalProperties: + properties: + contentType: + type: string + fileName: + type: string + upload: + description: Upload URL + type: string + type: object + description: Asset file details by locale + type: object + title: + additionalProperties: + type: string + description: Asset title by locale + type: object + type: object + + ContentType: + type: object + properties: + description: + description: Description of the content type + type: string + displayField: + description: ID of the field to use as the display field + type: string + fields: + items: + $ref: '#/components/schemas/Field' + type: array + name: + description: Name of the content type + type: string + sys: + $ref: '#/components/schemas/SystemProperties' + + ContentTypeCollection: + type: object + properties: + items: + items: + $ref: '#/components/schemas/ContentType' + type: array + limit: + description: Maximum number of content types returned + type: integer + skip: + description: Number of content types skipped + type: integer + sys: + properties: + type: + enum: [Array] + type: string + type: object + total: + description: Total number of content types + type: integer + + ContentTypeCreate: + type: object + properties: + description: + description: Description of the content type + type: string + displayField: + description: ID of the field to use as the display field + type: string + fields: + items: + $ref: '#/components/schemas/Field' + type: array + name: + description: Name of the content type + type: string + required: + - name + - fields + + Entry: + type: object + properties: + fields: + additionalProperties: true + description: Content fields with values by locale + type: object + sys: + $ref: '#/components/schemas/SystemProperties' + + EntryCollection: + type: object + properties: + items: + items: + $ref: '#/components/schemas/Entry' + type: array + limit: + description: Maximum number of entries returned + type: integer + skip: + description: Number of entries skipped + type: integer + sys: + properties: + type: + enum: [Array] + type: string + type: object + total: + description: Total number of entries + type: integer + + EntryCreate: + type: object + properties: + fields: + additionalProperties: true + description: Content fields with values by locale + type: object + + EntryUpdate: + type: object + properties: + fields: + additionalProperties: true + description: Content fields with values by locale + type: object + + Environment: + type: object + properties: + name: + description: Name of the environment + type: string + sys: + $ref: '#/components/schemas/SystemProperties' + + EnvironmentCollection: + type: object + properties: + items: + items: + $ref: '#/components/schemas/Environment' + type: array + limit: + description: Maximum number of environments returned + type: integer + skip: + description: Number of environments skipped + type: integer + sys: + properties: + type: + enum: [Array] + type: string + type: object + total: + description: Total number of environments + type: integer + + EnvironmentCreate: + type: object + properties: + name: + description: Name of the environment to create + type: string + required: + - name + + EnvironmentSystemProperties: + type: object + properties: + sys: + properties: + id: + description: Environment ID + type: string + linkType: + enum: [Environment] + type: string + type: + enum: [Link] + type: string + type: object + + EnvironmentUpdate: + type: object + properties: + name: + description: Updated name for the environment + type: string + required: + - name + + Field: + type: object + properties: + id: + description: ID of the field + type: string + items: + description: Used for Array fields to define the items type + type: object + linkType: + description: For Link fields, defines what type of resource it links to + enum: [Entry, Asset] + type: string + localized: + description: Whether the field is localized + type: boolean + name: + description: Name of the field + type: string + required: + description: Whether the field is required + type: boolean + type: + description: Type of the field + enum: + - Symbol + - Text + - Integer + - Number + - Date + - Location + - Boolean + - Link + - Array + - Object + type: string + required: + - id + - name + - type + + Locale: + type: object + properties: + code: + description: Locale code (e.g., en-US, de-DE) + type: string + contentDeliveryApi: + description: Whether this locale is available in the content delivery API + type: boolean + contentManagementApi: + description: Whether this locale is available in the content management API + type: boolean + default: + description: Whether this is the default locale + type: boolean + fallbackCode: + description: Code of the locale to use as a fallback + type: string + name: + description: Human readable name of the locale + type: string + optional: + description: Whether this locale is optional for content + type: boolean + sys: + $ref: '#/components/schemas/SystemProperties' + + LocaleCollection: + type: object + properties: + items: + items: + $ref: '#/components/schemas/Locale' + type: array + limit: + description: Maximum number of locales returned + type: integer + skip: + description: Number of locales skipped + type: integer + sys: + properties: + type: + enum: [Array] + type: string + type: object + total: + description: Total number of locales + type: integer + + LocaleCreate: + type: object + properties: + code: + description: Locale code (e.g., en-US, de-DE) + type: string + contentDeliveryApi: + default: true + description: Whether this locale is available in the content delivery API + type: boolean + contentManagementApi: + default: true + description: Whether this locale is available in the content management API + type: boolean + default: + default: false + description: Whether this is the default locale + type: boolean + fallbackCode: + description: Code of the locale to use as a fallback + nullable: true + type: string + name: + description: Human readable name of the locale + type: string + optional: + default: false + description: Whether this locale is optional for content + type: boolean + required: + - name + - code + + LocaleUpdate: + type: object + properties: + code: + description: Locale code (e.g., en-US, de-DE) + type: string + contentDeliveryApi: + description: Whether this locale is available in the content delivery API + type: boolean + contentManagementApi: + description: Whether this locale is available in the content management API + type: boolean + fallbackCode: + description: Code of the locale to use as a fallback + nullable: true + type: string + name: + description: Human readable name of the locale + type: string + optional: + description: Whether this locale is optional for content + type: boolean + required: + - name + - code + + Space: + type: object + properties: + defaultLocale: + description: Default locale of the space + type: string + name: + description: Name of the space + type: string + sys: + $ref: '#/components/schemas/SystemProperties' + + SpaceCollection: + type: object + properties: + items: + items: + $ref: '#/components/schemas/Space' + type: array + limit: + description: Maximum number of spaces returned + type: integer + skip: + description: Number of spaces skipped + type: integer + sys: + properties: + type: + enum: [Array] + type: string + type: object + total: + description: Total number of spaces + type: integer + + SpaceCreate: + type: object + properties: + defaultLocale: + default: en-US + description: Default locale for the space + type: string + name: + description: Name of the space to create + type: string + required: + - name + + SpaceUpdate: + type: object + properties: + name: + description: Updated name for the space + type: string + required: + - name + + SystemProperties: + type: object + properties: + archivedAt: + description: Archival timestamp + format: date-time + type: string + contentType: + properties: + sys: + properties: + id: + description: Content type ID + type: string + linkType: + enum: [ContentType] + type: string + type: + enum: [Link] + type: string + type: object + type: object + createdAt: + description: Creation timestamp + format: date-time + type: string + environment: + properties: + sys: + properties: + id: + description: Environment ID + type: string + linkType: + enum: [Environment] + type: string + type: + enum: [Link] + type: string + type: object + type: object + id: + description: Resource ID + type: string + publishedAt: + description: Publication timestamp + format: date-time + type: string + space: + $ref: '#/components/schemas/EnvironmentSystemProperties' + type: object + type: + description: Resource type + type: string + updatedAt: + description: Last update timestamp + format: date-time + type: string + version: + description: Resource version + type: integer + + Webhook: + type: object + properties: + headers: + description: HTTP headers to send with the webhook request + items: + $ref: '#/components/schemas/WebhookHeader' + type: object + type: array + httpBasicPassword: + description: Password for HTTP basic authentication + type: string + httpBasicUsername: + description: Username for HTTP basic authentication + type: string + name: + description: Name of the webhook + type: string + sys: + $ref: '#/components/schemas/SystemProperties' + topics: + description: Events that trigger the webhook + items: + type: string + type: array + url: + description: URL to call when the webhook is triggered + type: string + required: + - name + - url + - topics + - headers + + WebhookCollection: + type: object + properties: + items: + items: + $ref: '#/components/schemas/Webhook' + type: array + limit: + description: Maximum number of webhooks returned + type: integer + skip: + description: Number of webhooks skipped + type: integer + sys: + properties: + type: + enum: [Array] + type: string + type: object + total: + description: Total number of webhooks + type: integer + + WebhookCreate: + type: object + properties: + headers: + description: HTTP headers to send with the webhook request + items: + $ref: '#/components/schemas/WebhookHeader' + type: object + type: array + httpBasicPassword: + description: Password for HTTP basic authentication + type: string + httpBasicUsername: + description: Username for HTTP basic authentication + type: string + name: + description: Name of the webhook + type: string + topics: + description: Events that trigger the webhook + items: + type: string + type: array + url: + description: URL to call when the webhook is triggered + type: string + required: + - name + - url + - topics + + WebhookHeader: + type: object + properties: + key: + type: string + value: + type: string + required: + - key + - value + + WebhookUpdate: + type: object + properties: + headers: + description: HTTP headers to send with the webhook request + items: + $ref: '#/components/schemas/WebhookHeader' + type: object + type: array + httpBasicPassword: + description: Password for HTTP basic authentication + type: string + httpBasicUsername: + description: Username for HTTP basic authentication + type: string + name: + description: Name of the webhook + type: string + topics: + description: Events that trigger the webhook + items: + type: string + type: array + url: + description: URL to call when the webhook is triggered + type: string + required: + - name + - url + - topics diff --git a/provider.go b/provider.go deleted file mode 100644 index 9e47ad8..0000000 --- a/provider.go +++ /dev/null @@ -1,48 +0,0 @@ -package main - -import ( - "os" - - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/terraform" - contentful "github.com/tolgaakyuz/contentful-go" -) - -// Provider does shit -func Provider() terraform.ResourceProvider { - return &schema.Provider{ - Schema: map[string]*schema.Schema{ - "cma_token": &schema.Schema{ - Type: schema.TypeString, - Required: true, - DefaultFunc: schema.EnvDefaultFunc("CONTENTFUL_MANAGEMENT_TOKEN", nil), - Description: "The Contentful Management API token", - }, - "organization_id": &schema.Schema{ - Type: schema.TypeString, - Required: true, - DefaultFunc: schema.EnvDefaultFunc("CONTENTFUL_ORGANIZATION_ID", nil), - Description: "The organization ID", - }, - }, - ResourcesMap: map[string]*schema.Resource{ - "contentful_space": resourceContentfulSpace(), - "contentful_contenttype": resourceContentfulContentType(), - "contentful_apikey": resourceContentfulAPIKey(), - "contentful_webhook": resourceContentfulWebhook(), - "contentful_locale": resourceContentfulLocale(), - }, - ConfigureFunc: providerConfigure, - } -} - -func providerConfigure(d *schema.ResourceData) (interface{}, error) { - cma := contentful.NewCMA(d.Get("cma_token").(string)) - cma.SetOrganization(d.Get("organization_id").(string)) - - if os.Getenv("TF_LOG") != "" { - cma.Debug = true - } - - return cma, nil -} diff --git a/provider_test.go b/provider_test.go deleted file mode 100644 index 2ca91c1..0000000 --- a/provider_test.go +++ /dev/null @@ -1,39 +0,0 @@ -package main - -import ( - "os" - "testing" - - "github.com/hashicorp/terraform/helper/schema" - "github.com/hashicorp/terraform/terraform" -) - -var testAccProviders map[string]terraform.ResourceProvider -var testAccProvider *schema.Provider - -func init() { - testAccProvider = Provider().(*schema.Provider) - testAccProviders = map[string]terraform.ResourceProvider{ - "contentful": testAccProvider, - } -} - -func TestProvider(t *testing.T) { - if err := Provider().(*schema.Provider).InternalValidate(); err != nil { - t.Fatalf("err: %s", err) - } -} - -func TestProvider_impl(t *testing.T) { - var _ terraform.ResourceProvider = Provider() -} - -func testAccPreCheck(t *testing.T) { - var cmaToken, organizationID string - if cmaToken = os.Getenv("CONTENTFUL_MANAGEMENT_TOKEN"); cmaToken == "" { - t.Fatal("CONTENTFUL_MANAGEMENT_TOKEN must set with a valid Contentful Content Management API Token for acceptance tests") - } - if organizationID = os.Getenv("CONTENTFUL_ORGANIZATION_ID"); organizationID == "" { - t.Fatal("CONTENTFUL_ORGANIZATION_ID must set with a valid Contentful Organization ID for acceptance tests") - } -} diff --git a/resource_contentful_apikey.go b/resource_contentful_apikey.go deleted file mode 100644 index 1874f53..0000000 --- a/resource_contentful_apikey.go +++ /dev/null @@ -1,130 +0,0 @@ -package main - -import ( - "github.com/hashicorp/terraform/helper/schema" - contentful "github.com/tolgaakyuz/contentful-go" -) - -func resourceContentfulAPIKey() *schema.Resource { - return &schema.Resource{ - Create: resourceCreateAPIKey, - Read: resourceReadAPIKey, - Update: resourceUpdateAPIKey, - Delete: resourceDeleteAPIKey, - - Schema: map[string]*schema.Schema{ - "version": &schema.Schema{ - Type: schema.TypeInt, - Computed: true, - }, - "space_id": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "description": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - }, - } -} - -func resourceCreateAPIKey(d *schema.ResourceData, m interface{}) (err error) { - client := m.(*contentful.Contentful) - - apiKey := &contentful.APIKey{ - Name: d.Get("name").(string), - Description: d.Get("description").(string), - } - - err = client.APIKeys.Upsert(d.Get("space_id").(string), apiKey) - if err != nil { - return err - } - - if err := setAPIKeyProperties(d, apiKey); err != nil { - return err - } - - d.SetId(apiKey.Sys.ID) - - return nil -} - -func resourceUpdateAPIKey(d *schema.ResourceData, m interface{}) (err error) { - client := m.(*contentful.Contentful) - spaceID := d.Get("space_id").(string) - apiKeyID := d.Id() - - apiKey, err := client.APIKeys.Get(spaceID, apiKeyID) - if err != nil { - return err - } - - apiKey.Name = d.Get("name").(string) - apiKey.Description = d.Get("description").(string) - - err = client.APIKeys.Upsert(spaceID, apiKey) - if err != nil { - return err - } - - if err := setAPIKeyProperties(d, apiKey); err != nil { - return err - } - - d.SetId(apiKey.Sys.ID) - - return nil -} - -func resourceReadAPIKey(d *schema.ResourceData, m interface{}) (err error) { - client := m.(*contentful.Contentful) - spaceID := d.Get("space_id").(string) - apiKeyID := d.Id() - - apiKey, err := client.APIKeys.Get(spaceID, apiKeyID) - if _, ok := err.(contentful.NotFoundError); ok { - d.SetId("") - return nil - } - - return setAPIKeyProperties(d, apiKey) -} - -func resourceDeleteAPIKey(d *schema.ResourceData, m interface{}) (err error) { - client := m.(*contentful.Contentful) - spaceID := d.Get("space_id").(string) - apiKeyID := d.Id() - - apiKey, err := client.APIKeys.Get(spaceID, apiKeyID) - if err != nil { - return err - } - - return client.APIKeys.Delete(spaceID, apiKey) -} - -func setAPIKeyProperties(d *schema.ResourceData, apiKey *contentful.APIKey) error { - if err := d.Set("space_id", apiKey.Sys.Space.Sys.ID); err != nil { - return err - } - - if err := d.Set("version", apiKey.Sys.Version); err != nil { - return err - } - - if err := d.Set("name", apiKey.Name); err != nil { - return err - } - - if err := d.Set("description", apiKey.Description); err != nil { - return err - } - - return nil -} diff --git a/resource_contentful_apikey_test.go b/resource_contentful_apikey_test.go deleted file mode 100644 index bc1542a..0000000 --- a/resource_contentful_apikey_test.go +++ /dev/null @@ -1,153 +0,0 @@ -package main - -import ( - "fmt" - "testing" - - "github.com/hashicorp/terraform/helper/acctest" - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" - contentful "github.com/tolgaakyuz/contentful-go" -) - -func TestAccContentfulAPIKey_Basic(t *testing.T) { - var apiKey contentful.APIKey - - spaceName := fmt.Sprintf("space-name-%s", acctest.RandString(3)) - name := fmt.Sprintf("apikey-name-%s", acctest.RandString(3)) - description := fmt.Sprintf("apikey-description-%s", acctest.RandString(3)) - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccContentfulAPIKeyDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: testAccContentfulAPIKeyConfig(spaceName, name, description), - Check: resource.ComposeTestCheckFunc( - testAccCheckContentfulAPIKeyExists("contentful_apikey.myapikey", &apiKey), - testAccCheckContentfulAPIKeyAttributes(&apiKey, map[string]interface{}{ - "name": name, - "description": description, - }), - ), - }, - resource.TestStep{ - Config: testAccContentfulAPIKeyUpdateConfig(spaceName, name, description), - Check: resource.ComposeTestCheckFunc( - testAccCheckContentfulAPIKeyExists("contentful_apikey.myapikey", &apiKey), - testAccCheckContentfulAPIKeyAttributes(&apiKey, map[string]interface{}{ - "name": fmt.Sprintf("%s-updated", name), - "description": fmt.Sprintf("%s-updated", description), - }), - ), - }, - }, - }) -} - -func testAccCheckContentfulAPIKeyExists(n string, apiKey *contentful.APIKey) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not Found: %s", n) - } - - spaceID := rs.Primary.Attributes["space_id"] - if spaceID == "" { - return fmt.Errorf("No space_id is set") - } - - apiKeyID := rs.Primary.ID - if apiKeyID == "" { - return fmt.Errorf("No api key ID is set") - } - - client := testAccProvider.Meta().(*contentful.Contentful) - - contentfulAPIKey, err := client.APIKeys.Get(spaceID, apiKeyID) - if err != nil { - return err - } - - *apiKey = *contentfulAPIKey - - return nil - } -} - -func testAccCheckContentfulAPIKeyAttributes(apiKey *contentful.APIKey, attrs map[string]interface{}) resource.TestCheckFunc { - return func(s *terraform.State) error { - name := attrs["name"].(string) - if apiKey.Name != name { - return fmt.Errorf("APIKey name does not match: %s, %s", apiKey.Name, name) - } - - description := attrs["description"].(string) - if apiKey.Description != description { - return fmt.Errorf("APIKey description does not match: %s, %s", apiKey.Description, description) - } - - return nil - } -} - -func testAccContentfulAPIKeyDestroy(s *terraform.State) error { - for _, rs := range s.RootModule().Resources { - if rs.Type != "contentful_apikey" { - continue - } - - // get space id from resource data - spaceID := rs.Primary.Attributes["space_id"] - if spaceID == "" { - return fmt.Errorf("No space_id is set") - } - - apiKeyID := rs.Primary.ID - if apiKeyID == "" { - return fmt.Errorf("No apikey ID is set") - } - - client := testAccProvider.Meta().(*contentful.Contentful) - - _, err := client.APIKeys.Get(spaceID, apiKeyID) - if _, ok := err.(contentful.NotFoundError); ok { - return nil - } - - return fmt.Errorf("Api Key still exists with id: %s", rs.Primary.ID) - } - - return nil -} - -func testAccContentfulAPIKeyConfig(spaceName, name, description string) string { - return fmt.Sprintf(` -resource "contentful_space" "myspace" { - name = "%s" -} - -resource "contentful_apikey" "myapikey" { - space_id = "${contentful_space.myspace.id}" - - name = "%s" - description = "%s" -} -`, spaceName, name, description) -} - -func testAccContentfulAPIKeyUpdateConfig(spaceName, name, description string) string { - return fmt.Sprintf(` -resource "contentful_space" "myspace" { - name = "%s" -} - -resource "contentful_apikey" "myapikey" { - space_id = "${contentful_space.myspace.id}" - - name = "%s-updated" - description = "%s-updated" -} -`, spaceName, name, description) -} diff --git a/resource_contentful_contenttype.go b/resource_contentful_contenttype.go deleted file mode 100644 index bee476a..0000000 --- a/resource_contentful_contenttype.go +++ /dev/null @@ -1,361 +0,0 @@ -package main - -import ( - "github.com/hashicorp/terraform/helper/schema" - contentful "github.com/tolgaakyuz/contentful-go" -) - -func resourceContentfulContentType() *schema.Resource { - return &schema.Resource{ - Create: resourceContentTypeCreate, - Read: resourceContentTypeRead, - Update: resourceContentTypeUpdate, - Delete: resourceContentTypeDelete, - - Schema: map[string]*schema.Schema{ - "space_id": &schema.Schema{ - Type: schema.TypeString, - Required: true, - ForceNew: true, - }, - "version": &schema.Schema{ - Type: schema.TypeInt, - Computed: true, - }, - "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "description": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - "display_field": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "field": &schema.Schema{ - Type: schema.TypeSet, - Required: true, - MinItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "id": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - //@TODO Add ValidateFunc to validate field type - "type": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "link_type": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - }, - "items": &schema.Schema{ - Type: schema.TypeSet, - Optional: true, - MaxItems: 1, - Elem: &schema.Resource{ - Schema: map[string]*schema.Schema{ - "type": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "validations": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - "link_type": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - }, - }, - }, - "required": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "localized": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "disabled": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "omitted": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "validations": &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - }, - }, - }, - }, - }, - } -} - -func resourceContentTypeCreate(d *schema.ResourceData, m interface{}) (err error) { - client := m.(*contentful.Contentful) - spaceID := d.Get("space_id").(string) - - ct := &contentful.ContentType{ - Name: d.Get("name").(string), - DisplayField: d.Get("display_field").(string), - Fields: []*contentful.Field{}, - } - - if description, ok := d.GetOk("description"); ok { - ct.Description = description.(string) - } - - for _, rawField := range d.Get("field").(*schema.Set).List() { - field := rawField.(map[string]interface{}) - - contentfulField := &contentful.Field{ - ID: field["id"].(string), - Name: field["name"].(string), - Type: field["type"].(string), - Localized: field["localized"].(bool), - Required: field["required"].(bool), - Disabled: field["disabled"].(bool), - Omitted: field["omitted"].(bool), - } - - if linkType, ok := field["link_type"].(string); ok { - contentfulField.LinkType = linkType - } - - if validations, ok := field["validations"].([]interface{}); ok { - parsedValidations, err := contentful.ParseValidations(validations) - if err != nil { - return err - } - - contentfulField.Validations = parsedValidations - } - - if items := processItems(field["items"].(*schema.Set)); items != nil { - contentfulField.Items = items - } - - ct.Fields = append(ct.Fields, contentfulField) - } - - if err = client.ContentTypes.Upsert(spaceID, ct); err != nil { - return err - } - - if err = client.ContentTypes.Activate(spaceID, ct); err != nil { - //@TODO Maybe delete the CT ? - return err - } - - if err = setContentTypeProperties(d, ct); err != nil { - return err - } - - d.SetId(ct.Sys.ID) - - return nil -} - -func resourceContentTypeRead(d *schema.ResourceData, m interface{}) (err error) { - client := m.(*contentful.Contentful) - spaceID := d.Get("space_id").(string) - - _, err = client.ContentTypes.Get(spaceID, d.Id()) - - return err -} - -func resourceContentTypeUpdate(d *schema.ResourceData, m interface{}) (err error) { - var existingFields []*contentful.Field - var deletedFields []*contentful.Field - - client := m.(*contentful.Contentful) - spaceID := d.Get("space_id").(string) - - ct, err := client.ContentTypes.Get(spaceID, d.Id()) - if err != nil { - return err - } - - ct.Name = d.Get("name").(string) - ct.DisplayField = d.Get("display_field").(string) - - if description, ok := d.GetOk("description"); ok { - ct.Description = description.(string) - } - - if d.HasChange("field") { - old, new := d.GetChange("field") - - existingFields, deletedFields = checkFieldChanges(old.(*schema.Set), new.(*schema.Set)) - - ct.Fields = existingFields - - if deletedFields != nil { - ct.Fields = append(ct.Fields, deletedFields...) - } - } - - // To remove a field from a content type 4 API calls need to be made. - // Ommit 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 - } - - if err = client.ContentTypes.Activate(spaceID, ct); err != nil { - //@TODO Maybe delete the CT ? - return err - } - - if deletedFields != nil { - ct.Fields = existingFields - - if err = client.ContentTypes.Upsert(spaceID, ct); err != nil { - return err - } - - if err = client.ContentTypes.Activate(spaceID, ct); err != nil { - //@TODO Maybe delete the CT ? - return err - } - } - - return setContentTypeProperties(d, ct) -} - -func resourceContentTypeDelete(d *schema.ResourceData, m interface{}) (err error) { - client := m.(*contentful.Contentful) - spaceID := d.Get("space_id").(string) - - ct, err := client.ContentTypes.Get(spaceID, d.Id()) - if err != nil { - return err - } - - err = client.ContentTypes.Deactivate(spaceID, ct) - if err != nil { - return err - } - - if err = client.ContentTypes.Delete(spaceID, ct); err != nil { - return err - } - - return nil -} - -func setContentTypeProperties(d *schema.ResourceData, ct *contentful.ContentType) (err error) { - - if err = d.Set("version", ct.Sys.Version); err != nil { - return err - } - - return nil -} - -func checkFieldChanges(old, new *schema.Set) ([]*contentful.Field, []*contentful.Field) { - var contentfulField *contentful.Field - var existingFields []*contentful.Field - var deletedFields []*contentful.Field - var fieldRemoved bool - - for _, f := range old.List() { - oldField := f.(map[string]interface{}) - - fieldRemoved = true - for _, newField := range new.List() { - if oldField["id"].(string) == newField.(map[string]interface{})["id"].(string) { - fieldRemoved = false - break - } - } - - if fieldRemoved { - deletedFields = append(deletedFields, - &contentful.Field{ - ID: oldField["id"].(string), - Name: oldField["name"].(string), - Type: oldField["type"].(string), - LinkType: oldField["link_type"].(string), - Localized: oldField["localized"].(bool), - Required: oldField["required"].(bool), - Disabled: oldField["disabled"].(bool), - Omitted: true, - }) - } - } - - for _, f := range new.List() { - newField := f.(map[string]interface{}) - - contentfulField = &contentful.Field{ - ID: newField["id"].(string), - Name: newField["name"].(string), - Type: newField["type"].(string), - Localized: newField["localized"].(bool), - Required: newField["required"].(bool), - Disabled: newField["disabled"].(bool), - Omitted: newField["omitted"].(bool), - } - - if linkType, ok := newField["link_type"].(string); ok { - contentfulField.LinkType = linkType - } - - if validations, ok := newField["validations"].([]interface{}); ok { - parsedValidations, _ := contentful.ParseValidations(validations) - - contentfulField.Validations = parsedValidations - } - - if items := processItems(newField["items"].(*schema.Set)); items != nil { - contentfulField.Items = items - } - - existingFields = append(existingFields, contentfulField) - } - - return existingFields, deletedFields -} - -func processItems(fieldItems *schema.Set) *contentful.FieldTypeArrayItem { - var items *contentful.FieldTypeArrayItem - - for _, i := range fieldItems.List() { - item := i.(map[string]interface{}) - - var validations []contentful.FieldValidation - - if fieldValidations, ok := item["validations"].([]interface{}); ok { - validations, _ = contentful.ParseValidations(fieldValidations) - } - - items = &contentful.FieldTypeArrayItem{ - Type: item["type"].(string), - Validations: validations, - LinkType: item["link_type"].(string), - } - } - return items -} diff --git a/resource_contentful_contenttype_test.go b/resource_contentful_contenttype_test.go deleted file mode 100644 index cd1fcc5..0000000 --- a/resource_contentful_contenttype_test.go +++ /dev/null @@ -1,193 +0,0 @@ -package main - -import ( - "fmt" - "testing" - - "github.com/hashicorp/terraform/helper/resource" - "github.com/hashicorp/terraform/terraform" - contentful "github.com/tolgaakyuz/contentful-go" -) - -func TestAccContentfulContentType_Basic(t *testing.T) { - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckContentfulContentTypeDestroy, - Steps: []resource.TestStep{ - resource.TestStep{ - Config: testAccContentfulContentTypeConfig, - Check: resource.TestCheckResourceAttr( - "contentful_contenttype.mycontenttype", "name", "TF Acc Test CT 1"), - }, - resource.TestStep{ - Config: testAccContentfulContentTypeUpdateConfig, - Check: resource.TestCheckResourceAttr( - "contentful_contenttype.mycontenttype", "name", "TF Acc Test CT name change"), - }, - resource.TestStep{ - Config: testAccContentfulContentTypeLinkConfig, - Check: resource.TestCheckResourceAttr( - "contentful_contenttype.mylinked_contenttype", "name", "TF Acc Test Linked CT"), - }, - }, - }) -} - -func testAccCheckContentfulContentTypeExists(n string, contentType *contentful.ContentType) resource.TestCheckFunc { - return func(s *terraform.State) error { - rs, ok := s.RootModule().Resources[n] - if !ok { - return fmt.Errorf("Not found %s", n) - } - - if rs.Primary.ID == "" { - return fmt.Errorf("No content type ID is set") - } - - spaceID := rs.Primary.Attributes["space_id"] - if spaceID == "" { - return fmt.Errorf("No space_id is set") - } - - client := testAccProvider.Meta().(*contentful.Contentful) - - ct, err := client.ContentTypes.Get(spaceID, rs.Primary.ID) - if err != nil { - return err - } - - *contentType = *ct - - return nil - } -} - -func testAccCheckContentfulContentTypeDestroy(s *terraform.State) (err error) { - for _, rs := range s.RootModule().Resources { - if rs.Type != "contentful_contenttype" { - continue - } - - spaceID := rs.Primary.Attributes["space_id"] - if spaceID == "" { - return fmt.Errorf("No space_id is set") - } - - client := testAccProvider.Meta().(*contentful.Contentful) - - _, err := client.ContentTypes.Get(spaceID, rs.Primary.ID) - if _, ok := err.(contentful.NotFoundError); ok { - return nil - } - - return fmt.Errorf("Content Type still exists with id: %s", rs.Primary.ID) - } - - return nil -} - -var testAccContentfulContentTypeConfig = ` -// resource "contentful_space" "myspace" { -// name = "TF Acc Test Space" -// } - -resource "contentful_contenttype" "mycontenttype" { - space_id = "${contentful_space.myspace.id}" - - name = "TF Acc Test CT 1" - description = "Terraform Acc Test Content Type" - display_field = "field1" - - field { - id = "field1" - name = "Field 1" - type = "Text" - required = true - } - - field { - id = "field2" - name = "Field 2" - type = "Integer" - required = false - } -} -` - -var testAccContentfulContentTypeUpdateConfig = ` -resource "contentful_contenttype" "mycontenttype" { - space_id = "${contentful_space.myspace.id}" - - name = "TF Acc Test CT name change" - description = "Terraform Acc Test Content Type description change" - display_field = "field1" - - field { - id = "field1" - name = "Field 1 name change" - type = "Text" - required = true - } - - field { - id = "field3" - name = "Field 3 new field" - type = "Integer" - required = true - } -} -` -var testAccContentfulContentTypeLinkConfig = ` -resource "contentful_contenttype" "mycontenttype" { - space_id = "${contentful_space.myspace.id}" - - name = "TF Acc Test CT name change" - description = "Terraform Acc Test Content Type description change" - display_field = "field1" - - field { - id = "field1" - name = "Field 1 name change" - type = "Text" - required = true - } - - field { - id = "field3" - name = "Field 3 new field" - type = "Integer" - required = true - } -} - -resource "contentful_contenttype" "mylinked_contenttype" { - space_id = "${contentful_space.myspace.id}" - - name = "TF Acc Test Linked CT" - description = "Terraform Acc Test Content Type with links" - display_field = "asset_field" - - field { - id = "asset_field" - name = "Asset Field" - type = "Array" - items { - type = "Link" - link_type = "Asset" - } - required = true - } - - field { - id = "entry_link_field" - name = "Entry Link Field" - type = "Link" - link_type = "Entry" - validations = ["{\"linkContentType\": [\"${contentful_contenttype.mycontenttype.id}\"]}"] - required = false - } - -} - -` diff --git a/resource_contentful_locale.go b/resource_contentful_locale.go deleted file mode 100644 index 9983222..0000000 --- a/resource_contentful_locale.go +++ /dev/null @@ -1,186 +0,0 @@ -package main - -import ( - "github.com/hashicorp/terraform/helper/schema" - contentful "github.com/tolgaakyuz/contentful-go" -) - -func resourceContentfulLocale() *schema.Resource { - return &schema.Resource{ - Create: resourceCreateLocale, - Read: resourceReadLocale, - Update: resourceUpdateLocale, - Delete: resourceDeleteLocale, - - Schema: map[string]*schema.Schema{ - "version": &schema.Schema{ - Type: schema.TypeInt, - Computed: true, - }, - "space_id": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "code": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "fallback_code": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Default: "en-US", - }, - "optional": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - "cda": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - Default: true, - }, - "cma": &schema.Schema{ - Type: schema.TypeBool, - Optional: true, - Default: false, - }, - }, - } -} - -func resourceCreateLocale(d *schema.ResourceData, m interface{}) (err error) { - client := m.(*contentful.Contentful) - spaceID := d.Get("space_id").(string) - - locale := &contentful.Locale{ - Name: d.Get("name").(string), - Code: d.Get("code").(string), - FallbackCode: d.Get("fallback_code").(string), - Optional: d.Get("optional").(bool), - CDA: d.Get("cda").(bool), - CMA: d.Get("cma").(bool), - } - - err = client.Locales.Upsert(spaceID, locale) - if err != nil { - return err - } - - err = setLocaleProperties(d, locale) - if err != nil { - return err - } - - d.SetId(locale.Sys.ID) - - return nil -} - -func resourceReadLocale(d *schema.ResourceData, m interface{}) error { - client := m.(*contentful.Contentful) - spaceID := d.Get("space_id").(string) - localeID := d.Id() - - locale, err := client.Locales.Get(spaceID, localeID) - if _, ok := err.(*contentful.NotFoundError); ok { - d.SetId("") - return nil - } - - if err != nil { - return err - } - - return setLocaleProperties(d, locale) -} - -func resourceUpdateLocale(d *schema.ResourceData, m interface{}) (err error) { - client := m.(*contentful.Contentful) - spaceID := d.Get("space_id").(string) - localeID := d.Id() - - locale, err := client.Locales.Get(spaceID, localeID) - if err != nil { - return err - } - - locale.Name = d.Get("name").(string) - locale.Code = d.Get("code").(string) - locale.FallbackCode = d.Get("fallback_code").(string) - locale.Optional = d.Get("optional").(bool) - locale.CDA = d.Get("cda").(bool) - locale.CMA = d.Get("cma").(bool) - - err = client.Locales.Upsert(spaceID, locale) - if err != nil { - return err - } - - err = setLocaleProperties(d, locale) - if err != nil { - return err - } - - return nil -} - -func resourceDeleteLocale(d *schema.ResourceData, m interface{}) (err error) { - client := m.(*contentful.Contentful) - spaceID := d.Get("space_id").(string) - localeID := d.Id() - - locale, err := client.Locales.Get(spaceID, localeID) - if err != nil { - return err - } - - err = client.Locales.Delete(spaceID, locale) - if _, ok := err.(*contentful.NotFoundError); ok { - return nil - } - - if err != nil { - return err - } - - return nil -} - -func setLocaleProperties(d *schema.ResourceData, locale *contentful.Locale) error { - err := d.Set("name", locale.Name) - if err != nil { - return err - } - - err = d.Set("code", locale.Code) - if err != nil { - return err - } - - err = d.Set("fallback_code", locale.FallbackCode) - if err != nil { - return err - } - - err = d.Set("optional", locale.Optional) - if err != nil { - return err - } - - err = d.Set("cda", locale.CDA) - if err != nil { - return err - } - - err = d.Set("cma", locale.CMA) - if err != nil { - return err - } - - return nil -} diff --git a/resource_contentful_space.go b/resource_contentful_space.go deleted file mode 100644 index ee1d4e1..0000000 --- a/resource_contentful_space.go +++ /dev/null @@ -1,118 +0,0 @@ -package main - -import ( - "github.com/hashicorp/terraform/helper/schema" - contentful "github.com/tolgaakyuz/contentful-go" -) - -func resourceContentfulSpace() *schema.Resource { - return &schema.Resource{ - Create: resourceSpaceCreate, - Read: resourceSpaceRead, - Update: resourceSpaceUpdate, - Delete: resourceSpaceDelete, - - Schema: map[string]*schema.Schema{ - "version": &schema.Schema{ - Type: schema.TypeInt, - Computed: true, - }, - "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - // Space specific props - "default_locale": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Default: "en", - }, - }, - } -} - -func resourceSpaceCreate(d *schema.ResourceData, m interface{}) (err error) { - client := m.(*contentful.Contentful) - - space := &contentful.Space{ - Name: d.Get("name").(string), - DefaultLocale: d.Get("default_locale").(string), - } - - err = client.Spaces.Upsert(space) - if err != nil { - return err - } - - err = updateSpaceProperties(d, space) - if err != nil { - return err - } - - d.SetId(space.Sys.ID) - - return nil -} - -func resourceSpaceRead(d *schema.ResourceData, m interface{}) error { - client := m.(*contentful.Contentful) - spaceID := d.Id() - - _, err := client.Spaces.Get(spaceID) - if _, ok := err.(contentful.NotFoundError); ok { - d.SetId("") - return nil - } - - return err -} - -func resourceSpaceUpdate(d *schema.ResourceData, m interface{}) (err error) { - client := m.(*contentful.Contentful) - spaceID := d.Id() - - space, err := client.Spaces.Get(spaceID) - if err != nil { - return err - } - - space.Name = d.Get("name").(string) - - err = client.Spaces.Upsert(space) - if err != nil { - return err - } - - return updateSpaceProperties(d, space) -} - -func resourceSpaceDelete(d *schema.ResourceData, m interface{}) (err error) { - client := m.(*contentful.Contentful) - spaceID := d.Id() - - space, err := client.Spaces.Get(spaceID) - if err != nil { - return err - } - - err = client.Spaces.Delete(space) - if _, ok := err.(contentful.NotFoundError); ok { - return nil - } - - return err -} - -func updateSpaceProperties(d *schema.ResourceData, space *contentful.Space) error { - err := d.Set("version", space.Sys.Version) - if err != nil { - return err - } - - err = d.Set("name", space.Name) - if err != nil { - return err - } - - return nil -} diff --git a/resource_contentful_webhook.go b/resource_contentful_webhook.go deleted file mode 100644 index e7830bd..0000000 --- a/resource_contentful_webhook.go +++ /dev/null @@ -1,220 +0,0 @@ -package main - -import ( - "github.com/hashicorp/terraform/helper/schema" - contentful "github.com/tolgaakyuz/contentful-go" -) - -func resourceContentfulWebhook() *schema.Resource { - return &schema.Resource{ - Create: resourceCreateWebhook, - Read: resourceReadWebhook, - Update: resourceUpdateWebhook, - Delete: resourceDeleteWebhook, - - Schema: map[string]*schema.Schema{ - "version": &schema.Schema{ - Type: schema.TypeInt, - Computed: true, - }, - "space_id": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "name": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - // Webhook specific props - "url": &schema.Schema{ - Type: schema.TypeString, - Required: true, - }, - "http_basic_auth_username": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Default: "", - }, - "http_basic_auth_password": &schema.Schema{ - Type: schema.TypeString, - Optional: true, - Default: "", - }, - "headers": &schema.Schema{ - Type: schema.TypeMap, - Optional: true, - }, - "topics": &schema.Schema{ - Type: schema.TypeList, - Elem: &schema.Schema{ - Type: schema.TypeString, - }, - MinItems: 1, - Required: true, - }, - }, - } -} - -func resourceCreateWebhook(d *schema.ResourceData, m interface{}) (err error) { - client := m.(*contentful.Contentful) - spaceID := d.Get("space_id").(string) - - webhook := &contentful.Webhook{ - Name: d.Get("name").(string), - URL: d.Get("url").(string), - Topics: transformTopicsToContentfulFormat(d.Get("topics").([]interface{})), - Headers: transformHeadersToContentfulFormat(d.Get("headers")), - HTTPBasicUsername: d.Get("http_basic_auth_username").(string), - HTTPBasicPassword: d.Get("http_basic_auth_password").(string), - } - - err = client.Webhooks.Upsert(spaceID, webhook) - if err != nil { - return err - } - - err = setWebhookProperties(d, webhook) - if err != nil { - return err - } - - d.SetId(webhook.Sys.ID) - - return nil -} - -func resourceUpdateWebhook(d *schema.ResourceData, m interface{}) (err error) { - client := m.(*contentful.Contentful) - spaceID := d.Get("space_id").(string) - webhookID := d.Id() - - webhook, err := client.Webhooks.Get(spaceID, webhookID) - if err != nil { - return err - } - - webhook.Name = d.Get("name").(string) - webhook.URL = d.Get("url").(string) - webhook.Topics = transformTopicsToContentfulFormat(d.Get("topics").([]interface{})) - webhook.Headers = transformHeadersToContentfulFormat(d.Get("headers")) - webhook.HTTPBasicUsername = d.Get("http_basic_auth_username").(string) - webhook.HTTPBasicPassword = d.Get("http_basic_auth_password").(string) - - err = client.Webhooks.Upsert(spaceID, webhook) - if err != nil { - return err - } - - err = setWebhookProperties(d, webhook) - if err != nil { - return err - } - - d.SetId(webhook.Sys.ID) - - return nil -} - -func resourceReadWebhook(d *schema.ResourceData, m interface{}) error { - client := m.(*contentful.Contentful) - spaceID := d.Get("space_id").(string) - webhookID := d.Id() - - webhook, err := client.Webhooks.Get(spaceID, webhookID) - if _, ok := err.(contentful.NotFoundError); ok { - d.SetId("") - return nil - } - - if err != nil { - return err - } - - return setWebhookProperties(d, webhook) -} - -func resourceDeleteWebhook(d *schema.ResourceData, m interface{}) (err error) { - client := m.(*contentful.Contentful) - spaceID := d.Get("space_id").(string) - webhookID := d.Id() - - webhook, err := client.Webhooks.Get(spaceID, webhookID) - if err != nil { - return err - } - - err = client.Webhooks.Delete(spaceID, webhook) - if _, ok := err.(contentful.NotFoundError); ok { - return nil - } - - return err -} - -func setWebhookProperties(d *schema.ResourceData, webhook *contentful.Webhook) (err error) { - headers := make(map[string]string) - for _, entry := range webhook.Headers { - headers[entry.Key] = entry.Value - } - - err = d.Set("headers", headers) - if err != nil { - return err - } - - err = d.Set("space_id", webhook.Sys.Space.Sys.ID) - if err != nil { - return err - } - - err = d.Set("version", webhook.Sys.Version) - if err != nil { - return err - } - - err = d.Set("name", webhook.Name) - if err != nil { - return err - } - - err = d.Set("url", webhook.URL) - if err != nil { - return err - } - - err = d.Set("http_basic_auth_username", webhook.HTTPBasicUsername) - if err != nil { - return err - } - - err = d.Set("topics", webhook.Topics) - if err != nil { - return err - } - - return nil -} - -func transformHeadersToContentfulFormat(headersTerraform interface{}) []*contentful.WebhookHeader { - headers := []*contentful.WebhookHeader{} - - for k, v := range headersTerraform.(map[string]interface{}) { - headers = append(headers, &contentful.WebhookHeader{ - Key: k, - Value: v.(string), - }) - } - - return headers -} - -func transformTopicsToContentfulFormat(topicsTerraform []interface{}) []string { - var topics []string - - for _, v := range topicsTerraform { - topics = append(topics, v.(string)) - } - - return topics -} diff --git a/tools.go b/tools.go new file mode 100644 index 0000000..9ba48a4 --- /dev/null +++ b/tools.go @@ -0,0 +1,11 @@ +//go:build tools +// +build tools + +package main + +import ( + // document generation + + _ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs" + _ "github.com/oapi-codegen/oapi-codegen/v2/cmd/oapi-codegen" +)