From 0097320db3e89ea04153cbff61436474ea578837 Mon Sep 17 00:00:00 2001 From: Nishu Goel Date: Fri, 19 Jan 2024 23:22:05 +0100 Subject: [PATCH] Generate File terraform provider --- .gitattributes | 2 + .github/workflows/release.yml | 41 + .../workflows/speakeasy_sdk_generation.yml | 28 + .github/workflows/test.yml | 81 + .gitignore | 34 + .speakeasy/gen.lock | 121 ++ Makefile | 21 + README.md | 129 ++ USAGE.md | 10 + docs/index.md | 37 + docs/resources/file.md | 56 + docs/resources/upload_file.md | 49 + examples/README.md | 1 + examples/provider/provider.tf | 12 + .../resources/epilot-file_file/resource.tf | 9 + .../epilot-file_upload_file/resource.tf | 4 + gen.yaml | 38 + go.mod | 66 + go.sum | 268 +++ .../boolplanmodifier/suppress_diff.go | 56 + .../float64planmodifier/suppress_diff.go | 56 + .../int64planmodifier/suppress_diff.go | 56 + .../listplanmodifier/suppress_diff.go | 56 + .../mapplanmodifier/suppress_diff.go | 56 + .../numberplanmodifier/suppress_diff.go | 56 + .../objectplanmodifier/suppress_diff.go | 56 + .../setplanmodifier/suppress_diff.go | 56 + .../stringplanmodifier/suppress_diff.go | 56 + internal/planmodifiers/utils/state_check.go | 25 + internal/provider/file_resource.go | 331 ++++ internal/provider/file_resource_sdk.go | 115 ++ internal/provider/provider.go | 117 ++ internal/provider/reflect/diags.go | 115 ++ internal/provider/reflect/doc.go | 9 + .../provider/reflect/generic_attr_value.go | 14 + internal/provider/reflect/helpers.go | 98 + internal/provider/reflect/interfaces.go | 353 ++++ internal/provider/reflect/into.go | 216 +++ internal/provider/reflect/map.go | 188 ++ internal/provider/reflect/number.go | 374 ++++ internal/provider/reflect/options.go | 22 + internal/provider/reflect/outof.go | 94 + internal/provider/reflect/pointer.go | 126 ++ internal/provider/reflect/primitive.go | 111 ++ internal/provider/reflect/slice.go | 214 +++ internal/provider/reflect/struct.go | 266 +++ internal/provider/type_s3_reference.go | 10 + internal/provider/type_versions.go | 7 + internal/provider/uploadfile_resource.go | 236 +++ internal/provider/uploadfile_resource_sdk.go | 35 + internal/provider/utils.go | 86 + internal/sdk/files.go | 1643 +++++++++++++++++ .../pkg/models/operations/accesspubliclink.go | 56 + .../sdk/pkg/models/operations/deletefile.go | 37 + .../pkg/models/operations/deletesession.go | 37 + .../sdk/pkg/models/operations/downloadfile.go | 99 + .../pkg/models/operations/downloadfiles.go | 65 + .../pkg/models/operations/downloads3file.go | 98 + .../models/operations/generatepubliclink.go | 57 + .../operations/getallpubliclinksforfile.go | 70 + .../sdk/pkg/models/operations/getsession.go | 37 + internal/sdk/pkg/models/operations/options.go | 86 + .../sdk/pkg/models/operations/previewfile.go | 87 + .../models/operations/previewpublicfile.go | 96 + .../pkg/models/operations/previews3file.go | 67 + .../pkg/models/operations/previews3fileget.go | 76 + .../pkg/models/operations/revokepubliclink.go | 57 + .../sdk/pkg/models/operations/savefile.go | 47 + .../sdk/pkg/models/operations/uploadfile.go | 115 ++ .../pkg/models/operations/uploadfilepublic.go | 85 + .../sdk/pkg/models/operations/uploadfilev2.go | 67 + .../operations/verifycustomdownloadurl.go | 58 + internal/sdk/pkg/models/sdkerrors/sdkerror.go | 35 + .../pkg/models/shared/deletefilepayload.go | 14 + .../pkg/models/shared/downloadfilespayload.go | 23 + internal/sdk/pkg/models/shared/fileentity.go | 187 ++ internal/sdk/pkg/models/shared/fileupload.go | 50 + internal/sdk/pkg/models/shared/publiclink.go | 33 + internal/sdk/pkg/models/shared/s3reference.go | 22 + internal/sdk/pkg/models/shared/security.go | 22 + .../pkg/models/shared/uploadfilepayload.go | 38 + .../shared/verifycustomdownloadurlpayload.go | 15 + internal/sdk/pkg/types/bigint.go | 21 + internal/sdk/pkg/types/date.go | 90 + internal/sdk/pkg/types/datetime.go | 23 + internal/sdk/pkg/types/decimal.go | 20 + internal/sdk/pkg/types/pointers.go | 10 + internal/sdk/pkg/utils/contenttype.go | 33 + internal/sdk/pkg/utils/form.go | 117 ++ internal/sdk/pkg/utils/headers.go | 102 + internal/sdk/pkg/utils/json.go | 596 ++++++ internal/sdk/pkg/utils/pathparams.go | 145 ++ internal/sdk/pkg/utils/queryparams.go | 178 ++ internal/sdk/pkg/utils/requestbody.go | 396 ++++ internal/sdk/pkg/utils/retries.go | 117 ++ internal/sdk/pkg/utils/security.go | 298 +++ internal/sdk/pkg/utils/utils.go | 162 ++ internal/sdk/sdk.go | 172 ++ internal/sdk/session.go | 187 ++ internal/validators/DateValidator.go | 50 + internal/validators/ExactlyOneChild.go | 53 + internal/validators/JSONParseValidator.go | 51 + internal/validators/RFC3339Validator.go | 50 + .../validators/boolvalidators/not_null.go | 49 + .../validators/float64validators/not_null.go | 49 + .../validators/int64validators/not_null.go | 49 + .../validators/listvalidators/not_null.go | 49 + internal/validators/mapvalidators/not_null.go | 49 + .../validators/numbervalidators/not_null.go | 49 + .../validators/objectvalidators/not_null.go | 49 + internal/validators/setvalidators/not_null.go | 49 + .../validators/stringvalidators/not_null.go | 49 + main.go | 42 + overlay.yaml | 52 + terraform-registry-manifest.json | 6 + tools/tools.go | 10 + 116 files changed, 11574 insertions(+) create mode 100644 .gitattributes create mode 100644 .github/workflows/release.yml create mode 100644 .github/workflows/speakeasy_sdk_generation.yml create mode 100644 .github/workflows/test.yml create mode 100644 .gitignore create mode 100755 .speakeasy/gen.lock create mode 100644 Makefile create mode 100644 README.md create mode 100644 USAGE.md create mode 100644 docs/index.md create mode 100644 docs/resources/file.md create mode 100644 docs/resources/upload_file.md create mode 100644 examples/README.md create mode 100644 examples/provider/provider.tf create mode 100644 examples/resources/epilot-file_file/resource.tf create mode 100644 examples/resources/epilot-file_upload_file/resource.tf create mode 100644 gen.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 internal/planmodifiers/boolplanmodifier/suppress_diff.go create mode 100644 internal/planmodifiers/float64planmodifier/suppress_diff.go create mode 100644 internal/planmodifiers/int64planmodifier/suppress_diff.go create mode 100644 internal/planmodifiers/listplanmodifier/suppress_diff.go create mode 100644 internal/planmodifiers/mapplanmodifier/suppress_diff.go create mode 100644 internal/planmodifiers/numberplanmodifier/suppress_diff.go create mode 100644 internal/planmodifiers/objectplanmodifier/suppress_diff.go create mode 100644 internal/planmodifiers/setplanmodifier/suppress_diff.go create mode 100644 internal/planmodifiers/stringplanmodifier/suppress_diff.go create mode 100644 internal/planmodifiers/utils/state_check.go create mode 100644 internal/provider/file_resource.go create mode 100644 internal/provider/file_resource_sdk.go create mode 100644 internal/provider/provider.go create mode 100644 internal/provider/reflect/diags.go create mode 100644 internal/provider/reflect/doc.go create mode 100644 internal/provider/reflect/generic_attr_value.go create mode 100644 internal/provider/reflect/helpers.go create mode 100644 internal/provider/reflect/interfaces.go create mode 100644 internal/provider/reflect/into.go create mode 100644 internal/provider/reflect/map.go create mode 100644 internal/provider/reflect/number.go create mode 100644 internal/provider/reflect/options.go create mode 100644 internal/provider/reflect/outof.go create mode 100644 internal/provider/reflect/pointer.go create mode 100644 internal/provider/reflect/primitive.go create mode 100644 internal/provider/reflect/slice.go create mode 100644 internal/provider/reflect/struct.go create mode 100644 internal/provider/type_s3_reference.go create mode 100644 internal/provider/type_versions.go create mode 100644 internal/provider/uploadfile_resource.go create mode 100644 internal/provider/uploadfile_resource_sdk.go create mode 100644 internal/provider/utils.go create mode 100644 internal/sdk/files.go create mode 100644 internal/sdk/pkg/models/operations/accesspubliclink.go create mode 100644 internal/sdk/pkg/models/operations/deletefile.go create mode 100644 internal/sdk/pkg/models/operations/deletesession.go create mode 100644 internal/sdk/pkg/models/operations/downloadfile.go create mode 100644 internal/sdk/pkg/models/operations/downloadfiles.go create mode 100644 internal/sdk/pkg/models/operations/downloads3file.go create mode 100644 internal/sdk/pkg/models/operations/generatepubliclink.go create mode 100644 internal/sdk/pkg/models/operations/getallpubliclinksforfile.go create mode 100644 internal/sdk/pkg/models/operations/getsession.go create mode 100644 internal/sdk/pkg/models/operations/options.go create mode 100644 internal/sdk/pkg/models/operations/previewfile.go create mode 100644 internal/sdk/pkg/models/operations/previewpublicfile.go create mode 100644 internal/sdk/pkg/models/operations/previews3file.go create mode 100644 internal/sdk/pkg/models/operations/previews3fileget.go create mode 100644 internal/sdk/pkg/models/operations/revokepubliclink.go create mode 100644 internal/sdk/pkg/models/operations/savefile.go create mode 100644 internal/sdk/pkg/models/operations/uploadfile.go create mode 100644 internal/sdk/pkg/models/operations/uploadfilepublic.go create mode 100644 internal/sdk/pkg/models/operations/uploadfilev2.go create mode 100644 internal/sdk/pkg/models/operations/verifycustomdownloadurl.go create mode 100644 internal/sdk/pkg/models/sdkerrors/sdkerror.go create mode 100644 internal/sdk/pkg/models/shared/deletefilepayload.go create mode 100644 internal/sdk/pkg/models/shared/downloadfilespayload.go create mode 100644 internal/sdk/pkg/models/shared/fileentity.go create mode 100644 internal/sdk/pkg/models/shared/fileupload.go create mode 100644 internal/sdk/pkg/models/shared/publiclink.go create mode 100644 internal/sdk/pkg/models/shared/s3reference.go create mode 100644 internal/sdk/pkg/models/shared/security.go create mode 100644 internal/sdk/pkg/models/shared/uploadfilepayload.go create mode 100644 internal/sdk/pkg/models/shared/verifycustomdownloadurlpayload.go create mode 100644 internal/sdk/pkg/types/bigint.go create mode 100644 internal/sdk/pkg/types/date.go create mode 100644 internal/sdk/pkg/types/datetime.go create mode 100644 internal/sdk/pkg/types/decimal.go create mode 100644 internal/sdk/pkg/types/pointers.go create mode 100644 internal/sdk/pkg/utils/contenttype.go create mode 100644 internal/sdk/pkg/utils/form.go create mode 100644 internal/sdk/pkg/utils/headers.go create mode 100644 internal/sdk/pkg/utils/json.go create mode 100644 internal/sdk/pkg/utils/pathparams.go create mode 100644 internal/sdk/pkg/utils/queryparams.go create mode 100644 internal/sdk/pkg/utils/requestbody.go create mode 100644 internal/sdk/pkg/utils/retries.go create mode 100644 internal/sdk/pkg/utils/security.go create mode 100644 internal/sdk/pkg/utils/utils.go create mode 100644 internal/sdk/sdk.go create mode 100644 internal/sdk/session.go create mode 100644 internal/validators/DateValidator.go create mode 100644 internal/validators/ExactlyOneChild.go create mode 100644 internal/validators/JSONParseValidator.go create mode 100644 internal/validators/RFC3339Validator.go create mode 100644 internal/validators/boolvalidators/not_null.go create mode 100644 internal/validators/float64validators/not_null.go create mode 100644 internal/validators/int64validators/not_null.go create mode 100644 internal/validators/listvalidators/not_null.go create mode 100644 internal/validators/mapvalidators/not_null.go create mode 100644 internal/validators/numbervalidators/not_null.go create mode 100644 internal/validators/objectvalidators/not_null.go create mode 100644 internal/validators/setvalidators/not_null.go create mode 100644 internal/validators/stringvalidators/not_null.go create mode 100644 main.go create mode 100644 overlay.yaml create mode 100644 terraform-registry-manifest.json create mode 100644 tools/tools.go diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..e6a9944 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +# This allows generated code to be indexed correctly +*.go linguist-generated=false \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..4f73143 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,41 @@ +# Terraform Provider release workflow. +name: Release + +# This GitHub action creates a release when a tag that matches the pattern +# "v*" (e.g. v0.1.0) is created. +on: + push: + tags: + - 'v*' + +# Releases need permissions to read and write the repository contents. +# GitHub considers creating releases and uploading assets as writing contents. +permissions: + contents: write + +jobs: + goreleaser: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 + with: + # Allow goreleaser to access older tag information. + fetch-depth: 0 + - uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 + with: + go-version-file: 'go.mod' + cache: true + - name: Import GPG key + uses: crazy-max/ghaction-import-gpg@111c56156bcc6918c056dbef52164cfa583dc549 # v5.2.0 + id: import_gpg + with: + gpg_private_key: ${{ secrets.TERRAFORM_GPG_PRIVATE_KEY }} + passphrase: ${{ secrets.TERRAFORM_GPG_PASSPHRASE }} + - name: Run GoReleaser + uses: goreleaser/goreleaser-action@f82d6c1c344bcacabba2c841718984797f664a6b # v4.2.0 + with: + args: release --clean + env: + # GitHub sets the GITHUB_TOKEN secret automatically. + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} diff --git a/.github/workflows/speakeasy_sdk_generation.yml b/.github/workflows/speakeasy_sdk_generation.yml new file mode 100644 index 0000000..0511da6 --- /dev/null +++ b/.github/workflows/speakeasy_sdk_generation.yml @@ -0,0 +1,28 @@ +name: Generate + +on: + workflow_dispatch: # Allows manual triggering of the workflow to generate SDK + inputs: + force: + description: "Force generation of SDKs" + type: boolean + default: false + schedule: + - cron: 0 0 * * * # Runs every day at midnight + +jobs: + generate: + uses: speakeasy-api/sdk-generation-action/.github/workflows/sdk-generation.yaml@v14 + with: + speakeasy_version: latest + openapi_docs: | + - https://docs.api.epilot.io/file.yaml + overlay_docs: | + - ./overlay.yaml + languages: | + - terraform + mode: pr + force: ${{ github.event.inputs.force }} + secrets: + github_access_token: ${{ secrets.GITHUB_TOKEN }} + speakeasy_api_key: ${{ secrets.SPEAKEASY_API_KEY }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..34f2a08 --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,81 @@ +# Terraform Provider testing workflow. +name: Tests + +# This GitHub action runs your tests for each pull request and push. +# Optionally, you can turn it on using a schedule for regular testing. +on: + pull_request: + paths-ignore: + - 'README.md' + push: + paths-ignore: + - 'README.md' + +# Testing only needs permissions to read the repository contents. +permissions: + contents: read + +jobs: + # Ensure project builds before running testing matrix + build: + name: Build + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 + - uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 + with: + go-version-file: 'go.mod' + cache: true + - run: go mod download + - run: go build -v . + - name: Run linters + uses: golangci/golangci-lint-action@08e2f20817b15149a52b5b3ebe7de50aff2ba8c5 # v3.4.0 + with: + version: latest + + generate: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 + - uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 + with: + go-version-file: 'go.mod' + cache: true + - run: go generate ./... + - name: git diff + run: | + git diff --compact-summary --exit-code || \ + (echo; echo "Unexpected difference in directories after code generation. Run 'go generate ./...' command and commit."; exit 1) + + # Run acceptance tests in a matrix with Terraform CLI versions + test: + name: Terraform Provider Acceptance Tests + needs: build + runs-on: ubuntu-latest + timeout-minutes: 15 + strategy: + fail-fast: false + matrix: + # list whatever Terraform versions here you would like to support + terraform: + - '1.0.*' + - '1.1.*' + - '1.2.*' + - '1.3.*' + - '1.4.*' + steps: + - uses: actions/checkout@8f4b7f84864484a7bf31766abe9204da3cbe65b3 # v3.5.0 + - uses: actions/setup-go@4d34df0c2316fe8122ab82dc22947d607c0c91f9 # v4.0.0 + with: + go-version-file: 'go.mod' + cache: true + - uses: hashicorp/setup-terraform@633666f66e0061ca3b725c73b2ec20cd13a8fdd1 # v2.0.3 + with: + terraform_version: ${{ matrix.terraform }} + terraform_wrapper: false + - run: go mod download + - env: + TF_ACC: "1" + run: go test -v -cover ./internal/provider/ + timeout-minutes: 10 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..58719ef --- /dev/null +++ b/.gitignore @@ -0,0 +1,34 @@ +# .gitignore +.terraform +.terraform* +*.tfstate* +# Local .terraform directories +**/.terraform/* +# .tfstate files +*.tfstate +*.tfstate.* +# Crash log files +crash.log +crash.*.log +# Exclude all .tfvars files, which are likely to contain sensitive data, such as +# password, private keys, and other secrets. These should not be part of version +# control as they are data points which are potentially sensitive and subject +# to change depending on the environment. +*.tfvars +*.tfvars.json +# Ignore override files as they are usually used to override resources locally and so +# are not checked in +override.tf +override.tf.json +*_override.tf +*_override.tf.json +# Include override files you do wish to add to version control using negated pattern +# !example_override.tf +# Include tfplan files to ignore the plan output of command: terraform plan -out=tfplan +# example: *tfplan* +# Ignore CLI configuration files +.terraformrc +terraform.rc +.DS_Store + +terraform-provider-epilot-file \ No newline at end of file diff --git a/.speakeasy/gen.lock b/.speakeasy/gen.lock new file mode 100755 index 0000000..cf1d7e5 --- /dev/null +++ b/.speakeasy/gen.lock @@ -0,0 +1,121 @@ +lockVersion: 2.0.0 +id: 11c8545a-deb6-44f9-ba56-e71722af6a51 +management: + docChecksum: 4ceee81932bc3b2685969447fa208c0e + docVersion: 0.1.0 + speakeasyVersion: internal + generationVersion: 2.230.1 + releaseVersion: 0.0.1 + configChecksum: 173a2dbe4b228f5fa5f509353452c303 +features: + terraform: + constsAndDefaults: 0.1.2 + core: 3.8.1 + globalSecurity: 2.81.2 + globalServerURLs: 2.82.1 + retries: 2.81.1 +generatedFiles: + - internal/sdk/files.go + - internal/sdk/session.go + - internal/sdk/sdk.go + - examples/README.md + - go.mod + - go.sum + - internal/planmodifiers/boolplanmodifier/suppress_diff.go + - internal/planmodifiers/float64planmodifier/suppress_diff.go + - internal/planmodifiers/int64planmodifier/suppress_diff.go + - internal/planmodifiers/listplanmodifier/suppress_diff.go + - internal/planmodifiers/mapplanmodifier/suppress_diff.go + - internal/planmodifiers/numberplanmodifier/suppress_diff.go + - internal/planmodifiers/objectplanmodifier/suppress_diff.go + - internal/planmodifiers/setplanmodifier/suppress_diff.go + - internal/planmodifiers/stringplanmodifier/suppress_diff.go + - internal/planmodifiers/utils/state_check.go + - internal/provider/reflect/diags.go + - internal/provider/reflect/doc.go + - internal/provider/reflect/generic_attr_value.go + - internal/provider/reflect/helpers.go + - internal/provider/reflect/interfaces.go + - internal/provider/reflect/into.go + - internal/provider/reflect/map.go + - internal/provider/reflect/number.go + - internal/provider/reflect/options.go + - internal/provider/reflect/outof.go + - internal/provider/reflect/pointer.go + - internal/provider/reflect/primitive.go + - internal/provider/reflect/slice.go + - internal/provider/reflect/struct.go + - internal/provider/utils.go + - internal/sdk/pkg/models/sdkerrors/sdkerror.go + - internal/sdk/pkg/types/bigint.go + - internal/sdk/pkg/types/date.go + - internal/sdk/pkg/types/datetime.go + - internal/sdk/pkg/types/decimal.go + - internal/sdk/pkg/types/pointers.go + - internal/sdk/pkg/utils/contenttype.go + - internal/sdk/pkg/utils/form.go + - internal/sdk/pkg/utils/headers.go + - internal/sdk/pkg/utils/json.go + - internal/sdk/pkg/utils/pathparams.go + - internal/sdk/pkg/utils/queryparams.go + - internal/sdk/pkg/utils/requestbody.go + - internal/sdk/pkg/utils/retries.go + - internal/sdk/pkg/utils/security.go + - internal/sdk/pkg/utils/utils.go + - internal/validators/DateValidator.go + - internal/validators/ExactlyOneChild.go + - internal/validators/JSONParseValidator.go + - internal/validators/RFC3339Validator.go + - internal/validators/boolvalidators/not_null.go + - internal/validators/float64validators/not_null.go + - internal/validators/int64validators/not_null.go + - internal/validators/listvalidators/not_null.go + - internal/validators/mapvalidators/not_null.go + - internal/validators/numbervalidators/not_null.go + - internal/validators/objectvalidators/not_null.go + - internal/validators/setvalidators/not_null.go + - internal/validators/stringvalidators/not_null.go + - main.go + - terraform-registry-manifest.json + - tools/tools.go + - internal/sdk/pkg/models/operations/accesspubliclink.go + - internal/sdk/pkg/models/operations/deletefile.go + - internal/sdk/pkg/models/operations/downloadfile.go + - internal/sdk/pkg/models/operations/downloadfiles.go + - internal/sdk/pkg/models/operations/downloads3file.go + - internal/sdk/pkg/models/operations/generatepubliclink.go + - internal/sdk/pkg/models/operations/getallpubliclinksforfile.go + - internal/sdk/pkg/models/operations/previewfile.go + - internal/sdk/pkg/models/operations/previewpublicfile.go + - internal/sdk/pkg/models/operations/previews3file.go + - internal/sdk/pkg/models/operations/previews3fileget.go + - internal/sdk/pkg/models/operations/revokepubliclink.go + - internal/sdk/pkg/models/operations/savefile.go + - internal/sdk/pkg/models/operations/uploadfile.go + - internal/sdk/pkg/models/operations/uploadfilepublic.go + - internal/sdk/pkg/models/operations/uploadfilev2.go + - internal/sdk/pkg/models/operations/verifycustomdownloadurl.go + - internal/sdk/pkg/models/operations/deletesession.go + - internal/sdk/pkg/models/operations/getsession.go + - internal/sdk/pkg/models/shared/deletefilepayload.go + - internal/sdk/pkg/models/shared/s3reference.go + - internal/sdk/pkg/models/shared/downloadfilespayload.go + - internal/sdk/pkg/models/shared/publiclink.go + - internal/sdk/pkg/models/shared/fileentity.go + - internal/sdk/pkg/models/shared/uploadfilepayload.go + - internal/sdk/pkg/models/shared/fileupload.go + - internal/sdk/pkg/models/shared/verifycustomdownloadurlpayload.go + - internal/sdk/pkg/models/shared/security.go + - internal/provider/type_s3_reference.go + - internal/provider/type_versions.go + - USAGE.md + - internal/provider/provider.go + - examples/provider/provider.tf + - internal/provider/file_resource.go + - internal/provider/file_resource_sdk.go + - examples/resources/epilot-file_file/resource.tf + - internal/provider/uploadfile_resource.go + - internal/provider/uploadfile_resource_sdk.go + - examples/resources/epilot-file_upload_file/resource.tf + - internal/sdk/pkg/models/operations/options.go + - .gitattributes diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..587cd66 --- /dev/null +++ b/Makefile @@ -0,0 +1,21 @@ +.PHONY: all docs +all: speakeasy docs + +original.yaml: + curl https://docs.api.epilot.io/file.yaml > original.yaml + +original_modified.yaml: original.yaml + cp original.yaml original_modified.yaml + +overlay.yaml: + speakeasy overlay compare -s original.yaml -s original_modified.yaml > overlay.yaml + +speakeasy: + $(eval TMP := $(shell mktemp -d)) + curl https://docs.api.epilot.io/file.yaml > $(TMP)/openapi.yaml + speakeasy overlay apply -s $(TMP)/openapi.yaml -o overlay.yaml > $(TMP)/final.yaml + speakeasy generate sdk --lang terraform -o . -s $(TMP)/final.yaml + +docs: + go generate ./... + diff --git a/README.md b/README.md new file mode 100644 index 0000000..e87d335 --- /dev/null +++ b/README.md @@ -0,0 +1,129 @@ +# epilot-file + +
+ +
+ + + +To install this provider, copy and paste this code into your Terraform configuration. Then, run `terraform init`. + +```hcl +terraform { + required_providers { + epilot-file = { + source = "epilot-dev/epilot-file" + version = "0.2.0" + } + } +} + +provider "epilot-file" { + # Configuration options +} +``` + + + + +### Testing the provider locally + +Should you want to validate a change locally, the `--debug` flag allows you to execute the provider against a terraform instance locally. + +This also allows for debuggers (e.g. delve) to be attached to the provider. + +### Example + +```sh +go run main.go --debug +# Copy the TF_REATTACH_PROVIDERS env var +# In a new terminal +cd examples/your-example +TF_REATTACH_PROVIDERS=... terraform init +TF_REATTACH_PROVIDERS=... terraform apply +``` + + + + + + + + +## SDK Installation + +To install this provider, copy and paste this code into your Terraform configuration. Then, run `terraform init`. + +```hcl +terraform { + required_providers { + epilot-file = { + source = "epilot-dev/epilot-file" + version = "0.0.1" + } + } +} + +provider "epilot-file" { + # Configuration options +} +``` + + + +## SDK Example Usage + +### Testing the provider locally + +Should you want to validate a change locally, the `--debug` flag allows you to execute the provider against a terraform instance locally. + +This also allows for debuggers (e.g. delve) to be attached to the provider. + +### Example + +```sh +go run main.go --debug +# Copy the TF_REATTACH_PROVIDERS env var +# In a new terminal +cd examples/your-example +TF_REATTACH_PROVIDERS=... terraform init +TF_REATTACH_PROVIDERS=... terraform apply +``` + + + +## Available Resources and Operations + + + + + + +Terraform allows you to use local provider builds by setting a `dev_overrides` block in a configuration file called `.terraformrc`. This block overrides all other configured installation methods. + +Terraform searches for the `.terraformrc` file in your home directory and applies any configuration settings you set. + +``` +provider_installation { + + dev_overrides { + "registry.terraform.io/epilot-dev/epilot-file" = "" + } + + # For all other providers, install them directly from their origin provider + # registries as normal. If you omit this, Terraform will _only_ use + # the dev_overrides block, and so no other providers will be available. + direct {} +} +``` + +Your `` may vary depending on how your Go environment variables are configured. Execute `go env GOBIN` to set it, then set the `` to the value returned. If nothing is returned, set it to the default location, `$HOME/go/bin`. + +Note: To use the dev_overrides, please ensure you run `go build` in this folder. You must have a binary available for terraform to find. + +### Contributions + +While we value open-source contributions to this SDK, this library is generated programmatically. +Feel free to open a PR or a Github issue as a proof of concept and we'll do our best to include it in a future release! + +### SDK Created by [Speakeasy](https://docs.speakeasyapi.dev/docs/using-speakeasy/client-sdks) diff --git a/USAGE.md b/USAGE.md new file mode 100644 index 0000000..8e707bb --- /dev/null +++ b/USAGE.md @@ -0,0 +1,10 @@ + +```sh +go run main.go --debug +# Copy the TF_REATTACH_PROVIDERS env var +# In a new terminal +cd examples/your-example +TF_REATTACH_PROVIDERS=... terraform init +TF_REATTACH_PROVIDERS=... terraform apply +``` + \ No newline at end of file diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..a5a126e --- /dev/null +++ b/docs/index.md @@ -0,0 +1,37 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "epilot-file Provider" +subcategory: "" +description: |- + File API: Upload and manage all files stored in epilot +--- + +# epilot-file Provider + +File API: Upload and manage all files stored in epilot + +## Example Usage + +```terraform +terraform { + required_providers { + epilot-file = { + source = "epilot-dev/epilot-file" + version = "0.0.1" + } + } +} + +provider "epilot-file" { + # Configuration options +} +``` + + +## Schema + +### Optional + +- `cookie_auth` (String, Sensitive) +- `epilot_auth` (String, Sensitive) +- `server_url` (String) Server URL (defaults to https://file.sls.epilot.io) diff --git a/docs/resources/file.md b/docs/resources/file.md new file mode 100644 index 0000000..ce5008c --- /dev/null +++ b/docs/resources/file.md @@ -0,0 +1,56 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "epilot-file_file Resource - terraform-provider-epilot-file" +subcategory: "" +description: |- + File Resource +--- + +# epilot-file_file (Resource) + +File Resource + +## Example Usage + +```terraform +resource "epilot-file_file" "my_file" { + id = "ef7d985c-2385-44f4-9c71-ae06a52264f8" + access_control = "private" + filename = "document.pdf" + mime_type = "application/pdf" + public_url = "https://epilot-files-prod.s3.eu-central-1.amazonaws.com/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf" + size_bytes = 1 + type = "font" +} +``` + + +## Schema + +### Optional + +- `access_control` (String) Requires replacement if changed. ; must be one of ["private", "public-read"]; Default: "private" +- `filename` (String) Requires replacement if changed. +- `id` (String) Requires replacement if changed. +- `mime_type` (String) MIME type of the file. Requires replacement if changed. +- `public_url` (String) Direct URL for file (public only if file access control is public-read). Requires replacement if changed. +- `size_bytes` (Number) File size in bytes. Requires replacement if changed. +- `type` (String) Human readable type for file. Requires replacement if changed. ; must be one of ["document", "document_template", "text", "image", "video", "audio", "spreadsheet", "presentation", "font", "archive", "application", "unknown"] +- `versions` (Attributes List) Requires replacement if changed. (see [below for nested schema](#nestedatt--versions)) + + +### Nested Schema for `versions` + +Optional: + +- `s3ref` (Attributes) Requires replacement if changed. (see [below for nested schema](#nestedatt--versions--s3ref)) + + +### Nested Schema for `versions.s3ref` + +Optional: + +- `bucket` (String) Requires replacement if changed. ; Not Null +- `key` (String) Requires replacement if changed. ; Not Null + + diff --git a/docs/resources/upload_file.md b/docs/resources/upload_file.md new file mode 100644 index 0000000..43ed409 --- /dev/null +++ b/docs/resources/upload_file.md @@ -0,0 +1,49 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "epilot-file_upload_file Resource - terraform-provider-epilot-file" +subcategory: "" +description: |- + UploadFile Resource +--- + +# epilot-file_upload_file (Resource) + +UploadFile Resource + +## Example Usage + +```terraform +resource "epilot-file_upload_file" "my_uploadfile" { + file_entity_id = "ef7d985c-2385-44f4-9c71-ae06a52264f8" + filename = "document.pdf" + mime_type = "application/pdf" +} +``` + + +## Schema + +### Required + +- `filename` (String) Requires replacement if changed. + +### Optional + +- `file_entity_id` (String) file entity id. Requires replacement if changed. +- `mime_type` (String) MIME type of file. Requires replacement if changed. ; Default: "application/octet-stream" + +### Read-Only + +- `public_url` (String) Returned only if file is permanent i.e. file_entity_id is passed +- `s3ref` (Attributes) (see [below for nested schema](#nestedatt--s3ref)) +- `upload_url` (String) + + +### Nested Schema for `s3ref` + +Read-Only: + +- `bucket` (String) +- `key` (String) + + diff --git a/examples/README.md b/examples/README.md new file mode 100644 index 0000000..f87f5c1 --- /dev/null +++ b/examples/README.md @@ -0,0 +1 @@ +# TODO \ No newline at end of file diff --git a/examples/provider/provider.tf b/examples/provider/provider.tf new file mode 100644 index 0000000..69c8384 --- /dev/null +++ b/examples/provider/provider.tf @@ -0,0 +1,12 @@ +terraform { + required_providers { + epilot-file = { + source = "epilot-dev/epilot-file" + version = "0.0.1" + } + } +} + +provider "epilot-file" { + # Configuration options +} \ No newline at end of file diff --git a/examples/resources/epilot-file_file/resource.tf b/examples/resources/epilot-file_file/resource.tf new file mode 100644 index 0000000..6b303c2 --- /dev/null +++ b/examples/resources/epilot-file_file/resource.tf @@ -0,0 +1,9 @@ +resource "epilot-file_file" "my_file" { + id = "ef7d985c-2385-44f4-9c71-ae06a52264f8" + access_control = "private" + filename = "document.pdf" + mime_type = "application/pdf" + public_url = "https://epilot-files-prod.s3.eu-central-1.amazonaws.com/123/4d689aeb-1497-4410-a9fe-b36ca9ac4389/document.pdf" + size_bytes = 1 + type = "font" +} \ No newline at end of file diff --git a/examples/resources/epilot-file_upload_file/resource.tf b/examples/resources/epilot-file_upload_file/resource.tf new file mode 100644 index 0000000..468cff8 --- /dev/null +++ b/examples/resources/epilot-file_upload_file/resource.tf @@ -0,0 +1,4 @@ +resource "epilot-file_upload_file" "my_uploadfile" { + filename = "document.pdf" + mime_type = "application/pdf" +} \ No newline at end of file diff --git a/gen.yaml b/gen.yaml new file mode 100644 index 0000000..e273b98 --- /dev/null +++ b/gen.yaml @@ -0,0 +1,38 @@ +configVersion: 2.0.0 +generation: + sdkClassName: SDK + usageSnippets: + optionalPropertyRendering: withExample + fixes: + nameResolutionDec2023: false + telemetryEnabled: false +go: + version: 0.0.1 + clientServerStatusCodesAsErrors: true + flattenGlobalSecurity: true + imports: + option: openapi + paths: + callbacks: models/callbacks + errors: models/sdkerrors + operations: models/operations + shared: models/components + webhooks: models/webhooks + inputModelSuffix: input + maxMethodParams: 4 + outputModelSuffix: output + packageName: openapi +terraform: + version: 0.0.1 + author: epilot-dev + imports: + option: openapi + paths: + callbacks: callbacks + errors: sdkerrors + operations: operations + shared: shared + webhooks: webhooks + inputModelSuffix: input + outputModelSuffix: output + packageName: epilot-file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..6b7b0dc --- /dev/null +++ b/go.mod @@ -0,0 +1,66 @@ +module github.com/epilot-dev/terraform-provider-epilot-file + +go 1.18 + +require ( + github.com/cenkalti/backoff/v4 v4.2.0 + github.com/ericlagergren/decimal v0.0.0-20221120152707-495c53812d05 + github.com/hashicorp/terraform-plugin-framework v1.3.5 + github.com/hashicorp/terraform-plugin-go v0.18.0 + github.com/spyzhov/ajson v0.9.0 +) + +require ( + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.1.1 // indirect + github.com/Masterminds/sprig/v3 v3.2.2 // indirect + github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect + github.com/armon/go-radix v1.0.0 // indirect + github.com/bgentry/speakeasy v0.1.0 // indirect + github.com/fatih/color v1.13.0 // indirect + github.com/golang/protobuf v1.5.3 // indirect + github.com/google/uuid v1.3.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-hclog v1.5.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-plugin v1.4.10 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/go-version v1.6.0 // indirect + github.com/hashicorp/hc-install v0.4.0 // indirect + github.com/hashicorp/terraform-exec v0.17.2 // indirect + github.com/hashicorp/terraform-json v0.14.0 // indirect + github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect + github.com/hashicorp/terraform-registry-address v0.2.1 // indirect + github.com/hashicorp/terraform-svchost v0.1.1 // indirect + github.com/huandu/xstrings v1.3.2 // indirect + github.com/imdario/mergo v0.3.13 // indirect + github.com/mattn/go-colorable v0.1.12 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/mitchellh/cli v1.1.4 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/oklog/run v1.0.0 // indirect + github.com/posener/complete v1.2.3 // indirect + github.com/russross/blackfriday v1.6.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/vmihailenco/msgpack/v5 v5.3.5 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/zclconf/go-cty v1.13.1 // indirect + golang.org/x/crypto v0.10.0 // indirect + golang.org/x/net v0.11.0 // indirect + golang.org/x/sys v0.9.0 // indirect + golang.org/x/text v0.10.0 // indirect + google.golang.org/grpc v1.56.1 // indirect + google.golang.org/protobuf v1.31.0 // indirect +) + +require ( + github.com/hashicorp/terraform-plugin-docs v0.13.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.10.0 + github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect + google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..853448b --- /dev/null +++ b/go.sum @@ -0,0 +1,268 @@ +github.com/Masterminds/goutils v1.1.0/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +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.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/sprig/v3 v3.2.0/go.mod h1:tWhwTbUTndesPNeF0C900vKoq283u6zp4APT9vaF3SI= +github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= +github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= +github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= +github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/cFDk= +github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7 h1:YoJbenK9C67SkzkDfmQuVln04ygHj3vjZfd9FL+GmQQ= +github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo= +github.com/acomagu/bufpipe v1.0.3 h1:fxAGrHZTgQ9w5QqVItgzwj235/uYZYgbXitB+dLupOk= +github.com/acomagu/bufpipe v1.0.3/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= +github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= +github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= +github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +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/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/cenkalti/backoff/v4 v4.2.0 h1:HN5dHm3WBOgndBH6E8V0q2jIYIR3s9yglV8k/+MN3u4= +github.com/cenkalti/backoff/v4 v4.2.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= +github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= +github.com/ericlagergren/decimal v0.0.0-20221120152707-495c53812d05 h1:S92OBrGuLLZsyM5ybUzgc/mPjIYk2AZqufieooe98uw= +github.com/ericlagergren/decimal v0.0.0-20221120152707-495c53812d05/go.mod h1:M9R1FoZ3y//hwwnJtO51ypFGwm8ZfpxPT/ZLtO1mcgQ= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= +github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= +github.com/go-git/gcfg v1.5.0/go.mod h1:5m20vg6GwYabIxaOonVkTdrILxQMpEShl1xiMF4ua+E= +github.com/go-git/go-billy/v5 v5.2.0/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-billy/v5 v5.3.1 h1:CPiOUAzKtMRvolEKw+bG1PLRpT7D3LIs3/3ey4Aiu34= +github.com/go-git/go-billy/v5 v5.3.1/go.mod h1:pmpqyWchKfYfrkb/UVH4otLvyi/5gJlGI4Hb3ZqZ3W0= +github.com/go-git/go-git-fixtures/v4 v4.2.1/go.mod h1:K8zd3kDUAykwTdDCr+I0per6Y6vMiRR/nnVTBtavnB0= +github.com/go-git/go-git/v5 v5.4.2 h1:BXyZu9t0VkbiHtqrsvdq39UDhGJTl1h55VW6CSC4aY4= +github.com/go-git/go-git/v5 v5.4.2/go.mod h1:gQ1kArt6d+n+BGd+/B/I74HwRTLhth2+zti4ihgckDc= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= +github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +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.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +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-hclog v1.5.0 h1:bI2ocEMgcVlz55Oj1xZNBsVi900c7II+fWDyV9o+13c= +github.com/hashicorp/go-hclog v1.5.0/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.4.10 h1:xUbmA4jC6Dq163/fWcp8P3JuHilrHHMLNRxzGQJ9hNk= +github.com/hashicorp/go-plugin v1.4.10/go.mod h1:6/1TEzT0eQznvI/gV2CM29DLSkAK/e58mUWKVsPaph0= +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.5.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= +github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hc-install v0.4.0 h1:cZkRFr1WVa0Ty6x5fTvL1TuO1flul231rWkGH92oYYk= +github.com/hashicorp/hc-install v0.4.0/go.mod h1:5d155H8EC5ewegao9A4PUTMNPZaq+TbOzkJJZ4vrXeI= +github.com/hashicorp/terraform-exec v0.17.2 h1:EU7i3Fh7vDUI9nNRdMATCEfnm9axzTnad8zszYZ73Go= +github.com/hashicorp/terraform-exec v0.17.2/go.mod h1:tuIbsL2l4MlwwIZx9HPM+LOV9vVyEfBYu2GsO1uH3/8= +github.com/hashicorp/terraform-json v0.14.0 h1:sh9iZ1Y8IFJLx+xQiKHGud6/TSUCM0N8e17dKDpqV7s= +github.com/hashicorp/terraform-json v0.14.0/go.mod h1:5A9HIWPkk4e5aeeXIBbkcOvaZbIYnAIkEyqP2pNSckM= +github.com/hashicorp/terraform-plugin-docs v0.13.0 h1:6e+VIWsVGb6jYJewfzq2ok2smPzZrt1Wlm9koLeKazY= +github.com/hashicorp/terraform-plugin-docs v0.13.0/go.mod h1:W0oCmHAjIlTHBbvtppWHe8fLfZ2BznQbuv8+UD8OucQ= +github.com/hashicorp/terraform-plugin-framework v1.3.5 h1:FJ6s3CVWVAxlhiF/jhy6hzs4AnPHiflsp9KgzTGl1wo= +github.com/hashicorp/terraform-plugin-framework v1.3.5/go.mod h1:2gGDpWiTI0irr9NSTLFAKlTi6KwGti3AoU19rFqU30o= +github.com/hashicorp/terraform-plugin-framework-validators v0.10.0 h1:4L0tmy/8esP6OcvocVymw52lY0HyQ5OxB7VNl7k4bS0= +github.com/hashicorp/terraform-plugin-framework-validators v0.10.0/go.mod h1:qdQJCdimB9JeX2YwOpItEu+IrfoJjWQ5PhLpAOMDQAE= +github.com/hashicorp/terraform-plugin-go v0.18.0 h1:IwTkOS9cOW1ehLd/rG0y+u/TGLK9y6fGoBjXVUquzpE= +github.com/hashicorp/terraform-plugin-go v0.18.0/go.mod h1:l7VK+2u5Kf2y+A+742GX0ouLut3gttudmvMgN0PA74Y= +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-registry-address v0.2.1 h1:QuTf6oJ1+WSflJw6WYOHhLgwUiQ0FrROpHPYFtwTYWM= +github.com/hashicorp/terraform-registry-address v0.2.1/go.mod h1:BSE9fIFzp0qWsJUUyGquo4ldV9k2n+psif6NYkBRS3Y= +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.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= +github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= +github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/huandu/xstrings v1.3.2 h1:L18LIDzqlW6xN2rEkpdV8+oL/IXWJ1APd+vsdYy4Wdw= +github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= +github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= +github.com/imdario/mergo v0.3.13 h1:lFzP57bqS/wsqKssCGmtLAb8A0wKjLGrve2q3PPVcBk= +github.com/imdario/mergo v0.3.13/go.mod h1:4lJ1jqUDcsbIECGy0RUJAXNIhg+6ocWgb1ALK2O4oXg= +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/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= +github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= +github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +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/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12 h1:jF+Du6AlPIjs2BiUiQlKOX0rt3SujHxPnksPKZbaA40= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mitchellh/cli v1.1.4 h1:qj8czE26AU4PbiaPXK5uVmMSM+V5BYsFBiM9HhGRLUA= +github.com/mitchellh/cli v1.1.4/go.mod h1:vTLESy5mRhKOs9KDp0/RATawxP1UqBmdrpVRMnpcvKQ= +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-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +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/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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +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/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= +github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= +github.com/sebdah/goldie v1.0.0/go.mod h1:jXP4hmWywNEwZzhMuv2ccnqTSFpuq8iyQhtQdkkZBH4= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spyzhov/ajson v0.9.0 h1:tF46gJGOenYVj+k9K1U1XpCxVWhmiyY5PsVCAs1+OJ0= +github.com/spyzhov/ajson v0.9.0/go.mod h1:a6oSw0MMb7Z5aD2tPoPO+jq11ETKgXUr2XktHdT8Wt8= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/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.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= +github.com/vmihailenco/msgpack/v5 v5.3.5 h1:5gO0H1iULLWGhs2H5tbAHIZTV8/cYafcFOr9znI5mJU= +github.com/vmihailenco/msgpack/v5 v5.3.5/go.mod h1:7xyJ9e+0+9SaZT0Wt1RGleJXzli6Q/V5KbhBonMG9jc= +github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= +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/xanzy/ssh-agent v0.3.0 h1:wUMzuKtKilRgBAD1sUb8gOwwRr2FGoBVumcjoOACClI= +github.com/xanzy/ssh-agent v0.3.0/go.mod h1:3s9xbODqPuuhK9JV1R321M/FlMZSBvE5aY6eAcqrDh0= +github.com/zclconf/go-cty v1.2.0/go.mod h1:hOPWgoHbaTUnI5k4D2ld+GRpFJSCe6bCM7m1q/N4PQ8= +github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= +github.com/zclconf/go-cty v1.13.1 h1:0a6bRwuiSHtAmqCqNOE+c2oHgepv0ctoxU4FUe43kwc= +github.com/zclconf/go-cty v1.13.1/go.mod h1:YKQzy/7pZ7iq2jNFzy5go57xdxdWoLLpaEp4u238AE0= +github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +golang.org/x/crypto v0.0.0-20190219172222-a4c6cb3142f2/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20200414173820-0848c9571904/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.10.0 h1:LKqV2xt9+kDzSTfOhx4FrkEBcMrAgHSYgzywV9zcGmM= +golang.org/x/crypto v0.10.0/go.mod h1:o4eNf7Ede1fv+hwOwZsTHl9EsPFO6q6ZvYR8vYfY45I= +golang.org/x/net v0.0.0-20180811021610-c39426892332/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-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= +golang.org/x/net v0.11.0 h1:Gi2tvZIJyBtO9SDr1q9h5hEQCp/4L2RQ+ar0qjx2oNU= +golang.org/x/net v0.11.0/go.mod h1:2L/ixqYpgIVXmeoSA/4Lu7BzTG4KIyPIryS4IsOd1oQ= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/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-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/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-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= +golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.10.0 h1:UpjohKhiEgNc0CSauXmwYftY1+LlaC75SJwh0SgCX58= +golang.org/x/text v0.10.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= +google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= +google.golang.org/grpc v1.56.1 h1:z0dNfjIl0VpaZ9iSVjA6daGatAYwPGstTjt5vkRMFkQ= +google.golang.org/grpc v1.56.1/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= +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.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= +google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +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-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +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.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.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0/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= diff --git a/internal/planmodifiers/boolplanmodifier/suppress_diff.go b/internal/planmodifiers/boolplanmodifier/suppress_diff.go new file mode 100644 index 0000000..0947467 --- /dev/null +++ b/internal/planmodifiers/boolplanmodifier/suppress_diff.go @@ -0,0 +1,56 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package boolplanmodifier + +import ( + "context" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/planmodifiers/utils" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +const ( + // ExplicitSuppress strategy suppresses "(known after changes)" messages unless we're in the initial creation + ExplicitSuppress = iota +) + +// SuppressDiff returns a plan modifier that propagates a state value into the planned value, when it is Known, and the Plan Value is Unknown +func SuppressDiff(strategy int) planmodifier.Bool { + return suppressDiff{ + strategy: strategy, + } +} + +// suppressDiff implements the plan modifier. +type suppressDiff struct { + strategy int +} + +// Description returns a human-readable description of the plan modifier. +func (m suppressDiff) Description(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (m suppressDiff) MarkdownDescription(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// PlanModifyBool implements the plan modification logic. +func (m suppressDiff) PlanModifyBool(ctx context.Context, req planmodifier.BoolRequest, resp *planmodifier.BoolResponse) { + // Do nothing if there is a known planned value. + if !req.PlanValue.IsUnknown() { + return + } + + // Do nothing if there is an unknown configuration value + if req.ConfigValue.IsUnknown() { + return + } + + if utils.IsAllStateUnknown(ctx, req.State) { + return + } + + resp.PlanValue = req.StateValue +} diff --git a/internal/planmodifiers/float64planmodifier/suppress_diff.go b/internal/planmodifiers/float64planmodifier/suppress_diff.go new file mode 100644 index 0000000..f87549a --- /dev/null +++ b/internal/planmodifiers/float64planmodifier/suppress_diff.go @@ -0,0 +1,56 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package float64planmodifier + +import ( + "context" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/planmodifiers/utils" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +const ( + // ExplicitSuppress strategy suppresses "(known after changes)" messages unless we're in the initial creation + ExplicitSuppress = iota +) + +// SuppressDiff returns a plan modifier that propagates a state value into the planned value, when it is Known, and the Plan Value is Unknown +func SuppressDiff(strategy int) planmodifier.Float64 { + return suppressDiff{ + strategy: strategy, + } +} + +// suppressDiff implements the plan modifier. +type suppressDiff struct { + strategy int +} + +// Description returns a human-readable description of the plan modifier. +func (m suppressDiff) Description(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (m suppressDiff) MarkdownDescription(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// PlanModifyFloat64 implements the plan modification logic. +func (m suppressDiff) PlanModifyFloat64(ctx context.Context, req planmodifier.Float64Request, resp *planmodifier.Float64Response) { + // Do nothing if there is a known planned value. + if !req.PlanValue.IsUnknown() { + return + } + + // Do nothing if there is an unknown configuration value + if req.ConfigValue.IsUnknown() { + return + } + + if utils.IsAllStateUnknown(ctx, req.State) { + return + } + + resp.PlanValue = req.StateValue +} diff --git a/internal/planmodifiers/int64planmodifier/suppress_diff.go b/internal/planmodifiers/int64planmodifier/suppress_diff.go new file mode 100644 index 0000000..e552c1d --- /dev/null +++ b/internal/planmodifiers/int64planmodifier/suppress_diff.go @@ -0,0 +1,56 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package int64planmodifier + +import ( + "context" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/planmodifiers/utils" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +const ( + // ExplicitSuppress strategy suppresses "(known after changes)" messages unless we're in the initial creation + ExplicitSuppress = iota +) + +// SuppressDiff returns a plan modifier that propagates a state value into the planned value, when it is Known, and the Plan Value is Unknown +func SuppressDiff(strategy int) planmodifier.Int64 { + return suppressDiff{ + strategy: strategy, + } +} + +// suppressDiff implements the plan modifier. +type suppressDiff struct { + strategy int +} + +// Description returns a human-readable description of the plan modifier. +func (m suppressDiff) Description(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (m suppressDiff) MarkdownDescription(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// PlanModifyInt64 implements the plan modification logic. +func (m suppressDiff) PlanModifyInt64(ctx context.Context, req planmodifier.Int64Request, resp *planmodifier.Int64Response) { + // Do nothing if there is a known planned value. + if !req.PlanValue.IsUnknown() { + return + } + + // Do nothing if there is an unknown configuration value + if req.ConfigValue.IsUnknown() { + return + } + + if utils.IsAllStateUnknown(ctx, req.State) { + return + } + + resp.PlanValue = req.StateValue +} diff --git a/internal/planmodifiers/listplanmodifier/suppress_diff.go b/internal/planmodifiers/listplanmodifier/suppress_diff.go new file mode 100644 index 0000000..a536933 --- /dev/null +++ b/internal/planmodifiers/listplanmodifier/suppress_diff.go @@ -0,0 +1,56 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package listplanmodifier + +import ( + "context" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/planmodifiers/utils" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +const ( + // ExplicitSuppress strategy suppresses "(known after changes)" messages unless we're in the initial creation + ExplicitSuppress = iota +) + +// SuppressDiff returns a plan modifier that propagates a state value into the planned value, when it is Known, and the Plan Value is Unknown +func SuppressDiff(strategy int) planmodifier.List { + return suppressDiff{ + strategy: strategy, + } +} + +// suppressDiff implements the plan modifier. +type suppressDiff struct { + strategy int +} + +// Description returns a human-readable description of the plan modifier. +func (m suppressDiff) Description(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (m suppressDiff) MarkdownDescription(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// PlanModifyList implements the plan modification logic. +func (m suppressDiff) PlanModifyList(ctx context.Context, req planmodifier.ListRequest, resp *planmodifier.ListResponse) { + // Do nothing if there is a known planned value. + if !req.PlanValue.IsUnknown() { + return + } + + // Do nothing if there is an unknown configuration value + if req.ConfigValue.IsUnknown() { + return + } + + if utils.IsAllStateUnknown(ctx, req.State) { + return + } + + resp.PlanValue = req.StateValue +} diff --git a/internal/planmodifiers/mapplanmodifier/suppress_diff.go b/internal/planmodifiers/mapplanmodifier/suppress_diff.go new file mode 100644 index 0000000..1cd771b --- /dev/null +++ b/internal/planmodifiers/mapplanmodifier/suppress_diff.go @@ -0,0 +1,56 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package mapplanmodifier + +import ( + "context" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/planmodifiers/utils" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +const ( + // ExplicitSuppress strategy suppresses "(known after changes)" messages unless we're in the initial creation + ExplicitSuppress = iota +) + +// SuppressDiff returns a plan modifier that propagates a state value into the planned value, when it is Known, and the Plan Value is Unknown +func SuppressDiff(strategy int) planmodifier.Map { + return suppressDiff{ + strategy: strategy, + } +} + +// suppressDiff implements the plan modifier. +type suppressDiff struct { + strategy int +} + +// Description returns a human-readable description of the plan modifier. +func (m suppressDiff) Description(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (m suppressDiff) MarkdownDescription(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// PlanModifyMap implements the plan modification logic. +func (m suppressDiff) PlanModifyMap(ctx context.Context, req planmodifier.MapRequest, resp *planmodifier.MapResponse) { + // Do nothing if there is a known planned value. + if !req.PlanValue.IsUnknown() { + return + } + + // Do nothing if there is an unknown configuration value + if req.ConfigValue.IsUnknown() { + return + } + + if utils.IsAllStateUnknown(ctx, req.State) { + return + } + + resp.PlanValue = req.StateValue +} diff --git a/internal/planmodifiers/numberplanmodifier/suppress_diff.go b/internal/planmodifiers/numberplanmodifier/suppress_diff.go new file mode 100644 index 0000000..47c39b2 --- /dev/null +++ b/internal/planmodifiers/numberplanmodifier/suppress_diff.go @@ -0,0 +1,56 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package numberplanmodifier + +import ( + "context" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/planmodifiers/utils" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +const ( + // ExplicitSuppress strategy suppresses "(known after changes)" messages unless we're in the initial creation + ExplicitSuppress = iota +) + +// SuppressDiff returns a plan modifier that propagates a state value into the planned value, when it is Known, and the Plan Value is Unknown +func SuppressDiff(strategy int) planmodifier.Number { + return suppressDiff{ + strategy: strategy, + } +} + +// suppressDiff implements the plan modifier. +type suppressDiff struct { + strategy int +} + +// Description returns a human-readable description of the plan modifier. +func (m suppressDiff) Description(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (m suppressDiff) MarkdownDescription(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// PlanModifyNumber implements the plan modification logic. +func (m suppressDiff) PlanModifyNumber(ctx context.Context, req planmodifier.NumberRequest, resp *planmodifier.NumberResponse) { + // Do nothing if there is a known planned value. + if !req.PlanValue.IsUnknown() { + return + } + + // Do nothing if there is an unknown configuration value + if req.ConfigValue.IsUnknown() { + return + } + + if utils.IsAllStateUnknown(ctx, req.State) { + return + } + + resp.PlanValue = req.StateValue +} diff --git a/internal/planmodifiers/objectplanmodifier/suppress_diff.go b/internal/planmodifiers/objectplanmodifier/suppress_diff.go new file mode 100644 index 0000000..8cab698 --- /dev/null +++ b/internal/planmodifiers/objectplanmodifier/suppress_diff.go @@ -0,0 +1,56 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package objectplanmodifier + +import ( + "context" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/planmodifiers/utils" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +const ( + // ExplicitSuppress strategy suppresses "(known after changes)" messages unless we're in the initial creation + ExplicitSuppress = iota +) + +// SuppressDiff returns a plan modifier that propagates a state value into the planned value, when it is Known, and the Plan Value is Unknown +func SuppressDiff(strategy int) planmodifier.Object { + return suppressDiff{ + strategy: strategy, + } +} + +// suppressDiff implements the plan modifier. +type suppressDiff struct { + strategy int +} + +// Description returns a human-readable description of the plan modifier. +func (m suppressDiff) Description(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (m suppressDiff) MarkdownDescription(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// PlanModifyObject implements the plan modification logic. +func (m suppressDiff) PlanModifyObject(ctx context.Context, req planmodifier.ObjectRequest, resp *planmodifier.ObjectResponse) { + // Do nothing if there is a known planned value. + if !req.PlanValue.IsUnknown() { + return + } + + // Do nothing if there is an unknown configuration value + if req.ConfigValue.IsUnknown() { + return + } + + if utils.IsAllStateUnknown(ctx, req.State) { + return + } + + resp.PlanValue = req.StateValue +} diff --git a/internal/planmodifiers/setplanmodifier/suppress_diff.go b/internal/planmodifiers/setplanmodifier/suppress_diff.go new file mode 100644 index 0000000..de05ba0 --- /dev/null +++ b/internal/planmodifiers/setplanmodifier/suppress_diff.go @@ -0,0 +1,56 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package setplanmodifier + +import ( + "context" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/planmodifiers/utils" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +const ( + // ExplicitSuppress strategy suppresses "(known after changes)" messages unless we're in the initial creation + ExplicitSuppress = iota +) + +// SuppressDiff returns a plan modifier that propagates a state value into the planned value, when it is Known, and the Plan Value is Unknown +func SuppressDiff(strategy int) planmodifier.Set { + return suppressDiff{ + strategy: strategy, + } +} + +// suppressDiff implements the plan modifier. +type suppressDiff struct { + strategy int +} + +// Description returns a human-readable description of the plan modifier. +func (m suppressDiff) Description(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (m suppressDiff) MarkdownDescription(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// PlanModifySet implements the plan modification logic. +func (m suppressDiff) PlanModifySet(ctx context.Context, req planmodifier.SetRequest, resp *planmodifier.SetResponse) { + // Do nothing if there is a known planned value. + if !req.PlanValue.IsUnknown() { + return + } + + // Do nothing if there is an unknown configuration value + if req.ConfigValue.IsUnknown() { + return + } + + if utils.IsAllStateUnknown(ctx, req.State) { + return + } + + resp.PlanValue = req.StateValue +} diff --git a/internal/planmodifiers/stringplanmodifier/suppress_diff.go b/internal/planmodifiers/stringplanmodifier/suppress_diff.go new file mode 100644 index 0000000..a175df2 --- /dev/null +++ b/internal/planmodifiers/stringplanmodifier/suppress_diff.go @@ -0,0 +1,56 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package stringplanmodifier + +import ( + "context" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/planmodifiers/utils" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +const ( + // ExplicitSuppress strategy suppresses "(known after changes)" messages unless we're in the initial creation + ExplicitSuppress = iota +) + +// SuppressDiff returns a plan modifier that propagates a state value into the planned value, when it is Known, and the Plan Value is Unknown +func SuppressDiff(strategy int) planmodifier.String { + return suppressDiff{ + strategy: strategy, + } +} + +// suppressDiff implements the plan modifier. +type suppressDiff struct { + strategy int +} + +// Description returns a human-readable description of the plan modifier. +func (m suppressDiff) Description(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (m suppressDiff) MarkdownDescription(_ context.Context) string { + return "Once set, the value of this attribute in state will not change." +} + +// PlanModifyString implements the plan modification logic. +func (m suppressDiff) PlanModifyString(ctx context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) { + // Do nothing if there is a known planned value. + if !req.PlanValue.IsUnknown() { + return + } + + // Do nothing if there is an unknown configuration value + if req.ConfigValue.IsUnknown() { + return + } + + if utils.IsAllStateUnknown(ctx, req.State) { + return + } + + resp.PlanValue = req.StateValue +} diff --git a/internal/planmodifiers/utils/state_check.go b/internal/planmodifiers/utils/state_check.go new file mode 100644 index 0000000..66d9150 --- /dev/null +++ b/internal/planmodifiers/utils/state_check.go @@ -0,0 +1,25 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package utils + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/tfsdk" +) + +func IsAllStateUnknown(ctx context.Context, state tfsdk.State) bool { + attrs := state.Schema.GetAttributes() + anyFound := false + for k, _ := range attrs { + attrValue := new(attr.Value) + state.GetAttribute(ctx, path.Root(k), attrValue) + if attrValue != nil && !(*attrValue).IsUnknown() && !(*attrValue).IsNull() { + anyFound = true + break + } + } + + return !anyFound +} diff --git a/internal/provider/file_resource.go b/internal/provider/file_resource.go new file mode 100644 index 0000000..e4ced68 --- /dev/null +++ b/internal/provider/file_resource.go @@ -0,0 +1,331 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package provider + +import ( + "context" + "fmt" + speakeasy_int64planmodifier "github.com/epilot-dev/terraform-provider-epilot-file/internal/planmodifiers/int64planmodifier" + speakeasy_listplanmodifier "github.com/epilot-dev/terraform-provider-epilot-file/internal/planmodifiers/listplanmodifier" + speakeasy_objectplanmodifier "github.com/epilot-dev/terraform-provider-epilot-file/internal/planmodifiers/objectplanmodifier" + speakeasy_stringplanmodifier "github.com/epilot-dev/terraform-provider-epilot-file/internal/planmodifiers/stringplanmodifier" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk" + speakeasy_stringvalidators "github.com/epilot-dev/terraform-provider-epilot-file/internal/validators/stringvalidators" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" + "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/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &FileResource{} +var _ resource.ResourceWithImportState = &FileResource{} + +func NewFileResource() resource.Resource { + return &FileResource{} +} + +// FileResource defines the resource implementation. +type FileResource struct { + client *sdk.SDK +} + +// FileResourceModel describes the resource data model. +type FileResourceModel struct { + ID types.String `tfsdk:"id"` + AccessControl types.String `tfsdk:"access_control"` + Filename types.String `tfsdk:"filename"` + MimeType types.String `tfsdk:"mime_type"` + PublicURL types.String `tfsdk:"public_url"` + SizeBytes types.Int64 `tfsdk:"size_bytes"` + Type types.String `tfsdk:"type"` + Versions []Versions `tfsdk:"versions"` +} + +func (r *FileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_file" +} + +func (r *FileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "File Resource", + + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + speakeasy_stringplanmodifier.SuppressDiff(speakeasy_stringplanmodifier.ExplicitSuppress), + }, + Optional: true, + Description: `Requires replacement if changed. `, + }, + "access_control": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + speakeasy_stringplanmodifier.SuppressDiff(speakeasy_stringplanmodifier.ExplicitSuppress), + }, + Optional: true, + Description: `Requires replacement if changed. ; must be one of ["private", "public-read"]; Default: "private"`, + Validators: []validator.String{ + stringvalidator.OneOf( + "private", + "public-read", + ), + }, + }, + "filename": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + speakeasy_stringplanmodifier.SuppressDiff(speakeasy_stringplanmodifier.ExplicitSuppress), + }, + Optional: true, + Description: `Requires replacement if changed. `, + }, + "mime_type": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + speakeasy_stringplanmodifier.SuppressDiff(speakeasy_stringplanmodifier.ExplicitSuppress), + }, + Optional: true, + Description: `MIME type of the file. Requires replacement if changed. `, + }, + "public_url": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + speakeasy_stringplanmodifier.SuppressDiff(speakeasy_stringplanmodifier.ExplicitSuppress), + }, + Optional: true, + Description: `Direct URL for file (public only if file access control is public-read). Requires replacement if changed. `, + }, + "size_bytes": schema.Int64Attribute{ + Computed: true, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplaceIfConfigured(), + speakeasy_int64planmodifier.SuppressDiff(speakeasy_int64planmodifier.ExplicitSuppress), + }, + Optional: true, + Description: `File size in bytes. Requires replacement if changed. `, + }, + "type": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + speakeasy_stringplanmodifier.SuppressDiff(speakeasy_stringplanmodifier.ExplicitSuppress), + }, + Optional: true, + Description: `Human readable type for file. Requires replacement if changed. ; must be one of ["document", "document_template", "text", "image", "video", "audio", "spreadsheet", "presentation", "font", "archive", "application", "unknown"]`, + Validators: []validator.String{ + stringvalidator.OneOf( + "document", + "document_template", + "text", + "image", + "video", + "audio", + "spreadsheet", + "presentation", + "font", + "archive", + "application", + "unknown", + ), + }, + }, + "versions": schema.ListNestedAttribute{ + Computed: true, + PlanModifiers: []planmodifier.List{ + listplanmodifier.RequiresReplaceIfConfigured(), + speakeasy_listplanmodifier.SuppressDiff(speakeasy_listplanmodifier.ExplicitSuppress), + }, + Optional: true, + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "s3ref": schema.SingleNestedAttribute{ + Computed: true, + PlanModifiers: []planmodifier.Object{ + objectplanmodifier.RequiresReplaceIfConfigured(), + speakeasy_objectplanmodifier.SuppressDiff(speakeasy_objectplanmodifier.ExplicitSuppress), + }, + Optional: true, + Attributes: map[string]schema.Attribute{ + "bucket": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + speakeasy_stringplanmodifier.SuppressDiff(speakeasy_stringplanmodifier.ExplicitSuppress), + }, + Optional: true, + Description: `Requires replacement if changed. ; Not Null`, + Validators: []validator.String{ + speakeasy_stringvalidators.NotNull(), + }, + }, + "key": schema.StringAttribute{ + Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + speakeasy_stringplanmodifier.SuppressDiff(speakeasy_stringplanmodifier.ExplicitSuppress), + }, + Optional: true, + Description: `Requires replacement if changed. ; Not Null`, + Validators: []validator.String{ + speakeasy_stringvalidators.NotNull(), + }, + }, + }, + Description: `Requires replacement if changed. `, + }, + }, + }, + Description: `Requires replacement if changed. `, + }, + }, + } +} + +func (r *FileResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*sdk.SDK) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *sdk.SDK, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +func (r *FileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *FileResourceModel + var plan types.Object + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(plan.As(ctx, &data, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + })...) + + if resp.Diagnostics.HasError() { + return + } + + request := data.ToSharedFileEntity() + res, err := r.client.Files.SaveFile(ctx, request) + if err != nil { + resp.Diagnostics.AddError("failure to invoke API", err.Error()) + if res != nil && res.RawResponse != nil { + resp.Diagnostics.AddError("unexpected http request/response", debugResponse(res.RawResponse)) + } + return + } + if res == nil { + resp.Diagnostics.AddError("unexpected response from API", fmt.Sprintf("%v", res)) + return + } + if res.StatusCode != 201 { + resp.Diagnostics.AddError(fmt.Sprintf("unexpected response from API. Got an unexpected response code %v", res.StatusCode), debugResponse(res.RawResponse)) + return + } + if res.FileEntity == nil { + resp.Diagnostics.AddError("unexpected response from API. No response body", debugResponse(res.RawResponse)) + return + } + data.RefreshFromSharedFileEntity(res.FileEntity) + refreshPlan(ctx, plan, &data, resp.Diagnostics) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *FileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *FileResourceModel + var item types.Object + + resp.Diagnostics.Append(req.State.Get(ctx, &item)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(item.As(ctx, &data, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + })...) + + if resp.Diagnostics.HasError() { + return + } + + // Not Implemented; we rely entirely on CREATE API request response + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *FileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data *FileResourceModel + var plan types.Object + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + merge(ctx, req, resp, &data) + if resp.Diagnostics.HasError() { + return + } + + // Not Implemented; all attributes marked as RequiresReplace + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *FileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data *FileResourceModel + var item types.Object + + resp.Diagnostics.Append(req.State.Get(ctx, &item)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(item.As(ctx, &data, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + })...) + + if resp.Diagnostics.HasError() { + return + } + + // Not Implemented; entity does not have a configured DELETE operation +} + +func (r *FileResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resp.Diagnostics.AddError("Not Implemented", "No available import state operation is available for resource file.") +} diff --git a/internal/provider/file_resource_sdk.go b/internal/provider/file_resource_sdk.go new file mode 100644 index 0000000..f9ba97e --- /dev/null +++ b/internal/provider/file_resource_sdk.go @@ -0,0 +1,115 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package provider + +import ( + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/models/shared" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func (r *FileResourceModel) ToSharedFileEntity() *shared.FileEntity { + id := new(string) + if !r.ID.IsUnknown() && !r.ID.IsNull() { + *id = r.ID.ValueString() + } else { + id = nil + } + accessControl := new(shared.AccessControl) + if !r.AccessControl.IsUnknown() && !r.AccessControl.IsNull() { + *accessControl = shared.AccessControl(r.AccessControl.ValueString()) + } else { + accessControl = nil + } + filename := new(string) + if !r.Filename.IsUnknown() && !r.Filename.IsNull() { + *filename = r.Filename.ValueString() + } else { + filename = nil + } + mimeType := new(string) + if !r.MimeType.IsUnknown() && !r.MimeType.IsNull() { + *mimeType = r.MimeType.ValueString() + } else { + mimeType = nil + } + publicURL := new(string) + if !r.PublicURL.IsUnknown() && !r.PublicURL.IsNull() { + *publicURL = r.PublicURL.ValueString() + } else { + publicURL = nil + } + sizeBytes := new(int64) + if !r.SizeBytes.IsUnknown() && !r.SizeBytes.IsNull() { + *sizeBytes = r.SizeBytes.ValueInt64() + } else { + sizeBytes = nil + } + typeVar := new(shared.Type) + if !r.Type.IsUnknown() && !r.Type.IsNull() { + *typeVar = shared.Type(r.Type.ValueString()) + } else { + typeVar = nil + } + var versions []shared.Versions = nil + for _, versionsItem := range r.Versions { + var s3ref *shared.S3Reference + if versionsItem.S3ref != nil { + bucket := versionsItem.S3ref.Bucket.ValueString() + key := versionsItem.S3ref.Key.ValueString() + s3ref = &shared.S3Reference{ + Bucket: bucket, + Key: key, + } + } + versions = append(versions, shared.Versions{ + S3ref: s3ref, + }) + } + out := shared.FileEntity{ + ID: id, + AccessControl: accessControl, + Filename: filename, + MimeType: mimeType, + PublicURL: publicURL, + SizeBytes: sizeBytes, + Type: typeVar, + Versions: versions, + } + return &out +} + +func (r *FileResourceModel) RefreshFromSharedFileEntity(resp *shared.FileEntity) { + r.ID = types.StringPointerValue(resp.ID) + if resp.AccessControl != nil { + r.AccessControl = types.StringValue(string(*resp.AccessControl)) + } else { + r.AccessControl = types.StringNull() + } + r.Filename = types.StringPointerValue(resp.Filename) + r.MimeType = types.StringPointerValue(resp.MimeType) + r.PublicURL = types.StringPointerValue(resp.PublicURL) + r.SizeBytes = types.Int64PointerValue(resp.SizeBytes) + if resp.Type != nil { + r.Type = types.StringValue(string(*resp.Type)) + } else { + r.Type = types.StringNull() + } + if len(r.Versions) > len(resp.Versions) { + r.Versions = r.Versions[:len(resp.Versions)] + } + for versionsCount, versionsItem := range resp.Versions { + var versions1 Versions + if versionsItem.S3ref == nil { + versions1.S3ref = nil + } else { + versions1.S3ref = &S3Reference{} + versions1.S3ref.Bucket = types.StringValue(versionsItem.S3ref.Bucket) + versions1.S3ref.Key = types.StringValue(versionsItem.S3ref.Key) + } + if versionsCount+1 > len(r.Versions) { + r.Versions = append(r.Versions, versions1) + } else { + r.Versions[versionsCount].S3ref = versions1.S3ref + } + } +} diff --git a/internal/provider/provider.go b/internal/provider/provider.go new file mode 100644 index 0000000..04f4097 --- /dev/null +++ b/internal/provider/provider.go @@ -0,0 +1,117 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package provider + +import ( + "context" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/models/shared" + "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" +) + +var _ provider.Provider = &EpilotFileProvider{} + +type EpilotFileProvider struct { + // version is set to the provider version on release, "dev" when the + // provider is built and ran locally, and "test" when running acceptance + // testing. + version string +} + +// EpilotFileProviderModel describes the provider data model. +type EpilotFileProviderModel struct { + ServerURL types.String `tfsdk:"server_url"` + CookieAuth types.String `tfsdk:"cookie_auth"` + EpilotAuth types.String `tfsdk:"epilot_auth"` +} + +func (p *EpilotFileProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "epilot-file" + resp.Version = p.version +} + +func (p *EpilotFileProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: `File API: Upload and manage all files stored in epilot`, + Attributes: map[string]schema.Attribute{ + "server_url": schema.StringAttribute{ + MarkdownDescription: "Server URL (defaults to https://file.sls.epilot.io)", + Optional: true, + Required: false, + }, + "cookie_auth": schema.StringAttribute{ + Optional: true, + Sensitive: true, + }, + "epilot_auth": schema.StringAttribute{ + Optional: true, + Sensitive: true, + }, + }, + } +} + +func (p *EpilotFileProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + var data EpilotFileProviderModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + + if resp.Diagnostics.HasError() { + return + } + + ServerURL := data.ServerURL.ValueString() + + if ServerURL == "" { + ServerURL = "https://file.sls.epilot.io" + } + + cookieAuth := new(string) + if !data.CookieAuth.IsUnknown() && !data.CookieAuth.IsNull() { + *cookieAuth = data.CookieAuth.ValueString() + } else { + cookieAuth = nil + } + epilotAuth := new(string) + if !data.EpilotAuth.IsUnknown() && !data.EpilotAuth.IsNull() { + *epilotAuth = data.EpilotAuth.ValueString() + } else { + epilotAuth = nil + } + security := shared.Security{ + CookieAuth: cookieAuth, + EpilotAuth: epilotAuth, + } + + opts := []sdk.SDKOption{ + sdk.WithServerURL(ServerURL), + sdk.WithSecurity(security), + } + client := sdk.New(opts...) + + resp.DataSourceData = client + resp.ResourceData = client +} + +func (p *EpilotFileProvider) Resources(ctx context.Context) []func() resource.Resource { + return []func() resource.Resource{ + NewFileResource, + NewUploadFileResource, + } +} + +func (p *EpilotFileProvider) DataSources(ctx context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{} +} + +func New(version string) func() provider.Provider { + return func() provider.Provider { + return &EpilotFileProvider{ + version: version, + } + } +} diff --git a/internal/provider/reflect/diags.go b/internal/provider/reflect/diags.go new file mode 100644 index 0000000..a91bba9 --- /dev/null +++ b/internal/provider/reflect/diags.go @@ -0,0 +1,115 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package reflect + +import ( + "fmt" + "reflect" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +func toTerraform5ValueErrorDiag(err error, path path.Path) diag.DiagnosticWithPath { + return diag.NewAttributeErrorDiagnostic( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert into a Terraform value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) +} + +func toTerraformValueErrorDiag(err error, path path.Path) diag.DiagnosticWithPath { + return diag.NewAttributeErrorDiagnostic( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert the Attribute value into a Terraform value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) +} + +func validateValueErrorDiag(err error, path path.Path) diag.DiagnosticWithPath { + return diag.NewAttributeErrorDiagnostic( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to validate the Terraform value type. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) +} + +func valueFromTerraformErrorDiag(err error, path path.Path) diag.DiagnosticWithPath { + return diag.NewAttributeErrorDiagnostic( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert the Terraform value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) +} + +type DiagIntoIncompatibleType struct { + Val tftypes.Value + TargetType reflect.Type + Err error +} + +func (d DiagIntoIncompatibleType) Severity() diag.Severity { + return diag.SeverityError +} + +func (d DiagIntoIncompatibleType) Summary() string { + return "Value Conversion Error" +} + +func (d DiagIntoIncompatibleType) Detail() string { + return fmt.Sprintf("An unexpected error was encountered trying to convert %T into %s. This is always an error in the provider. Please report the following to the provider developer:\n\n%s", d.Val, d.TargetType, d.Err.Error()) +} + +func (d DiagIntoIncompatibleType) Equal(o diag.Diagnostic) bool { + od, ok := o.(DiagIntoIncompatibleType) + if !ok { + return false + } + if !d.Val.Equal(od.Val) { + return false + } + if d.TargetType != od.TargetType { + return false + } + if d.Err.Error() != od.Err.Error() { + return false + } + return true +} + +type DiagNewAttributeValueIntoWrongType struct { + ValType reflect.Type + TargetType reflect.Type + SchemaType attr.Type +} + +func (d DiagNewAttributeValueIntoWrongType) Severity() diag.Severity { + return diag.SeverityError +} + +func (d DiagNewAttributeValueIntoWrongType) Summary() string { + return "Value Conversion Error" +} + +func (d DiagNewAttributeValueIntoWrongType) Detail() string { + return fmt.Sprintf("An unexpected error was encountered trying to convert into a Terraform value. This is always an error in the provider. Please report the following to the provider developer:\n\nCannot use attr.Value %s, only %s is supported because %T is the type in the schema", d.TargetType, d.ValType, d.SchemaType) +} + +func (d DiagNewAttributeValueIntoWrongType) Equal(o diag.Diagnostic) bool { + od, ok := o.(DiagNewAttributeValueIntoWrongType) + if !ok { + return false + } + if d.ValType != od.ValType { + return false + } + if d.TargetType != od.TargetType { + return false + } + if !d.SchemaType.Equal(od.SchemaType) { + return false + } + return true +} diff --git a/internal/provider/reflect/doc.go b/internal/provider/reflect/doc.go new file mode 100644 index 0000000..ec91111 --- /dev/null +++ b/internal/provider/reflect/doc.go @@ -0,0 +1,9 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +// Package reflect is a forked version of https://github.com/hashicorp/terraform-plugin-framework/tree/main/internal/reflect +// that has been modified to support speakeasy's terraform generator. +// In particular, behaviour differs in that it is intended to support merging Terraform State and Terraform Plan structures +// into a single data structure, with Known Plan values overriding State values. This allows for code to be written +// that drives API calls from a single point of truth. +// Fork Commit hash is 99f28445b60580b6e39afda88a4bb469461f9bbb +package reflect diff --git a/internal/provider/reflect/generic_attr_value.go b/internal/provider/reflect/generic_attr_value.go new file mode 100644 index 0000000..430ce2f --- /dev/null +++ b/internal/provider/reflect/generic_attr_value.go @@ -0,0 +1,14 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package reflect + +import ( + "context" + "reflect" + + "github.com/hashicorp/terraform-plugin-framework/attr" +) + +func IsGenericAttrValue(ctx context.Context, target interface{}) bool { + return reflect.TypeOf((*attr.Value)(nil)) == reflect.TypeOf(target) +} diff --git a/internal/provider/reflect/helpers.go b/internal/provider/reflect/helpers.go new file mode 100644 index 0000000..82b9515 --- /dev/null +++ b/internal/provider/reflect/helpers.go @@ -0,0 +1,98 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package reflect + +import ( + "context" + "fmt" + "reflect" + "regexp" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/path" +) + +// trueReflectValue returns the reflect.Value for `in` after derefencing all +// the pointers and unwrapping all the interfaces. It's the concrete value +// beneath it all. +func trueReflectValue(val reflect.Value) reflect.Value { + kind := val.Type().Kind() + for kind == reflect.Interface || kind == reflect.Ptr { + innerVal := val.Elem() + if !innerVal.IsValid() { + break + } + val = innerVal + kind = val.Type().Kind() + } + return val +} + +// commaSeparatedString returns an English joining of the strings in `in`, +// using "and" and commas as appropriate. +func commaSeparatedString(in []string) string { + switch len(in) { + case 0: + return "" + case 1: + return in[0] + case 2: + return strings.Join(in, " and ") + default: + in[len(in)-1] = "and " + in[len(in)-1] + return strings.Join(in, ", ") + } +} + +// getStructTags returns a map of Terraform field names to their position in +// the tags of the struct `in`. `in` must be a struct. +func getStructTags(_ context.Context, in reflect.Value, path path.Path) (map[string]int, error) { + tags := map[string]int{} + typ := trueReflectValue(in).Type() + if typ.Kind() != reflect.Struct { + return nil, fmt.Errorf("%s: can't get struct tags of %s, is not a struct", path, in.Type()) + } + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + if field.PkgPath != "" { + // skip unexported fields + continue + } + tag := field.Tag.Get(`tfsdk`) + if tag == "-" { + // skip explicitly excluded fields + continue + } + if tag == "" { + return nil, fmt.Errorf(`%s: need a struct tag for "tfsdk" on %s`, path, field.Name) + } + path := path.AtName(tag) + if !isValidFieldName(tag) { + return nil, fmt.Errorf("%s: invalid field name, must only use lowercase letters, underscores, and numbers, and must start with a letter", path) + } + if other, ok := tags[tag]; ok { + return nil, fmt.Errorf("%s: can't use field name for both %s and %s", path, typ.Field(other).Name, field.Name) + } + tags[tag] = i + } + return tags, nil +} + +// isValidFieldName returns true if `name` can be used as a field name in a +// Terraform resource or data source. +func isValidFieldName(name string) bool { + re := regexp.MustCompile("^[a-z][a-z0-9_]*$") + return re.MatchString(name) +} + +// canBeNil returns true if `target`'s type can hold a nil value +func canBeNil(target reflect.Value) bool { + switch target.Kind() { + case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Interface: + // these types can all hold nils + return true + default: + // nothing else can be set to nil + return false + } +} diff --git a/internal/provider/reflect/interfaces.go b/internal/provider/reflect/interfaces.go new file mode 100644 index 0000000..364ff86 --- /dev/null +++ b/internal/provider/reflect/interfaces.go @@ -0,0 +1,353 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package reflect + +import ( + "context" + "fmt" + "reflect" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/attr/xattr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Unknownable is an interface for types that can be explicitly set to known or +// unknown. +type Unknownable interface { + SetUnknown(context.Context, bool) error + SetValue(context.Context, interface{}) error + GetUnknown(context.Context) bool + GetValue(context.Context) interface{} +} + +// NewUnknownable creates a zero value of `target` (or the concrete type it's +// referencing, if it's a pointer) and calls its SetUnknown method. +// +// It is meant to be called through Into, not directly. +func NewUnknownable(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path path.Path) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics + receiver := pointerSafeZeroValue(ctx, target) + method := receiver.MethodByName("SetUnknown") + if !method.IsValid() { + err := fmt.Errorf("cannot find SetUnknown method on type %s", receiver.Type().String()) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags + } + results := method.Call([]reflect.Value{ + reflect.ValueOf(ctx), + reflect.ValueOf(!val.IsKnown()), + }) + err := results[0].Interface() + if err != nil { + var underlyingErr error + switch e := err.(type) { + case error: + underlyingErr = e + default: + underlyingErr = fmt.Errorf("unknown error type %T: %v", e, e) + } + underlyingErr = fmt.Errorf("reflection error: %w", underlyingErr) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert into a value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+underlyingErr.Error(), + ) + return target, diags + } + return receiver, diags +} + +// FromUnknownable creates an attr.Value from the data in an Unknownable. +// +// It is meant to be called through FromValue, not directly. +func FromUnknownable(ctx context.Context, typ attr.Type, val Unknownable, path path.Path) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + if val.GetUnknown(ctx) { + tfVal := tftypes.NewValue(typ.TerraformType(ctx), tftypes.UnknownValue) + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfVal, path)...) + + if diags.HasError() { + return nil, diags + } + } + + res, err := typ.ValueFromTerraform(ctx, tfVal) + if err != nil { + return nil, append(diags, valueFromTerraformErrorDiag(err, path)) + } + return res, nil + } + err := tftypes.ValidateValue(typ.TerraformType(ctx), val.GetValue(ctx)) + if err != nil { + return nil, append(diags, validateValueErrorDiag(err, path)) + } + + tfVal := tftypes.NewValue(typ.TerraformType(ctx), val.GetValue(ctx)) + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfVal, path)...) + + if diags.HasError() { + return nil, diags + } + } + + res, err := typ.ValueFromTerraform(ctx, tfVal) + if err != nil { + return nil, append(diags, valueFromTerraformErrorDiag(err, path)) + } + return res, nil +} + +// Nullable is an interface for types that can be explicitly set to null. +type Nullable interface { + SetNull(context.Context, bool) error + SetValue(context.Context, interface{}) error + GetNull(context.Context) bool + GetValue(context.Context) interface{} +} + +// NewNullable creates a zero value of `target` (or the concrete type it's +// referencing, if it's a pointer) and calls its SetNull method. +// +// It is meant to be called through Into, not directly. +func NewNullable(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path path.Path) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics + receiver := pointerSafeZeroValue(ctx, target) + method := receiver.MethodByName("SetNull") + if !method.IsValid() { + err := fmt.Errorf("cannot find SetNull method on type %s", receiver.Type().String()) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags + } + results := method.Call([]reflect.Value{ + reflect.ValueOf(ctx), + reflect.ValueOf(val.IsNull()), + }) + err := results[0].Interface() + if err != nil { + var underlyingErr error + switch e := err.(type) { + case error: + underlyingErr = e + default: + underlyingErr = fmt.Errorf("unknown error type: %T", e) + } + underlyingErr = fmt.Errorf("reflection error: %w", underlyingErr) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert into a value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+underlyingErr.Error(), + ) + return target, diags + } + return receiver, diags +} + +// FromNullable creates an attr.Value from the data in a Nullable. +// +// It is meant to be called through FromValue, not directly. +func FromNullable(ctx context.Context, typ attr.Type, val Nullable, path path.Path) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + if val.GetNull(ctx) { + tfVal := tftypes.NewValue(typ.TerraformType(ctx), nil) + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfVal, path)...) + + if diags.HasError() { + return nil, diags + } + } + + res, err := typ.ValueFromTerraform(ctx, tfVal) + if err != nil { + return nil, append(diags, valueFromTerraformErrorDiag(err, path)) + } + return res, nil + } + err := tftypes.ValidateValue(typ.TerraformType(ctx), val.GetValue(ctx)) + if err != nil { + return nil, append(diags, validateValueErrorDiag(err, path)) + } + + tfVal := tftypes.NewValue(typ.TerraformType(ctx), val.GetValue(ctx)) + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfVal, path)...) + + if diags.HasError() { + return nil, diags + } + } + + res, err := typ.ValueFromTerraform(ctx, tfVal) + if err != nil { + return nil, append(diags, valueFromTerraformErrorDiag(err, path)) + } + return res, diags +} + +// NewValueConverter creates a zero value of `target` (or the concrete type +// it's referencing, if it's a pointer) and calls its FromTerraform5Value +// method. +// +// It is meant to be called through Into, not directly. +func NewValueConverter(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path path.Path) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics + receiver := pointerSafeZeroValue(ctx, target) + method := receiver.MethodByName("FromTerraform5Value") + if !method.IsValid() { + err := fmt.Errorf("could not find FromTerraform5Type method on type %s", receiver.Type().String()) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert into a value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags + } + results := method.Call([]reflect.Value{reflect.ValueOf(val)}) + err := results[0].Interface() + if err != nil { + var underlyingErr error + switch e := err.(type) { + case error: + underlyingErr = e + default: + underlyingErr = fmt.Errorf("unknown error type: %T", e) + } + underlyingErr = fmt.Errorf("reflection error: %w", underlyingErr) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert into a value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+underlyingErr.Error(), + ) + return target, diags + } + return receiver, diags +} + +// FromValueCreator creates an attr.Value from the data in a +// tftypes.ValueCreator, calling its ToTerraform5Value method and converting +// the result to an attr.Value using `typ`. +// +// It is meant to be called from FromValue, not directly. +func FromValueCreator(ctx context.Context, typ attr.Type, val tftypes.ValueCreator, path path.Path) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + raw, err := val.ToTerraform5Value() + if err != nil { + return nil, append(diags, toTerraform5ValueErrorDiag(err, path)) + } + err = tftypes.ValidateValue(typ.TerraformType(ctx), raw) + if err != nil { + return nil, append(diags, validateValueErrorDiag(err, path)) + } + tfVal := tftypes.NewValue(typ.TerraformType(ctx), raw) + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfVal, path)...) + + if diags.HasError() { + return nil, diags + } + } + + res, err := typ.ValueFromTerraform(ctx, tfVal) + if err != nil { + return nil, append(diags, valueFromTerraformErrorDiag(err, path)) + } + return res, diags +} + +// NewAttributeValue creates a new reflect.Value by calling the +// ValueFromTerraform method on `typ`. It will return an error if the returned +// `attr.Value` is not the same type as `target`. +// +// It is meant to be called through Into, not directly. +func NewAttributeValue(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path path.Path) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, val, path)...) + + if diags.HasError() { + return target, diags + } + } + + res, err := typ.ValueFromTerraform(ctx, val) + if err != nil { + return target, append(diags, valueFromTerraformErrorDiag(err, path)) + } + if reflect.TypeOf(res) != target.Type() { + diags.Append(diag.WithPath(path, DiagNewAttributeValueIntoWrongType{ + ValType: reflect.TypeOf(res), + TargetType: target.Type(), + SchemaType: typ, + })) + return target, diags + } + return reflect.ValueOf(res), diags +} + +// FromAttributeValue creates an attr.Value from an attr.Value. It just returns +// the attr.Value it is passed or an error if there is an unexpected mismatch +// between the attr.Type and attr.Value. +// +// It is meant to be called through FromValue, not directly. +func FromAttributeValue(ctx context.Context, typ attr.Type, val attr.Value, path path.Path) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + // Since the reflection logic is a generic Go type implementation with + // user input, it is possible to get into awkward situations where + // the logic is expecting a certain type while a value may not be + // compatible. This check will ensure the framework raises its own + // error is there is a mismatch, rather than a terraform-plugin-go + // error or worse a panic. + // + // If this validation causes major issues, another option is to + // validate via tftypes.Type for both the type and value type. + if !typ.Equal(val.Type(ctx)) { + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered while verifying an attribute value matched its expected type to prevent unexpected behavior or panics. "+ + "This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + fmt.Sprintf("Expected type: %s\n", typ)+ + fmt.Sprintf("Value type: %s\n", val.Type(ctx))+ + fmt.Sprintf("Path: %s", path), + ) + + return nil, diags + } + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + tfVal, err := val.ToTerraformValue(ctx) + if err != nil { + return val, append(diags, toTerraformValueErrorDiag(err, path)) + } + + diags.Append(typeWithValidate.Validate(ctx, tfVal, path)...) + + if diags.HasError() { + return val, diags + } + } + + return val, diags +} diff --git a/internal/provider/reflect/into.go b/internal/provider/reflect/into.go new file mode 100644 index 0000000..c53d1be --- /dev/null +++ b/internal/provider/reflect/into.go @@ -0,0 +1,216 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package reflect + +import ( + "context" + "fmt" + "math/big" + "reflect" + + "github.com/hashicorp/terraform-plugin-go/tftypes" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" +) + +// Into uses the data in `val` to populate `target`, using the reflection +// package to recursively reflect into structs and slices. If `target` is an +// attr.Value, its assignment method will be used instead of reflecting. If +// `target` is a tftypes.ValueConverter, the FromTerraformValue method will be +// used instead of using reflection. Primitives are set using the val.As +// method. Structs use reflection: each exported struct field must have a +// "tfsdk" tag with the name of the field in the tftypes.Value, and all fields +// in the tftypes.Value must have a corresponding property in the struct. Into +// will be called for each struct field. Slices will have Into called for each +// element. +func Into(ctx context.Context, typ attr.Type, val tftypes.Value, target interface{}, opts Options, path path.Path) diag.Diagnostics { + var diags diag.Diagnostics + + v := reflect.ValueOf(target) + if v.Kind() != reflect.Ptr { + err := fmt.Errorf("target must be a pointer, got %T, which is a %s", target, v.Kind()) + diags.AddAttributeError( + path, + "Value Conversion Error", + fmt.Sprintf("An unexpected error was encountered trying to convert the value. This is always an error in the provider. Please report the following to the provider developer:\n\nPath: %s\nError: %s", path.String(), err.Error()), + ) + return diags + } + result, diags := BuildValue(ctx, typ, val, v.Elem(), opts, path) + if diags.HasError() { + return diags + } + v.Elem().Set(result) + return diags +} + +// BuildValue constructs a reflect.Value of the same type as `target`, +// populated with the data in `val`. It will defensively instantiate new values +// to set, making it safe for use with pointer types which may be nil. It tries +// to give consumers the ability to override its default behaviors wherever +// possible. +func BuildValue(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path path.Path) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + // if this isn't a valid reflect.Value, bail before we accidentally + // panic + if !target.IsValid() { + err := fmt.Errorf("invalid target") + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to build a value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags + } + + // ------------------------- + // -- Fork Start ----------- + // ------------------------- + + if !val.IsKnown() { + return target, diags + } + + // ------------------------- + // -- Fork End - ----------- + // ------------------------- + + // if this is an attr.Value, build the type from that + if target.Type().Implements(reflect.TypeOf((*attr.Value)(nil)).Elem()) { + return NewAttributeValue(ctx, typ, val, target, opts, path) + } + // if this tells tftypes how to build an instance of it out of a + // tftypes.Value, well, that's what we want, so do that instead of our + // default logic. + if target.Type().Implements(reflect.TypeOf((*tftypes.ValueConverter)(nil)).Elem()) { + return NewValueConverter(ctx, typ, val, target, opts, path) + } + // if this can explicitly be set to unknown, do that + if target.Type().Implements(reflect.TypeOf((*Unknownable)(nil)).Elem()) { + res, unknownableDiags := NewUnknownable(ctx, typ, val, target, opts, path) + diags.Append(unknownableDiags...) + if diags.HasError() { + return target, diags + } + target = res + // only return if it's unknown; we want to call SetUnknown + // either way, but if the value is unknown, there's nothing + // else to do, so bail + if !val.IsKnown() { + return target, nil + } + } + // if this can explicitly be set to null, do that + if target.Type().Implements(reflect.TypeOf((*Nullable)(nil)).Elem()) { + res, nullableDiags := NewNullable(ctx, typ, val, target, opts, path) + diags.Append(nullableDiags...) + if diags.HasError() { + return target, diags + } + target = res + // only return if it's null; we want to call SetNull either + // way, but if the value is null, there's nothing else to do, + // so bail + if val.IsNull() { + return target, nil + } + } + // ------------------------- + // -- Fork Start ----------- + // ------------------------- + //if !val.IsKnown() { + // // we already handled unknown the only ways we can + // // we checked that target doesn't have a SetUnknown method we + // // can call + // // we checked that target isn't an attr.Value + // // all that's left to us now is to set it as an empty value or + // // throw an error, depending on what's in opts + // if !opts.UnhandledUnknownAsEmpty { + // diags.AddAttributeError( + // path, + // "Value Conversion Error", + // "An unexpected error was encountered trying to build a value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + // "Received unknown value, however the target type cannot handle unknown values. Use the corresponding `types` package type or a custom type that handles unknown values.\n\n"+ + // fmt.Sprintf("Path: %s\nTarget Type: %s\nSuggested Type: %s", path.String(), target.Type(), reflect.TypeOf(typ.ValueType(ctx))), + // ) + // return target, diags + // } + // // we want to set unhandled unknowns to the empty value + // return reflect.Zero(target.Type()), diags + //} + // ------------------------- + // -- Fork End - ----------- + // ------------------------- + + if val.IsNull() { + // we already handled null the only ways we can + // we checked that target doesn't have a SetNull method we can + // call + // we checked that target isn't an attr.Value + // all that's left to us now is to set it as an empty value or + // throw an error, depending on what's in opts + if canBeNil(target) || opts.UnhandledNullAsEmpty { + return reflect.Zero(target.Type()), nil + } + + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to build a value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + "Received null value, however the target type cannot handle null values. Use the corresponding `types` package type, a pointer type or a custom type that handles null values.\n\n"+ + fmt.Sprintf("Path: %s\nTarget Type: %s\nSuggested `types` Type: %s\nSuggested Pointer Type: *%s", path.String(), target.Type(), reflect.TypeOf(typ.ValueType(ctx)), target.Type()), + ) + + return target, diags + } + // *big.Float and *big.Int are technically pointers, but we want them + // handled as numbers + if target.Type() == reflect.TypeOf(big.NewFloat(0)) || target.Type() == reflect.TypeOf(big.NewInt(0)) { + return Number(ctx, typ, val, target, opts, path) + } + switch target.Kind() { + case reflect.Struct: + val, valDiags := Struct(ctx, typ, val, target, opts, path) + diags.Append(valDiags...) + return val, diags + case reflect.Bool, reflect.String: + val, valDiags := Primitive(ctx, typ, val, target, path) + diags.Append(valDiags...) + return val, diags + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, + reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, + reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: + // numbers are the wooooorst and need their own special handling + // because we can't just hand them off to tftypes and also + // because we can't just make people use *big.Floats, because a + // nil *big.Float will crash everything if we don't handle it + // as a special case, so let's just special case numbers and + // let people use the types they want + val, valDiags := Number(ctx, typ, val, target, opts, path) + diags.Append(valDiags...) + return val, diags + case reflect.Slice: + val, valDiags := reflectSlice(ctx, typ, val, target, opts, path) + diags.Append(valDiags...) + return val, diags + case reflect.Map: + val, valDiags := Map(ctx, typ, val, target, opts, path) + diags.Append(valDiags...) + return val, diags + case reflect.Ptr: + val, valDiags := Pointer(ctx, typ, val, target, opts, path) + diags.Append(valDiags...) + return val, diags + default: + err := fmt.Errorf("don't know how to reflect %s into %s", val.Type(), target.Type()) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to build a value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags + } +} diff --git a/internal/provider/reflect/map.go b/internal/provider/reflect/map.go new file mode 100644 index 0000000..3310d42 --- /dev/null +++ b/internal/provider/reflect/map.go @@ -0,0 +1,188 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package reflect + +import ( + "context" + "fmt" + "reflect" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/attr/xattr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Map creates a map value that matches the type of `target`, and populates it +// with the contents of `val`. +func Map(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path path.Path) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics + underlyingValue := trueReflectValue(target) + + // this only works with maps, so check that out first + if underlyingValue.Kind() != reflect.Map { + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Val: val, + TargetType: target.Type(), + Err: fmt.Errorf("expected a map type, got %s", target.Type()), + })) + return target, diags + } + if !val.Type().Is(tftypes.Map{}) { + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Val: val, + TargetType: target.Type(), + Err: fmt.Errorf("cannot reflect %s into a map, must be a map", val.Type().String()), + })) + return target, diags + } + elemTyper, ok := typ.(attr.TypeWithElementType) + if !ok { + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Val: val, + TargetType: target.Type(), + Err: fmt.Errorf("cannot reflect map using type information provided by %T, %T must be an attr.TypeWithElementType", typ, typ), + })) + return target, diags + } + + // we need our value to become a map of values so we can iterate over + // them and handle them individually + values := map[string]tftypes.Value{} + err := val.As(&values) + if err != nil { + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Val: val, + TargetType: target.Type(), + Err: err, + })) + return target, diags + } + + // we need to know the type the slice is wrapping + elemType := underlyingValue.Type().Elem() + elemAttrType := elemTyper.ElementType() + + // we want an empty version of the map + m := reflect.MakeMapWithSize(underlyingValue.Type(), len(values)) + + // go over each of the values passed in, create a Go value of the right + // type for them, and add it to our new map + for key, value := range values { + // create a new Go value of the type that can go in the map + targetValue := reflect.Zero(elemType) + + // update our path so we can have nice errors + path := path.AtMapKey(key) + + // reflect the value into our new target + result, elemDiags := BuildValue(ctx, elemAttrType, value, targetValue, opts, path) + diags.Append(elemDiags...) + + if diags.HasError() { + return target, diags + } + + m.SetMapIndex(reflect.ValueOf(key), result) + } + + return m, diags +} + +// FromMap returns an attr.Value representing the data contained in `val`. +// `val` must be a map type with keys that are a string type. The attr.Value +// will be of the type produced by `typ`. +// +// It is meant to be called through FromValue, not directly. +func FromMap(ctx context.Context, typ attr.TypeWithElementType, val reflect.Value, path path.Path) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + tfType := typ.TerraformType(ctx) + + if val.IsNil() { + tfVal := tftypes.NewValue(tfType, nil) + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfVal, path)...) + + if diags.HasError() { + return nil, diags + } + } + + attrVal, err := typ.ValueFromTerraform(ctx, tfVal) + + if err != nil { + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from map value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + + return attrVal, diags + } + + elemType := typ.ElementType() + tfElems := map[string]tftypes.Value{} + for _, key := range val.MapKeys() { + if key.Kind() != reflect.String { + err := fmt.Errorf("map keys must be strings, got %s", key.Type()) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert into a Terraform value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + val, valDiags := FromValue(ctx, elemType, val.MapIndex(key).Interface(), path.AtMapKey(key.String())) + diags.Append(valDiags...) + + if diags.HasError() { + return nil, diags + } + tfVal, err := val.ToTerraformValue(ctx) + if err != nil { + return nil, append(diags, toTerraformValueErrorDiag(err, path)) + } + + if typeWithValidate, ok := elemType.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfVal, path.AtMapKey(key.String()))...) + + if diags.HasError() { + return nil, diags + } + } + + tfElems[key.String()] = tfVal + } + + err := tftypes.ValidateValue(tfType, tfElems) + if err != nil { + return nil, append(diags, validateValueErrorDiag(err, path)) + } + + tfVal := tftypes.NewValue(tfType, tfElems) + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfVal, path)...) + + if diags.HasError() { + return nil, diags + } + } + + attrVal, err := typ.ValueFromTerraform(ctx, tfVal) + + if err != nil { + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to map value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + + return attrVal, diags +} diff --git a/internal/provider/reflect/number.go b/internal/provider/reflect/number.go new file mode 100644 index 0000000..37eef21 --- /dev/null +++ b/internal/provider/reflect/number.go @@ -0,0 +1,374 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package reflect + +import ( + "context" + "fmt" + "math" + "math/big" + "reflect" + "strconv" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/attr/xattr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Number creates a *big.Float and populates it with the data in `val`. It then +// gets converted to the type of `target`, as long as `target` is a valid +// number type (any of the built-in int, uint, or float types, *big.Float, and +// *big.Int). +// +// Number will loudly fail when a number cannot be losslessly represented using +// the requested type, unless opts.AllowRoundingNumbers is set to true. This +// setting is mildly dangerous, because Terraform does not like when you round +// things, as a general rule of thumb. +// +// It is meant to be called through Into, not directly. +func Number(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path path.Path) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics + result := big.NewFloat(0) + err := val.As(&result) + if err != nil { + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Err: err, + TargetType: target.Type(), + Val: val, + })) + return target, diags + } + roundingError := fmt.Errorf("cannot store %s in %s", result.String(), target.Type()) + roundingErrorDiag := diag.NewAttributeErrorDiagnostic( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+roundingError.Error(), + ) + + switch target.Type() { + case reflect.TypeOf(big.NewFloat(0)): + return reflect.ValueOf(result), diags + case reflect.TypeOf(big.NewInt(0)): + intResult, acc := result.Int(nil) + if acc != big.Exact && !opts.AllowRoundingNumbers { + return reflect.ValueOf(result), append(diags, roundingErrorDiag) + } + return reflect.ValueOf(intResult), diags + } + switch target.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, + reflect.Int64: + intResult, acc := result.Int64() + if acc != big.Exact && !opts.AllowRoundingNumbers { + return target, append(diags, roundingErrorDiag) + } + switch target.Kind() { + case reflect.Int: + if strconv.IntSize == 32 && intResult > math.MaxInt32 { + if !opts.AllowRoundingNumbers { + return target, append(diags, roundingErrorDiag) + } + intResult = math.MaxInt32 + } + if strconv.IntSize == 32 && intResult < math.MinInt32 { + if !opts.AllowRoundingNumbers { + return target, append(diags, roundingErrorDiag) + } + intResult = math.MinInt32 + } + return reflect.ValueOf(int(intResult)), diags + case reflect.Int8: + if intResult > math.MaxInt8 { + if !opts.AllowRoundingNumbers { + return target, append(diags, roundingErrorDiag) + } + intResult = math.MaxInt8 + } + if intResult < math.MinInt8 { + if !opts.AllowRoundingNumbers { + return target, append(diags, roundingErrorDiag) + } + intResult = math.MinInt8 + } + return reflect.ValueOf(int8(intResult)), diags + case reflect.Int16: + if intResult > math.MaxInt16 { + if !opts.AllowRoundingNumbers { + return target, append(diags, roundingErrorDiag) + } + intResult = math.MaxInt16 + } + if intResult < math.MinInt16 { + if !opts.AllowRoundingNumbers { + return target, append(diags, roundingErrorDiag) + } + intResult = math.MinInt16 + } + return reflect.ValueOf(int16(intResult)), diags + case reflect.Int32: + if intResult > math.MaxInt32 { + if !opts.AllowRoundingNumbers { + return target, append(diags, roundingErrorDiag) + } + intResult = math.MaxInt32 + } + if intResult < math.MinInt32 { + if !opts.AllowRoundingNumbers { + return target, append(diags, roundingErrorDiag) + } + intResult = math.MinInt32 + } + return reflect.ValueOf(int32(intResult)), diags + case reflect.Int64: + return reflect.ValueOf(intResult), diags + } + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, + reflect.Uint64: + uintResult, acc := result.Uint64() + if acc != big.Exact && !opts.AllowRoundingNumbers { + return target, append(diags, roundingErrorDiag) + } + switch target.Kind() { + case reflect.Uint: + if strconv.IntSize == 32 && uintResult > math.MaxUint32 { + if !opts.AllowRoundingNumbers { + return target, append(diags, roundingErrorDiag) + } + uintResult = math.MaxUint32 + } + return reflect.ValueOf(uint(uintResult)), diags + case reflect.Uint8: + if uintResult > math.MaxUint8 { + if !opts.AllowRoundingNumbers { + return target, append(diags, roundingErrorDiag) + } + uintResult = math.MaxUint8 + } + return reflect.ValueOf(uint8(uintResult)), diags + case reflect.Uint16: + if uintResult > math.MaxUint16 { + if !opts.AllowRoundingNumbers { + return target, append(diags, roundingErrorDiag) + } + uintResult = math.MaxUint16 + } + return reflect.ValueOf(uint16(uintResult)), diags + case reflect.Uint32: + if uintResult > math.MaxUint32 { + if !opts.AllowRoundingNumbers { + return target, append(diags, roundingErrorDiag) + } + uintResult = math.MaxUint32 + } + return reflect.ValueOf(uint32(uintResult)), diags + case reflect.Uint64: + return reflect.ValueOf(uintResult), diags + } + case reflect.Float32: + floatResult, acc := result.Float32() + if acc != big.Exact && !opts.AllowRoundingNumbers { + return target, append(diags, roundingErrorDiag) + } else if acc == big.Above { + floatResult = math.MaxFloat32 + } else if acc == big.Below { + floatResult = math.SmallestNonzeroFloat32 + } else if acc != big.Exact { + err := fmt.Errorf("unsure how to round %s and %f", acc, floatResult) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags + } + return reflect.ValueOf(floatResult), diags + case reflect.Float64: + floatResult, acc := result.Float64() + if acc != big.Exact && !opts.AllowRoundingNumbers { + return target, append(diags, roundingErrorDiag) + } + if acc == big.Above { + if floatResult == math.Inf(1) || floatResult == math.MaxFloat64 { + floatResult = math.MaxFloat64 + } else if floatResult == 0.0 || floatResult == math.SmallestNonzeroFloat64 { + floatResult = -math.SmallestNonzeroFloat64 + } else { + err := fmt.Errorf("not sure how to round %s and %f", acc, floatResult) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags + } + } else if acc == big.Below { + if floatResult == math.Inf(-1) || floatResult == -math.MaxFloat64 { + floatResult = -math.MaxFloat64 + } else if floatResult == 0.0 || floatResult == -math.SmallestNonzeroFloat64 { + floatResult = math.SmallestNonzeroFloat64 + } else { + err := fmt.Errorf("not sure how to round %s and %f", acc, floatResult) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags + } + } else if acc != big.Exact { + err := fmt.Errorf("not sure how to round %s and %f", acc, floatResult) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags + } + return reflect.ValueOf(floatResult), diags + } + err = fmt.Errorf("cannot convert number to %s", target.Type()) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to number. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags +} + +// FromInt creates an attr.Value using `typ` from an int64. +// +// It is meant to be called through FromValue, not directly. +func FromInt(ctx context.Context, typ attr.Type, val int64, path path.Path) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + err := tftypes.ValidateValue(tftypes.Number, val) + if err != nil { + return nil, append(diags, validateValueErrorDiag(err, path)) + } + tfNum := tftypes.NewValue(tftypes.Number, val) + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfNum, path)...) + + if diags.HasError() { + return nil, diags + } + } + + num, err := typ.ValueFromTerraform(ctx, tfNum) + if err != nil { + return nil, append(diags, valueFromTerraformErrorDiag(err, path)) + } + + return num, diags +} + +// FromUint creates an attr.Value using `typ` from a uint64. +// +// It is meant to be called through FromValue, not directly. +func FromUint(ctx context.Context, typ attr.Type, val uint64, path path.Path) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + err := tftypes.ValidateValue(tftypes.Number, val) + if err != nil { + return nil, append(diags, validateValueErrorDiag(err, path)) + } + tfNum := tftypes.NewValue(tftypes.Number, val) + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfNum, path)...) + + if diags.HasError() { + return nil, diags + } + } + + num, err := typ.ValueFromTerraform(ctx, tfNum) + if err != nil { + return nil, append(diags, valueFromTerraformErrorDiag(err, path)) + } + + return num, diags +} + +// FromFloat creates an attr.Value using `typ` from a float64. +// +// It is meant to be called through FromValue, not directly. +func FromFloat(ctx context.Context, typ attr.Type, val float64, path path.Path) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + err := tftypes.ValidateValue(tftypes.Number, val) + if err != nil { + return nil, append(diags, validateValueErrorDiag(err, path)) + } + tfNum := tftypes.NewValue(tftypes.Number, val) + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfNum, path)...) + + if diags.HasError() { + return nil, diags + } + } + + num, err := typ.ValueFromTerraform(ctx, tfNum) + if err != nil { + return nil, append(diags, valueFromTerraformErrorDiag(err, path)) + } + + return num, diags +} + +// FromBigFloat creates an attr.Value using `typ` from a *big.Float. +// +// It is meant to be called through FromValue, not directly. +func FromBigFloat(ctx context.Context, typ attr.Type, val *big.Float, path path.Path) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + err := tftypes.ValidateValue(tftypes.Number, val) + if err != nil { + return nil, append(diags, validateValueErrorDiag(err, path)) + } + tfNum := tftypes.NewValue(tftypes.Number, val) + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfNum, path)...) + + if diags.HasError() { + return nil, diags + } + } + + num, err := typ.ValueFromTerraform(ctx, tfNum) + if err != nil { + return nil, append(diags, valueFromTerraformErrorDiag(err, path)) + } + + return num, diags +} + +// FromBigInt creates an attr.Value using `typ` from a *big.Int. +// +// It is meant to be called through FromValue, not directly. +func FromBigInt(ctx context.Context, typ attr.Type, val *big.Int, path path.Path) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + fl := big.NewFloat(0).SetInt(val) + err := tftypes.ValidateValue(tftypes.Number, fl) + if err != nil { + return nil, append(diags, validateValueErrorDiag(err, path)) + } + tfNum := tftypes.NewValue(tftypes.Number, fl) + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfNum, path)...) + + if diags.HasError() { + return nil, diags + } + } + + num, err := typ.ValueFromTerraform(ctx, tfNum) + if err != nil { + return nil, append(diags, valueFromTerraformErrorDiag(err, path)) + } + + return num, diags +} diff --git a/internal/provider/reflect/options.go b/internal/provider/reflect/options.go new file mode 100644 index 0000000..490c5f1 --- /dev/null +++ b/internal/provider/reflect/options.go @@ -0,0 +1,22 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package reflect + +// Options provides configuration settings for how the reflection behavior +// works, letting callers tweak different behaviors based on their needs. +type Options struct { + // UnhandledNullAsEmpty controls whether null values should be + // translated into empty values without provider interaction, or if + // they must be explicitly handled. + UnhandledNullAsEmpty bool + + // UnhandledUnknownAsEmpty controls whether null values should be + // translated into empty values without provider interaction, or if + // they must be explicitly handled. + UnhandledUnknownAsEmpty bool + + // AllowRoundingNumbers silently rounds numbers that don't fit + // perfectly in the types they're being stored in, rather than + // returning errors. Numbers will always be rounded towards 0. + AllowRoundingNumbers bool +} diff --git a/internal/provider/reflect/outof.go b/internal/provider/reflect/outof.go new file mode 100644 index 0000000..7852781 --- /dev/null +++ b/internal/provider/reflect/outof.go @@ -0,0 +1,94 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package reflect + +import ( + "context" + "fmt" + "math/big" + "reflect" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// FromValue is the inverse of Into, taking a Go value (`val`) and transforming it +// into an attr.Value using the attr.Type supplied. `val` will first be +// transformed into a tftypes.Value, then passed to `typ`'s ValueFromTerraform +// method. +func FromValue(ctx context.Context, typ attr.Type, val interface{}, path path.Path) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + if v, ok := val.(attr.Value); ok { + return FromAttributeValue(ctx, typ, v, path) + } + if v, ok := val.(tftypes.ValueCreator); ok { + return FromValueCreator(ctx, typ, v, path) + } + if v, ok := val.(Unknownable); ok { + return FromUnknownable(ctx, typ, v, path) + } + if v, ok := val.(Nullable); ok { + return FromNullable(ctx, typ, v, path) + } + if bf, ok := val.(*big.Float); ok { + return FromBigFloat(ctx, typ, bf, path) + } + if bi, ok := val.(*big.Int); ok { + return FromBigInt(ctx, typ, bi, path) + } + value := reflect.ValueOf(val) + kind := value.Kind() + switch kind { + case reflect.Struct: + t, ok := typ.(attr.TypeWithAttributeTypes) + if !ok { + err := fmt.Errorf("cannot use type %T as schema type %T; %T must be an attr.TypeWithAttributeTypes to hold %T", val, typ, typ, val) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + return FromStruct(ctx, t, value, path) + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, + reflect.Int64: + return FromInt(ctx, typ, value.Int(), path) + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, + reflect.Uint64: + return FromUint(ctx, typ, value.Uint(), path) + case reflect.Float32, reflect.Float64: + return FromFloat(ctx, typ, value.Float(), path) + case reflect.Bool: + return FromBool(ctx, typ, value.Bool(), path) + case reflect.String: + return FromString(ctx, typ, value.String(), path) + case reflect.Slice: + return FromSlice(ctx, typ, value, path) + case reflect.Map: + t, ok := typ.(attr.TypeWithElementType) + if !ok { + err := fmt.Errorf("cannot use type %T as schema type %T; %T must be an attr.TypeWithElementType to hold %T", val, typ, typ, val) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + return FromMap(ctx, t, value, path) + case reflect.Ptr: + return FromPointer(ctx, typ, value, path) + default: + err := fmt.Errorf("cannot construct attr.Type from %T (%s)", val, kind) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } +} diff --git a/internal/provider/reflect/pointer.go b/internal/provider/reflect/pointer.go new file mode 100644 index 0000000..e02c9eb --- /dev/null +++ b/internal/provider/reflect/pointer.go @@ -0,0 +1,126 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package reflect + +import ( + "context" + "fmt" + "reflect" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/attr/xattr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Pointer builds a new zero value of the concrete type that `target` +// references, populates it with BuildValue, and takes a pointer to it. +// +// It is meant to be called through Into, not directly. +func Pointer(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path path.Path) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + if target.Kind() != reflect.Ptr { + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Val: val, + TargetType: target.Type(), + Err: fmt.Errorf("cannot dereference pointer, not a pointer, is a %s (%s)", target.Type(), target.Kind()), + })) + return target, diags + } + // we may have gotten a nil pointer, so we need to create our own that + // we can set + // FORK START + pointer := target + if target.IsNil() { + pointer = reflect.New(target.Type().Elem()) + } + // FORK END + // build out whatever the pointer is pointing to + pointed, pointedDiags := BuildValue(ctx, typ, val, pointer.Elem(), opts, path) + diags.Append(pointedDiags...) + + if diags.HasError() { + return target, diags + } + // to be able to set the pointer to our new pointer, we need to create + // a pointer to the pointer + pointerPointer := reflect.New(pointer.Type()) + // we set the pointer we created on the pointer to the pointer + pointerPointer.Elem().Set(pointer) + // then it's settable, so we can now set the concrete value we created + // on the pointer + pointerPointer.Elem().Elem().Set(pointed) + // return the pointer we created + return pointerPointer.Elem(), diags +} + +// create a zero value of concrete type underlying any number of pointers, then +// wrap it in that number of pointers again. The end result is to wind up with +// the same exact type, except now you can be sure it's pointing to actual data +// and will not give you a nil pointer dereference panic unexpectedly. +func pointerSafeZeroValue(_ context.Context, target reflect.Value) reflect.Value { + pointer := target.Type() + var pointers int + for pointer.Kind() == reflect.Ptr { + pointer = pointer.Elem() + pointers++ + } + receiver := reflect.Zero(pointer) + for i := 0; i < pointers; i++ { + newReceiver := reflect.New(receiver.Type()) + newReceiver.Elem().Set(receiver) + receiver = newReceiver + } + return receiver +} + +// FromPointer turns a pointer into an attr.Value using `typ`. If the pointer +// is nil, the attr.Value will use its null representation. If it is not nil, +// it will recurse into FromValue to find the attr.Value of the type the value +// the pointer is referencing. +// +// It is meant to be called through FromValue, not directly. +func FromPointer(ctx context.Context, typ attr.Type, value reflect.Value, path path.Path) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + if value.Kind() != reflect.Ptr { + err := fmt.Errorf("cannot use type %s as a pointer", value.Type()) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from pointer value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + if value.IsNil() { + tfVal := tftypes.NewValue(typ.TerraformType(ctx), nil) + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfVal, path)...) + + if diags.HasError() { + return nil, diags + } + } + + attrVal, err := typ.ValueFromTerraform(ctx, tfVal) + + if err != nil { + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from pointer value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + + return attrVal, diags + } + + attrVal, attrValDiags := FromValue(ctx, typ, value.Elem().Interface(), path) + diags.Append(attrValDiags...) + + return attrVal, diags +} diff --git a/internal/provider/reflect/primitive.go b/internal/provider/reflect/primitive.go new file mode 100644 index 0000000..3113c39 --- /dev/null +++ b/internal/provider/reflect/primitive.go @@ -0,0 +1,111 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package reflect + +import ( + "context" + "errors" + "reflect" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/attr/xattr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Primitive builds a string or boolean, depending on the type of `target`, and +// populates it with the data in `val`. +// +// It is meant to be called through `Into`, not directly. +func Primitive(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, path path.Path) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + switch target.Kind() { + case reflect.Bool: + var b bool + err := val.As(&b) + if err != nil { + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Val: val, + TargetType: target.Type(), + Err: err, + })) + return target, diags + } + return reflect.ValueOf(b).Convert(target.Type()), nil + case reflect.String: + var s string + err := val.As(&s) + if err != nil { + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Val: val, + TargetType: target.Type(), + Err: err, + })) + return target, diags + } + return reflect.ValueOf(s).Convert(target.Type()), nil + default: + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Val: val, + TargetType: target.Type(), + Err: errors.New("unknown type"), + })) + return target, diags + } +} + +// FromString returns an attr.Value as produced by `typ` from a string. +// +// It is meant to be called through FromValue, not directly. +func FromString(ctx context.Context, typ attr.Type, val string, path path.Path) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + err := tftypes.ValidateValue(tftypes.String, val) + if err != nil { + return nil, append(diags, validateValueErrorDiag(err, path)) + } + tfStr := tftypes.NewValue(tftypes.String, val) + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfStr, path)...) + + if diags.HasError() { + return nil, diags + } + } + + str, err := typ.ValueFromTerraform(ctx, tfStr) + if err != nil { + return nil, append(diags, valueFromTerraformErrorDiag(err, path)) + } + + return str, diags +} + +// FromBool returns an attr.Value as produced by `typ` from a bool. +// +// It is meant to be called through FromValue, not directly. +func FromBool(ctx context.Context, typ attr.Type, val bool, path path.Path) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + err := tftypes.ValidateValue(tftypes.Bool, val) + if err != nil { + return nil, append(diags, validateValueErrorDiag(err, path)) + } + tfBool := tftypes.NewValue(tftypes.Bool, val) + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfBool, path)...) + + if diags.HasError() { + return nil, diags + } + } + + b, err := typ.ValueFromTerraform(ctx, tfBool) + if err != nil { + return nil, append(diags, valueFromTerraformErrorDiag(err, path)) + } + + return b, diags +} diff --git a/internal/provider/reflect/slice.go b/internal/provider/reflect/slice.go new file mode 100644 index 0000000..b91f729 --- /dev/null +++ b/internal/provider/reflect/slice.go @@ -0,0 +1,214 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package reflect + +import ( + "context" + "fmt" + "reflect" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/attr/xattr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// build a slice of elements, matching the type of `target`, and fill it with +// the data in `val`. +func reflectSlice(ctx context.Context, typ attr.Type, val tftypes.Value, target reflect.Value, opts Options, path path.Path) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + // this only works with slices, so check that out first + if target.Kind() != reflect.Slice { + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Val: val, + TargetType: target.Type(), + Err: fmt.Errorf("expected a slice type, got %s", target.Type()), + })) + return target, diags + } + // TODO: check that the val is a list or set or tuple + elemTyper, ok := typ.(attr.TypeWithElementType) + if !ok { + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Val: val, + TargetType: target.Type(), + Err: fmt.Errorf("cannot reflect %s using type information provided by %T, %T must be an attr.TypeWithElementType", val.Type(), typ, typ), + })) + return target, diags + } + + // we need our value to become a list of values so we can iterate over + // them and handle them individually + var values []tftypes.Value + err := val.As(&values) + if err != nil { + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Val: val, + TargetType: target.Type(), + Err: err, + })) + return target, diags + } + + // we need to know the type the slice is wrapping + elemType := target.Type().Elem() + elemAttrType := elemTyper.ElementType() + + // we want an empty version of the slice + slice := reflect.MakeSlice(target.Type(), 0, len(values)) + + // go over each of the values passed in, create a Go value of the right + // type for them, and add it to our new slice + for pos, value := range values { + // create a new Go value of the type that can go in the slice + targetValue := reflect.Zero(elemType) + + // update our path so we can have nice errors + valPath := path.AtListIndex(pos) + + if typ.TerraformType(ctx).Is(tftypes.Set{}) { + attrVal, err := elemAttrType.ValueFromTerraform(ctx, value) + + if err != nil { + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert to slice value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return target, diags + } + + valPath = path.AtSetValue(attrVal) + } + + // reflect the value into our new target + val, valDiags := BuildValue(ctx, elemAttrType, value, targetValue, opts, valPath) + diags.Append(valDiags...) + + if diags.HasError() { + return target, diags + } + + // add the new target to our slice + slice = reflect.Append(slice, val) + } + + return slice, diags +} + +// FromSlice returns an attr.Value as produced by `typ` using the data in +// `val`. `val` must be a slice. `typ` must be an attr.TypeWithElementType or +// attr.TypeWithElementTypes. If the slice is nil, the representation of null +// for `typ` will be returned. Otherwise, FromSlice will recurse into FromValue +// for each element in the slice, using the element type or types defined on +// `typ` to construct values for them. +// +// It is meant to be called through FromValue, not directly. +func FromSlice(ctx context.Context, typ attr.Type, val reflect.Value, path path.Path) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + // TODO: support tuples, which are attr.TypeWithElementTypes + tfType := typ.TerraformType(ctx) + + if val.IsNil() { + tfVal := tftypes.NewValue(tfType, nil) + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfVal, path)...) + + if diags.HasError() { + return nil, diags + } + } + + attrVal, err := typ.ValueFromTerraform(ctx, tfVal) + + if err != nil { + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from slice value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + + return attrVal, diags + } + + t, ok := typ.(attr.TypeWithElementType) + if !ok { + err := fmt.Errorf("cannot use type %T as schema type %T; %T must be an attr.TypeWithElementType to hold %T", val, typ, typ, val) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from slice value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + + elemType := t.ElementType() + tfElems := make([]tftypes.Value, 0, val.Len()) + for i := 0; i < val.Len(); i++ { + // The underlying reflect.Slice is fetched by Index(). For set types, + // the path is value-based instead of index-based. Since there is only + // the index until the value is retrieved, this will pass the + // technically incorrect index-based path at first for framework + // debugging purposes, then correct the path afterwards. + valPath := path.AtListIndex(i) + + val, valDiags := FromValue(ctx, elemType, val.Index(i).Interface(), valPath) + diags.Append(valDiags...) + + if diags.HasError() { + return nil, diags + } + + tfVal, err := val.ToTerraformValue(ctx) + if err != nil { + return nil, append(diags, toTerraformValueErrorDiag(err, path)) + } + + if tfType.Is(tftypes.Set{}) { + valPath = path.AtSetValue(val) + } + + if typeWithValidate, ok := elemType.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfVal, valPath)...) + if diags.HasError() { + return nil, diags + } + } + + tfElems = append(tfElems, tfVal) + } + + err := tftypes.ValidateValue(tfType, tfElems) + if err != nil { + return nil, append(diags, validateValueErrorDiag(err, path)) + } + + tfVal := tftypes.NewValue(tfType, tfElems) + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfVal, path)...) + + if diags.HasError() { + return nil, diags + } + } + + attrVal, err := typ.ValueFromTerraform(ctx, tfVal) + + if err != nil { + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from slice value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + + return attrVal, diags +} diff --git a/internal/provider/reflect/struct.go b/internal/provider/reflect/struct.go new file mode 100644 index 0000000..b670228 --- /dev/null +++ b/internal/provider/reflect/struct.go @@ -0,0 +1,266 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package reflect + +import ( + "context" + "fmt" + "reflect" + "strings" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/attr/xattr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-go/tftypes" +) + +// Struct builds a new struct using the data in `object`, as long as `object` +// is a `tftypes.Object`. It will take the struct type from `target`, which +// must be a struct type. +// +// The properties on `target` must be tagged with a "tfsdk" label containing +// the field name to map to that property. Every property must be tagged, and +// every property must be present in the type of `object`, and all the +// attributes in the type of `object` must have a corresponding property. +// Properties that don't map to object attributes must have a `tfsdk:"-"` tag, +// explicitly defining them as not part of the object. This is to catch typos +// and other mistakes early. +// +// Struct is meant to be called from Into, not directly. +func Struct(ctx context.Context, typ attr.Type, object tftypes.Value, target reflect.Value, opts Options, path path.Path) (reflect.Value, diag.Diagnostics) { + var diags diag.Diagnostics + + // this only works with object values, so make sure that constraint is + // met + if target.Kind() != reflect.Struct { + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Val: object, + TargetType: target.Type(), + Err: fmt.Errorf("expected a struct type, got %s", target.Type()), + })) + return target, diags + } + if !object.Type().Is(tftypes.Object{}) { + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Val: object, + TargetType: target.Type(), + Err: fmt.Errorf("cannot reflect %s into a struct, must be an object", object.Type().String()), + })) + return target, diags + } + attrsType, ok := typ.(attr.TypeWithAttributeTypes) + if !ok { + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Val: object, + TargetType: target.Type(), + Err: fmt.Errorf("cannot reflect object using type information provided by %T, %T must be an attr.TypeWithAttributeTypes", typ, typ), + })) + return target, diags + } + + // collect a map of fields that are in the object passed in + var objectFields map[string]tftypes.Value + err := object.As(&objectFields) + if err != nil { + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Val: object, + TargetType: target.Type(), + Err: err, + })) + return target, diags + } + + // collect a map of fields that are defined in the tags of the struct + // passed in + targetFields, err := getStructTags(ctx, target, path) + if err != nil { + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Val: object, + TargetType: target.Type(), + Err: fmt.Errorf("error retrieving field names from struct tags: %w", err), + })) + return target, diags + } + + // we require an exact, 1:1 match of these fields to avoid typos + // leading to surprises, so let's ensure they have the exact same + // fields defined + var objectMissing, targetMissing []string + for field := range targetFields { + if _, ok := objectFields[field]; !ok { + objectMissing = append(objectMissing, field) + } + } + for field := range objectFields { + if _, ok := targetFields[field]; !ok { + targetMissing = append(targetMissing, field) + } + } + if len(objectMissing) > 0 || len(targetMissing) > 0 { + var missing []string + if len(objectMissing) > 0 { + missing = append(missing, fmt.Sprintf("Struct defines fields not found in object: %s.", commaSeparatedString(objectMissing))) + } + if len(targetMissing) > 0 { + missing = append(missing, fmt.Sprintf("Object defines fields not found in struct: %s.", commaSeparatedString(targetMissing))) + } + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Val: object, + TargetType: target.Type(), + Err: fmt.Errorf("mismatch between struct and object: %s", strings.Join(missing, " ")), + })) + return target, diags + } + + attrTypes := attrsType.AttributeTypes() + + // now that we know they match perfectly, fill the struct with the + // values in the object + // Fork start + //result := reflect.New(target.Type()).Elem() + var result reflect.Value + if target.CanSet() { + result = target + } else { + result = reflect.New(target.Type()).Elem() + } + // Fork End + for field, structFieldPos := range targetFields { + attrType, ok := attrTypes[field] + if !ok { + diags.Append(diag.WithPath(path, DiagIntoIncompatibleType{ + Val: object, + TargetType: target.Type(), + Err: fmt.Errorf("could not find type information for attribute in supplied attr.Type %T", typ), + })) + return target, diags + } + structField := result.Field(structFieldPos) + fieldVal, fieldValDiags := BuildValue(ctx, attrType, objectFields[field], structField, opts, path.AtName(field)) + diags.Append(fieldValDiags...) + + if diags.HasError() { + return target, diags + } + structField.Set(fieldVal) + } + return result, diags +} + +// FromStruct builds an attr.Value as produced by `typ` from the data in `val`. +// `val` must be a struct type, and must have all its properties tagged and be +// a 1:1 match with the attributes reported by `typ`. FromStruct will recurse +// into FromValue for each attribute, using the type of the attribute as +// reported by `typ`. +// +// It is meant to be called through FromValue, not directly. +func FromStruct(ctx context.Context, typ attr.TypeWithAttributeTypes, val reflect.Value, path path.Path) (attr.Value, diag.Diagnostics) { + var diags diag.Diagnostics + objTypes := map[string]tftypes.Type{} + objValues := map[string]tftypes.Value{} + + // collect a map of fields that are defined in the tags of the struct + // passed in + targetFields, err := getStructTags(ctx, val, path) + if err != nil { + err = fmt.Errorf("error retrieving field names from struct tags: %w", err) + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from struct value. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error(), + ) + return nil, diags + } + + attrTypes := typ.AttributeTypes() + + var objectMissing, structMissing []string + + for field := range targetFields { + if _, ok := attrTypes[field]; !ok { + objectMissing = append(objectMissing, field) + } + } + + for attrName, attrType := range attrTypes { + if attrType == nil { + objectMissing = append(objectMissing, attrName) + } + + if _, ok := targetFields[attrName]; !ok { + structMissing = append(structMissing, attrName) + } + } + + if len(objectMissing) > 0 || len(structMissing) > 0 { + missing := make([]string, 0, len(objectMissing)+len(structMissing)) + + if len(objectMissing) > 0 { + missing = append(missing, fmt.Sprintf("Struct defines fields not found in object: %s.", commaSeparatedString(objectMissing))) + } + + if len(structMissing) > 0 { + missing = append(missing, fmt.Sprintf("Object defines fields not found in struct: %s.", commaSeparatedString(structMissing))) + } + + diags.AddAttributeError( + path, + "Value Conversion Error", + "An unexpected error was encountered trying to convert from struct into an object. "+ + "This is always an error in the provider. Please report the following to the provider developer:\n\n"+ + fmt.Sprintf("Mismatch between struct and object type: %s\n", strings.Join(missing, " "))+ + fmt.Sprintf("Struct: %s\n", val.Type())+ + fmt.Sprintf("Object type: %s", typ), + ) + + return nil, diags + } + + for name, fieldNo := range targetFields { + path := path.AtName(name) + fieldValue := val.Field(fieldNo) + + attrVal, attrValDiags := FromValue(ctx, attrTypes[name], fieldValue.Interface(), path) + diags.Append(attrValDiags...) + + if diags.HasError() { + return nil, diags + } + + tfObjVal, err := attrVal.ToTerraformValue(ctx) + if err != nil { + return nil, append(diags, toTerraformValueErrorDiag(err, path)) + } + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfObjVal, path)...) + + if diags.HasError() { + return nil, diags + } + } + + objValues[name] = tfObjVal + objTypes[name] = tfObjVal.Type() + } + + tfVal := tftypes.NewValue(tftypes.Object{ + AttributeTypes: objTypes, + }, objValues) + + if typeWithValidate, ok := typ.(xattr.TypeWithValidate); ok { + diags.Append(typeWithValidate.Validate(ctx, tfVal, path)...) + + if diags.HasError() { + return nil, diags + } + } + + ret, err := typ.ValueFromTerraform(ctx, tfVal) + if err != nil { + return nil, append(diags, valueFromTerraformErrorDiag(err, path)) + } + + return ret, diags +} diff --git a/internal/provider/type_s3_reference.go b/internal/provider/type_s3_reference.go new file mode 100644 index 0000000..44eff13 --- /dev/null +++ b/internal/provider/type_s3_reference.go @@ -0,0 +1,10 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package provider + +import "github.com/hashicorp/terraform-plugin-framework/types" + +type S3Reference struct { + Bucket types.String `tfsdk:"bucket"` + Key types.String `tfsdk:"key"` +} diff --git a/internal/provider/type_versions.go b/internal/provider/type_versions.go new file mode 100644 index 0000000..fc4b5ad --- /dev/null +++ b/internal/provider/type_versions.go @@ -0,0 +1,7 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package provider + +type Versions struct { + S3ref *S3Reference `tfsdk:"s3ref"` +} diff --git a/internal/provider/uploadfile_resource.go b/internal/provider/uploadfile_resource.go new file mode 100644 index 0000000..235e357 --- /dev/null +++ b/internal/provider/uploadfile_resource.go @@ -0,0 +1,236 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package provider + +import ( + "context" + "fmt" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/models/operations" + "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/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" +) + +// Ensure provider defined types fully satisfy framework interfaces. +var _ resource.Resource = &UploadFileResource{} +var _ resource.ResourceWithImportState = &UploadFileResource{} + +func NewUploadFileResource() resource.Resource { + return &UploadFileResource{} +} + +// UploadFileResource defines the resource implementation. +type UploadFileResource struct { + client *sdk.SDK +} + +// UploadFileResourceModel describes the resource data model. +type UploadFileResourceModel struct { + FileEntityID types.String `tfsdk:"file_entity_id"` + Filename types.String `tfsdk:"filename"` + MimeType types.String `tfsdk:"mime_type"` + PublicURL types.String `tfsdk:"public_url"` + S3ref *S3Reference `tfsdk:"s3ref"` + UploadURL types.String `tfsdk:"upload_url"` +} + +func (r *UploadFileResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_upload_file" +} + +func (r *UploadFileResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "UploadFile Resource", + + Attributes: map[string]schema.Attribute{ + "file_entity_id": schema.StringAttribute{ + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + }, + Optional: true, + Description: `file entity id. Requires replacement if changed. `, + }, + "filename": schema.StringAttribute{ + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + }, + Required: true, + Description: `Requires replacement if changed. `, + }, + "mime_type": schema.StringAttribute{ + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplaceIfConfigured(), + }, + Optional: true, + Description: `MIME type of file. Requires replacement if changed. ; Default: "application/octet-stream"`, + }, + "public_url": schema.StringAttribute{ + Computed: true, + Description: `Returned only if file is permanent i.e. file_entity_id is passed`, + }, + "s3ref": schema.SingleNestedAttribute{ + Computed: true, + Attributes: map[string]schema.Attribute{ + "bucket": schema.StringAttribute{ + Computed: true, + }, + "key": schema.StringAttribute{ + Computed: true, + }, + }, + }, + "upload_url": schema.StringAttribute{ + Computed: true, + }, + }, + } +} + +func (r *UploadFileResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*sdk.SDK) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *sdk.SDK, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + r.client = client +} + +func (r *UploadFileResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var data *UploadFileResourceModel + var plan types.Object + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(plan.As(ctx, &data, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + })...) + + if resp.Diagnostics.HasError() { + return + } + + uploadFilePayload := data.ToSharedUploadFilePayload() + fileEntityID := new(string) + if !data.FileEntityID.IsUnknown() && !data.FileEntityID.IsNull() { + *fileEntityID = data.FileEntityID.ValueString() + } else { + fileEntityID = nil + } + request := operations.UploadFileV2Request{ + UploadFilePayload: uploadFilePayload, + FileEntityID: fileEntityID, + } + res, err := r.client.Files.UploadFileV2(ctx, request) + if err != nil { + resp.Diagnostics.AddError("failure to invoke API", err.Error()) + if res != nil && res.RawResponse != nil { + resp.Diagnostics.AddError("unexpected http request/response", debugResponse(res.RawResponse)) + } + return + } + if res == nil { + resp.Diagnostics.AddError("unexpected response from API", fmt.Sprintf("%v", res)) + return + } + if res.StatusCode != 201 { + resp.Diagnostics.AddError(fmt.Sprintf("unexpected response from API. Got an unexpected response code %v", res.StatusCode), debugResponse(res.RawResponse)) + return + } + if res.FileUpload == nil { + resp.Diagnostics.AddError("unexpected response from API. No response body", debugResponse(res.RawResponse)) + return + } + data.RefreshFromSharedFileUpload(res.FileUpload) + refreshPlan(ctx, plan, &data, resp.Diagnostics) + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *UploadFileResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var data *UploadFileResourceModel + var item types.Object + + resp.Diagnostics.Append(req.State.Get(ctx, &item)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(item.As(ctx, &data, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + })...) + + if resp.Diagnostics.HasError() { + return + } + + // Not Implemented; we rely entirely on CREATE API request response + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *UploadFileResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var data *UploadFileResourceModel + var plan types.Object + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + merge(ctx, req, resp, &data) + if resp.Diagnostics.HasError() { + return + } + + // Not Implemented; all attributes marked as RequiresReplace + + // Save updated data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (r *UploadFileResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var data *UploadFileResourceModel + var item types.Object + + resp.Diagnostics.Append(req.State.Get(ctx, &item)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(item.As(ctx, &data, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + })...) + + if resp.Diagnostics.HasError() { + return + } + + // Not Implemented; entity does not have a configured DELETE operation +} + +func (r *UploadFileResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resp.Diagnostics.AddError("Not Implemented", "No available import state operation is available for resource upload_file.") +} diff --git a/internal/provider/uploadfile_resource_sdk.go b/internal/provider/uploadfile_resource_sdk.go new file mode 100644 index 0000000..17b09cd --- /dev/null +++ b/internal/provider/uploadfile_resource_sdk.go @@ -0,0 +1,35 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package provider + +import ( + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/models/shared" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +func (r *UploadFileResourceModel) ToSharedUploadFilePayload() *shared.UploadFilePayload { + filename := r.Filename.ValueString() + mimeType := new(string) + if !r.MimeType.IsUnknown() && !r.MimeType.IsNull() { + *mimeType = r.MimeType.ValueString() + } else { + mimeType = nil + } + out := shared.UploadFilePayload{ + Filename: filename, + MimeType: mimeType, + } + return &out +} + +func (r *UploadFileResourceModel) RefreshFromSharedFileUpload(resp *shared.FileUpload) { + r.PublicURL = types.StringPointerValue(resp.PublicURL) + if resp.S3ref == nil { + r.S3ref = nil + } else { + r.S3ref = &S3Reference{} + r.S3ref.Bucket = types.StringValue(resp.S3ref.Bucket) + r.S3ref.Key = types.StringValue(resp.S3ref.Key) + } + r.UploadURL = types.StringPointerValue(resp.UploadURL) +} diff --git a/internal/provider/utils.go b/internal/provider/utils.go new file mode 100644 index 0000000..0e70af2 --- /dev/null +++ b/internal/provider/utils.go @@ -0,0 +1,86 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package provider + +import ( + "context" + "encoding/json" + "fmt" + tfReflect "github.com/epilot-dev/terraform-provider-epilot-file/internal/provider/reflect" + "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/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "net/http" + "net/http/httputil" + "reflect" +) + +func debugResponse(response *http.Response) string { + dumpReq, err := httputil.DumpRequest(response.Request, true) + if err != nil { + dumpReq, err = httputil.DumpRequest(response.Request, false) + if err != nil { + return err.Error() + } + } + dumpRes, err := httputil.DumpResponse(response, true) + if err != nil { + dumpRes, err = httputil.DumpResponse(response, false) + if err != nil { + return err.Error() + } + } + return fmt.Sprintf("**Request**:\n%s\n**Response**:\n%s", string(dumpReq), string(dumpRes)) +} + +func reflectJSONKey(data any, key string) reflect.Value { + jsonIfied, err := json.Marshal(data) + if err != nil { + panic(fmt.Errorf("failed to marshal data: %w", err)) + } + var jsonMap map[string]interface{} + err = json.Unmarshal(jsonIfied, &jsonMap) + if err != nil { + panic(fmt.Errorf("failed to unmarshal data: %w", err)) + } + return reflect.ValueOf(jsonMap[key]) +} + +func merge(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, target interface{}) { + var plan types.Object + var state types.Object + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(state.As(ctx, target, basetypes.ObjectAsOptions{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + })...) + if resp.Diagnostics.HasError() { + return + } + + refreshPlan(ctx, plan, target, resp.Diagnostics) +} + +func refreshPlan(ctx context.Context, plan types.Object, target interface{}, diagnostics diag.Diagnostics) { + obj := types.ObjectType{AttrTypes: plan.AttributeTypes(ctx)} + val, err := plan.ToTerraformValue(ctx) + if err != nil { + diagnostics.Append(diag.NewErrorDiagnostic("Object Conversion Error", "An unexpected error was encountered trying to convert object. This is always an error in the provider. Please report the following to the provider developer:\n\n"+err.Error())) + return + } + diagnostics.Append(tfReflect.Into(ctx, obj, val, target, tfReflect.Options{ + UnhandledNullAsEmpty: true, + UnhandledUnknownAsEmpty: true, + }, path.Empty())...) +} diff --git a/internal/sdk/files.go b/internal/sdk/files.go new file mode 100644 index 0000000..d6a5db5 --- /dev/null +++ b/internal/sdk/files.go @@ -0,0 +1,1643 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package sdk + +import ( + "bytes" + "context" + "fmt" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/models/operations" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/models/sdkerrors" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/models/shared" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/utils" + "io" + "net/http" + "strings" +) + +// Files API +type Files struct { + sdkConfiguration sdkConfiguration +} + +func newFiles(sdkConfig sdkConfiguration) *Files { + return &Files{ + sdkConfiguration: sdkConfig, + } +} + +// AccessPublicLink - accessPublicLink +// Redirects to a accessible signed url for the respective file associated to the public link +func (s *Files) AccessPublicLink(ctx context.Context, request operations.AccessPublicLinkRequest, opts ...operations.Option) (*operations.AccessPublicLinkResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url, err := utils.GenerateURL(ctx, baseURL, "/v1/files/public/links/{id}/{filename}", request, nil) + if err != nil { + return nil, fmt.Errorf("error generating URL: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "*/*") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.AccessPublicLinkResponse{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 302: + } + + return res, nil +} + +// DeleteFile - deleteFile +// Delete file entity +func (s *Files) DeleteFile(ctx context.Context, request *shared.DeleteFilePayload, opts ...operations.Option) (*operations.DeleteFileResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url := strings.TrimSuffix(baseURL, "/") + "/v1/files/delete" + + bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "Request", "json", `request:"mediaType=application/json"`) + if err != nil { + return nil, fmt.Errorf("error serializing request body: %w", err) + } + debugBody := bytes.NewBuffer([]byte{}) + debugReader := io.TeeReader(bodyReader, debugBody) + + req, err := http.NewRequestWithContext(ctx, "DELETE", url, debugReader) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "*/*") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + req.Header.Set("Content-Type", reqContentType) + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Request.Body = io.NopCloser(debugBody) + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.DeleteFileResponse{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 200: + } + + return res, nil +} + +// DownloadFile - downloadFile +// Generate pre-signed download S3 url for a file +func (s *Files) DownloadFile(ctx context.Context, request operations.DownloadFileRequest, opts ...operations.Option) (*operations.DownloadFileResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url, err := utils.GenerateURL(ctx, baseURL, "/v1/files/{id}/download", request, nil) + if err != nil { + return nil, fmt.Errorf("error generating URL: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { + return nil, fmt.Errorf("error populating query params: %w", err) + } + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.DownloadFileResponse{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 200: + switch { + case utils.MatchContentType(contentType, `application/json`): + var out operations.DownloadFileResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &out + default: + return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", contentType), httpRes.StatusCode, string(rawBody), httpRes) + } + } + + return res, nil +} + +// DownloadFiles - downloadFiles +// Generate pre-signed download S3 urls for multiple files +func (s *Files) DownloadFiles(ctx context.Context, request []shared.DownloadFilesPayload, opts ...operations.Option) (*operations.DownloadFilesResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url := strings.TrimSuffix(baseURL, "/") + "/v1/files:downloadFiles" + + bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "Request", "json", `request:"mediaType=application/json"`) + if err != nil { + return nil, fmt.Errorf("error serializing request body: %w", err) + } + debugBody := bytes.NewBuffer([]byte{}) + debugReader := io.TeeReader(bodyReader, debugBody) + + req, err := http.NewRequestWithContext(ctx, "POST", url, debugReader) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + req.Header.Set("Content-Type", reqContentType) + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Request.Body = io.NopCloser(debugBody) + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.DownloadFilesResponse{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 200: + switch { + case utils.MatchContentType(contentType, `application/json`): + var out []operations.ResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Classes = out + default: + return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", contentType), httpRes.StatusCode, string(rawBody), httpRes) + } + } + + return res, nil +} + +// DownloadS3File - downloadS3File +// Generate pre-signed download S3 url for a file +func (s *Files) DownloadS3File(ctx context.Context, request operations.DownloadS3FileRequest, opts ...operations.Option) (*operations.DownloadS3FileResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url := strings.TrimSuffix(baseURL, "/") + "/v1/files:downloadS3" + + req, err := http.NewRequestWithContext(ctx, "POST", url, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { + return nil, fmt.Errorf("error populating query params: %w", err) + } + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.DownloadS3FileResponse{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 200: + switch { + case utils.MatchContentType(contentType, `application/json`): + var out operations.DownloadS3FileResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &out + default: + return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", contentType), httpRes.StatusCode, string(rawBody), httpRes) + } + } + + return res, nil +} + +// GeneratePublicLink - generatePublicLink +// Generates a public link to access the private files +func (s *Files) GeneratePublicLink(ctx context.Context, request operations.GeneratePublicLinkRequest, opts ...operations.Option) (*operations.GeneratePublicLinkResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url, err := utils.GenerateURL(ctx, baseURL, "/v1/files/{id}/public/links", request, nil) + if err != nil { + return nil, fmt.Errorf("error generating URL: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, "POST", url, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.GeneratePublicLinkResponse{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 201: + switch { + case utils.MatchContentType(contentType, `application/json`): + out := string(rawBody) + res.Res = &out + default: + return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", contentType), httpRes.StatusCode, string(rawBody), httpRes) + } + } + + return res, nil +} + +// GetAllPublicLinksForFile - getAllPublicLinksForFile +// Not yet implemented; This API would fetches all the public links that are previously generated for a file +func (s *Files) GetAllPublicLinksForFile(ctx context.Context, request operations.GetAllPublicLinksForFileRequest, opts ...operations.Option) (*operations.GetAllPublicLinksForFileResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url, err := utils.GenerateURL(ctx, baseURL, "/v1/files/{id}/public/links", request, nil) + if err != nil { + return nil, fmt.Errorf("error generating URL: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.GetAllPublicLinksForFileResponse{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 200: + switch { + case utils.MatchContentType(contentType, `application/json`): + var out operations.GetAllPublicLinksForFileResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &out + default: + return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", contentType), httpRes.StatusCode, string(rawBody), httpRes) + } + } + + return res, nil +} + +// PreviewFile - previewFile +// Generate thumbnail preview for a file entity +func (s *Files) PreviewFile(ctx context.Context, request operations.PreviewFileRequest, opts ...operations.Option) (*operations.PreviewFileResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url, err := utils.GenerateURL(ctx, baseURL, "/v1/files/{id}/preview", request, nil) + if err != nil { + return nil, fmt.Errorf("error generating URL: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "*/*") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { + return nil, fmt.Errorf("error populating query params: %w", err) + } + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.PreviewFileResponse{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 200: + } + + return res, nil +} + +// PreviewPublicFile - previewPublicFile +// Generate thumbnail preview for a public file entity +func (s *Files) PreviewPublicFile(ctx context.Context, request operations.PreviewPublicFileRequest, opts ...operations.Option) (*operations.PreviewPublicFileResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url, err := utils.GenerateURL(ctx, baseURL, "/v1/files/public/{id}/preview", request, nil) + if err != nil { + return nil, fmt.Errorf("error generating URL: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "*/*") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { + return nil, fmt.Errorf("error populating query params: %w", err) + } + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.PreviewPublicFileResponse{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 200: + } + + return res, nil +} + +// PreviewS3File - previewS3File +// Generate thumbnail preview from an s3 reference for a file entity +func (s *Files) PreviewS3File(ctx context.Context, request operations.PreviewS3FileRequest, opts ...operations.Option) (*operations.PreviewS3FileResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url := strings.TrimSuffix(baseURL, "/") + "/v1/files:previewS3" + + bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "S3Reference", "json", `request:"mediaType=application/json"`) + if err != nil { + return nil, fmt.Errorf("error serializing request body: %w", err) + } + debugBody := bytes.NewBuffer([]byte{}) + debugReader := io.TeeReader(bodyReader, debugBody) + + req, err := http.NewRequestWithContext(ctx, "POST", url, debugReader) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "*/*") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + req.Header.Set("Content-Type", reqContentType) + + if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { + return nil, fmt.Errorf("error populating query params: %w", err) + } + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Request.Body = io.NopCloser(debugBody) + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.PreviewS3FileResponse{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 200: + } + + return res, nil +} + +// PreviewS3FileGet - previewS3FileGet +// Get thumbnail preview from an s3 reference for a file entity +func (s *Files) PreviewS3FileGet(ctx context.Context, request operations.PreviewS3FileGetRequest, opts ...operations.Option) (*operations.PreviewS3FileGetResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url := strings.TrimSuffix(baseURL, "/") + "/v1/files:previewS3" + + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "*/*") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { + return nil, fmt.Errorf("error populating query params: %w", err) + } + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.PreviewS3FileGetResponse{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 200: + } + + return res, nil +} + +// RevokePublicLink - revokePublicLink +// Not yet implemented; This operation would revokes a given public link by ID +func (s *Files) RevokePublicLink(ctx context.Context, request operations.RevokePublicLinkRequest, opts ...operations.Option) (*operations.RevokePublicLinkResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url, err := utils.GenerateURL(ctx, baseURL, "/v1/files/public/links/{id}", request, nil) + if err != nil { + return nil, fmt.Errorf("error generating URL: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, "DELETE", url, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.RevokePublicLinkResponse{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 204: + switch { + case utils.MatchContentType(contentType, `application/json`): + out := string(rawBody) + res.Res = &out + default: + return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", contentType), httpRes.StatusCode, string(rawBody), httpRes) + } + } + + return res, nil +} + +// SaveFile - saveFile +// Create / Update a permanent File entity +// +// # Makes file object permanent +// +// Saves metadata to file entity +func (s *Files) SaveFile(ctx context.Context, request *shared.FileEntity, opts ...operations.Option) (*operations.SaveFileResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url := strings.TrimSuffix(baseURL, "/") + "/v1/files" + + bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "Request", "json", `request:"mediaType=application/json"`) + if err != nil { + return nil, fmt.Errorf("error serializing request body: %w", err) + } + debugBody := bytes.NewBuffer([]byte{}) + debugReader := io.TeeReader(bodyReader, debugBody) + + req, err := http.NewRequestWithContext(ctx, "POST", url, debugReader) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + req.Header.Set("Content-Type", reqContentType) + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Request.Body = io.NopCloser(debugBody) + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.SaveFileResponse{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 201: + switch { + case utils.MatchContentType(contentType, `application/json`): + var out shared.FileEntity + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.FileEntity = &out + default: + return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", contentType), httpRes.StatusCode, string(rawBody), httpRes) + } + } + + return res, nil +} + +// UploadFile - uploadFile +// Create pre-signed S3 URL to upload a file to keep temporarily (one week). +// +// Use the createFile operation to store file file permanently. +func (s *Files) UploadFile(ctx context.Context, request operations.UploadFileRequest, opts ...operations.Option) (*operations.UploadFileResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url := strings.TrimSuffix(baseURL, "/") + "/v1/files/upload" + + bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "UploadFilePayload", "json", `request:"mediaType=application/json"`) + if err != nil { + return nil, fmt.Errorf("error serializing request body: %w", err) + } + debugBody := bytes.NewBuffer([]byte{}) + debugReader := io.TeeReader(bodyReader, debugBody) + + req, err := http.NewRequestWithContext(ctx, "POST", url, debugReader) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + req.Header.Set("Content-Type", reqContentType) + + if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { + return nil, fmt.Errorf("error populating query params: %w", err) + } + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Request.Body = io.NopCloser(debugBody) + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.UploadFileResponse{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 201: + switch { + case utils.MatchContentType(contentType, `application/json`): + var out operations.UploadFileResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &out + default: + return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", contentType), httpRes.StatusCode, string(rawBody), httpRes) + } + } + + return res, nil +} + +// UploadFilePublic - uploadFilePublic +// Create pre-signed S3 URL to upload a file to keep temporarily (one week). +// +// Use the createFile operation to store file file permanently. +func (s *Files) UploadFilePublic(ctx context.Context, request *shared.UploadFilePayload, opts ...operations.Option) (*operations.UploadFilePublicResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url := strings.TrimSuffix(baseURL, "/") + "/v1/files/public/upload" + + bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "Request", "json", `request:"mediaType=application/json"`) + if err != nil { + return nil, fmt.Errorf("error serializing request body: %w", err) + } + debugBody := bytes.NewBuffer([]byte{}) + debugReader := io.TeeReader(bodyReader, debugBody) + + req, err := http.NewRequestWithContext(ctx, "POST", url, debugReader) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + req.Header.Set("Content-Type", reqContentType) + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Request.Body = io.NopCloser(debugBody) + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.UploadFilePublicResponse{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 201: + switch { + case utils.MatchContentType(contentType, `application/json`): + var out operations.UploadFilePublicResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &out + default: + return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", contentType), httpRes.StatusCode, string(rawBody), httpRes) + } + } + + return res, nil +} + +// UploadFileV2 - uploadFileV2 +// Create pre-signed S3 URL to upload a file to keep temporarily (one week). - v2 +// +// Use the createFile operation to store file file permanently. +func (s *Files) UploadFileV2(ctx context.Context, request operations.UploadFileV2Request, opts ...operations.Option) (*operations.UploadFileV2Response, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url := strings.TrimSuffix(baseURL, "/") + "/v2/files/upload" + + bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "UploadFilePayload", "json", `request:"mediaType=application/json"`) + if err != nil { + return nil, fmt.Errorf("error serializing request body: %w", err) + } + debugBody := bytes.NewBuffer([]byte{}) + debugReader := io.TeeReader(bodyReader, debugBody) + + req, err := http.NewRequestWithContext(ctx, "POST", url, debugReader) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + req.Header.Set("Content-Type", reqContentType) + + if err := utils.PopulateQueryParams(ctx, req, request, nil); err != nil { + return nil, fmt.Errorf("error populating query params: %w", err) + } + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Request.Body = io.NopCloser(debugBody) + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.UploadFileV2Response{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 201: + switch { + case utils.MatchContentType(contentType, `application/json`): + var out shared.FileUpload + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.FileUpload = &out + default: + return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", contentType), httpRes.StatusCode, string(rawBody), httpRes) + } + } + + return res, nil +} + +// VerifyCustomDownloadURL - verifyCustomDownloadUrl +// Verify a pre-signed custom download url for a file +func (s *Files) VerifyCustomDownloadURL(ctx context.Context, request *shared.VerifyCustomDownloadURLPayload, opts ...operations.Option) (*operations.VerifyCustomDownloadURLResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url := strings.TrimSuffix(baseURL, "/") + "/v1/files/download:verify" + + bodyReader, reqContentType, err := utils.SerializeRequestBody(ctx, request, false, true, "Request", "json", `request:"mediaType=application/json"`) + if err != nil { + return nil, fmt.Errorf("error serializing request body: %w", err) + } + debugBody := bytes.NewBuffer([]byte{}) + debugReader := io.TeeReader(bodyReader, debugBody) + + req, err := http.NewRequestWithContext(ctx, "POST", url, debugReader) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + req.Header.Set("Content-Type", reqContentType) + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Request.Body = io.NopCloser(debugBody) + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.VerifyCustomDownloadURLResponse{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 200: + switch { + case utils.MatchContentType(contentType, `application/json`): + var out operations.VerifyCustomDownloadURLResponseBody + if err := utils.UnmarshalJsonFromResponseBody(bytes.NewBuffer(rawBody), &out, ""); err != nil { + return nil, err + } + + res.Object = &out + default: + return nil, sdkerrors.NewSDKError(fmt.Sprintf("unknown content-type received: %s", contentType), httpRes.StatusCode, string(rawBody), httpRes) + } + } + + return res, nil +} diff --git a/internal/sdk/pkg/models/operations/accesspubliclink.go b/internal/sdk/pkg/models/operations/accesspubliclink.go new file mode 100644 index 0000000..6bdd9b9 --- /dev/null +++ b/internal/sdk/pkg/models/operations/accesspubliclink.go @@ -0,0 +1,56 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "net/http" +) + +type AccessPublicLinkRequest struct { + Filename string `pathParam:"style=simple,explode=false,name=filename"` + ID string `pathParam:"style=simple,explode=false,name=id"` +} + +func (o *AccessPublicLinkRequest) GetFilename() string { + if o == nil { + return "" + } + return o.Filename +} + +func (o *AccessPublicLinkRequest) GetID() string { + if o == nil { + return "" + } + return o.ID +} + +type AccessPublicLinkResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response +} + +func (o *AccessPublicLinkResponse) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *AccessPublicLinkResponse) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *AccessPublicLinkResponse) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} diff --git a/internal/sdk/pkg/models/operations/deletefile.go b/internal/sdk/pkg/models/operations/deletefile.go new file mode 100644 index 0000000..41534c7 --- /dev/null +++ b/internal/sdk/pkg/models/operations/deletefile.go @@ -0,0 +1,37 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "net/http" +) + +type DeleteFileResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response +} + +func (o *DeleteFileResponse) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *DeleteFileResponse) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *DeleteFileResponse) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} diff --git a/internal/sdk/pkg/models/operations/deletesession.go b/internal/sdk/pkg/models/operations/deletesession.go new file mode 100644 index 0000000..dbabda2 --- /dev/null +++ b/internal/sdk/pkg/models/operations/deletesession.go @@ -0,0 +1,37 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "net/http" +) + +type DeleteSessionResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response +} + +func (o *DeleteSessionResponse) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *DeleteSessionResponse) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *DeleteSessionResponse) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} diff --git a/internal/sdk/pkg/models/operations/downloadfile.go b/internal/sdk/pkg/models/operations/downloadfile.go new file mode 100644 index 0000000..1ea2732 --- /dev/null +++ b/internal/sdk/pkg/models/operations/downloadfile.go @@ -0,0 +1,99 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/utils" + "net/http" +) + +type DownloadFileRequest struct { + // Controls the Content-Disposition header to control browser behaviour. Set to true to trigger download. + Attachment *bool `default:"true" queryParam:"style=form,explode=true,name=attachment"` + ID string `pathParam:"style=simple,explode=false,name=id"` + // index of file version + Version *int64 `default:"0" queryParam:"style=form,explode=true,name=version"` +} + +func (d DownloadFileRequest) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(d, "", false) +} + +func (d *DownloadFileRequest) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &d, "", false, false); err != nil { + return err + } + return nil +} + +func (o *DownloadFileRequest) GetAttachment() *bool { + if o == nil { + return nil + } + return o.Attachment +} + +func (o *DownloadFileRequest) GetID() string { + if o == nil { + return "" + } + return o.ID +} + +func (o *DownloadFileRequest) GetVersion() *int64 { + if o == nil { + return nil + } + return o.Version +} + +// DownloadFileResponseBody - Generated thumbnail image +type DownloadFileResponseBody struct { + DownloadURL *string `json:"download_url,omitempty"` +} + +func (o *DownloadFileResponseBody) GetDownloadURL() *string { + if o == nil { + return nil + } + return o.DownloadURL +} + +type DownloadFileResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response + // Generated thumbnail image + Object *DownloadFileResponseBody +} + +func (o *DownloadFileResponse) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *DownloadFileResponse) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *DownloadFileResponse) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} + +func (o *DownloadFileResponse) GetObject() *DownloadFileResponseBody { + if o == nil { + return nil + } + return o.Object +} diff --git a/internal/sdk/pkg/models/operations/downloadfiles.go b/internal/sdk/pkg/models/operations/downloadfiles.go new file mode 100644 index 0000000..e6bd438 --- /dev/null +++ b/internal/sdk/pkg/models/operations/downloadfiles.go @@ -0,0 +1,65 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "net/http" +) + +type ResponseBody struct { + DownloadURL *string `json:"download_url,omitempty"` + FileEntityID *string `json:"file_entity_id,omitempty"` +} + +func (o *ResponseBody) GetDownloadURL() *string { + if o == nil { + return nil + } + return o.DownloadURL +} + +func (o *ResponseBody) GetFileEntityID() *string { + if o == nil { + return nil + } + return o.FileEntityID +} + +type DownloadFilesResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response + // Generated download urls + Classes []ResponseBody +} + +func (o *DownloadFilesResponse) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *DownloadFilesResponse) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *DownloadFilesResponse) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} + +func (o *DownloadFilesResponse) GetClasses() []ResponseBody { + if o == nil { + return nil + } + return o.Classes +} diff --git a/internal/sdk/pkg/models/operations/downloads3file.go b/internal/sdk/pkg/models/operations/downloads3file.go new file mode 100644 index 0000000..a9e55b8 --- /dev/null +++ b/internal/sdk/pkg/models/operations/downloads3file.go @@ -0,0 +1,98 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/utils" + "net/http" +) + +type DownloadS3FileRequest struct { + // Controls the Content-Disposition header to control browser behaviour. Set to true to trigger download. + Attachment *bool `default:"true" queryParam:"style=form,explode=true,name=attachment"` + S3Bucket string `queryParam:"style=form,explode=true,name=s3_bucket"` + S3Key string `queryParam:"style=form,explode=true,name=s3_key"` +} + +func (d DownloadS3FileRequest) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(d, "", false) +} + +func (d *DownloadS3FileRequest) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &d, "", false, false); err != nil { + return err + } + return nil +} + +func (o *DownloadS3FileRequest) GetAttachment() *bool { + if o == nil { + return nil + } + return o.Attachment +} + +func (o *DownloadS3FileRequest) GetS3Bucket() string { + if o == nil { + return "" + } + return o.S3Bucket +} + +func (o *DownloadS3FileRequest) GetS3Key() string { + if o == nil { + return "" + } + return o.S3Key +} + +// DownloadS3FileResponseBody - Generated thumbnail image +type DownloadS3FileResponseBody struct { + DownloadURL *string `json:"download_url,omitempty"` +} + +func (o *DownloadS3FileResponseBody) GetDownloadURL() *string { + if o == nil { + return nil + } + return o.DownloadURL +} + +type DownloadS3FileResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response + // Generated thumbnail image + Object *DownloadS3FileResponseBody +} + +func (o *DownloadS3FileResponse) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *DownloadS3FileResponse) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *DownloadS3FileResponse) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} + +func (o *DownloadS3FileResponse) GetObject() *DownloadS3FileResponseBody { + if o == nil { + return nil + } + return o.Object +} diff --git a/internal/sdk/pkg/models/operations/generatepubliclink.go b/internal/sdk/pkg/models/operations/generatepubliclink.go new file mode 100644 index 0000000..96b4ddf --- /dev/null +++ b/internal/sdk/pkg/models/operations/generatepubliclink.go @@ -0,0 +1,57 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "net/http" +) + +type GeneratePublicLinkRequest struct { + ID string `pathParam:"style=simple,explode=false,name=id"` +} + +func (o *GeneratePublicLinkRequest) GetID() string { + if o == nil { + return "" + } + return o.ID +} + +type GeneratePublicLinkResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response + // Returns the public link which can be used to access the file later + Res *string +} + +func (o *GeneratePublicLinkResponse) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *GeneratePublicLinkResponse) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *GeneratePublicLinkResponse) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} + +func (o *GeneratePublicLinkResponse) GetRes() *string { + if o == nil { + return nil + } + return o.Res +} diff --git a/internal/sdk/pkg/models/operations/getallpubliclinksforfile.go b/internal/sdk/pkg/models/operations/getallpubliclinksforfile.go new file mode 100644 index 0000000..7ab1382 --- /dev/null +++ b/internal/sdk/pkg/models/operations/getallpubliclinksforfile.go @@ -0,0 +1,70 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/models/shared" + "net/http" +) + +type GetAllPublicLinksForFileRequest struct { + ID string `pathParam:"style=simple,explode=false,name=id"` +} + +func (o *GetAllPublicLinksForFileRequest) GetID() string { + if o == nil { + return "" + } + return o.ID +} + +// GetAllPublicLinksForFileResponseBody - Public links of a file retrieved successfully +type GetAllPublicLinksForFileResponseBody struct { + Results []shared.PublicLink `json:"results,omitempty"` +} + +func (o *GetAllPublicLinksForFileResponseBody) GetResults() []shared.PublicLink { + if o == nil { + return nil + } + return o.Results +} + +type GetAllPublicLinksForFileResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response + // Public links of a file retrieved successfully + Object *GetAllPublicLinksForFileResponseBody +} + +func (o *GetAllPublicLinksForFileResponse) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *GetAllPublicLinksForFileResponse) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *GetAllPublicLinksForFileResponse) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} + +func (o *GetAllPublicLinksForFileResponse) GetObject() *GetAllPublicLinksForFileResponseBody { + if o == nil { + return nil + } + return o.Object +} diff --git a/internal/sdk/pkg/models/operations/getsession.go b/internal/sdk/pkg/models/operations/getsession.go new file mode 100644 index 0000000..af62ac5 --- /dev/null +++ b/internal/sdk/pkg/models/operations/getsession.go @@ -0,0 +1,37 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "net/http" +) + +type GetSessionResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response +} + +func (o *GetSessionResponse) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *GetSessionResponse) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *GetSessionResponse) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} diff --git a/internal/sdk/pkg/models/operations/options.go b/internal/sdk/pkg/models/operations/options.go new file mode 100644 index 0000000..a54a582 --- /dev/null +++ b/internal/sdk/pkg/models/operations/options.go @@ -0,0 +1,86 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "errors" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/utils" +) + +var ErrUnsupportedOption = errors.New("unsupported option") + +const ( + SupportedOptionServerURL = "serverURL" + SupportedOptionRetries = "retries" + SupportedOptionAcceptHeaderOverride = "acceptHeaderOverride" +) + +type AcceptHeaderEnum string + +const ( + AcceptHeaderEnumWildcardWildcard AcceptHeaderEnum = "*/*" + AcceptHeaderEnumApplicationJson AcceptHeaderEnum = "application/json" +) + +func (e AcceptHeaderEnum) ToPointer() *AcceptHeaderEnum { + return &e +} + +type Options struct { + ServerURL *string + Retries *utils.RetryConfig + AcceptHeaderOverride *AcceptHeaderEnum +} + +type Option func(*Options, ...string) error + +// WithServerURL allows providing an alternative server URL. +func WithServerURL(serverURL string) Option { + return func(opts *Options, supportedOptions ...string) error { + if !utils.Contains(supportedOptions, SupportedOptionServerURL) { + return ErrUnsupportedOption + } + + opts.ServerURL = &serverURL + return nil + } +} + +// WithTemplatedServerURL allows providing an alternative server URL with templated parameters. +func WithTemplatedServerURL(serverURL string, params map[string]string) Option { + return func(opts *Options, supportedOptions ...string) error { + if !utils.Contains(supportedOptions, SupportedOptionServerURL) { + return ErrUnsupportedOption + } + + if params != nil { + serverURL = utils.ReplaceParameters(serverURL, params) + } + + opts.ServerURL = &serverURL + return nil + } +} + +// WithRetries allows customizing the default retry configuration. +func WithRetries(config utils.RetryConfig) Option { + return func(opts *Options, supportedOptions ...string) error { + if !utils.Contains(supportedOptions, SupportedOptionRetries) { + return ErrUnsupportedOption + } + + opts.Retries = &config + return nil + } +} + +func WithAcceptHeaderOverride(acceptHeaderOverride AcceptHeaderEnum) Option { + return func(opts *Options, supportedOptions ...string) error { + if !utils.Contains(supportedOptions, SupportedOptionAcceptHeaderOverride) { + return ErrUnsupportedOption + } + + opts.AcceptHeaderOverride = &acceptHeaderOverride + return nil + } +} diff --git a/internal/sdk/pkg/models/operations/previewfile.go b/internal/sdk/pkg/models/operations/previewfile.go new file mode 100644 index 0000000..1e283da --- /dev/null +++ b/internal/sdk/pkg/models/operations/previewfile.go @@ -0,0 +1,87 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/utils" + "net/http" +) + +type PreviewFileRequest struct { + // height + H *int64 `queryParam:"style=form,explode=true,name=h"` + ID string `pathParam:"style=simple,explode=false,name=id"` + // index of file version + Version *int64 `default:"0" queryParam:"style=form,explode=true,name=version"` + // width + W *int64 `queryParam:"style=form,explode=true,name=w"` +} + +func (p PreviewFileRequest) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(p, "", false) +} + +func (p *PreviewFileRequest) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &p, "", false, false); err != nil { + return err + } + return nil +} + +func (o *PreviewFileRequest) GetH() *int64 { + if o == nil { + return nil + } + return o.H +} + +func (o *PreviewFileRequest) GetID() string { + if o == nil { + return "" + } + return o.ID +} + +func (o *PreviewFileRequest) GetVersion() *int64 { + if o == nil { + return nil + } + return o.Version +} + +func (o *PreviewFileRequest) GetW() *int64 { + if o == nil { + return nil + } + return o.W +} + +type PreviewFileResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response +} + +func (o *PreviewFileResponse) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *PreviewFileResponse) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *PreviewFileResponse) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} diff --git a/internal/sdk/pkg/models/operations/previewpublicfile.go b/internal/sdk/pkg/models/operations/previewpublicfile.go new file mode 100644 index 0000000..2996d27 --- /dev/null +++ b/internal/sdk/pkg/models/operations/previewpublicfile.go @@ -0,0 +1,96 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/utils" + "net/http" +) + +type PreviewPublicFileRequest struct { + // height + H *int64 `queryParam:"style=form,explode=true,name=h"` + ID string `pathParam:"style=simple,explode=false,name=id"` + // Org id + OrgID *string `queryParam:"style=form,explode=true,name=org_id"` + // index of file version + Version *int64 `default:"0" queryParam:"style=form,explode=true,name=version"` + // width + W *int64 `queryParam:"style=form,explode=true,name=w"` +} + +func (p PreviewPublicFileRequest) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(p, "", false) +} + +func (p *PreviewPublicFileRequest) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &p, "", false, false); err != nil { + return err + } + return nil +} + +func (o *PreviewPublicFileRequest) GetH() *int64 { + if o == nil { + return nil + } + return o.H +} + +func (o *PreviewPublicFileRequest) GetID() string { + if o == nil { + return "" + } + return o.ID +} + +func (o *PreviewPublicFileRequest) GetOrgID() *string { + if o == nil { + return nil + } + return o.OrgID +} + +func (o *PreviewPublicFileRequest) GetVersion() *int64 { + if o == nil { + return nil + } + return o.Version +} + +func (o *PreviewPublicFileRequest) GetW() *int64 { + if o == nil { + return nil + } + return o.W +} + +type PreviewPublicFileResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response +} + +func (o *PreviewPublicFileResponse) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *PreviewPublicFileResponse) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *PreviewPublicFileResponse) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} diff --git a/internal/sdk/pkg/models/operations/previews3file.go b/internal/sdk/pkg/models/operations/previews3file.go new file mode 100644 index 0000000..42809b2 --- /dev/null +++ b/internal/sdk/pkg/models/operations/previews3file.go @@ -0,0 +1,67 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/models/shared" + "net/http" +) + +type PreviewS3FileRequest struct { + S3Reference *shared.S3Reference `request:"mediaType=application/json"` + // height + H *int64 `queryParam:"style=form,explode=true,name=h"` + // width + W *int64 `queryParam:"style=form,explode=true,name=w"` +} + +func (o *PreviewS3FileRequest) GetS3Reference() *shared.S3Reference { + if o == nil { + return nil + } + return o.S3Reference +} + +func (o *PreviewS3FileRequest) GetH() *int64 { + if o == nil { + return nil + } + return o.H +} + +func (o *PreviewS3FileRequest) GetW() *int64 { + if o == nil { + return nil + } + return o.W +} + +type PreviewS3FileResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response +} + +func (o *PreviewS3FileResponse) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *PreviewS3FileResponse) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *PreviewS3FileResponse) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} diff --git a/internal/sdk/pkg/models/operations/previews3fileget.go b/internal/sdk/pkg/models/operations/previews3fileget.go new file mode 100644 index 0000000..4408a96 --- /dev/null +++ b/internal/sdk/pkg/models/operations/previews3fileget.go @@ -0,0 +1,76 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "net/http" +) + +type PreviewS3FileGetRequest struct { + // s3 bucket + Bucket string `queryParam:"style=form,explode=true,name=bucket"` + // height + H *int64 `queryParam:"style=form,explode=true,name=h"` + // s3 key + Key string `queryParam:"style=form,explode=true,name=key"` + // width + W *int64 `queryParam:"style=form,explode=true,name=w"` +} + +func (o *PreviewS3FileGetRequest) GetBucket() string { + if o == nil { + return "" + } + return o.Bucket +} + +func (o *PreviewS3FileGetRequest) GetH() *int64 { + if o == nil { + return nil + } + return o.H +} + +func (o *PreviewS3FileGetRequest) GetKey() string { + if o == nil { + return "" + } + return o.Key +} + +func (o *PreviewS3FileGetRequest) GetW() *int64 { + if o == nil { + return nil + } + return o.W +} + +type PreviewS3FileGetResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response +} + +func (o *PreviewS3FileGetResponse) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *PreviewS3FileGetResponse) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *PreviewS3FileGetResponse) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} diff --git a/internal/sdk/pkg/models/operations/revokepubliclink.go b/internal/sdk/pkg/models/operations/revokepubliclink.go new file mode 100644 index 0000000..91d18b6 --- /dev/null +++ b/internal/sdk/pkg/models/operations/revokepubliclink.go @@ -0,0 +1,57 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "net/http" +) + +type RevokePublicLinkRequest struct { + ID string `pathParam:"style=simple,explode=false,name=id"` +} + +func (o *RevokePublicLinkRequest) GetID() string { + if o == nil { + return "" + } + return o.ID +} + +type RevokePublicLinkResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response + // Revokes a public link successfully. + Res *string +} + +func (o *RevokePublicLinkResponse) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *RevokePublicLinkResponse) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *RevokePublicLinkResponse) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} + +func (o *RevokePublicLinkResponse) GetRes() *string { + if o == nil { + return nil + } + return o.Res +} diff --git a/internal/sdk/pkg/models/operations/savefile.go b/internal/sdk/pkg/models/operations/savefile.go new file mode 100644 index 0000000..31e2b82 --- /dev/null +++ b/internal/sdk/pkg/models/operations/savefile.go @@ -0,0 +1,47 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/models/shared" + "net/http" +) + +type SaveFileResponse struct { + // HTTP response content type for this operation + ContentType string + // Created File Entity + FileEntity *shared.FileEntity + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response +} + +func (o *SaveFileResponse) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *SaveFileResponse) GetFileEntity() *shared.FileEntity { + if o == nil { + return nil + } + return o.FileEntity +} + +func (o *SaveFileResponse) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *SaveFileResponse) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} diff --git a/internal/sdk/pkg/models/operations/uploadfile.go b/internal/sdk/pkg/models/operations/uploadfile.go new file mode 100644 index 0000000..4d18b4b --- /dev/null +++ b/internal/sdk/pkg/models/operations/uploadfile.go @@ -0,0 +1,115 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/models/shared" + "net/http" +) + +type UploadFileRequest struct { + UploadFilePayload *shared.UploadFilePayload `request:"mediaType=application/json"` + // file entity id + FileEntityID *string `queryParam:"style=form,explode=true,name=file_entity_id"` +} + +func (o *UploadFileRequest) GetUploadFilePayload() *shared.UploadFilePayload { + if o == nil { + return nil + } + return o.UploadFilePayload +} + +func (o *UploadFileRequest) GetFileEntityID() *string { + if o == nil { + return nil + } + return o.FileEntityID +} + +type S3ref struct { + Bucket string `json:"bucket"` + Key string `json:"key"` +} + +func (o *S3ref) GetBucket() string { + if o == nil { + return "" + } + return o.Bucket +} + +func (o *S3ref) GetKey() string { + if o == nil { + return "" + } + return o.Key +} + +// UploadFileResponseBody - Pre-signed URL for POST / PUT upload +type UploadFileResponseBody struct { + // Returned only if file is permanent i.e. file_entity_id is passed + PublicURL *string `json:"public_url,omitempty"` + S3ref *S3ref `json:"s3ref,omitempty"` + UploadURL *string `json:"upload_url,omitempty"` +} + +func (o *UploadFileResponseBody) GetPublicURL() *string { + if o == nil { + return nil + } + return o.PublicURL +} + +func (o *UploadFileResponseBody) GetS3ref() *S3ref { + if o == nil { + return nil + } + return o.S3ref +} + +func (o *UploadFileResponseBody) GetUploadURL() *string { + if o == nil { + return nil + } + return o.UploadURL +} + +type UploadFileResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response + // Pre-signed URL for POST / PUT upload + Object *UploadFileResponseBody +} + +func (o *UploadFileResponse) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *UploadFileResponse) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *UploadFileResponse) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} + +func (o *UploadFileResponse) GetObject() *UploadFileResponseBody { + if o == nil { + return nil + } + return o.Object +} diff --git a/internal/sdk/pkg/models/operations/uploadfilepublic.go b/internal/sdk/pkg/models/operations/uploadfilepublic.go new file mode 100644 index 0000000..f13e15f --- /dev/null +++ b/internal/sdk/pkg/models/operations/uploadfilepublic.go @@ -0,0 +1,85 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "net/http" +) + +type UploadFilePublicS3ref struct { + Bucket string `json:"bucket"` + Key string `json:"key"` +} + +func (o *UploadFilePublicS3ref) GetBucket() string { + if o == nil { + return "" + } + return o.Bucket +} + +func (o *UploadFilePublicS3ref) GetKey() string { + if o == nil { + return "" + } + return o.Key +} + +// UploadFilePublicResponseBody - Pre-signed URL for POST / PUT upload +type UploadFilePublicResponseBody struct { + S3ref *UploadFilePublicS3ref `json:"s3ref,omitempty"` + UploadURL *string `json:"upload_url,omitempty"` +} + +func (o *UploadFilePublicResponseBody) GetS3ref() *UploadFilePublicS3ref { + if o == nil { + return nil + } + return o.S3ref +} + +func (o *UploadFilePublicResponseBody) GetUploadURL() *string { + if o == nil { + return nil + } + return o.UploadURL +} + +type UploadFilePublicResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response + // Pre-signed URL for POST / PUT upload + Object *UploadFilePublicResponseBody +} + +func (o *UploadFilePublicResponse) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *UploadFilePublicResponse) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *UploadFilePublicResponse) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} + +func (o *UploadFilePublicResponse) GetObject() *UploadFilePublicResponseBody { + if o == nil { + return nil + } + return o.Object +} diff --git a/internal/sdk/pkg/models/operations/uploadfilev2.go b/internal/sdk/pkg/models/operations/uploadfilev2.go new file mode 100644 index 0000000..4d2b5fc --- /dev/null +++ b/internal/sdk/pkg/models/operations/uploadfilev2.go @@ -0,0 +1,67 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/models/shared" + "net/http" +) + +type UploadFileV2Request struct { + UploadFilePayload *shared.UploadFilePayload `request:"mediaType=application/json"` + // file entity id + FileEntityID *string `queryParam:"style=form,explode=true,name=file_entity_id"` +} + +func (o *UploadFileV2Request) GetUploadFilePayload() *shared.UploadFilePayload { + if o == nil { + return nil + } + return o.UploadFilePayload +} + +func (o *UploadFileV2Request) GetFileEntityID() *string { + if o == nil { + return nil + } + return o.FileEntityID +} + +type UploadFileV2Response struct { + // HTTP response content type for this operation + ContentType string + // Pre-signed URL for POST / PUT upload + FileUpload *shared.FileUpload + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response +} + +func (o *UploadFileV2Response) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *UploadFileV2Response) GetFileUpload() *shared.FileUpload { + if o == nil { + return nil + } + return o.FileUpload +} + +func (o *UploadFileV2Response) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *UploadFileV2Response) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} diff --git a/internal/sdk/pkg/models/operations/verifycustomdownloadurl.go b/internal/sdk/pkg/models/operations/verifycustomdownloadurl.go new file mode 100644 index 0000000..ae4f7ff --- /dev/null +++ b/internal/sdk/pkg/models/operations/verifycustomdownloadurl.go @@ -0,0 +1,58 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package operations + +import ( + "net/http" +) + +// VerifyCustomDownloadURLResponseBody - Download Url matches signature and has not expired +type VerifyCustomDownloadURLResponseBody struct { + Valid *bool `json:"valid,omitempty"` +} + +func (o *VerifyCustomDownloadURLResponseBody) GetValid() *bool { + if o == nil { + return nil + } + return o.Valid +} + +type VerifyCustomDownloadURLResponse struct { + // HTTP response content type for this operation + ContentType string + // HTTP response status code for this operation + StatusCode int + // Raw HTTP response; suitable for custom response parsing + RawResponse *http.Response + // Download Url matches signature and has not expired + Object *VerifyCustomDownloadURLResponseBody +} + +func (o *VerifyCustomDownloadURLResponse) GetContentType() string { + if o == nil { + return "" + } + return o.ContentType +} + +func (o *VerifyCustomDownloadURLResponse) GetStatusCode() int { + if o == nil { + return 0 + } + return o.StatusCode +} + +func (o *VerifyCustomDownloadURLResponse) GetRawResponse() *http.Response { + if o == nil { + return nil + } + return o.RawResponse +} + +func (o *VerifyCustomDownloadURLResponse) GetObject() *VerifyCustomDownloadURLResponseBody { + if o == nil { + return nil + } + return o.Object +} diff --git a/internal/sdk/pkg/models/sdkerrors/sdkerror.go b/internal/sdk/pkg/models/sdkerrors/sdkerror.go new file mode 100644 index 0000000..5c1affd --- /dev/null +++ b/internal/sdk/pkg/models/sdkerrors/sdkerror.go @@ -0,0 +1,35 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package sdkerrors + +import ( + "fmt" + "net/http" +) + +type SDKError struct { + Message string + StatusCode int + Body string + RawResponse *http.Response +} + +var _ error = &SDKError{} + +func NewSDKError(message string, statusCode int, body string, httpRes *http.Response) *SDKError { + return &SDKError{ + Message: message, + StatusCode: statusCode, + Body: body, + RawResponse: httpRes, + } +} + +func (e *SDKError) Error() string { + body := "" + if len(e.Body) > 0 { + body = fmt.Sprintf("\n%s", e.Body) + } + + return fmt.Sprintf("%s: Status %d%s", e.Message, e.StatusCode, body) +} diff --git a/internal/sdk/pkg/models/shared/deletefilepayload.go b/internal/sdk/pkg/models/shared/deletefilepayload.go new file mode 100644 index 0000000..e8ad06f --- /dev/null +++ b/internal/sdk/pkg/models/shared/deletefilepayload.go @@ -0,0 +1,14 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package shared + +type DeleteFilePayload struct { + S3ref S3Reference `json:"s3ref"` +} + +func (o *DeleteFilePayload) GetS3ref() S3Reference { + if o == nil { + return S3Reference{} + } + return o.S3ref +} diff --git a/internal/sdk/pkg/models/shared/downloadfilespayload.go b/internal/sdk/pkg/models/shared/downloadfilespayload.go new file mode 100644 index 0000000..90bd934 --- /dev/null +++ b/internal/sdk/pkg/models/shared/downloadfilespayload.go @@ -0,0 +1,23 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package shared + +type DownloadFilesPayload struct { + ID string `json:"id"` + // File version + Version *int64 `json:"version,omitempty"` +} + +func (o *DownloadFilesPayload) GetID() string { + if o == nil { + return "" + } + return o.ID +} + +func (o *DownloadFilesPayload) GetVersion() *int64 { + if o == nil { + return nil + } + return o.Version +} diff --git a/internal/sdk/pkg/models/shared/fileentity.go b/internal/sdk/pkg/models/shared/fileentity.go new file mode 100644 index 0000000..e55a6b6 --- /dev/null +++ b/internal/sdk/pkg/models/shared/fileentity.go @@ -0,0 +1,187 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package shared + +import ( + "encoding/json" + "fmt" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/utils" +) + +type AccessControl string + +const ( + AccessControlPrivate AccessControl = "private" + AccessControlPublicRead AccessControl = "public-read" +) + +func (e AccessControl) ToPointer() *AccessControl { + return &e +} + +func (e *AccessControl) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + switch v { + case "private": + fallthrough + case "public-read": + *e = AccessControl(v) + return nil + default: + return fmt.Errorf("invalid value for AccessControl: %v", v) + } +} + +// Type - Human readable type for file +type Type string + +const ( + TypeDocument Type = "document" + TypeDocumentTemplate Type = "document_template" + TypeText Type = "text" + TypeImage Type = "image" + TypeVideo Type = "video" + TypeAudio Type = "audio" + TypeSpreadsheet Type = "spreadsheet" + TypePresentation Type = "presentation" + TypeFont Type = "font" + TypeArchive Type = "archive" + TypeApplication Type = "application" + TypeUnknown Type = "unknown" +) + +func (e Type) ToPointer() *Type { + return &e +} + +func (e *Type) UnmarshalJSON(data []byte) error { + var v string + if err := json.Unmarshal(data, &v); err != nil { + return err + } + switch v { + case "document": + fallthrough + case "document_template": + fallthrough + case "text": + fallthrough + case "image": + fallthrough + case "video": + fallthrough + case "audio": + fallthrough + case "spreadsheet": + fallthrough + case "presentation": + fallthrough + case "font": + fallthrough + case "archive": + fallthrough + case "application": + fallthrough + case "unknown": + *e = Type(v) + return nil + default: + return fmt.Errorf("invalid value for Type: %v", v) + } +} + +type Versions struct { + S3ref *S3Reference `json:"s3ref,omitempty"` +} + +func (o *Versions) GetS3ref() *S3Reference { + if o == nil { + return nil + } + return o.S3ref +} + +type FileEntity struct { + ID *string `json:"_id,omitempty"` + AccessControl *AccessControl `default:"private" json:"access_control"` + Filename *string `json:"filename,omitempty"` + // MIME type of the file + MimeType *string `json:"mime_type,omitempty"` + // Direct URL for file (public only if file access control is public-read) + PublicURL *string `json:"public_url,omitempty"` + // File size in bytes + SizeBytes *int64 `json:"size_bytes,omitempty"` + // Human readable type for file + Type *Type `json:"type,omitempty"` + Versions []Versions `json:"versions,omitempty"` +} + +func (f FileEntity) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(f, "", false) +} + +func (f *FileEntity) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &f, "", false, false); err != nil { + return err + } + return nil +} + +func (o *FileEntity) GetID() *string { + if o == nil { + return nil + } + return o.ID +} + +func (o *FileEntity) GetAccessControl() *AccessControl { + if o == nil { + return nil + } + return o.AccessControl +} + +func (o *FileEntity) GetFilename() *string { + if o == nil { + return nil + } + return o.Filename +} + +func (o *FileEntity) GetMimeType() *string { + if o == nil { + return nil + } + return o.MimeType +} + +func (o *FileEntity) GetPublicURL() *string { + if o == nil { + return nil + } + return o.PublicURL +} + +func (o *FileEntity) GetSizeBytes() *int64 { + if o == nil { + return nil + } + return o.SizeBytes +} + +func (o *FileEntity) GetType() *Type { + if o == nil { + return nil + } + return o.Type +} + +func (o *FileEntity) GetVersions() []Versions { + if o == nil { + return nil + } + return o.Versions +} diff --git a/internal/sdk/pkg/models/shared/fileupload.go b/internal/sdk/pkg/models/shared/fileupload.go new file mode 100644 index 0000000..b8e94c6 --- /dev/null +++ b/internal/sdk/pkg/models/shared/fileupload.go @@ -0,0 +1,50 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package shared + +type S3ref struct { + Bucket string `json:"bucket"` + Key string `json:"key"` +} + +func (o *S3ref) GetBucket() string { + if o == nil { + return "" + } + return o.Bucket +} + +func (o *S3ref) GetKey() string { + if o == nil { + return "" + } + return o.Key +} + +type FileUpload struct { + // Returned only if file is permanent i.e. file_entity_id is passed + PublicURL *string `json:"public_url,omitempty"` + S3ref *S3ref `json:"s3ref,omitempty"` + UploadURL *string `json:"upload_url,omitempty"` +} + +func (o *FileUpload) GetPublicURL() *string { + if o == nil { + return nil + } + return o.PublicURL +} + +func (o *FileUpload) GetS3ref() *S3ref { + if o == nil { + return nil + } + return o.S3ref +} + +func (o *FileUpload) GetUploadURL() *string { + if o == nil { + return nil + } + return o.UploadURL +} diff --git a/internal/sdk/pkg/models/shared/publiclink.go b/internal/sdk/pkg/models/shared/publiclink.go new file mode 100644 index 0000000..5b1edd2 --- /dev/null +++ b/internal/sdk/pkg/models/shared/publiclink.go @@ -0,0 +1,33 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package shared + +type PublicLink struct { + // ID of the public link + ID *string `json:"id,omitempty"` + // The most recent timestamp when the file was accessed + LastAccessedAt *string `json:"last_accessed_at,omitempty"` + // Public link of the file + Link *string `json:"link,omitempty"` +} + +func (o *PublicLink) GetID() *string { + if o == nil { + return nil + } + return o.ID +} + +func (o *PublicLink) GetLastAccessedAt() *string { + if o == nil { + return nil + } + return o.LastAccessedAt +} + +func (o *PublicLink) GetLink() *string { + if o == nil { + return nil + } + return o.Link +} diff --git a/internal/sdk/pkg/models/shared/s3reference.go b/internal/sdk/pkg/models/shared/s3reference.go new file mode 100644 index 0000000..e790d61 --- /dev/null +++ b/internal/sdk/pkg/models/shared/s3reference.go @@ -0,0 +1,22 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package shared + +type S3Reference struct { + Bucket string `json:"bucket"` + Key string `json:"key"` +} + +func (o *S3Reference) GetBucket() string { + if o == nil { + return "" + } + return o.Bucket +} + +func (o *S3Reference) GetKey() string { + if o == nil { + return "" + } + return o.Key +} diff --git a/internal/sdk/pkg/models/shared/security.go b/internal/sdk/pkg/models/shared/security.go new file mode 100644 index 0000000..f0e282a --- /dev/null +++ b/internal/sdk/pkg/models/shared/security.go @@ -0,0 +1,22 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package shared + +type Security struct { + CookieAuth *string `security:"scheme,type=apiKey,subtype=cookie,name=token"` + EpilotAuth *string `security:"scheme,type=http,subtype=bearer,name=Authorization"` +} + +func (o *Security) GetCookieAuth() *string { + if o == nil { + return nil + } + return o.CookieAuth +} + +func (o *Security) GetEpilotAuth() *string { + if o == nil { + return nil + } + return o.EpilotAuth +} diff --git a/internal/sdk/pkg/models/shared/uploadfilepayload.go b/internal/sdk/pkg/models/shared/uploadfilepayload.go new file mode 100644 index 0000000..0d883eb --- /dev/null +++ b/internal/sdk/pkg/models/shared/uploadfilepayload.go @@ -0,0 +1,38 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package shared + +import ( + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/utils" +) + +type UploadFilePayload struct { + Filename string `json:"filename"` + // MIME type of file + MimeType *string `default:"application/octet-stream" json:"mime_type"` +} + +func (u UploadFilePayload) MarshalJSON() ([]byte, error) { + return utils.MarshalJSON(u, "", false) +} + +func (u *UploadFilePayload) UnmarshalJSON(data []byte) error { + if err := utils.UnmarshalJSON(data, &u, "", false, false); err != nil { + return err + } + return nil +} + +func (o *UploadFilePayload) GetFilename() string { + if o == nil { + return "" + } + return o.Filename +} + +func (o *UploadFilePayload) GetMimeType() *string { + if o == nil { + return nil + } + return o.MimeType +} diff --git a/internal/sdk/pkg/models/shared/verifycustomdownloadurlpayload.go b/internal/sdk/pkg/models/shared/verifycustomdownloadurlpayload.go new file mode 100644 index 0000000..adb4e5b --- /dev/null +++ b/internal/sdk/pkg/models/shared/verifycustomdownloadurlpayload.go @@ -0,0 +1,15 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package shared + +type VerifyCustomDownloadURLPayload struct { + // Custom external download url with signature and expiration time + CustomDownloadURL string `json:"custom_download_url"` +} + +func (o *VerifyCustomDownloadURLPayload) GetCustomDownloadURL() string { + if o == nil { + return "" + } + return o.CustomDownloadURL +} diff --git a/internal/sdk/pkg/types/bigint.go b/internal/sdk/pkg/types/bigint.go new file mode 100644 index 0000000..afd0cd2 --- /dev/null +++ b/internal/sdk/pkg/types/bigint.go @@ -0,0 +1,21 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package types + +import ( + "fmt" + "math/big" +) + +// MustNewBigIntFromString returns an instance of big.Int from a string +// The string is assumed to be base 10 and if it is not a valid big.Int +// then the function panics. +// Avoid using this function in production code. +func MustNewBigIntFromString(s string) *big.Int { + i, ok := new(big.Int).SetString(s, 10) + if !ok { + panic(fmt.Errorf("failed to parse string as big.Int")) + } + + return i +} diff --git a/internal/sdk/pkg/types/date.go b/internal/sdk/pkg/types/date.go new file mode 100644 index 0000000..c4648fa --- /dev/null +++ b/internal/sdk/pkg/types/date.go @@ -0,0 +1,90 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package types + +import ( + "encoding/json" + "fmt" + "strings" + "time" +) + +// Date is a wrapper around time.Time that allows for JSON marshaling a date string formatted as "2006-01-02". +type Date struct { + time.Time +} + +var ( + _ json.Marshaler = &Date{} + _ json.Unmarshaler = &Date{} + _ fmt.Stringer = &Date{} +) + +// NewDate returns an instance of Date from a time.Time. +func NewDate(t time.Time) *Date { + d := DateFromTime(t) + return &d +} + +// DateFromTime returns a Date from a time.Time. +func DateFromTime(t time.Time) Date { + return Date{t} +} + +// NewDateFromString returns an instance of Date from a string formatted as "2006-01-02". +func NewDateFromString(str string) (*Date, error) { + d, err := DateFromString(str) + if err != nil { + return nil, err + } + + return &d, nil +} + +// DateFromString returns a Date from a string formatted as "2006-01-02". +func DateFromString(str string) (Date, error) { + var d Date + var err error + + d.Time, err = time.Parse("2006-01-02", str) + return d, err +} + +// MustNewDateFromString returns an instance of Date from a string formatted as "2006-01-02" or panics. +// Avoid using this function in production code. +func MustNewDateFromString(str string) *Date { + d := MustDateFromString(str) + return &d +} + +// MustDateFromString returns a Date from a string formatted as "2006-01-02" or panics. +// Avoid using this function in production code. +func MustDateFromString(str string) Date { + d, err := DateFromString(str) + if err != nil { + panic(err) + } + return d +} + +func (d Date) GetTime() time.Time { + return d.Time +} + +func (d Date) MarshalJSON() ([]byte, error) { + return []byte(fmt.Sprintf(`"%s"`, d.Time.Format("2006-01-02"))), nil +} + +func (d *Date) UnmarshalJSON(data []byte) error { + var err error + + str := string(data) + str = strings.Trim(str, `"`) + + d.Time, err = time.Parse("2006-01-02", str) + return err +} + +func (d Date) String() string { + return d.Time.Format("2006-01-02") +} diff --git a/internal/sdk/pkg/types/datetime.go b/internal/sdk/pkg/types/datetime.go new file mode 100644 index 0000000..0529b25 --- /dev/null +++ b/internal/sdk/pkg/types/datetime.go @@ -0,0 +1,23 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package types + +import "time" + +// MustTimeFromString returns a time.Time from a string formatted as "2006-01-02T15:04:05Z07:00" or panics. +// Avoid using this function in production code. +func MustTimeFromString(str string) time.Time { + t, err := time.Parse(time.RFC3339, str) + if err != nil { + panic(err) + } + + return t +} + +// MustNewTimeFromString returns an instance of time.Time from a string formatted as "2006-01-02T15:04:05Z07:00" or panics. +// Avoid using this function in production code. +func MustNewTimeFromString(str string) *time.Time { + t := MustTimeFromString(str) + return &t +} diff --git a/internal/sdk/pkg/types/decimal.go b/internal/sdk/pkg/types/decimal.go new file mode 100644 index 0000000..a42284b --- /dev/null +++ b/internal/sdk/pkg/types/decimal.go @@ -0,0 +1,20 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package types + +import ( + "fmt" + + "github.com/ericlagergren/decimal" +) + +// MustNewDecimalFromString returns an instance of Decimal from a string +// Avoid using this function in production code. +func MustNewDecimalFromString(s string) *decimal.Big { + d, ok := new(decimal.Big).SetString(s) + if !ok { + panic(fmt.Errorf("failed to parse string as decimal.Big")) + } + + return d +} diff --git a/internal/sdk/pkg/types/pointers.go b/internal/sdk/pkg/types/pointers.go new file mode 100644 index 0000000..4f15e99 --- /dev/null +++ b/internal/sdk/pkg/types/pointers.go @@ -0,0 +1,10 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package types + +func String(s string) *string { return &s } +func Bool(b bool) *bool { return &b } +func Int(i int) *int { return &i } +func Int64(i int64) *int64 { return &i } +func Float32(f float32) *float32 { return &f } +func Float64(f float64) *float64 { return &f } diff --git a/internal/sdk/pkg/utils/contenttype.go b/internal/sdk/pkg/utils/contenttype.go new file mode 100644 index 0000000..8ed13e2 --- /dev/null +++ b/internal/sdk/pkg/utils/contenttype.go @@ -0,0 +1,33 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package utils + +import ( + "fmt" + "mime" + "strings" +) + +func MatchContentType(contentType string, pattern string) bool { + if contentType == pattern || pattern == "*" || pattern == "*/*" { + return true + } + + mediaType, _, err := mime.ParseMediaType(contentType) + if err != nil { + return false + } + + if mediaType == pattern { + return true + } + + parts := strings.Split(mediaType, "/") + if len(parts) == 2 { + if fmt.Sprintf("%s/*", parts[0]) == pattern || fmt.Sprintf("*/%s", parts[1]) == pattern { + return true + } + } + + return false +} diff --git a/internal/sdk/pkg/utils/form.go b/internal/sdk/pkg/utils/form.go new file mode 100644 index 0000000..eb3ab61 --- /dev/null +++ b/internal/sdk/pkg/utils/form.go @@ -0,0 +1,117 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package utils + +import ( + "fmt" + "math/big" + "net/url" + "reflect" + "strings" + "time" + + "github.com/ericlagergren/decimal" + + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/types" +) + +func populateForm(paramName string, explode bool, objType reflect.Type, objValue reflect.Value, delimiter string, getFieldName func(reflect.StructField) string) url.Values { + + formValues := url.Values{} + + if isNil(objType, objValue) { + return formValues + } + + if objType.Kind() == reflect.Pointer { + objType = objType.Elem() + objValue = objValue.Elem() + } + + switch objType.Kind() { + case reflect.Struct: + switch objValue.Interface().(type) { + case time.Time: + formValues.Add(paramName, valToString(objValue.Interface())) + case types.Date: + formValues.Add(paramName, valToString(objValue.Interface())) + case big.Int: + formValues.Add(paramName, valToString(objValue.Interface())) + case decimal.Big: + formValues.Add(paramName, valToString(objValue.Interface())) + default: + var items []string + + for i := 0; i < objType.NumField(); i++ { + fieldType := objType.Field(i) + valType := objValue.Field(i) + + if isNil(fieldType.Type, valType) { + continue + } + + if valType.Kind() == reflect.Pointer { + valType = valType.Elem() + } + + fieldName := getFieldName(fieldType) + if fieldName == "" { + continue + } + + if explode { + formValues.Add(fieldName, valToString(valType.Interface())) + } else { + items = append(items, fmt.Sprintf("%s%s%s", fieldName, delimiter, valToString(valType.Interface()))) + } + } + + if len(items) > 0 { + formValues.Add(paramName, strings.Join(items, delimiter)) + } + } + case reflect.Map: + items := []string{} + + iter := objValue.MapRange() + for iter.Next() { + if explode { + formValues.Add(iter.Key().String(), valToString(iter.Value().Interface())) + } else { + items = append(items, fmt.Sprintf("%s%s%s", iter.Key().String(), delimiter, valToString(iter.Value().Interface()))) + } + } + + if len(items) > 0 { + formValues.Add(paramName, strings.Join(items, delimiter)) + } + case reflect.Slice, reflect.Array: + values := parseDelimitedArray(explode, objValue, delimiter) + for _, v := range values { + formValues.Add(paramName, v) + } + default: + formValues.Add(paramName, valToString(objValue.Interface())) + } + + return formValues +} + +func parseDelimitedArray(explode bool, objValue reflect.Value, delimiter string) []string { + values := []string{} + items := []string{} + + for i := 0; i < objValue.Len(); i++ { + if explode { + values = append(values, valToString(objValue.Index(i).Interface())) + } else { + items = append(items, valToString(objValue.Index(i).Interface())) + } + } + + if len(items) > 0 { + values = append(values, strings.Join(items, delimiter)) + } + + return values +} diff --git a/internal/sdk/pkg/utils/headers.go b/internal/sdk/pkg/utils/headers.go new file mode 100644 index 0000000..0837022 --- /dev/null +++ b/internal/sdk/pkg/utils/headers.go @@ -0,0 +1,102 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package utils + +import ( + "context" + "fmt" + "net/http" + "reflect" + "strings" +) + +func PopulateHeaders(ctx context.Context, req *http.Request, headers interface{}) { + headerParamsStructType := reflect.TypeOf(headers) + headerParamsValType := reflect.ValueOf(headers) + + for i := 0; i < headerParamsStructType.NumField(); i++ { + fieldType := headerParamsStructType.Field(i) + valType := headerParamsValType.Field(i) + + tag := parseParamTag(headerParamTagKey, fieldType, "simple", false) + if tag == nil { + continue + } + + value := serializeHeader(fieldType.Type, valType, tag.Explode) + if value != "" { + req.Header.Add(tag.ParamName, value) + } + } +} + +func serializeHeader(objType reflect.Type, objValue reflect.Value, explode bool) string { + if isNil(objType, objValue) { + return "" + } + + if objType.Kind() == reflect.Pointer { + objType = objType.Elem() + objValue = objValue.Elem() + } + + switch objType.Kind() { + case reflect.Struct: + items := []string{} + + for i := 0; i < objType.NumField(); i++ { + fieldType := objType.Field(i) + valType := objValue.Field(i) + + if isNil(fieldType.Type, valType) { + continue + } + + if fieldType.Type.Kind() == reflect.Pointer { + valType = valType.Elem() + } + + tag := parseParamTag(headerParamTagKey, fieldType, "simple", false) + if tag == nil { + continue + } + + fieldName := tag.ParamName + + if fieldName == "" { + continue + } + + if explode { + items = append(items, fmt.Sprintf("%s=%s", fieldName, valToString(valType.Interface()))) + } else { + items = append(items, fieldName, valToString(valType.Interface())) + } + } + + return strings.Join(items, ",") + case reflect.Map: + items := []string{} + + iter := objValue.MapRange() + for iter.Next() { + if explode { + items = append(items, fmt.Sprintf("%s=%s", iter.Key().String(), valToString(iter.Value().Interface()))) + } else { + items = append(items, iter.Key().String(), valToString(iter.Value().Interface())) + } + } + + return strings.Join(items, ",") + case reflect.Slice, reflect.Array: + items := []string{} + + for i := 0; i < objValue.Len(); i++ { + items = append(items, valToString(objValue.Index(i).Interface())) + } + + return strings.Join(items, ",") + default: + return valToString(objValue.Interface()) + } +} diff --git a/internal/sdk/pkg/utils/json.go b/internal/sdk/pkg/utils/json.go new file mode 100644 index 0000000..0ea96a5 --- /dev/null +++ b/internal/sdk/pkg/utils/json.go @@ -0,0 +1,596 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package utils + +import ( + "bytes" + "encoding/json" + "fmt" + "math/big" + "reflect" + "strings" + "time" + "unsafe" + + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/types" + + "github.com/ericlagergren/decimal" +) + +func MarshalJSON(v interface{}, tag reflect.StructTag, topLevel bool) ([]byte, error) { + typ, val := dereferencePointers(reflect.TypeOf(v), reflect.ValueOf(v)) + + switch { + case isModelType(typ): + if topLevel { + return json.Marshal(v) + } + + if isNil(typ, val) { + return []byte("null"), nil + } + + out := map[string]json.RawMessage{} + + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + fieldVal := val.Field(i) + + fieldName := field.Name + + omitEmpty := false + jsonTag := field.Tag.Get("json") + if jsonTag != "" { + for _, tag := range strings.Split(jsonTag, ",") { + if tag == "omitempty" { + omitEmpty = true + } else { + fieldName = tag + } + } + } + + if isNil(field.Type, fieldVal) && field.Tag.Get("const") == "" { + if omitEmpty { + continue + } + } + + if !field.IsExported() && field.Tag.Get("const") == "" { + continue + } + + additionalProperties := field.Tag.Get("additionalProperties") + if fieldName == "-" && additionalProperties == "" { + continue + } + + if additionalProperties == "true" { + if isNil(field.Type, fieldVal) { + continue + } + fieldVal := trueReflectValue(fieldVal) + if fieldVal.Type().Kind() != reflect.Map { + return nil, fmt.Errorf("additionalProperties must be a map") + } + + for _, key := range fieldVal.MapKeys() { + r, err := marshalValue(fieldVal.MapIndex(key).Interface(), field.Tag) + if err != nil { + return nil, err + } + + out[key.String()] = r + } + + continue + } + + var fv interface{} + + if field.IsExported() { + fv = fieldVal.Interface() + } else { + pt := reflect.New(typ).Elem() + pt.Set(val) + + pf := pt.Field(i) + + fv = reflect.NewAt(pf.Type(), unsafe.Pointer(pf.UnsafeAddr())).Elem().Interface() + } + + r, err := marshalValue(fv, field.Tag) + if err != nil { + return nil, err + } + + out[fieldName] = r + } + + return json.Marshal(out) + default: + return marshalValue(v, tag) + } +} + +func UnmarshalJSON(b []byte, v interface{}, tag reflect.StructTag, topLevel bool, disallowUnknownFields bool) error { + if reflect.TypeOf(v).Kind() != reflect.Ptr { + return fmt.Errorf("v must be a pointer") + } + + typ, val := dereferencePointers(reflect.TypeOf(v), reflect.ValueOf(v)) + + switch { + case isModelType(typ): + if topLevel || bytes.Equal(b, []byte("null")) { + d := json.NewDecoder(bytes.NewReader(b)) + if disallowUnknownFields { + d.DisallowUnknownFields() + } + return d.Decode(v) + } + + var unmarhsaled map[string]json.RawMessage + + if err := json.Unmarshal(b, &unmarhsaled); err != nil { + return err + } + + var additionalPropertiesField *reflect.StructField + var additionalPropertiesValue *reflect.Value + + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + fieldVal := val.Field(i) + + fieldName := field.Name + + jsonTag := field.Tag.Get("json") + if jsonTag != "" { + for _, tag := range strings.Split(jsonTag, ",") { + if tag != "omitempty" { + fieldName = tag + } + } + } + + if field.Tag.Get("additionalProperties") == "true" { + additionalPropertiesField = &field + additionalPropertiesValue = &fieldVal + continue + } + + // If we receive a value for a const field ignore it but mark it as unmarshaled + if field.Tag.Get("const") != "" { + if r, ok := unmarhsaled[fieldName]; ok { + val := string(r) + if strings.HasPrefix(val, `"`) && strings.HasSuffix(val, `"`) { + val = val[1 : len(val)-1] + } + if val != field.Tag.Get("const") { + return fmt.Errorf("const field %s does not match expected value %s", fieldName, field.Tag.Get("const")) + } + + delete(unmarhsaled, fieldName) + } + } else if !field.IsExported() { + continue + } + + value, ok := unmarhsaled[fieldName] + if !ok { + defaultTag := field.Tag.Get("default") + if defaultTag != "" { + value = handleDefaultConstValue(defaultTag, fieldVal.Interface(), field.Tag) + ok = true + } + } else { + delete(unmarhsaled, fieldName) + } + + if ok { + if err := unmarshalValue(value, fieldVal, field.Tag, disallowUnknownFields); err != nil { + return err + } + } + } + + keys := make([]string, 0, len(unmarhsaled)) + for k := range unmarhsaled { + keys = append(keys, k) + } + + if len(keys) > 0 { + if disallowUnknownFields && (additionalPropertiesField == nil || additionalPropertiesValue == nil) { + return fmt.Errorf("unknown fields: %v", keys) + } + + if additionalPropertiesField != nil && additionalPropertiesValue != nil { + typeOfMap := additionalPropertiesField.Type + if additionalPropertiesValue.Type().Kind() == reflect.Interface { + typeOfMap = reflect.TypeOf(map[string]interface{}{}) + } else if additionalPropertiesValue.Type().Kind() != reflect.Map { + return fmt.Errorf("additionalProperties must be a map") + } + + mapValue := reflect.MakeMap(typeOfMap) + + for key, value := range unmarhsaled { + val := reflect.New(typeOfMap.Elem()) + + if err := unmarshalValue(value, val, additionalPropertiesField.Tag, disallowUnknownFields); err != nil { + return err + } + + if val.Elem().Type().String() == typeOfMap.Elem().String() { + mapValue.SetMapIndex(reflect.ValueOf(key), val.Elem()) + } else { + mapValue.SetMapIndex(reflect.ValueOf(key), trueReflectValue(val)) + } + + } + if additionalPropertiesValue.Type().Kind() == reflect.Interface { + additionalPropertiesValue.Set(mapValue) + } else { + additionalPropertiesValue.Set(mapValue) + } + } + } + default: + return unmarshalValue(b, reflect.ValueOf(v), tag, disallowUnknownFields) + } + + return nil +} + +func marshalValue(v interface{}, tag reflect.StructTag) (json.RawMessage, error) { + constTag := tag.Get("const") + if constTag != "" { + return handleDefaultConstValue(constTag, v, tag), nil + } + + if isNil(reflect.TypeOf(v), reflect.ValueOf(v)) { + defaultTag := tag.Get("default") + if defaultTag != "" { + return handleDefaultConstValue(defaultTag, v, tag), nil + } + + return []byte("null"), nil + } + + typ, val := dereferencePointers(reflect.TypeOf(v), reflect.ValueOf(v)) + switch typ.Kind() { + case reflect.Map: + if isNil(typ, val) { + return []byte("null"), nil + } + + out := map[string]json.RawMessage{} + + for _, key := range val.MapKeys() { + itemVal := val.MapIndex(key) + + if isNil(itemVal.Type(), itemVal) { + out[key.String()] = []byte("null") + continue + } + + r, err := marshalValue(itemVal.Interface(), tag) + if err != nil { + return nil, err + } + + out[key.String()] = r + } + + return json.Marshal(out) + case reflect.Slice, reflect.Array: + if isNil(typ, val) { + return []byte("null"), nil + } + + out := []json.RawMessage{} + + for i := 0; i < val.Len(); i++ { + itemVal := val.Index(i) + + if isNil(itemVal.Type(), itemVal) { + out = append(out, []byte("null")) + continue + } + + r, err := marshalValue(itemVal.Interface(), tag) + if err != nil { + return nil, err + } + + out = append(out, r) + } + + return json.Marshal(out) + case reflect.Struct: + switch typ { + case reflect.TypeOf(time.Time{}): + return []byte(fmt.Sprintf(`"%s"`, val.Interface().(time.Time).Format(time.RFC3339Nano))), nil + case reflect.TypeOf(big.Int{}): + format := tag.Get("bigint") + if format == "string" { + b := val.Interface().(big.Int) + return []byte(fmt.Sprintf(`"%s"`, (&b).String())), nil + } + case reflect.TypeOf(decimal.Big{}): + format := tag.Get("decimal") + if format == "number" { + b := val.Interface().(decimal.Big) + f, ok := (&b).Float64() + if ok { + return []byte(b.String()), nil + } + + return []byte(fmt.Sprintf(`%f`, f)), nil + } + } + } + + return json.Marshal(v) +} + +func handleDefaultConstValue(tagValue string, val interface{}, tag reflect.StructTag) json.RawMessage { + if tagValue == "null" { + return []byte("null") + } + + typ := dereferenceTypePointer(reflect.TypeOf(val)) + switch typ { + case reflect.TypeOf(time.Time{}): + return []byte(fmt.Sprintf(`"%s"`, tagValue)) + case reflect.TypeOf(big.Int{}): + bigIntTag := tag.Get("bigint") + if bigIntTag == "string" { + return []byte(fmt.Sprintf(`"%s"`, tagValue)) + } + case reflect.TypeOf(decimal.Big{}): + decimalTag := tag.Get("decimal") + if decimalTag != "number" { + return []byte(fmt.Sprintf(`"%s"`, tagValue)) + } + case reflect.TypeOf(types.Date{}): + return []byte(fmt.Sprintf(`"%s"`, tagValue)) + default: + if typ.Kind() == reflect.String { + return []byte(fmt.Sprintf(`"%s"`, tagValue)) + } + } + + return []byte(tagValue) +} + +func unmarshalValue(value json.RawMessage, v reflect.Value, tag reflect.StructTag, disallowUnknownFields bool) error { + if bytes.Equal(value, []byte("null")) { + if v.CanAddr() { + return json.Unmarshal(value, v.Addr().Interface()) + } else { + return json.Unmarshal(value, v.Interface()) + } + } + + typ := dereferenceTypePointer(v.Type()) + + switch typ.Kind() { + case reflect.Map: + if bytes.Equal(value, []byte("null")) || !isComplexValueType(dereferenceTypePointer(typ.Elem())) { + if v.CanAddr() { + return json.Unmarshal(value, v.Addr().Interface()) + } else { + return json.Unmarshal(value, v.Interface()) + } + } + + var unmarhsaled map[string]json.RawMessage + + if err := json.Unmarshal(value, &unmarhsaled); err != nil { + return err + } + + m := reflect.MakeMap(typ) + + for k, value := range unmarhsaled { + itemVal := reflect.New(typ.Elem()) + + if err := unmarshalValue(value, itemVal, tag, disallowUnknownFields); err != nil { + return err + } + + m.SetMapIndex(reflect.ValueOf(k), itemVal.Elem()) + } + + v.Set(m) + return nil + case reflect.Slice, reflect.Array: + if bytes.Equal(value, []byte("null")) || !isComplexValueType(dereferenceTypePointer(typ.Elem())) { + if v.CanAddr() { + return json.Unmarshal(value, v.Addr().Interface()) + } else { + return json.Unmarshal(value, v.Interface()) + } + } + + var unmarhsaled []json.RawMessage + + if err := json.Unmarshal(value, &unmarhsaled); err != nil { + return err + } + + arrVal := v + + for _, value := range unmarhsaled { + itemVal := reflect.New(typ.Elem()) + + if err := unmarshalValue(value, itemVal, tag, disallowUnknownFields); err != nil { + return err + } + + arrVal = reflect.Append(arrVal, itemVal.Elem()) + } + + v.Set(arrVal) + return nil + case reflect.Struct: + switch typ { + case reflect.TypeOf(time.Time{}): + var s string + if err := json.Unmarshal(value, &s); err != nil { + return err + } + + t, err := time.Parse(time.RFC3339Nano, s) + if err != nil { + return fmt.Errorf("failed to parse string as time.Time: %w", err) + } + + if v.Kind() == reflect.Ptr { + if v.IsNil() { + v.Set(reflect.New(typ)) + } + v = v.Elem() + } + + v.Set(reflect.ValueOf(t)) + return nil + case reflect.TypeOf(big.Int{}): + var b *big.Int + + format := tag.Get("bigint") + if format == "string" { + var s string + if err := json.Unmarshal(value, &s); err != nil { + return err + } + + var ok bool + b, ok = new(big.Int).SetString(s, 10) + if !ok { + return fmt.Errorf("failed to parse string as big.Int") + } + } else { + if err := json.Unmarshal(value, &b); err != nil { + return err + } + } + + if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Ptr { + v = v.Elem() + } + + v.Set(reflect.ValueOf(b)) + return nil + case reflect.TypeOf(decimal.Big{}): + var d *decimal.Big + format := tag.Get("decimal") + if format == "number" { + var ok bool + d, ok = new(decimal.Big).SetString(string(value)) + if !ok { + return fmt.Errorf("failed to parse number as decimal.Big") + } + } else { + if err := json.Unmarshal(value, &d); err != nil { + return err + } + } + + if v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Ptr { + v = v.Elem() + } + + v.Set(reflect.ValueOf(d)) + return nil + case reflect.TypeOf(types.Date{}): + var s string + + if err := json.Unmarshal(value, &s); err != nil { + return err + } + + d, err := types.DateFromString(s) + if err != nil { + return fmt.Errorf("failed to parse string as types.Date: %w", err) + } + + if v.Kind() == reflect.Ptr { + if v.IsNil() { + v.Set(reflect.New(typ)) + } + v = v.Elem() + } + + v.Set(reflect.ValueOf(d)) + return nil + } + } + + var val interface{} + + if v.CanAddr() { + val = v.Addr().Interface() + } else { + val = v.Interface() + } + + d := json.NewDecoder(bytes.NewReader(value)) + if disallowUnknownFields { + d.DisallowUnknownFields() + } + return d.Decode(val) +} + +func dereferencePointers(typ reflect.Type, val reflect.Value) (reflect.Type, reflect.Value) { + if typ.Kind() == reflect.Ptr { + typ = typ.Elem() + val = val.Elem() + } else { + return typ, val + } + + return dereferencePointers(typ, val) +} + +func dereferenceTypePointer(typ reflect.Type) reflect.Type { + if typ.Kind() == reflect.Ptr { + typ = typ.Elem() + } else { + return typ + } + + return dereferenceTypePointer(typ) +} + +func isComplexValueType(typ reflect.Type) bool { + switch typ.Kind() { + case reflect.Struct: + switch typ { + case reflect.TypeOf(time.Time{}): + fallthrough + case reflect.TypeOf(big.Int{}): + fallthrough + case reflect.TypeOf(decimal.Big{}): + fallthrough + case reflect.TypeOf(types.Date{}): + return true + } + } + + return false +} + +func isModelType(typ reflect.Type) bool { + if isComplexValueType(typ) { + return false + } + + if typ.Kind() == reflect.Struct { + return true + } + + return false +} diff --git a/internal/sdk/pkg/utils/pathparams.go b/internal/sdk/pkg/utils/pathparams.go new file mode 100644 index 0000000..1ec9b9d --- /dev/null +++ b/internal/sdk/pkg/utils/pathparams.go @@ -0,0 +1,145 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package utils + +import ( + "context" + "fmt" + "math/big" + "net/url" + "reflect" + "strings" + "time" + + "github.com/ericlagergren/decimal" + + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/types" +) + +func GenerateURL(ctx context.Context, serverURL, path string, pathParams interface{}, globals map[string]map[string]map[string]interface{}) (string, error) { + uri := strings.TrimSuffix(serverURL, "/") + path + + pathParamsStructType := reflect.TypeOf(pathParams) + pathParamsValType := reflect.ValueOf(pathParams) + + parsedParameters := map[string]string{} + + for i := 0; i < pathParamsStructType.NumField(); i++ { + fieldType := pathParamsStructType.Field(i) + valType := pathParamsValType.Field(i) + + requestTag := getRequestTag(fieldType) + if requestTag != nil { + continue + } + + ppTag := parseParamTag(pathParamTagKey, fieldType, "simple", false) + if ppTag == nil { + continue + } + + valType = populateFromGlobals(fieldType, valType, "pathParam", globals) + + if ppTag.Serialization != "" { + vals, err := populateSerializedParams(ppTag, fieldType.Type, valType) + if err != nil { + return "", err + } + for k, v := range vals { + parsedParameters[k] = url.PathEscape(v) + } + } else { + // TODO: support other styles + switch ppTag.Style { + case "simple": + simpleParams := getSimplePathParams(ctx, ppTag.ParamName, fieldType.Type, valType, ppTag.Explode) + for k, v := range simpleParams { + parsedParameters[k] = v + } + } + } + } + + // TODO should we handle the case where there are no matching path params? + return ReplaceParameters(uri, parsedParameters), nil +} + +func getSimplePathParams(ctx context.Context, parentName string, objType reflect.Type, objValue reflect.Value, explode bool) map[string]string { + pathParams := make(map[string]string) + + if isNil(objType, objValue) { + return nil + } + + if objType.Kind() == reflect.Ptr { + objType = objType.Elem() + objValue = objValue.Elem() + } + + switch objType.Kind() { + case reflect.Array, reflect.Slice: + if objValue.Len() == 0 { + return nil + } + var ppVals []string + for i := 0; i < objValue.Len(); i++ { + ppVals = append(ppVals, valToString(objValue.Index(i).Interface())) + } + pathParams[parentName] = strings.Join(ppVals, ",") + case reflect.Map: + if objValue.Len() == 0 { + return nil + } + var ppVals []string + objMap := objValue.MapRange() + for objMap.Next() { + if explode { + ppVals = append(ppVals, fmt.Sprintf("%s=%s", objMap.Key().String(), valToString(objMap.Value().Interface()))) + } else { + ppVals = append(ppVals, fmt.Sprintf("%s,%s", objMap.Key().String(), valToString(objMap.Value().Interface()))) + } + } + pathParams[parentName] = strings.Join(ppVals, ",") + case reflect.Struct: + switch objValue.Interface().(type) { + case time.Time: + pathParams[parentName] = valToString(objValue.Interface()) + case types.Date: + pathParams[parentName] = valToString(objValue.Interface()) + case big.Int: + pathParams[parentName] = valToString(objValue.Interface()) + case decimal.Big: + pathParams[parentName] = valToString(objValue.Interface()) + default: + var ppVals []string + for i := 0; i < objType.NumField(); i++ { + fieldType := objType.Field(i) + valType := objValue.Field(i) + + ppTag := parseParamTag(pathParamTagKey, fieldType, "simple", explode) + if ppTag == nil { + continue + } + + if isNil(fieldType.Type, valType) { + continue + } + + if fieldType.Type.Kind() == reflect.Pointer { + valType = valType.Elem() + } + + if explode { + ppVals = append(ppVals, fmt.Sprintf("%s=%s", ppTag.ParamName, valToString(valType.Interface()))) + } else { + ppVals = append(ppVals, fmt.Sprintf("%s,%s", ppTag.ParamName, valToString(valType.Interface()))) + } + } + pathParams[parentName] = strings.Join(ppVals, ",") + } + default: + pathParams[parentName] = valToString(objValue.Interface()) + } + + return pathParams +} diff --git a/internal/sdk/pkg/utils/queryparams.go b/internal/sdk/pkg/utils/queryparams.go new file mode 100644 index 0000000..0e94bce --- /dev/null +++ b/internal/sdk/pkg/utils/queryparams.go @@ -0,0 +1,178 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package utils + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "net/url" + "reflect" +) + +func PopulateQueryParams(ctx context.Context, req *http.Request, queryParams interface{}, globals map[string]map[string]map[string]interface{}) error { + queryParamsStructType := reflect.TypeOf(queryParams) + queryParamsValType := reflect.ValueOf(queryParams) + + values := url.Values{} + + for i := 0; i < queryParamsStructType.NumField(); i++ { + fieldType := queryParamsStructType.Field(i) + valType := queryParamsValType.Field(i) + + requestTag := getRequestTag(fieldType) + if requestTag != nil { + continue + } + + qpTag := parseQueryParamTag(fieldType) + if qpTag == nil { + continue + } + + valType = populateFromGlobals(fieldType, valType, "queryParam", globals) + + if qpTag.Serialization != "" { + vals, err := populateSerializedParams(qpTag, fieldType.Type, valType) + if err != nil { + return err + } + for k, v := range vals { + values.Add(k, v) + } + } else { + switch qpTag.Style { + case "deepObject": + vals := populateDeepObjectParams(req, qpTag, fieldType.Type, valType) + for k, v := range vals { + for _, vv := range v { + values.Add(k, vv) + } + } + case "form": + vals := populateFormParams(req, qpTag, fieldType.Type, valType, ",") + for k, v := range vals { + for _, vv := range v { + values.Add(k, vv) + } + } + case "pipeDelimited": + vals := populateFormParams(req, qpTag, fieldType.Type, valType, "|") + for k, v := range vals { + for _, vv := range v { + values.Add(k, vv) + } + } + default: + return fmt.Errorf("unsupported style: %s", qpTag.Style) + } + } + } + + req.URL.RawQuery = values.Encode() + + return nil +} + +func populateSerializedParams(tag *paramTag, objType reflect.Type, objValue reflect.Value) (map[string]string, error) { + if isNil(objType, objValue) { + return nil, nil + } + + if objType.Kind() == reflect.Pointer { + objValue = objValue.Elem() + } + + values := map[string]string{} + + switch tag.Serialization { + case "json": + data, err := json.Marshal(objValue.Interface()) + if err != nil { + return nil, fmt.Errorf("error marshaling json: %v", err) + } + values[tag.ParamName] = string(data) + } + + return values, nil +} + +func populateDeepObjectParams(req *http.Request, tag *paramTag, objType reflect.Type, objValue reflect.Value) url.Values { + values := url.Values{} + + if isNil(objType, objValue) { + return values + } + + if objType.Kind() == reflect.Pointer { + objType = objType.Elem() + objValue = objValue.Elem() + } + + switch objType.Kind() { + case reflect.Struct: + for i := 0; i < objType.NumField(); i++ { + fieldType := objType.Field(i) + valType := objValue.Field(i) + + if isNil(fieldType.Type, valType) { + continue + } + + if fieldType.Type.Kind() == reflect.Pointer { + valType = valType.Elem() + } + + qpTag := parseQueryParamTag(fieldType) + if qpTag == nil { + continue + } + + switch valType.Kind() { + case reflect.Array, reflect.Slice: + for i := 0; i < valType.Len(); i++ { + values.Add(fmt.Sprintf("%s[%s]", tag.ParamName, qpTag.ParamName), valToString(valType.Index(i).Interface())) + } + default: + values.Add(fmt.Sprintf("%s[%s]", tag.ParamName, qpTag.ParamName), valToString(valType.Interface())) + } + } + case reflect.Map: + iter := objValue.MapRange() + for iter.Next() { + switch iter.Value().Kind() { + case reflect.Array, reflect.Slice: + for i := 0; i < iter.Value().Len(); i++ { + values.Add(fmt.Sprintf("%s[%s]", tag.ParamName, iter.Key().String()), valToString(iter.Value().Index(i).Interface())) + } + default: + values.Add(fmt.Sprintf("%s[%s]", tag.ParamName, iter.Key().String()), valToString(iter.Value().Interface())) + } + } + } + + return values +} + +func populateFormParams(req *http.Request, tag *paramTag, objType reflect.Type, objValue reflect.Value, delimiter string) url.Values { + return populateForm(tag.ParamName, tag.Explode, objType, objValue, delimiter, func(fieldType reflect.StructField) string { + qpTag := parseQueryParamTag(fieldType) + if qpTag == nil { + return "" + } + + return qpTag.ParamName + }) +} + +type paramTag struct { + Style string + Explode bool + ParamName string + Serialization string +} + +func parseQueryParamTag(field reflect.StructField) *paramTag { + return parseParamTag(queryParamTagKey, field, "form", true) +} diff --git a/internal/sdk/pkg/utils/requestbody.go b/internal/sdk/pkg/utils/requestbody.go new file mode 100644 index 0000000..950f39a --- /dev/null +++ b/internal/sdk/pkg/utils/requestbody.go @@ -0,0 +1,396 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package utils + +import ( + "bytes" + "context" + "fmt" + "io" + "mime/multipart" + "net/url" + "reflect" + "regexp" +) + +const ( + requestTagKey = "request" + multipartFormTagKey = "multipartForm" + formTagKey = "form" +) + +var ( + jsonEncodingRegex = regexp.MustCompile(`(application|text)\/.*?\+*json.*`) + multipartEncodingRegex = regexp.MustCompile(`multipart\/.*`) + urlEncodedEncodingRegex = regexp.MustCompile(`application\/x-www-form-urlencoded.*`) +) + +func SerializeRequestBody(ctx context.Context, request interface{}, nullable, optional bool, requestFieldName, serializationMethod, tag string) (io.Reader, string, error) { + requestStructType := reflect.TypeOf(request) + requestValType := reflect.ValueOf(request) + + if isNil(requestStructType, requestValType) { + if !nullable && optional { + return nil, "", nil + } + + return serializeContentType(requestFieldName, SerializationMethodToContentType[serializationMethod], requestValType, tag) + } + + if requestStructType.Kind() == reflect.Pointer { + requestStructType = requestStructType.Elem() + requestValType = requestValType.Elem() + } + + if requestStructType.Kind() != reflect.Struct { + return serializeContentType(requestFieldName, SerializationMethodToContentType[serializationMethod], requestValType, tag) + } + + requestField, ok := requestStructType.FieldByName(requestFieldName) + + if ok { + tag := getRequestTag(requestField) + if tag != nil { + // request object (non-flattened) + requestVal := requestValType.FieldByName(requestFieldName) + if isNil(requestField.Type, requestVal) { + if !nullable && optional { + return nil, "", nil + } + + return serializeContentType(requestFieldName, tag.MediaType, requestVal, string(requestField.Tag)) + } + + return serializeContentType(requestFieldName, tag.MediaType, requestVal, string(requestField.Tag)) + } + } + + // flattened request object + return serializeContentType(requestFieldName, SerializationMethodToContentType[serializationMethod], reflect.ValueOf(request), tag) +} + +func serializeContentType(fieldName string, mediaType string, val reflect.Value, tag string) (*bytes.Buffer, string, error) { + buf := &bytes.Buffer{} + + if isNil(val.Type(), val) { + // TODO: what does a null mean for other content types? Just returning an empty buffer for now + if jsonEncodingRegex.MatchString(mediaType) { + if _, err := buf.Write([]byte("null")); err != nil { + return nil, "", err + } + } + + return buf, mediaType, nil + } + + switch { + case jsonEncodingRegex.MatchString(mediaType): + data, err := MarshalJSON(val.Interface(), reflect.StructTag(tag), true) + if err != nil { + return nil, "", err + } + + if _, err := buf.Write(data); err != nil { + return nil, "", err + } + case multipartEncodingRegex.MatchString(mediaType): + var err error + mediaType, err = encodeMultipartFormData(buf, val.Interface()) + if err != nil { + return nil, "", err + } + case urlEncodedEncodingRegex.MatchString(mediaType): + if err := encodeFormData(fieldName, buf, val.Interface()); err != nil { + return nil, "", err + } + default: + val = reflect.Indirect(val) + + switch { + case val.Type().Kind() == reflect.String: + if _, err := buf.WriteString(valToString(val.Interface())); err != nil { + return nil, "", err + } + case val.Type() == reflect.TypeOf([]byte(nil)): + if _, err := buf.Write(val.Bytes()); err != nil { + return nil, "", err + } + default: + return nil, "", fmt.Errorf("invalid request body type %s for mediaType %s", val.Type(), mediaType) + } + } + + return buf, mediaType, nil +} + +func encodeMultipartFormData(w io.Writer, data interface{}) (string, error) { + requestStructType := reflect.TypeOf(data) + requestValType := reflect.ValueOf(data) + + if requestStructType.Kind() == reflect.Pointer { + requestStructType = requestStructType.Elem() + requestValType = requestValType.Elem() + } + + writer := multipart.NewWriter(w) + + for i := 0; i < requestStructType.NumField(); i++ { + field := requestStructType.Field(i) + fieldType := field.Type + valType := requestValType.Field(i) + + if isNil(fieldType, valType) { + continue + } + + if fieldType.Kind() == reflect.Pointer { + fieldType = fieldType.Elem() + valType = valType.Elem() + } + + tag := parseMultipartFormTag(field) + if tag.File { + if err := encodeMultipartFormDataFile(writer, fieldType, valType); err != nil { + writer.Close() + return "", err + } + } else if tag.JSON { + jw, err := writer.CreateFormField(tag.Name) + if err != nil { + writer.Close() + return "", err + } + d, err := MarshalJSON(valType.Interface(), field.Tag, true) + if err != nil { + writer.Close() + return "", err + } + if _, err := jw.Write(d); err != nil { + writer.Close() + return "", err + } + } else { + switch fieldType.Kind() { + case reflect.Slice, reflect.Array: + values := parseDelimitedArray(true, valType, ",") + for _, v := range values { + if err := writer.WriteField(tag.Name+"[]", v); err != nil { + writer.Close() + return "", err + } + } + default: + if err := writer.WriteField(tag.Name, valToString(valType.Interface())); err != nil { + writer.Close() + return "", err + } + } + } + } + + if err := writer.Close(); err != nil { + return "", err + } + + return writer.FormDataContentType(), nil +} + +func encodeMultipartFormDataFile(w *multipart.Writer, fieldType reflect.Type, valType reflect.Value) error { + if fieldType.Kind() != reflect.Struct { + return fmt.Errorf("invalid type %s for multipart/form-data file", valType.Type()) + } + + var fieldName string + var fileName string + var content []byte + + for i := 0; i < fieldType.NumField(); i++ { + field := fieldType.Field(i) + val := valType.Field(i) + + tag := parseMultipartFormTag(field) + if !tag.Content && tag.Name == "" { + continue + } + + if tag.Content { + content = val.Bytes() + } else { + fieldName = tag.Name + fileName = val.String() + } + } + + if fieldName == "" || fileName == "" || content == nil { + return fmt.Errorf("invalid multipart/form-data file") + } + + fw, err := w.CreateFormFile(fieldName, fileName) + if err != nil { + return err + } + if _, err := fw.Write(content); err != nil { + return err + } + + return nil +} + +func encodeFormData(fieldName string, w io.Writer, data interface{}) error { + requestType := reflect.TypeOf(data) + requestValType := reflect.ValueOf(data) + + if requestType.Kind() == reflect.Pointer { + requestType = requestType.Elem() + requestValType = requestValType.Elem() + } + + dataValues := url.Values{} + + switch requestType.Kind() { + case reflect.Struct: + for i := 0; i < requestType.NumField(); i++ { + field := requestType.Field(i) + fieldType := field.Type + valType := requestValType.Field(i) + + if isNil(fieldType, valType) { + continue + } + + if fieldType.Kind() == reflect.Pointer { + fieldType = fieldType.Elem() + valType = valType.Elem() + } + + tag := parseFormTag(field) + if tag.JSON { + data, err := MarshalJSON(valType.Interface(), field.Tag, true) + if err != nil { + return err + } + dataValues.Set(tag.Name, string(data)) + } else { + switch tag.Style { + // TODO: support other styles + case "form": + values := populateForm(tag.Name, tag.Explode, fieldType, valType, ",", func(sf reflect.StructField) string { + tag := parseFormTag(field) + if tag == nil { + return "" + } + + return tag.Name + }) + for k, v := range values { + for _, vv := range v { + dataValues.Add(k, vv) + } + } + } + } + } + case reflect.Map: + for _, k := range requestValType.MapKeys() { + v := requestValType.MapIndex(k) + dataValues.Set(fmt.Sprintf("%v", k.Interface()), valToString(v.Interface())) + } + case reflect.Slice, reflect.Array: + for i := 0; i < requestValType.Len(); i++ { + v := requestValType.Index(i) + dataValues.Set(fieldName, valToString(v.Interface())) + } + } + + if _, err := w.Write([]byte(dataValues.Encode())); err != nil { + return err + } + + return nil +} + +type requestTag struct { + MediaType string +} + +func getRequestTag(field reflect.StructField) *requestTag { + // example `request:"mediaType=multipart/form-data"` + values := parseStructTag(requestTagKey, field) + if values == nil { + return nil + } + + tag := &requestTag{ + MediaType: "application/octet-stream", + } + + for k, v := range values { + switch k { + case "mediaType": + tag.MediaType = v + } + } + + return tag +} + +type multipartFormTag struct { + File bool + Content bool + JSON bool + Name string +} + +func parseMultipartFormTag(field reflect.StructField) *multipartFormTag { + // example `multipartForm:"name=file"` + values := parseStructTag(multipartFormTagKey, field) + + tag := &multipartFormTag{} + + for k, v := range values { + switch k { + case "file": + tag.File = v == "true" + case "content": + tag.Content = v == "true" + case "name": + tag.Name = v + case "json": + tag.JSON = v == "true" + } + } + + return tag +} + +type formTag struct { + Name string + JSON bool + Style string + Explode bool +} + +func parseFormTag(field reflect.StructField) *formTag { + // example `form:"name=propName,style=spaceDelimited,explode"` + values := parseStructTag(formTagKey, field) + + tag := &formTag{ + Style: "form", + Explode: true, + } + + for k, v := range values { + switch k { + case "name": + tag.Name = v + case "json": + tag.JSON = v == "true" + case "style": + tag.Style = v + case "explode": + tag.Explode = v == "true" + } + } + + return tag +} diff --git a/internal/sdk/pkg/utils/retries.go b/internal/sdk/pkg/utils/retries.go new file mode 100644 index 0000000..ff39d0d --- /dev/null +++ b/internal/sdk/pkg/utils/retries.go @@ -0,0 +1,117 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package utils + +import ( + "context" + "errors" + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + "time" + + "github.com/cenkalti/backoff/v4" +) + +var errRequestFailed = errors.New("request failed") + +type BackoffStrategy struct { + InitialInterval int + MaxInterval int + Exponent float64 + MaxElapsedTime int +} + +type RetryConfig struct { + Strategy string + Backoff *BackoffStrategy + RetryConnectionErrors bool +} + +type Retries struct { + Config *RetryConfig + StatusCodes []string +} + +func Retry(ctx context.Context, r Retries, action func() (*http.Response, error)) (*http.Response, error) { + switch r.Config.Strategy { + case "backoff": + if r.Config.Backoff == nil { + return action() + } + + config := backoff.NewExponentialBackOff() + config.InitialInterval = time.Duration(r.Config.Backoff.InitialInterval) * time.Millisecond + config.MaxInterval = time.Duration(r.Config.Backoff.MaxInterval) * time.Millisecond + config.Multiplier = r.Config.Backoff.Exponent + config.MaxElapsedTime = time.Duration(r.Config.Backoff.MaxElapsedTime) * time.Millisecond + config.Reset() + + var resp *http.Response + + err := backoff.Retry(func() error { + if resp != nil { + resp.Body.Close() + } + + select { + case <-ctx.Done(): + return backoff.Permanent(ctx.Err()) + default: + } + + res, err := action() + if err != nil { + urlError := new(url.Error) + if errors.As(err, &urlError) { + if (urlError.Temporary() || urlError.Timeout()) && r.Config.RetryConnectionErrors { + return err + } + } + + return backoff.Permanent(err) + } + resp = res + if res == nil { + return fmt.Errorf("no response") + } + + for _, code := range r.StatusCodes { + if strings.Contains(strings.ToUpper(code), "X") { + codeRange, err := strconv.Atoi(code[:1]) + if err != nil { + continue + } + + s := res.StatusCode / 100 + + if s >= codeRange && s < codeRange+1 { + return errRequestFailed + } + } else { + parsedCode, err := strconv.Atoi(code) + if err != nil { + continue + } + + if res.StatusCode == parsedCode { + return errRequestFailed + } + } + } + + resp = res + + return nil + }, config) + if err != nil && !errors.Is(err, errRequestFailed) { + return nil, err + } + + return resp, nil + default: + return action() + } +} diff --git a/internal/sdk/pkg/utils/security.go b/internal/sdk/pkg/utils/security.go new file mode 100644 index 0000000..fa5eff7 --- /dev/null +++ b/internal/sdk/pkg/utils/security.go @@ -0,0 +1,298 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package utils + +import ( + "context" + "encoding/base64" + "fmt" + "net/http" + "reflect" + "strings" +) + +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} + +const ( + securityTagKey = "security" +) + +type securityTag struct { + Option bool + Scheme bool + Name string + Type string + SubType string +} + +type securityConfig struct { + headers map[string]string + queryParams map[string]string +} + +type SecurityClient struct { + HTTPClient + security func(ctx context.Context) (interface{}, error) +} + +func newSecurityClient(client HTTPClient, security func(ctx context.Context) (interface{}, error)) *SecurityClient { + return &SecurityClient{ + HTTPClient: client, + security: security, + } +} + +func (c *SecurityClient) Do(req *http.Request) (*http.Response, error) { + securityCtx, err := c.security(req.Context()) + if err != nil { + return nil, err + } + + ctx := securityConfig{ + headers: make(map[string]string), + queryParams: make(map[string]string), + } + parseSecurityStruct(&ctx, securityCtx) + + for k, v := range ctx.headers { + req.Header.Set(k, v) + } + + queryParams := req.URL.Query() + + for k, v := range ctx.queryParams { + queryParams.Add(k, v) + } + + req.URL.RawQuery = queryParams.Encode() + + return c.HTTPClient.Do(req) +} + +func ConfigureSecurityClient(c HTTPClient, security func(ctx context.Context) (interface{}, error)) *SecurityClient { + return newSecurityClient(c, security) +} + +func trueReflectValue(val reflect.Value) reflect.Value { + kind := val.Type().Kind() + for kind == reflect.Interface || kind == reflect.Ptr { + innerVal := val.Elem() + if !innerVal.IsValid() { + break + } + val = innerVal + kind = val.Type().Kind() + } + return val +} + +func parseSecurityStruct(c *securityConfig, security interface{}) { + securityValType := trueReflectValue(reflect.ValueOf(security)) + securityStructType := securityValType.Type() + + if isNil(securityStructType, securityValType) { + return + } + + if securityStructType.Kind() == reflect.Ptr { + securityStructType = securityStructType.Elem() + securityValType = securityValType.Elem() + } + + for i := 0; i < securityStructType.NumField(); i++ { + fieldType := securityStructType.Field(i) + valType := securityValType.Field(i) + + kind := valType.Kind() + + if isNil(fieldType.Type, valType) { + continue + } + + if fieldType.Type.Kind() == reflect.Pointer { + kind = valType.Elem().Kind() + } + + secTag := parseSecurityTag(fieldType) + if secTag != nil { + if secTag.Option { + handleSecurityOption(c, valType.Interface()) + } else if secTag.Scheme { + // Special case for basic auth which could be a flattened struct + if secTag.SubType == "basic" && kind != reflect.Struct { + parseSecurityScheme(c, secTag, security) + } else { + parseSecurityScheme(c, secTag, valType.Interface()) + } + } + } + } +} + +func handleSecurityOption(c *securityConfig, option interface{}) error { + optionValType := trueReflectValue(reflect.ValueOf(option)) + optionStructType := optionValType.Type() + + if isNil(optionStructType, optionValType) { + return nil + } + + for i := 0; i < optionStructType.NumField(); i++ { + fieldType := optionStructType.Field(i) + valType := optionValType.Field(i) + + secTag := parseSecurityTag(fieldType) + if secTag != nil && secTag.Scheme { + parseSecurityScheme(c, secTag, valType.Interface()) + } + } + + return nil +} + +func parseSecurityScheme(client *securityConfig, schemeTag *securityTag, scheme interface{}) { + schemeVal := trueReflectValue(reflect.ValueOf(scheme)) + schemeType := schemeVal.Type() + + if isNil(schemeType, schemeVal) { + return + } + + if schemeType.Kind() == reflect.Struct { + if schemeTag.Type == "http" && schemeTag.SubType == "basic" { + handleBasicAuthScheme(client, schemeVal.Interface()) + return + } + + for i := 0; i < schemeType.NumField(); i++ { + fieldType := schemeType.Field(i) + valType := schemeVal.Field(i) + + if isNil(fieldType.Type, valType) { + continue + } + + if fieldType.Type.Kind() == reflect.Ptr { + valType = valType.Elem() + } + + secTag := parseSecurityTag(fieldType) + if secTag == nil || secTag.Name == "" { + return + } + + parseSecuritySchemeValue(client, schemeTag, secTag, valType.Interface()) + } + } else { + parseSecuritySchemeValue(client, schemeTag, schemeTag, schemeVal.Interface()) + } +} + +func parseSecuritySchemeValue(client *securityConfig, schemeTag *securityTag, secTag *securityTag, val interface{}) { + switch schemeTag.Type { + case "apiKey": + switch schemeTag.SubType { + case "header": + client.headers[secTag.Name] = valToString(val) + case "query": + client.queryParams[secTag.Name] = valToString(val) + case "cookie": + client.headers["Cookie"] = fmt.Sprintf("%s=%s", secTag.Name, valToString(val)) + default: + panic("not supported") + } + case "openIdConnect": + client.headers[secTag.Name] = valToString(val) + case "oauth2": + client.headers[secTag.Name] = valToString(val) + case "http": + switch schemeTag.SubType { + case "bearer": + client.headers[secTag.Name] = prefixBearer(valToString(val)) + default: + panic("not supported") + } + default: + panic("not supported") + } +} + +func prefixBearer(authHeaderValue string) string { + if strings.HasPrefix(strings.ToLower(authHeaderValue), "bearer ") { + return authHeaderValue + } + + return fmt.Sprintf("Bearer %s", authHeaderValue) +} + +func handleBasicAuthScheme(client *securityConfig, scheme interface{}) { + schemeStructType := reflect.TypeOf(scheme) + schemeValType := reflect.ValueOf(scheme) + + var username, password string + + for i := 0; i < schemeStructType.NumField(); i++ { + fieldType := schemeStructType.Field(i) + valType := schemeValType.Field(i) + + secTag := parseSecurityTag(fieldType) + if secTag == nil || secTag.Name == "" { + continue + } + + switch secTag.Name { + case "username": + username = valType.String() + case "password": + password = valType.String() + } + } + + client.headers["Authorization"] = fmt.Sprintf("Basic %s", base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("%s:%s", username, password)))) +} + +func parseSecurityTag(field reflect.StructField) *securityTag { + tag := field.Tag.Get(securityTagKey) + if tag == "" { + return nil + } + + option := false + scheme := false + name := "" + securityType := "" + securitySubType := "" + + options := strings.Split(tag, ",") + for _, optionConf := range options { + parts := strings.Split(optionConf, "=") + if len(parts) < 1 || len(parts) > 2 { + continue + } + + switch parts[0] { + case "name": + name = parts[1] + case "type": + securityType = parts[1] + case "subtype": + securitySubType = parts[1] + case "option": + option = true + case "scheme": + scheme = true + } + } + + // TODO: validate tag? + + return &securityTag{ + Option: option, + Scheme: scheme, + Name: name, + Type: securityType, + SubType: securitySubType, + } +} diff --git a/internal/sdk/pkg/utils/utils.go b/internal/sdk/pkg/utils/utils.go new file mode 100644 index 0000000..8c8161a --- /dev/null +++ b/internal/sdk/pkg/utils/utils.go @@ -0,0 +1,162 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package utils + +import ( + "fmt" + "io" + "math/big" + "reflect" + "regexp" + "strings" + "time" + + "github.com/ericlagergren/decimal" +) + +const ( + queryParamTagKey = "queryParam" + headerParamTagKey = "header" + pathParamTagKey = "pathParam" +) + +var ( + paramRegex = regexp.MustCompile(`({.*?})`) + SerializationMethodToContentType = map[string]string{ + "json": "application/json", + "form": "application/x-www-form-urlencoded", + "multipart": "multipart/form-data", + "raw": "application/octet-stream", + "string": "text/plain", + } +) + +func UnmarshalJsonFromResponseBody(body io.Reader, out interface{}, tag string) error { + data, err := io.ReadAll(body) + if err != nil { + return fmt.Errorf("error reading response body: %w", err) + } + if err := UnmarshalJSON(data, out, reflect.StructTag(tag), true, false); err != nil { + return fmt.Errorf("error unmarshalling json response body: %w", err) + } + + return nil +} + +func ReplaceParameters(stringWithParams string, params map[string]string) string { + if len(params) == 0 { + return stringWithParams + } + + return paramRegex.ReplaceAllStringFunc(stringWithParams, func(match string) string { + match = match[1 : len(match)-1] + return params[match] + }) +} + +func Contains(slice []string, item string) bool { + for _, s := range slice { + if s == item { + return true + } + } + return false +} + +func parseStructTag(tagKey string, field reflect.StructField) map[string]string { + tag := field.Tag.Get(tagKey) + if tag == "" { + return nil + } + + values := map[string]string{} + + options := strings.Split(tag, ",") + for _, optionConf := range options { + parts := strings.Split(optionConf, "=") + + switch len(parts) { + case 1: + // flag option + parts = append(parts, "true") + case 2: + // key=value option + default: + // invalid option + continue + } + + values[parts[0]] = parts[1] + } + + return values +} + +func parseParamTag(tagKey string, field reflect.StructField, defaultStyle string, defaultExplode bool) *paramTag { + // example `{tagKey}:"style=simple,explode=false,name=apiID"` + values := parseStructTag(tagKey, field) + if values == nil { + return nil + } + + tag := ¶mTag{ + Style: defaultStyle, + Explode: defaultExplode, + ParamName: strings.ToLower(field.Name), + } + + for k, v := range values { + switch k { + case "style": + tag.Style = v + case "explode": + tag.Explode = v == "true" + case "name": + tag.ParamName = v + case "serialization": + tag.Serialization = v + } + } + + return tag +} + +func valToString(val interface{}) string { + switch v := val.(type) { + case time.Time: + return v.Format(time.RFC3339Nano) + case big.Int: + return v.String() + case decimal.Big: + return v.String() + default: + return fmt.Sprintf("%v", v) + } +} + +func populateFromGlobals(fieldType reflect.StructField, valType reflect.Value, paramType string, globals map[string]map[string]map[string]interface{}) reflect.Value { + if globals != nil && fieldType.Type.Kind() == reflect.Ptr { + parameters, ok := globals["parameters"] + if ok { + paramsOfType, ok := parameters[paramType] + if ok { + globalVal, ok := paramsOfType[fieldType.Name] + if ok { + if reflect.TypeOf(globalVal).Kind() == fieldType.Type.Elem().Kind() && valType.IsNil() { + valType = reflect.ValueOf(&globalVal) + } + } + } + } + } + + return valType +} + +func isNil(typ reflect.Type, val reflect.Value) bool { + if typ.Kind() == reflect.Ptr || typ.Kind() == reflect.Map || typ.Kind() == reflect.Slice || typ.Kind() == reflect.Interface { + return val.IsNil() + } + + return false +} diff --git a/internal/sdk/sdk.go b/internal/sdk/sdk.go new file mode 100644 index 0000000..d2d993d --- /dev/null +++ b/internal/sdk/sdk.go @@ -0,0 +1,172 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package sdk + +import ( + "context" + "fmt" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/models/shared" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/utils" + "net/http" + "time" +) + +// ServerList contains the list of servers available to the SDK +var ServerList = []string{ + "https://file.sls.epilot.io", +} + +// HTTPClient provides an interface for suplying the SDK with a custom HTTP client +type HTTPClient interface { + Do(req *http.Request) (*http.Response, error) +} + +// String provides a helper function to return a pointer to a string +func String(s string) *string { return &s } + +// Bool provides a helper function to return a pointer to a bool +func Bool(b bool) *bool { return &b } + +// Int provides a helper function to return a pointer to an int +func Int(i int) *int { return &i } + +// Int64 provides a helper function to return a pointer to an int64 +func Int64(i int64) *int64 { return &i } + +// Float32 provides a helper function to return a pointer to a float32 +func Float32(f float32) *float32 { return &f } + +// Float64 provides a helper function to return a pointer to a float64 +func Float64(f float64) *float64 { return &f } + +type sdkConfiguration struct { + DefaultClient HTTPClient + SecurityClient HTTPClient + Security func(context.Context) (interface{}, error) + ServerURL string + ServerIndex int + Language string + OpenAPIDocVersion string + SDKVersion string + GenVersion string + UserAgent string + RetryConfig *utils.RetryConfig +} + +func (c *sdkConfiguration) GetServerDetails() (string, map[string]string) { + if c.ServerURL != "" { + return c.ServerURL, nil + } + + return ServerList[c.ServerIndex], nil +} + +// SDK - File API: Upload and manage all files stored in epilot +type SDK struct { + // Files API + Files *Files + // Session API for cookie authentication + Session *Session + + sdkConfiguration sdkConfiguration +} + +type SDKOption func(*SDK) + +// WithServerURL allows the overriding of the default server URL +func WithServerURL(serverURL string) SDKOption { + return func(sdk *SDK) { + sdk.sdkConfiguration.ServerURL = serverURL + } +} + +// WithTemplatedServerURL allows the overriding of the default server URL with a templated URL populated with the provided parameters +func WithTemplatedServerURL(serverURL string, params map[string]string) SDKOption { + return func(sdk *SDK) { + if params != nil { + serverURL = utils.ReplaceParameters(serverURL, params) + } + + sdk.sdkConfiguration.ServerURL = serverURL + } +} + +// WithServerIndex allows the overriding of the default server by index +func WithServerIndex(serverIndex int) SDKOption { + return func(sdk *SDK) { + if serverIndex < 0 || serverIndex >= len(ServerList) { + panic(fmt.Errorf("server index %d out of range", serverIndex)) + } + + sdk.sdkConfiguration.ServerIndex = serverIndex + } +} + +// WithClient allows the overriding of the default HTTP client used by the SDK +func WithClient(client HTTPClient) SDKOption { + return func(sdk *SDK) { + sdk.sdkConfiguration.DefaultClient = client + } +} + +func withSecurity(security interface{}) func(context.Context) (interface{}, error) { + return func(context.Context) (interface{}, error) { + return &security, nil + } +} + +// WithSecurity configures the SDK to use the provided security details +func WithSecurity(security shared.Security) SDKOption { + return func(sdk *SDK) { + sdk.sdkConfiguration.Security = withSecurity(security) + } +} + +// WithSecuritySource configures the SDK to invoke the Security Source function on each method call to determine authentication +func WithSecuritySource(security func(context.Context) (shared.Security, error)) SDKOption { + return func(sdk *SDK) { + sdk.sdkConfiguration.Security = func(ctx context.Context) (interface{}, error) { + return security(ctx) + } + } +} + +func WithRetryConfig(retryConfig utils.RetryConfig) SDKOption { + return func(sdk *SDK) { + sdk.sdkConfiguration.RetryConfig = &retryConfig + } +} + +// New creates a new instance of the SDK with the provided options +func New(opts ...SDKOption) *SDK { + sdk := &SDK{ + sdkConfiguration: sdkConfiguration{ + Language: "go", + OpenAPIDocVersion: "0.1.0", + SDKVersion: "0.0.1", + GenVersion: "2.230.1", + UserAgent: "speakeasy-sdk/go 0.0.1 2.230.1 0.1.0 epilot-file", + }, + } + for _, opt := range opts { + opt(sdk) + } + + // Use WithClient to override the default client if you would like to customize the timeout + if sdk.sdkConfiguration.DefaultClient == nil { + sdk.sdkConfiguration.DefaultClient = &http.Client{Timeout: 60 * time.Second} + } + if sdk.sdkConfiguration.SecurityClient == nil { + if sdk.sdkConfiguration.Security != nil { + sdk.sdkConfiguration.SecurityClient = utils.ConfigureSecurityClient(sdk.sdkConfiguration.DefaultClient, sdk.sdkConfiguration.Security) + } else { + sdk.sdkConfiguration.SecurityClient = sdk.sdkConfiguration.DefaultClient + } + } + + sdk.Files = newFiles(sdk.sdkConfiguration) + + sdk.Session = newSession(sdk.sdkConfiguration) + + return sdk +} diff --git a/internal/sdk/session.go b/internal/sdk/session.go new file mode 100644 index 0000000..5614344 --- /dev/null +++ b/internal/sdk/session.go @@ -0,0 +1,187 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package sdk + +import ( + "bytes" + "context" + "fmt" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/models/operations" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/utils" + "io" + "net/http" + "strings" +) + +// Session API for cookie authentication +type Session struct { + sdkConfiguration sdkConfiguration +} + +func newSession(sdkConfig sdkConfiguration) *Session { + return &Session{ + sdkConfiguration: sdkConfig, + } +} + +// DeleteSession - deleteSession +// End browser session by deleting token cookie +func (s *Session) DeleteSession(ctx context.Context, opts ...operations.Option) (*operations.DeleteSessionResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url := strings.TrimSuffix(baseURL, "/") + "/v1/files/session" + + req, err := http.NewRequestWithContext(ctx, "DELETE", url, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "*/*") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.DeleteSessionResponse{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 200: + } + + return res, nil +} + +// GetSession - getSession +// Start a browser session by setting passed Authorization token in a server side cookie. +// +// Allows using preview urls directly in img src for private files using cookie authentication. +func (s *Session) GetSession(ctx context.Context, opts ...operations.Option) (*operations.GetSessionResponse, error) { + o := operations.Options{} + supportedOptions := []string{ + operations.SupportedOptionRetries, + } + + for _, opt := range opts { + if err := opt(&o, supportedOptions...); err != nil { + return nil, fmt.Errorf("error applying option: %w", err) + } + } + baseURL := utils.ReplaceParameters(s.sdkConfiguration.GetServerDetails()) + url := strings.TrimSuffix(baseURL, "/") + "/v1/files/session" + + req, err := http.NewRequestWithContext(ctx, "GET", url, nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %w", err) + } + req.Header.Set("Accept", "*/*") + req.Header.Set("user-agent", s.sdkConfiguration.UserAgent) + + client := s.sdkConfiguration.SecurityClient + + globalRetryConfig := s.sdkConfiguration.RetryConfig + retryConfig := o.Retries + if retryConfig == nil { + if globalRetryConfig == nil { + retryConfig = &utils.RetryConfig{ + Strategy: "backoff", + Backoff: &utils.BackoffStrategy{ + InitialInterval: 5000, + MaxInterval: 60000, + Exponent: 1.5, + MaxElapsedTime: 3600000, + }, + RetryConnectionErrors: true, + } + } else { + retryConfig = globalRetryConfig + } + } + + httpRes, err := utils.Retry(ctx, utils.Retries{ + Config: retryConfig, + StatusCodes: []string{ + "5XX", + }, + }, func() (*http.Response, error) { + return client.Do(req) + }) + if err != nil { + return nil, fmt.Errorf("error sending request: %w", err) + } + if httpRes == nil { + return nil, fmt.Errorf("error sending request: no response") + } + + rawBody, err := io.ReadAll(httpRes.Body) + if err != nil { + return nil, fmt.Errorf("error reading response body: %w", err) + } + httpRes.Body.Close() + httpRes.Body = io.NopCloser(bytes.NewBuffer(rawBody)) + + contentType := httpRes.Header.Get("Content-Type") + + res := &operations.GetSessionResponse{ + StatusCode: httpRes.StatusCode, + ContentType: contentType, + RawResponse: httpRes, + } + switch { + case httpRes.StatusCode == 200: + } + + return res, nil +} diff --git a/internal/validators/DateValidator.go b/internal/validators/DateValidator.go new file mode 100644 index 0000000..4df680e --- /dev/null +++ b/internal/validators/DateValidator.go @@ -0,0 +1,50 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package validators + +import ( + "context" + "github.com/epilot-dev/terraform-provider-epilot-file/internal/sdk/pkg/types" + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.String = DateValidator{} + +type DateValidator struct { +} + +func (validator DateValidator) Description(ctx context.Context) string { + return "value must be a string in YYYY-MM-DD format" +} + +func (validator DateValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +func (validator DateValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + // Only validate the attribute configuration value if it is known. + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + if _, err := types.NewDateFromString(req.ConfigValue.ValueString()); err != nil { + resp.Diagnostics.Append(validatordiag.InvalidAttributeTypeDiagnostic( + req.Path, + validator.MarkdownDescription(ctx), + req.ConfigValue.ValueString(), + )) + return + } +} + +// IsDate returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a String. +// - Is in YYYY-MM-DD Format. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func IsValidDate() validator.String { + return DateValidator{} +} diff --git a/internal/validators/ExactlyOneChild.go b/internal/validators/ExactlyOneChild.go new file mode 100644 index 0000000..5741558 --- /dev/null +++ b/internal/validators/ExactlyOneChild.go @@ -0,0 +1,53 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package validators + +import ( + "context" + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.Object = exactlyOneChild{} + +// exactlyOneChild validates if the provided value is of type string and can be parsed as JSON. +type exactlyOneChild struct { +} + +func (validator exactlyOneChild) ValidateObject(ctx context.Context, req validator.ObjectRequest, resp *validator.ObjectResponse) { + // Only validate the attribute configuration value if it is known. + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + defined := make(map[string]bool) + count := 0 + for key, attr := range req.ConfigValue.Attributes() { + if attr.IsUnknown() || attr.IsNull() { + continue + } + defined[key] = true + count++ + } + if count != 1 { + resp.Diagnostics.Append(validatordiag.InvalidAttributeTypeDiagnostic( + req.Path, + validator.MarkdownDescription(ctx), + req.ConfigValue.String(), + )) + } +} + +func (validator exactlyOneChild) Description(ctx context.Context) string { + return "value must have exactly one child attribute defined" +} + +func (validator exactlyOneChild) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +// ExactlyOneChild returns an AttributeValidator which ensures that any configured +// attribute object has only one child attribute. +// Null (unconfigured) and unknown values are skipped. +func ExactlyOneChild() validator.Object { + return exactlyOneChild{} +} diff --git a/internal/validators/JSONParseValidator.go b/internal/validators/JSONParseValidator.go new file mode 100644 index 0000000..3a341e1 --- /dev/null +++ b/internal/validators/JSONParseValidator.go @@ -0,0 +1,51 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package validators + +import ( + "context" + "encoding/json" + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.String = JSONParseValidator{} + +// JSONParseValidator validates if the provided value is of type string and can be parsed as JSON. +type JSONParseValidator struct { +} + +func (validator JSONParseValidator) Description(ctx context.Context) string { + return "value must be parsable as JSON" +} + +func (validator JSONParseValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +func (validator JSONParseValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + // Only validate the attribute configuration value if it is known. + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + if !json.Valid([]byte(req.ConfigValue.ValueString())) { + resp.Diagnostics.Append(validatordiag.InvalidAttributeTypeDiagnostic( + req.Path, + validator.MarkdownDescription(ctx), + req.ConfigValue.ValueString(), + )) + return + } +} + +// IsValidJSON returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a String. +// - Is considered valid JSON. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func IsValidJSON() validator.String { + return JSONParseValidator{} +} diff --git a/internal/validators/RFC3339Validator.go b/internal/validators/RFC3339Validator.go new file mode 100644 index 0000000..ad3d715 --- /dev/null +++ b/internal/validators/RFC3339Validator.go @@ -0,0 +1,50 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package validators + +import ( + "context" + "time" + + "github.com/hashicorp/terraform-plugin-framework-validators/helpers/validatordiag" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.String = RFC3339TimeValidator{} + +type RFC3339TimeValidator struct{} + +func (validator RFC3339TimeValidator) Description(ctx context.Context) string { + return "value must be a string in RFC3339 format" +} + +func (validator RFC3339TimeValidator) MarkdownDescription(ctx context.Context) string { + return validator.Description(ctx) +} + +func (validator RFC3339TimeValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + // Only validate the attribute configuration value if it is known. + if req.ConfigValue.IsNull() || req.ConfigValue.IsUnknown() { + return + } + + if _, err := time.Parse(time.RFC3339Nano, req.ConfigValue.ValueString()); err != nil { + resp.Diagnostics.Append(validatordiag.InvalidAttributeTypeDiagnostic( + req.Path, + validator.MarkdownDescription(ctx), + req.ConfigValue.ValueString(), + )) + return + } +} + +// IsRFC3339 returns an AttributeValidator which ensures that any configured +// attribute value: +// +// - Is a String. +// - Is in RFC3339Nano Format. +// +// Null (unconfigured) and unknown (known after apply) values are skipped. +func IsRFC3339() validator.String { + return RFC3339TimeValidator{} +} diff --git a/internal/validators/boolvalidators/not_null.go b/internal/validators/boolvalidators/not_null.go new file mode 100644 index 0000000..45e2e97 --- /dev/null +++ b/internal/validators/boolvalidators/not_null.go @@ -0,0 +1,49 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package boolvalidators + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.Bool = BoolNotNullValidator{} + +// BoolNotNullValidator validates that an attribute is not null. Most +// attributes should set Required: true instead, however in certain scenarios, +// such as a computed nested attribute, all underlying attributes must also be +// computed for planning to not show unexpected differences. +type BoolNotNullValidator struct{} + +// Description describes the validation in plain text formatting. +func (v BoolNotNullValidator) Description(_ context.Context) string { + return "value must be configured" +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v BoolNotNullValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// Validate performs the validation. +func (v BoolNotNullValidator) ValidateBool(ctx context.Context, req validator.BoolRequest, resp *validator.BoolResponse) { + if !req.ConfigValue.IsNull() { + return + } + + resp.Diagnostics.AddAttributeError( + req.Path, + "Missing Attribute Value", + req.Path.String()+": "+v.Description(ctx), + ) +} + +// NotNull returns an validator which ensures that the attribute is +// configured. Most attributes should set Required: true instead, however in +// certain scenarios, such as a computed nested attribute, all underlying +// attributes must also be computed for planning to not show unexpected +// differences. +func NotNull() validator.Bool { + return BoolNotNullValidator{} +} diff --git a/internal/validators/float64validators/not_null.go b/internal/validators/float64validators/not_null.go new file mode 100644 index 0000000..75099c2 --- /dev/null +++ b/internal/validators/float64validators/not_null.go @@ -0,0 +1,49 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package float64validators + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.Float64 = Float64NotNullValidator{} + +// Float64NotNullValidator validates that an attribute is not null. Most +// attributes should set Required: true instead, however in certain scenarios, +// such as a computed nested attribute, all underlying attributes must also be +// computed for planning to not show unexpected differences. +type Float64NotNullValidator struct{} + +// Description describes the validation in plain text formatting. +func (v Float64NotNullValidator) Description(_ context.Context) string { + return "value must be configured" +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v Float64NotNullValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// Validate performs the validation. +func (v Float64NotNullValidator) ValidateFloat64(ctx context.Context, req validator.Float64Request, resp *validator.Float64Response) { + if !req.ConfigValue.IsNull() { + return + } + + resp.Diagnostics.AddAttributeError( + req.Path, + "Missing Attribute Value", + req.Path.String()+": "+v.Description(ctx), + ) +} + +// NotNull returns an validator which ensures that the attribute is +// configured. Most attributes should set Required: true instead, however in +// certain scenarios, such as a computed nested attribute, all underlying +// attributes must also be computed for planning to not show unexpected +// differences. +func NotNull() validator.Float64 { + return Float64NotNullValidator{} +} diff --git a/internal/validators/int64validators/not_null.go b/internal/validators/int64validators/not_null.go new file mode 100644 index 0000000..32192d8 --- /dev/null +++ b/internal/validators/int64validators/not_null.go @@ -0,0 +1,49 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package int64validators + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.Int64 = Int64NotNullValidator{} + +// Int64NotNullValidator validates that an attribute is not null. Most +// attributes should set Required: true instead, however in certain scenarios, +// such as a computed nested attribute, all underlying attributes must also be +// computed for planning to not show unexpected differences. +type Int64NotNullValidator struct{} + +// Description describes the validation in plain text formatting. +func (v Int64NotNullValidator) Description(_ context.Context) string { + return "value must be configured" +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v Int64NotNullValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// Validate performs the validation. +func (v Int64NotNullValidator) ValidateInt64(ctx context.Context, req validator.Int64Request, resp *validator.Int64Response) { + if !req.ConfigValue.IsNull() { + return + } + + resp.Diagnostics.AddAttributeError( + req.Path, + "Missing Attribute Value", + req.Path.String()+": "+v.Description(ctx), + ) +} + +// NotNull returns an validator which ensures that the attribute is +// configured. Most attributes should set Required: true instead, however in +// certain scenarios, such as a computed nested attribute, all underlying +// attributes must also be computed for planning to not show unexpected +// differences. +func NotNull() validator.Int64 { + return Int64NotNullValidator{} +} diff --git a/internal/validators/listvalidators/not_null.go b/internal/validators/listvalidators/not_null.go new file mode 100644 index 0000000..c7bfa6e --- /dev/null +++ b/internal/validators/listvalidators/not_null.go @@ -0,0 +1,49 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package listvalidators + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.List = ListNotNullValidator{} + +// ListNotNullValidator validates that an attribute is not null. Most +// attributes should set Required: true instead, however in certain scenarios, +// such as a computed nested attribute, all underlying attributes must also be +// computed for planning to not show unexpected differences. +type ListNotNullValidator struct{} + +// Description describes the validation in plain text formatting. +func (v ListNotNullValidator) Description(_ context.Context) string { + return "value must be configured" +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v ListNotNullValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// Validate performs the validation. +func (v ListNotNullValidator) ValidateList(ctx context.Context, req validator.ListRequest, resp *validator.ListResponse) { + if !req.ConfigValue.IsNull() { + return + } + + resp.Diagnostics.AddAttributeError( + req.Path, + "Missing Attribute Value", + req.Path.String()+": "+v.Description(ctx), + ) +} + +// NotNull returns an validator which ensures that the attribute is +// configured. Most attributes should set Required: true instead, however in +// certain scenarios, such as a computed nested attribute, all underlying +// attributes must also be computed for planning to not show unexpected +// differences. +func NotNull() validator.List { + return ListNotNullValidator{} +} diff --git a/internal/validators/mapvalidators/not_null.go b/internal/validators/mapvalidators/not_null.go new file mode 100644 index 0000000..7d22172 --- /dev/null +++ b/internal/validators/mapvalidators/not_null.go @@ -0,0 +1,49 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package mapvalidators + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.Map = MapNotNullValidator{} + +// MapNotNullValidator validates that an attribute is not null. Most +// attributes should set Required: true instead, however in certain scenarios, +// such as a computed nested attribute, all underlying attributes must also be +// computed for planning to not show unexpected differences. +type MapNotNullValidator struct{} + +// Description describes the validation in plain text formatting. +func (v MapNotNullValidator) Description(_ context.Context) string { + return "value must be configured" +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v MapNotNullValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// Validate performs the validation. +func (v MapNotNullValidator) ValidateMap(ctx context.Context, req validator.MapRequest, resp *validator.MapResponse) { + if !req.ConfigValue.IsNull() { + return + } + + resp.Diagnostics.AddAttributeError( + req.Path, + "Missing Attribute Value", + req.Path.String()+": "+v.Description(ctx), + ) +} + +// NotNull returns an validator which ensures that the attribute is +// configured. Most attributes should set Required: true instead, however in +// certain scenarios, such as a computed nested attribute, all underlying +// attributes must also be computed for planning to not show unexpected +// differences. +func NotNull() validator.Map { + return MapNotNullValidator{} +} diff --git a/internal/validators/numbervalidators/not_null.go b/internal/validators/numbervalidators/not_null.go new file mode 100644 index 0000000..b085170 --- /dev/null +++ b/internal/validators/numbervalidators/not_null.go @@ -0,0 +1,49 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package numbervalidators + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.Number = NumberNotNullValidator{} + +// NumberNotNullValidator validates that an attribute is not null. Most +// attributes should set Required: true instead, however in certain scenarios, +// such as a computed nested attribute, all underlying attributes must also be +// computed for planning to not show unexpected differences. +type NumberNotNullValidator struct{} + +// Description describes the validation in plain text formatting. +func (v NumberNotNullValidator) Description(_ context.Context) string { + return "value must be configured" +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v NumberNotNullValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// Validate performs the validation. +func (v NumberNotNullValidator) ValidateNumber(ctx context.Context, req validator.NumberRequest, resp *validator.NumberResponse) { + if !req.ConfigValue.IsNull() { + return + } + + resp.Diagnostics.AddAttributeError( + req.Path, + "Missing Attribute Value", + req.Path.String()+": "+v.Description(ctx), + ) +} + +// NotNull returns an validator which ensures that the attribute is +// configured. Most attributes should set Required: true instead, however in +// certain scenarios, such as a computed nested attribute, all underlying +// attributes must also be computed for planning to not show unexpected +// differences. +func NotNull() validator.Number { + return NumberNotNullValidator{} +} diff --git a/internal/validators/objectvalidators/not_null.go b/internal/validators/objectvalidators/not_null.go new file mode 100644 index 0000000..c45ca8d --- /dev/null +++ b/internal/validators/objectvalidators/not_null.go @@ -0,0 +1,49 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package objectvalidators + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.Object = ObjectNotNullValidator{} + +// ObjectNotNullValidator validates that an attribute is not null. Most +// attributes should set Required: true instead, however in certain scenarios, +// such as a computed nested attribute, all underlying attributes must also be +// computed for planning to not show unexpected differences. +type ObjectNotNullValidator struct{} + +// Description describes the validation in plain text formatting. +func (v ObjectNotNullValidator) Description(_ context.Context) string { + return "value must be configured" +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v ObjectNotNullValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// Validate performs the validation. +func (v ObjectNotNullValidator) ValidateObject(ctx context.Context, req validator.ObjectRequest, resp *validator.ObjectResponse) { + if !req.ConfigValue.IsNull() { + return + } + + resp.Diagnostics.AddAttributeError( + req.Path, + "Missing Attribute Value", + req.Path.String()+": "+v.Description(ctx), + ) +} + +// NotNull returns an validator which ensures that the attribute is +// configured. Most attributes should set Required: true instead, however in +// certain scenarios, such as a computed nested attribute, all underlying +// attributes must also be computed for planning to not show unexpected +// differences. +func NotNull() validator.Object { + return ObjectNotNullValidator{} +} diff --git a/internal/validators/setvalidators/not_null.go b/internal/validators/setvalidators/not_null.go new file mode 100644 index 0000000..23d6c16 --- /dev/null +++ b/internal/validators/setvalidators/not_null.go @@ -0,0 +1,49 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package setvalidators + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.Set = SetNotNullValidator{} + +// SetNotNullValidator validates that an attribute is not null. Most +// attributes should set Required: true instead, however in certain scenarios, +// such as a computed nested attribute, all underlying attributes must also be +// computed for planning to not show unexpected differences. +type SetNotNullValidator struct{} + +// Description describes the validation in plain text formatting. +func (v SetNotNullValidator) Description(_ context.Context) string { + return "value must be configured" +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v SetNotNullValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// Validate performs the validation. +func (v SetNotNullValidator) ValidateSet(ctx context.Context, req validator.SetRequest, resp *validator.SetResponse) { + if !req.ConfigValue.IsNull() { + return + } + + resp.Diagnostics.AddAttributeError( + req.Path, + "Missing Attribute Value", + req.Path.String()+": "+v.Description(ctx), + ) +} + +// NotNull returns an validator which ensures that the attribute is +// configured. Most attributes should set Required: true instead, however in +// certain scenarios, such as a computed nested attribute, all underlying +// attributes must also be computed for planning to not show unexpected +// differences. +func NotNull() validator.Set { + return SetNotNullValidator{} +} diff --git a/internal/validators/stringvalidators/not_null.go b/internal/validators/stringvalidators/not_null.go new file mode 100644 index 0000000..f15f8ae --- /dev/null +++ b/internal/validators/stringvalidators/not_null.go @@ -0,0 +1,49 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package stringvalidators + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" +) + +var _ validator.String = StringNotNullValidator{} + +// StringNotNullValidator validates that an attribute is not null. Most +// attributes should set Required: true instead, however in certain scenarios, +// such as a computed nested attribute, all underlying attributes must also be +// computed for planning to not show unexpected differences. +type StringNotNullValidator struct{} + +// Description describes the validation in plain text formatting. +func (v StringNotNullValidator) Description(_ context.Context) string { + return "value must be configured" +} + +// MarkdownDescription describes the validation in Markdown formatting. +func (v StringNotNullValidator) MarkdownDescription(ctx context.Context) string { + return v.Description(ctx) +} + +// Validate performs the validation. +func (v StringNotNullValidator) ValidateString(ctx context.Context, req validator.StringRequest, resp *validator.StringResponse) { + if !req.ConfigValue.IsNull() { + return + } + + resp.Diagnostics.AddAttributeError( + req.Path, + "Missing Attribute Value", + req.Path.String()+": "+v.Description(ctx), + ) +} + +// NotNull returns an validator which ensures that the attribute is +// configured. Most attributes should set Required: true instead, however in +// certain scenarios, such as a computed nested attribute, all underlying +// attributes must also be computed for planning to not show unexpected +// differences. +func NotNull() validator.String { + return StringNotNullValidator{} +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..a1801a1 --- /dev/null +++ b/main.go @@ -0,0 +1,42 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +package main + +import ( + "context" + "flag" + "log" + + "github.com/epilot-dev/terraform-provider-epilot-file/internal/provider" + "github.com/hashicorp/terraform-plugin-framework/providerserver" +) + +// Run "go generate" to generate the docs for the registry/website on each regeneration of the provider. + +// 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 generate --rendered-provider-name terraform-provider-epilot-file + +var ( + // these will be set by the goreleaser configuration + // to appropriate values for the compiled binary + version string = "dev" +) + +func main() { + var debug bool + + flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve") + flag.Parse() + + opts := providerserver.ServeOpts{ + Address: "registry.terraform.io/epilot-dev/epilot-file", + Debug: debug, + } + + err := providerserver.Serve(context.Background(), provider.New(version), opts) + + if err != nil { + log.Fatal(err.Error()) + } +} diff --git a/overlay.yaml b/overlay.yaml new file mode 100644 index 0000000..450bcc8 --- /dev/null +++ b/overlay.yaml @@ -0,0 +1,52 @@ +overlay: 1.0.0 +info: + title: Overlay epilot file to make a file resource + version: 0.0.1 +actions: + - target: $ + update: + x-speakeasy-retries: + strategy: backoff + backoff: + initialInterval: 5000 # 5 seconds + maxInterval: 60000 # 60 seconds + maxElapsedTime: 3600000 # 5 minutes + exponent: 1.5 + statusCodes: + - 5XX + retryConnectionErrors: true + - target: $["paths"]["/v2/files/upload"]["post"] + update: + x-speakeasy-entity-operation: UploadFile#create + - target: $["paths"]["/v1/files"]["post"] + update: + x-speakeasy-entity-operation: File#create + - target: $["paths"]["/v1/files"]["post"]["requestBody"]["content"]["application/json"]["schema"]["$ref"] + update: "#/components/schemas/FileEntity" + # - target: $["components"]["schemas"]["FileEntity"] + # update: + # x-speakeasy-entity: File + - target: $["components"]["schemas"]["FileUpload"] + update: + x-speakeasy-entity: UploadFile + # - target: $["components"]["schemas"]["SaveFilePayload"] + # update: + # x-speakeasy-entity: SaveFile + - target: $["components"]["schemas"]["FileEntity"] + update: + x-speakeasy-entity: File + # - target: $["components"]["schemas"]["FileEntity"]["properties"]["versions"] + # update: + # items: {} + # - target: $["components"]["schemas"]["DownloadFilesPayload"] + # update: + # items: {} + # - target: $["components"]["schemas"]["CommonSaveFilePayload"]["properties"]["_tags"] + # update: + # items: {} + # - target: $["components"]["schemas"]["CommonSaveFilePayload"]["properties"]["relations"] + # update: + # items: {} + # - target: $["components"]["schemas"]["FileRelationItem"]["properties"]["_tags"] + # update: + # items: {} \ No newline at end of file diff --git a/terraform-registry-manifest.json b/terraform-registry-manifest.json new file mode 100644 index 0000000..fec2a56 --- /dev/null +++ b/terraform-registry-manifest.json @@ -0,0 +1,6 @@ +{ + "version": 1, + "metadata": { + "protocol_versions": ["6.0"] + } +} diff --git a/tools/tools.go b/tools/tools.go new file mode 100644 index 0000000..4f42a8c --- /dev/null +++ b/tools/tools.go @@ -0,0 +1,10 @@ +// Code generated by Speakeasy (https://speakeasyapi.dev). DO NOT EDIT. + +//go:build tools + +package tools + +import ( + // Documentation generation + _ "github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs" +)