diff --git a/.github/workflows/acceptance-kiota.yml b/.github/workflows/acceptance-kiota.yml new file mode 100644 index 00000000..4a1c40e9 --- /dev/null +++ b/.github/workflows/acceptance-kiota.yml @@ -0,0 +1,115 @@ +# Runs the kiota pilot's generated acceptance tests against a live ThousandEyes tenant. +# +# A separate workflow from acceptance.yml rather than a matrix leg, deliberately: the two +# pilots exist to prove two dialects of the same generator, and a failure must name which +# dialect broke without reading logs. The resty pilot passing while this one fails is the +# single most useful signal this workflow can produce -- it means the API did not move, +# the kiota binding did. +# +# Everything else follows the resty workflow's rules, for the same reasons it has them: +# hand-triggered or scheduled, never `pull_request`, because the token is a long-lived +# credential the ThousandEyes API cannot revoke; gated on the same `acceptance` +# environment, whose secrets this run reuses -- one tenant, one credential, two dialects. +name: go | Acceptance tests (kiota) + +# No workflow_dispatch inputs; see acceptance.yml for the injection and CKV_GHA_7 +# reasoning, which applies here unchanged. +on: + workflow_dispatch: + schedule: + # Weekly, Monday 07:30 UTC: after the resty run's 06:00 slot. The shared concurrency + # group below is what actually serialises them; the offset just keeps the queue short. + - cron: "30 7 * * 1" + +permissions: + contents: read + +# The SAME group as acceptance.yml, not a kiota-specific one. Both pilots create objects +# in the same tenant with the same tfacc- fixture prefixes, so two live runs -- whichever +# workflows they came from -- would fight over state that is not Terraform's. +concurrency: + group: acceptance-${{ github.repository }} + cancel-in-progress: false + +jobs: + acceptance: + name: "๐ŸŒ Acceptance, kiota dialect (live tenant)" + runs-on: ubuntu-24.04-arm + # The same gated environment as the resty run: its required reviewers and its + # THOUSANDEYES_* secrets apply to both dialects identically. + environment: acceptance + + steps: + - name: Harden Runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Check Out + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Set up Go + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: "go.mod" + cache-dependency-path: "go.sum" + cache: true + + # terraform-plugin-testing downloads a Terraform binary at run time unless one is on + # the PATH; pinning it here keeps a scheduled failure unambiguous between API drift + # and CLI drift -- the same version the resty workflow pins. + - name: Set up Terraform + uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1 + with: + terraform_version: "1.14.*" + terraform_wrapper: false + + - name: Refuse to run without a credential + env: + THOUSANDEYES_BEARER_TOKEN: ${{ secrets.THOUSANDEYES_BEARER_TOKEN }} + run: | + if [ -z "${THOUSANDEYES_BEARER_TOKEN}" ]; then + echo "::error::THOUSANDEYES_BEARER_TOKEN is not set for the 'acceptance' environment." + echo "Every acceptance test would skip, and the run would report success having" + echo "tested nothing. That is the failure this step exists to prevent." + exit 1 + fi + echo "โœ… A credential is present." + + # TF_ACC is what terraform-plugin-testing gates on; without it every TestAcc skips. + # The token reaches the provider through the environment and never through a flag, + # because a flag lands in the process table and in shell history. + - name: Run the acceptance tests + working-directory: pilot/thousandeyes-kiota + env: + TF_ACC: "1" + THOUSANDEYES_BEARER_TOKEN: ${{ secrets.THOUSANDEYES_BEARER_TOKEN }} + THOUSANDEYES_ACCOUNT_GROUP_ID: ${{ secrets.THOUSANDEYES_ACCOUNT_GROUP_ID }} + run: | + echo "::group::๐Ÿ“ฆ Download" + go mod download + echo "::endgroup::" + + # -count=1 defeats the test cache -- a cached pass proves nothing about a live + # API -- and -p 1 serialises test packages against the one shared tenant, both + # for the reasons acceptance.yml records at length. + go test \ + -v \ + -count=1 \ + -p 1 \ + -timeout 90m \ + -run 'TestAcc' \ + ./... + + # No sweep step, exactly as in acceptance.yml and for the reasons it records: + # terraform-plugin-testing destroys what each test created, the generated + # CheckDestroyed proves the API agrees, and `probe sweep` tracks the prober's + # ledger, not Terraform's objects. + - name: Note on cleanup + if: always() + run: | + echo "Acceptance tests destroy what they create; CheckDestroyed fails the test if the" + echo "API still holds the object. If this job was cancelled or the runner died, look" + echo "for objects prefixed 'tfacc-' in the tenant." diff --git a/.github/workflows/codegen-verify.yml b/.github/workflows/codegen-verify.yml index 4b9ccca9..1e8e21f1 100644 --- a/.github/workflows/codegen-verify.yml +++ b/.github/workflows/codegen-verify.yml @@ -70,6 +70,11 @@ jobs: -blueprint blueprints/thousandeyes \ -out pilot/thousandeyes echo "::endgroup::" + echo "::group::๐Ÿ” Regenerating the kiota pilot provider" + go run ./cmd/tfpfgen provider generate \ + -blueprint blueprints/thousandeyes-kiota \ + -out pilot/thousandeyes-kiota + echo "::endgroup::" # git diff rather than `tfpfgen provider generate -check`, deliberately. verify compares # the files the blueprints produce against disk, so it cannot see a stale file @@ -204,6 +209,11 @@ jobs: -module pilot/thousandeyes \ -out blueprints/thousandeyes/static.facts.json -check echo "::endgroup::" + echo "::group::๐Ÿ”— Bindings (kiota pilot)" + go run ./cmd/tfpfgen bindings check \ + -blueprint blueprints/thousandeyes-kiota \ + -module pilot/thousandeyes-kiota + echo "::endgroup::" interop: name: "๐Ÿ”€ Round-trip through tfplugingen-framework" @@ -306,6 +316,57 @@ jobs: echo "โœ… HashiCorp's own generator accepted the export and produced formatted Go." + sdk-drift: + name: "๐Ÿงฐ Kiota SDK matches the snapshot" + runs-on: ubuntu-24.04-arm + if: github.event.pull_request.draft == false + env: + # The version must equal the committed kiota-lock.json's kiotaVersion -- + # tfpfgen refuses otherwise -- and the checksum is recorded here because + # the release publishes none. + KIOTA_VERSION: "1.34.1" + KIOTA_SHA256: "0dc6ccc5d00cd870a47434d8209758f708ff2b905d318731407210c0c6938144" + + steps: + - name: Harden Runner + uses: step-security/harden-runner@bf7454d06d71f1098171f2acdf0cd4708d7b5920 # v2.20.0 + with: + egress-policy: audit + + - name: Check Out + uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 + with: + persist-credentials: false + + - name: Set up Go + uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7.0.0 + with: + go-version-file: "go.mod" + cache-dependency-path: "go.sum" + cache: true + + - name: Install the pinned kiota + run: | + echo "::group::๐Ÿงฐ kiota v${KIOTA_VERSION}" + curl -sL -o /tmp/kiota.zip \ + "https://github.com/microsoft/kiota/releases/download/v${KIOTA_VERSION}/linux-arm64.zip" + echo "${KIOTA_SHA256} /tmp/kiota.zip" | sha256sum -c - + mkdir -p "$HOME/kiota" + unzip -q /tmp/kiota.zip -d "$HOME/kiota" + chmod +x "$HOME/kiota/kiota" + echo "$HOME/kiota" >> "$GITHUB_PATH" + echo "::endgroup::" + + - name: Regenerate the SDK and diff + run: | + echo "::group::๐Ÿงฐ sdk generate -check" + go run ./cmd/tfpfgen sdk generate \ + -out pilot/thousandeyes-kiota/internal/sdk \ + -include "/tags,/tags/**" \ + -client-name ThousandEyesClient \ + -check + echo "::endgroup::" + probe-replay: name: "๐Ÿ”ฌ Re-derive probe facts offline" runs-on: ubuntu-24.04-arm diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f33235c..42776208 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,24 @@ This file is maintained by [release-please](.github/workflows/release-please.yml from conventional commit messages. Add entries by writing good commit messages, not by editing this file by hand. +## Unreleased โ€” Kiota SDK generation and the kiotaFluent dialect + +`tfpfgen sdk generate` derives a Go SDK from a pinned OpenAPI snapshot with +Microsoft Kiota (PATH tool, hard version gate on the committed +kiota-lock.json; `-mode embed` into the provider module by default, +`-mode external` with its own go.mod; `-check` regenerates and +byte-compares). The reserved `kiotaFluent` dialect is implemented end to +end: blueprints record fluent chains as data (`Operation.chain`), the +emitter renders them with method access, nil-result guards and enum parse +companions, `bindings check` walks chains and Get/Set pairs against the +real SDK with did-you-mean, `blueprint draft -sdk-dialect kiotaFluent` +infers the whole shape from the snapshot, and `provider.sdk` gains +`mode`/`generator` with a go.mod assertion in the postcheck. A second +pilot, `pilot/thousandeyes-kiota`, binds the `tag` resource against an +embedded kiota SDK and re-derives identical facts from the shared +`recordings/thousandeyes` โ€” the probe layer is wire-level, so switching +dialect is a binding change, not an evidence change. + ## Unreleased โ€” provider push `tfpfgen provider push -out DIR -repo URL` publishes the generated provider diff --git a/README.md b/README.md index 92a23e80..2a4d8d6a 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,13 @@ flowchart LR bp -. "provider generate -check\n(fails on drift)" .-> prov ``` -with drift checking: `provider generate -check`. Alongside the main line, +with drift checking: `provider generate -check`. An optional `sdk generate` +stage sits between the snapshot and the blueprints: it derives a Go SDK from +the same pinned OpenAPI document with [Microsoft +Kiota](https://github.com/microsoft/kiota) โ€” embedded in the provider tree or +as a standalone module โ€” and the `kiotaFluent` dialect binds fluent +request-builder chains against it, so a provider no longer requires a +hand-written SDK. Alongside the main line, `bindings facts` derives static facts from the pinned SDK for `blueprint merge` to fold in, human curation (scenarios, hints, presence) feeds the same merge, and `provider generate` finishes with a postcheck battery (build ยท docs ยท fmt). diff --git a/blueprints/thousandeyes-kiota/provider.blueprint.json b/blueprints/thousandeyes-kiota/provider.blueprint.json new file mode 100644 index 00000000..6b266e2b --- /dev/null +++ b/blueprints/thousandeyes-kiota/provider.blueprint.json @@ -0,0 +1,55 @@ +{ + "formatVersion": "4", + "provider": { + "name": "thousandeyes", + "goModule": "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota", + "typePrefix": "thousandeyes", + "sdk": { + "dialect": "kiotaFluent", + "mode": "embed", + "generator": "kiota", + "modulePath": "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota", + "clientType": "*sdk.ThousandEyesClient", + "clientImport": { + "path": "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk", + "alias": "sdk" + } + }, + "conventions": { + "resourceRoot": "internal/services/resources", + "dataSourceRoot": "internal/services/datasources", + "providerPkgDir": "internal/provider", + "defaultTimeouts": { + "createSeconds": 180, + "readSeconds": 180, + "updateSeconds": 180, + "deleteSeconds": 180 + } + }, + "support": { + "convert": { + "path": "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/services/common/convert" + }, + "crud": { + "path": "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/services/common/crud" + }, + "commonSchema": { + "path": "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/services/common/schema", + "alias": "commonschema" + }, + "errors": { + "path": "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/services/common/errors" + }, + "client": { + "path": "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/client" + } + } + }, + "source": { + "specFile": "api.yaml", + "specVersion": "7.0.98", + "specSha256": "969c3dfceafed4ab3db33675e688d9957f460aa8263689fdab3108e7f0e6b7cb", + "snapshotDir": "7.0.98-t1785502133469", + "sdkVersion": "v0.2.0" + } +} \ No newline at end of file diff --git a/blueprints/thousandeyes-kiota/resources/tag.blueprint.json b/blueprints/thousandeyes-kiota/resources/tag.blueprint.json new file mode 100644 index 00000000..76527104 --- /dev/null +++ b/blueprints/thousandeyes-kiota/resources/tag.blueprint.json @@ -0,0 +1,983 @@ +{ + "formatVersion": "4", + "resources": [ + { + "key": "tag", + "name": "tag", + "goPackage": "tag", + "goPackageAlias": "v7Tag", + "goTypeName": "TagResource", + "modelTypeName": "TagResourceModel", + "serviceGroup": "tags", + "apiVersionDir": "v7", + "docRefUrl": "https://developer.cisco.com/docs/thousandeyes/list-tags/", + "schema": { + "attributes": [ + { + "name": "id", + "goField": "ID", + "type": { + "kind": "string", + "constraints": { + "format": "uuid" + } + }, + "computedOptionalRequired": "computed", + "markdownDescription": "The tag's unique identifier, assigned by the API.", + "wire": { + "jsonPath": "id", + "sdkField": "Id", + "sdkGoType": "*uuid.UUID", + "flatten": { + "func": "convert.PtrStringerToFramework" + }, + "skipExpand": true + } + }, + { + "name": "key", + "goField": "Key", + "type": { + "kind": "string" + }, + "computedOptionalRequired": "required", + "markdownDescription": "The tag's key. Together with `value` this forms the label applied to assigned objects.", + "behaviour": { + "writable": true, + "immutable": false, + "returnedOnRead": true + }, + "wire": { + "jsonPath": "key", + "sdkField": "Key", + "sdkGoType": "*string", + "expand": { + "func": "convert.FrameworkToPtrString" + }, + "flatten": { + "func": "convert.PtrStringToFramework" + } + } + }, + { + "name": "value", + "goField": "Value", + "type": { + "kind": "string" + }, + "computedOptionalRequired": "optional", + "markdownDescription": "The tag's value.\n\n\nThe API enforces this field's presence, which the specification does not declare.\n", + "behaviour": { + "writable": true, + "requiredByApi": true, + "returnedOnRead": true + }, + "wire": { + "jsonPath": "value", + "sdkField": "Value", + "sdkGoType": "*string", + "expand": { + "func": "convert.FrameworkToPtrString" + }, + "flatten": { + "func": "convert.PtrStringToFramework" + } + } + }, + { + "name": "color", + "goField": "Color", + "type": { + "kind": "string" + }, + "computedOptionalRequired": "computed_optional", + "markdownDescription": "The tag's display colour as a hex string. Computed as well as optional because the API assigns one when it is omitted; this has not yet been confirmed by probing.\n\n\nObserved: the API assigns \"#A7EB10\" when this is omitted.\n", + "behaviour": { + "immutable": false, + "serverDefault": { + "kind": "", + "raw": "\"#A7EB10\"" + }, + "returnedOnRead": true + }, + "wire": { + "jsonPath": "color", + "sdkField": "Color", + "sdkGoType": "*string", + "expand": { + "func": "convert.FrameworkToPtrString" + }, + "flatten": { + "func": "convert.PtrStringToFramework" + } + } + }, + { + "name": "description", + "goField": "Description", + "type": { + "kind": "string" + }, + "computedOptionalRequired": "optional", + "markdownDescription": "A human-readable description of the tag.", + "behaviour": { + "writable": true, + "returnedOnRead": true + }, + "wire": { + "jsonPath": "description", + "sdkField": "Description", + "sdkGoType": "*string", + "expand": { + "func": "convert.FrameworkToPtrString" + }, + "flatten": { + "func": "convert.PtrStringToFramework" + } + } + }, + { + "name": "icon", + "goField": "Icon", + "type": { + "kind": "string" + }, + "computedOptionalRequired": "computed_optional", + "markdownDescription": "The tag's icon.\n\n\nObserved: the API assigns \"LABEL\" when this is omitted.\n", + "behaviour": { + "serverDefault": { + "kind": "", + "raw": "\"LABEL\"" + }, + "returnedOnRead": true + }, + "wire": { + "jsonPath": "icon", + "sdkField": "Icon", + "sdkGoType": "*string", + "expand": { + "func": "convert.FrameworkToPtrString" + }, + "flatten": { + "func": "convert.PtrStringToFramework" + } + } + }, + { + "name": "object_type", + "goField": "ObjectType", + "type": { + "kind": "string", + "allowedValues": [ + "test", + "dashboard", + "endpoint-test", + "v-agent", + "connected-devices-test", + "endpoint-agent" + ] + }, + "computedOptionalRequired": "required", + "markdownDescription": "The kind of object the tag may be assigned to. Documented values are `test`, `v-agent`, `endpoint-test`, `dashboard` and `connected-devices-test`. The generated validator permits the whole documented set, which is wider than what any one tenant accepts, so a stale specification surfaces as the API's own error rather than as a plan failure.\n\n\nValues accepted here: `test`, `dashboard`, `endpoint-test`, `v-agent`, `connected-devices-test`.\nThe API enforces this field's presence, which the specification does not declare.\n\n\n\nThe SDK's encoding drops this type's zero value (omitempty), so configuring false, 0 or \"\" silently becomes an omission.\n", + "behaviour": { + "writable": true, + "immutable": true, + "requiredByApi": true, + "returnedOnRead": true, + "zeroValueUnsendable": true, + "acceptedValues": [ + "test", + "dashboard", + "endpoint-test", + "v-agent", + "connected-devices-test" + ], + "valuesClosed": true + }, + "wire": { + "jsonPath": "objectType", + "sdkField": "ObjectType", + "sdkGoType": "*models.Tags_API_ObjectType", + "expand": { + "func": "convert.FrameworkToKiotaEnum", + "typeArgs": [ + "models.Tags_API_ObjectType" + ], + "extraArgs": [ + "models.ParseTags_API_ObjectType" + ], + "returnsError": true + }, + "flatten": { + "func": "convert.PtrStringerToFramework" + } + } + }, + { + "name": "access_type", + "goField": "AccessType", + "type": { + "kind": "string", + "allowedValues": [ + "all", + "partner", + "system" + ] + }, + "computedOptionalRequired": "computed_optional", + "markdownDescription": "The tag's access level. Documented values are `all`, `partner` and `system`.\n\n\nValues accepted here: `all`.\nThe specification documents `system`, which the API rejected.\nThe API enforces this field's presence, which the specification does not declare.\n\n\n\nThe SDK's encoding drops this type's zero value (omitempty), so configuring false, 0 or \"\" silently becomes an omission.\n", + "behaviour": { + "requiredByApi": true, + "returnedOnRead": true, + "zeroValueUnsendable": true, + "acceptedValues": [ + "all" + ], + "rejectedValues": [ + "system" + ], + "valuesClosed": true + }, + "wire": { + "jsonPath": "accessType", + "sdkField": "AccessType", + "sdkGoType": "*models.Tags_API_AccessType", + "expand": { + "func": "convert.FrameworkToKiotaEnum", + "typeArgs": [ + "models.Tags_API_AccessType" + ], + "extraArgs": [ + "models.ParseTags_API_AccessType" + ], + "returnsError": true + }, + "flatten": { + "func": "convert.PtrStringerToFramework" + } + } + }, + { + "name": "match_type", + "goField": "MatchType", + "type": { + "kind": "string", + "allowedValues": [ + "and", + "or" + ] + }, + "computedOptionalRequired": "computed_optional", + "markdownDescription": "How the tag's filters combine when it is assigned dynamically.\n\n\nValues accepted here: `and`, `or`.\nConditionally, when objectType is \"endpoint-agent\" and type is \"dynamic\": a create omitting this field was refused with 400 and the error named the field, when objectType is \"endpoint-agent\" and type is \"dynamic\". Applied as a behaviour variant.\nConditionally, when objectType is \"endpoint-agent\": the field appeared in the read after 1 create(s), when objectType is \"endpoint-agent\". Applied as a behaviour variant.\nConditionally, when objectType is \"test\": the field was sent on 1 create(s) and appeared in no read response, including every expansion the plan declares, when objectType is \"test\". Applied as a behaviour variant.\n\n\n\nThe SDK's encoding drops this type's zero value (omitempty), so configuring false, 0 or \"\" silently becomes an omission.\n", + "behaviour": { + "zeroValueUnsendable": true, + "acceptedValues": [ + "and", + "or" + ], + "valuesClosed": true, + "conditional": [ + { + "when": [ + { + "jsonPath": "objectType", + "equals": "endpoint-agent" + } + ], + "behaviour": { + "returnedOnRead": true + } + }, + { + "when": [ + { + "jsonPath": "objectType", + "equals": "endpoint-agent" + }, + { + "jsonPath": "type", + "equals": "dynamic" + } + ], + "behaviour": { + "requiredByApi": true + } + }, + { + "when": [ + { + "jsonPath": "objectType", + "equals": "test" + } + ], + "behaviour": { + "returnedOnRead": false + } + } + ] + }, + "wire": { + "jsonPath": "matchType", + "sdkField": "MatchType", + "sdkGoType": "*models.Tags_API_TagMatchType", + "expand": { + "func": "convert.FrameworkToKiotaEnum", + "typeArgs": [ + "models.Tags_API_TagMatchType" + ], + "extraArgs": [ + "models.ParseTags_API_TagMatchType" + ], + "returnsError": true + }, + "flatten": { + "func": "convert.PtrStringerToFramework" + } + } + }, + { + "name": "type", + "goField": "Type", + "type": { + "kind": "string", + "allowedValues": [ + "static", + "dynamic" + ] + }, + "computedOptionalRequired": "computed_optional", + "markdownDescription": "The tag's type, assigned by the API.\n\n\nConditionally, when objectType is \"endpoint-agent\": a create omitting this field was refused with 400 and the error named the field, when objectType is \"endpoint-agent\". Applied as a behaviour variant.\n\n\n\nThe SDK's encoding drops this type's zero value (omitempty), so configuring false, 0 or \"\" silently becomes an omission.\n", + "behaviour": { + "zeroValueUnsendable": true, + "conditional": [ + { + "when": [ + { + "jsonPath": "objectType", + "equals": "endpoint-agent" + } + ], + "behaviour": { + "requiredByApi": true + } + } + ] + }, + "wire": { + "jsonPath": "type", + "sdkField": "TypeEscaped", + "sdkGoType": "*models.Tags_API_Type", + "expand": { + "func": "convert.FrameworkToKiotaEnum", + "typeArgs": [ + "models.Tags_API_Type" + ], + "extraArgs": [ + "models.ParseTags_API_Type" + ], + "returnsError": true + }, + "flatten": { + "func": "convert.PtrStringerToFramework" + } + } + }, + { + "name": "built_in", + "goField": "BuiltIn", + "type": { + "kind": "bool" + }, + "computedOptionalRequired": "computed", + "markdownDescription": "Whether the tag is built in rather than user-created.", + "wire": { + "jsonPath": "builtIn", + "sdkField": "BuiltIn", + "sdkGoType": "*bool", + "flatten": { + "func": "convert.PtrBoolToFramework" + }, + "skipExpand": true + } + }, + { + "name": "account_group_id", + "goField": "AccountGroupID", + "type": { + "kind": "int64" + }, + "computedOptionalRequired": "computed", + "markdownDescription": "The account group the tag belongs to. Computed rather than configurable: the provider scopes every request through its own `account_group_id` setting, so accepting a second value here would let the two disagree.", + "wire": { + "jsonPath": "aid", + "sdkField": "Aid", + "sdkGoType": "*int64", + "flatten": { + "func": "convert.PtrInt64ToFramework" + }, + "skipExpand": true + } + }, + { + "name": "create_date", + "goField": "CreateDate", + "type": { + "kind": "string", + "constraints": { + "format": "yyyy-MM-ddTHH:mm[:ss]Z" + } + }, + "computedOptionalRequired": "computed", + "markdownDescription": "When the tag was created.", + "wire": { + "jsonPath": "createDate", + "sdkField": "CreateDate", + "sdkGoType": "*string", + "flatten": { + "func": "convert.PtrStringToFramework" + }, + "skipExpand": true + } + }, + { + "name": "modified_date", + "goField": "ModifiedDate", + "type": { + "kind": "string", + "constraints": { + "format": "date-time" + } + }, + "computedOptionalRequired": "computed", + "markdownDescription": "When the tag was last modified.", + "wire": { + "jsonPath": "modifiedDate", + "sdkField": "ModifiedDate", + "sdkGoType": "*time.Time", + "flatten": { + "func": "convert.PtrTimeToFramework" + }, + "skipExpand": true + } + }, + { + "name": "legacy_id", + "goField": "LegacyID", + "type": { + "kind": "float64" + }, + "computedOptionalRequired": "computed", + "markdownDescription": "The tag's identifier in the v6 API. Typed as a number because the specification declares it as one, although observed values are integral; probing will settle whether this should be an integer.", + "wire": { + "jsonPath": "legacyId", + "sdkField": "LegacyId", + "sdkGoType": "*float64", + "flatten": { + "func": "convert.PtrFloat64ToFramework" + }, + "skipExpand": true + } + }, + { + "name": "assignments", + "goField": "Assignments", + "type": { + "kind": "set_nested", + "nestedObject": { + "goTypeName": "TagAssignmentModel", + "sdkType": "models.Tags_API_Assignmentable", + "attrTypesVar": "tagAssignmentAttrTypes", + "objectTypeVar": "tagAssignmentObjectType", + "expandFunc": "expandTagAssignments", + "flattenFunc": "flattenTagAssignments", + "attributes": [ + { + "name": "id", + "goField": "ID", + "type": { + "kind": "string" + }, + "computedOptionalRequired": "required", + "markdownDescription": "The identifier of the object the tag is assigned to.", + "wire": { + "jsonPath": "id", + "sdkField": "Id", + "sdkGoType": "*string", + "expand": { + "func": "convert.FrameworkToPtrString" + }, + "flatten": { + "func": "convert.PtrStringToFramework" + } + } + }, + { + "name": "type", + "goField": "Type", + "type": { + "kind": "string" + }, + "computedOptionalRequired": "required", + "markdownDescription": "The kind of object assigned. Documented values are `test`, `v-agent`, `endpoint-test`, `dashboard` and `connected-devices-test`.", + "wire": { + "jsonPath": "type", + "sdkField": "TypeEscaped", + "sdkGoType": "*models.Tags_API_AssignmentType", + "expand": { + "func": "convert.FrameworkToKiotaEnum", + "typeArgs": [ + "models.Tags_API_AssignmentType" + ], + "extraArgs": [ + "models.ParseTags_API_AssignmentType" + ], + "returnsError": true + }, + "flatten": { + "func": "convert.PtrStringerToFramework" + } + } + } + ], + "constructorExpr": "models.NewTags_API_Assignment()" + } + }, + "computedOptionalRequired": "computed", + "markdownDescription": "Objects this tag is assigned to. A set rather than a list because the API does not preserve ordering.", + "wire": { + "jsonPath": "assignments", + "sdkField": "Assignments", + "sdkGoType": "[]models.Tags_API_Assignmentable", + "flatten": { + "func": "flattenTagAssignments", + "needsCtx": true, + "returnsError": true + }, + "skipExpand": true + } + }, + { + "name": "filters", + "goField": "Filters", + "type": { + "kind": "set_nested", + "nestedObject": { + "goTypeName": "TagFilterModel", + "sdkType": "models.Tags_API_TagFilterable", + "attrTypesVar": "tagFilterAttrTypes", + "objectTypeVar": "tagFilterObjectType", + "expandFunc": "expandTagFilters", + "flattenFunc": "flattenTagFilters", + "attributes": [ + { + "name": "key", + "goField": "Key", + "type": { + "kind": "string" + }, + "computedOptionalRequired": "required", + "markdownDescription": "The filter key used for matching.\n\n\nConditionally, when objectType is \"endpoint-agent\" and type is \"dynamic\": the field appeared in the read after 1 create(s), when objectType is \"endpoint-agent\" and type is \"dynamic\". Applied as a behaviour variant.\n", + "behaviour": { + "conditional": [ + { + "when": [ + { + "jsonPath": "objectType", + "equals": "endpoint-agent" + }, + { + "jsonPath": "type", + "equals": "dynamic" + } + ], + "behaviour": { + "returnedOnRead": true + } + } + ] + }, + "wire": { + "jsonPath": "key", + "sdkField": "Key", + "sdkGoType": "*string", + "expand": { + "func": "convert.FrameworkToPtrString" + }, + "flatten": { + "func": "convert.PtrStringToFramework" + } + } + }, + { + "name": "mode", + "goField": "Mode", + "type": { + "kind": "string" + }, + "computedOptionalRequired": "computed_optional", + "markdownDescription": "How the filter values are matched.\n\n\nConditionally, when objectType is \"endpoint-agent\" and type is \"dynamic\": the field appeared in the read after 1 create(s), when objectType is \"endpoint-agent\" and type is \"dynamic\". Applied as a behaviour variant.\n", + "behaviour": { + "conditional": [ + { + "when": [ + { + "jsonPath": "objectType", + "equals": "endpoint-agent" + }, + { + "jsonPath": "type", + "equals": "dynamic" + } + ], + "behaviour": { + "returnedOnRead": true + } + } + ] + }, + "wire": { + "jsonPath": "mode", + "sdkField": "Mode", + "sdkGoType": "*models.Tags_API_TagFilterMode", + "expand": { + "func": "convert.FrameworkToKiotaEnum", + "typeArgs": [ + "models.Tags_API_TagFilterMode" + ], + "extraArgs": [ + "models.ParseTags_API_TagFilterMode" + ], + "returnsError": true + }, + "flatten": { + "func": "convert.PtrStringerToFramework" + } + } + }, + { + "name": "scope", + "goField": "Scope", + "type": { + "kind": "string" + }, + "computedOptionalRequired": "computed_optional", + "markdownDescription": "The scope the filter applies within.\n\n\nConditionally, when objectType is \"endpoint-agent\" and type is \"dynamic\": the field appeared in the read after 1 create(s), when objectType is \"endpoint-agent\" and type is \"dynamic\". Applied as a behaviour variant.\n", + "behaviour": { + "conditional": [ + { + "when": [ + { + "jsonPath": "objectType", + "equals": "endpoint-agent" + }, + { + "jsonPath": "type", + "equals": "dynamic" + } + ], + "behaviour": { + "returnedOnRead": true + } + } + ] + }, + "wire": { + "jsonPath": "scope", + "sdkField": "Scope", + "sdkGoType": "*models.Tags_API_TagFilterScope", + "expand": { + "func": "convert.FrameworkToKiotaEnum", + "typeArgs": [ + "models.Tags_API_TagFilterScope" + ], + "extraArgs": [ + "models.ParseTags_API_TagFilterScope" + ], + "returnsError": true + }, + "flatten": { + "func": "convert.PtrStringerToFramework" + } + } + }, + { + "name": "values", + "goField": "Values", + "type": { + "kind": "set", + "elementType": { + "kind": "string" + } + }, + "computedOptionalRequired": "required", + "markdownDescription": "The values the filter matches against.\n\n\nConditionally, when objectType is \"endpoint-agent\" and type is \"dynamic\": the field appeared in the read after 1 create(s), when objectType is \"endpoint-agent\" and type is \"dynamic\". Applied as a behaviour variant.\n", + "behaviour": { + "conditional": [ + { + "when": [ + { + "jsonPath": "objectType", + "equals": "endpoint-agent" + }, + { + "jsonPath": "type", + "equals": "dynamic" + } + ], + "behaviour": { + "returnedOnRead": true + } + } + ] + }, + "wire": { + "jsonPath": "values", + "sdkField": "Values", + "sdkGoType": "[]string", + "expand": { + "func": "convert.FrameworkSetToStringSlice", + "needsCtx": true, + "returnsError": true + }, + "flatten": { + "func": "convert.StringSliceToFrameworkSet", + "needsCtx": true, + "returnsError": true + } + } + } + ], + "constructorExpr": "models.NewTags_API_TagFilter()" + } + }, + "computedOptionalRequired": "computed_optional", + "markdownDescription": "Filters that dynamically assign this tag to endpoint agents.\n\n\nConditionally, when objectType is \"endpoint-agent\" and type is \"dynamic\": a create omitting this field was refused with 400 and the error named the field, when objectType is \"endpoint-agent\" and type is \"dynamic\". Applied as a behaviour variant.\nConditionally, when objectType is \"endpoint-agent\" and type is \"dynamic\": the field appeared in the read after 1 create(s), when objectType is \"endpoint-agent\" and type is \"dynamic\". Applied as a behaviour variant.\n", + "behaviour": { + "conditional": [ + { + "when": [ + { + "jsonPath": "objectType", + "equals": "endpoint-agent" + }, + { + "jsonPath": "type", + "equals": "dynamic" + } + ], + "behaviour": { + "requiredByApi": true, + "returnedOnRead": true + } + } + ] + }, + "wire": { + "jsonPath": "filters", + "sdkField": "Filters", + "sdkGoType": "[]models.Tags_API_TagFilterable", + "expand": { + "func": "expandTagFilters", + "needsCtx": true, + "returnsError": true + }, + "flatten": { + "func": "flattenTagFilters", + "needsCtx": true, + "returnsError": true + } + } + } + ], + "markdownDescription": "Manages a ThousandEyes tag. Tags are key/value labels that can be assigned to tests, agents and dashboards." + }, + "binding": { + "service": { + "importPath": "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk/models", + "alias": "models", + "accessor": "r.client" + }, + "create": { + "style": "fluent", + "chain": [ + { + "method": "Tags" + }, + { + "method": "Post", + "args": [ + { + "kind": "ctx" + }, + { + "kind": "body" + }, + { + "kind": "literal", + "expr": "nil" + } + ] + } + ], + "return": "resultError", + "resultType": "models.Tags_API_Tagable", + "httpMethod": "POST", + "pathTemplate": "/tags", + "successCodes": [ + 201 + ] + }, + "read": { + "style": "fluent", + "chain": [ + { + "method": "Tags" + }, + { + "method": "ById", + "args": [ + { + "kind": "stateField", + "field": "ID" + } + ] + }, + { + "method": "Get", + "args": [ + { + "kind": "ctx" + }, + { + "kind": "literal", + "expr": "nil" + } + ] + } + ], + "return": "resultError", + "resultType": "models.Tags_API_Tagable", + "httpMethod": "GET", + "pathTemplate": "/tags/{id}", + "successCodes": [ + 200 + ] + }, + "update": { + "style": "fluent", + "chain": [ + { + "method": "Tags" + }, + { + "method": "ById", + "args": [ + { + "kind": "stateField", + "field": "ID" + } + ] + }, + { + "method": "Put", + "args": [ + { + "kind": "ctx" + }, + { + "kind": "body" + }, + { + "kind": "literal", + "expr": "nil" + } + ] + } + ], + "return": "resultError", + "resultType": "models.Tags_API_Tagable", + "httpMethod": "PUT", + "pathTemplate": "/tags/{id}", + "successCodes": [ + 200 + ] + }, + "delete": { + "style": "fluent", + "chain": [ + { + "method": "Tags" + }, + { + "method": "ById", + "args": [ + { + "kind": "stateField", + "field": "ID" + } + ] + }, + { + "method": "Delete", + "args": [ + { + "kind": "ctx" + }, + { + "kind": "literal", + "expr": "nil" + } + ] + } + ], + "return": "error", + "httpMethod": "DELETE", + "pathTemplate": "/tags/{id}", + "successCodes": [ + 204 + ] + }, + "id": { + "attribute": "id", + "goField": "ID", + "fromCreate": "created.GetId()", + "fromCreateIsPointer": true + }, + "body": { + "requestType": "models.Tags_API_TagInfo", + "responseType": "models.Tags_API_Tagable", + "constructorExpr": "models.NewTags_API_TagInfo()", + "accessStyle": "method" + } + }, + "policy": { + "updateStyle": "putFull", + "delete": { + "notFoundIsSuccess": true + } + }, + "import": { + "style": "passthroughId", + "attribute": "id", + "example": "thousandeyes_tag.example 1234" + }, + "identity": { + "attributes": [ + { + "name": "id", + "goField": "ID", + "kind": "string", + "requiredForImport": true, + "description": "The tag's unique identifier, which Terraform addresses it by.", + "fromAttribute": "id" + } + ], + "goTypeName": "TagResourceIdentity" + }, + "hooks": { + "modifyPlan": true, + "readBackPredicate": true + } + } + ] +} \ No newline at end of file diff --git a/blueprints/thousandeyes-kiota/tag.scenario.json b/blueprints/thousandeyes-kiota/tag.scenario.json new file mode 100644 index 00000000..7440d5f3 --- /dev/null +++ b/blueprints/thousandeyes-kiota/tag.scenario.json @@ -0,0 +1,78 @@ +{ + "fixtures": [ + { + "name": "test-tag", + "body": { + "key": "placeholder-replaced-with-the-stamped-name", + "value": "probe", + "objectType": "test", + "accessType": "all" + } + }, + { + "name": "dashboard-tag", + "body": { + "key": "placeholder-replaced-with-the-stamped-name", + "value": "probe-two", + "objectType": "dashboard", + "accessType": "all" + } + }, + { + "name": "endpoint-agent-tag", + "body": { + "key": "placeholder-replaced-with-the-stamped-name", + "value": "probe-three", + "objectType": "endpoint-agent", + "accessType": "all", + "type": "dynamic", + "matchType": "and", + "filters": [ + { + "key": "probe-filter-key", + "mode": "in", + "scope": "custom", + "values": [ + "probe-filter-value" + ] + } + ] + } + } + ], + "candidates": { + "key": [ + "candidate-key-one", + "candidate-key-two" + ], + "objectType": [ + "dashboard", + "endpoint-test" + ], + "icon": [ + "LABEL" + ], + "color": [ + "#A1B2C3", + "#D4E5F6" + ] + }, + "defaultInfluencers": [ + "objectType", + "type" + ], + "expansions": [ + "expand=assignments" + ], + "deny": [ + "assignments", + "assignments.id", + "assignments.type" + ], + "budget": { + "maxRequests": 340, + "maxCreates": 96, + "maxWallClockSeconds": 600, + "maxSweepSeconds": 120 + } +} diff --git a/cmd/tfpfgen/bindings.go b/cmd/tfpfgen/bindings.go index 31df903a..4b7b9792 100644 --- a/cmd/tfpfgen/bindings.go +++ b/cmd/tfpfgen/bindings.go @@ -162,6 +162,11 @@ func staticFacts(bp blueprint.Blueprint, module, out, check string) error { return err } + if bp.Provider.SDK.Dialect == blueprint.DialectKiotaFluent { + log.Printf("dialect %s derives no static facts: its models are pointer-typed behind "+ + "accessors with no encoder-level omitempty, so zero-value-unsendable cannot arise", + bp.Provider.SDK.Dialect) + } log.Printf("%d static fact(s) derived from the SDK's types", len(facts)) if out != "" { diff --git a/cmd/tfpfgen/blueprint_draft.go b/cmd/tfpfgen/blueprint_draft.go index b1c44d5c..bc3ebc67 100644 --- a/cmd/tfpfgen/blueprint_draft.go +++ b/cmd/tfpfgen/blueprint_draft.go @@ -2,6 +2,7 @@ package main import ( "encoding/json" + "flag" "fmt" "log" "os" @@ -16,7 +17,8 @@ import ( "github.com/deploymenttheory/terraform-plugin-framework-codegen/internal/snapshot" ) -const usageBlueprintDraft = "blueprint draft [-openapi-dir DIR] [-snapshot NAME] [-tag TAG] [-out DIR] [-dry-run]" +const usageBlueprintDraft = "blueprint draft [-openapi-dir DIR] [-snapshot NAME] [-tag TAG] " + + "[-sdk-dialect restyService|kiotaFluent] [-out DIR] [-dry-run]" func runBlueprintDraft(args []string) error { fs, _ := newFlagSet("blueprint draft", usageBlueprintDraft) @@ -38,12 +40,42 @@ func runBlueprintDraft(args []string) error { apiVersionDir = fs.String("api-version-dir", "v7", "version directory generated packages live under") scenarioDrafts = fs.String("scenario-drafts", "", "also scaffold a KEY.scenario.draft.json scenario worksheet per resource under this directory") + sdkDialect = fs.String("sdk-dialect", string(blueprint.DialectRestyService), + "binding shape to infer: restyService, or kiotaFluent for a kiota-generated SDK") + sdkModels = fs.String("sdk-models-package", "", + "import path of the kiota SDK's models package (required with -sdk-dialect kiotaFluent)") ) if err := parse(fs, args); err != nil { return err } + dialect := blueprint.SDKDialect(*sdkDialect) + switch dialect { + case blueprint.DialectRestyService, blueprint.DialectKiotaFluent: + default: + return usagef("-sdk-dialect %q is not restyService or kiotaFluent", *sdkDialect) + } + + if dialect == blueprint.DialectKiotaFluent { + if *sdkModels == "" { + return usagef("-sdk-models-package is required with -sdk-dialect kiotaFluent: " + + "it names where the generated models live") + } + // The resty knobs mean nothing to a fluent binding; a value somebody + // typed deserves a refusal, not silence. + var misused []string + fs.Visit(func(f *flag.Flag) { + if f.Name == "sdk-service-root" || f.Name == "sdk-accessor" { + misused = append(misused, "-"+f.Name) + } + }) + if len(misused) > 0 { + return usagef("%s do(es) not apply under -sdk-dialect kiotaFluent; a fluent "+ + "binding resolves from the client and the models package", strings.Join(misused, ", ")) + } + } + path, err := resolveOpenAPIPath(*openapiPath, *openapiDir, *snapshot) if err != nil { return err @@ -75,6 +107,8 @@ func runBlueprintDraft(args []string) error { SDKServiceRoot: *sdkRoot, SDKAccessorPrefix: *accessor, APIVersionDir: *apiVersionDir, + SDKDialect: dialect, + SDKModelsImport: *sdkModels, } return inferAll(doc, candidates, opts, *out, *scenarioDrafts) diff --git a/cmd/tfpfgen/commands.go b/cmd/tfpfgen/commands.go index 056beba6..74e4621c 100644 --- a/cmd/tfpfgen/commands.go +++ b/cmd/tfpfgen/commands.go @@ -34,6 +34,12 @@ var commands = []command{ usage: "openapi [flags]", run: runOpenAPI, }, + { + name: "sdk", + summary: "generate a Go SDK from a pinned OpenAPI snapshot", + usage: "sdk [flags]", + run: runSDK, + }, { name: "blueprint", summary: "draft, merge, validate, diff or list blueprints", diff --git a/cmd/tfpfgen/docs_test.go b/cmd/tfpfgen/docs_test.go index a213699c..8d90fe95 100644 --- a/cmd/tfpfgen/docs_test.go +++ b/cmd/tfpfgen/docs_test.go @@ -25,6 +25,7 @@ func TestUnit_CLI_DocsMatchTheBinary(t *testing.T) { groups := map[string][]command{ "openapi": openapiVerbs, + "sdk": sdkVerbs, "blueprint": blueprintVerbs, "probe": probeVerbs, "provider": providerVerbs, @@ -66,8 +67,10 @@ func TestUnit_CLI_DocsMatchTheBinary(t *testing.T) { // Retired spellings must not resurface. Their presence means an edit // described the old surface, which is exactly the drift this test exists // to catch. + // "probe -mode" rather than "-mode": the retired spelling was probe's mode + // flag; sdk generate legitimately carries a -mode of its own. for _, banned := range []string{ - "tfpluginframeworkgen", "-mode ", "-output-dir", "-github-summary", + "tfpluginframeworkgen", "probe -mode", "-output-dir", "-github-summary", "-accept-conflicts", "-facts-out", "-facts-check", "-no-rehearse", "probe.plan", "probe-evidence", "interop-specs", "openapi-specs", "--allow-mutations", diff --git a/cmd/tfpfgen/generate.go b/cmd/tfpfgen/generate.go index 62e23a71..43d16367 100644 --- a/cmd/tfpfgen/generate.go +++ b/cmd/tfpfgen/generate.go @@ -145,7 +145,7 @@ func runProviderGenerate(args []string) error { return nil } - return postcheck(o.out) + return postcheck(o.out, bp) } // postcheck holds generated output to the tools that gate it. @@ -157,7 +157,7 @@ func runProviderGenerate(args []string) error { // what it writes, so a diff here is a bug worth seeing, and fmt writing the fix keeps // the tree correct either way. terraform validate stays a CI job: it needs the // provider built into a dev-override, which is workflow plumbing rather than generation. -func postcheck(out string) error { +func postcheck(out string, bp blueprint.Blueprint) error { // A scratch generation -- a test's temp dir, a -resource inspection -- has no module // to compile or docs to regenerate, and holding it to the battery would gate // the wrong thing. The pilot and any real provider root carry go.mod. @@ -166,6 +166,13 @@ func postcheck(out string) error { return nil } + // Cheapest first, and offline: the blueprint's declared SDK requirements + // must be present before compiling can mean anything. + log.Printf("postcheck: go.mod declares the blueprint's SDK requirements") + if err := generate.AssertGoMod(out, bp); err != nil { + return fmt.Errorf("postcheck: %w", err) + } + log.Printf("postcheck: go build ./...") build := exec.Command("go", "build", "./...") build.Dir = out diff --git a/cmd/tfpfgen/main_test.go b/cmd/tfpfgen/main_test.go index 82b16229..654368de 100644 --- a/cmd/tfpfgen/main_test.go +++ b/cmd/tfpfgen/main_test.go @@ -75,6 +75,9 @@ var builtCommands = map[string]bool{ // openapi fetch pins; with -url omitted it re-fetches from the latest // snapshot's own recorded source, which is the weekly-refresh loop. "openapi fetch": true, + // sdk generate shells out to kiota; the verb itself is built, and its + // no-argument invocation fails on required flags, not errNotImplemented. + "sdk generate": true, // blueprint draft infers resource blueprints from a pinned snapshot. Data // sources, actions and the provider block are still hand-authored, and every // skip is printed as a note rather than being silent. @@ -109,6 +112,7 @@ func TestUnit_CLI_Dispatch_ImplementationClaimsAreTrue(t *testing.T) { groups := map[string][]command{ "openapi": openapiVerbs, + "sdk": sdkVerbs, "blueprint": blueprintVerbs, "probe": probeVerbs, "provider": providerVerbs, diff --git a/cmd/tfpfgen/sdk.go b/cmd/tfpfgen/sdk.go new file mode 100644 index 00000000..7db4ee1f --- /dev/null +++ b/cmd/tfpfgen/sdk.go @@ -0,0 +1,395 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/deploymenttheory/terraform-plugin-framework-codegen/internal/kiota" + "github.com/deploymenttheory/terraform-plugin-framework-codegen/internal/snapshot" +) + +const usageSDKGenerate = "sdk generate [-openapi-dir DIR] [-snapshot NAME] -out DIR " + + "[-mode embed|external] [-module PATH] [-client-name NAME] [-check] [-dry-run]" + +// SDK modes. Embed is the default: one module, one drift check, no publishing +// loop between the SDK and the provider that consumes it. +const ( + sdkModeEmbed = "embed" + sdkModeExternal = "external" +) + +// sdkVerbs is the sdk group's verb table. +var sdkVerbs = []command{ + { + name: "generate", + summary: "generate a Go SDK from a pinned OpenAPI snapshot with kiota; -check fails on drift", + usage: usageSDKGenerate, + run: runSDKGenerate, + }, +} + +func runSDK(args []string) error { + return runVerbs("sdk", sdkVerbs, "", args) +} + +type sdkGenerateOptions struct { + openapiDir string + snapshotName string + out string + mode string + module string + clientName string + include string + exclude string + clean bool + check bool + dryRun bool +} + +func runSDKGenerate(args []string) error { + fs, _ := newFlagSet("sdk generate", usageSDKGenerate) + + var o sdkGenerateOptions + fs.StringVar(&o.openapiDir, "openapi-dir", "openapi/thousandeyes", "directory holding pinned OpenAPI snapshots") + fs.StringVar(&o.snapshotName, "snapshot", "", "snapshot to read (default: the newest)") + fs.StringVar(&o.out, "out", "", "SDK root to write into (required)") + fs.StringVar(&o.mode, "mode", sdkModeEmbed, + "embed generates under the enclosing module; external emits a standalone tree with its own go.mod") + fs.StringVar(&o.module, "module", "", + "Go import path of the SDK root; derived from the enclosing go.mod under embed, required under external") + fs.StringVar(&o.clientName, "client-name", "ApiClient", "root client type name") + fs.StringVar(&o.include, "include", "", "restrict to these API path globs, e.g. /tags/**; comma-separate several") + fs.StringVar(&o.exclude, "exclude", "", "drop these API path globs; comma-separate several") + fs.BoolVar(&o.clean, "clean", false, "delete files a previous generation produced that this one does not") + fs.BoolVar(&o.check, "check", false, + "write nothing and exit 1 if the committed SDK has drifted from the snapshot or the pinned kiota version") + fs.BoolVar(&o.dryRun, "dry-run", false, "print the resolved snapshot and invocation, run nothing") + + if err := parse(fs, args); err != nil { + return err + } + + if o.out == "" { + return usagef("-out is required: it names the SDK root") + } + switch o.mode { + case sdkModeEmbed, sdkModeExternal: + default: + return usagef("-mode %q is not embed or external", o.mode) + } + if o.mode == sdkModeExternal && o.module == "" { + return usagef("-module is required under -mode external: it becomes the SDK's own module path") + } + + snap, err := snapshot.Find(o.openapiDir, o.snapshotName) + if err != nil { + return err + } + // A snapshot is meant to be immutable; generating an SDK from an edited one + // would make the committed tree unreproducible from anything committed. + if err := snap.Verify(); err != nil { + return err + } + + module := o.module + if module == "" { + module, err = embedModulePath(o.out) + if err != nil { + return err + } + } + + gen := kiota.GenerateOptions{ + Description: snap.SpecPath(), + Out: o.out, + Module: module, + ClientName: o.clientName, + Include: splitGlobs(o.include), + Exclude: splitGlobs(o.exclude), + Clean: o.clean, + } + + if o.dryRun { + log.Printf("snapshot: %s", snap.SpecPath()) + log.Printf("would run: kiota generate -l go -d %s -o %s -n %s -c %s --exclude-backward-compatible", + gen.Description, gen.Out, gen.Module, gen.ClientName) + log.Printf("dry run; nothing was generated") + return nil + } + + // The version gate: a committed tree names the kiota that produced it, and + // running any other version rewrites the whole tree as an inexplicable diff. + lock, hadLock, err := kiota.ReadLock(o.out) + if err != nil { + return err + } + if hadLock { + if err := kiota.Gate(lock); err != nil { + return err + } + } else if _, err := kiota.BinaryVersion(); err != nil { + return err + } + + if o.check { + if !hadLock { + return fmt.Errorf("%s has no %s to check against; generate the SDK first", o.out, kiota.LockFileName) + } + return checkSDKDrift(gen, o.out) + } + + log.Printf("generating %s from %s", o.out, snap.SpecPath()) + if err := kiota.Generate(gen); err != nil { + return err + } + + if o.mode == sdkModeExternal { + if err := ensureSDKModule(o.out, module); err != nil { + return err + } + } + + return sdkPostcheck(o.out) +} + +// embedModulePath derives kiota's -n for an embedded SDK: the enclosing +// module's path joined with the SDK root's relative directory -- kiota's own +// rule for generating inside an existing module. +func embedModulePath(out string) (string, error) { + abs, err := filepath.Abs(out) + if err != nil { + return "", err + } + + for dir := abs; ; dir = filepath.Dir(dir) { + data, err := os.ReadFile(filepath.Join(dir, "go.mod")) //nolint:gosec // fixed name on an ancestor walk + if err == nil { + module := modulePathOf(data) + if module == "" { + return "", fmt.Errorf("%s/go.mod declares no module path", dir) + } + rel, err := filepath.Rel(dir, abs) + if err != nil { + return "", err + } + if rel == "." { + return module, nil + } + return module + "/" + filepath.ToSlash(rel), nil + } + if !os.IsNotExist(err) { + return "", err + } + if parent := filepath.Dir(dir); parent == dir { + return "", usagef("-out %s is not inside a Go module and -mode is embed; "+ + "create the provider module first, or pass -mode external with -module", out) + } + } +} + +// modulePathOf reads the module directive out of go.mod bytes. +func modulePathOf(data []byte) string { + for _, line := range strings.Split(string(data), "\n") { + if rest, ok := strings.CutPrefix(strings.TrimSpace(line), "module "); ok { + return strings.TrimSpace(rest) + } + } + return "" +} + +func splitGlobs(s string) []string { + if s == "" { + return nil + } + var out []string + for _, g := range strings.Split(s, ",") { + if g = strings.TrimSpace(g); g != "" { + out = append(out, g) + } + } + return out +} + +// checkSDKDrift regenerates into scratch and diffs against the committed tree. +// +// Byte comparison rather than lock comparison, deliberately: the lock proves +// which inputs produced a tree, but only regeneration proves the tree still +// follows from the pinned snapshot -- including after a kiota version bump +// that slipped past review, or a hand edit to a generated file. +func checkSDKDrift(gen kiota.GenerateOptions, committed string) error { + scratch, err := os.MkdirTemp("", "tfpfgen-sdk-check-*") + if err != nil { + return err + } + defer func() { _ = os.RemoveAll(scratch) }() + + gen.Out = scratch + gen.Clean = false + if err := kiota.Generate(gen); err != nil { + return err + } + + drifted, err := diffTrees(scratch, committed) + if err != nil { + return err + } + if len(drifted) == 0 { + log.Printf("โœ… %s matches a fresh generation from the pinned snapshot", committed) + return nil + } + + for _, p := range drifted { + fmt.Fprintf(os.Stderr, " drifted %s\n", p) + } + fmt.Fprintf(os.Stderr, "::error::the committed SDK is out of date; run sdk generate and commit the result\n") + return fmt.Errorf("%d file(s) differ from a fresh generation", len(drifted)) +} + +// diffTrees compares fresh against committed, ignoring the committed tree's +// go.mod/go.sum (creation-time scaffold kiota does not produce) and anything +// that is not a regular file. +func diffTrees(fresh, committed string) ([]string, error) { + var drifted []string + + seen := map[string]bool{} + err := filepath.Walk(fresh, func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { + return err + } + rel, err := filepath.Rel(fresh, path) + if err != nil { + return err + } + seen[rel] = true + + want, err := os.ReadFile(path) //nolint:gosec // scratch tree this run wrote + if err != nil { + return err + } + have, err := os.ReadFile(filepath.Join(committed, rel)) //nolint:gosec // operator-supplied root + switch { + case os.IsNotExist(err): + drifted = append(drifted, rel+" (missing)") + case err != nil: + return err + case rel == kiota.LockFileName: + // The lock records the description's path relative to the output + // directory, so a scratch regeneration always differs on that one + // field; every other field must still match byte-for-byte. + if !lockEquivalent(want, have) { + drifted = append(drifted, rel) + } + case !bytes.Equal(want, have): + drifted = append(drifted, rel) + } + return nil + }) + if err != nil { + return nil, err + } + + // Files the committed tree carries that a fresh generation does not. + err = filepath.Walk(committed, func(path string, info os.FileInfo, err error) error { + if err != nil || info.IsDir() { + return err + } + rel, relErr := filepath.Rel(committed, path) + if relErr != nil { + return relErr + } + if seen[rel] || rel == "go.mod" || rel == "go.sum" { + return nil + } + drifted = append(drifted, rel+" (orphaned)") + return nil + }) + return drifted, err +} + +// lockEquivalent compares two kiota lock files ignoring descriptionLocation, +// the one field that legitimately depends on where the output directory sits. +func lockEquivalent(a, b []byte) bool { + var la, lb map[string]any + if json.Unmarshal(a, &la) != nil || json.Unmarshal(b, &lb) != nil { + return bytes.Equal(a, b) + } + delete(la, "descriptionLocation") + delete(lb, "descriptionLocation") + ja, errA := json.Marshal(la) + jb, errB := json.Marshal(lb) + return errA == nil && errB == nil && bytes.Equal(ja, jb) +} + +// ensureSDKModule writes a minimal go.mod for an external SDK tree and lets +// `go mod tidy` compute the requirements -- a creation step, run once; the +// file is the operator's afterwards, exactly like a scaffold. +func ensureSDKModule(out, module string) error { + modPath := filepath.Join(out, "go.mod") + if _, err := os.Stat(modPath); err == nil { + return nil + } + + goVersion, err := toolchainGoVersion() + if err != nil { + return err + } + + content := fmt.Sprintf("module %s\n\ngo %s\n", module, goVersion) + if err := os.WriteFile(modPath, []byte(content), 0o600); err != nil { + return err + } + + log.Printf("go mod tidy (resolving the kiota runtime modules)") + tidy := exec.Command("go", "mod", "tidy") + tidy.Dir = out + if msg, err := tidy.CombinedOutput(); err != nil { + return fmt.Errorf("go mod tidy in %s: %v\n%s", out, err, msg) + } + return nil +} + +// toolchainGoVersion reads the language version out of the nearest enclosing +// go.mod, so the SDK module never claims a newer Go than the tree it is +// generated from builds with. +func toolchainGoVersion() (string, error) { + dir, err := filepath.Abs(".") + if err != nil { + return "", err + } + for ; ; dir = filepath.Dir(dir) { + data, err := os.ReadFile(filepath.Join(dir, "go.mod")) //nolint:gosec // fixed name on an ancestor walk + if err == nil { + for _, line := range strings.Split(string(data), "\n") { + if rest, ok := strings.CutPrefix(strings.TrimSpace(line), "go "); ok { + return strings.TrimSpace(rest), nil + } + } + return "", fmt.Errorf("%s/go.mod carries no go directive", dir) + } + if !os.IsNotExist(err) { + return "", err + } + if parent := filepath.Dir(dir); parent == dir { + return "", fmt.Errorf("no go.mod found above the working directory for the go directive") + } + } +} + +// sdkPostcheck compiles the generated tree; nothing else matters if it does +// not build. +func sdkPostcheck(out string) error { + log.Printf("postcheck: go build ./...") + build := exec.Command("go", "build", "./...") + build.Dir = out + if msg, err := build.CombinedOutput(); err != nil { + return fmt.Errorf("postcheck: the generated SDK does not compile:\n%s", msg) + } + log.Printf("โœ… SDK generated at %s", out) + return nil +} diff --git a/cmd/tfpfgen/sdk_test.go b/cmd/tfpfgen/sdk_test.go new file mode 100644 index 00000000..fbaf7332 --- /dev/null +++ b/cmd/tfpfgen/sdk_test.go @@ -0,0 +1,251 @@ +package main + +import ( + "crypto/sha256" + "encoding/hex" + "os" + "path/filepath" + "runtime" + "strings" + "testing" + "time" + + "github.com/deploymenttheory/terraform-plugin-framework-codegen/internal/snapshot" +) + +// fakeKiotaVersion is what the stub binary reports and stamps into its lock. +const fakeKiotaVersion = "1.99.0" + +// fakeKiota puts a stub kiota first on PATH. The stub is deterministic: given +// the same arguments it writes the same tree, which is exactly the property +// the real generator has and the -check path depends on. +func fakeKiota(t *testing.T) { + t.Helper() + if runtime.GOOS == "windows" { + t.Skip("the stub kiota is a shell script") + } + + dir := t.TempDir() + script := `#!/bin/sh +case "$1" in +--version) + echo "` + fakeKiotaVersion + `+stub" + ;; +info) + echo '{"dependencies":[{"name":"github.com/microsoft/kiota-bundle-go","version":"v1.4.3","dependencyType":"bundle"}]}' + ;; +generate) + out=""; ns=""; class=""; desc="" + prev="" + for a in "$@"; do + case "$prev" in + --output) out="$a" ;; + --namespace-name) ns="$a" ;; + --class-name) class="$a" ;; + --openapi) desc="$a" ;; + esac + prev="$a" + done + mkdir -p "$out/models" + base=$(basename "$ns") + cat > "$out/client.go" < "$out/models/model.go" < "$out/kiota-lock.json" <able interfaces, bodies are built by constructor and setter, + // and the chain starts at the bare client. + sdkPkg = "models" + reqT := kiotaName(requestType) + respT := kiotaName(responseType) + + body := blueprint.BodyModels{ + RequestType: "models." + reqT, + ResponseType: "models." + respT + "able", + ConstructorExpr: "models.New" + reqT + "()", + AccessStyle: blueprint.AccessMethod, + } + if updateType := d.updateBodyTypeName(c); updateType != "" && updateType != requestType { + upT := kiotaName(updateType) + body.UpdateRequestType = "models." + upT + body.UpdateConstructorExpr = "models.New" + upT + "()" + } - // An update whose request schema is its own named type is a split body, and - // pretending update sends the create type would fail the bindings check on the - // first divergent field. Inferred here so curation starts from the truth; the - // emitter reuses one assignment list against both types, which sdkbind proves - // safe field by field. - if updateType := d.updateBodyTypeName(c); updateType != "" && updateType != requestType { - body.UpdateRequestType = sdkPkg + "." + updateType - body.UpdateConstructorExpr = "&" + sdkPkg + "." + updateType + "{}" - } + r.Binding = blueprint.ResourceBinding{ + Service: blueprint.ServiceRef{ + ImportPath: strings.TrimSuffix(opts.SDKModelsImport, "/"), + Alias: "models", + Accessor: "r.client", + }, + Body: body, + ID: blueprint.IDBinding{ + Attribute: "id", GoField: "ID", + FromCreate: "created.GetId()", FromCreateIsPointer: true, + }, + } - r.Binding = blueprint.ResourceBinding{ - Service: blueprint.ServiceRef{ - ImportPath: strings.TrimRight(opts.SDKServiceRoot, "/") + "/" + sdkPkg, - TypeName: service, - Accessor: strings.TrimRight(opts.SDKAccessorPrefix, ".") + "." + service, - }, - Body: body, - ID: blueprint.IDBinding{ - Attribute: "id", GoField: "ID", - FromCreate: "created.ID", FromCreateIsPointer: true, - }, - } + bindOperationsKiota(&r, c, body.ResponseType) + } else { + body := blueprint.BodyModels{ + RequestType: sdkPkg + "." + requestType, + ResponseType: sdkPkg + "." + responseType, + ConstructorExpr: "&" + sdkPkg + "." + requestType + "{}", + AccessStyle: blueprint.AccessStructField, + } + + // An update whose request schema is its own named type is a split body, and + // pretending update sends the create type would fail the bindings check on the + // first divergent field. Inferred here so curation starts from the truth; the + // emitter reuses one assignment list against both types, which sdkbind proves + // safe field by field. + if updateType := d.updateBodyTypeName(c); updateType != "" && updateType != requestType { + body.UpdateRequestType = sdkPkg + "." + updateType + body.UpdateConstructorExpr = "&" + sdkPkg + "." + updateType + "{}" + } - bindOperations(&r, c, sdkPkg+"."+responseType) + r.Binding = blueprint.ResourceBinding{ + Service: blueprint.ServiceRef{ + ImportPath: strings.TrimRight(opts.SDKServiceRoot, "/") + "/" + sdkPkg, + TypeName: service, + Accessor: strings.TrimRight(opts.SDKAccessorPrefix, ".") + "." + service, + }, + Body: body, + ID: blueprint.IDBinding{ + Attribute: "id", GoField: "ID", + FromCreate: "created.ID", FromCreateIsPointer: true, + }, + } + + bindOperations(&r, c, sdkPkg+"."+responseType) + } - attrs, attrNotes := d.attributes(c, sdkPkg) + attrs, attrNotes := d.attributes(c, sdkPkg, opts.SDKDialect) r.Schema.Attributes = attrs notes = append(notes, attrNotes...) @@ -237,7 +281,7 @@ func bindOperations(r *blueprint.Resource, c Candidate, resultType string) { // A field in both is configurable; one only in the response is computed. That // merge is the whole of presence inference, and it is why both schemas are read // rather than just one. -func (d *Document) attributes(c Candidate, sdkPkg string) ([]blueprint.Attribute, []Caveat) { +func (d *Document) attributes(c Candidate, sdkPkg string, dialect blueprint.SDKDialect) ([]blueprint.Attribute, []Caveat) { writable := map[string]Field{} for _, f := range Fields(d.requestSchema(c)) { writable[f.Name] = f @@ -260,7 +304,7 @@ func (d *Document) attributes(c Candidate, sdkPkg string) ([]blueprint.Attribute } sort.Strings(names) - ctx := newInferCtx(c.Key, sdkPkg) + ctx := newInferCtx(c.Key, sdkPkg, dialect) var out []blueprint.Attribute @@ -377,6 +421,14 @@ func (ctx *inferCtx) attributeOf( ) (blueprint.Attribute, string) { goField := namingOpts.GoFieldName(f.Name) + // The wire join key is the SDK's spelling of the field, which differs by + // generator: our models follow Go initialism conventions, kiota's accessors + // are plain word-capitalisation plus keyword mangling. + sdkField := goField + if ctx.dialect == blueprint.DialectKiotaFluent { + sdkField = kiotaAccessorBase(f.Name) + } + a := blueprint.Attribute{ Name: naming.TerraformName(f.Name), GoField: goField, @@ -427,16 +479,23 @@ func (ctx *inferCtx) attributeOf( a.Type.NestedObject = n // The SDK holds a collection of objects as a slice and a single object behind a - // pointer. That distinction decides the generated helper's signature, so it is - // recorded rather than left for the emitter to guess from the kind. + // pointer -- except a setter-based SDK, whose builders traffic in bare + // interfaces. The distinction decides the generated helper's signature, + // so it is recorded rather than left for the emitter to guess. sdkGoType := "*" + n.SDKType if f.Kind.IsNestedCollection() { sdkGoType = "[]" + n.SDKType } + if ctx.dialect == blueprint.DialectKiotaFluent { + sdkGoType = n.SDKType + if f.Kind.IsNestedCollection() { + sdkGoType = "[]" + n.SDKType + } + } a.Wire = blueprint.WireBinding{ JSONPath: f.Name, - SDKField: goField, + SDKField: sdkField, SDKGoType: sdkGoType, Flatten: &blueprint.ConvertCall{ Func: n.FlattenFunc, NeedsCtx: true, ReturnsError: true, @@ -454,11 +513,11 @@ func (ctx *inferCtx) attributeOf( return a, "" } - sdkType, convertFlatten, convertExpand := conversionsFor(f, ctx.sdkPkg) + sdkType, convertFlatten, convertExpand := conversionsFor(f, ctx.sdkPkg, ctx.dialect) a.Wire = blueprint.WireBinding{ JSONPath: f.Name, - SDKField: goField, + SDKField: sdkField, SDKGoType: sdkType, Flatten: convertFlatten, } @@ -497,8 +556,22 @@ func presenceOf(f Field, inWrite bool) blueprint.ComputedOptionalRequired { func conversionsFor( f Field, sdkPkg string, + dialect blueprint.SDKDialect, ) (sdkType string, flatten, expand *blueprint.ConvertCall) { if f.IsEnum() { + if dialect == blueprint.DialectKiotaFluent { + // A kiota enum is an int-backed named type behind a pointer, paired + // with a Parse function; the attribute stays a string driven by the + // OpenAPI members, and the parse failure a validator missed becomes + // a diagnostic rather than a silent nil. + named := "models." + kiotaName(f.EnumTypeName) + return "*" + named, + &blueprint.ConvertCall{Func: "convert.KiotaEnumToFramework"}, + &blueprint.ConvertCall{ + Func: "convert.FrameworkToKiotaEnum", TypeArgs: []string{named}, + ExtraArgs: []string{"models.Parse" + kiotaName(f.EnumTypeName)}, ReturnsError: true, + } + } // Generated SDKs hold enumerations by value as a named string type. named := sdkPkg + "." + namingOpts.GoTypeName(f.EnumTypeName) return named, @@ -506,6 +579,27 @@ func conversionsFor( &blueprint.ConvertCall{Func: "convert.FrameworkToEnum", TypeArgs: []string{named}} } + // Kiota reads integer formats literally: a plain integer is *int32 and only + // format int64 widens, where the resty dialect holds every number as + // *float64 or *int64. The attribute kind stays framework-facing; the + // converters bridge the width. + if dialect == blueprint.DialectKiotaFluent { + switch f.Kind { + case blueprint.KindInt64: + if f.Format != "int64" { + return "*int32", + &blueprint.ConvertCall{Func: "convert.PtrInt32ToFramework"}, + &blueprint.ConvertCall{Func: "convert.FrameworkToPtrInt32"} + } + case blueprint.KindFloat64, blueprint.KindNumber: + if f.Format == "float" { + return "*float32", + &blueprint.ConvertCall{Func: "convert.PtrFloat32ToFramework"}, + &blueprint.ConvertCall{Func: "convert.FrameworkToPtrFloat32"} + } + } + } + switch f.Kind { case blueprint.KindString: return "*string", diff --git a/internal/openapi/kiota.go b/internal/openapi/kiota.go new file mode 100644 index 00000000..815e6cd1 --- /dev/null +++ b/internal/openapi/kiota.go @@ -0,0 +1,143 @@ +package openapi + +import ( + "strings" + + "github.com/deploymenttheory/terraform-plugin-framework-codegen/internal/blueprint" +) + +// This file is the kiota-shaped half of inference: chain synthesis from path +// templates and the accessor naming a kiota-generated Go SDK actually uses. +// +// The names are derived from the OpenAPI document, not by parsing the +// generated SDK -- inference deliberately runs from the pinned snapshot alone, +// preserving the draft-then-curate pipeline order. A wrong guess here is not +// silent: `bindings check` resolves every name against the real SDK and its +// did-you-mean answers with the true spelling. + +// goReservedWords are the identifiers kiota mangles with an "Escaped" suffix: +// Go keywords plus the predeclared names ("error" is the live case -- kiota +// generates GetErrorEscaped). +var goReservedWords = map[string]bool{ + "break": true, "case": true, "chan": true, "const": true, "continue": true, + "default": true, "defer": true, "else": true, "fallthrough": true, "for": true, + "func": true, "go": true, "goto": true, "if": true, "import": true, + "interface": true, "map": true, "package": true, "range": true, "return": true, + "select": true, "struct": true, "switch": true, "type": true, "var": true, + "any": true, "bool": true, "byte": true, "comparable": true, "complex64": true, + "complex128": true, "error": true, "float32": true, "float64": true, + "int": true, "int8": true, "int16": true, "int32": true, "int64": true, + "rune": true, "string": true, "uint": true, "uint8": true, "uint16": true, + "uint32": true, "uint64": true, "uintptr": true, "true": true, "false": true, + "iota": true, "nil": true, "append": true, "cap": true, "clear": true, + "close": true, "complex": true, "copy": true, "delete": true, "imag": true, + "len": true, "make": true, "max": true, "min": true, "new": true, + "panic": true, "print": true, "println": true, "real": true, "recover": true, +} + +// kiotaName renders a property or schema name the way kiota's Go generator +// does: each word's first letter upper-cased and the rest kept -- no initialism +// conventions, so accountGroupId becomes AccountGroupId, never AccountGroupID. +func kiotaName(name string) string { + var b strings.Builder + upperNext := true + for _, r := range name { + switch { + case r == '_' || r == '-' || r == '.' || r == ' ': + upperNext = true + case upperNext: + b.WriteString(strings.ToUpper(string(r))) + upperNext = false + default: + b.WriteRune(r) + } + } + return b.String() +} + +// kiotaAccessorBase is the Get/Set base for one JSON property, including the +// keyword mangling: a property named "error" is reached as GetErrorEscaped. +func kiotaAccessorBase(jsonName string) string { + base := kiotaName(jsonName) + if goReservedWords[strings.ToLower(jsonName)] { + base += "Escaped" + } + return base +} + +// kiotaChain synthesises the request-builder chain for one operation from its +// path template: a literal segment is a builder method, a parameter segment a +// typed indexer, and the HTTP verb the final segment. +// +// /tags/{tagId} + GET -> Tags().ByTagId(id).Get(ctx, nil) +func kiotaChain(pathTemplate, verb string, verbArgs []blueprint.Argument) []blueprint.ChainSegment { + var chain []blueprint.ChainSegment + + idArg := blueprint.Argument{Kind: blueprint.ArgStateField, Field: "ID"} + + for _, segment := range strings.Split(strings.Trim(pathTemplate, "/"), "/") { + if segment == "" { + continue + } + if strings.HasPrefix(segment, "{") && strings.HasSuffix(segment, "}") { + param := strings.Trim(segment, "{}") + chain = append(chain, blueprint.ChainSegment{ + Method: "By" + kiotaName(param), + Args: []blueprint.Argument{idArg}, + }) + continue + } + chain = append(chain, blueprint.ChainSegment{Method: kiotaName(segment)}) + } + + return append(chain, blueprint.ChainSegment{Method: verb, Args: verbArgs}) +} + +// bindOperationsKiota is bindOperations for the fluent dialect: the call is a +// chain, the identifier rides mid-chain, and the trailing argument is the +// per-verb request configuration, nil until somebody curates one. +func bindOperationsKiota(r *blueprint.Resource, c Candidate, responseType string) { + ctxArg := blueprint.Argument{Kind: blueprint.ArgContext} + bodyArg := blueprint.Argument{Kind: blueprint.ArgBody} + nilCfg := blueprint.Argument{Kind: blueprint.ArgLiteral, Expr: "nil"} + + if c.Create != nil { + r.Binding.Create = &blueprint.Operation{ + Style: blueprint.CallStyleFluent, + Chain: kiotaChain(c.Create.Path, "Post", + []blueprint.Argument{ctxArg, bodyArg, nilCfg}), + Return: blueprint.ReturnResultError, ResultType: responseType, + HTTPMethod: c.Create.Method, PathTemplate: c.Create.Path, + } + } + if c.Read != nil { + r.Binding.Read = &blueprint.Operation{ + Style: blueprint.CallStyleFluent, + Chain: kiotaChain(c.Read.Path, "Get", + []blueprint.Argument{ctxArg, nilCfg}), + Return: blueprint.ReturnResultError, ResultType: responseType, + HTTPMethod: c.Read.Method, PathTemplate: c.Read.Path, + } + } + if c.Update != nil { + verb := kiotaName(strings.ToLower(c.Update.Method)) + r.Binding.Update = &blueprint.Operation{ + Style: blueprint.CallStyleFluent, + Chain: kiotaChain(c.Update.Path, verb, + []blueprint.Argument{ctxArg, bodyArg, nilCfg}), + Return: blueprint.ReturnResultError, ResultType: responseType, + HTTPMethod: c.Update.Method, PathTemplate: c.Update.Path, + } + } + if c.Delete != nil { + // A kiota delete returns error alone; there is no transport value and + // no body to discard. + r.Binding.Delete = &blueprint.Operation{ + Style: blueprint.CallStyleFluent, + Chain: kiotaChain(c.Delete.Path, "Delete", + []blueprint.Argument{ctxArg, nilCfg}), + Return: blueprint.ReturnError, + HTTPMethod: c.Delete.Method, PathTemplate: c.Delete.Path, + } + } +} diff --git a/internal/openapi/kiota_test.go b/internal/openapi/kiota_test.go new file mode 100644 index 00000000..9f189f29 --- /dev/null +++ b/internal/openapi/kiota_test.go @@ -0,0 +1,100 @@ +package openapi + +import ( + "testing" + + "github.com/deploymenttheory/terraform-plugin-framework-codegen/internal/blueprint" +) + +func TestUnit_OpenAPI_KiotaNames(t *testing.T) { + t.Parallel() + + for in, want := range map[string]string{ + "accountGroupId": "AccountGroupId", // no initialism conventions, unlike our own models + "tag_value": "TagValue", + "name": "Name", + "match-type": "MatchType", + } { + if got := kiotaName(in); got != want { + t.Errorf("kiotaName(%q) = %q, want %q", in, got, want) + } + } + + // The keyword mangling: kiota reaches a property named "error" through + // GetErrorEscaped, and "type" through GetTypeEscaped. + if got := kiotaAccessorBase("error"); got != "ErrorEscaped" { + t.Errorf("kiotaAccessorBase(error) = %q", got) + } + if got := kiotaAccessorBase("type"); got != "TypeEscaped" { + t.Errorf("kiotaAccessorBase(type) = %q", got) + } + if got := kiotaAccessorBase("name"); got != "Name" { + t.Errorf("kiotaAccessorBase(name) = %q", got) + } +} + +func TestUnit_OpenAPI_KiotaChainFromPathTemplate(t *testing.T) { + t.Parallel() + + ctx := blueprint.Argument{Kind: blueprint.ArgContext} + nilCfg := blueprint.Argument{Kind: blueprint.ArgLiteral, Expr: "nil"} + + chain := kiotaChain("/tags/{tagId}", "Get", []blueprint.Argument{ctx, nilCfg}) + + if len(chain) != 3 { + t.Fatalf("chain length = %d: %+v", len(chain), chain) + } + if chain[0].Method != "Tags" || len(chain[0].Args) != 0 { + t.Errorf("segment 0 = %+v", chain[0]) + } + if chain[1].Method != "ByTagId" || len(chain[1].Args) != 1 || + chain[1].Args[0].Kind != blueprint.ArgStateField { + t.Errorf("segment 1 = %+v", chain[1]) + } + if chain[2].Method != "Get" || len(chain[2].Args) != 2 { + t.Errorf("segment 2 = %+v", chain[2]) + } + + // A deeper path is one builder hop per literal segment. + deep := kiotaChain("/dashboards/filters/{id}", "Delete", []blueprint.Argument{ctx, nilCfg}) + if len(deep) != 4 || deep[0].Method != "Dashboards" || deep[1].Method != "Filters" || deep[2].Method != "ById" { + t.Errorf("deep chain = %+v", deep) + } +} + +func TestUnit_OpenAPI_KiotaNumberWidths(t *testing.T) { + t.Parallel() + + // Kiota reads formats literally: a plain integer is *int32, format int64 + // widens; the resty dialect is untouched. + sdkType, _, _ := conversionsFor(Field{Name: "n", Kind: blueprint.KindInt64}, "models", blueprint.DialectKiotaFluent) + if sdkType != "*int32" { + t.Errorf("kiota plain integer = %q, want *int32", sdkType) + } + sdkType, _, _ = conversionsFor(Field{Name: "n", Kind: blueprint.KindInt64, Format: "int64"}, "models", blueprint.DialectKiotaFluent) + if sdkType != "*int64" { + t.Errorf("kiota int64 integer = %q, want *int64", sdkType) + } + sdkType, _, _ = conversionsFor(Field{Name: "n", Kind: blueprint.KindInt64}, "tags", blueprint.DialectRestyService) + if sdkType != "*int64" { + t.Errorf("resty integer = %q, want *int64", sdkType) + } +} + +func TestUnit_OpenAPI_KiotaEnumWiring(t *testing.T) { + t.Parallel() + + f := Field{Name: "matchType", Kind: blueprint.KindString, EnumTypeName: "matchType", EnumValues: []string{"and", "or"}} + sdkType, flatten, expand := conversionsFor(f, "models", blueprint.DialectKiotaFluent) + + if sdkType != "*models.MatchType" { + t.Errorf("enum sdk type = %q", sdkType) + } + if flatten.Func != "convert.KiotaEnumToFramework" { + t.Errorf("flatten = %+v", flatten) + } + if expand.Func != "convert.FrameworkToKiotaEnum" || !expand.ReturnsError || + len(expand.ExtraArgs) != 1 || expand.ExtraArgs[0] != "models.ParseMatchType" { + t.Errorf("expand = %+v", expand) + } +} diff --git a/internal/openapi/nested.go b/internal/openapi/nested.go index 24850471..d2a7f3a4 100644 --- a/internal/openapi/nested.go +++ b/internal/openapi/nested.go @@ -22,16 +22,20 @@ type inferCtx struct { resourceBase string // sdkPkg is the SDK package a nested object's Go type lives in, e.g. "tags". sdkPkg string + // dialect decides the SDK-facing spellings: accessor bases, model type + // shapes and constructors. + dialect blueprint.SDKDialect taken map[string]bool notes []Caveat } -func newInferCtx(resourceKey, sdkPkg string) *inferCtx { +func newInferCtx(resourceKey, sdkPkg string, dialect blueprint.SDKDialect) *inferCtx { return &inferCtx{ resource: resourceKey, resourceBase: namingOpts.GoTypeName(resourceKey), sdkPkg: sdkPkg, + dialect: dialect, taken: map[string]bool{}, } } @@ -73,11 +77,25 @@ func (ctx *inferCtx) nestedObject( helperBase = pluralise(base) } + sdkType := ctx.sdkPkg + "." + namingOpts.GoTypeName(f.ObjectTypeName) + constructor := "" + if ctx.dialect == blueprint.DialectKiotaFluent { + // Builders traffic in the able interface; elements are built by + // constructor and filled through setters. + kn := kiotaName(f.ObjectTypeName) + if kn == "" { + kn = ctx.resourceBase + "Object" + } + sdkType = "models." + kn + "able" + constructor = "models.New" + kn + "()" + } + n := &blueprint.NestedAttributeObject{ - GoTypeName: naming.Unique(ctx.taken, base+"Model"), - SDKType: ctx.sdkPkg + "." + namingOpts.GoTypeName(f.ObjectTypeName), - AttrTypesVar: naming.Unique(ctx.taken, varBase+"AttrTypes"), - ObjectTypeVar: naming.Unique(ctx.taken, varBase+"ObjectType"), + GoTypeName: naming.Unique(ctx.taken, base+"Model"), + SDKType: sdkType, + ConstructorExpr: constructor, + AttrTypesVar: naming.Unique(ctx.taken, varBase+"AttrTypes"), + ObjectTypeVar: naming.Unique(ctx.taken, varBase+"ObjectType"), // Both directions are named even when the object is read-only. The name costs // nothing, blueprint.Validate requires it on a resource, and a later decision to // make the attribute writable then needs no new identifier. diff --git a/internal/openapi/schema.go b/internal/openapi/schema.go index 29f26756..64eb212b 100644 --- a/internal/openapi/schema.go +++ b/internal/openapi/schema.go @@ -34,6 +34,9 @@ type Field struct { Required bool // Nullable is the specification's nullable marker. Nullable bool + // Format is the schema's format string (int64, float, date-time...), which + // a generated SDK reads literally when choosing a Go width. + Format string // Deprecated marks a field the API documents as going away. Deprecated bool @@ -272,6 +275,7 @@ func fieldOf(name string, proxy *base.SchemaProxy, path []string) (Field, bool) Description: strings.TrimSpace(s.Description), ReadOnly: boolValue(s.ReadOnly), Nullable: boolValue(s.Nullable), + Format: s.Format, Deprecated: boolValue(s.Deprecated), } diff --git a/internal/sdkbind/facts.go b/internal/sdkbind/facts.go index e4234041..d0b78ce9 100644 --- a/internal/sdkbind/facts.go +++ b/internal/sdkbind/facts.go @@ -25,6 +25,15 @@ import ( // interactions, so they must live in their own committed file rather than inside // a snapshot -- replay purity is asserted for snapshot facts only. func StaticFacts(l *Loader, bp blueprint.Blueprint) ([]probe.Fact, error) { + // A method-access dialect derives no static facts, structurally: its models + // are pointer-typed behind accessors with serializer methods instead of + // encoder tags, so the zero-value-unsendable class this file detects cannot + // arise. Stated as an empty result rather than an error, so the committed + // empty document and its -check drift gate work identically across dialects. + if bp.Provider.SDK.Dialect == blueprint.DialectKiotaFluent { + return nil, nil + } + var facts []probe.Fact for _, res := range bp.Resources { @@ -202,6 +211,11 @@ func pkgName(importPath string) string { // live acceptance run, with an error naming neither. The class is detectable from // the pinned SDK's types alone, so it is refused here with names instead. func UnsendableStructWarnings(l *Loader, bp blueprint.Blueprint) []string { + // See StaticFacts: the class cannot arise under a method-access dialect. + if bp.Provider.SDK.Dialect == blueprint.DialectKiotaFluent { + return nil + } + var out []string for _, res := range bp.Resources { diff --git a/internal/sdkbind/methodchain.go b/internal/sdkbind/methodchain.go new file mode 100644 index 00000000..69be77a3 --- /dev/null +++ b/internal/sdkbind/methodchain.go @@ -0,0 +1,141 @@ +package sdkbind + +import ( + "fmt" + "go/types" + + "github.com/deploymenttheory/terraform-plugin-framework-codegen/internal/blueprint" +) + +// This file is the method-shaped half of the type checker: fluent +// request-builder chains and Get/Set accessor pairs, beside the struct-field +// machinery in sdkbind.go. Nothing here knows which generator produced the +// SDK -- it resolves whatever methods the loaded types actually carry. + +// methodOn resolves a method on t, following a pointer receiver for named +// types the way LookupMethod does, and working directly on interfaces -- +// generated builders return interface types, and their method sets carry no +// pointer receivers. +func methodOn(t types.Type, name string) (*types.Signature, bool) { + ms := methodSetOf(t) + for i := range ms.Len() { + if sel := ms.At(i); sel.Obj().Name() == name { + if sig, ok := sel.Obj().Type().(*types.Signature); ok { + return sig, true + } + } + } + return nil, false +} + +func methodSetOf(t types.Type) *types.MethodSet { + switch t.Underlying().(type) { + case *types.Interface, *types.Pointer: + return types.NewMethodSet(t) + default: + return types.NewMethodSet(types.NewPointer(t)) + } +} + +func methodNamesOf(t types.Type) []string { + ms := methodSetOf(t) + names := make([]string, 0, ms.Len()) + for i := range ms.Len() { + names = append(names, ms.At(i).Obj().Name()) + } + return names +} + +// MethodChain walks a fluent chain from a starting type and returns the final +// segment's method, for the same arity and result checks a flat call gets. +// +// Every hop before the last must exist with the declared argument count and +// return exactly one value -- a builder hop yielding two has no single type +// for the next hop to resolve against, so the walk refuses it by name rather +// than guessing which result carries the builder. +func (l *Loader) MethodChain(start types.Type, chain []blueprint.ChainSegment) (Method, error) { + current := start + + for i, seg := range chain { + sig, ok := methodOn(current, seg.Method) + if !ok { + return Method{}, fmt.Errorf("%w: %s has no method %s%s", + ErrBindings, shortType(current), seg.Method, + didYouMean(seg.Method, methodNamesOf(current))) + } + + if got, want := len(seg.Args), sig.Params().Len(); got != want { + return Method{}, fmt.Errorf( + "%w: chain segment %s declares %d argument(s) but the method takes %d: %s", + ErrBindings, seg.Method, got, want, methodFrom(seg.Method, sig).Signature()) + } + + if i == len(chain)-1 { + return methodFrom(seg.Method, sig), nil + } + + if sig.Results().Len() != 1 { + return Method{}, fmt.Errorf( + "%w: chain segment %s returns %d values; a builder hop must return exactly one", + ErrBindings, seg.Method, sig.Results().Len()) + } + current = sig.Results().At(0).Type() + } + + return Method{}, fmt.Errorf("%w: the chain is empty", ErrBindings) +} + +// LookupAccessorPair verifies a model exposes the Get/Set pair for a field and +// returns the getter's result type -- the method-access analogue of +// LookupField, and the place a mangling miss (GetError vs GetErrorEscaped) +// surfaces with the real spelling beside the guess. +// +// wantSet is false for a response model: builders return read-side interfaces +// whose method sets legitimately carry getters only. +func (l *Loader) LookupAccessorPair(importPath, typeName, field string, wantSet bool) (types.Type, error) { + named, err := l.LookupType(importPath, typeName) + if err != nil { + return nil, err + } + + getter := "Get" + field + sig, ok := methodOn(named, getter) + if !ok { + return nil, fmt.Errorf("%w: %s has no method %s%s", + ErrBindings, shortType(named), getter, didYouMean(getter, methodNamesOf(named))) + } + if sig.Params().Len() != 0 || sig.Results().Len() != 1 { + return nil, fmt.Errorf("%w: %s.%s is not an accessor: %s", + ErrBindings, shortType(named), getter, methodFrom(getter, sig).Signature()) + } + result := sig.Results().At(0).Type() + + if wantSet { + setter := "Set" + field + setSig, ok := methodOn(named, setter) + if !ok { + return nil, fmt.Errorf("%w: %s has no method %s%s", + ErrBindings, shortType(named), setter, didYouMean(setter, methodNamesOf(named))) + } + if setSig.Params().Len() != 1 { + return nil, fmt.Errorf("%w: %s.%s is not a setter: %s", + ErrBindings, shortType(named), setter, methodFrom(setter, setSig).Signature()) + } + } + + return result, nil +} + +// LookupFieldAccess dispatches on the binding's access style: a struct-field +// SDK is checked through LookupField, a method-access one through the Get/Set +// pair. This is the checker-side twin of the emitter's access seam. +func (l *Loader) LookupFieldAccess( + style blueprint.AccessStyle, + importPath, typeName, field string, + wantSet bool, +) (types.Type, error) { + if style == blueprint.AccessMethod { + return l.LookupAccessorPair(importPath, typeName, field, wantSet) + } + return l.LookupField(importPath, typeName, field) +} diff --git a/internal/sdkbind/methodchain_test.go b/internal/sdkbind/methodchain_test.go new file mode 100644 index 00000000..988b426c --- /dev/null +++ b/internal/sdkbind/methodchain_test.go @@ -0,0 +1,193 @@ +package sdkbind + +import ( + "strings" + "testing" + + "github.com/deploymenttheory/terraform-plugin-framework-codegen/internal/blueprint" +) + +// kiotaLoader resolves the miniature kiota-shaped SDK module in testdata. One +// per test run, like sharedLoader: go/packages loads are the expensive part. +var kiotaLoader = NewLoader("testdata/kiotasdk") + +func kiotaClientType(t *testing.T) *blueprint.ServiceRef { + t.Helper() + return &blueprint.ServiceRef{ImportPath: "example.com/kiotasdk/models", Alias: "models", Accessor: "r.client"} +} + +func chain(segs ...blueprint.ChainSegment) []blueprint.ChainSegment { return segs } + +func seg(method string, args ...blueprint.Argument) blueprint.ChainSegment { + return blueprint.ChainSegment{Method: method, Args: args} +} + +var ( + argCtx = blueprint.Argument{Kind: blueprint.ArgContext} + argNil = blueprint.Argument{Kind: blueprint.ArgLiteral, Expr: "nil"} + argID = blueprint.Argument{Kind: blueprint.ArgStateField, Field: "ID"} +) + +func TestUnit_SDKBind_MethodChainWalksAFluentCall(t *testing.T) { + t.Parallel() + + client, err := kiotaLoader.LookupType("example.com/kiotasdk/client", "ApiClient") + if err != nil { + t.Fatalf("loading the client type: %v", err) + } + + t.Run("a full read chain resolves to its verb", func(t *testing.T) { + t.Parallel() + m, err := kiotaLoader.MethodChain(client, chain( + seg("Tags"), + seg("ByTagId", argID), + seg("Get", argCtx, argNil), + )) + if err != nil { + t.Fatalf("MethodChain: %v", err) + } + if m.Name != "Get" || len(m.Results) != 2 { + t.Errorf("final method = %s %v", m.Name, m.Results) + } + if !strings.Contains(m.Results[0], "Tagable") { + t.Errorf("result type = %v, want the Tagable interface", m.Results) + } + }) + + t.Run("a delete chain returns error alone", func(t *testing.T) { + t.Parallel() + m, err := kiotaLoader.MethodChain(client, chain( + seg("Tags"), seg("ByTagId", argID), seg("Delete", argCtx, argNil), + )) + if err != nil { + t.Fatalf("MethodChain: %v", err) + } + if len(m.Results) != 1 { + t.Errorf("Delete results = %v", m.Results) + } + }) + + t.Run("a misspelled hop names the neighbours", func(t *testing.T) { + t.Parallel() + _, err := kiotaLoader.MethodChain(client, chain(seg("Tag"), seg("Get", argCtx, argNil))) + if err == nil || !strings.Contains(err.Error(), "Tags") { + t.Errorf("want a did-you-mean naming Tags, got: %v", err) + } + }) + + t.Run("a wrong argument count names the signature", func(t *testing.T) { + t.Parallel() + _, err := kiotaLoader.MethodChain(client, chain(seg("Tags"), seg("ByTagId"), seg("Get", argCtx, argNil))) + if err == nil || !strings.Contains(err.Error(), "declares 0 argument(s) but the method takes 1") { + t.Errorf("want the arity refusal, got: %v", err) + } + }) + + t.Run("a two-result builder hop cannot anchor a chain", func(t *testing.T) { + t.Parallel() + _, err := kiotaLoader.MethodChain(client, chain(seg("Tags"), seg("TwoResults"), seg("Get", argCtx, argNil))) + if err == nil || !strings.Contains(err.Error(), "must return exactly one") { + t.Errorf("want the builder-hop refusal, got: %v", err) + } + }) +} + +func TestUnit_SDKBind_AccessorPairs(t *testing.T) { + t.Parallel() + + const models = "example.com/kiotasdk/models" + + t.Run("a get and set pair on the concrete model", func(t *testing.T) { + t.Parallel() + if _, err := kiotaLoader.LookupAccessorPair(models, "Tag", "Name", true); err != nil { + t.Errorf("LookupAccessorPair: %v", err) + } + }) + + t.Run("a getter-only interface passes without the setter", func(t *testing.T) { + t.Parallel() + if _, err := kiotaLoader.LookupAccessorPair(models, "Tagable", "Name", false); err != nil { + t.Errorf("read-side pair: %v", err) + } + }) + + t.Run("a read-only field fails when a setter is demanded", func(t *testing.T) { + t.Parallel() + _, err := kiotaLoader.LookupAccessorPair(models, "Tag", "Id", true) + if err == nil || !strings.Contains(err.Error(), "SetId") { + t.Errorf("want the missing setter named, got: %v", err) + } + }) + + t.Run("a mangling miss names the real spelling", func(t *testing.T) { + t.Parallel() + _, err := kiotaLoader.LookupAccessorPair(models, "Tag", "Error", true) + if err == nil || !strings.Contains(err.Error(), "GetErrorEscaped") { + t.Errorf("want the Escaped spelling suggested, got: %v", err) + } + }) + + t.Run("the dispatcher routes struct style to fields", func(t *testing.T) { + t.Parallel() + // The concrete Tag has unexported fields only, so struct-style lookup + // must fail while method-style succeeds -- proving the dispatch. + if _, err := kiotaLoader.LookupFieldAccess(blueprint.AccessStructField, models, "Tag", "Name", true); err == nil { + t.Error("struct-style lookup of a method-access model must fail") + } + if _, err := kiotaLoader.LookupFieldAccess(blueprint.AccessMethod, models, "Tag", "Name", true); err != nil { + t.Errorf("method-style lookup: %v", err) + } + }) +} + +func TestUnit_SDKBind_FluentOperationVerifies(t *testing.T) { + t.Parallel() + + client, err := kiotaLoader.LookupType("example.com/kiotasdk/client", "ApiClient") + if err != nil { + t.Fatalf("loading the client type: %v", err) + } + svc := *kiotaClientType(t) + + t.Run("a sound fluent read passes", func(t *testing.T) { + t.Parallel() + var r Report + verifyOperation(kiotaLoader, client, "tag", svc, "read", blueprint.Operation{ + Style: blueprint.CallStyleFluent, + Chain: chain(seg("Tags"), seg("ByTagId", argID), seg("Get", argCtx, argNil)), + Return: blueprint.ReturnResultError, + ResultType: "models.Tagable", + }, &r) + if len(r.Problems) != 0 { + t.Fatalf("unexpected problems: %v", r.Problems) + } + }) + + t.Run("a wrong result type is named", func(t *testing.T) { + t.Parallel() + var r Report + verifyOperation(kiotaLoader, client, "tag", svc, "read", blueprint.Operation{ + Style: blueprint.CallStyleFluent, + Chain: chain(seg("Tags"), seg("ByTagId", argID), seg("Get", argCtx, argNil)), + Return: blueprint.ReturnResultError, + ResultType: "models.Widget", + }, &r) + if len(r.Problems) != 1 || !strings.Contains(r.Problems[0].Detail, "Tagable") { + t.Fatalf("want the real result type named, got: %v", r.Problems) + } + }) + + t.Run("a transport arity cannot be satisfied", func(t *testing.T) { + t.Parallel() + var r Report + verifyOperation(kiotaLoader, client, "tag", svc, "read", blueprint.Operation{ + Style: blueprint.CallStyleFluent, + Chain: chain(seg("Tags"), seg("ByTagId", argID), seg("Get", argCtx, argNil)), + Return: blueprint.ReturnResultTransportError, + ResultType: "models.Tagable", + }, &r) + if len(r.Problems) != 1 || !strings.Contains(r.Problems[0].Path, "return") { + t.Fatalf("want the arity mismatch on .return, got: %v", r.Problems) + } + }) +} diff --git a/internal/sdkbind/sdkbind_test.go b/internal/sdkbind/sdkbind_test.go index 5193d7eb..0290b66d 100644 --- a/internal/sdkbind/sdkbind_test.go +++ b/internal/sdkbind/sdkbind_test.go @@ -339,7 +339,7 @@ func TestUnit_SDKBind_AccessorChain(t *testing.T) { }{ {"r.client.API.Tags", []string{"API", "Tags"}, true}, {"r.client.Tags", []string{"Tags"}, true}, - {"r.client", nil, false}, + {"r.client", []string{}, true}, // the fluent root: chain starts at the client itself {"client.API.Tags", nil, false}, {"someGlobal.Tags", nil, false}, {"", nil, false}, diff --git a/internal/sdkbind/testdata/kiotasdk/client/client.go b/internal/sdkbind/testdata/kiotasdk/client/client.go new file mode 100644 index 00000000..badd5189 --- /dev/null +++ b/internal/sdkbind/testdata/kiotasdk/client/client.go @@ -0,0 +1,38 @@ +// Package client is a miniature kiota-shaped fluent surface: a root client, a +// builder hop, a typed indexer, and verbs returning (interface, error). +package client + +import ( + "context" + + "example.com/kiotasdk/models" +) + +type ApiClient struct{} + +func (c *ApiClient) Tags() *TagsRequestBuilder { return &TagsRequestBuilder{} } + +type TagsRequestBuilder struct{} + +func (b *TagsRequestBuilder) ByTagId(id string) *TagItemRequestBuilder { + return &TagItemRequestBuilder{} +} + +func (b *TagsRequestBuilder) Post(ctx context.Context, body *models.Tag, cfg *RequestConfiguration) (models.Tagable, error) { + return nil, nil +} + +type TagItemRequestBuilder struct{} + +func (b *TagItemRequestBuilder) Get(ctx context.Context, cfg *RequestConfiguration) (models.Tagable, error) { + return nil, nil +} + +func (b *TagItemRequestBuilder) Delete(ctx context.Context, cfg *RequestConfiguration) error { + return nil +} + +// TwoResults exists so a builder hop that cannot anchor a chain is testable. +func (b *TagsRequestBuilder) TwoResults() (*TagItemRequestBuilder, error) { return nil, nil } + +type RequestConfiguration struct{} diff --git a/internal/sdkbind/testdata/kiotasdk/go.mod b/internal/sdkbind/testdata/kiotasdk/go.mod new file mode 100644 index 00000000..2195d29a --- /dev/null +++ b/internal/sdkbind/testdata/kiotasdk/go.mod @@ -0,0 +1,3 @@ +module example.com/kiotasdk + +go 1.25 diff --git a/internal/sdkbind/testdata/kiotasdk/models/models.go b/internal/sdkbind/testdata/kiotasdk/models/models.go new file mode 100644 index 00000000..9dd7c5c0 --- /dev/null +++ b/internal/sdkbind/testdata/kiotasdk/models/models.go @@ -0,0 +1,25 @@ +// Package models is a miniature kiota-shaped model surface: pointer-typed +// properties behind Get/Set pairs, an interface the builders return, a +// constructor, and one keyword-mangled accessor -- everything the checker's +// method-access paths have to resolve. +package models + +type Tag struct { + name *string + errorEscaped *string + id *string +} + +func NewTag() *Tag { return &Tag{} } + +func (t *Tag) GetName() *string { return t.name } +func (t *Tag) SetName(v *string) { t.name = v } +func (t *Tag) GetErrorEscaped() *string { return t.errorEscaped } +func (t *Tag) SetErrorEscaped(v *string) { t.errorEscaped = v } +func (t *Tag) GetId() *string { return t.id } + +type Tagable interface { + GetName() *string + GetErrorEscaped() *string + GetId() *string +} diff --git a/internal/sdkbind/verify.go b/internal/sdkbind/verify.go index ca83fa40..33168f92 100644 --- a/internal/sdkbind/verify.go +++ b/internal/sdkbind/verify.go @@ -146,7 +146,7 @@ func verifyEphemeral( ) if e.Binding.Open != nil { - verifyOperation(l, e.Key, svc, "open", *e.Binding.Open, r) + verifyOperation(l, clientType, e.Key, svc, "open", *e.Binding.Open, r) } if !responseOK { @@ -160,7 +160,7 @@ func verifyEphemeral( continue } verifyFieldOn( - l, e.Key, + l, e.Binding.Response.AccessStyle, e.Key, fmt.Sprintf("schema.attributes[%s].wire.sdkField", a.Name), response, a.Wire.SDKField, svc, r, ) @@ -191,7 +191,7 @@ func verifyDataSource( ) if d.Binding.Read != nil { - verifyOperation(l, d.Key, svc, "read", *d.Binding.Read, r) + verifyOperation(l, clientType, d.Key, svc, "read", *d.Binding.Read, r) } if !responseOK { @@ -207,7 +207,7 @@ func verifyDataSource( continue } verifyFieldOn( - l, d.Key, + l, d.Binding.Response.AccessStyle, d.Key, fmt.Sprintf("schema.attributes[%s].wire.sdkField", a.Name), response, a.Wire.SDKField, svc, r, ) @@ -231,7 +231,7 @@ func verifyListFacet( verifyNamedType(l, res.Key, "list.response.type", lf.Response.Type, svc, r) if lf.Read != nil { - verifyOperation(l, res.Key, svc, "read", *lf.Read, r) + verifyOperation(l, clientType, res.Key, svc, "read", *lf.Read, r) } if !verifyNamedType(l, res.Key, "list.elementType", lf.ElementType, svc, r) { @@ -242,13 +242,13 @@ func verifyListFacet( for i, m := range lf.IdentityFrom { verifyFieldOn( - l, res.Key, + l, res.Binding.Body.AccessStyle, res.Key, fmt.Sprintf("list.identityFrom[%d].fromSdkField", i), element, m.FromSDKField, svc, r, ) } - verifyFieldOn(l, res.Key, "list.displayNameFrom", element, lf.DisplayNameFrom, svc, r) + verifyFieldOn(l, res.Binding.Body.AccessStyle, res.Key, "list.displayNameFrom", element, lf.DisplayNameFrom, svc, r) } // verifyAction checks an action's binding. @@ -267,7 +267,7 @@ func verifyAction( verifyAccessor(l, clientType, a.Key, "binding.service.accessor", svc, r) if a.Binding.Invoke != nil { - verifyOperation(l, a.Key, svc, "invoke", *a.Binding.Invoke, r) + verifyOperation(l, clientType, a.Key, svc, "invoke", *a.Binding.Invoke, r) } } @@ -301,6 +301,21 @@ func verifyAccessor( return } + // A bare ".client" is the fluent root and legitimate only there; + // a binding that declares a service type has to actually reach it. + if len(chain) == 0 && svc.TypeName != "" { + r.Problems = append(r.Problems, Problem{ + Resource: key, + Path: path, + Detail: fmt.Sprintf( + "%q stops at the client, but the binding declares service type %s; name the field that reaches it", + svc.Accessor, svc.TypeName, + ), + }) + + return + } + if _, err := l.FieldChain(clientType, chain); err != nil { r.Problems = append(r.Problems, Problem{ Resource: key, @@ -341,6 +356,7 @@ func verifyNamedType(l *Loader, key, path, expr string, svc blueprint.ServiceRef // of the same type the code compiles and the provider moves the wrong value. func verifyFieldOn( l *Loader, + style blueprint.AccessStyle, key, path, typeName, field string, svc blueprint.ServiceRef, r *Report, @@ -349,7 +365,7 @@ func verifyFieldOn( return } - if _, err := l.LookupField(svc.ImportPath, typeName, field); err != nil { + if _, err := l.LookupFieldAccess(style, svc.ImportPath, typeName, field, false); err != nil { r.Problems = append(r.Problems, Problem{ Resource: key, Path: path, @@ -399,17 +415,20 @@ func verifyResource( verifyAccessor(l, clientType, res.Key, "binding.service.accessor", svc, r) - // The service type itself. - if _, err := l.LookupType(svc.ImportPath, svc.TypeName); err != nil { - r.Problems = append(r.Problems, Problem{ - Resource: res.Key, - Path: "binding.service.typeName", - Detail: unwrapDetail(err), - }) - // Without the service type there are no methods to check. - return + // The service type itself. Optional under a fluent binding: there is no + // service struct, the chain resolves from the client type. + if svc.TypeName != "" { + if _, err := l.LookupType(svc.ImportPath, svc.TypeName); err != nil { + r.Problems = append(r.Problems, Problem{ + Resource: res.Key, + Path: "binding.service.typeName", + Detail: unwrapDetail(err), + }) + // Without the service type there are no methods to check. + return + } + r.Checked++ } - r.Checked++ ops := map[string]*blueprint.Operation{ "create": res.Binding.Create, @@ -421,7 +440,7 @@ func verifyResource( if op == nil { continue } - verifyOperation(l, res.Key, svc, name, *op, r) + verifyOperation(l, clientType, res.Key, svc, name, *op, r) } ok := verifyBodyModels(l, res, r) @@ -430,6 +449,7 @@ func verifyResource( func verifyOperation( l *Loader, + clientType types.Type, key string, svc blueprint.ServiceRef, name string, @@ -438,6 +458,45 @@ func verifyOperation( ) { path := "binding." + name + // A fluent operation's call is its chain: resolve the accessor to a start + // type, then walk every hop by method. The final segment gets exactly the + // arity and result checks a flat call gets. + if op.Style == blueprint.CallStyleFluent { + chain, ok := accessorChain(svc.Accessor) + if !ok { + r.Problems = append(r.Problems, Problem{ + Resource: key, + Path: path + ".chain", + Detail: fmt.Sprintf( + "accessor %q is not of the form \".client...\", so the chain has no start", + svc.Accessor), + }) + return + } + start, err := l.FieldChain(clientType, chain) + if err != nil { + r.Problems = append(r.Problems, Problem{ + Resource: key, + Path: path + ".chain", + Detail: fmt.Sprintf("accessor %q does not resolve: %v", svc.Accessor, unwrapDetail(err)), + }) + return + } + + m, err := l.MethodChain(start, op.Chain) + if err != nil { + r.Problems = append(r.Problems, Problem{ + Resource: key, + Path: path + ".chain", + Detail: unwrapDetail(err), + }) + return + } + r.Checked++ + verifyMethodContract(m, key, path, op, r) + return + } + m, err := l.LookupMethod(svc.ImportPath, svc.TypeName, op.Method) if err != nil { r.Problems = append(r.Problems, Problem{ @@ -448,7 +507,12 @@ func verifyOperation( return } r.Checked++ + verifyMethodContract(m, key, path, op, r) +} +// verifyMethodContract holds a resolved method to the operation's declared +// return contract, whichever way the method was reached. +func verifyMethodContract(m Method, key, path string, op blueprint.Operation, r *Report) { // The declared return arity must match the method's, because it decides the // arity of every error return in the generated body. A mismatch produces code // that does not compile, so catching it here is strictly better than in the @@ -461,7 +525,7 @@ func verifyOperation( Resource: key, Path: path + ".return", Detail: fmt.Sprintf("declared %q implies %d result(s), but %s returns %d: %s", - op.Return, gotArity, op.Method, wantResults, m.Signature()), + op.Return, gotArity, m.Name, wantResults, m.Signature()), }) return } @@ -476,7 +540,7 @@ func verifyOperation( Resource: key, Path: path + ".resultType", Detail: fmt.Sprintf("declared %q but %s returns %s: %s", - op.ResultType, op.Method, m.Results[0], m.Signature()), + op.ResultType, m.Name, m.Results[0], m.Signature()), }) } } @@ -586,8 +650,10 @@ func verifyWireFields(l *Loader, res blueprint.Resource, ok bodyTypesOK, r *Repo checks[response] = "flatten" } + style := res.Binding.Body.AccessStyle for typeName, direction := range checks { - if _, err := l.LookupField(svc.ImportPath, typeName, a.Wire.SDKField); err != nil { + wantSet := strings.HasPrefix(direction, "expand") + if _, err := l.LookupFieldAccess(style, svc.ImportPath, typeName, a.Wire.SDKField, wantSet); err != nil { r.Problems = append(r.Problems, Problem{ Resource: res.Key, Path: fmt.Sprintf("attributes[%s].wire.sdkField", a.Name), @@ -603,7 +669,7 @@ func verifyWireFields(l *Loader, res blueprint.Resource, ok bodyTypesOK, r *Repo r.Checked++ } - verifySplitFieldsAgree(l, svc, res.Key, a, request, update, r) + verifySplitFieldsAgree(l, svc, style, res.Key, a, request, update, r) } } @@ -619,6 +685,7 @@ func verifyWireFields(l *Loader, res blueprint.Resource, ok bodyTypesOK, r *Repo func verifySplitFieldsAgree( l *Loader, svc blueprint.ServiceRef, + style blueprint.AccessStyle, key string, a blueprint.Attribute, request, update string, @@ -635,8 +702,8 @@ func verifySplitFieldsAgree( return } - created, errC := l.LookupField(svc.ImportPath, request, a.Wire.SDKField) - updated, errU := l.LookupField(svc.ImportPath, update, a.Wire.SDKField) + created, errC := l.LookupFieldAccess(style, svc.ImportPath, request, a.Wire.SDKField, true) + updated, errU := l.LookupFieldAccess(style, svc.ImportPath, update, a.Wire.SDKField, true) if errC != nil || errU != nil { // Absence is already reported by the existence checks; repeating it here // would double every miss. @@ -681,10 +748,12 @@ func accessorChain(accessor string) ([]string, bool) { // skipped. It was pinned to "r" while this package walked resources and nothing else, // which meant every other kind's accessor came back unverifiable rather than verified. // - // What must hold is the shape: a receiver, a field named client, and at least one field - // after it. The field chain from client onwards is the part that is resolved against the - // real client type, and that is where a wrong accessor is actually caught. - if len(parts) < 3 || parts[0] == "" || parts[1] != "client" { + // What must hold is the shape: a receiver, a field named client, then zero or more + // fields. Zero is the fluent case -- the chain starts at the client itself and the + // hops are methods, resolved by MethodChain rather than here. One or more is the + // struct-field case, resolved against the real client type, and that is where a + // wrong accessor is actually caught. + if len(parts) < 2 || parts[0] == "" || parts[1] != "client" { return nil, false } diff --git a/internal/templates/_nested_flatten.go.tmpl b/internal/templates/_nested_flatten.go.tmpl index 1419b098..6e14bcae 100644 --- a/internal/templates/_nested_flatten.go.tmpl +++ b/internal/templates/_nested_flatten.go.tmpl @@ -14,7 +14,7 @@ this fragment cannot reach anything kind-specific even by accident. {{- if .IsCollection }} func {{ .FuncName }}(ctx context.Context, in []{{ .SDKType }}) ({{ .FrameworkType }}, diag.Diagnostics) { var diags diag.Diagnostics -{{- if .NeedsDiagnostics }} +{{- if .SharedDiag }} var d diag.Diagnostics {{- end }} diff --git a/internal/templates/construct.go.tmpl b/internal/templates/construct.go.tmpl index 7b2d4622..c127cead 100644 --- a/internal/templates/construct.go.tmpl +++ b/internal/templates/construct.go.tmpl @@ -66,7 +66,7 @@ func constructResourceUpdate(ctx context.Context, data *{{ .ModelTypeName }}) *{ {{- if .IsCollection }} func {{ .FuncName }}(ctx context.Context, v {{ .FrameworkType }}) ([]{{ .SDKType }}, diag.Diagnostics) { var diags diag.Diagnostics -{{- if .NeedsDiagnostics }} +{{- if .SharedDiag }} var d diag.Diagnostics {{- end }} @@ -76,15 +76,19 @@ func {{ .FuncName }}(ctx context.Context, v {{ .FrameworkType }}) ([]{{ .SDKType return nil, diags } - var models []{{ .ModelType }} - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []{{ .ModelType }} + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]{{ .SDKType }}, 0, len(models)) - for _, m := range models { + out := make([]{{ .SDKType }}, 0, len(elements)) + for _, m := range elements { +{{- if .ConstructorExpr }} + item := {{ .ConstructorExpr }} +{{- else }} var item {{ .SDKType }} +{{- end }} {{ range .Assignments }} {{ . }} {{- end }} diff --git a/internal/templates/crud.go.tmpl b/internal/templates/crud.go.tmpl index ea5729b0..d4184354 100644 --- a/internal/templates/crud.go.tmpl +++ b/internal/templates/crud.go.tmpl @@ -40,6 +40,15 @@ func (r *{{ $.GoTypeName }}) Create(ctx context.Context, req resource.CreateRequ errors.Handle(&resp.Diagnostics, TypeName, {{ .ErrorOp }}, err) return } +{{- if .NilResultGuard }} + + // An empty success body cannot be recorded: without a response there is no + // identifier, and inventing one would orphan whatever the API just created. + if {{ .ResultVar }} == nil { + errors.Handle(&resp.Diagnostics, TypeName, {{ .ErrorOp }}, errors.ErrEmptyResponse) + return + } +{{- end }} // The identifier is assigned first, so that a partially-successful create still // leaves a resource Terraform can find and delete rather than an orphan it has no @@ -144,6 +153,14 @@ func (r *{{ $.GoTypeName }}) readState( return false, diags } +{{- if .NilResultGuard }} + + // An empty success body means the object is gone as far as this provider can + // tell; treat it exactly like a not-found rather than mapping nothing. + if {{ .ResultVar }} == nil { + return false, diags + } +{{- end }} {{- if $.State.NeedsDiagnostics }} diags.Append(mapRemoteStateToTerraform(ctx, state, {{ .ResultVar }})...) diff --git a/internal/templates/datasource_read.go.tmpl b/internal/templates/datasource_read.go.tmpl index 60240119..ac7ee999 100644 --- a/internal/templates/datasource_read.go.tmpl +++ b/internal/templates/datasource_read.go.tmpl @@ -36,6 +36,15 @@ func (d *{{ $.GoTypeName }}) Read(ctx context.Context, req datasource.ReadReques errors.Handle(&resp.Diagnostics, TypeName, {{ .ErrorOp }}, err) return } +{{- if .NilResultGuard }} + + // An empty success body is the same failure as a missing object: the + // practitioner named something the API answered nothing about. + if {{ .ResultVar }} == nil { + errors.Handle(&resp.Diagnostics, TypeName, {{ .ErrorOp }}, errors.ErrEmptyResponse) + return + } +{{- end }} {{- if $.State.NeedsDiagnostics }} resp.Diagnostics.Append(mapRemoteStateToTerraform(ctx, &data, {{ .ResultVar }})...) diff --git a/internal/templates/datasource_state.go.tmpl b/internal/templates/datasource_state.go.tmpl index 4e326f33..a1dcfce2 100644 --- a/internal/templates/datasource_state.go.tmpl +++ b/internal/templates/datasource_state.go.tmpl @@ -12,7 +12,7 @@ import ( // generated code, a field whose conversion is wrong is a compile error rather // than a surprise during apply. {{- if .State.NeedsDiagnostics }} -func mapRemoteStateToTerraform(ctx context.Context, data *{{ .ModelTypeName }}, remote *{{ .State.ResponseType }}) diag.Diagnostics { +func mapRemoteStateToTerraform(ctx context.Context, data *{{ .ModelTypeName }}, remote {{ .State.ResponseType }}) diag.Diagnostics { var ( diags diag.Diagnostics d diag.Diagnostics @@ -25,7 +25,7 @@ func mapRemoteStateToTerraform(ctx context.Context, data *{{ .ModelTypeName }}, return diags } {{- else }} -func mapRemoteStateToTerraform(ctx context.Context, data *{{ .ModelTypeName }}, remote *{{ .State.ResponseType }}) { +func mapRemoteStateToTerraform(ctx context.Context, data *{{ .ModelTypeName }}, remote {{ .State.ResponseType }}) { if remote == nil { tflog.Debug(ctx, "remote object is nil, leaving state untouched", map[string]any{ "dataSource": TypeName, diff --git a/internal/templates/ephemeral_open.go.tmpl b/internal/templates/ephemeral_open.go.tmpl index d0a97499..534b660f 100644 --- a/internal/templates/ephemeral_open.go.tmpl +++ b/internal/templates/ephemeral_open.go.tmpl @@ -46,6 +46,15 @@ func (e *{{ $.GoTypeName }}) Open(ctx context.Context, req ephemeral.OpenRequest errors.Handle(&resp.Diagnostics, TypeName, {{ .ErrorOp }}, err) return } +{{- if .NilResultGuard }} + + // An empty success body is the same failure as a missing object: whatever + // was going to consume the value has nothing to consume. + if {{ .ResultVar }} == nil { + errors.Handle(&resp.Diagnostics, TypeName, {{ .ErrorOp }}, errors.ErrEmptyResponse) + return + } +{{- end }} {{- if $.State.NeedsDiagnostics }} resp.Diagnostics.Append(mapRemoteToResult(ctx, &data, {{ .ResultVar }})...) diff --git a/internal/templates/ephemeral_state.go.tmpl b/internal/templates/ephemeral_state.go.tmpl index fd3041a8..38d004e6 100644 --- a/internal/templates/ephemeral_state.go.tmpl +++ b/internal/templates/ephemeral_state.go.tmpl @@ -11,7 +11,7 @@ import ( // Named for where the values go: an ephemeral has no state, and calling this a state // mapper would claim a persistence the kind exists to avoid. {{- if .State.NeedsDiagnostics }} -func mapRemoteToResult(ctx context.Context, data *{{ .ModelTypeName }}, remote *{{ .State.ResponseType }}) diag.Diagnostics { +func mapRemoteToResult(ctx context.Context, data *{{ .ModelTypeName }}, remote {{ .State.ResponseType }}) diag.Diagnostics { var ( diags diag.Diagnostics d diag.Diagnostics @@ -24,7 +24,7 @@ func mapRemoteToResult(ctx context.Context, data *{{ .ModelTypeName }}, remote * return diags } {{- else }} -func mapRemoteToResult(ctx context.Context, data *{{ .ModelTypeName }}, remote *{{ .State.ResponseType }}) { +func mapRemoteToResult(ctx context.Context, data *{{ .ModelTypeName }}, remote {{ .State.ResponseType }}) { if remote == nil { tflog.Debug(ctx, "remote object is nil, leaving the result untouched", map[string]any{ "ephemeral": TypeName, diff --git a/internal/templates/list_resource.go.tmpl b/internal/templates/list_resource.go.tmpl index fddc6422..12250149 100644 --- a/internal/templates/list_resource.go.tmpl +++ b/internal/templates/list_resource.go.tmpl @@ -123,6 +123,15 @@ func (l *{{ $.List.GoTypeName }}) List(ctx context.Context, req list.ListRequest return } +{{- if .NilResultGuard }} + + // An empty success body is an empty collection, not an error. + if {{ .ResultVar }} == nil { + stream.Results = list.NoListResults + + return + } +{{- end }} stream.Results = func(push func(list.ListResult) bool) { for _, item := range {{ $.List.Elements }} { diff --git a/internal/templates/state.go.tmpl b/internal/templates/state.go.tmpl index a486bd90..d04b422d 100644 --- a/internal/templates/state.go.tmpl +++ b/internal/templates/state.go.tmpl @@ -12,7 +12,7 @@ import ( // generated code, a field whose conversion is wrong is a compile error rather // than a surprise during apply. {{- if .State.NeedsDiagnostics }} -func mapRemoteStateToTerraform(ctx context.Context, data *{{ .ModelTypeName }}, remote *{{ .State.ResponseType }}) diag.Diagnostics { +func mapRemoteStateToTerraform(ctx context.Context, data *{{ .ModelTypeName }}, remote {{ .State.ResponseType }}) diag.Diagnostics { var ( diags diag.Diagnostics d diag.Diagnostics @@ -25,7 +25,7 @@ func mapRemoteStateToTerraform(ctx context.Context, data *{{ .ModelTypeName }}, return diags } {{- else }} -func mapRemoteStateToTerraform(ctx context.Context, data *{{ .ModelTypeName }}, remote *{{ .State.ResponseType }}) { +func mapRemoteStateToTerraform(ctx context.Context, data *{{ .ModelTypeName }}, remote {{ .State.ResponseType }}) { if remote == nil { tflog.Debug(ctx, "remote resource is nil, leaving state untouched", map[string]any{ "resource": TypeName, diff --git a/pilot/thousandeyes-kiota/.tfpfgen/manifest.json b/pilot/thousandeyes-kiota/.tfpfgen/manifest.json new file mode 100644 index 00000000..fe423fab --- /dev/null +++ b/pilot/thousandeyes-kiota/.tfpfgen/manifest.json @@ -0,0 +1,86 @@ +{ + "formatVersion": "1", + "toolVersion": "dev", + "files": [ + { + "path": "internal/provider/actions.go", + "sha256": "85b73066bc9298847a9fb9d925c2c1ef4859bb3ab803c2f5cc37d3b0db6c5e85", + "blueprint": "blueprints/thousandeyes-kiota" + }, + { + "path": "internal/provider/datasources.go", + "sha256": "3307b81156907a7893cd550d52454e6a081e59b5595a4bc4334544593084626b", + "blueprint": "blueprints/thousandeyes-kiota" + }, + { + "path": "internal/provider/ephemerals.go", + "sha256": "2a06ad565ca50d2005285f4d6f9bf441a41eae3e2fb8cd8b4e40047589922189", + "blueprint": "blueprints/thousandeyes-kiota" + }, + { + "path": "internal/provider/list_resources.go", + "sha256": "b35ad541ba4235ce9f8808dd0f260be32b2ed666c6f5009b70d26d5e1317e57e", + "blueprint": "blueprints/thousandeyes-kiota" + }, + { + "path": "internal/provider/resources.go", + "sha256": "1934955d5707d0fa56ad0e2f9bd64e972ce0385ead92ae51a6888ca26e7e4c9b", + "blueprint": "blueprints/thousandeyes-kiota" + }, + { + "path": "internal/services/resources/tags/v7/tag/conditional_validators.go", + "sha256": "b75e9275c114aa77870b25b6c9637281db33486129daab7a646cf3f3dcd9c1ef", + "blueprint": "blueprints/thousandeyes-kiota" + }, + { + "path": "internal/services/resources/tags/v7/tag/construct.go", + "sha256": "1d87d8118e1cc68d8ac93917d677c56cc61a2fb40bf7eb488ca903955469dbbc", + "blueprint": "blueprints/thousandeyes-kiota" + }, + { + "path": "internal/services/resources/tags/v7/tag/crud.go", + "sha256": "5b61e322e805800425506619053a1db2e276737f306eaf0aa6f9af72c081cf37", + "blueprint": "blueprints/thousandeyes-kiota" + }, + { + "path": "internal/services/resources/tags/v7/tag/model.go", + "sha256": "2a357c92554730d5689d9da14f1034d7e825b0f890fd98dc23c4054c4f02455d", + "blueprint": "blueprints/thousandeyes-kiota" + }, + { + "path": "internal/services/resources/tags/v7/tag/resource.go", + "sha256": "338fff4afedd5827b68e345be84787124fb5d456e3b9a5d6a1a4a1efc0621489", + "blueprint": "blueprints/thousandeyes-kiota" + }, + { + "path": "internal/services/resources/tags/v7/tag/resource_acceptance_test.go", + "sha256": "e11a6ffa158ef7fd0e3bbe5c512ce5e8a335c62f3b48854fb703c4c2005759ee", + "blueprint": "blueprints/thousandeyes-kiota" + }, + { + "path": "internal/services/resources/tags/v7/tag/state.go", + "sha256": "9b995385270ce354fd676bb64c46d5209244db32385a076e73d272566e029e5c", + "blueprint": "blueprints/thousandeyes-kiota" + }, + { + "path": "internal/services/resources/tags/v7/tag/test_helper_test.go", + "sha256": "5b42e685d55baebe81601b3dc2db7ec7b209931f124c9484ccd8137461ec8f1a", + "blueprint": "blueprints/thousandeyes-kiota" + }, + { + "path": "internal/services/resources/tags/v7/tag/testdata/maximal.tf", + "sha256": "b358c10fd995af29108c0e675ba07378296eff4d4771b8397da4688ed533f091", + "blueprint": "blueprints/thousandeyes-kiota" + }, + { + "path": "internal/services/resources/tags/v7/tag/testdata/minimal.tf", + "sha256": "ee635e3c5e43d7f6fe5565eb5917882328ae13ad2689ec1431f5942f09b09488", + "blueprint": "blueprints/thousandeyes-kiota" + }, + { + "path": "internal/services/resources/tags/v7/tag/testdata/replace.tf", + "sha256": "26b263dc7175ea611376b3be1e3562add3ce454546322314932590757b5e38a2", + "blueprint": "blueprints/thousandeyes-kiota" + } + ] +} diff --git a/pilot/thousandeyes-kiota/docs/index.md b/pilot/thousandeyes-kiota/docs/index.md new file mode 100644 index 00000000..cef5cfd1 --- /dev/null +++ b/pilot/thousandeyes-kiota/docs/index.md @@ -0,0 +1,37 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "ThousandEyes Provider" +description: |- + Manages ThousandEyes resources through the v7 API. +--- + +# ThousandEyes Provider + +Manages ThousandEyes resources through the v7 API. + +## Example Usage + +```terraform +terraform { + required_providers { + thousandeyes = { + source = "deploymenttheory/thousandeyes" + } + } +} + +provider "thousandeyes" { + # Credentials are read from the environment when not set here. +} +``` + + +## Schema + +### Optional + +- `account_group_id` (String) Scopes every request to an account group, sent as the `aid` query parameter. When unset, the API uses the token owner's default account group, which means an object can appear absent rather than merely invisible. May also be set with the `THOUSANDEYES_ACCOUNT_GROUP_ID` environment variable. +- `api_endpoint` (String) Overrides the API base URL. Defaults to the SDK's v7 endpoint. May also be set with the `THOUSANDEYES_API_ENDPOINT` environment variable. +- `bearer_token` (String, Sensitive) A ThousandEyes user API token, created under **Account Settings > Users and Roles > Profile**. May also be set with the `THOUSANDEYES_BEARER_TOKEN` environment variable. + +ThousandEyes has no service-account identity type: this token belongs to a user, does not expire, and cannot be revoked through the API. diff --git a/pilot/thousandeyes-kiota/docs/resources/tag.md b/pilot/thousandeyes-kiota/docs/resources/tag.md new file mode 100644 index 00000000..f89e9ef8 --- /dev/null +++ b/pilot/thousandeyes-kiota/docs/resources/tag.md @@ -0,0 +1,95 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "thousandeyes_tag Resource - ThousandEyes" +subcategory: "" +description: |- + Manages a ThousandEyes tag. Tags are key/value labels that can be assigned to tests, agents and dashboards. +--- + +# thousandeyes_tag (Resource) + +Manages a ThousandEyes tag. Tags are key/value labels that can be assigned to tests, agents and dashboards. + +## Example Usage + +```terraform +resource "thousandeyes_tag" "example" { + key = "tfacc-key" + value = "tfacc-value" + object_type = "test" + access_type = "all" +} +``` + + +## Schema + +### Required + +- `key` (String) The tag's key. Together with `value` this forms the label applied to assigned objects. +- `object_type` (String) The kind of object the tag may be assigned to. Documented values are `test`, `v-agent`, `endpoint-test`, `dashboard` and `connected-devices-test`. The generated validator permits the whole documented set, which is wider than what any one tenant accepts, so a stale specification surfaces as the API's own error rather than as a plan failure. Values accepted here: `test`, `dashboard`, `endpoint-test`, `v-agent`, `connected-devices-test`. The API enforces this field's presence, which the specification does not declare. The SDK's encoding drops this type's zero value (omitempty), so configuring false, 0 or "" silently becomes an omission. + +### Optional + +- `access_type` (String) The tag's access level. Documented values are `all`, `partner` and `system`. Values accepted here: `all`. The specification documents `system`, which the API rejected. The API enforces this field's presence, which the specification does not declare. The SDK's encoding drops this type's zero value (omitempty), so configuring false, 0 or "" silently becomes an omission. +- `color` (String) The tag's display colour as a hex string. Computed as well as optional because the API assigns one when it is omitted; this has not yet been confirmed by probing. Observed: the API assigns "#A7EB10" when this is omitted. +- `description` (String) A human-readable description of the tag. +- `filters` (Attributes Set) Filters that dynamically assign this tag to endpoint agents. Conditionally, when objectType is "endpoint-agent" and type is "dynamic": a create omitting this field was refused with 400 and the error named the field, when objectType is "endpoint-agent" and type is "dynamic". Applied as a behaviour variant. Conditionally, when objectType is "endpoint-agent" and type is "dynamic": the field appeared in the read after 1 create(s), when objectType is "endpoint-agent" and type is "dynamic". Applied as a behaviour variant. (see [below for nested schema](#nestedatt--filters)) +- `icon` (String) The tag's icon. Observed: the API assigns "LABEL" when this is omitted. +- `match_type` (String) How the tag's filters combine when it is assigned dynamically. Values accepted here: `and`, `or`. Conditionally, when objectType is "endpoint-agent" and type is "dynamic": a create omitting this field was refused with 400 and the error named the field, when objectType is "endpoint-agent" and type is "dynamic". Applied as a behaviour variant. Conditionally, when objectType is "endpoint-agent": the field appeared in the read after 1 create(s), when objectType is "endpoint-agent". Applied as a behaviour variant. Conditionally, when objectType is "test": the field was sent on 1 create(s) and appeared in no read response, including every expansion the plan declares, when objectType is "test". Applied as a behaviour variant. The SDK's encoding drops this type's zero value (omitempty), so configuring false, 0 or "" silently becomes an omission. +- `timeouts` (Block, Optional) (see [below for nested schema](#nestedblock--timeouts)) +- `type` (String) The tag's type, assigned by the API. Conditionally, when objectType is "endpoint-agent": a create omitting this field was refused with 400 and the error named the field, when objectType is "endpoint-agent". Applied as a behaviour variant. The SDK's encoding drops this type's zero value (omitempty), so configuring false, 0 or "" silently becomes an omission. +- `value` (String) The tag's value. The API enforces this field's presence, which the specification does not declare. + +### Read-Only + +- `account_group_id` (Number) The account group the tag belongs to. Computed rather than configurable: the provider scopes every request through its own `account_group_id` setting, so accepting a second value here would let the two disagree. +- `assignments` (Attributes Set) Objects this tag is assigned to. A set rather than a list because the API does not preserve ordering. (see [below for nested schema](#nestedatt--assignments)) +- `built_in` (Boolean) Whether the tag is built in rather than user-created. +- `create_date` (String) When the tag was created. +- `id` (String) The tag's unique identifier, assigned by the API. +- `legacy_id` (Number) The tag's identifier in the v6 API. Typed as a number because the specification declares it as one, although observed values are integral; probing will settle whether this should be an integer. +- `modified_date` (String) When the tag was last modified. + + +### Nested Schema for `filters` + +Required: + +- `key` (String) The filter key used for matching. Conditionally, when objectType is "endpoint-agent" and type is "dynamic": the field appeared in the read after 1 create(s), when objectType is "endpoint-agent" and type is "dynamic". Applied as a behaviour variant. +- `values` (Set of String) The values the filter matches against. Conditionally, when objectType is "endpoint-agent" and type is "dynamic": the field appeared in the read after 1 create(s), when objectType is "endpoint-agent" and type is "dynamic". Applied as a behaviour variant. + +Optional: + +- `mode` (String) How the filter values are matched. Conditionally, when objectType is "endpoint-agent" and type is "dynamic": the field appeared in the read after 1 create(s), when objectType is "endpoint-agent" and type is "dynamic". Applied as a behaviour variant. +- `scope` (String) The scope the filter applies within. Conditionally, when objectType is "endpoint-agent" and type is "dynamic": the field appeared in the read after 1 create(s), when objectType is "endpoint-agent" and type is "dynamic". Applied as a behaviour variant. + + + +### Nested Schema for `timeouts` + +Optional: + +- `create` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). +- `delete` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Setting a timeout for a Delete operation is only applicable if changes are saved into state before the destroy operation occurs. +- `read` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). Read operations occur during any refresh or planning operation when refresh is enabled. +- `update` (String) A string that can be [parsed as a duration](https://pkg.go.dev/time#ParseDuration) consisting of numbers and unit suffixes, such as "30s" or "2h45m". Valid time units are "s" (seconds), "m" (minutes), "h" (hours). + + + +### Nested Schema for `assignments` + +Required: + +- `id` (String) The identifier of the object the tag is assigned to. +- `type` (String) The kind of object assigned. Documented values are `test`, `v-agent`, `endpoint-test`, `dashboard` and `connected-devices-test`. + +## Import + +Import is supported using the following syntax: + +The [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import) can be used, for example: + +```shell +terraform import thousandeyes_tag.example 1234 +``` diff --git a/pilot/thousandeyes-kiota/examples/provider/provider.tf b/pilot/thousandeyes-kiota/examples/provider/provider.tf new file mode 100644 index 00000000..993788e5 --- /dev/null +++ b/pilot/thousandeyes-kiota/examples/provider/provider.tf @@ -0,0 +1,11 @@ +terraform { + required_providers { + thousandeyes = { + source = "deploymenttheory/thousandeyes" + } + } +} + +provider "thousandeyes" { + # Credentials are read from the environment when not set here. +} diff --git a/pilot/thousandeyes-kiota/examples/resources/thousandeyes_tag/import.sh b/pilot/thousandeyes-kiota/examples/resources/thousandeyes_tag/import.sh new file mode 100644 index 00000000..1bdbebbe --- /dev/null +++ b/pilot/thousandeyes-kiota/examples/resources/thousandeyes_tag/import.sh @@ -0,0 +1 @@ +terraform import thousandeyes_tag.example 1234 diff --git a/pilot/thousandeyes-kiota/examples/resources/thousandeyes_tag/resource.tf b/pilot/thousandeyes-kiota/examples/resources/thousandeyes_tag/resource.tf new file mode 100644 index 00000000..822fd330 --- /dev/null +++ b/pilot/thousandeyes-kiota/examples/resources/thousandeyes_tag/resource.tf @@ -0,0 +1,6 @@ +resource "thousandeyes_tag" "example" { + key = "tfacc-key" + value = "tfacc-value" + object_type = "test" + access_type = "all" +} diff --git a/pilot/thousandeyes-kiota/go.mod b/pilot/thousandeyes-kiota/go.mod new file mode 100644 index 00000000..61ea6079 --- /dev/null +++ b/pilot/thousandeyes-kiota/go.mod @@ -0,0 +1,104 @@ +module github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota + +go 1.25.8 + +require ( + github.com/google/uuid v1.6.0 + github.com/hashicorp/terraform-plugin-framework v1.19.0 + github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0 + github.com/hashicorp/terraform-plugin-framework-validators v0.19.0 + github.com/hashicorp/terraform-plugin-go v0.31.0 + github.com/hashicorp/terraform-plugin-log v0.11.0 + github.com/hashicorp/terraform-plugin-testing v1.16.0 + github.com/microsoft/kiota-abstractions-go v1.9.4 + github.com/microsoft/kiota-http-go v1.5.6 + github.com/microsoft/kiota-serialization-form-go v1.1.2 + github.com/microsoft/kiota-serialization-json-go v1.1.4 + github.com/microsoft/kiota-serialization-multipart-go v1.1.2 + github.com/microsoft/kiota-serialization-text-go v1.1.3 +) + +require ( + github.com/BurntSushi/toml v1.2.1 // indirect + github.com/Kunde21/markdownfmt/v3 v3.1.0 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.2.0 // indirect + github.com/Masterminds/sprig/v3 v3.2.3 // indirect + github.com/ProtonMail/go-crypto v1.4.1 // indirect + github.com/agext/levenshtein v1.2.3 // indirect + github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect + github.com/armon/go-radix v1.0.0 // indirect + github.com/bgentry/speakeasy v0.1.0 // indirect + github.com/bmatcuk/doublestar/v4 v4.10.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/cloudflare/circl v1.6.3 // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/hashicorp/cli v1.1.7 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-checkpoint v0.5.0 // indirect + github.com/hashicorp/go-cleanhttp v0.5.2 // indirect + github.com/hashicorp/go-cty v1.5.0 // indirect + github.com/hashicorp/go-hclog v1.6.3 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-plugin v1.7.0 // indirect + github.com/hashicorp/go-retryablehttp v0.7.8 // indirect + github.com/hashicorp/go-uuid v1.0.3 // indirect + github.com/hashicorp/go-version v1.9.0 // indirect + github.com/hashicorp/hc-install v0.9.4 // indirect + github.com/hashicorp/hcl/v2 v2.24.0 // indirect + github.com/hashicorp/logutils v1.0.0 // indirect + github.com/hashicorp/terraform-exec v0.25.1 // indirect + github.com/hashicorp/terraform-json v0.27.3-0.20260213134036-298b8f6b673a // indirect + github.com/hashicorp/terraform-plugin-docs v0.25.0 // indirect + github.com/hashicorp/terraform-plugin-sdk/v2 v2.40.0 // indirect + github.com/hashicorp/terraform-registry-address v0.4.0 // indirect + github.com/hashicorp/terraform-svchost v0.2.1 // indirect + github.com/hashicorp/yamux v0.1.2 // indirect + github.com/huandu/xstrings v1.3.3 // indirect + github.com/imdario/mergo v0.3.15 // indirect + github.com/mattn/go-colorable v0.1.14 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.9 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/go-testing-interface v1.14.1 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/oklog/run v1.2.0 // indirect + github.com/posener/complete v1.2.3 // indirect + github.com/shopspring/decimal v1.3.1 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3 // indirect + github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect + github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect + github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect + github.com/yuin/goldmark v1.7.7 // indirect + github.com/yuin/goldmark-meta v1.1.0 // indirect + github.com/zclconf/go-cty v1.18.1 // indirect + go.abhg.dev/goldmark/frontmatter v0.2.0 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/otel v1.44.0 // indirect + go.opentelemetry.io/otel/metric v1.44.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.44.0 // indirect + go.opentelemetry.io/otel/trace v1.44.0 // indirect + golang.org/x/crypto v0.53.0 // indirect + golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect + golang.org/x/mod v0.37.0 // indirect + golang.org/x/net v0.56.0 // indirect + golang.org/x/sync v0.21.0 // indirect + golang.org/x/sys v0.46.0 // indirect + golang.org/x/text v0.39.0 // indirect + golang.org/x/tools v0.47.0 // indirect + google.golang.org/appengine v1.6.8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 // indirect + google.golang.org/grpc v1.82.1 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) + +tool github.com/hashicorp/terraform-plugin-docs/cmd/tfplugindocs diff --git a/pilot/thousandeyes-kiota/go.sum b/pilot/thousandeyes-kiota/go.sum new file mode 100644 index 00000000..099c69af --- /dev/null +++ b/pilot/thousandeyes-kiota/go.sum @@ -0,0 +1,322 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= +github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0= +github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.2.0 h1:3MEsd0SM6jqZojhjLWWeBY+Kcjy9i6MQAeY7YgDP83g= +github.com/Masterminds/semver/v3 v3.2.0/go.mod h1:qvl/7zhW3nngYb5+80sSMF+FG2BjYrf8m9wsX0PNOMQ= +github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj9n6YA= +github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM= +github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= +github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= +github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= +github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= +github.com/apparentlymart/go-textseg/v15 v15.0.0 h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY= +github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4= +github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bmatcuk/doublestar/v4 v4.10.0 h1:zU9WiOla1YA122oLM6i4EXvGW62DvKZVxIe6TYWexEs= +github.com/bmatcuk/doublestar/v4 v4.10.0/go.mod h1:xBQ8jztBU6kakFMg+8WGxn0c6z1fTSPVIjEY1Wr7jzc= +github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw= +github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= +github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= +github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= +github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.8.0 h1:I8hjc3LbBlXTtVuFNJuwYuMiHvQJDq1AT6u4DwDzZG0= +github.com/go-git/go-billy/v5 v5.8.0/go.mod h1:RpvI/rw4Vr5QA+Z60c6d6LXH0rYJo0uD5SqfmrrheCY= +github.com/go-git/go-git/v5 v5.18.0 h1:O831KI+0PR51hM2kep6T8k+w0/LIAD490gvqMCvL5hM= +github.com/go-git/go-git/v5 v5.18.0/go.mod h1:pW/VmeqkanRFqR6AljLcs7EA7FbZaN5MQqO7oZADXpo= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= +github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/google/go-cmp v0.3.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.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/hashicorp/cli v1.1.7 h1:/fZJ+hNdwfTSfsxMBa9WWMlfjUZbX8/LnUxgAd7lCVU= +github.com/hashicorp/cli v1.1.7/go.mod h1:e6Mfpga9OCT1vqzFuoGZiiF/KaG9CbUfO5s3ghU3YgU= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-checkpoint v0.5.0 h1:MFYpPZCnQqQTE18jFwSII6eUQrD/oxMFp3mlgcqk5mU= +github.com/hashicorp/go-checkpoint v0.5.0/go.mod h1:7nfLNL10NsxqO4iWuW6tWW0HjZuDrwkBuEQsVcpCOgg= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-cty v1.5.0 h1:EkQ/v+dDNUqnuVpmS5fPqyY71NXVgT5gf32+57xY8g0= +github.com/hashicorp/go-cty v1.5.0/go.mod h1:lFUCG5kd8exDobgSfyj4ONE/dc822kiYMguVKdHGMLM= +github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k= +github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA= +github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8= +github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48= +github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw= +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.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA= +github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/hc-install v0.9.4 h1:KKWOpUG0EqIV63Qk2GGFrZ0s275NVs5lKf9N5vjBNoc= +github.com/hashicorp/hc-install v0.9.4/go.mod h1:4LRYeEN2bMIFfIv57ldMWt9awfuZhvpbRt0vWmv51WU= +github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= +github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= +github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/terraform-exec v0.25.1 h1:PRutYRGM8pixV3B8812NYoBK5O+yuf3qcB/70KFKGiU= +github.com/hashicorp/terraform-exec v0.25.1/go.mod h1:+izOYrs9sKMQK4OYvGDnrSSJHY/pm4e4eXFqSL2Q5mA= +github.com/hashicorp/terraform-json v0.27.3-0.20260213134036-298b8f6b673a h1:T7AMR21kjrbeEpN+KhGlyd31XXHsSZF5zg+ivfeYte4= +github.com/hashicorp/terraform-json v0.27.3-0.20260213134036-298b8f6b673a/go.mod h1:yjb5C2W07l8lmAzdyVgOLji0/D2IoHkR3rusBzUO4O0= +github.com/hashicorp/terraform-plugin-docs v0.25.0 h1:qHs1V257NxVe8tv6HS4UQfNqjaPP5eUlLeDf7jYk85U= +github.com/hashicorp/terraform-plugin-docs v0.25.0/go.mod h1:MQggCmY8zgP7R7E/cC0b0cmTvA9hSj3ZKyrrsDjRbLo= +github.com/hashicorp/terraform-plugin-framework v1.19.0 h1:q0bwyhxAOR3vfdgbk9iplv3MlTv/dhBHTXjQOtQDoBA= +github.com/hashicorp/terraform-plugin-framework v1.19.0/go.mod h1:YRXOBu0jvs7xp4AThBbX4mAzYaMJ1JgtFH//oGKxwLc= +github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0 h1:jblRy1PkLfPm5hb5XeMa3tezusnMRziUGqtT5epSYoI= +github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0/go.mod h1:5jm2XK8uqrdiSRfD5O47OoxyGMCnwTcl8eoiDgSa+tc= +github.com/hashicorp/terraform-plugin-framework-validators v0.19.0 h1:Zz3iGgzxe/1XBkooZCewS0nJAaCFPFPHdNJd8FgE4Ow= +github.com/hashicorp/terraform-plugin-framework-validators v0.19.0/go.mod h1:GBKTNGbGVJohU03dZ7U8wHqc2zYnMUawgCN+gC0itLc= +github.com/hashicorp/terraform-plugin-go v0.31.0 h1:0Fz2r9DQ+kNNl6bx8HRxFd1TfMKUvnrOtvJPmp3Z0q8= +github.com/hashicorp/terraform-plugin-go v0.31.0/go.mod h1:A88bDhd/cW7FnwqxQRz3slT+QY6yzbHKc6AOTtmdeS8= +github.com/hashicorp/terraform-plugin-log v0.11.0 h1:WjhcpZIVqP8YRe83+dIZXncwSgtu4vh27i23G33PUQY= +github.com/hashicorp/terraform-plugin-log v0.11.0/go.mod h1:XygBz8+m5kgwTb73MMyrnUjeNQeVWECEfg+h2opMsj0= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.40.0 h1:MKS/2URqeJRwJdbOfcbdsZCq/IRrNkqJNN0GtVIsuGs= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.40.0/go.mod h1:PuG4P97Ju3QXW6c6vRkRadWJbvnEu2Xh+oOuqcYOqX4= +github.com/hashicorp/terraform-plugin-testing v1.16.0 h1:GB97nGnJ1hESpDrCjqZig38RodSF0gdRzxlDupLXP38= +github.com/hashicorp/terraform-plugin-testing v1.16.0/go.mod h1:eQPYAy9xFMV7xtIFX8Y+wJGtUB++HBl329zCF6PBMZk= +github.com/hashicorp/terraform-registry-address v0.4.0 h1:S1yCGomj30Sao4l5BMPjTGZmCNzuv7/GDTDX99E9gTk= +github.com/hashicorp/terraform-registry-address v0.4.0/go.mod h1:LRS1Ay0+mAiRkUyltGT+UHWkIqTFvigGn/LbMshfflE= +github.com/hashicorp/terraform-svchost v0.2.1 h1:ubvrTFw3Q7CsoEaX7V06PtCTKG3wu7GyyobAoN4eF3Q= +github.com/hashicorp/terraform-svchost v0.2.1/go.mod h1:zDMheBLvNzu7Q6o9TBvPqiZToJcSuCLXjAXxBslSky4= +github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8= +github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns= +github.com/huandu/xstrings v1.3.3 h1:/Gcsuc1x8JVbJ9/rlye4xZnVAbEkGauT8lbebqcQws4= +github.com/huandu/xstrings v1.3.3/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.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= +github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94= +github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE= +github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.9 h1:Lm995f3rfxdpd6TSmuVCHVb/QhupuXlYr8sCI/QdE+0= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= +github.com/microsoft/kiota-abstractions-go v1.9.4 h1:VI3UVzSCQHHhRswe3jyaAQHUQWIFhUMp0z5mtZbTbcs= +github.com/microsoft/kiota-abstractions-go v1.9.4/go.mod h1:f06pl3qSyvUHEfVNkiRpXPkafx7khZqQEb71hN/pmuU= +github.com/microsoft/kiota-http-go v1.5.6 h1:KBdk7sxWYXZnRRExLjIcNt4I7LoOfh/XQJWWid4zBKE= +github.com/microsoft/kiota-http-go v1.5.6/go.mod h1:bpJkXfBAcnmiXRg03GXdnb/vF3Sqk3+EgLvXXjmzzQM= +github.com/microsoft/kiota-serialization-form-go v1.1.2 h1:SD6MATqNw+Dc5beILlsb/D87C36HKC/Zw7l+N9+HY2A= +github.com/microsoft/kiota-serialization-form-go v1.1.2/go.mod h1:m4tY2JT42jAZmgbqFwPy3zGDF+NPJACuyzmjNXeuHio= +github.com/microsoft/kiota-serialization-json-go v1.1.4 h1:XAhsBjxiunPgsPLLHEbuvAe81Q3wD1pNOE22Dq2CD3k= +github.com/microsoft/kiota-serialization-json-go v1.1.4/go.mod h1:HUTiYs9llTGLjh9+O+yOkBbNEaZ1kxh3sBPU5tPhmeI= +github.com/microsoft/kiota-serialization-multipart-go v1.1.2 h1:1pUyA1QgIeKslQwbk7/ox1TehjlCUUT3r1f8cNlkvn4= +github.com/microsoft/kiota-serialization-multipart-go v1.1.2/go.mod h1:j2K7ZyYErloDu7Kuuk993DsvfoP7LPWvAo7rfDpdPio= +github.com/microsoft/kiota-serialization-text-go v1.1.3 h1:8z7Cebn0YAAr++xswVgfdxZjnAZ4GOB9O7XP4+r5r/M= +github.com/microsoft/kiota-serialization-text-go v1.1.3/go.mod h1:NDSvz4A3QalGMjNboKKQI9wR+8k+ih8UuagNmzIRgTQ= +github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= +github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/reflectwalk v1.0.0/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E= +github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk= +github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4= +github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8= +github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= +github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= +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/std-uritemplate/std-uritemplate/go/v2 v2.0.3 h1:7hth9376EoQEd1hH4lAp3vnaLP2UMyxuMMghLKzDHyU= +github.com/std-uritemplate/std-uritemplate/go/v2 v2.0.3/go.mod h1:Z5KcoM0YLC7INlNhEezeIZ0TZNYf7WSNO0Lvah4DSeQ= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack v4.0.4+incompatible h1:dSLoQfGFAo3F6OoNhwUmLwVgaUXK79GlxNBwueZn0xI= +github.com/vmihailenco/msgpack v4.0.4+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= +github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8= +github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok= +github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= +github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yuin/goldmark v1.7.7 h1:5m9rrB1sW3JUMToKFQfb+FGt1U7r57IHu5GrYrG2nqU= +github.com/yuin/goldmark v1.7.7/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= +github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= +github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= +github.com/zclconf/go-cty v1.18.1 h1:yEGE8M4iIZlyKQURZNb2SnEyZlZHUcBCnx6KF81KuwM= +github.com/zclconf/go-cty v1.18.1/go.mod h1:qpnV6EDNgC1sns/AleL1fvatHw72j+S+nS+MJ+T2CSg= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= +go.abhg.dev/goldmark/frontmatter v0.2.0 h1:P8kPG0YkL12+aYk2yU3xHv4tcXzeVnN+gU0tJ5JnxRw= +go.abhg.dev/goldmark/frontmatter v0.2.0/go.mod h1:XqrEkZuM57djk7zrlRUB02x8I5J0px76YjkOzhB4YlU= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/otel v1.44.0 h1:JjwHmHpA4iZ3wBxluu2fbbE7j4kqlE8jXyAyPXH7HqU= +go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= +go.opentelemetry.io/otel/metric v1.44.0 h1:1w0gILTcHdr3YI+ixLyjemwrVnsMURbTZFrSYCdDdmc= +go.opentelemetry.io/otel/metric v1.44.0/go.mod h1:8O7hanEPBNgEMmybD3s2VBKcgWOCsA6tzHBPODAiquo= +go.opentelemetry.io/otel/sdk v1.44.0 h1:nHYwb9lK+fJPU/dnT6s7W7Z8itMWyqrnVfbheVYrZ58= +go.opentelemetry.io/otel/sdk v1.44.0/go.mod h1:Osuydd3Se74nqjAKxid74N5eC+jfEqfTegHRnq58oK0= +go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= +go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= +go.opentelemetry.io/otel/trace v1.44.0 h1:jxF5CsGYCe74MCRx2X4g7WsY/VBKRqqpNvXlX/6gtIk= +go.opentelemetry.io/otel/trace v1.44.0/go.mod h1:oLl1jrMQAVo6v3GAggN+1VH9VIz9iUSvW53sW1Q8PIE= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.53.0 h1:QZ4Muo8THX6CizN2vPPd5fBGHyogrdK9fG4wLPFUsto= +golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= +golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.37.0 h1:vF1DjpVEshcIqoEaauuHebaLk1O1forxjxBaVn884JQ= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.56.0 h1:Rw8j/hFzGvJUZwNBXnAtf5sVDVt+65SK2C7IxCxZt5o= +golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.21.0 h1:HLII4xRRTtCRkxYp4HNFF0Js/Og6q2i++KXbg0gHCwM= +golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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-20201119102817-f84b799fce68/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.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.46.0 h1:noSf2Fq6F8DBgS+LysIkx7rIExoNHJsxOAtPp4rthXw= +golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.39.0 h1:UbZz4pLOvn600D6Oh6GGEI6VAmndrEBLv8/6BEXzyus= +golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.47.0 h1:7Kn5x/d1svx/PzryTsqeoZN4TZwqeH5pGWjefhLi/1Q= +golang.org/x/tools v0.47.0/go.mod h1:dFHnyTvFWY212G+h7ZY4Vsp/K3U4/7W9TyVaAul8uCA= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= +gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= +google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478 h1:RmoJA1ujG+/lRGNfUnOMfhCy5EipVMyvUE+KNbPbTlw= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260414002931-afd174a4e478/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.82.1 h1:NnAxzGRA0677vCa4BUkOAnO5+FfQqVl9iUXeD0IqcGE= +google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +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-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +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.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +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/pilot/thousandeyes-kiota/internal/acceptance/acceptance.go b/pilot/thousandeyes-kiota/internal/acceptance/acceptance.go new file mode 100644 index 00000000..a8d43475 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/acceptance/acceptance.go @@ -0,0 +1,55 @@ +// Package acceptance holds what every acceptance test needs before it can run: the provider +// under test, and the check that it is safe to run at all. +// +// It is hand-written and owned, like internal/client, because authentication and the decision to +// touch a live tenant are not things a generator should decide. +package acceptance + +import ( + "os" + "testing" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-testing/echoprovider" + + tfprovider "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/provider" +) + +// envBearerToken is the credential an acceptance run needs. +// +// Read from the environment and never from a flag: a flag lands in shell history and in the +// process table, where a token that cannot be revoked through the ThousandEyes API would stay +// valid until somebody deleted it by hand. +const envBearerToken = "THOUSANDEYES_BEARER_TOKEN" + +// TestVersion is the version string the provider under test reports. +const TestVersion = "acc" + +// ProtoV6ProviderFactories is what a resource.TestCase needs to serve the provider in process. +// +// The provider under test is named as it is in HCL, so a generated `.tf` fixture needs no +// provider block of its own. The echo provider rides beside it for ephemeral tests only: +// it is HashiCorp's fixture for observing an ephemeral value, which by design never +// reaches state where an ordinary check could read it. +var ProtoV6ProviderFactories = map[string]func() (tfprotov6.ProviderServer, error){ + "thousandeyes": providerserver.NewProtocol6WithError(tfprovider.New(TestVersion)()), + "echo": echoprovider.NewProviderServer(), +} + +// PreCheck skips or fails a test that cannot possibly pass. +// +// terraform-plugin-testing already requires TF_ACC, so this is only about credentials. Fatal +// rather than a skip: if somebody has set TF_ACC they have asked for a live run, and silently +// skipping would let a CI job report success having tested nothing. +func PreCheck(t *testing.T) { + t.Helper() + + if os.Getenv(envBearerToken) == "" { + t.Fatalf( + "%s must be set for an acceptance run. It is read from the environment only -- "+ + "never pass it as a flag.", + envBearerToken, + ) + } +} diff --git a/pilot/thousandeyes-kiota/internal/acceptance/check/check.go b/pilot/thousandeyes-kiota/internal/acceptance/check/check.go new file mode 100644 index 00000000..f15ad605 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/acceptance/check/check.go @@ -0,0 +1,141 @@ +// Package check is a fluent front end to terraform-plugin-testing's check functions. +// +// The value is legibility at the call site. `resource.TestCheckResourceAttr(n, "colour", "#A7EB10")` +// and `resource.TestCheckResourceAttrSet(n, "id")` are different function names for the same idea, +// and a generated test is mostly a long list of them. `check.That(n).Key("colour").HasValue(...)` +// reads as a sentence and, more usefully, groups by resource so the generator writes the address +// once rather than on every line. +package check + +import ( + "fmt" + "regexp" + "strconv" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/acceptance/types" +) + +// That begins an assertion about one resource address, e.g. "thousandeyes_tag.test". +func That(address string) Resource { + return Resource{address: address} +} + +// Resource is an assertion about the object as a whole. +type Resource struct { + address string +} + +// ExistsInAPI asserts the object is really present, by asking the API rather than the state. +// +// Distinct from Key("id").Exists(), which only proves Terraform wrote something down. A resource +// can be in state and absent from the API -- that is precisely the drift an acceptance test is +// there to catch. +func (r Resource) ExistsInAPI(tr types.TestResource) resource.TestCheckFunc { + return func(s *terraform.State) error { + state, err := instanceState(s, r.address) + if err != nil { + return err + } + + present, err := tr.Exists(testContext(), state) + if err != nil { + return fmt.Errorf("checking whether %s exists: %w", r.address, err) + } + if present == nil { + return fmt.Errorf("%s: the existence check answered neither yes nor no", r.address) + } + if !*present { + return fmt.Errorf("%s is in state but absent from the API", r.address) + } + + return nil + } +} + +// Key narrows the assertion to one attribute, e.g. "colour" or "assignments.0.group_id". +func (r Resource) Key(key string) Attribute { + return Attribute{address: r.address, key: key} +} + +// Attribute is an assertion about one attribute of one resource. +type Attribute struct { + address string + key string +} + +// HasValue asserts an exact value. +func (a Attribute) HasValue(value string) resource.TestCheckFunc { + return resource.TestCheckResourceAttr(a.address, a.key, value) +} + +// Exists asserts the attribute is set to something. +func (a Attribute) Exists() resource.TestCheckFunc { + return resource.TestCheckResourceAttrSet(a.address, a.key) +} + +// DoesNotExist asserts the attribute is absent from state entirely. +func (a Attribute) DoesNotExist() resource.TestCheckFunc { + return resource.TestCheckNoResourceAttr(a.address, a.key) +} + +// MatchesRegex asserts the value matches a pattern. +// +// This is what a generated constraint assertion uses: the specification's own `pattern` becomes +// the expectation, so a server that stores something outside its documented shape is caught. +func (a Attribute) MatchesRegex(re *regexp.Regexp) resource.TestCheckFunc { + return resource.TestMatchResourceAttr(a.address, a.key, re) +} + +// MatchesOtherKey asserts two attributes agree, possibly across resources. +func (a Attribute) MatchesOtherKey(other Attribute) resource.TestCheckFunc { + return resource.TestCheckResourceAttrPair(a.address, a.key, other.address, other.key) +} + +// IsNotEmpty asserts the attribute is set and not the empty string. +// +// Separate from Exists because Terraform records an empty string as set, so an attribute the API +// returned as "" satisfies Exists while carrying no information. +func (a Attribute) IsNotEmpty() resource.TestCheckFunc { + return resource.TestCheckResourceAttrWith(a.address, a.key, func(value string) error { + if value == "" { + return fmt.Errorf("is set but empty") + } + + return nil + }) +} + +// CountAtLeast asserts a collection's count key holds at least n elements. +// +// Used on a `.#` key. IsNotEmpty cannot serve here: Terraform records an empty +// collection's count as the string "0", which is set and non-empty while carrying +// exactly the information the assertion exists to refuse. +func (a Attribute) CountAtLeast(n int) resource.TestCheckFunc { + return resource.TestCheckResourceAttrWith(a.address, a.key, func(value string) error { + count, err := strconv.Atoi(value) + if err != nil { + return fmt.Errorf("%q is not a count; is this a collection's # key?", value) + } + if count < n { + return fmt.Errorf("holds %d element(s), want at least %d", count, n) + } + + return nil + }) +} + +// instanceState finds one resource's primary state, with a message that says which. +func instanceState(s *terraform.State, address string) (*terraform.InstanceState, error) { + rs, ok := s.RootModule().Resources[address] + if !ok { + return nil, fmt.Errorf("%s is not in the state", address) + } + if rs.Primary == nil { + return nil, fmt.Errorf("%s has no primary instance state", address) + } + + return rs.Primary, nil +} diff --git a/pilot/thousandeyes-kiota/internal/acceptance/check/context.go b/pilot/thousandeyes-kiota/internal/acceptance/check/context.go new file mode 100644 index 00000000..64eab37c --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/acceptance/check/context.go @@ -0,0 +1,11 @@ +package check + +import "context" + +// testContext is the context a check runs under. +// +// terraform-plugin-testing's TestCheckFunc signature takes only a state, so there is no context +// to inherit and one has to be made. Deliberately not context.TODO(): the deadline belongs to +// the SDK call, which applies its own, and a check that timed out here would report a resource +// as absent when the request had merely been cut short. +func testContext() context.Context { return context.Background() } diff --git a/pilot/thousandeyes-kiota/internal/acceptance/destroy/destroy.go b/pilot/thousandeyes-kiota/internal/acceptance/destroy/destroy.go new file mode 100644 index 00000000..9a1742da --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/acceptance/destroy/destroy.go @@ -0,0 +1,88 @@ +// Package destroy verifies that a destroyed resource really left the API. +// +// Terraform considers a destroy successful when the provider's Delete returns no error, which is +// a statement about the request rather than about the object. An API that accepts a delete and +// then keeps the object -- or deletes it asynchronously and is not done yet -- passes that bar +// and fails this one. +package destroy + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/acceptance/types" + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/services/common/crud" +) + +// CheckDestroyed returns a CheckDestroy for every instance of one resource type in the state. +// +// The timeout exists because deletion is not always synchronous. Where the prober measured how +// long this API takes to become consistent, the generated test passes that measurement in; the +// caller supplying it rather than this package choosing is what keeps a measured wait +// distinguishable from a guessed one. +func CheckDestroyed( + tr types.TestResource, + resourceType string, + timeout time.Duration, +) resource.TestCheckFunc { + return func(s *terraform.State) error { + for address, rs := range s.RootModule().Resources { + if rs.Type != resourceType || rs.Primary == nil { + continue + } + + if err := confirmGone(tr, address, rs.Primary, timeout); err != nil { + return err + } + } + + return nil + } +} + +// confirmGone polls until the object is absent, or gives up and says so. +// +// crud.ReadBack is the provider's own retry loop, reused with its predicate inverted: it polls +// until its read reports success, so "success" here is the object being *gone*. Reusing it rather +// than writing a second loop means the test cannot disagree with the provider about how long to +// wait or how to treat a hard failure -- ReadBack returns a real error immediately rather than +// burning the whole budget on a 403. +func confirmGone( + tr types.TestResource, + address string, + state *terraform.InstanceState, + timeout time.Duration, +) error { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + opts := crud.DefaultReadBackOptions() + opts.ResourceType = state.Attributes["id"] + opts.Operation = "destroy" + opts.Reason = "an accepted delete does not prove the object is gone" + + err := crud.ReadBack(ctx, opts, func(ctx context.Context) (bool, error) { + present, existsErr := tr.Exists(ctx, state) + if existsErr != nil { + return false, existsErr + } + if present == nil { + return false, fmt.Errorf("the existence check answered neither yes nor no") + } + + // Inverted: ReadBack stops when this is true, and what we are waiting for is absence. + return !*present, nil + }) + if err != nil { + return fmt.Errorf( + "%s (id %q) was still present %s after being destroyed: %w", + address, state.ID, timeout, err, + ) + } + + return nil +} diff --git a/pilot/thousandeyes-kiota/internal/acceptance/exists/client.go b/pilot/thousandeyes-kiota/internal/acceptance/exists/client.go new file mode 100644 index 00000000..a40a042e --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/acceptance/exists/client.go @@ -0,0 +1,57 @@ +package exists + +import ( + "fmt" + "os" + "sync" + + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk" + + teclient "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/client" +) + +// Environment variables an acceptance run reads. +// +// The same names the provider itself reads, deliberately: a test that authenticated differently +// from the provider under test could pass while the provider was misconfigured. +const ( + envBearerToken = "THOUSANDEYES_BEARER_TOKEN" + envAccountGroupID = "THOUSANDEYES_ACCOUNT_GROUP_ID" + envAPIEndpoint = "THOUSANDEYES_API_ENDPOINT" +) + +var ( + clientOnce sync.Once + clientRef *sdk.ThousandEyesClient + clientErr error +) + +// Client returns a client for checking objects out of band. +// +// Out of band is the point: the provider's own client is not reachable from a test check, and +// reaching for it would mean the assertion and the code under test shared a code path. A +// separate client asks the API directly, so "the resource exists" is not merely "the provider +// believes it does". +// +// Built once per run, because a test case can hold dozens of checks and each building its own +// client would spend the run authenticating. +func Client() (*sdk.ThousandEyesClient, error) { + clientOnce.Do(func() { + token := os.Getenv(envBearerToken) + if token == "" { + clientErr = fmt.Errorf( + "%s is not set, so no acceptance check can reach the API", envBearerToken, + ) + + return + } + + clientRef, clientErr = teclient.New(teclient.Config{ + BearerToken: token, + AccountGroupID: os.Getenv(envAccountGroupID), + APIEndpoint: os.Getenv(envAPIEndpoint), + }) + }) + + return clientRef, clientErr +} diff --git a/pilot/thousandeyes-kiota/internal/acceptance/exists/exists.go b/pilot/thousandeyes-kiota/internal/acceptance/exists/exists.go new file mode 100644 index 00000000..a683f78c --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/acceptance/exists/exists.go @@ -0,0 +1,62 @@ +// Package exists turns "the read call returned an error" into "the object is not there". +// +// Every generated test helper needs that translation and none of them should own it: a helper +// that decided for itself which errors mean absent would be one place the rule could be got +// wrong per resource. +package exists + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk" + + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/services/common/errors" +) + +// ReadFunc performs the API read whose success or failure answers the question. +// +// It returns only an error because the object itself is not wanted -- the test asks whether the +// read succeeded, not what came back. Anything more would invite assertions here, and those +// belong in the test's own Check functions where a reader can see them. A tag's ReadFunc, for +// instance, is one fluent call: client.Tags().ById(state.ID).Get(ctx, nil). +type ReadFunc func(ctx context.Context, client *sdk.ThousandEyesClient, state *terraform.InstanceState) error + +// Check reports whether an object exists, by attempting to read it. +// +// A not-found is a successful answer of "no", not a failure. Every other error is a failure to +// answer, and is returned as one: reporting "absent" because the token expired would turn a +// broken test run into a passing destroy check. +// +// errors.IsNotFound is the same predicate the generated CRUD uses to decide that a resource has +// been deleted out from under Terraform. Sharing it means an acceptance test cannot disagree +// with the provider about what "gone" looks like. +func Check(ctx context.Context, state *terraform.InstanceState, read ReadFunc) (*bool, error) { + if state == nil { + return nil, fmt.Errorf("no instance state, so there is no object to look for") + } + if state.ID == "" { + return nil, fmt.Errorf("the instance state carries no id") + } + + client, err := Client() + if err != nil { + return nil, err + } + + if readErr := read(ctx, client, state); readErr != nil { + if errors.IsNotFound(readErr) { + absent := false + + return &absent, nil + } + + return nil, fmt.Errorf("reading %s to check whether it exists: %w", state.ID, readErr) + } + + present := true + + return &present, nil +} diff --git a/pilot/thousandeyes-kiota/internal/acceptance/testlog/testlog.go b/pilot/thousandeyes-kiota/internal/acceptance/testlog/testlog.go new file mode 100644 index 00000000..e3a91106 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/acceptance/testlog/testlog.go @@ -0,0 +1,35 @@ +// Package testlog narrates an acceptance run. +// +// An acceptance test spends most of its wall clock waiting on a live API, and a run that prints +// nothing is indistinguishable from a run that has hung. These lines are the difference between +// "it is stuck" and "it is waiting 30 seconds for consistency, as measured". +package testlog + +import ( + "fmt" + "os" + "time" +) + +// StepAction announces what a test step is about to do. +func StepAction(resourceType, action string) { + announce("STEP", "%s: %s", resourceType, action) +} + +// WaitForConsistency announces a deliberate wait, and why it is that long. +// +// The reason is the point. A bare sleep in a test is indistinguishable from superstition; one +// that names the fact it came from can be shortened when the measurement changes, and deleted +// when the API stops needing it. +func WaitForConsistency(resourceType string, d time.Duration, reason string) { + announce("WAIT", "%s: waiting %s before asserting -- %s", resourceType, d, reason) +} + +// Cleanup announces out-of-band tidying, which is the part that matters when it fails. +func Cleanup(resourceType, detail string) { + announce("CLEANUP", "%s: %s", resourceType, detail) +} + +func announce(kind, format string, args ...any) { + fmt.Fprintf(os.Stderr, "[acc %s] %s\n", kind, fmt.Sprintf(format, args...)) +} diff --git a/pilot/thousandeyes-kiota/internal/acceptance/types/types.go b/pilot/thousandeyes-kiota/internal/acceptance/types/types.go new file mode 100644 index 00000000..1bb2bca5 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/acceptance/types/types.go @@ -0,0 +1,28 @@ +// Package types declares what an acceptance test needs to know about a resource. +// +// It is one interface, in its own package, for an import-cycle reason: the per-resource test +// helper lives in the resource's own package and implements this, while `check` and `destroy` +// consume it. If the interface lived in either of those, the resource package would import the +// checker and the checker would import the resource package. +package types + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +// TestResource is how an acceptance test asks the API whether an object is really there. +// +// Every resource gets one, and it is generated rather than hand-written: the question "does +// this object exist" is answered by the resource's own read operation, which the blueprint +// already describes. +type TestResource interface { + // Exists reports whether the object named by the state is present in the API. + // + // The tri-state return is the point. A nil *bool with an error means the check itself + // failed -- a network problem, a bad token -- which is not the same as the object being + // absent, and a test that conflated the two would report a passing destroy for an API + // that was simply unreachable. + Exists(ctx context.Context, state *terraform.InstanceState) (*bool, error) +} diff --git a/pilot/thousandeyes-kiota/internal/client/client.go b/pilot/thousandeyes-kiota/internal/client/client.go new file mode 100644 index 00000000..9db1bb31 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/client/client.go @@ -0,0 +1,180 @@ +// Package client builds the ThousandEyes SDK client and hands it to resources. +// +// It is hand-written and owned by the provider. Authentication is the one part of +// a provider that is always bespoke, and the generator makes no attempt at it: a +// generated resource only ever asks this package for a configured client. +package client + +import ( + "context" + "fmt" + "net/url" + + "github.com/hashicorp/terraform-plugin-framework/action" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/ephemeral" + "github.com/hashicorp/terraform-plugin-framework/resource" + + "github.com/microsoft/kiota-abstractions-go/authentication" + kiotahttp "github.com/microsoft/kiota-http-go" + + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk" +) + +// defaultAPIEndpoint is the v7 base URL the SDK itself defaults to, restated +// here because the allowed-hosts validator needs the host before the SDK's +// default has been applied. +const defaultAPIEndpoint = "https://api.thousandeyes.com/v7" + +// Config is what the provider block resolves to, independent of the framework's +// value types, so building a client is testable without a Terraform plan. +type Config struct { + BearerToken string + AccountGroupID string + APIEndpoint string +} + +// staticTokenProvider hands Kiota's authentication pipeline a token that never +// changes. ThousandEyes tokens are created by hand and do not expire, so there +// is nothing to refresh and no flow to run. +// +// The token must never reach a log file: it cannot be revoked through the API, +// so one that leaks stays valid until somebody deletes it by hand in the UI. +// Nothing here or in the Kiota pipeline prints it. +type staticTokenProvider struct { + token string + validator *authentication.AllowedHostsValidator +} + +func (p *staticTokenProvider) GetAuthorizationToken(_ context.Context, _ *url.URL, _ map[string]any) (string, error) { + return p.token, nil +} + +func (p *staticTokenProvider) GetAllowedHostsValidator() *authentication.AllowedHostsValidator { + return p.validator +} + +// New builds an SDK client. +func New(cfg Config) (*sdk.ThousandEyesClient, error) { + endpoint := cfg.APIEndpoint + if endpoint == "" { + endpoint = defaultAPIEndpoint + } + + parsed, err := url.Parse(endpoint) + if err != nil || parsed.Hostname() == "" { + return nil, fmt.Errorf("the API endpoint %q is not a usable URL", endpoint) + } + + // The validator pins the bearer token to the configured host, so a redirect + // or a mis-set endpoint cannot leak the credential to a host it was never + // meant for. + validator, err := authentication.NewAllowedHostsValidatorErrorCheck([]string{parsed.Hostname()}) + if err != nil { + return nil, fmt.Errorf("building the allowed-hosts validator: %w", err) + } + + authProvider := authentication.NewBaseBearerTokenAuthenticationProvider(&staticTokenProvider{ + token: cfg.BearerToken, + validator: validator, + }) + + adapter, err := kiotahttp.NewNetHttpRequestAdapter(authProvider) + if err != nil { + return nil, fmt.Errorf("building the ThousandEyes client: %w", err) + } + adapter.SetBaseUrl(endpoint) + + // AccountGroupID is deliberately unused here. The resty SDK attached it to + // every request as the aid query parameter; Kiota models aid per request + // instead, so generated code applies it where an operation declares it + // rather than this package inventing request middleware. + _ = cfg.AccountGroupID + + return sdk.NewThousandEyesClient(adapter), nil +} + +// ForResource retrieves the configured client during a resource's Configure. +// +// It tolerates a nil ProviderData because the framework calls Configure once with +// no data, before the provider itself has been configured. Treating that as an +// error produces a spurious diagnostic on every run. +func ForResource(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse, resourceType string) *sdk.ThousandEyesClient { + if req.ProviderData == nil { + return nil + } + + client, ok := req.ProviderData.(*sdk.ThousandEyesClient) + if !ok { + resp.Diagnostics.AddError( + "Unexpected provider data", + fmt.Sprintf("%s expected a *sdk.ThousandEyesClient but got %T. This is a bug in the provider.", + resourceType, req.ProviderData), + ) + return nil + } + + return client +} + +// ForAction is the action counterpart of ForResource. +// +// A third near-identical function rather than one generic helper, because the three +// ConfigureRequest types are distinct structs with no common interface -- the framework gives +// each block kind its own. A generic version would need a type parameter per field it reads, +// which is more machinery than the duplication costs. +func ForAction(_ context.Context, req action.ConfigureRequest, resp *action.ConfigureResponse, actionType string) *sdk.ThousandEyesClient { + if req.ProviderData == nil { + return nil + } + + client, ok := req.ProviderData.(*sdk.ThousandEyesClient) + if !ok { + resp.Diagnostics.AddError( + "Unexpected provider data", + fmt.Sprintf("%s expected a *sdk.ThousandEyesClient but got %T. This is a bug in the provider.", + actionType, req.ProviderData), + ) + return nil + } + + return client +} + +// ForDataSource is the data source counterpart of ForResource. +func ForDataSource(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse, resourceType string) *sdk.ThousandEyesClient { + if req.ProviderData == nil { + return nil + } + + client, ok := req.ProviderData.(*sdk.ThousandEyesClient) + if !ok { + resp.Diagnostics.AddError( + "Unexpected provider data", + fmt.Sprintf("%s expected a *sdk.ThousandEyesClient but got %T. This is a bug in the provider.", + resourceType, req.ProviderData), + ) + return nil + } + + return client +} + +// ForEphemeral is the ephemeral resource counterpart of ForResource. +func ForEphemeral(_ context.Context, req ephemeral.ConfigureRequest, resp *ephemeral.ConfigureResponse, ephemeralType string) *sdk.ThousandEyesClient { + if req.ProviderData == nil { + return nil + } + + client, ok := req.ProviderData.(*sdk.ThousandEyesClient) + if !ok { + resp.Diagnostics.AddError( + "Unexpected provider data", + fmt.Sprintf("%s expected a *sdk.ThousandEyesClient but got %T. This is a bug in the provider.", + ephemeralType, req.ProviderData), + ) + return nil + } + + return client +} diff --git a/pilot/thousandeyes-kiota/internal/provider/actions.go b/pilot/thousandeyes-kiota/internal/provider/actions.go new file mode 100644 index 00000000..617a7e3b --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/provider/actions.go @@ -0,0 +1,21 @@ +// Code generated by tfpfgen from blueprints/thousandeyes-kiota +// (sha256:82a8701a0f77258aba49054f577921f206b8fcaecd42c5fb57874c3fd6799173). DO NOT EDIT. + +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/action" +) + +// Actions returns every action this provider serves. +// +// Declaring this method is what makes the provider satisfy provider.ProviderWithActions, so +// adding an action needs no hand-written change here. +// +// This file is generated in full. Add an action by adding a blueprint and regenerating, not +// by editing here. +func (p *Provider) Actions(_ context.Context) []func() action.Action { + return []func() action.Action{} +} diff --git a/pilot/thousandeyes-kiota/internal/provider/datasources.go b/pilot/thousandeyes-kiota/internal/provider/datasources.go new file mode 100644 index 00000000..5ea310b6 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/provider/datasources.go @@ -0,0 +1,18 @@ +// Code generated by tfpfgen from blueprints/thousandeyes-kiota +// (sha256:82a8701a0f77258aba49054f577921f206b8fcaecd42c5fb57874c3fd6799173). DO NOT EDIT. + +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/datasource" +) + +// DataSources returns every data source this provider serves. +// +// This file is generated in full. Add a data source by adding a blueprint and +// regenerating, not by editing here. +func (p *Provider) DataSources(_ context.Context) []func() datasource.DataSource { + return []func() datasource.DataSource{} +} diff --git a/pilot/thousandeyes-kiota/internal/provider/ephemerals.go b/pilot/thousandeyes-kiota/internal/provider/ephemerals.go new file mode 100644 index 00000000..a51c8c39 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/provider/ephemerals.go @@ -0,0 +1,18 @@ +// Code generated by tfpfgen from blueprints/thousandeyes-kiota +// (sha256:82a8701a0f77258aba49054f577921f206b8fcaecd42c5fb57874c3fd6799173). DO NOT EDIT. + +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/ephemeral" +) + +// EphemeralResources returns every ephemeral resource this provider serves. +// +// This file is generated in full. Add an ephemeral by adding a blueprint and +// regenerating, not by editing here. +func (p *Provider) EphemeralResources(_ context.Context) []func() ephemeral.EphemeralResource { + return []func() ephemeral.EphemeralResource{} +} diff --git a/pilot/thousandeyes-kiota/internal/provider/interfaces.go b/pilot/thousandeyes-kiota/internal/provider/interfaces.go new file mode 100644 index 00000000..95b5a8e0 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/provider/interfaces.go @@ -0,0 +1,15 @@ +// Package provider's compile-time interface assertions. +// +// Hand-written and owned by the provider, so generation never touches this file. It exists to +// turn "the generated registration files are what make this provider serve each block kind" +// from a claim into a build failure if it stops being true. +// +// Only the core provider.Provider assertion is present for now: the Kiota pilot's generated +// surface starts empty, and the ProviderWithListResources, ProviderWithActions and +// ProviderWithEphemeralResources assertions return alongside the generated files that satisfy +// them -- asserting them earlier would fail the build the assertions exist to protect. +package provider + +import "github.com/hashicorp/terraform-plugin-framework/provider" + +var _ provider.Provider = (*Provider)(nil) diff --git a/pilot/thousandeyes-kiota/internal/provider/list_resources.go b/pilot/thousandeyes-kiota/internal/provider/list_resources.go new file mode 100644 index 00000000..2ddc05c5 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/provider/list_resources.go @@ -0,0 +1,22 @@ +// Code generated by tfpfgen from blueprints/thousandeyes-kiota +// (sha256:82a8701a0f77258aba49054f577921f206b8fcaecd42c5fb57874c3fd6799173). DO NOT EDIT. + +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/list" +) + +// ListResources returns every list resource this provider serves. +// +// Declaring this method is what makes the provider satisfy +// provider.ProviderWithListResources, so a resource gaining a list facet needs no +// hand-written change here -- which is the point of generating the file whole. +// +// This file is generated in full. Add a list resource by giving a resource a list facet in +// its blueprint and regenerating, not by editing here. +func (p *Provider) ListResources(_ context.Context) []func() list.ListResource { + return []func() list.ListResource{} +} diff --git a/pilot/thousandeyes-kiota/internal/provider/provider.go b/pilot/thousandeyes-kiota/internal/provider/provider.go new file mode 100644 index 00000000..ea892b0d --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/provider/provider.go @@ -0,0 +1,168 @@ +// Package provider is the provider server. +// +// The schema and configuration plumbing here are hand-written, because +// authentication is always bespoke. The resource and data source registration +// lists are generated: see resources.go and datasources.go, which carry a +// generated header and must not be edited. +package provider + +import ( + "context" + "os" + + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/provider/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + + teclient "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/client" +) + +// Environment variables the provider falls back to, matching the SDK's own +// names so a practitioner does not have to learn two sets. +const ( + envBearerToken = "THOUSANDEYES_BEARER_TOKEN" + envAccountGroupID = "THOUSANDEYES_ACCOUNT_GROUP_ID" + envAPIEndpoint = "THOUSANDEYES_API_ENDPOINT" +) + +var _ provider.Provider = (*Provider)(nil) + +// Provider is the ThousandEyes provider. +type Provider struct { + // version is set at build time and reported to Terraform. + version string + // client, when non-nil, is used instead of building one from configuration. + // Unit tests set it so they can run against an interception layer without + // credentials. + client any +} + +// New returns a provider factory for the given version. +func New(version string) func() provider.Provider { + return func() provider.Provider { + return &Provider{version: version} + } +} + +// NewWithClient returns a provider that uses a pre-built client. It exists for +// unit tests, which must never depend on credentials being present. +func NewWithClient(version string, client any) func() provider.Provider { + return func() provider.Provider { + return &Provider{version: version, client: client} + } +} + +func (p *Provider) Metadata(_ context.Context, _ provider.MetadataRequest, resp *provider.MetadataResponse) { + resp.TypeName = "thousandeyes" + resp.Version = p.version +} + +// Model is the provider block's configuration. +type Model struct { + BearerToken types.String `tfsdk:"bearer_token"` + AccountGroupID types.String `tfsdk:"account_group_id"` + APIEndpoint types.String `tfsdk:"api_endpoint"` +} + +func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *provider.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Manages ThousandEyes resources through the v7 API.", + Attributes: map[string]schema.Attribute{ + "bearer_token": schema.StringAttribute{ + MarkdownDescription: "A ThousandEyes user API token, created under " + + "**Account Settings > Users and Roles > Profile**. May also be set with the `" + + envBearerToken + "` environment variable.\n\n" + + "ThousandEyes has no service-account identity type: this token belongs to a " + + "user, does not expire, and cannot be revoked through the API.", + Optional: true, + Sensitive: true, + }, + "account_group_id": schema.StringAttribute{ + MarkdownDescription: "Scopes every request to an account group, sent as the `aid` " + + "query parameter. When unset, the API uses the token owner's default account " + + "group, which means an object can appear absent rather than merely invisible. " + + "May also be set with the `" + envAccountGroupID + "` environment variable.", + Optional: true, + }, + "api_endpoint": schema.StringAttribute{ + MarkdownDescription: "Overrides the API base URL. Defaults to the SDK's v7 endpoint. " + + "May also be set with the `" + envAPIEndpoint + "` environment variable.", + Optional: true, + }, + }, + } +} + +func (p *Provider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { + // A test-supplied client short-circuits configuration entirely, so unit + // tests need no credentials and cannot reach the network by accident. + if p.client != nil { + configureAllKinds(resp, p.client) + return + } + + var cfg Model + resp.Diagnostics.Append(req.Config.Get(ctx, &cfg)...) + if resp.Diagnostics.HasError() { + return + } + + resolved := teclient.Config{ + BearerToken: firstNonEmpty(cfg.BearerToken, envBearerToken), + AccountGroupID: firstNonEmpty(cfg.AccountGroupID, envAccountGroupID), + APIEndpoint: firstNonEmpty(cfg.APIEndpoint, envAPIEndpoint), + } + + if resolved.BearerToken == "" { + resp.Diagnostics.AddAttributeError( + path.Root("bearer_token"), + "Missing ThousandEyes bearer token", + "Set the bearer_token provider attribute or the "+envBearerToken+" environment variable. "+ + "Create a token under Account Settings > Users and Roles > Profile.", + ) + return + } + + client, err := teclient.New(resolved) + if err != nil { + resp.Diagnostics.AddError("Unable to configure the ThousandEyes client", err.Error()) + return + } + + tflog.Debug(ctx, "configured the ThousandEyes client", map[string]any{ + "account_group_id_set": resolved.AccountGroupID != "", + "custom_endpoint": resolved.APIEndpoint != "", + }) + + configureAllKinds(resp, client) +} + +// configureAllKinds hands the client to every block kind the provider serves. +// +// The framework carries one field per kind, and setting only some of them is the bug the +// first live acceptance run found: actions, ephemerals and list resources received a nil +// client and crashed on first use. One function, so a kind added later is added here or +// visibly nowhere. +func configureAllKinds(resp *provider.ConfigureResponse, client any) { + resp.ResourceData = client + resp.DataSourceData = client + resp.ActionData = client + resp.EphemeralResourceData = client + resp.ListResourceData = client +} + +// firstNonEmpty prefers the configured value, then the environment variable. +// Configuration winning over the environment is the convention practitioners +// expect: an explicit value in a file should not be silently overridden by a +// stray shell export. +func firstNonEmpty(configured types.String, envVar string) string { + if !configured.IsNull() && !configured.IsUnknown() && configured.ValueString() != "" { + return configured.ValueString() + } + return os.Getenv(envVar) +} + +// Resources and DataSources are implemented in the generated resources.go and +// datasources.go, so adding a resource is a regeneration rather than an edit here. diff --git a/pilot/thousandeyes-kiota/internal/provider/resources.go b/pilot/thousandeyes-kiota/internal/provider/resources.go new file mode 100644 index 00000000..40ed002b --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/provider/resources.go @@ -0,0 +1,22 @@ +// Code generated by tfpfgen from blueprints/thousandeyes-kiota +// (sha256:82a8701a0f77258aba49054f577921f206b8fcaecd42c5fb57874c3fd6799173). DO NOT EDIT. + +package provider + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" + + v7Tag "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag" +) + +// Resources returns every managed resource this provider serves. +// +// This file is generated in full. Add a resource by adding a blueprint and +// regenerating, not by editing here. +func (p *Provider) Resources(_ context.Context) []func() resource.Resource { + return []func() resource.Resource{ + v7Tag.NewTagResource, + } +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/kiota-lock.json b/pilot/thousandeyes-kiota/internal/sdk/kiota-lock.json new file mode 100644 index 00000000..dbcdf5aa --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/kiota-lock.json @@ -0,0 +1,38 @@ +{ + "descriptionHash": "3EE8972B8856F95DDACB7458A00D4759743AC58ABD6EE4E5139696A6055F32185E321FB81BD67E301E3F16A091B0FE75F215572F1DFCAA530247FAD22B0B795B", + "descriptionLocation": "../../../../openapi/thousandeyes/7.0.98-t1785502133469/api.yaml", + "lockFileVersion": "1.0.0", + "kiotaVersion": "1.34.1", + "clientClassName": "ThousandEyesClient", + "typeAccessModifier": "Public", + "clientNamespaceName": "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk", + "language": "Go", + "usesBackingStore": false, + "excludeBackwardCompatible": true, + "includeAdditionalData": true, + "disableSSLValidation": false, + "serializers": [ + "Microsoft.Kiota.Serialization.Json.JsonSerializationWriterFactory", + "Microsoft.Kiota.Serialization.Text.TextSerializationWriterFactory", + "Microsoft.Kiota.Serialization.Form.FormSerializationWriterFactory", + "Microsoft.Kiota.Serialization.Multipart.MultipartSerializationWriterFactory" + ], + "deserializers": [ + "Microsoft.Kiota.Serialization.Json.JsonParseNodeFactory", + "Microsoft.Kiota.Serialization.Text.TextParseNodeFactory", + "Microsoft.Kiota.Serialization.Form.FormParseNodeFactory" + ], + "structuredMimeTypes": [ + "application/json", + "text/plain;q=0.9", + "application/x-www-form-urlencoded;q=0.2", + "multipart/form-data;q=0.1" + ], + "includePatterns": [ + "/tags", + "/tags/**" + ], + "excludePatterns": [], + "disabledValidationRules": [], + "allowedExternalOrigins": [] +} \ No newline at end of file diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_access_type.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_access_type.go new file mode 100644 index 00000000..451eb0af --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_access_type.go @@ -0,0 +1,44 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +// The access level of the tag. The access level determines the label's visibility in the UI and the permissions required to modify it. Accepted values are:* `all`: The tag is visible and editable by any user in the account group with the standard **View tags** or **Edit tags** permission. Default for all user-created tags.* `partner`: The tag is owned by a specific integration partner. Only that partner can read or modify it. It's hidden from all regular account users and excluded from their GET responses.* `system`: Reserved for ThousandEyes internal use only. This value may appear in GET responses on internally managed tags but cannot be set by users or integration partners. +type Tags_API_AccessType int + +const ( + ALL_TAGS_API_ACCESSTYPE Tags_API_AccessType = iota + PARTNER_TAGS_API_ACCESSTYPE + SYSTEM_TAGS_API_ACCESSTYPE +) + +func (i Tags_API_AccessType) String() string { + return []string{"all", "partner", "system"}[i] +} + +func ParseTags_API_AccessType(v string) (any, error) { + result := ALL_TAGS_API_ACCESSTYPE + switch v { + case "all": + result = ALL_TAGS_API_ACCESSTYPE + case "partner": + result = PARTNER_TAGS_API_ACCESSTYPE + case "system": + result = SYSTEM_TAGS_API_ACCESSTYPE + default: + return nil, nil + } + return &result, nil +} + +func SerializeTags_API_AccessType(values []Tags_API_AccessType) []string { + result := make([]string, len(values)) + for i, v := range values { + result[i] = v.String() + } + return result +} + +func (i Tags_API_AccessType) isMultiValue() bool { + return false +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_api_error.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_api_error.go new file mode 100644 index 00000000..e35ed688 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_api_error.go @@ -0,0 +1,194 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +import ( + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f "github.com/microsoft/kiota-abstractions-go" + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +type Tags_API_ApiError struct { + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ApiError + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]any + // The errors property + errors *string + // The path property + path *string + // The status property + status *int32 + // The timestamp property + timestamp *int64 +} + +// NewTags_API_ApiError instantiates a new Tags_API_ApiError and sets the default values. +func NewTags_API_ApiError() *Tags_API_ApiError { + m := &Tags_API_ApiError{ + ApiError: *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewApiError(), + } + m.SetAdditionalData(make(map[string]any)) + return m +} + +// CreateTags_API_ApiErrorFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateTags_API_ApiErrorFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewTags_API_ApiError(), nil +} + +// Error the primary error message. +// returns a string when successful +func (m *Tags_API_ApiError) Error() string { + return m.ApiError.Error() +} + +// GetAdditionalData gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +// returns a map[string]any when successful +func (m *Tags_API_ApiError) GetAdditionalData() map[string]any { + return m.additionalData +} + +// GetErrors gets the errors property value. The errors property +// returns a *string when successful +func (m *Tags_API_ApiError) GetErrors() *string { + return m.errors +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *Tags_API_ApiError) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error) + res["errors"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetErrors(val) + } + return nil + } + res["path"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetPath(val) + } + return nil + } + res["status"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetInt32Value() + if err != nil { + return err + } + if val != nil { + m.SetStatus(val) + } + return nil + } + res["timestamp"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetInt64Value() + if err != nil { + return err + } + if val != nil { + m.SetTimestamp(val) + } + return nil + } + return res +} + +// GetPath gets the path property value. The path property +// returns a *string when successful +func (m *Tags_API_ApiError) GetPath() *string { + return m.path +} + +// GetStatus gets the status property value. The status property +// returns a *int32 when successful +func (m *Tags_API_ApiError) GetStatus() *int32 { + return m.status +} + +// GetTimestamp gets the timestamp property value. The timestamp property +// returns a *int64 when successful +func (m *Tags_API_ApiError) GetTimestamp() *int64 { + return m.timestamp +} + +// Serialize serializes information the current object +func (m *Tags_API_ApiError) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + { + err := writer.WriteStringValue("errors", m.GetErrors()) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("path", m.GetPath()) + if err != nil { + return err + } + } + { + err := writer.WriteInt32Value("status", m.GetStatus()) + if err != nil { + return err + } + } + { + err := writer.WriteInt64Value("timestamp", m.GetTimestamp()) + if err != nil { + return err + } + } + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAdditionalData sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *Tags_API_ApiError) SetAdditionalData(value map[string]any) { + m.additionalData = value +} + +// SetErrors sets the errors property value. The errors property +func (m *Tags_API_ApiError) SetErrors(value *string) { + m.errors = value +} + +// SetPath sets the path property value. The path property +func (m *Tags_API_ApiError) SetPath(value *string) { + m.path = value +} + +// SetStatus sets the status property value. The status property +func (m *Tags_API_ApiError) SetStatus(value *int32) { + m.status = value +} + +// SetTimestamp sets the timestamp property value. The timestamp property +func (m *Tags_API_ApiError) SetTimestamp(value *int64) { + m.timestamp = value +} + +type Tags_API_ApiErrorable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable + GetErrors() *string + GetPath() *string + GetStatus() *int32 + GetTimestamp() *int64 + SetErrors(value *string) + SetPath(value *string) + SetStatus(value *int32) + SetTimestamp(value *int64) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_assignment.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_assignment.go new file mode 100644 index 00000000..0c95aa2b --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_assignment.go @@ -0,0 +1,123 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +import ( + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +type Tags_API_Assignment struct { + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]any + // Object Id + id *string + // The type property + typeEscaped *Tags_API_AssignmentType +} + +// NewTags_API_Assignment instantiates a new Tags_API_Assignment and sets the default values. +func NewTags_API_Assignment() *Tags_API_Assignment { + m := &Tags_API_Assignment{} + m.SetAdditionalData(make(map[string]any)) + return m +} + +// CreateTags_API_AssignmentFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateTags_API_AssignmentFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewTags_API_Assignment(), nil +} + +// GetAdditionalData gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +// returns a map[string]any when successful +func (m *Tags_API_Assignment) GetAdditionalData() map[string]any { + return m.additionalData +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *Tags_API_Assignment) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error) + res["id"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetId(val) + } + return nil + } + res["type"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetEnumValue(ParseTags_API_AssignmentType) + if err != nil { + return err + } + if val != nil { + m.SetTypeEscaped(val.(*Tags_API_AssignmentType)) + } + return nil + } + return res +} + +// GetId gets the id property value. Object Id +// returns a *string when successful +func (m *Tags_API_Assignment) GetId() *string { + return m.id +} + +// GetTypeEscaped gets the type property value. The type property +// returns a *Tags_API_AssignmentType when successful +func (m *Tags_API_Assignment) GetTypeEscaped() *Tags_API_AssignmentType { + return m.typeEscaped +} + +// Serialize serializes information the current object +func (m *Tags_API_Assignment) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + { + err := writer.WriteStringValue("id", m.GetId()) + if err != nil { + return err + } + } + if m.GetTypeEscaped() != nil { + cast := (*m.GetTypeEscaped()).String() + err := writer.WriteStringValue("type", &cast) + if err != nil { + return err + } + } + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAdditionalData sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *Tags_API_Assignment) SetAdditionalData(value map[string]any) { + m.additionalData = value +} + +// SetId sets the id property value. Object Id +func (m *Tags_API_Assignment) SetId(value *string) { + m.id = value +} + +// SetTypeEscaped sets the type property value. The type property +func (m *Tags_API_Assignment) SetTypeEscaped(value *Tags_API_AssignmentType) { + m.typeEscaped = value +} + +type Tags_API_Assignmentable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable + GetId() *string + GetTypeEscaped() *Tags_API_AssignmentType + SetId(value *string) + SetTypeEscaped(value *Tags_API_AssignmentType) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_assignment_type.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_assignment_type.go new file mode 100644 index 00000000..644ff1fa --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_assignment_type.go @@ -0,0 +1,49 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +type Tags_API_AssignmentType int + +const ( + TEST_TAGS_API_ASSIGNMENTTYPE Tags_API_AssignmentType = iota + VAGENT_TAGS_API_ASSIGNMENTTYPE + ENDPOINTTEST_TAGS_API_ASSIGNMENTTYPE + DASHBOARD_TAGS_API_ASSIGNMENTTYPE + CONNECTEDDEVICESTEST_TAGS_API_ASSIGNMENTTYPE +) + +func (i Tags_API_AssignmentType) String() string { + return []string{"test", "v-agent", "endpoint-test", "dashboard", "connected-devices-test"}[i] +} + +func ParseTags_API_AssignmentType(v string) (any, error) { + result := TEST_TAGS_API_ASSIGNMENTTYPE + switch v { + case "test": + result = TEST_TAGS_API_ASSIGNMENTTYPE + case "v-agent": + result = VAGENT_TAGS_API_ASSIGNMENTTYPE + case "endpoint-test": + result = ENDPOINTTEST_TAGS_API_ASSIGNMENTTYPE + case "dashboard": + result = DASHBOARD_TAGS_API_ASSIGNMENTTYPE + case "connected-devices-test": + result = CONNECTEDDEVICESTEST_TAGS_API_ASSIGNMENTTYPE + default: + return nil, nil + } + return &result, nil +} + +func SerializeTags_API_AssignmentType(values []Tags_API_AssignmentType) []string { + result := make([]string, len(values)) + for i, v := range values { + result[i] = v.String() + } + return result +} + +func (i Tags_API_AssignmentType) isMultiValue() bool { + return false +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_bulk_tag_assignment.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_bulk_tag_assignment.go new file mode 100644 index 00000000..cb349f12 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_bulk_tag_assignment.go @@ -0,0 +1,104 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +import ( + i561e97a8befe7661a44c8f54600992b4207a3a0cf6770e5559949bc276de2e22 "github.com/google/uuid" + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +type Tags_API_BulkTagAssignment struct { + Tags_API_TagAssignment + // A links object containing the self link. + links Tags_API_SelfLinksable + // The ID of the tag to assign + tagId *i561e97a8befe7661a44c8f54600992b4207a3a0cf6770e5559949bc276de2e22.UUID +} + +// NewTags_API_BulkTagAssignment instantiates a new Tags_API_BulkTagAssignment and sets the default values. +func NewTags_API_BulkTagAssignment() *Tags_API_BulkTagAssignment { + m := &Tags_API_BulkTagAssignment{ + Tags_API_TagAssignment: *NewTags_API_TagAssignment(), + } + return m +} + +// CreateTags_API_BulkTagAssignmentFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateTags_API_BulkTagAssignmentFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewTags_API_BulkTagAssignment(), nil +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *Tags_API_BulkTagAssignment) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := m.Tags_API_TagAssignment.GetFieldDeserializers() + res["_links"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetObjectValue(CreateTags_API_SelfLinksFromDiscriminatorValue) + if err != nil { + return err + } + if val != nil { + m.SetLinks(val.(Tags_API_SelfLinksable)) + } + return nil + } + res["tagId"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetUUIDValue() + if err != nil { + return err + } + if val != nil { + m.SetTagId(val) + } + return nil + } + return res +} + +// GetLinks gets the _links property value. A links object containing the self link. +// returns a Tags_API_SelfLinksable when successful +func (m *Tags_API_BulkTagAssignment) GetLinks() Tags_API_SelfLinksable { + return m.links +} + +// GetTagId gets the tagId property value. The ID of the tag to assign +// returns a *UUID when successful +func (m *Tags_API_BulkTagAssignment) GetTagId() *i561e97a8befe7661a44c8f54600992b4207a3a0cf6770e5559949bc276de2e22.UUID { + return m.tagId +} + +// Serialize serializes information the current object +func (m *Tags_API_BulkTagAssignment) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + err := m.Tags_API_TagAssignment.Serialize(writer) + if err != nil { + return err + } + { + err = writer.WriteUUIDValue("tagId", m.GetTagId()) + if err != nil { + return err + } + } + return nil +} + +// SetLinks sets the _links property value. A links object containing the self link. +func (m *Tags_API_BulkTagAssignment) SetLinks(value Tags_API_SelfLinksable) { + m.links = value +} + +// SetTagId sets the tagId property value. The ID of the tag to assign +func (m *Tags_API_BulkTagAssignment) SetTagId(value *i561e97a8befe7661a44c8f54600992b4207a3a0cf6770e5559949bc276de2e22.UUID) { + m.tagId = value +} + +type Tags_API_BulkTagAssignmentable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable + Tags_API_TagAssignmentable + GetLinks() Tags_API_SelfLinksable + GetTagId() *i561e97a8befe7661a44c8f54600992b4207a3a0cf6770e5559949bc276de2e22.UUID + SetLinks(value Tags_API_SelfLinksable) + SetTagId(value *i561e97a8befe7661a44c8f54600992b4207a3a0cf6770e5559949bc276de2e22.UUID) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_bulk_tag_assignments.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_bulk_tag_assignments.go new file mode 100644 index 00000000..06ed2db9 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_bulk_tag_assignments.go @@ -0,0 +1,128 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +import ( + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +type Tags_API_BulkTagAssignments struct { + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]any + // A links object containing the self link. + links Tags_API_SelfLinksable + // The tags property + tags []Tags_API_BulkTagAssignmentable +} + +// NewTags_API_BulkTagAssignments instantiates a new Tags_API_BulkTagAssignments and sets the default values. +func NewTags_API_BulkTagAssignments() *Tags_API_BulkTagAssignments { + m := &Tags_API_BulkTagAssignments{} + m.SetAdditionalData(make(map[string]any)) + return m +} + +// CreateTags_API_BulkTagAssignmentsFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateTags_API_BulkTagAssignmentsFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewTags_API_BulkTagAssignments(), nil +} + +// GetAdditionalData gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +// returns a map[string]any when successful +func (m *Tags_API_BulkTagAssignments) GetAdditionalData() map[string]any { + return m.additionalData +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *Tags_API_BulkTagAssignments) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error) + res["_links"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetObjectValue(CreateTags_API_SelfLinksFromDiscriminatorValue) + if err != nil { + return err + } + if val != nil { + m.SetLinks(val.(Tags_API_SelfLinksable)) + } + return nil + } + res["tags"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetCollectionOfObjectValues(CreateTags_API_BulkTagAssignmentFromDiscriminatorValue) + if err != nil { + return err + } + if val != nil { + res := make([]Tags_API_BulkTagAssignmentable, len(val)) + for i, v := range val { + if v != nil { + res[i] = v.(Tags_API_BulkTagAssignmentable) + } + } + m.SetTags(res) + } + return nil + } + return res +} + +// GetLinks gets the _links property value. A links object containing the self link. +// returns a Tags_API_SelfLinksable when successful +func (m *Tags_API_BulkTagAssignments) GetLinks() Tags_API_SelfLinksable { + return m.links +} + +// GetTags gets the tags property value. The tags property +// returns a []Tags_API_BulkTagAssignmentable when successful +func (m *Tags_API_BulkTagAssignments) GetTags() []Tags_API_BulkTagAssignmentable { + return m.tags +} + +// Serialize serializes information the current object +func (m *Tags_API_BulkTagAssignments) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + if m.GetTags() != nil { + cast := make([]i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, len(m.GetTags())) + for i, v := range m.GetTags() { + if v != nil { + cast[i] = v.(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable) + } + } + err := writer.WriteCollectionOfObjectValues("tags", cast) + if err != nil { + return err + } + } + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAdditionalData sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *Tags_API_BulkTagAssignments) SetAdditionalData(value map[string]any) { + m.additionalData = value +} + +// SetLinks sets the _links property value. A links object containing the self link. +func (m *Tags_API_BulkTagAssignments) SetLinks(value Tags_API_SelfLinksable) { + m.links = value +} + +// SetTags sets the tags property value. The tags property +func (m *Tags_API_BulkTagAssignments) SetTags(value []Tags_API_BulkTagAssignmentable) { + m.tags = value +} + +type Tags_API_BulkTagAssignmentsable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable + GetLinks() Tags_API_SelfLinksable + GetTags() []Tags_API_BulkTagAssignmentable + SetLinks(value Tags_API_SelfLinksable) + SetTags(value []Tags_API_BulkTagAssignmentable) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_bulk_tag_response.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_bulk_tag_response.go new file mode 100644 index 00000000..15cb0d46 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_bulk_tag_response.go @@ -0,0 +1,159 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +import ( + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +type Tags_API_BulkTagResponse struct { + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]any + // The errors property + errors []Tags_API_TagBulkCreateErrorable + // A links object containing the self link. + links Tags_API_SelfLinksable + // The tags property + tags []Tags_API_Tagable +} + +// NewTags_API_BulkTagResponse instantiates a new Tags_API_BulkTagResponse and sets the default values. +func NewTags_API_BulkTagResponse() *Tags_API_BulkTagResponse { + m := &Tags_API_BulkTagResponse{} + m.SetAdditionalData(make(map[string]any)) + return m +} + +// CreateTags_API_BulkTagResponseFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateTags_API_BulkTagResponseFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewTags_API_BulkTagResponse(), nil +} + +// GetAdditionalData gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +// returns a map[string]any when successful +func (m *Tags_API_BulkTagResponse) GetAdditionalData() map[string]any { + return m.additionalData +} + +// GetErrors gets the errors property value. The errors property +// returns a []Tags_API_TagBulkCreateErrorable when successful +func (m *Tags_API_BulkTagResponse) GetErrors() []Tags_API_TagBulkCreateErrorable { + return m.errors +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *Tags_API_BulkTagResponse) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error) + res["errors"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetCollectionOfObjectValues(CreateTags_API_TagBulkCreateErrorFromDiscriminatorValue) + if err != nil { + return err + } + if val != nil { + res := make([]Tags_API_TagBulkCreateErrorable, len(val)) + for i, v := range val { + if v != nil { + res[i] = v.(Tags_API_TagBulkCreateErrorable) + } + } + m.SetErrors(res) + } + return nil + } + res["_links"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetObjectValue(CreateTags_API_SelfLinksFromDiscriminatorValue) + if err != nil { + return err + } + if val != nil { + m.SetLinks(val.(Tags_API_SelfLinksable)) + } + return nil + } + res["tags"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetCollectionOfObjectValues(CreateTags_API_TagFromDiscriminatorValue) + if err != nil { + return err + } + if val != nil { + res := make([]Tags_API_Tagable, len(val)) + for i, v := range val { + if v != nil { + res[i] = v.(Tags_API_Tagable) + } + } + m.SetTags(res) + } + return nil + } + return res +} + +// GetLinks gets the _links property value. A links object containing the self link. +// returns a Tags_API_SelfLinksable when successful +func (m *Tags_API_BulkTagResponse) GetLinks() Tags_API_SelfLinksable { + return m.links +} + +// GetTags gets the tags property value. The tags property +// returns a []Tags_API_Tagable when successful +func (m *Tags_API_BulkTagResponse) GetTags() []Tags_API_Tagable { + return m.tags +} + +// Serialize serializes information the current object +func (m *Tags_API_BulkTagResponse) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + if m.GetTags() != nil { + cast := make([]i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, len(m.GetTags())) + for i, v := range m.GetTags() { + if v != nil { + cast[i] = v.(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable) + } + } + err := writer.WriteCollectionOfObjectValues("tags", cast) + if err != nil { + return err + } + } + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAdditionalData sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *Tags_API_BulkTagResponse) SetAdditionalData(value map[string]any) { + m.additionalData = value +} + +// SetErrors sets the errors property value. The errors property +func (m *Tags_API_BulkTagResponse) SetErrors(value []Tags_API_TagBulkCreateErrorable) { + m.errors = value +} + +// SetLinks sets the _links property value. A links object containing the self link. +func (m *Tags_API_BulkTagResponse) SetLinks(value Tags_API_SelfLinksable) { + m.links = value +} + +// SetTags sets the tags property value. The tags property +func (m *Tags_API_BulkTagResponse) SetTags(value []Tags_API_Tagable) { + m.tags = value +} + +type Tags_API_BulkTagResponseable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable + GetErrors() []Tags_API_TagBulkCreateErrorable + GetLinks() Tags_API_SelfLinksable + GetTags() []Tags_API_Tagable + SetErrors(value []Tags_API_TagBulkCreateErrorable) + SetLinks(value Tags_API_SelfLinksable) + SetTags(value []Tags_API_Tagable) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_error.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_error.go new file mode 100644 index 00000000..07e476ac --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_error.go @@ -0,0 +1,225 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +import ( + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f "github.com/microsoft/kiota-abstractions-go" + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +type Tags_API_Error struct { + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ApiError + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]any + // A human-readable explanation specific to this occurrence of the problem. + detail *string + // A URI reference that identifies the specific occurrence of the problem. + instance *string + // The HTTP status code generated by the origin server for this occurrence of the problem. + status *int32 + // A short, human-readable summary of the problem type. + title *string + // A URI reference that identifies the problem type. When this member is not present, its value is assumed to be "about:blank". + typeEscaped *string +} + +// NewTags_API_Error instantiates a new Tags_API_Error and sets the default values. +func NewTags_API_Error() *Tags_API_Error { + m := &Tags_API_Error{ + ApiError: *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewApiError(), + } + m.SetAdditionalData(make(map[string]any)) + return m +} + +// CreateTags_API_ErrorFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateTags_API_ErrorFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewTags_API_Error(), nil +} + +// Error the primary error message. +// returns a string when successful +func (m *Tags_API_Error) Error() string { + return m.ApiError.Error() +} + +// GetAdditionalData gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +// returns a map[string]any when successful +func (m *Tags_API_Error) GetAdditionalData() map[string]any { + return m.additionalData +} + +// GetDetail gets the detail property value. A human-readable explanation specific to this occurrence of the problem. +// returns a *string when successful +func (m *Tags_API_Error) GetDetail() *string { + return m.detail +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *Tags_API_Error) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error) + res["detail"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetDetail(val) + } + return nil + } + res["instance"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetInstance(val) + } + return nil + } + res["status"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetInt32Value() + if err != nil { + return err + } + if val != nil { + m.SetStatus(val) + } + return nil + } + res["title"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetTitle(val) + } + return nil + } + res["type"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetTypeEscaped(val) + } + return nil + } + return res +} + +// GetInstance gets the instance property value. A URI reference that identifies the specific occurrence of the problem. +// returns a *string when successful +func (m *Tags_API_Error) GetInstance() *string { + return m.instance +} + +// GetStatus gets the status property value. The HTTP status code generated by the origin server for this occurrence of the problem. +// returns a *int32 when successful +func (m *Tags_API_Error) GetStatus() *int32 { + return m.status +} + +// GetTitle gets the title property value. A short, human-readable summary of the problem type. +// returns a *string when successful +func (m *Tags_API_Error) GetTitle() *string { + return m.title +} + +// GetTypeEscaped gets the type property value. A URI reference that identifies the problem type. When this member is not present, its value is assumed to be "about:blank". +// returns a *string when successful +func (m *Tags_API_Error) GetTypeEscaped() *string { + return m.typeEscaped +} + +// Serialize serializes information the current object +func (m *Tags_API_Error) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + { + err := writer.WriteStringValue("detail", m.GetDetail()) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("instance", m.GetInstance()) + if err != nil { + return err + } + } + { + err := writer.WriteInt32Value("status", m.GetStatus()) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("title", m.GetTitle()) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("type", m.GetTypeEscaped()) + if err != nil { + return err + } + } + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAdditionalData sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *Tags_API_Error) SetAdditionalData(value map[string]any) { + m.additionalData = value +} + +// SetDetail sets the detail property value. A human-readable explanation specific to this occurrence of the problem. +func (m *Tags_API_Error) SetDetail(value *string) { + m.detail = value +} + +// SetInstance sets the instance property value. A URI reference that identifies the specific occurrence of the problem. +func (m *Tags_API_Error) SetInstance(value *string) { + m.instance = value +} + +// SetStatus sets the status property value. The HTTP status code generated by the origin server for this occurrence of the problem. +func (m *Tags_API_Error) SetStatus(value *int32) { + m.status = value +} + +// SetTitle sets the title property value. A short, human-readable summary of the problem type. +func (m *Tags_API_Error) SetTitle(value *string) { + m.title = value +} + +// SetTypeEscaped sets the type property value. A URI reference that identifies the problem type. When this member is not present, its value is assumed to be "about:blank". +func (m *Tags_API_Error) SetTypeEscaped(value *string) { + m.typeEscaped = value +} + +type Tags_API_Errorable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable + GetDetail() *string + GetInstance() *string + GetStatus() *int32 + GetTitle() *string + GetTypeEscaped() *string + SetDetail(value *string) + SetInstance(value *string) + SetStatus(value *int32) + SetTitle(value *string) + SetTypeEscaped(value *string) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_expand_tags_options.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_expand_tags_options.go new file mode 100644 index 00000000..675c01af --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_expand_tags_options.go @@ -0,0 +1,37 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +type Tags_API_ExpandTagsOptions int + +const ( + ASSIGNMENTS_TAGS_API_EXPANDTAGSOPTIONS Tags_API_ExpandTagsOptions = iota +) + +func (i Tags_API_ExpandTagsOptions) String() string { + return []string{"assignments"}[i] +} + +func ParseTags_API_ExpandTagsOptions(v string) (any, error) { + result := ASSIGNMENTS_TAGS_API_EXPANDTAGSOPTIONS + switch v { + case "assignments": + result = ASSIGNMENTS_TAGS_API_EXPANDTAGSOPTIONS + default: + return nil, nil + } + return &result, nil +} + +func SerializeTags_API_ExpandTagsOptions(values []Tags_API_ExpandTagsOptions) []string { + result := make([]string, len(values)) + for i, v := range values { + result[i] = v.String() + } + return result +} + +func (i Tags_API_ExpandTagsOptions) isMultiValue() bool { + return false +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_link.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_link.go new file mode 100644 index 00000000..539dd78a --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_link.go @@ -0,0 +1,309 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +import ( + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +// Tags_API_Link a hyperlink from the containing resource to a URI. +type Tags_API_Link struct { + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]any + // Its presence indicates that the link is to be deprecated at a future date. Its value is a URL that should provide further information about the deprecation. + deprecation *string + // Its value is either a URI [RFC3986] or a URI template [RFC6570]. + href *string + // Indicates the language of the target resource + hreflang *string + // Its value may be used as a secondary key for selecting link objects that share the same relation type. + name *string + // A URI that hints about the profile of the target resource. + profile *string + // Should be true when the link object's "href" property is a URI template. + templated *bool + // Intended for labelling the link with a human-readable identifier + title *string + // Used as a hint to indicate the media type expected when dereferencing the target resource. + typeEscaped *string +} + +// NewTags_API_Link instantiates a new Tags_API_Link and sets the default values. +func NewTags_API_Link() *Tags_API_Link { + m := &Tags_API_Link{} + m.SetAdditionalData(make(map[string]any)) + return m +} + +// CreateTags_API_LinkFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateTags_API_LinkFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewTags_API_Link(), nil +} + +// GetAdditionalData gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +// returns a map[string]any when successful +func (m *Tags_API_Link) GetAdditionalData() map[string]any { + return m.additionalData +} + +// GetDeprecation gets the deprecation property value. Its presence indicates that the link is to be deprecated at a future date. Its value is a URL that should provide further information about the deprecation. +// returns a *string when successful +func (m *Tags_API_Link) GetDeprecation() *string { + return m.deprecation +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *Tags_API_Link) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error) + res["deprecation"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetDeprecation(val) + } + return nil + } + res["href"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetHref(val) + } + return nil + } + res["hreflang"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetHreflang(val) + } + return nil + } + res["name"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetName(val) + } + return nil + } + res["profile"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetProfile(val) + } + return nil + } + res["templated"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetBoolValue() + if err != nil { + return err + } + if val != nil { + m.SetTemplated(val) + } + return nil + } + res["title"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetTitle(val) + } + return nil + } + res["type"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetTypeEscaped(val) + } + return nil + } + return res +} + +// GetHref gets the href property value. Its value is either a URI [RFC3986] or a URI template [RFC6570]. +// returns a *string when successful +func (m *Tags_API_Link) GetHref() *string { + return m.href +} + +// GetHreflang gets the hreflang property value. Indicates the language of the target resource +// returns a *string when successful +func (m *Tags_API_Link) GetHreflang() *string { + return m.hreflang +} + +// GetName gets the name property value. Its value may be used as a secondary key for selecting link objects that share the same relation type. +// returns a *string when successful +func (m *Tags_API_Link) GetName() *string { + return m.name +} + +// GetProfile gets the profile property value. A URI that hints about the profile of the target resource. +// returns a *string when successful +func (m *Tags_API_Link) GetProfile() *string { + return m.profile +} + +// GetTemplated gets the templated property value. Should be true when the link object's "href" property is a URI template. +// returns a *bool when successful +func (m *Tags_API_Link) GetTemplated() *bool { + return m.templated +} + +// GetTitle gets the title property value. Intended for labelling the link with a human-readable identifier +// returns a *string when successful +func (m *Tags_API_Link) GetTitle() *string { + return m.title +} + +// GetTypeEscaped gets the type property value. Used as a hint to indicate the media type expected when dereferencing the target resource. +// returns a *string when successful +func (m *Tags_API_Link) GetTypeEscaped() *string { + return m.typeEscaped +} + +// Serialize serializes information the current object +func (m *Tags_API_Link) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + { + err := writer.WriteStringValue("deprecation", m.GetDeprecation()) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("href", m.GetHref()) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("hreflang", m.GetHreflang()) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("name", m.GetName()) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("profile", m.GetProfile()) + if err != nil { + return err + } + } + { + err := writer.WriteBoolValue("templated", m.GetTemplated()) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("title", m.GetTitle()) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("type", m.GetTypeEscaped()) + if err != nil { + return err + } + } + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAdditionalData sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *Tags_API_Link) SetAdditionalData(value map[string]any) { + m.additionalData = value +} + +// SetDeprecation sets the deprecation property value. Its presence indicates that the link is to be deprecated at a future date. Its value is a URL that should provide further information about the deprecation. +func (m *Tags_API_Link) SetDeprecation(value *string) { + m.deprecation = value +} + +// SetHref sets the href property value. Its value is either a URI [RFC3986] or a URI template [RFC6570]. +func (m *Tags_API_Link) SetHref(value *string) { + m.href = value +} + +// SetHreflang sets the hreflang property value. Indicates the language of the target resource +func (m *Tags_API_Link) SetHreflang(value *string) { + m.hreflang = value +} + +// SetName sets the name property value. Its value may be used as a secondary key for selecting link objects that share the same relation type. +func (m *Tags_API_Link) SetName(value *string) { + m.name = value +} + +// SetProfile sets the profile property value. A URI that hints about the profile of the target resource. +func (m *Tags_API_Link) SetProfile(value *string) { + m.profile = value +} + +// SetTemplated sets the templated property value. Should be true when the link object's "href" property is a URI template. +func (m *Tags_API_Link) SetTemplated(value *bool) { + m.templated = value +} + +// SetTitle sets the title property value. Intended for labelling the link with a human-readable identifier +func (m *Tags_API_Link) SetTitle(value *string) { + m.title = value +} + +// SetTypeEscaped sets the type property value. Used as a hint to indicate the media type expected when dereferencing the target resource. +func (m *Tags_API_Link) SetTypeEscaped(value *string) { + m.typeEscaped = value +} + +type Tags_API_Linkable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable + GetDeprecation() *string + GetHref() *string + GetHreflang() *string + GetName() *string + GetProfile() *string + GetTemplated() *bool + GetTitle() *string + GetTypeEscaped() *string + SetDeprecation(value *string) + SetHref(value *string) + SetHreflang(value *string) + SetName(value *string) + SetProfile(value *string) + SetTemplated(value *bool) + SetTitle(value *string) + SetTypeEscaped(value *string) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_object_type.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_object_type.go new file mode 100644 index 00000000..166b44ab --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_object_type.go @@ -0,0 +1,53 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +// The object type associated with the tag +type Tags_API_ObjectType int + +const ( + TEST_TAGS_API_OBJECTTYPE Tags_API_ObjectType = iota + DASHBOARD_TAGS_API_OBJECTTYPE + ENDPOINTTEST_TAGS_API_OBJECTTYPE + VAGENT_TAGS_API_OBJECTTYPE + CONNECTEDDEVICESTEST_TAGS_API_OBJECTTYPE + ENDPOINTAGENT_TAGS_API_OBJECTTYPE +) + +func (i Tags_API_ObjectType) String() string { + return []string{"test", "dashboard", "endpoint-test", "v-agent", "connected-devices-test", "endpoint-agent"}[i] +} + +func ParseTags_API_ObjectType(v string) (any, error) { + result := TEST_TAGS_API_OBJECTTYPE + switch v { + case "test": + result = TEST_TAGS_API_OBJECTTYPE + case "dashboard": + result = DASHBOARD_TAGS_API_OBJECTTYPE + case "endpoint-test": + result = ENDPOINTTEST_TAGS_API_OBJECTTYPE + case "v-agent": + result = VAGENT_TAGS_API_OBJECTTYPE + case "connected-devices-test": + result = CONNECTEDDEVICESTEST_TAGS_API_OBJECTTYPE + case "endpoint-agent": + result = ENDPOINTAGENT_TAGS_API_OBJECTTYPE + default: + return nil, nil + } + return &result, nil +} + +func SerializeTags_API_ObjectType(values []Tags_API_ObjectType) []string { + result := make([]string, len(values)) + for i, v := range values { + result[i] = v.String() + } + return result +} + +func (i Tags_API_ObjectType) isMultiValue() bool { + return false +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_self_links.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_self_links.go new file mode 100644 index 00000000..a93947fc --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_self_links.go @@ -0,0 +1,92 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +import ( + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +// Tags_API_SelfLinks a links object containing the self link. +type Tags_API_SelfLinks struct { + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]any + // A hyperlink from the containing resource to a URI. + self Tags_API_Linkable +} + +// NewTags_API_SelfLinks instantiates a new Tags_API_SelfLinks and sets the default values. +func NewTags_API_SelfLinks() *Tags_API_SelfLinks { + m := &Tags_API_SelfLinks{} + m.SetAdditionalData(make(map[string]any)) + return m +} + +// CreateTags_API_SelfLinksFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateTags_API_SelfLinksFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewTags_API_SelfLinks(), nil +} + +// GetAdditionalData gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +// returns a map[string]any when successful +func (m *Tags_API_SelfLinks) GetAdditionalData() map[string]any { + return m.additionalData +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *Tags_API_SelfLinks) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error) + res["self"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetObjectValue(CreateTags_API_LinkFromDiscriminatorValue) + if err != nil { + return err + } + if val != nil { + m.SetSelf(val.(Tags_API_Linkable)) + } + return nil + } + return res +} + +// GetSelf gets the self property value. A hyperlink from the containing resource to a URI. +// returns a Tags_API_Linkable when successful +func (m *Tags_API_SelfLinks) GetSelf() Tags_API_Linkable { + return m.self +} + +// Serialize serializes information the current object +func (m *Tags_API_SelfLinks) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + { + err := writer.WriteObjectValue("self", m.GetSelf()) + if err != nil { + return err + } + } + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAdditionalData sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *Tags_API_SelfLinks) SetAdditionalData(value map[string]any) { + m.additionalData = value +} + +// SetSelf sets the self property value. A hyperlink from the containing resource to a URI. +func (m *Tags_API_SelfLinks) SetSelf(value Tags_API_Linkable) { + m.self = value +} + +type Tags_API_SelfLinksable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable + GetSelf() Tags_API_Linkable + SetSelf(value Tags_API_Linkable) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag.go new file mode 100644 index 00000000..1371a55f --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag.go @@ -0,0 +1,72 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +import ( + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +type Tags_API_Tag struct { + Tags_API_TagInfo + // A links object containing the self link. + links Tags_API_SelfLinksable +} + +// NewTags_API_Tag instantiates a new Tags_API_Tag and sets the default values. +func NewTags_API_Tag() *Tags_API_Tag { + m := &Tags_API_Tag{ + Tags_API_TagInfo: *NewTags_API_TagInfo(), + } + return m +} + +// CreateTags_API_TagFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateTags_API_TagFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewTags_API_Tag(), nil +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *Tags_API_Tag) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := m.Tags_API_TagInfo.GetFieldDeserializers() + res["_links"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetObjectValue(CreateTags_API_SelfLinksFromDiscriminatorValue) + if err != nil { + return err + } + if val != nil { + m.SetLinks(val.(Tags_API_SelfLinksable)) + } + return nil + } + return res +} + +// GetLinks gets the _links property value. A links object containing the self link. +// returns a Tags_API_SelfLinksable when successful +func (m *Tags_API_Tag) GetLinks() Tags_API_SelfLinksable { + return m.links +} + +// Serialize serializes information the current object +func (m *Tags_API_Tag) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + err := m.Tags_API_TagInfo.Serialize(writer) + if err != nil { + return err + } + return nil +} + +// SetLinks sets the _links property value. A links object containing the self link. +func (m *Tags_API_Tag) SetLinks(value Tags_API_SelfLinksable) { + m.links = value +} + +type Tags_API_Tagable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable + Tags_API_TagInfoable + GetLinks() Tags_API_SelfLinksable + SetLinks(value Tags_API_SelfLinksable) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_assignment.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_assignment.go new file mode 100644 index 00000000..ca05a943 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_assignment.go @@ -0,0 +1,103 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +import ( + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +type Tags_API_TagAssignment struct { + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]any + // The assignments property + assignments []Tags_API_Assignmentable +} + +// NewTags_API_TagAssignment instantiates a new Tags_API_TagAssignment and sets the default values. +func NewTags_API_TagAssignment() *Tags_API_TagAssignment { + m := &Tags_API_TagAssignment{} + m.SetAdditionalData(make(map[string]any)) + return m +} + +// CreateTags_API_TagAssignmentFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateTags_API_TagAssignmentFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewTags_API_TagAssignment(), nil +} + +// GetAdditionalData gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +// returns a map[string]any when successful +func (m *Tags_API_TagAssignment) GetAdditionalData() map[string]any { + return m.additionalData +} + +// GetAssignments gets the assignments property value. The assignments property +// returns a []Tags_API_Assignmentable when successful +func (m *Tags_API_TagAssignment) GetAssignments() []Tags_API_Assignmentable { + return m.assignments +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *Tags_API_TagAssignment) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error) + res["assignments"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetCollectionOfObjectValues(CreateTags_API_AssignmentFromDiscriminatorValue) + if err != nil { + return err + } + if val != nil { + res := make([]Tags_API_Assignmentable, len(val)) + for i, v := range val { + if v != nil { + res[i] = v.(Tags_API_Assignmentable) + } + } + m.SetAssignments(res) + } + return nil + } + return res +} + +// Serialize serializes information the current object +func (m *Tags_API_TagAssignment) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + if m.GetAssignments() != nil { + cast := make([]i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, len(m.GetAssignments())) + for i, v := range m.GetAssignments() { + if v != nil { + cast[i] = v.(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable) + } + } + err := writer.WriteCollectionOfObjectValues("assignments", cast) + if err != nil { + return err + } + } + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAdditionalData sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *Tags_API_TagAssignment) SetAdditionalData(value map[string]any) { + m.additionalData = value +} + +// SetAssignments sets the assignments property value. The assignments property +func (m *Tags_API_TagAssignment) SetAssignments(value []Tags_API_Assignmentable) { + m.assignments = value +} + +type Tags_API_TagAssignmentable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable + GetAssignments() []Tags_API_Assignmentable + SetAssignments(value []Tags_API_Assignmentable) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_bulk_create_error.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_bulk_create_error.go new file mode 100644 index 00000000..bbd3e150 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_bulk_create_error.go @@ -0,0 +1,153 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +import ( + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +type Tags_API_TagBulkCreateError struct { + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]any + // Status / error message + message *string + // HTTP response code + responseCode *int32 + // The tag property + tag Tags_API_TagBulkCreateError_tagable +} + +// NewTags_API_TagBulkCreateError instantiates a new Tags_API_TagBulkCreateError and sets the default values. +func NewTags_API_TagBulkCreateError() *Tags_API_TagBulkCreateError { + m := &Tags_API_TagBulkCreateError{} + m.SetAdditionalData(make(map[string]any)) + return m +} + +// CreateTags_API_TagBulkCreateErrorFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateTags_API_TagBulkCreateErrorFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewTags_API_TagBulkCreateError(), nil +} + +// GetAdditionalData gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +// returns a map[string]any when successful +func (m *Tags_API_TagBulkCreateError) GetAdditionalData() map[string]any { + return m.additionalData +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *Tags_API_TagBulkCreateError) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error) + res["message"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetMessage(val) + } + return nil + } + res["responseCode"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetInt32Value() + if err != nil { + return err + } + if val != nil { + m.SetResponseCode(val) + } + return nil + } + res["tag"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetObjectValue(CreateTags_API_TagBulkCreateError_tagFromDiscriminatorValue) + if err != nil { + return err + } + if val != nil { + m.SetTag(val.(Tags_API_TagBulkCreateError_tagable)) + } + return nil + } + return res +} + +// GetMessage gets the message property value. Status / error message +// returns a *string when successful +func (m *Tags_API_TagBulkCreateError) GetMessage() *string { + return m.message +} + +// GetResponseCode gets the responseCode property value. HTTP response code +// returns a *int32 when successful +func (m *Tags_API_TagBulkCreateError) GetResponseCode() *int32 { + return m.responseCode +} + +// GetTag gets the tag property value. The tag property +// returns a Tags_API_TagBulkCreateError_tagable when successful +func (m *Tags_API_TagBulkCreateError) GetTag() Tags_API_TagBulkCreateError_tagable { + return m.tag +} + +// Serialize serializes information the current object +func (m *Tags_API_TagBulkCreateError) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + { + err := writer.WriteStringValue("message", m.GetMessage()) + if err != nil { + return err + } + } + { + err := writer.WriteInt32Value("responseCode", m.GetResponseCode()) + if err != nil { + return err + } + } + { + err := writer.WriteObjectValue("tag", m.GetTag()) + if err != nil { + return err + } + } + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAdditionalData sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *Tags_API_TagBulkCreateError) SetAdditionalData(value map[string]any) { + m.additionalData = value +} + +// SetMessage sets the message property value. Status / error message +func (m *Tags_API_TagBulkCreateError) SetMessage(value *string) { + m.message = value +} + +// SetResponseCode sets the responseCode property value. HTTP response code +func (m *Tags_API_TagBulkCreateError) SetResponseCode(value *int32) { + m.responseCode = value +} + +// SetTag sets the tag property value. The tag property +func (m *Tags_API_TagBulkCreateError) SetTag(value Tags_API_TagBulkCreateError_tagable) { + m.tag = value +} + +type Tags_API_TagBulkCreateErrorable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable + GetMessage() *string + GetResponseCode() *int32 + GetTag() Tags_API_TagBulkCreateError_tagable + SetMessage(value *string) + SetResponseCode(value *int32) + SetTag(value Tags_API_TagBulkCreateError_tagable) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_bulk_create_error_escaped_tag.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_bulk_create_error_escaped_tag.go new file mode 100644 index 00000000..6bcedcab --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_bulk_create_error_escaped_tag.go @@ -0,0 +1,60 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +import ( + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +type Tags_API_TagBulkCreateError_tag struct { + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]any +} + +// NewTags_API_TagBulkCreateError_tag instantiates a new Tags_API_TagBulkCreateError_tag and sets the default values. +func NewTags_API_TagBulkCreateError_tag() *Tags_API_TagBulkCreateError_tag { + m := &Tags_API_TagBulkCreateError_tag{} + m.SetAdditionalData(make(map[string]any)) + return m +} + +// CreateTags_API_TagBulkCreateError_tagFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateTags_API_TagBulkCreateError_tagFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewTags_API_TagBulkCreateError_tag(), nil +} + +// GetAdditionalData gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +// returns a map[string]any when successful +func (m *Tags_API_TagBulkCreateError_tag) GetAdditionalData() map[string]any { + return m.additionalData +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *Tags_API_TagBulkCreateError_tag) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error) + return res +} + +// Serialize serializes information the current object +func (m *Tags_API_TagBulkCreateError_tag) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAdditionalData sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *Tags_API_TagBulkCreateError_tag) SetAdditionalData(value map[string]any) { + m.additionalData = value +} + +type Tags_API_TagBulkCreateError_tagable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_filter.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_filter.go new file mode 100644 index 00000000..651b9299 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_filter.go @@ -0,0 +1,194 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +import ( + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +type Tags_API_TagFilter struct { + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]any + // Filter key used for filtering.When `scope` is `default`, accepted values are `agent-id`, `location`, `serial-number`, `public-network`, `local-network`, `connection`, `gateway`, `platform`, `nic-model`, `nic-driver-version`, `agent-type`, `proxy-target`, `vpn-vendor`, `vpn-gateway-address`, `vpn-target`, `vpn-client-network`, `vpn-client-address`, `ip-address-family`, `ssid`, `bssid`, `hostname`, `username`, and `asn`.When `scope` is `custom`, use a user-defined check-in metadata key. + key *string + // Type of matching logic applied to the values:* `in`: The value on the agent must match one of the list of values provided.* `not-in`: The value on the agent must not match any of the list of values provided. + mode *Tags_API_TagFilterMode + // Scope of the filter.* `default`: The filter is from the default set provided by ThousandEyes.* `custom`: The filter is user-defined. Set in check-in metadata. + scope *Tags_API_TagFilterScope + // The values property + values []string +} + +// NewTags_API_TagFilter instantiates a new Tags_API_TagFilter and sets the default values. +func NewTags_API_TagFilter() *Tags_API_TagFilter { + m := &Tags_API_TagFilter{} + m.SetAdditionalData(make(map[string]any)) + scopeValue := DEFAULT_TAGS_API_TAGFILTERSCOPE + m.SetScope(&scopeValue) + return m +} + +// CreateTags_API_TagFilterFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateTags_API_TagFilterFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewTags_API_TagFilter(), nil +} + +// GetAdditionalData gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +// returns a map[string]any when successful +func (m *Tags_API_TagFilter) GetAdditionalData() map[string]any { + return m.additionalData +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *Tags_API_TagFilter) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error) + res["key"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetKey(val) + } + return nil + } + res["mode"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetEnumValue(ParseTags_API_TagFilterMode) + if err != nil { + return err + } + if val != nil { + m.SetMode(val.(*Tags_API_TagFilterMode)) + } + return nil + } + res["scope"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetEnumValue(ParseTags_API_TagFilterScope) + if err != nil { + return err + } + if val != nil { + m.SetScope(val.(*Tags_API_TagFilterScope)) + } + return nil + } + res["values"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetCollectionOfPrimitiveValues("string") + if err != nil { + return err + } + if val != nil { + res := make([]string, len(val)) + for i, v := range val { + if v != nil { + res[i] = *v.(*string) + } + } + m.SetValues(res) + } + return nil + } + return res +} + +// GetKey gets the key property value. Filter key used for filtering.When `scope` is `default`, accepted values are `agent-id`, `location`, `serial-number`, `public-network`, `local-network`, `connection`, `gateway`, `platform`, `nic-model`, `nic-driver-version`, `agent-type`, `proxy-target`, `vpn-vendor`, `vpn-gateway-address`, `vpn-target`, `vpn-client-network`, `vpn-client-address`, `ip-address-family`, `ssid`, `bssid`, `hostname`, `username`, and `asn`.When `scope` is `custom`, use a user-defined check-in metadata key. +// returns a *string when successful +func (m *Tags_API_TagFilter) GetKey() *string { + return m.key +} + +// GetMode gets the mode property value. Type of matching logic applied to the values:* `in`: The value on the agent must match one of the list of values provided.* `not-in`: The value on the agent must not match any of the list of values provided. +// returns a *Tags_API_TagFilterMode when successful +func (m *Tags_API_TagFilter) GetMode() *Tags_API_TagFilterMode { + return m.mode +} + +// GetScope gets the scope property value. Scope of the filter.* `default`: The filter is from the default set provided by ThousandEyes.* `custom`: The filter is user-defined. Set in check-in metadata. +// returns a *Tags_API_TagFilterScope when successful +func (m *Tags_API_TagFilter) GetScope() *Tags_API_TagFilterScope { + return m.scope +} + +// GetValues gets the values property value. The values property +// returns a []string when successful +func (m *Tags_API_TagFilter) GetValues() []string { + return m.values +} + +// Serialize serializes information the current object +func (m *Tags_API_TagFilter) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + { + err := writer.WriteStringValue("key", m.GetKey()) + if err != nil { + return err + } + } + if m.GetMode() != nil { + cast := (*m.GetMode()).String() + err := writer.WriteStringValue("mode", &cast) + if err != nil { + return err + } + } + if m.GetScope() != nil { + cast := (*m.GetScope()).String() + err := writer.WriteStringValue("scope", &cast) + if err != nil { + return err + } + } + if m.GetValues() != nil { + err := writer.WriteCollectionOfStringValues("values", m.GetValues()) + if err != nil { + return err + } + } + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAdditionalData sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *Tags_API_TagFilter) SetAdditionalData(value map[string]any) { + m.additionalData = value +} + +// SetKey sets the key property value. Filter key used for filtering.When `scope` is `default`, accepted values are `agent-id`, `location`, `serial-number`, `public-network`, `local-network`, `connection`, `gateway`, `platform`, `nic-model`, `nic-driver-version`, `agent-type`, `proxy-target`, `vpn-vendor`, `vpn-gateway-address`, `vpn-target`, `vpn-client-network`, `vpn-client-address`, `ip-address-family`, `ssid`, `bssid`, `hostname`, `username`, and `asn`.When `scope` is `custom`, use a user-defined check-in metadata key. +func (m *Tags_API_TagFilter) SetKey(value *string) { + m.key = value +} + +// SetMode sets the mode property value. Type of matching logic applied to the values:* `in`: The value on the agent must match one of the list of values provided.* `not-in`: The value on the agent must not match any of the list of values provided. +func (m *Tags_API_TagFilter) SetMode(value *Tags_API_TagFilterMode) { + m.mode = value +} + +// SetScope sets the scope property value. Scope of the filter.* `default`: The filter is from the default set provided by ThousandEyes.* `custom`: The filter is user-defined. Set in check-in metadata. +func (m *Tags_API_TagFilter) SetScope(value *Tags_API_TagFilterScope) { + m.scope = value +} + +// SetValues sets the values property value. The values property +func (m *Tags_API_TagFilter) SetValues(value []string) { + m.values = value +} + +type Tags_API_TagFilterable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable + GetKey() *string + GetMode() *Tags_API_TagFilterMode + GetScope() *Tags_API_TagFilterScope + GetValues() []string + SetKey(value *string) + SetMode(value *Tags_API_TagFilterMode) + SetScope(value *Tags_API_TagFilterScope) + SetValues(value []string) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_filter_mode.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_filter_mode.go new file mode 100644 index 00000000..83bfa6ce --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_filter_mode.go @@ -0,0 +1,41 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +// Type of matching logic applied to the values:* `in`: The value on the agent must match one of the list of values provided.* `not-in`: The value on the agent must not match any of the list of values provided. +type Tags_API_TagFilterMode int + +const ( + IN_TAGS_API_TAGFILTERMODE Tags_API_TagFilterMode = iota + NOTIN_TAGS_API_TAGFILTERMODE +) + +func (i Tags_API_TagFilterMode) String() string { + return []string{"in", "not-in"}[i] +} + +func ParseTags_API_TagFilterMode(v string) (any, error) { + result := IN_TAGS_API_TAGFILTERMODE + switch v { + case "in": + result = IN_TAGS_API_TAGFILTERMODE + case "not-in": + result = NOTIN_TAGS_API_TAGFILTERMODE + default: + return nil, nil + } + return &result, nil +} + +func SerializeTags_API_TagFilterMode(values []Tags_API_TagFilterMode) []string { + result := make([]string, len(values)) + for i, v := range values { + result[i] = v.String() + } + return result +} + +func (i Tags_API_TagFilterMode) isMultiValue() bool { + return false +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_filter_scope.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_filter_scope.go new file mode 100644 index 00000000..9d416e44 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_filter_scope.go @@ -0,0 +1,41 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +// Scope of the filter.* `default`: The filter is from the default set provided by ThousandEyes.* `custom`: The filter is user-defined. Set in check-in metadata. +type Tags_API_TagFilterScope int + +const ( + DEFAULT_TAGS_API_TAGFILTERSCOPE Tags_API_TagFilterScope = iota + CUSTOM_TAGS_API_TAGFILTERSCOPE +) + +func (i Tags_API_TagFilterScope) String() string { + return []string{"default", "custom"}[i] +} + +func ParseTags_API_TagFilterScope(v string) (any, error) { + result := DEFAULT_TAGS_API_TAGFILTERSCOPE + switch v { + case "default": + result = DEFAULT_TAGS_API_TAGFILTERSCOPE + case "custom": + result = CUSTOM_TAGS_API_TAGFILTERSCOPE + default: + return nil, nil + } + return &result, nil +} + +func SerializeTags_API_TagFilterScope(values []Tags_API_TagFilterScope) []string { + result := make([]string, len(values)) + for i, v := range values { + result[i] = v.String() + } + return result +} + +func (i Tags_API_TagFilterScope) isMultiValue() bool { + return false +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_info.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_info.go new file mode 100644 index 00000000..b9de2c5c --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_info.go @@ -0,0 +1,570 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +import ( + i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e "time" + + i561e97a8befe7661a44c8f54600992b4207a3a0cf6770e5559949bc276de2e22 "github.com/google/uuid" + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +type Tags_API_TagInfo struct { + // The access level of the tag. The access level determines the label's visibility in the UI and the permissions required to modify it. Accepted values are:* `all`: The tag is visible and editable by any user in the account group with the standard **View tags** or **Edit tags** permission. Default for all user-created tags.* `partner`: The tag is owned by a specific integration partner. Only that partner can read or modify it. It's hidden from all regular account users and excluded from their GET responses.* `system`: Reserved for ThousandEyes internal use only. This value may appear in GET responses on internally managed tags but cannot be set by users or integration partners. + accessType *Tags_API_AccessType + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]any + // The account group ID + aid *int64 + // The assignments property + assignments []Tags_API_Assignmentable + // Indicates whether it is a built-in tag or a user-created (custom) tag. + builtIn *bool + // Tag color + color *string + // Tag creation date + createDate *string + // The tag's description. + description *string + // The combination of filters (filter keys) dynamically assigned to an `endpoint-agent` as determined by the matching logic (`and` or `or`). For example, if you filter on `bssid` and `ssid` with a matching logic of `and`, both filters are assigned as tags to the `endpoint-agent`; `or` means either filter can be assigned. **Note:** filters currently only apply to `endpoint-agent` object types. + filters []Tags_API_TagFilterable + // The icon property + icon *string + // The tag ID + id *i561e97a8befe7661a44c8f54600992b4207a3a0cf6770e5559949bc276de2e22.UUID + // The tags's key + key *string + // The legacyId property + legacyId *float64 + // Specifies how filters (tag keys) are applied (matched) to objects for tag assignment.* `and`: All filters must be matched.* `or`: Any of the filters should match.Note: filtering and matching currently only apply to `endpoint-agent` object types, as these functions are used for dynamic tag assignment. + matchType *Tags_API_TagMatchType + // The date and time the tag was last modified. + modifiedDate *i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e.Time + // The object type associated with the tag + objectType *Tags_API_ObjectType + // The nature of the tag - whether the tag is dynamically assigned to objects based on a filter rule or statically assigned to specified objects. Currently only `endpoint-agent` object types support dynamic assignment; all other object types (for example, `test`, `agent`) support only static assignment. + typeEscaped *Tags_API_Type + // The tag's value + value *string +} + +// NewTags_API_TagInfo instantiates a new Tags_API_TagInfo and sets the default values. +func NewTags_API_TagInfo() *Tags_API_TagInfo { + m := &Tags_API_TagInfo{} + m.SetAdditionalData(make(map[string]any)) + return m +} + +// CreateTags_API_TagInfoFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateTags_API_TagInfoFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewTags_API_TagInfo(), nil +} + +// GetAccessType gets the accessType property value. The access level of the tag. The access level determines the label's visibility in the UI and the permissions required to modify it. Accepted values are:* `all`: The tag is visible and editable by any user in the account group with the standard **View tags** or **Edit tags** permission. Default for all user-created tags.* `partner`: The tag is owned by a specific integration partner. Only that partner can read or modify it. It's hidden from all regular account users and excluded from their GET responses.* `system`: Reserved for ThousandEyes internal use only. This value may appear in GET responses on internally managed tags but cannot be set by users or integration partners. +// returns a *Tags_API_AccessType when successful +func (m *Tags_API_TagInfo) GetAccessType() *Tags_API_AccessType { + return m.accessType +} + +// GetAdditionalData gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +// returns a map[string]any when successful +func (m *Tags_API_TagInfo) GetAdditionalData() map[string]any { + return m.additionalData +} + +// GetAid gets the aid property value. The account group ID +// returns a *int64 when successful +func (m *Tags_API_TagInfo) GetAid() *int64 { + return m.aid +} + +// GetAssignments gets the assignments property value. The assignments property +// returns a []Tags_API_Assignmentable when successful +func (m *Tags_API_TagInfo) GetAssignments() []Tags_API_Assignmentable { + return m.assignments +} + +// GetBuiltIn gets the builtIn property value. Indicates whether it is a built-in tag or a user-created (custom) tag. +// returns a *bool when successful +func (m *Tags_API_TagInfo) GetBuiltIn() *bool { + return m.builtIn +} + +// GetColor gets the color property value. Tag color +// returns a *string when successful +func (m *Tags_API_TagInfo) GetColor() *string { + return m.color +} + +// GetCreateDate gets the createDate property value. Tag creation date +// returns a *string when successful +func (m *Tags_API_TagInfo) GetCreateDate() *string { + return m.createDate +} + +// GetDescription gets the description property value. The tag's description. +// returns a *string when successful +func (m *Tags_API_TagInfo) GetDescription() *string { + return m.description +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *Tags_API_TagInfo) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error) + res["accessType"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetEnumValue(ParseTags_API_AccessType) + if err != nil { + return err + } + if val != nil { + m.SetAccessType(val.(*Tags_API_AccessType)) + } + return nil + } + res["aid"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetInt64Value() + if err != nil { + return err + } + if val != nil { + m.SetAid(val) + } + return nil + } + res["assignments"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetCollectionOfObjectValues(CreateTags_API_AssignmentFromDiscriminatorValue) + if err != nil { + return err + } + if val != nil { + res := make([]Tags_API_Assignmentable, len(val)) + for i, v := range val { + if v != nil { + res[i] = v.(Tags_API_Assignmentable) + } + } + m.SetAssignments(res) + } + return nil + } + res["builtIn"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetBoolValue() + if err != nil { + return err + } + if val != nil { + m.SetBuiltIn(val) + } + return nil + } + res["color"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetColor(val) + } + return nil + } + res["createDate"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetCreateDate(val) + } + return nil + } + res["description"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetDescription(val) + } + return nil + } + res["filters"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetCollectionOfObjectValues(CreateTags_API_TagFilterFromDiscriminatorValue) + if err != nil { + return err + } + if val != nil { + res := make([]Tags_API_TagFilterable, len(val)) + for i, v := range val { + if v != nil { + res[i] = v.(Tags_API_TagFilterable) + } + } + m.SetFilters(res) + } + return nil + } + res["icon"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetIcon(val) + } + return nil + } + res["id"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetUUIDValue() + if err != nil { + return err + } + if val != nil { + m.SetId(val) + } + return nil + } + res["key"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetKey(val) + } + return nil + } + res["legacyId"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetFloat64Value() + if err != nil { + return err + } + if val != nil { + m.SetLegacyId(val) + } + return nil + } + res["matchType"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetEnumValue(ParseTags_API_TagMatchType) + if err != nil { + return err + } + if val != nil { + m.SetMatchType(val.(*Tags_API_TagMatchType)) + } + return nil + } + res["modifiedDate"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetTimeValue() + if err != nil { + return err + } + if val != nil { + m.SetModifiedDate(val) + } + return nil + } + res["objectType"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetEnumValue(ParseTags_API_ObjectType) + if err != nil { + return err + } + if val != nil { + m.SetObjectType(val.(*Tags_API_ObjectType)) + } + return nil + } + res["type"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetEnumValue(ParseTags_API_Type) + if err != nil { + return err + } + if val != nil { + m.SetTypeEscaped(val.(*Tags_API_Type)) + } + return nil + } + res["value"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetValue(val) + } + return nil + } + return res +} + +// GetFilters gets the filters property value. The combination of filters (filter keys) dynamically assigned to an `endpoint-agent` as determined by the matching logic (`and` or `or`). For example, if you filter on `bssid` and `ssid` with a matching logic of `and`, both filters are assigned as tags to the `endpoint-agent`; `or` means either filter can be assigned. **Note:** filters currently only apply to `endpoint-agent` object types. +// returns a []Tags_API_TagFilterable when successful +func (m *Tags_API_TagInfo) GetFilters() []Tags_API_TagFilterable { + return m.filters +} + +// GetIcon gets the icon property value. The icon property +// returns a *string when successful +func (m *Tags_API_TagInfo) GetIcon() *string { + return m.icon +} + +// GetId gets the id property value. The tag ID +// returns a *UUID when successful +func (m *Tags_API_TagInfo) GetId() *i561e97a8befe7661a44c8f54600992b4207a3a0cf6770e5559949bc276de2e22.UUID { + return m.id +} + +// GetKey gets the key property value. The tags's key +// returns a *string when successful +func (m *Tags_API_TagInfo) GetKey() *string { + return m.key +} + +// GetLegacyId gets the legacyId property value. The legacyId property +// returns a *float64 when successful +func (m *Tags_API_TagInfo) GetLegacyId() *float64 { + return m.legacyId +} + +// GetMatchType gets the matchType property value. Specifies how filters (tag keys) are applied (matched) to objects for tag assignment.* `and`: All filters must be matched.* `or`: Any of the filters should match.Note: filtering and matching currently only apply to `endpoint-agent` object types, as these functions are used for dynamic tag assignment. +// returns a *Tags_API_TagMatchType when successful +func (m *Tags_API_TagInfo) GetMatchType() *Tags_API_TagMatchType { + return m.matchType +} + +// GetModifiedDate gets the modifiedDate property value. The date and time the tag was last modified. +// returns a *Time when successful +func (m *Tags_API_TagInfo) GetModifiedDate() *i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e.Time { + return m.modifiedDate +} + +// GetObjectType gets the objectType property value. The object type associated with the tag +// returns a *Tags_API_ObjectType when successful +func (m *Tags_API_TagInfo) GetObjectType() *Tags_API_ObjectType { + return m.objectType +} + +// GetTypeEscaped gets the type property value. The nature of the tag - whether the tag is dynamically assigned to objects based on a filter rule or statically assigned to specified objects. Currently only `endpoint-agent` object types support dynamic assignment; all other object types (for example, `test`, `agent`) support only static assignment. +// returns a *Tags_API_Type when successful +func (m *Tags_API_TagInfo) GetTypeEscaped() *Tags_API_Type { + return m.typeEscaped +} + +// GetValue gets the value property value. The tag's value +// returns a *string when successful +func (m *Tags_API_TagInfo) GetValue() *string { + return m.value +} + +// Serialize serializes information the current object +func (m *Tags_API_TagInfo) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + if m.GetAccessType() != nil { + cast := (*m.GetAccessType()).String() + err := writer.WriteStringValue("accessType", &cast) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("color", m.GetColor()) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("description", m.GetDescription()) + if err != nil { + return err + } + } + if m.GetFilters() != nil { + cast := make([]i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, len(m.GetFilters())) + for i, v := range m.GetFilters() { + if v != nil { + cast[i] = v.(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable) + } + } + err := writer.WriteCollectionOfObjectValues("filters", cast) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("icon", m.GetIcon()) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("key", m.GetKey()) + if err != nil { + return err + } + } + if m.GetMatchType() != nil { + cast := (*m.GetMatchType()).String() + err := writer.WriteStringValue("matchType", &cast) + if err != nil { + return err + } + } + if m.GetObjectType() != nil { + cast := (*m.GetObjectType()).String() + err := writer.WriteStringValue("objectType", &cast) + if err != nil { + return err + } + } + if m.GetTypeEscaped() != nil { + cast := (*m.GetTypeEscaped()).String() + err := writer.WriteStringValue("type", &cast) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("value", m.GetValue()) + if err != nil { + return err + } + } + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAccessType sets the accessType property value. The access level of the tag. The access level determines the label's visibility in the UI and the permissions required to modify it. Accepted values are:* `all`: The tag is visible and editable by any user in the account group with the standard **View tags** or **Edit tags** permission. Default for all user-created tags.* `partner`: The tag is owned by a specific integration partner. Only that partner can read or modify it. It's hidden from all regular account users and excluded from their GET responses.* `system`: Reserved for ThousandEyes internal use only. This value may appear in GET responses on internally managed tags but cannot be set by users or integration partners. +func (m *Tags_API_TagInfo) SetAccessType(value *Tags_API_AccessType) { + m.accessType = value +} + +// SetAdditionalData sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *Tags_API_TagInfo) SetAdditionalData(value map[string]any) { + m.additionalData = value +} + +// SetAid sets the aid property value. The account group ID +func (m *Tags_API_TagInfo) SetAid(value *int64) { + m.aid = value +} + +// SetAssignments sets the assignments property value. The assignments property +func (m *Tags_API_TagInfo) SetAssignments(value []Tags_API_Assignmentable) { + m.assignments = value +} + +// SetBuiltIn sets the builtIn property value. Indicates whether it is a built-in tag or a user-created (custom) tag. +func (m *Tags_API_TagInfo) SetBuiltIn(value *bool) { + m.builtIn = value +} + +// SetColor sets the color property value. Tag color +func (m *Tags_API_TagInfo) SetColor(value *string) { + m.color = value +} + +// SetCreateDate sets the createDate property value. Tag creation date +func (m *Tags_API_TagInfo) SetCreateDate(value *string) { + m.createDate = value +} + +// SetDescription sets the description property value. The tag's description. +func (m *Tags_API_TagInfo) SetDescription(value *string) { + m.description = value +} + +// SetFilters sets the filters property value. The combination of filters (filter keys) dynamically assigned to an `endpoint-agent` as determined by the matching logic (`and` or `or`). For example, if you filter on `bssid` and `ssid` with a matching logic of `and`, both filters are assigned as tags to the `endpoint-agent`; `or` means either filter can be assigned. **Note:** filters currently only apply to `endpoint-agent` object types. +func (m *Tags_API_TagInfo) SetFilters(value []Tags_API_TagFilterable) { + m.filters = value +} + +// SetIcon sets the icon property value. The icon property +func (m *Tags_API_TagInfo) SetIcon(value *string) { + m.icon = value +} + +// SetId sets the id property value. The tag ID +func (m *Tags_API_TagInfo) SetId(value *i561e97a8befe7661a44c8f54600992b4207a3a0cf6770e5559949bc276de2e22.UUID) { + m.id = value +} + +// SetKey sets the key property value. The tags's key +func (m *Tags_API_TagInfo) SetKey(value *string) { + m.key = value +} + +// SetLegacyId sets the legacyId property value. The legacyId property +func (m *Tags_API_TagInfo) SetLegacyId(value *float64) { + m.legacyId = value +} + +// SetMatchType sets the matchType property value. Specifies how filters (tag keys) are applied (matched) to objects for tag assignment.* `and`: All filters must be matched.* `or`: Any of the filters should match.Note: filtering and matching currently only apply to `endpoint-agent` object types, as these functions are used for dynamic tag assignment. +func (m *Tags_API_TagInfo) SetMatchType(value *Tags_API_TagMatchType) { + m.matchType = value +} + +// SetModifiedDate sets the modifiedDate property value. The date and time the tag was last modified. +func (m *Tags_API_TagInfo) SetModifiedDate(value *i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e.Time) { + m.modifiedDate = value +} + +// SetObjectType sets the objectType property value. The object type associated with the tag +func (m *Tags_API_TagInfo) SetObjectType(value *Tags_API_ObjectType) { + m.objectType = value +} + +// SetTypeEscaped sets the type property value. The nature of the tag - whether the tag is dynamically assigned to objects based on a filter rule or statically assigned to specified objects. Currently only `endpoint-agent` object types support dynamic assignment; all other object types (for example, `test`, `agent`) support only static assignment. +func (m *Tags_API_TagInfo) SetTypeEscaped(value *Tags_API_Type) { + m.typeEscaped = value +} + +// SetValue sets the value property value. The tag's value +func (m *Tags_API_TagInfo) SetValue(value *string) { + m.value = value +} + +type Tags_API_TagInfoable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable + GetAccessType() *Tags_API_AccessType + GetAid() *int64 + GetAssignments() []Tags_API_Assignmentable + GetBuiltIn() *bool + GetColor() *string + GetCreateDate() *string + GetDescription() *string + GetFilters() []Tags_API_TagFilterable + GetIcon() *string + GetId() *i561e97a8befe7661a44c8f54600992b4207a3a0cf6770e5559949bc276de2e22.UUID + GetKey() *string + GetLegacyId() *float64 + GetMatchType() *Tags_API_TagMatchType + GetModifiedDate() *i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e.Time + GetObjectType() *Tags_API_ObjectType + GetTypeEscaped() *Tags_API_Type + GetValue() *string + SetAccessType(value *Tags_API_AccessType) + SetAid(value *int64) + SetAssignments(value []Tags_API_Assignmentable) + SetBuiltIn(value *bool) + SetColor(value *string) + SetCreateDate(value *string) + SetDescription(value *string) + SetFilters(value []Tags_API_TagFilterable) + SetIcon(value *string) + SetId(value *i561e97a8befe7661a44c8f54600992b4207a3a0cf6770e5559949bc276de2e22.UUID) + SetKey(value *string) + SetLegacyId(value *float64) + SetMatchType(value *Tags_API_TagMatchType) + SetModifiedDate(value *i336074805fc853987abe6f7fe3ad97a6a6f3077a16391fec744f671a015fbd7e.Time) + SetObjectType(value *Tags_API_ObjectType) + SetTypeEscaped(value *Tags_API_Type) + SetValue(value *string) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_match_type.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_match_type.go new file mode 100644 index 00000000..c0aeff5a --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tag_match_type.go @@ -0,0 +1,41 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +// Specifies how filters (tag keys) are applied (matched) to objects for tag assignment.* `and`: All filters must be matched.* `or`: Any of the filters should match.Note: filtering and matching currently only apply to `endpoint-agent` object types, as these functions are used for dynamic tag assignment. +type Tags_API_TagMatchType int + +const ( + AND_TAGS_API_TAGMATCHTYPE Tags_API_TagMatchType = iota + OR_TAGS_API_TAGMATCHTYPE +) + +func (i Tags_API_TagMatchType) String() string { + return []string{"and", "or"}[i] +} + +func ParseTags_API_TagMatchType(v string) (any, error) { + result := AND_TAGS_API_TAGMATCHTYPE + switch v { + case "and": + result = AND_TAGS_API_TAGMATCHTYPE + case "or": + result = OR_TAGS_API_TAGMATCHTYPE + default: + return nil, nil + } + return &result, nil +} + +func SerializeTags_API_TagMatchType(values []Tags_API_TagMatchType) []string { + result := make([]string, len(values)) + for i, v := range values { + result[i] = v.String() + } + return result +} + +func (i Tags_API_TagMatchType) isMultiValue() bool { + return false +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tags.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tags.go new file mode 100644 index 00000000..a7532dee --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_tags.go @@ -0,0 +1,128 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +import ( + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +type Tags_API_Tags struct { + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]any + // A links object containing the self link. + links Tags_API_SelfLinksable + // The tags property + tags []Tags_API_Tagable +} + +// NewTags_API_Tags instantiates a new Tags_API_Tags and sets the default values. +func NewTags_API_Tags() *Tags_API_Tags { + m := &Tags_API_Tags{} + m.SetAdditionalData(make(map[string]any)) + return m +} + +// CreateTags_API_TagsFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateTags_API_TagsFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewTags_API_Tags(), nil +} + +// GetAdditionalData gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +// returns a map[string]any when successful +func (m *Tags_API_Tags) GetAdditionalData() map[string]any { + return m.additionalData +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *Tags_API_Tags) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error) + res["_links"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetObjectValue(CreateTags_API_SelfLinksFromDiscriminatorValue) + if err != nil { + return err + } + if val != nil { + m.SetLinks(val.(Tags_API_SelfLinksable)) + } + return nil + } + res["tags"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetCollectionOfObjectValues(CreateTags_API_TagFromDiscriminatorValue) + if err != nil { + return err + } + if val != nil { + res := make([]Tags_API_Tagable, len(val)) + for i, v := range val { + if v != nil { + res[i] = v.(Tags_API_Tagable) + } + } + m.SetTags(res) + } + return nil + } + return res +} + +// GetLinks gets the _links property value. A links object containing the self link. +// returns a Tags_API_SelfLinksable when successful +func (m *Tags_API_Tags) GetLinks() Tags_API_SelfLinksable { + return m.links +} + +// GetTags gets the tags property value. The tags property +// returns a []Tags_API_Tagable when successful +func (m *Tags_API_Tags) GetTags() []Tags_API_Tagable { + return m.tags +} + +// Serialize serializes information the current object +func (m *Tags_API_Tags) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + if m.GetTags() != nil { + cast := make([]i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, len(m.GetTags())) + for i, v := range m.GetTags() { + if v != nil { + cast[i] = v.(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable) + } + } + err := writer.WriteCollectionOfObjectValues("tags", cast) + if err != nil { + return err + } + } + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAdditionalData sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *Tags_API_Tags) SetAdditionalData(value map[string]any) { + m.additionalData = value +} + +// SetLinks sets the _links property value. A links object containing the self link. +func (m *Tags_API_Tags) SetLinks(value Tags_API_SelfLinksable) { + m.links = value +} + +// SetTags sets the tags property value. The tags property +func (m *Tags_API_Tags) SetTags(value []Tags_API_Tagable) { + m.tags = value +} + +type Tags_API_Tagsable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable + GetLinks() Tags_API_SelfLinksable + GetTags() []Tags_API_Tagable + SetLinks(value Tags_API_SelfLinksable) + SetTags(value []Tags_API_Tagable) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_type.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_type.go new file mode 100644 index 00000000..3a78c6db --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_type.go @@ -0,0 +1,41 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +// The nature of the tag - whether the tag is dynamically assigned to objects based on a filter rule or statically assigned to specified objects. Currently only `endpoint-agent` object types support dynamic assignment; all other object types (for example, `test`, `agent`) support only static assignment. +type Tags_API_Type int + +const ( + STATIC_TAGS_API_TYPE Tags_API_Type = iota + DYNAMIC_TAGS_API_TYPE +) + +func (i Tags_API_Type) String() string { + return []string{"static", "dynamic"}[i] +} + +func ParseTags_API_Type(v string) (any, error) { + result := STATIC_TAGS_API_TYPE + switch v { + case "static": + result = STATIC_TAGS_API_TYPE + case "dynamic": + result = DYNAMIC_TAGS_API_TYPE + default: + return nil, nil + } + return &result, nil +} + +func SerializeTags_API_Type(values []Tags_API_Type) []string { + result := make([]string, len(values)) + for i, v := range values { + result[i] = v.String() + } + return result +} + +func (i Tags_API_Type) isMultiValue() bool { + return false +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_unauthorized_error.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_unauthorized_error.go new file mode 100644 index 00000000..a0b7909d --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_unauthorized_error.go @@ -0,0 +1,132 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +import ( + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f "github.com/microsoft/kiota-abstractions-go" + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +type Tags_API_UnauthorizedError struct { + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ApiError + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]any + // The error_description property + error_description *string + // The error property + errorEscaped *string +} + +// NewTags_API_UnauthorizedError instantiates a new Tags_API_UnauthorizedError and sets the default values. +func NewTags_API_UnauthorizedError() *Tags_API_UnauthorizedError { + m := &Tags_API_UnauthorizedError{ + ApiError: *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewApiError(), + } + m.SetAdditionalData(make(map[string]any)) + return m +} + +// CreateTags_API_UnauthorizedErrorFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateTags_API_UnauthorizedErrorFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewTags_API_UnauthorizedError(), nil +} + +// Error the primary error message. +// returns a string when successful +func (m *Tags_API_UnauthorizedError) Error() string { + return m.ApiError.Error() +} + +// GetAdditionalData gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +// returns a map[string]any when successful +func (m *Tags_API_UnauthorizedError) GetAdditionalData() map[string]any { + return m.additionalData +} + +// GetErrorDescription gets the error_description property value. The error_description property +// returns a *string when successful +func (m *Tags_API_UnauthorizedError) GetErrorDescription() *string { + return m.error_description +} + +// GetErrorEscaped gets the error property value. The error property +// returns a *string when successful +func (m *Tags_API_UnauthorizedError) GetErrorEscaped() *string { + return m.errorEscaped +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *Tags_API_UnauthorizedError) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error) + res["error_description"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetErrorDescription(val) + } + return nil + } + res["error"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetErrorEscaped(val) + } + return nil + } + return res +} + +// Serialize serializes information the current object +func (m *Tags_API_UnauthorizedError) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + { + err := writer.WriteStringValue("error", m.GetErrorEscaped()) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("error_description", m.GetErrorDescription()) + if err != nil { + return err + } + } + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAdditionalData sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *Tags_API_UnauthorizedError) SetAdditionalData(value map[string]any) { + m.additionalData = value +} + +// SetErrorDescription sets the error_description property value. The error_description property +func (m *Tags_API_UnauthorizedError) SetErrorDescription(value *string) { + m.error_description = value +} + +// SetErrorEscaped sets the error property value. The error property +func (m *Tags_API_UnauthorizedError) SetErrorEscaped(value *string) { + m.errorEscaped = value +} + +type Tags_API_UnauthorizedErrorable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable + GetErrorDescription() *string + GetErrorEscaped() *string + SetErrorDescription(value *string) + SetErrorEscaped(value *string) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_validation_error.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_validation_error.go new file mode 100644 index 00000000..80c69507 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_validation_error.go @@ -0,0 +1,268 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +import ( + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f "github.com/microsoft/kiota-abstractions-go" + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +type Tags_API_ValidationError struct { + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ApiError + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]any + // A human-readable explanation specific to this occurrence of the problem. + detail *string + // (Optional) When multiple errors occur, the details for each error are listed. + errors []Tags_API_ValidationErrorItemable + // A URI reference that identifies the specific occurrence of the problem. + instance *string + // The HTTP status code generated by the origin server for this occurrence of the problem. + status *int32 + // A short, human-readable summary of the problem type. + title *string + // A URI reference that identifies the problem type. When this member is not present, its value is assumed to be "about:blank". + typeEscaped *string +} + +// NewTags_API_ValidationError instantiates a new Tags_API_ValidationError and sets the default values. +func NewTags_API_ValidationError() *Tags_API_ValidationError { + m := &Tags_API_ValidationError{ + ApiError: *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewApiError(), + } + m.SetAdditionalData(make(map[string]any)) + return m +} + +// CreateTags_API_ValidationErrorFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateTags_API_ValidationErrorFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewTags_API_ValidationError(), nil +} + +// Error the primary error message. +// returns a string when successful +func (m *Tags_API_ValidationError) Error() string { + return m.ApiError.Error() +} + +// GetAdditionalData gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +// returns a map[string]any when successful +func (m *Tags_API_ValidationError) GetAdditionalData() map[string]any { + return m.additionalData +} + +// GetDetail gets the detail property value. A human-readable explanation specific to this occurrence of the problem. +// returns a *string when successful +func (m *Tags_API_ValidationError) GetDetail() *string { + return m.detail +} + +// GetErrors gets the errors property value. (Optional) When multiple errors occur, the details for each error are listed. +// returns a []Tags_API_ValidationErrorItemable when successful +func (m *Tags_API_ValidationError) GetErrors() []Tags_API_ValidationErrorItemable { + return m.errors +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *Tags_API_ValidationError) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error) + res["detail"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetDetail(val) + } + return nil + } + res["errors"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetCollectionOfObjectValues(CreateTags_API_ValidationErrorItemFromDiscriminatorValue) + if err != nil { + return err + } + if val != nil { + res := make([]Tags_API_ValidationErrorItemable, len(val)) + for i, v := range val { + if v != nil { + res[i] = v.(Tags_API_ValidationErrorItemable) + } + } + m.SetErrors(res) + } + return nil + } + res["instance"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetInstance(val) + } + return nil + } + res["status"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetInt32Value() + if err != nil { + return err + } + if val != nil { + m.SetStatus(val) + } + return nil + } + res["title"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetTitle(val) + } + return nil + } + res["type"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetTypeEscaped(val) + } + return nil + } + return res +} + +// GetInstance gets the instance property value. A URI reference that identifies the specific occurrence of the problem. +// returns a *string when successful +func (m *Tags_API_ValidationError) GetInstance() *string { + return m.instance +} + +// GetStatus gets the status property value. The HTTP status code generated by the origin server for this occurrence of the problem. +// returns a *int32 when successful +func (m *Tags_API_ValidationError) GetStatus() *int32 { + return m.status +} + +// GetTitle gets the title property value. A short, human-readable summary of the problem type. +// returns a *string when successful +func (m *Tags_API_ValidationError) GetTitle() *string { + return m.title +} + +// GetTypeEscaped gets the type property value. A URI reference that identifies the problem type. When this member is not present, its value is assumed to be "about:blank". +// returns a *string when successful +func (m *Tags_API_ValidationError) GetTypeEscaped() *string { + return m.typeEscaped +} + +// Serialize serializes information the current object +func (m *Tags_API_ValidationError) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + { + err := writer.WriteStringValue("detail", m.GetDetail()) + if err != nil { + return err + } + } + if m.GetErrors() != nil { + cast := make([]i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, len(m.GetErrors())) + for i, v := range m.GetErrors() { + if v != nil { + cast[i] = v.(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable) + } + } + err := writer.WriteCollectionOfObjectValues("errors", cast) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("instance", m.GetInstance()) + if err != nil { + return err + } + } + { + err := writer.WriteInt32Value("status", m.GetStatus()) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("title", m.GetTitle()) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("type", m.GetTypeEscaped()) + if err != nil { + return err + } + } + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAdditionalData sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *Tags_API_ValidationError) SetAdditionalData(value map[string]any) { + m.additionalData = value +} + +// SetDetail sets the detail property value. A human-readable explanation specific to this occurrence of the problem. +func (m *Tags_API_ValidationError) SetDetail(value *string) { + m.detail = value +} + +// SetErrors sets the errors property value. (Optional) When multiple errors occur, the details for each error are listed. +func (m *Tags_API_ValidationError) SetErrors(value []Tags_API_ValidationErrorItemable) { + m.errors = value +} + +// SetInstance sets the instance property value. A URI reference that identifies the specific occurrence of the problem. +func (m *Tags_API_ValidationError) SetInstance(value *string) { + m.instance = value +} + +// SetStatus sets the status property value. The HTTP status code generated by the origin server for this occurrence of the problem. +func (m *Tags_API_ValidationError) SetStatus(value *int32) { + m.status = value +} + +// SetTitle sets the title property value. A short, human-readable summary of the problem type. +func (m *Tags_API_ValidationError) SetTitle(value *string) { + m.title = value +} + +// SetTypeEscaped sets the type property value. A URI reference that identifies the problem type. When this member is not present, its value is assumed to be "about:blank". +func (m *Tags_API_ValidationError) SetTypeEscaped(value *string) { + m.typeEscaped = value +} + +type Tags_API_ValidationErrorable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable + GetDetail() *string + GetErrors() []Tags_API_ValidationErrorItemable + GetInstance() *string + GetStatus() *int32 + GetTitle() *string + GetTypeEscaped() *string + SetDetail(value *string) + SetErrors(value []Tags_API_ValidationErrorItemable) + SetInstance(value *string) + SetStatus(value *int32) + SetTitle(value *string) + SetTypeEscaped(value *string) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_validation_error_item.go b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_validation_error_item.go new file mode 100644 index 00000000..ac069be2 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/models/tags_escaped_a_p_i_escaped_validation_error_item.go @@ -0,0 +1,153 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package models + +import ( + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" +) + +type Tags_API_ValidationErrorItem struct { + // Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. + additionalData map[string]any + // (Optional) A unique error type/code that can be referenced in the documentation for further details. + code *string + // Identifies the field that triggered this particular error. + field *string + // A short, human-readable summary of the error. + message *string +} + +// NewTags_API_ValidationErrorItem instantiates a new Tags_API_ValidationErrorItem and sets the default values. +func NewTags_API_ValidationErrorItem() *Tags_API_ValidationErrorItem { + m := &Tags_API_ValidationErrorItem{} + m.SetAdditionalData(make(map[string]any)) + return m +} + +// CreateTags_API_ValidationErrorItemFromDiscriminatorValue creates a new instance of the appropriate class based on discriminator value +// returns a Parsable when successful +func CreateTags_API_ValidationErrorItemFromDiscriminatorValue(parseNode i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) (i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable, error) { + return NewTags_API_ValidationErrorItem(), nil +} + +// GetAdditionalData gets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +// returns a map[string]any when successful +func (m *Tags_API_ValidationErrorItem) GetAdditionalData() map[string]any { + return m.additionalData +} + +// GetCode gets the code property value. (Optional) A unique error type/code that can be referenced in the documentation for further details. +// returns a *string when successful +func (m *Tags_API_ValidationErrorItem) GetCode() *string { + return m.code +} + +// GetField gets the field property value. Identifies the field that triggered this particular error. +// returns a *string when successful +func (m *Tags_API_ValidationErrorItem) GetField() *string { + return m.field +} + +// GetFieldDeserializers the deserialization information for the current model +// returns a map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode)(error) when successful +func (m *Tags_API_ValidationErrorItem) GetFieldDeserializers() map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + res := make(map[string]func(i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error) + res["code"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetCode(val) + } + return nil + } + res["field"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetField(val) + } + return nil + } + res["message"] = func(n i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNode) error { + val, err := n.GetStringValue() + if err != nil { + return err + } + if val != nil { + m.SetMessage(val) + } + return nil + } + return res +} + +// GetMessage gets the message property value. A short, human-readable summary of the error. +// returns a *string when successful +func (m *Tags_API_ValidationErrorItem) GetMessage() *string { + return m.message +} + +// Serialize serializes information the current object +func (m *Tags_API_ValidationErrorItem) Serialize(writer i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriter) error { + { + err := writer.WriteStringValue("code", m.GetCode()) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("field", m.GetField()) + if err != nil { + return err + } + } + { + err := writer.WriteStringValue("message", m.GetMessage()) + if err != nil { + return err + } + } + { + err := writer.WriteAdditionalData(m.GetAdditionalData()) + if err != nil { + return err + } + } + return nil +} + +// SetAdditionalData sets the AdditionalData property value. Stores additional data not described in the OpenAPI description found when deserializing. Can be used for serialization as well. +func (m *Tags_API_ValidationErrorItem) SetAdditionalData(value map[string]any) { + m.additionalData = value +} + +// SetCode sets the code property value. (Optional) A unique error type/code that can be referenced in the documentation for further details. +func (m *Tags_API_ValidationErrorItem) SetCode(value *string) { + m.code = value +} + +// SetField sets the field property value. Identifies the field that triggered this particular error. +func (m *Tags_API_ValidationErrorItem) SetField(value *string) { + m.field = value +} + +// SetMessage sets the message property value. A short, human-readable summary of the error. +func (m *Tags_API_ValidationErrorItem) SetMessage(value *string) { + m.message = value +} + +type Tags_API_ValidationErrorItemable interface { + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.AdditionalDataHolder + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.Parsable + GetCode() *string + GetField() *string + GetMessage() *string + SetCode(value *string) + SetField(value *string) + SetMessage(value *string) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/tags/assign_request_builder.go b/pilot/thousandeyes-kiota/internal/sdk/tags/assign_request_builder.go new file mode 100644 index 00000000..20a396c4 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/tags/assign_request_builder.go @@ -0,0 +1,85 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package tags + +import ( + "context" + + ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2 "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk/models" + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f "github.com/microsoft/kiota-abstractions-go" +) + +// AssignRequestBuilder builds and executes requests for operations under \tags\assign +type AssignRequestBuilder struct { + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.BaseRequestBuilder +} + +// AssignRequestBuilderPostQueryParameters assigns the specified static tags to the specified objects. This operation has cumulative behavior: The tags are statically assigned to the specified objects, and the previous assignments persist. No unassignment takes place. Note: This endpoint does not support dynamic tag assignment (for example, for `endpoint-agent` objects). See `Type` for more information about static vs. dynamic tags. +type AssignRequestBuilderPostQueryParameters struct { + // A unique identifier associated with your account group. You can retrieve your `AccountGroupId` from the `/account-groups` endpoint. Note that you must be assigned to the target account group. Specifying this parameter without being assigned to the target account group will result in an error response. + Aid *string "uriparametername:\"aid\"" +} + +// NewAssignRequestBuilderInternal instantiates a new AssignRequestBuilder and sets the default values. +func NewAssignRequestBuilderInternal(pathParameters map[string]string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *AssignRequestBuilder { + m := &AssignRequestBuilder{ + BaseRequestBuilder: *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewBaseRequestBuilder(requestAdapter, "{+baseurl}/tags/assign{?aid*}", pathParameters), + } + return m +} + +// NewAssignRequestBuilder instantiates a new AssignRequestBuilder and sets the default values. +func NewAssignRequestBuilder(rawUrl string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *AssignRequestBuilder { + urlParams := make(map[string]string) + urlParams["request-raw-url"] = rawUrl + return NewAssignRequestBuilderInternal(urlParams, requestAdapter) +} + +// Post assigns the specified static tags to the specified objects. This operation has cumulative behavior: The tags are statically assigned to the specified objects, and the previous assignments persist. No unassignment takes place. Note: This endpoint does not support dynamic tag assignment (for example, for `endpoint-agent` objects). See `Type` for more information about static vs. dynamic tags. +// returns a []byte when successful +// returns a Tags_API_UnauthorizedError error when the service returns a 401 status code +// returns a Tags_API_Error error when the service returns a 403 status code +// returns a Tags_API_Error error when the service returns a 404 status code +// returns a Tags_API_Error error when the service returns a 429 status code +// returns a Tags_API_ApiError error when the service returns a 500 status code +func (m *AssignRequestBuilder) Post(ctx context.Context, body ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_BulkTagAssignmentsable, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[AssignRequestBuilderPostQueryParameters]) ([]byte, error) { + requestInfo, err := m.ToPostRequestInformation(ctx, body, requestConfiguration) + if err != nil { + return nil, err + } + errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{ + "401": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_UnauthorizedErrorFromDiscriminatorValue, + "403": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "404": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "429": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "500": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ApiErrorFromDiscriminatorValue, + } + res, err := m.BaseRequestBuilder.RequestAdapter.SendPrimitive(ctx, requestInfo, "[]byte", errorMapping) + if err != nil { + return nil, err + } + if res == nil { + return nil, nil + } + return res.([]byte), nil +} + +// ToPostRequestInformation assigns the specified static tags to the specified objects. This operation has cumulative behavior: The tags are statically assigned to the specified objects, and the previous assignments persist. No unassignment takes place. Note: This endpoint does not support dynamic tag assignment (for example, for `endpoint-agent` objects). See `Type` for more information about static vs. dynamic tags. +// returns a *RequestInformation when successful +func (m *AssignRequestBuilder) ToPostRequestInformation(ctx context.Context, body ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_BulkTagAssignmentsable, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[AssignRequestBuilderPostQueryParameters]) (*i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestInformation, error) { + requestInfo := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.POST, m.BaseRequestBuilder.UrlTemplate, m.BaseRequestBuilder.PathParameters) + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ConfigureRequestInformation(requestInfo, requestConfiguration) + requestInfo.Headers.TryAdd("Accept", "application/hal+json, application/json, application/problem+json") + err := requestInfo.SetContentFromParsable(ctx, m.BaseRequestBuilder.RequestAdapter, "application/json", body) + if err != nil { + return nil, err + } + return requestInfo, nil +} + +// WithUrl returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. +// returns a *AssignRequestBuilder when successful +func (m *AssignRequestBuilder) WithUrl(rawUrl string) *AssignRequestBuilder { + return NewAssignRequestBuilder(rawUrl, m.BaseRequestBuilder.RequestAdapter) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/tags/bulk_request_builder.go b/pilot/thousandeyes-kiota/internal/sdk/tags/bulk_request_builder.go new file mode 100644 index 00000000..308907c1 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/tags/bulk_request_builder.go @@ -0,0 +1,81 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package tags + +import ( + "context" + + ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2 "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk/models" + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f "github.com/microsoft/kiota-abstractions-go" +) + +// BulkRequestBuilder builds and executes requests for operations under \tags\bulk +type BulkRequestBuilder struct { + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.BaseRequestBuilder +} + +// BulkRequestBuilderPostQueryParameters creates multiple tags. Note the response includes a `statuses` array. This array provides status information for each tag object, indexed 1:1 with the `tags` array. +type BulkRequestBuilderPostQueryParameters struct { + // A unique identifier associated with your account group. You can retrieve your `AccountGroupId` from the `/account-groups` endpoint. Note that you must be assigned to the target account group. Specifying this parameter without being assigned to the target account group will result in an error response. + Aid *string "uriparametername:\"aid\"" +} + +// NewBulkRequestBuilderInternal instantiates a new BulkRequestBuilder and sets the default values. +func NewBulkRequestBuilderInternal(pathParameters map[string]string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *BulkRequestBuilder { + m := &BulkRequestBuilder{ + BaseRequestBuilder: *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewBaseRequestBuilder(requestAdapter, "{+baseurl}/tags/bulk{?aid*}", pathParameters), + } + return m +} + +// NewBulkRequestBuilder instantiates a new BulkRequestBuilder and sets the default values. +func NewBulkRequestBuilder(rawUrl string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *BulkRequestBuilder { + urlParams := make(map[string]string) + urlParams["request-raw-url"] = rawUrl + return NewBulkRequestBuilderInternal(urlParams, requestAdapter) +} + +// Post creates multiple tags. Note the response includes a `statuses` array. This array provides status information for each tag object, indexed 1:1 with the `tags` array. +// returns a []byte when successful +// returns a Tags_API_ValidationError error when the service returns a 400 status code +// returns a Tags_API_UnauthorizedError error when the service returns a 401 status code +// returns a Tags_API_ApiError error when the service returns a 500 status code +func (m *BulkRequestBuilder) Post(ctx context.Context, body ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_BulkTagResponseable, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[BulkRequestBuilderPostQueryParameters]) ([]byte, error) { + requestInfo, err := m.ToPostRequestInformation(ctx, body, requestConfiguration) + if err != nil { + return nil, err + } + errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{ + "400": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ValidationErrorFromDiscriminatorValue, + "401": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_UnauthorizedErrorFromDiscriminatorValue, + "500": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ApiErrorFromDiscriminatorValue, + } + res, err := m.BaseRequestBuilder.RequestAdapter.SendPrimitive(ctx, requestInfo, "[]byte", errorMapping) + if err != nil { + return nil, err + } + if res == nil { + return nil, nil + } + return res.([]byte), nil +} + +// ToPostRequestInformation creates multiple tags. Note the response includes a `statuses` array. This array provides status information for each tag object, indexed 1:1 with the `tags` array. +// returns a *RequestInformation when successful +func (m *BulkRequestBuilder) ToPostRequestInformation(ctx context.Context, body ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_BulkTagResponseable, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[BulkRequestBuilderPostQueryParameters]) (*i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestInformation, error) { + requestInfo := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.POST, m.BaseRequestBuilder.UrlTemplate, m.BaseRequestBuilder.PathParameters) + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ConfigureRequestInformation(requestInfo, requestConfiguration) + requestInfo.Headers.TryAdd("Accept", "application/hal+json, application/json, application/problem+json") + err := requestInfo.SetContentFromParsable(ctx, m.BaseRequestBuilder.RequestAdapter, "application/json", body) + if err != nil { + return nil, err + } + return requestInfo, nil +} + +// WithUrl returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. +// returns a *BulkRequestBuilder when successful +func (m *BulkRequestBuilder) WithUrl(rawUrl string) *BulkRequestBuilder { + return NewBulkRequestBuilder(rawUrl, m.BaseRequestBuilder.RequestAdapter) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/tags/item_assign_request_builder.go b/pilot/thousandeyes-kiota/internal/sdk/tags/item_assign_request_builder.go new file mode 100644 index 00000000..cd7c1baa --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/tags/item_assign_request_builder.go @@ -0,0 +1,85 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package tags + +import ( + "context" + + ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2 "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk/models" + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f "github.com/microsoft/kiota-abstractions-go" +) + +// ItemAssignRequestBuilder builds and executes requests for operations under \tags\{id}\assign +type ItemAssignRequestBuilder struct { + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.BaseRequestBuilder +} + +// ItemAssignRequestBuilderPostQueryParameters assigns a static tag to one or more objects. This operation has cumulative behavior: The tag is statically assigned to the specified objects, and the previous assignments persist. No unassignment takes place. Note: This endpoint does not support dynamic tag assignment (for example, for `endpoint-agent` objects). See `Type` for more information about static vs. dynamic tags. +type ItemAssignRequestBuilderPostQueryParameters struct { + // A unique identifier associated with your account group. You can retrieve your `AccountGroupId` from the `/account-groups` endpoint. Note that you must be assigned to the target account group. Specifying this parameter without being assigned to the target account group will result in an error response. + Aid *string "uriparametername:\"aid\"" +} + +// NewItemAssignRequestBuilderInternal instantiates a new ItemAssignRequestBuilder and sets the default values. +func NewItemAssignRequestBuilderInternal(pathParameters map[string]string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *ItemAssignRequestBuilder { + m := &ItemAssignRequestBuilder{ + BaseRequestBuilder: *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewBaseRequestBuilder(requestAdapter, "{+baseurl}/tags/{id}/assign{?aid*}", pathParameters), + } + return m +} + +// NewItemAssignRequestBuilder instantiates a new ItemAssignRequestBuilder and sets the default values. +func NewItemAssignRequestBuilder(rawUrl string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *ItemAssignRequestBuilder { + urlParams := make(map[string]string) + urlParams["request-raw-url"] = rawUrl + return NewItemAssignRequestBuilderInternal(urlParams, requestAdapter) +} + +// Post assigns a static tag to one or more objects. This operation has cumulative behavior: The tag is statically assigned to the specified objects, and the previous assignments persist. No unassignment takes place. Note: This endpoint does not support dynamic tag assignment (for example, for `endpoint-agent` objects). See `Type` for more information about static vs. dynamic tags. +// returns a []byte when successful +// returns a Tags_API_UnauthorizedError error when the service returns a 401 status code +// returns a Tags_API_Error error when the service returns a 403 status code +// returns a Tags_API_Error error when the service returns a 404 status code +// returns a Tags_API_Error error when the service returns a 429 status code +// returns a Tags_API_ApiError error when the service returns a 500 status code +func (m *ItemAssignRequestBuilder) Post(ctx context.Context, body ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_TagAssignmentable, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[ItemAssignRequestBuilderPostQueryParameters]) ([]byte, error) { + requestInfo, err := m.ToPostRequestInformation(ctx, body, requestConfiguration) + if err != nil { + return nil, err + } + errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{ + "401": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_UnauthorizedErrorFromDiscriminatorValue, + "403": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "404": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "429": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "500": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ApiErrorFromDiscriminatorValue, + } + res, err := m.BaseRequestBuilder.RequestAdapter.SendPrimitive(ctx, requestInfo, "[]byte", errorMapping) + if err != nil { + return nil, err + } + if res == nil { + return nil, nil + } + return res.([]byte), nil +} + +// ToPostRequestInformation assigns a static tag to one or more objects. This operation has cumulative behavior: The tag is statically assigned to the specified objects, and the previous assignments persist. No unassignment takes place. Note: This endpoint does not support dynamic tag assignment (for example, for `endpoint-agent` objects). See `Type` for more information about static vs. dynamic tags. +// returns a *RequestInformation when successful +func (m *ItemAssignRequestBuilder) ToPostRequestInformation(ctx context.Context, body ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_TagAssignmentable, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[ItemAssignRequestBuilderPostQueryParameters]) (*i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestInformation, error) { + requestInfo := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.POST, m.BaseRequestBuilder.UrlTemplate, m.BaseRequestBuilder.PathParameters) + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ConfigureRequestInformation(requestInfo, requestConfiguration) + requestInfo.Headers.TryAdd("Accept", "application/hal+json, application/json, application/problem+json") + err := requestInfo.SetContentFromParsable(ctx, m.BaseRequestBuilder.RequestAdapter, "application/json", body) + if err != nil { + return nil, err + } + return requestInfo, nil +} + +// WithUrl returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. +// returns a *ItemAssignRequestBuilder when successful +func (m *ItemAssignRequestBuilder) WithUrl(rawUrl string) *ItemAssignRequestBuilder { + return NewItemAssignRequestBuilder(rawUrl, m.BaseRequestBuilder.RequestAdapter) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/tags/item_unassign_request_builder.go b/pilot/thousandeyes-kiota/internal/sdk/tags/item_unassign_request_builder.go new file mode 100644 index 00000000..f00fec87 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/tags/item_unassign_request_builder.go @@ -0,0 +1,81 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package tags + +import ( + "context" + + ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2 "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk/models" + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f "github.com/microsoft/kiota-abstractions-go" +) + +// ItemUnassignRequestBuilder builds and executes requests for operations under \tags\{id}\unassign +type ItemUnassignRequestBuilder struct { + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.BaseRequestBuilder +} + +// ItemUnassignRequestBuilderPostQueryParameters removes a static tag from one or more objects. Note: This endpoint does not support dynamic tag assignment (for example, for `endpoint-agent` objects). See `Type` for more information about static vs. dynamic tags. +type ItemUnassignRequestBuilderPostQueryParameters struct { + // A unique identifier associated with your account group. You can retrieve your `AccountGroupId` from the `/account-groups` endpoint. Note that you must be assigned to the target account group. Specifying this parameter without being assigned to the target account group will result in an error response. + Aid *string "uriparametername:\"aid\"" +} + +// NewItemUnassignRequestBuilderInternal instantiates a new ItemUnassignRequestBuilder and sets the default values. +func NewItemUnassignRequestBuilderInternal(pathParameters map[string]string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *ItemUnassignRequestBuilder { + m := &ItemUnassignRequestBuilder{ + BaseRequestBuilder: *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewBaseRequestBuilder(requestAdapter, "{+baseurl}/tags/{id}/unassign{?aid*}", pathParameters), + } + return m +} + +// NewItemUnassignRequestBuilder instantiates a new ItemUnassignRequestBuilder and sets the default values. +func NewItemUnassignRequestBuilder(rawUrl string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *ItemUnassignRequestBuilder { + urlParams := make(map[string]string) + urlParams["request-raw-url"] = rawUrl + return NewItemUnassignRequestBuilderInternal(urlParams, requestAdapter) +} + +// Post removes a static tag from one or more objects. Note: This endpoint does not support dynamic tag assignment (for example, for `endpoint-agent` objects). See `Type` for more information about static vs. dynamic tags. +// returns a Tags_API_UnauthorizedError error when the service returns a 401 status code +// returns a Tags_API_Error error when the service returns a 403 status code +// returns a Tags_API_Error error when the service returns a 404 status code +// returns a Tags_API_Error error when the service returns a 429 status code +// returns a Tags_API_ApiError error when the service returns a 500 status code +func (m *ItemUnassignRequestBuilder) Post(ctx context.Context, body ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_TagAssignmentable, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[ItemUnassignRequestBuilderPostQueryParameters]) error { + requestInfo, err := m.ToPostRequestInformation(ctx, body, requestConfiguration) + if err != nil { + return err + } + errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{ + "401": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_UnauthorizedErrorFromDiscriminatorValue, + "403": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "404": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "429": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "500": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ApiErrorFromDiscriminatorValue, + } + err = m.BaseRequestBuilder.RequestAdapter.SendNoContent(ctx, requestInfo, errorMapping) + if err != nil { + return err + } + return nil +} + +// ToPostRequestInformation removes a static tag from one or more objects. Note: This endpoint does not support dynamic tag assignment (for example, for `endpoint-agent` objects). See `Type` for more information about static vs. dynamic tags. +// returns a *RequestInformation when successful +func (m *ItemUnassignRequestBuilder) ToPostRequestInformation(ctx context.Context, body ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_TagAssignmentable, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[ItemUnassignRequestBuilderPostQueryParameters]) (*i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestInformation, error) { + requestInfo := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.POST, m.BaseRequestBuilder.UrlTemplate, m.BaseRequestBuilder.PathParameters) + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ConfigureRequestInformation(requestInfo, requestConfiguration) + requestInfo.Headers.TryAdd("Accept", "application/json, application/problem+json") + err := requestInfo.SetContentFromParsable(ctx, m.BaseRequestBuilder.RequestAdapter, "application/json", body) + if err != nil { + return nil, err + } + return requestInfo, nil +} + +// WithUrl returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. +// returns a *ItemUnassignRequestBuilder when successful +func (m *ItemUnassignRequestBuilder) WithUrl(rawUrl string) *ItemUnassignRequestBuilder { + return NewItemUnassignRequestBuilder(rawUrl, m.BaseRequestBuilder.RequestAdapter) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/tags/tags_item_request_builder.go b/pilot/thousandeyes-kiota/internal/sdk/tags/tags_item_request_builder.go new file mode 100644 index 00000000..ce9df636 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/tags/tags_item_request_builder.go @@ -0,0 +1,183 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package tags + +import ( + "context" + + ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2 "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk/models" + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f "github.com/microsoft/kiota-abstractions-go" +) + +// TagsItemRequestBuilder builds and executes requests for operations under \tags\{id} +type TagsItemRequestBuilder struct { + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.BaseRequestBuilder +} + +// TagsItemRequestBuilderDeleteQueryParameters deletes a tag. +type TagsItemRequestBuilderDeleteQueryParameters struct { + // A unique identifier associated with your account group. You can retrieve your `AccountGroupId` from the `/account-groups` endpoint. Note that you must be assigned to the target account group. Specifying this parameter without being assigned to the target account group will result in an error response. + Aid *string "uriparametername:\"aid\"" +} + +// TagsItemRequestBuilderGetQueryParameters retrieves a tag using its ID. +type TagsItemRequestBuilderGetQueryParameters struct { + // A unique identifier associated with your account group. You can retrieve your `AccountGroupId` from the `/account-groups` endpoint. Note that you must be assigned to the target account group. Specifying this parameter without being assigned to the target account group will result in an error response. + Aid *string "uriparametername:\"aid\"" + // Optional, to retrieve associated assignments. Only static tags will return object assignments. + Expand []ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_ExpandTagsOptions "uriparametername:\"expand\"" +} + +// TagsItemRequestBuilderPutQueryParameters updates a tag. +type TagsItemRequestBuilderPutQueryParameters struct { + // A unique identifier associated with your account group. You can retrieve your `AccountGroupId` from the `/account-groups` endpoint. Note that you must be assigned to the target account group. Specifying this parameter without being assigned to the target account group will result in an error response. + Aid *string "uriparametername:\"aid\"" +} + +// Assign the assign property +// returns a *ItemAssignRequestBuilder when successful +func (m *TagsItemRequestBuilder) Assign() *ItemAssignRequestBuilder { + return NewItemAssignRequestBuilderInternal(m.BaseRequestBuilder.PathParameters, m.BaseRequestBuilder.RequestAdapter) +} + +// NewTagsItemRequestBuilderInternal instantiates a new TagsItemRequestBuilder and sets the default values. +func NewTagsItemRequestBuilderInternal(pathParameters map[string]string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *TagsItemRequestBuilder { + m := &TagsItemRequestBuilder{ + BaseRequestBuilder: *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewBaseRequestBuilder(requestAdapter, "{+baseurl}/tags/{id}{?aid*,expand*}", pathParameters), + } + return m +} + +// NewTagsItemRequestBuilder instantiates a new TagsItemRequestBuilder and sets the default values. +func NewTagsItemRequestBuilder(rawUrl string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *TagsItemRequestBuilder { + urlParams := make(map[string]string) + urlParams["request-raw-url"] = rawUrl + return NewTagsItemRequestBuilderInternal(urlParams, requestAdapter) +} + +// Delete deletes a tag. +// returns a Tags_API_UnauthorizedError error when the service returns a 401 status code +// returns a Tags_API_Error error when the service returns a 403 status code +// returns a Tags_API_Error error when the service returns a 404 status code +// returns a Tags_API_Error error when the service returns a 429 status code +// returns a Tags_API_ApiError error when the service returns a 500 status code +func (m *TagsItemRequestBuilder) Delete(ctx context.Context, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[TagsItemRequestBuilderDeleteQueryParameters]) error { + requestInfo, err := m.ToDeleteRequestInformation(ctx, requestConfiguration) + if err != nil { + return err + } + errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{ + "401": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_UnauthorizedErrorFromDiscriminatorValue, + "403": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "404": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "429": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "500": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ApiErrorFromDiscriminatorValue, + } + err = m.BaseRequestBuilder.RequestAdapter.SendNoContent(ctx, requestInfo, errorMapping) + if err != nil { + return err + } + return nil +} + +// Get retrieves a tag using its ID. +// returns a Tags_API_Tagable when successful +// returns a Tags_API_UnauthorizedError error when the service returns a 401 status code +// returns a Tags_API_Error error when the service returns a 403 status code +// returns a Tags_API_Error error when the service returns a 404 status code +// returns a Tags_API_Error error when the service returns a 429 status code +// returns a Tags_API_ApiError error when the service returns a 500 status code +func (m *TagsItemRequestBuilder) Get(ctx context.Context, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[TagsItemRequestBuilderGetQueryParameters]) (ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_Tagable, error) { + requestInfo, err := m.ToGetRequestInformation(ctx, requestConfiguration) + if err != nil { + return nil, err + } + errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{ + "401": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_UnauthorizedErrorFromDiscriminatorValue, + "403": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "404": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "429": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "500": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ApiErrorFromDiscriminatorValue, + } + res, err := m.BaseRequestBuilder.RequestAdapter.Send(ctx, requestInfo, ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_TagFromDiscriminatorValue, errorMapping) + if err != nil { + return nil, err + } + if res == nil { + return nil, nil + } + return res.(ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_Tagable), nil +} + +// Put updates a tag. +// returns a Tags_API_Tagable when successful +// returns a Tags_API_UnauthorizedError error when the service returns a 401 status code +// returns a Tags_API_Error error when the service returns a 403 status code +// returns a Tags_API_Error error when the service returns a 404 status code +// returns a Tags_API_Error error when the service returns a 429 status code +// returns a Tags_API_ApiError error when the service returns a 500 status code +func (m *TagsItemRequestBuilder) Put(ctx context.Context, body ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_TagInfoable, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[TagsItemRequestBuilderPutQueryParameters]) (ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_Tagable, error) { + requestInfo, err := m.ToPutRequestInformation(ctx, body, requestConfiguration) + if err != nil { + return nil, err + } + errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{ + "401": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_UnauthorizedErrorFromDiscriminatorValue, + "403": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "404": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "429": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "500": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ApiErrorFromDiscriminatorValue, + } + res, err := m.BaseRequestBuilder.RequestAdapter.Send(ctx, requestInfo, ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_TagFromDiscriminatorValue, errorMapping) + if err != nil { + return nil, err + } + if res == nil { + return nil, nil + } + return res.(ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_Tagable), nil +} + +// ToDeleteRequestInformation deletes a tag. +// returns a *RequestInformation when successful +func (m *TagsItemRequestBuilder) ToDeleteRequestInformation(ctx context.Context, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[TagsItemRequestBuilderDeleteQueryParameters]) (*i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestInformation, error) { + requestInfo := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.DELETE, m.BaseRequestBuilder.UrlTemplate, m.BaseRequestBuilder.PathParameters) + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ConfigureRequestInformation(requestInfo, requestConfiguration) + requestInfo.Headers.TryAdd("Accept", "application/json, application/problem+json") + return requestInfo, nil +} + +// ToGetRequestInformation retrieves a tag using its ID. +// returns a *RequestInformation when successful +func (m *TagsItemRequestBuilder) ToGetRequestInformation(ctx context.Context, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[TagsItemRequestBuilderGetQueryParameters]) (*i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestInformation, error) { + requestInfo := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.GET, m.BaseRequestBuilder.UrlTemplate, m.BaseRequestBuilder.PathParameters) + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ConfigureRequestInformation(requestInfo, requestConfiguration) + requestInfo.Headers.TryAdd("Accept", "application/hal+json") + return requestInfo, nil +} + +// ToPutRequestInformation updates a tag. +// returns a *RequestInformation when successful +func (m *TagsItemRequestBuilder) ToPutRequestInformation(ctx context.Context, body ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_TagInfoable, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[TagsItemRequestBuilderPutQueryParameters]) (*i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestInformation, error) { + requestInfo := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.PUT, m.BaseRequestBuilder.UrlTemplate, m.BaseRequestBuilder.PathParameters) + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ConfigureRequestInformation(requestInfo, requestConfiguration) + requestInfo.Headers.TryAdd("Accept", "application/hal+json") + err := requestInfo.SetContentFromParsable(ctx, m.BaseRequestBuilder.RequestAdapter, "application/json", body) + if err != nil { + return nil, err + } + return requestInfo, nil +} + +// Unassign the unassign property +// returns a *ItemUnassignRequestBuilder when successful +func (m *TagsItemRequestBuilder) Unassign() *ItemUnassignRequestBuilder { + return NewItemUnassignRequestBuilderInternal(m.BaseRequestBuilder.PathParameters, m.BaseRequestBuilder.RequestAdapter) +} + +// WithUrl returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. +// returns a *TagsItemRequestBuilder when successful +func (m *TagsItemRequestBuilder) WithUrl(rawUrl string) *TagsItemRequestBuilder { + return NewTagsItemRequestBuilder(rawUrl, m.BaseRequestBuilder.RequestAdapter) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/tags/tags_request_builder.go b/pilot/thousandeyes-kiota/internal/sdk/tags/tags_request_builder.go new file mode 100644 index 00000000..fb8236e7 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/tags/tags_request_builder.go @@ -0,0 +1,158 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package tags + +import ( + "context" + + ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2 "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk/models" + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f "github.com/microsoft/kiota-abstractions-go" +) + +// TagsRequestBuilder builds and executes requests for operations under \tags +type TagsRequestBuilder struct { + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.BaseRequestBuilder +} + +// TagsRequestBuilderGetQueryParameters this operation returns a list of tags in the specified account group (`aid`). +type TagsRequestBuilderGetQueryParameters struct { + // A unique identifier associated with your account group. You can retrieve your `AccountGroupId` from the `/account-groups` endpoint. Note that you must be assigned to the target account group. Specifying this parameter without being assigned to the target account group will result in an error response. + Aid *string "uriparametername:\"aid\"" + // Optional, to retrieve associated assignments. Only static tags will return object assignments. + Expand []ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_ExpandTagsOptions "uriparametername:\"expand\"" +} + +// TagsRequestBuilderPostQueryParameters creates a new tag. +type TagsRequestBuilderPostQueryParameters struct { + // A unique identifier associated with your account group. You can retrieve your `AccountGroupId` from the `/account-groups` endpoint. Note that you must be assigned to the target account group. Specifying this parameter without being assigned to the target account group will result in an error response. + Aid *string "uriparametername:\"aid\"" +} + +// Assign the assign property +// returns a *AssignRequestBuilder when successful +func (m *TagsRequestBuilder) Assign() *AssignRequestBuilder { + return NewAssignRequestBuilderInternal(m.BaseRequestBuilder.PathParameters, m.BaseRequestBuilder.RequestAdapter) +} + +// Bulk the bulk property +// returns a *BulkRequestBuilder when successful +func (m *TagsRequestBuilder) Bulk() *BulkRequestBuilder { + return NewBulkRequestBuilderInternal(m.BaseRequestBuilder.PathParameters, m.BaseRequestBuilder.RequestAdapter) +} + +// ById gets an item from the github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk.tags.item collection +// returns a *TagsItemRequestBuilder when successful +func (m *TagsRequestBuilder) ById(id string) *TagsItemRequestBuilder { + urlTplParams := make(map[string]string) + for idx, item := range m.BaseRequestBuilder.PathParameters { + urlTplParams[idx] = item + } + if id != "" { + urlTplParams["id"] = id + } + return NewTagsItemRequestBuilderInternal(urlTplParams, m.BaseRequestBuilder.RequestAdapter) +} + +// NewTagsRequestBuilderInternal instantiates a new TagsRequestBuilder and sets the default values. +func NewTagsRequestBuilderInternal(pathParameters map[string]string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *TagsRequestBuilder { + m := &TagsRequestBuilder{ + BaseRequestBuilder: *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewBaseRequestBuilder(requestAdapter, "{+baseurl}/tags{?aid*,expand*}", pathParameters), + } + return m +} + +// NewTagsRequestBuilder instantiates a new TagsRequestBuilder and sets the default values. +func NewTagsRequestBuilder(rawUrl string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *TagsRequestBuilder { + urlParams := make(map[string]string) + urlParams["request-raw-url"] = rawUrl + return NewTagsRequestBuilderInternal(urlParams, requestAdapter) +} + +// Get this operation returns a list of tags in the specified account group (`aid`). +// returns a Tags_API_Tagsable when successful +// returns a Tags_API_UnauthorizedError error when the service returns a 401 status code +// returns a Tags_API_Error error when the service returns a 403 status code +// returns a Tags_API_Error error when the service returns a 404 status code +// returns a Tags_API_Error error when the service returns a 429 status code +// returns a Tags_API_ApiError error when the service returns a 500 status code +func (m *TagsRequestBuilder) Get(ctx context.Context, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[TagsRequestBuilderGetQueryParameters]) (ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_Tagsable, error) { + requestInfo, err := m.ToGetRequestInformation(ctx, requestConfiguration) + if err != nil { + return nil, err + } + errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{ + "401": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_UnauthorizedErrorFromDiscriminatorValue, + "403": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "404": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "429": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "500": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ApiErrorFromDiscriminatorValue, + } + res, err := m.BaseRequestBuilder.RequestAdapter.Send(ctx, requestInfo, ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_TagsFromDiscriminatorValue, errorMapping) + if err != nil { + return nil, err + } + if res == nil { + return nil, nil + } + return res.(ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_Tagsable), nil +} + +// Post creates a new tag. +// returns a Tags_API_Tagable when successful +// returns a Tags_API_ValidationError error when the service returns a 400 status code +// returns a Tags_API_UnauthorizedError error when the service returns a 401 status code +// returns a Tags_API_ApiError error when the service returns a 500 status code +func (m *TagsRequestBuilder) Post(ctx context.Context, body ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_TagInfoable, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[TagsRequestBuilderPostQueryParameters]) (ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_Tagable, error) { + requestInfo, err := m.ToPostRequestInformation(ctx, body, requestConfiguration) + if err != nil { + return nil, err + } + errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{ + "400": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ValidationErrorFromDiscriminatorValue, + "401": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_UnauthorizedErrorFromDiscriminatorValue, + "500": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ApiErrorFromDiscriminatorValue, + } + res, err := m.BaseRequestBuilder.RequestAdapter.Send(ctx, requestInfo, ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_TagFromDiscriminatorValue, errorMapping) + if err != nil { + return nil, err + } + if res == nil { + return nil, nil + } + return res.(ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_Tagable), nil +} + +// ToGetRequestInformation this operation returns a list of tags in the specified account group (`aid`). +// returns a *RequestInformation when successful +func (m *TagsRequestBuilder) ToGetRequestInformation(ctx context.Context, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[TagsRequestBuilderGetQueryParameters]) (*i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestInformation, error) { + requestInfo := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.GET, m.BaseRequestBuilder.UrlTemplate, m.BaseRequestBuilder.PathParameters) + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ConfigureRequestInformation(requestInfo, requestConfiguration) + requestInfo.Headers.TryAdd("Accept", "application/hal+json") + return requestInfo, nil +} + +// ToPostRequestInformation creates a new tag. +// returns a *RequestInformation when successful +func (m *TagsRequestBuilder) ToPostRequestInformation(ctx context.Context, body ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_TagInfoable, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[TagsRequestBuilderPostQueryParameters]) (*i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestInformation, error) { + requestInfo := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.POST, m.BaseRequestBuilder.UrlTemplate, m.BaseRequestBuilder.PathParameters) + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ConfigureRequestInformation(requestInfo, requestConfiguration) + requestInfo.Headers.TryAdd("Accept", "application/hal+json") + err := requestInfo.SetContentFromParsable(ctx, m.BaseRequestBuilder.RequestAdapter, "application/json", body) + if err != nil { + return nil, err + } + return requestInfo, nil +} + +// Unassign the unassign property +// returns a *UnassignRequestBuilder when successful +func (m *TagsRequestBuilder) Unassign() *UnassignRequestBuilder { + return NewUnassignRequestBuilderInternal(m.BaseRequestBuilder.PathParameters, m.BaseRequestBuilder.RequestAdapter) +} + +// WithUrl returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. +// returns a *TagsRequestBuilder when successful +func (m *TagsRequestBuilder) WithUrl(rawUrl string) *TagsRequestBuilder { + return NewTagsRequestBuilder(rawUrl, m.BaseRequestBuilder.RequestAdapter) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/tags/unassign_request_builder.go b/pilot/thousandeyes-kiota/internal/sdk/tags/unassign_request_builder.go new file mode 100644 index 00000000..e68b3e7d --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/tags/unassign_request_builder.go @@ -0,0 +1,85 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package tags + +import ( + "context" + + ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2 "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk/models" + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f "github.com/microsoft/kiota-abstractions-go" +) + +// UnassignRequestBuilder builds and executes requests for operations under \tags\unassign +type UnassignRequestBuilder struct { + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.BaseRequestBuilder +} + +// UnassignRequestBuilderPostQueryParameters removes the specified static tags from one or more objects. Note: This endpoint does not support dynamic tag assignment (for example, for `endpoint-agent` objects). See `Type` for more information about static vs. dynamic tags. +type UnassignRequestBuilderPostQueryParameters struct { + // A unique identifier associated with your account group. You can retrieve your `AccountGroupId` from the `/account-groups` endpoint. Note that you must be assigned to the target account group. Specifying this parameter without being assigned to the target account group will result in an error response. + Aid *string "uriparametername:\"aid\"" +} + +// NewUnassignRequestBuilderInternal instantiates a new UnassignRequestBuilder and sets the default values. +func NewUnassignRequestBuilderInternal(pathParameters map[string]string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *UnassignRequestBuilder { + m := &UnassignRequestBuilder{ + BaseRequestBuilder: *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewBaseRequestBuilder(requestAdapter, "{+baseurl}/tags/unassign{?aid*}", pathParameters), + } + return m +} + +// NewUnassignRequestBuilder instantiates a new UnassignRequestBuilder and sets the default values. +func NewUnassignRequestBuilder(rawUrl string, requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *UnassignRequestBuilder { + urlParams := make(map[string]string) + urlParams["request-raw-url"] = rawUrl + return NewUnassignRequestBuilderInternal(urlParams, requestAdapter) +} + +// Post removes the specified static tags from one or more objects. Note: This endpoint does not support dynamic tag assignment (for example, for `endpoint-agent` objects). See `Type` for more information about static vs. dynamic tags. +// returns a []byte when successful +// returns a Tags_API_UnauthorizedError error when the service returns a 401 status code +// returns a Tags_API_Error error when the service returns a 403 status code +// returns a Tags_API_Error error when the service returns a 404 status code +// returns a Tags_API_Error error when the service returns a 429 status code +// returns a Tags_API_ApiError error when the service returns a 500 status code +func (m *UnassignRequestBuilder) Post(ctx context.Context, body ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_BulkTagAssignmentsable, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[UnassignRequestBuilderPostQueryParameters]) ([]byte, error) { + requestInfo, err := m.ToPostRequestInformation(ctx, body, requestConfiguration) + if err != nil { + return nil, err + } + errorMapping := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ErrorMappings{ + "401": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_UnauthorizedErrorFromDiscriminatorValue, + "403": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "404": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "429": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ErrorFromDiscriminatorValue, + "500": ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.CreateTags_API_ApiErrorFromDiscriminatorValue, + } + res, err := m.BaseRequestBuilder.RequestAdapter.SendPrimitive(ctx, requestInfo, "[]byte", errorMapping) + if err != nil { + return nil, err + } + if res == nil { + return nil, nil + } + return res.([]byte), nil +} + +// ToPostRequestInformation removes the specified static tags from one or more objects. Note: This endpoint does not support dynamic tag assignment (for example, for `endpoint-agent` objects). See `Type` for more information about static vs. dynamic tags. +// returns a *RequestInformation when successful +func (m *UnassignRequestBuilder) ToPostRequestInformation(ctx context.Context, body ib3cdb5eaa5a948486987b089215df409d351e444d9a7341cc7e6e6229e9563d2.Tags_API_BulkTagAssignmentsable, requestConfiguration *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestConfiguration[UnassignRequestBuilderPostQueryParameters]) (*i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestInformation, error) { + requestInfo := i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewRequestInformationWithMethodAndUrlTemplateAndPathParameters(i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.POST, m.BaseRequestBuilder.UrlTemplate, m.BaseRequestBuilder.PathParameters) + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.ConfigureRequestInformation(requestInfo, requestConfiguration) + requestInfo.Headers.TryAdd("Accept", "application/hal+json, application/json, application/problem+json") + err := requestInfo.SetContentFromParsable(ctx, m.BaseRequestBuilder.RequestAdapter, "application/json", body) + if err != nil { + return nil, err + } + return requestInfo, nil +} + +// WithUrl returns a request builder with the provided arbitrary URL. Using this method means any other path or query parameters are ignored. +// returns a *UnassignRequestBuilder when successful +func (m *UnassignRequestBuilder) WithUrl(rawUrl string) *UnassignRequestBuilder { + return NewUnassignRequestBuilder(rawUrl, m.BaseRequestBuilder.RequestAdapter) +} diff --git a/pilot/thousandeyes-kiota/internal/sdk/thousand_eyes_client.go b/pilot/thousandeyes-kiota/internal/sdk/thousand_eyes_client.go new file mode 100644 index 00000000..1a252643 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/sdk/thousand_eyes_client.go @@ -0,0 +1,58 @@ +// Code generated by Microsoft Kiota - DO NOT EDIT. +// Changes may cause incorrect behavior and will be lost if the code is regenerated. + +package sdk + +import ( + i25c7329e78df1c2b1e358f58900ea34a06ae6af9ccdcfa45e661044f2de29a6f "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk/tags" + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f "github.com/microsoft/kiota-abstractions-go" + i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91 "github.com/microsoft/kiota-abstractions-go/serialization" + i4bcdc892e61ac17e2afc10b5e2b536b29f4fd6c1ad30f4a5a68df47495db3347 "github.com/microsoft/kiota-serialization-form-go" + i25911dc319edd61cbac496af7eab5ef20b6069a42515e22ec6a9bc97bf598488 "github.com/microsoft/kiota-serialization-json-go" + i56887720f41ac882814261620b1c8459c4a992a0207af547c4453dd39fabc426 "github.com/microsoft/kiota-serialization-multipart-go" + i7294a22093d408fdca300f11b81a887d89c47b764af06c8b803e2323973fdb83 "github.com/microsoft/kiota-serialization-text-go" +) + +// ThousandEyesClient the main entry point of the SDK, exposes the configuration and the fluent API. +type ThousandEyesClient struct { + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.BaseRequestBuilder +} + +// NewThousandEyesClient instantiates a new ThousandEyesClient and sets the default values. +func NewThousandEyesClient(requestAdapter i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RequestAdapter) *ThousandEyesClient { + m := &ThousandEyesClient{ + BaseRequestBuilder: *i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.NewBaseRequestBuilder(requestAdapter, "{+baseurl}", map[string]string{}), + } + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RegisterDefaultSerializer(func() i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriterFactory { + return i25911dc319edd61cbac496af7eab5ef20b6069a42515e22ec6a9bc97bf598488.NewJsonSerializationWriterFactory() + }) + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RegisterDefaultSerializer(func() i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriterFactory { + return i7294a22093d408fdca300f11b81a887d89c47b764af06c8b803e2323973fdb83.NewTextSerializationWriterFactory() + }) + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RegisterDefaultSerializer(func() i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriterFactory { + return i4bcdc892e61ac17e2afc10b5e2b536b29f4fd6c1ad30f4a5a68df47495db3347.NewFormSerializationWriterFactory() + }) + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RegisterDefaultSerializer(func() i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.SerializationWriterFactory { + return i56887720f41ac882814261620b1c8459c4a992a0207af547c4453dd39fabc426.NewMultipartSerializationWriterFactory() + }) + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RegisterDefaultDeserializer(func() i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNodeFactory { + return i25911dc319edd61cbac496af7eab5ef20b6069a42515e22ec6a9bc97bf598488.NewJsonParseNodeFactory() + }) + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RegisterDefaultDeserializer(func() i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNodeFactory { + return i7294a22093d408fdca300f11b81a887d89c47b764af06c8b803e2323973fdb83.NewTextParseNodeFactory() + }) + i2ae4187f7daee263371cb1c977df639813ab50ffa529013b7437480d1ec0158f.RegisterDefaultDeserializer(func() i878a80d2330e89d26896388a3f487eef27b0a0e6c010c493bf80be1452208f91.ParseNodeFactory { + return i4bcdc892e61ac17e2afc10b5e2b536b29f4fd6c1ad30f4a5a68df47495db3347.NewFormParseNodeFactory() + }) + if m.BaseRequestBuilder.RequestAdapter.GetBaseUrl() == "" { + m.BaseRequestBuilder.RequestAdapter.SetBaseUrl("https://api.thousandeyes.com/v7") + } + m.BaseRequestBuilder.PathParameters["baseurl"] = m.BaseRequestBuilder.RequestAdapter.GetBaseUrl() + return m +} + +// Tags the tags property +// returns a *TagsRequestBuilder when successful +func (m *ThousandEyesClient) Tags() *i25c7329e78df1c2b1e358f58900ea34a06ae6af9ccdcfa45e661044f2de29a6f.TagsRequestBuilder { + return i25c7329e78df1c2b1e358f58900ea34a06ae6af9ccdcfa45e661044f2de29a6f.NewTagsRequestBuilderInternal(m.BaseRequestBuilder.PathParameters, m.BaseRequestBuilder.RequestAdapter) +} diff --git a/pilot/thousandeyes-kiota/internal/services/common/convert/convert.go b/pilot/thousandeyes-kiota/internal/services/common/convert/convert.go new file mode 100644 index 00000000..db6eba0c --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/common/convert/convert.go @@ -0,0 +1,440 @@ +// Package convert moves values between the SDK's Go types and the Terraform +// framework's value types. +// +// It exists so that generated code contains one call per field instead of an +// inlined nil check, and so the null-versus-empty policy for a whole provider +// lives in one reviewable place rather than being restated a thousand times. +// +// Most of the functions were written for the "resty service" SDK dialect, where +// a model is a plain struct: optional scalars are pointers, enumerations are +// named string types held by value, and there are no getters or setters. They +// carry over unchanged to a Kiota-generated SDK, whose getters and setters also +// trade in pointers to scalars; what Kiota adds on top -- closed enumerations +// parsed through a factory, uuid.UUID identifiers and time.Time timestamps -- +// has its own helpers at the end of the file. +// +// # Null and unknown +// +// Flattening a nil pointer yields null, not the zero value. Writing +// types.StringValue("") for an absent field is the single most common cause of a +// provider that reports a permanent diff, because the practitioner's +// configuration says nothing and state says empty string. +// +// Expanding null or unknown yields nil, which the SDK's `omitempty` tags then +// omit from the request. On an API whose update is a full-body PUT that means the +// field is cleared, which is the correct reading of an attribute the practitioner +// removed from configuration. +package convert + +import ( + "context" + "fmt" + "time" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// ----------------------------------------------------------------------------- +// SDK -> framework (flatten) +// ----------------------------------------------------------------------------- + +// PtrStringToFramework converts an optional string to types.String. +func PtrStringToFramework[T ~string](v *T) types.String { + if v == nil { + return types.StringNull() + } + return types.StringValue(string(*v)) +} + +// PtrBoolToFramework converts an optional bool to types.Bool. +func PtrBoolToFramework[T ~bool](v *T) types.Bool { + if v == nil { + return types.BoolNull() + } + return types.BoolValue(bool(*v)) +} + +// PtrInt64ToFramework converts an optional int64 to types.Int64. +func PtrInt64ToFramework[T ~int64 | ~int](v *T) types.Int64 { + if v == nil { + return types.Int64Null() + } + return types.Int64Value(int64(*v)) +} + +// PtrInt32ToFramework converts an optional int32 to types.Int32. +func PtrInt32ToFramework[T ~int32](v *T) types.Int32 { + if v == nil { + return types.Int32Null() + } + return types.Int32Value(int32(*v)) +} + +// PtrFloat64ToFramework converts an optional float64 to types.Float64. +func PtrFloat64ToFramework[T ~float64](v *T) types.Float64 { + if v == nil { + return types.Float64Null() + } + return types.Float64Value(float64(*v)) +} + +// EnumToFramework converts a named string enumeration to types.String. +// +// It deliberately converts the underlying string rather than calling any String +// method the type may have. The SDK's enumerations are open by design: a value +// the specification does not list still decodes, and the SDK's String method +// renders such a value as "TypeName(raw)" for logging. Putting that rendering +// into Terraform state would corrupt it, so the raw wire value is what travels. +// +// An empty enumeration is null rather than the empty string, because the SDK +// marks these fields omitempty and an absent value decodes to "". +func EnumToFramework[T ~string](v T) types.String { + if v == "" { + return types.StringNull() + } + return types.StringValue(string(v)) +} + +// IntEnumToFramework converts a named integer enumeration to types.Int64. +// +// The classic tests' interval is the motivating case: the SDK declares +// `type TestInterval int`, so the string-enum converters cannot carry it. Zero +// becomes null for the same reason EnumToFramework maps "" to null: these +// fields are omitempty on the wire, and an absent value decodes to the zero. +func IntEnumToFramework[T ~int | ~int64](v T) types.Int64 { + if v == 0 { + return types.Int64Null() + } + return types.Int64Value(int64(v)) +} + +// NamedBoolToFramework converts a value-typed named bool to types.Bool. +// +// Always a concrete value, never null: the wire cannot distinguish an absent +// bool from false once omitempty has had its way, and inventing null here would +// claim a distinction the transport does not carry. +func NamedBoolToFramework[T ~bool](v T) types.Bool { + return types.BoolValue(bool(v)) +} + +// StringToFramework converts a value-typed wire string to types.String. +// +// For SDK fields declared as plain `string` rather than `*string` -- the +// agents member's agentId is the motivating case. An empty value becomes null, +// matching the pointer converters' treatment of absence. +func StringToFramework(v string) types.String { + if v == "" { + return types.StringNull() + } + return types.StringValue(v) +} + +// StringSliceToFrameworkSet converts a string slice to types.Set. +// +// A nil slice becomes a null set and an empty slice becomes an empty set, so a +// field the API omits and a field it returns as [] are not conflated. Conflating +// them produces a diff that cannot be resolved from configuration. +func StringSliceToFrameworkSet(ctx context.Context, v []string) (types.Set, diag.Diagnostics) { + if v == nil { + return types.SetNull(types.StringType), nil + } + return types.SetValueFrom(ctx, types.StringType, v) +} + +// StringSliceToFrameworkList converts a string slice to types.List, preserving +// order. Use it when the API's ordering is meaningful; prefer a set when it is +// not, since a reordered list is a spurious diff. +func StringSliceToFrameworkList(ctx context.Context, v []string) (types.List, diag.Diagnostics) { + if v == nil { + return types.ListNull(types.StringType), nil + } + return types.ListValueFrom(ctx, types.StringType, v) +} + +// ----------------------------------------------------------------------------- +// framework -> SDK (expand) +// ----------------------------------------------------------------------------- + +// FrameworkToPtrString converts types.String to an optional string. +// +// Null and unknown both yield nil. Unknown matters on create: a computed +// attribute is unknown in the plan, and sending its unresolved value would write +// a literal placeholder to the API. +func FrameworkToPtrString(v types.String) *string { + if v.IsNull() || v.IsUnknown() { + return nil + } + s := v.ValueString() + return &s +} + +// FrameworkToPtrBool converts types.Bool to an optional bool. +func FrameworkToPtrBool(v types.Bool) *bool { + if v.IsNull() || v.IsUnknown() { + return nil + } + b := v.ValueBool() + return &b +} + +// FrameworkToPtrInt64 converts types.Int64 to an optional int64. +func FrameworkToPtrInt64(v types.Int64) *int64 { + if v.IsNull() || v.IsUnknown() { + return nil + } + i := v.ValueInt64() + return &i +} + +// FrameworkToPtrInt32 converts types.Int32 to an optional int32. +func FrameworkToPtrInt32(v types.Int32) *int32 { + if v.IsNull() || v.IsUnknown() { + return nil + } + i := v.ValueInt32() + return &i +} + +// FrameworkToPtrFloat64 converts types.Float64 to an optional float64. +func FrameworkToPtrFloat64(v types.Float64) *float64 { + if v.IsNull() || v.IsUnknown() { + return nil + } + f := v.ValueFloat64() + return &f +} + +// FrameworkToEnum converts types.String to a named string enumeration. +// +// The value is not checked against the enumeration's known members. These +// enumerations are open, and rejecting an unrecognised value here would turn a +// routine upstream addition into a provider that cannot express it. +func FrameworkToEnum[T ~string](v types.String) T { + if v.IsNull() || v.IsUnknown() { + return T("") + } + return T(v.ValueString()) +} + +// FrameworkToIntEnum converts types.Int64 to a named integer enumeration. +// +// Unchecked against the known members for the reason FrameworkToEnum is: the +// sets are open, and refusing an unrecognised value would turn a routine +// upstream addition into a provider that cannot express it. +func FrameworkToIntEnum[T ~int](v types.Int64) T { + if v.IsNull() || v.IsUnknown() { + return T(0) + } + return T(v.ValueInt64()) +} + +// FrameworkToString converts types.String to a value-typed wire string. +// +// Null and unknown become "", which the omitempty wire treatment drops; a +// required attribute is never null here, so nothing real is lost. +func FrameworkToString(v types.String) string { + if v.IsNull() || v.IsUnknown() { + return "" + } + return v.ValueString() +} + +// FrameworkToNamedBool converts types.Bool to a value-typed named bool. +// +// Null and unknown become false, which omitempty then drops from the request -- +// the value-typed field's structural limit, recorded rather than papered over: +// an explicit false cannot travel either, and the probe is what documents what +// the API does about that. +func FrameworkToNamedBool[T ~bool](v types.Bool) T { + if v.IsNull() || v.IsUnknown() { + return T(false) + } + return T(v.ValueBool()) +} + +// FrameworkToPtrNamed converts types.String to an optional named string. +func FrameworkToPtrNamed[T ~string](v types.String) *T { + if v.IsNull() || v.IsUnknown() { + return nil + } + t := T(v.ValueString()) + return &t +} + +// FrameworkToPtrNamedBool converts types.Bool to an optional named bool. +func FrameworkToPtrNamedBool[T ~bool](v types.Bool) *T { + if v.IsNull() || v.IsUnknown() { + return nil + } + t := T(v.ValueBool()) + return &t +} + +// FrameworkToPtrNamedInt converts types.Int64 to an optional named integer, +// covering both the SDK's plain *int fields and its named integer types. +func FrameworkToPtrNamedInt[T ~int | ~int64](v types.Int64) *T { + if v.IsNull() || v.IsUnknown() { + return nil + } + t := T(v.ValueInt64()) + return &t +} + +// FrameworkSetToStringSlice converts types.Set to a string slice. Null and +// unknown both yield nil so the field is omitted from the request. +func FrameworkSetToStringSlice(ctx context.Context, v types.Set) ([]string, diag.Diagnostics) { + return elementsToStringSlice(ctx, v.IsNull() || v.IsUnknown(), v.Elements(), v) +} + +// FrameworkListToStringSlice converts types.List to a string slice. +func FrameworkListToStringSlice(ctx context.Context, v types.List) ([]string, diag.Diagnostics) { + return elementsToStringSlice(ctx, v.IsNull() || v.IsUnknown(), v.Elements(), v) +} + +// collection is the shared surface of the framework's list and set values. +type collection interface { + ElementsAs(ctx context.Context, target any, allowUnhandled bool) diag.Diagnostics +} + +func elementsToStringSlice(ctx context.Context, absent bool, elems []attr.Value, c collection) ([]string, diag.Diagnostics) { + if absent { + return nil, nil + } + // An explicitly empty collection is distinct from an absent one, and must + // travel as an empty slice so the API is told to clear the field. + if len(elems) == 0 { + return []string{}, nil + } + + out := make([]string, 0, len(elems)) + diags := c.ElementsAs(ctx, &out, false) + + return out, diags +} + +// Deref reads a pointer field as a plain Go value, treating nil as the zero value. +// +// The exception to this package's null-versus-empty rule, and only because its one caller +// is outside Terraform's value system. A resource identity holds plain Go scalars, not +// framework values, so there is no null to represent: a list result either carries an +// identifier or it is unusable. Yielding the zero value keeps a query that meets one odd +// element from panicking, and an empty identity is visible in the output. +// +// Nothing that writes Terraform state should use this. Flattening a nil pointer to the zero +// value is what produces a provider with a permanent diff, which is why every other +// function here returns null instead. +func Deref[T any](p *T) T { + if p == nil { + var zero T + + return zero + } + + return *p +} + +// FrameworkSetToEnumSlice converts a set of strings into a named-string slice -- +// a suppression window's daysOfWeek is []DaysOfWeek, not []string. +func FrameworkSetToEnumSlice[T ~string](ctx context.Context, v types.Set) ([]T, diag.Diagnostics) { + raw, diags := FrameworkSetToStringSlice(ctx, v) + if raw == nil { + return nil, diags + } + out := make([]T, len(raw)) + for i, s := range raw { + out[i] = T(s) + } + return out, diags +} + +// EnumSliceToFrameworkSet is the read-side mirror of FrameworkSetToEnumSlice. +func EnumSliceToFrameworkSet[T ~string](ctx context.Context, v []T) (types.Set, diag.Diagnostics) { + if v == nil { + return StringSliceToFrameworkSet(ctx, nil) + } + raw := make([]string, len(v)) + for i, s := range v { + raw[i] = string(s) + } + return StringSliceToFrameworkSet(ctx, raw) +} + +// ----------------------------------------------------------------------------- +// Kiota-specific helpers +// ----------------------------------------------------------------------------- + +// PtrStringerToFramework converts an optional value that renders itself as a +// string -- a *uuid.UUID identifier or a pointer to a Kiota enumeration -- to +// types.String. +// +// Calling String here is safe where EnumToFramework had to refuse it: a Kiota +// enumeration is closed, so a value that exists at all renders as its wire +// string rather than as a logging decoration. +func PtrStringerToFramework[T fmt.Stringer](p *T) types.String { + if p == nil { + return types.StringNull() + } + return types.StringValue((*p).String()) +} + +// PtrTimeToFramework converts an optional timestamp to types.String in RFC 3339, +// which is the shape it arrived in on the wire and the only shape Terraform can +// compare without a semantic-equality plugin. +func PtrTimeToFramework(p *time.Time) types.String { + if p == nil { + return types.StringNull() + } + return types.StringValue(p.Format(time.RFC3339)) +} + +// FrameworkToKiotaEnum converts types.String to a pointer to a Kiota +// enumeration, through the ParseX function the SDK generates beside each one. +// +// A Kiota enumeration is closed: ParseX answers (nil, nil) for a value outside +// the set, and sending such a value is impossible because the type is an integer +// index into the known members. That is why this helper can reject an +// unrecognised value with a diagnostic where FrameworkToEnum deliberately could +// not -- refusing here is surfacing a certain 400 early, not closing an open set. +// +// ParseX returns its result as a *T wrapped in any, which is what the type +// assertion unwraps. +func FrameworkToKiotaEnum[T any](v types.String, parse func(string) (any, error)) (*T, diag.Diagnostics) { + var diags diag.Diagnostics + + if v.IsNull() || v.IsUnknown() { + return nil, diags + } + + raw := v.ValueString() + + parsed, err := parse(raw) + if err != nil { + diags.AddError( + "Invalid enumeration value", + fmt.Sprintf("Parsing %q: %s", raw, err), + ) + return nil, diags + } + if parsed == nil { + var zero T + diags.AddError( + "Invalid enumeration value", + fmt.Sprintf("%q is not a member of %T, and the API would reject it.", raw, zero), + ) + return nil, diags + } + + typed, ok := parsed.(*T) + if !ok { + diags.AddError( + "Invalid enumeration value", + fmt.Sprintf("The parser for %q returned %T where a *%T was expected. This is a bug in the provider.", + raw, parsed, *new(T)), + ) + return nil, diags + } + + return typed, diags +} diff --git a/pilot/thousandeyes-kiota/internal/services/common/convert/convert_test.go b/pilot/thousandeyes-kiota/internal/services/common/convert/convert_test.go new file mode 100644 index 00000000..6e5ae2c7 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/common/convert/convert_test.go @@ -0,0 +1,467 @@ +package convert + +import ( + "context" + "errors" + "testing" + "time" + + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// testEnum stands in for an SDK enumeration: a named string type, held by value, +// open to values its specification does not list. +type testEnum string + +const ( + testEnumAll testEnum = "all" + testEnumPartner testEnum = "partner" +) + +func ptr[T any](v T) *T { return &v } + +// TestUnit_Convert_NilPointerFlattensToNull pins the policy that matters most. +// Flattening an absent field to the zero value rather than to null is the single +// most common cause of a provider with a permanent diff: configuration says +// nothing, state says empty string, and no configuration change can reconcile them. +func TestUnit_Convert_NilPointerFlattensToNull(t *testing.T) { + t.Parallel() + + if got := PtrStringToFramework[string](nil); !got.IsNull() { + t.Errorf("PtrStringToFramework[string](nil) = %v, want null", got) + } + if got := PtrBoolToFramework[bool](nil); !got.IsNull() { + t.Errorf("PtrBoolToFramework[bool](nil) = %v, want null", got) + } + if got := PtrInt64ToFramework[int64](nil); !got.IsNull() { + t.Errorf("PtrInt64ToFramework[int64](nil) = %v, want null", got) + } + if got := PtrInt32ToFramework[int32](nil); !got.IsNull() { + t.Errorf("PtrInt32ToFramework[int32](nil) = %v, want null", got) + } + if got := PtrFloat64ToFramework[float64](nil); !got.IsNull() { + t.Errorf("PtrFloat64ToFramework[float64](nil) = %v, want null", got) + } +} + +// TestUnit_Convert_EmptyStringIsPreservedNotNulled is the counterpart: a pointer +// to the empty string is a value the API actually returned, and must survive as +// an empty string rather than collapsing to null. +func TestUnit_Convert_EmptyStringIsPreservedNotNulled(t *testing.T) { + t.Parallel() + + got := PtrStringToFramework(ptr("")) + if got.IsNull() { + t.Fatal("a pointer to the empty string must not flatten to null") + } + if got.ValueString() != "" { + t.Errorf("got %q, want the empty string", got.ValueString()) + } +} + +func TestUnit_Convert_PointerScalarsRoundTrip(t *testing.T) { + t.Parallel() + + t.Run("string", func(t *testing.T) { + t.Parallel() + got := FrameworkToPtrString(PtrStringToFramework(ptr("hello"))) + if got == nil || *got != "hello" { + t.Errorf("round trip = %v, want \"hello\"", got) + } + }) + + t.Run("bool false survives", func(t *testing.T) { + t.Parallel() + // false is the zero value, so a naive implementation loses it. + got := FrameworkToPtrBool(PtrBoolToFramework(ptr(false))) + if got == nil { + t.Fatal("round trip of false yielded nil; the zero value was lost") + } + if *got { + t.Errorf("round trip = true, want false") + } + }) + + t.Run("int64 zero survives", func(t *testing.T) { + t.Parallel() + got := FrameworkToPtrInt64(PtrInt64ToFramework(ptr(int64(0)))) + if got == nil { + t.Fatal("round trip of 0 yielded nil; the zero value was lost") + } + if *got != 0 { + t.Errorf("round trip = %d, want 0", *got) + } + }) + + t.Run("float64", func(t *testing.T) { + t.Parallel() + got := FrameworkToPtrFloat64(PtrFloat64ToFramework(ptr(1.5))) + if got == nil || *got != 1.5 { + t.Errorf("round trip = %v, want 1.5", got) + } + }) +} + +// TestUnit_Convert_UnknownExpandsToNil matters on create: a computed attribute is +// unknown in the plan, and sending its unresolved value would write a literal +// placeholder to the API. +func TestUnit_Convert_UnknownExpandsToNil(t *testing.T) { + t.Parallel() + + if got := FrameworkToPtrString(types.StringUnknown()); got != nil { + t.Errorf("FrameworkToPtrString(unknown) = %v, want nil", got) + } + if got := FrameworkToPtrBool(types.BoolUnknown()); got != nil { + t.Errorf("FrameworkToPtrBool(unknown) = %v, want nil", got) + } + if got := FrameworkToPtrInt64(types.Int64Unknown()); got != nil { + t.Errorf("FrameworkToPtrInt64(unknown) = %v, want nil", got) + } + if got := FrameworkToPtrFloat64(types.Float64Unknown()); got != nil { + t.Errorf("FrameworkToPtrFloat64(unknown) = %v, want nil", got) + } + if got := FrameworkToPtrInt32(types.Int32Unknown()); got != nil { + t.Errorf("FrameworkToPtrInt32(unknown) = %v, want nil", got) + } +} + +func TestUnit_Convert_NullExpandsToNil(t *testing.T) { + t.Parallel() + + if got := FrameworkToPtrString(types.StringNull()); got != nil { + t.Errorf("FrameworkToPtrString(null) = %v, want nil", got) + } + if got := FrameworkToPtrBool(types.BoolNull()); got != nil { + t.Errorf("FrameworkToPtrBool(null) = %v, want nil", got) + } +} + +// TestUnit_Convert_EnumUsesRawWireValue is the trap this package exists to avoid. +// The SDK's enumerations are open, and their String method renders an unlisted +// value as "TypeName(raw)" for logging. Putting that rendering into Terraform +// state would corrupt it, so the underlying string is what must travel. +func TestUnit_Convert_EnumUsesRawWireValue(t *testing.T) { + t.Parallel() + + t.Run("known value", func(t *testing.T) { + t.Parallel() + if got := EnumToFramework(testEnumAll); got.ValueString() != "all" { + t.Errorf("got %q, want %q", got.ValueString(), "all") + } + }) + + t.Run("value outside the enumeration survives verbatim", func(t *testing.T) { + t.Parallel() + // The API adding a value must not corrupt state or fail the provider. + got := EnumToFramework(testEnum("newly-added-upstream")) + if got.ValueString() != "newly-added-upstream" { + t.Errorf("got %q, want the raw value verbatim", got.ValueString()) + } + }) + + t.Run("empty enum flattens to null", func(t *testing.T) { + t.Parallel() + // These fields are omitempty in the SDK, so an absent value decodes to + // "" and must present as null rather than as an empty string. + if got := EnumToFramework(testEnum("")); !got.IsNull() { + t.Errorf("got %v, want null", got) + } + }) +} + +func TestUnit_Convert_EnumRoundTrips(t *testing.T) { + t.Parallel() + + for _, want := range []testEnum{testEnumAll, testEnumPartner, "unlisted"} { + t.Run(string(want), func(t *testing.T) { + t.Parallel() + if got := FrameworkToEnum[testEnum](EnumToFramework(want)); got != want { + t.Errorf("round trip = %q, want %q", got, want) + } + }) + } +} + +func TestUnit_Convert_EnumExpandsAbsentToEmpty(t *testing.T) { + t.Parallel() + + // The SDK holds enumerations by value with omitempty, so the empty string is + // how "not set" is expressed on the wire. + if got := FrameworkToEnum[testEnum](types.StringNull()); got != "" { + t.Errorf("null expanded to %q, want the empty string", got) + } + if got := FrameworkToEnum[testEnum](types.StringUnknown()); got != "" { + t.Errorf("unknown expanded to %q, want the empty string", got) + } +} + +// TestUnit_Convert_NilSliceAndEmptySliceAreDistinct guards a distinction that is +// easy to lose and impossible for a practitioner to work around once lost: a +// field the API omits and a field it returns as [] mean different things. +func TestUnit_Convert_NilSliceAndEmptySliceAreDistinct(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + nullSet, diags := StringSliceToFrameworkSet(ctx, nil) + if diags.HasError() { + t.Fatalf("unexpected diagnostics: %v", diags) + } + if !nullSet.IsNull() { + t.Error("a nil slice must flatten to a null set") + } + + emptySet, diags := StringSliceToFrameworkSet(ctx, []string{}) + if diags.HasError() { + t.Fatalf("unexpected diagnostics: %v", diags) + } + if emptySet.IsNull() { + t.Error("an empty slice must flatten to an empty set, not null") + } + if len(emptySet.Elements()) != 0 { + t.Errorf("empty set has %d elements", len(emptySet.Elements())) + } +} + +func TestUnit_Convert_StringCollectionsRoundTrip(t *testing.T) { + t.Parallel() + + ctx := context.Background() + want := []string{"a", "b", "c"} + + t.Run("set", func(t *testing.T) { + t.Parallel() + + set, diags := StringSliceToFrameworkSet(ctx, want) + if diags.HasError() { + t.Fatalf("flatten: %v", diags) + } + + got, diags := FrameworkSetToStringSlice(ctx, set) + if diags.HasError() { + t.Fatalf("expand: %v", diags) + } + if len(got) != len(want) { + t.Fatalf("round trip = %v, want %v", got, want) + } + }) + + t.Run("list preserves order", func(t *testing.T) { + t.Parallel() + + list, diags := StringSliceToFrameworkList(ctx, want) + if diags.HasError() { + t.Fatalf("flatten: %v", diags) + } + + got, diags := FrameworkListToStringSlice(ctx, list) + if diags.HasError() { + t.Fatalf("expand: %v", diags) + } + for i := range want { + if got[i] != want[i] { + t.Fatalf("round trip = %v, want %v (order matters for a list)", got, want) + } + } + }) +} + +func TestUnit_Convert_AbsentCollectionsExpandToNil(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + tests := map[string]func() ([]string, bool){ + "null set": func() ([]string, bool) { + got, d := FrameworkSetToStringSlice(ctx, types.SetNull(types.StringType)) + return got, d.HasError() + }, + "unknown set": func() ([]string, bool) { + got, d := FrameworkSetToStringSlice(ctx, types.SetUnknown(types.StringType)) + return got, d.HasError() + }, + "null list": func() ([]string, bool) { + got, d := FrameworkListToStringSlice(ctx, types.ListNull(types.StringType)) + return got, d.HasError() + }, + "unknown list": func() ([]string, bool) { + got, d := FrameworkListToStringSlice(ctx, types.ListUnknown(types.StringType)) + return got, d.HasError() + }, + } + + for name, fn := range tests { + t.Run(name, func(t *testing.T) { + t.Parallel() + got, hasErr := fn() + if hasErr { + t.Fatal("unexpected diagnostics") + } + if got != nil { + t.Errorf("got %v, want nil so the field is omitted from the request", got) + } + }) + } +} + +// TestUnit_Convert_EmptyCollectionExpandsToEmptySlice completes the pairing: an +// explicitly empty collection must travel as [] so the API is told to clear the +// field, rather than being omitted and left alone. +func TestUnit_Convert_EmptyCollectionExpandsToEmptySlice(t *testing.T) { + t.Parallel() + + ctx := context.Background() + + emptySet, diags := types.SetValueFrom(ctx, types.StringType, []string{}) + if diags.HasError() { + t.Fatalf("building the fixture: %v", diags) + } + + got, diags := FrameworkSetToStringSlice(ctx, emptySet) + if diags.HasError() { + t.Fatalf("expand: %v", diags) + } + if got == nil { + t.Fatal("an explicitly empty set must expand to an empty slice, not nil") + } + if len(got) != 0 { + t.Errorf("got %v, want an empty slice", got) + } +} + +// fakeKiotaEnum mirrors the shape Kiota generates for a closed enumeration: an +// integer index whose String method renders the wire value, parsed through a +// factory that answers (nil, nil) for a value outside the set. A local stand-in +// rather than an import of the SDK models, so these tests pin the contract +// without coupling to any one generated package. +type fakeKiotaEnum int + +const ( + fakeEnumAll fakeKiotaEnum = iota + fakeEnumPartner +) + +func (e fakeKiotaEnum) String() string { + return []string{"all", "partner"}[e] +} + +func parseFakeKiotaEnum(v string) (any, error) { + result := fakeEnumAll + switch v { + case "all": + result = fakeEnumAll + case "partner": + result = fakeEnumPartner + default: + return nil, nil + } + return &result, nil +} + +func TestUnit_Convert_PtrStringerToFramework(t *testing.T) { + t.Parallel() + + t.Run("nil flattens to null", func(t *testing.T) { + t.Parallel() + if got := PtrStringerToFramework[fakeKiotaEnum](nil); !got.IsNull() { + t.Errorf("PtrStringerToFramework(nil) = %v, want null", got) + } + }) + + t.Run("value renders through String", func(t *testing.T) { + t.Parallel() + v := fakeEnumPartner + if got := PtrStringerToFramework(&v); got.ValueString() != "partner" { + t.Errorf("got %q, want %q", got.ValueString(), "partner") + } + }) +} + +func TestUnit_Convert_PtrTimeToFramework(t *testing.T) { + t.Parallel() + + t.Run("nil flattens to null", func(t *testing.T) { + t.Parallel() + if got := PtrTimeToFramework(nil); !got.IsNull() { + t.Errorf("PtrTimeToFramework(nil) = %v, want null", got) + } + }) + + t.Run("value renders as RFC 3339", func(t *testing.T) { + t.Parallel() + v := time.Date(2026, time.August, 4, 12, 30, 0, 0, time.UTC) + if got := PtrTimeToFramework(&v); got.ValueString() != "2026-08-04T12:30:00Z" { + t.Errorf("got %q, want %q", got.ValueString(), "2026-08-04T12:30:00Z") + } + }) +} + +func TestUnit_Convert_FrameworkToKiotaEnum(t *testing.T) { + t.Parallel() + + t.Run("null and unknown expand to nil without diagnostics", func(t *testing.T) { + t.Parallel() + for name, v := range map[string]types.String{ + "null": types.StringNull(), + "unknown": types.StringUnknown(), + } { + got, diags := FrameworkToKiotaEnum[fakeKiotaEnum](v, parseFakeKiotaEnum) + if diags.HasError() { + t.Errorf("%s: unexpected diagnostics: %v", name, diags) + } + if got != nil { + t.Errorf("%s: got %v, want nil so the setter is never called", name, got) + } + } + }) + + t.Run("member round trips", func(t *testing.T) { + t.Parallel() + got, diags := FrameworkToKiotaEnum[fakeKiotaEnum](types.StringValue("partner"), parseFakeKiotaEnum) + if diags.HasError() { + t.Fatalf("unexpected diagnostics: %v", diags) + } + if got == nil || *got != fakeEnumPartner { + t.Fatalf("got %v, want %v", got, fakeEnumPartner) + } + if PtrStringerToFramework(got).ValueString() != "partner" { + t.Errorf("round trip through PtrStringerToFramework lost the wire value") + } + }) + + t.Run("non-member is a diagnostic naming the value", func(t *testing.T) { + t.Parallel() + got, diags := FrameworkToKiotaEnum[fakeKiotaEnum](types.StringValue("outsider"), parseFakeKiotaEnum) + if got != nil { + t.Errorf("got %v, want nil for a value the enumeration cannot hold", got) + } + if !diags.HasError() { + t.Fatal("a non-member must produce an error diagnostic") + } + if detail := diags.Errors()[0].Detail(); !containsString(detail, "outsider") { + t.Errorf("the diagnostic %q does not name the offending value", detail) + } + }) + + t.Run("parse failure is a diagnostic", func(t *testing.T) { + t.Parallel() + failing := func(string) (any, error) { return nil, errors.New("boom") } + got, diags := FrameworkToKiotaEnum[fakeKiotaEnum](types.StringValue("all"), failing) + if got != nil { + t.Errorf("got %v, want nil when parsing failed", got) + } + if !diags.HasError() { + t.Fatal("a parse failure must produce an error diagnostic") + } + }) +} + +// containsString avoids importing strings for one call in one assertion. +func containsString(haystack, needle string) bool { + for i := 0; i+len(needle) <= len(haystack); i++ { + if haystack[i:i+len(needle)] == needle { + return true + } + } + return false +} diff --git a/pilot/thousandeyes-kiota/internal/services/common/crud/crud.go b/pilot/thousandeyes-kiota/internal/services/common/crud/crud.go new file mode 100644 index 00000000..b381b7ba --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/common/crud/crud.go @@ -0,0 +1,159 @@ +// Package crud holds the operation-shaped helpers every generated resource uses: +// timeout handling and the re-read after a write. +// +// It is hand-written and owned by the provider. Generated CRUD bodies are a fixed +// skeleton that calls into here, which keeps the generated code short enough to read +// and puts the fiddly parts somewhere they can be tested directly. +package crud + +import ( + "context" + "time" + + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// TimeoutFunc resolves a configured timeout, falling back to a default. It +// matches the signature of the timeouts value's Create, Read, Update and Delete +// methods so a generated call can pass one directly. +type TimeoutFunc func(ctx context.Context, def time.Duration) (time.Duration, diag.Diagnostics) + +// HandleTimeout derives a context bounded by the configured timeout. +// +// It returns a nil cancel function when the timeout could not be resolved, which +// is the signal for the caller to return without proceeding. Returning a usable +// context alongside an error would invite a caller to ignore the error and run the +// operation unbounded. +func HandleTimeout( + ctx context.Context, + resolve TimeoutFunc, + def time.Duration, + diags *diag.Diagnostics, +) (context.Context, context.CancelFunc) { + d := def + + if resolve != nil { + resolved, tdiags := resolve(ctx, def) + diags.Append(tdiags...) + if diags.HasError() { + return ctx, nil + } + d = resolved + } + + c, cancel := context.WithTimeout(ctx, d) + return c, cancel +} + +// Timeout resolves one of a resource's four timeouts to a duration, treating a +// zero configured value as the default. +func Timeout(ctx context.Context, v timeouts.Value, which Phase, def time.Duration, diags *diag.Diagnostics) time.Duration { + var ( + d time.Duration + tdiags diag.Diagnostics + ) + + switch which { + case PhaseCreate: + d, tdiags = v.Create(ctx, def) + case PhaseRead: + d, tdiags = v.Read(ctx, def) + case PhaseUpdate: + d, tdiags = v.Update(ctx, def) + case PhaseDelete: + d, tdiags = v.Delete(ctx, def) + } + + diags.Append(tdiags...) + if d <= 0 { + return def + } + return d +} + +// Phase identifies which of a resource's timeouts to resolve. +type Phase string + +const ( + PhaseCreate Phase = "create" + PhaseRead Phase = "read" + PhaseUpdate Phase = "update" + PhaseDelete Phase = "delete" +) + +// ReadBackOptions configures the re-read after a write. +type ReadBackOptions struct { + // MaxRetries is the number of additional attempts after the first. + MaxRetries int + // Interval is the wait between attempts. + Interval time.Duration + // ResourceType and Operation appear in log lines. + ResourceType string + Operation string + // Reason records why the re-read is needed, so a retry loop in generated code + // is not indistinguishable from cargo cult. + Reason string +} + +// DefaultReadBackOptions is a conservative starting point for an API whose +// consistency has not been measured. The prober replaces these with observed +// values, and is only permitted to increase them. +func DefaultReadBackOptions() ReadBackOptions { + return ReadBackOptions{MaxRetries: 10, Interval: 2 * time.Second} +} + +// ReadBack calls read until it reports success, or until the retries or the +// context are exhausted. +// +// read reports whether the resource was observed. It is separate from the error +// return because "not there yet" and "the request failed" need different handling: +// the first is worth retrying and the second is not. +func ReadBack(ctx context.Context, opts ReadBackOptions, read func(context.Context) (bool, error)) error { + if opts.Interval <= 0 { + opts.Interval = time.Second + } + + var lastErr error + + for attempt := 0; attempt <= opts.MaxRetries; attempt++ { + found, err := read(ctx) + switch { + case err != nil: + // A hard failure is not retried. Retrying a 403 just delays the + // report by the whole retry budget. + return err + case found: + if attempt > 0 { + tflog.Debug(ctx, "resource became readable after retrying", map[string]any{ + "resource": opts.ResourceType, + "operation": opts.Operation, + "attempts": attempt + 1, + }) + } + return nil + } + + lastErr = ErrNotYetReadable + + if attempt == opts.MaxRetries { + break + } + + select { + case <-ctx.Done(): + return ctx.Err() + case <-time.After(opts.Interval): + } + } + + tflog.Warn(ctx, "resource did not become readable within the retry budget", map[string]any{ + "resource": opts.ResourceType, + "operation": opts.Operation, + "attempts": opts.MaxRetries + 1, + "reason": opts.Reason, + }) + + return lastErr +} diff --git a/pilot/thousandeyes-kiota/internal/services/common/crud/errors.go b/pilot/thousandeyes-kiota/internal/services/common/crud/errors.go new file mode 100644 index 00000000..2ed9c573 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/common/crud/errors.go @@ -0,0 +1,27 @@ +package crud + +import "errors" + +// ErrNotYetReadable is returned when a resource did not become readable within +// the re-read budget. +// +// It is a sentinel rather than a formatted error so a caller can distinguish +// "the API is still catching up" from a genuine failure, and decide whether that +// is fatal for the operation in hand. +var ErrNotYetReadable = errors.New("resource did not become readable within the retry budget") + +// ErrStopRetrying tells ReadBack to give up without treating the outcome as a failure of +// its own. +// +// It exists for one shape, which generated code hits on every resource: the callback has +// already turned a hard failure into a diagnostic, and now needs the loop to stop without +// the error being reported a second time. Returning ErrNotYetReadable instead would stop +// the loop but say something untrue about why. +var ErrStopRetrying = errors.New("the caller has reported a failure and asked to stop retrying") + +// IsStopRetrying reports whether err is ErrStopRetrying. +// +// A predicate rather than leaving callers to use errors.Is, because the callers are +// generated files that already import a provider package named errors -- so the standard +// library's is not reachable there without an alias nobody would choose to read. +func IsStopRetrying(err error) bool { return errors.Is(err, ErrStopRetrying) } diff --git a/pilot/thousandeyes-kiota/internal/services/common/errors/errors.go b/pilot/thousandeyes-kiota/internal/services/common/errors/errors.go new file mode 100644 index 00000000..fa7e7b74 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/common/errors/errors.go @@ -0,0 +1,131 @@ +// Package errors turns SDK errors into Terraform diagnostics. +// +// It is hand-written and owned by the provider, not generated, because mapping an +// API's failure modes onto messages a practitioner can act on is judgement rather +// than transcription. Generated CRUD calls into it by name. +package errors + +import ( + stderrors "errors" + "fmt" + "net/http" + + "github.com/hashicorp/terraform-plugin-framework/diag" + + abstractions "github.com/microsoft/kiota-abstractions-go" +) + +// Operation names the CRUD phase an error occurred in, so a diagnostic says what +// the provider was attempting rather than only what failed. +type Operation string + +const ( + OpCreate Operation = "create" + OpRead Operation = "read" + OpUpdate Operation = "update" + OpDelete Operation = "delete" +) + +// ErrEmptyResponse is returned when a fluent SDK call reports success but hands +// back no body. Kiota renders that outcome as (nil, nil), which generated code +// must turn into an error rather than dereference: a create that "succeeded" +// without returning the object leaves nothing to write to state. +var ErrEmptyResponse = stderrors.New("the API returned success with an empty body") + +// IsNotFound reports whether err is a 404. +// +// Read uses it to remove a resource from state rather than fail: something +// deleted outside Terraform should produce a plan that recreates it, not an error +// the practitioner cannot clear. +// +// Every generated error model embeds abstractions.ApiError, so the interface +// assertion reaches them all without this package naming any of them. +func IsNotFound(err error) bool { + var apiErr abstractions.ApiErrorable + if !stderrors.As(err, &apiErr) { + return false + } + + return apiErr.GetStatusCode() == http.StatusNotFound +} + +// Handle appends a diagnostic describing err. +// +// The message leads with the resource and operation, then the API's own +// explanation, then a hint for the failure modes where the cause is not obvious +// from the status alone. A bare "request failed: 403" leaves a practitioner with +// nowhere to go. +func Handle(diags *diag.Diagnostics, resourceType string, op Operation, err error) { + if err == nil { + return + } + + summary := fmt.Sprintf("Unable to %s %s", op, resourceType) + + var apiErr abstractions.ApiErrorable + if !stderrors.As(err, &apiErr) { + // Transport failures, context cancellation and timeouts land here. There + // is no API explanation to quote, so the wrapped error is all there is. + diags.AddError(summary, err.Error()) + return + } + + // The generated error models embed abstractions.ApiError and carry the API's + // own explanation in its Message, which err.Error() renders. Kiota does not + // record the method or path on the error, so unlike the resty SDK's + // diagnostics the status line names only the status. + status := apiErr.GetStatusCode() + + detail := err.Error() + if detail == "" || detail == "error status code received from the API" { + // Kiota's fallback message restates that there was an error without + // saying anything the status line does not, so it earns no second line. + detail = "The API offered no further explanation." + } + + msg := fmt.Sprintf("The ThousandEyes API returned %d %s.\n\n%s", + status, http.StatusText(status), detail) + + if hint := hintFor(status, op); hint != "" { + msg += "\n\n" + hint + } + + diags.AddError(summary, msg) +} + +// hintFor returns advice for the statuses whose cause is not evident from the +// status line. It deliberately says nothing for the rest: a hint that merely +// restates the status trains people to ignore hints. +func hintFor(status int, op Operation) string { + switch status { + case http.StatusUnauthorized: + return "The bearer token was rejected. ThousandEyes tokens are created by hand " + + "under Account Settings > Users and Roles and do not expire, so this usually " + + "means the token was revoked or is malformed rather than that it needs refreshing." + + case http.StatusForbidden: + return "The token authenticated but lacks permission for this operation, or the " + + "account group it is scoped to does not contain the object. Check the token " + + "owner's role and the provider's account_group_id." + + case http.StatusNotFound: + if op == OpDelete { + return "The object was already gone. This is usually benign." + } + return "The object does not exist in the account group the token is scoped to. " + + "An object can be invisible rather than absent if account_group_id is wrong." + + case http.StatusTooManyRequests: + return "The organisation's rate limit was exhausted. The SDK retries these " + + "automatically, so reaching this error means the limit stayed exhausted for " + + "the whole retry budget; reduce parallelism with -parallelism." + + case http.StatusBadRequest, http.StatusUnprocessableEntity: + return "The API rejected the request body. ThousandEyes validation errors often " + + "omit the offending field, so compare the configuration against the API " + + "reference for this endpoint." + + default: + return "" + } +} diff --git a/pilot/thousandeyes-kiota/internal/services/common/schema/schema.go b/pilot/thousandeyes-kiota/internal/services/common/schema/schema.go new file mode 100644 index 00000000..6706d523 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/common/schema/schema.go @@ -0,0 +1,32 @@ +// Package schema holds schema fragments shared by every generated resource. +// +// A generated schema calls into here rather than repeating the fragment, so that +// changing the timeouts block, for instance, is one edit rather than one per +// resource. +package schema + +import ( + "context" + + timeoutsds "github.com/hashicorp/terraform-plugin-framework-timeouts/datasource/timeouts" + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + dsschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" +) + +// ResourceTimeouts returns the timeouts block for a resource with all four +// operations. +func ResourceTimeouts(ctx context.Context) schema.Block { + return timeouts.Block(ctx, timeouts.Opts{ + Create: true, + Read: true, + Update: true, + Delete: true, + }) +} + +// DataSourceTimeouts returns the timeouts block for a data source, which only +// reads. +func DataSourceTimeouts(ctx context.Context) dsschema.Block { + return timeoutsds.Block(ctx) +} diff --git a/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/conditional_validators.go b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/conditional_validators.go new file mode 100644 index 00000000..d6e78d56 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/conditional_validators.go @@ -0,0 +1,75 @@ +// Code generated by tfpfgen from blueprints/thousandeyes-kiota +// (sha256:82a8701a0f77258aba49054f577921f206b8fcaecd42c5fb57874c3fd6799173). DO NOT EDIT. + +package tag + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// requiredWhen enforces a requirement the API applies and the specification does not +// declare: the target attribute must be set while the gate attribute holds one specific +// value. Each instance in ConfigValidators was derived from recorded probe evidence -- +// the API refused a request omitting the target on exactly that branch. +// +// The framework's stock validators cannot express this: AlsoRequires relates presence to +// presence, never presence to a value. The type lives in the generated package rather +// than a shared helper so the resource stays self-contained. +type requiredWhen struct { + gate path.Path + equals string + target path.Path +} + +var _ resource.ConfigValidator = requiredWhen{} + +func (v requiredWhen) Description(ctx context.Context) string { + return v.MarkdownDescription(ctx) +} + +func (v requiredWhen) MarkdownDescription(context.Context) string { + return fmt.Sprintf("%s is required when %s is %q", v.target, v.gate, v.equals) +} + +// ValidateResource reports the omission at plan time, where it costs a message, instead +// of at apply time, where the API's refusal costs a failed run. +func (v requiredWhen) ValidateResource( + ctx context.Context, + req resource.ValidateConfigRequest, + resp *resource.ValidateConfigResponse, +) { + var gate types.String + resp.Diagnostics.Append(req.Config.GetAttribute(ctx, v.gate, &gate)...) + if resp.Diagnostics.HasError() || gate.IsNull() || gate.IsUnknown() { + // An unknown gate may resolve to either branch; deciding now would reject + // configurations that are about to become valid. + return + } + if gate.ValueString() != v.equals { + return + } + + var target attr.Value + resp.Diagnostics.Append(req.Config.GetAttribute(ctx, v.target, &target)...) + if resp.Diagnostics.HasError() || target == nil || target.IsUnknown() { + return + } + + if target.IsNull() { + resp.Diagnostics.AddAttributeError( + v.target, + fmt.Sprintf("Missing attribute required when %s is %q", v.gate, v.equals), + fmt.Sprintf( + "The API requires %s whenever %s is %q, although its specification does "+ + "not declare it. Set the attribute, or change %s.", + v.target, v.gate, v.equals, v.gate, + ), + ) + } +} diff --git a/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/construct.go b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/construct.go new file mode 100644 index 00000000..8cf554ae --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/construct.go @@ -0,0 +1,88 @@ +// Code generated by tfpfgen from blueprints/thousandeyes-kiota +// (sha256:82a8701a0f77258aba49054f577921f206b8fcaecd42c5fb57874c3fd6799173). DO NOT EDIT. + +package tag + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + + models "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk/models" + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/services/common/convert" +) + +// constructResource maps Terraform configuration onto the API's request body. +// +// Only writable attributes appear here. A computed attribute has no expand +// conversion, so it cannot be sent by construction rather than by discipline. +func constructResource(ctx context.Context, data *TagResourceModel) (*models.Tags_API_TagInfo, diag.Diagnostics) { + var ( + diags diag.Diagnostics + d diag.Diagnostics + ) + tflog.Debug(ctx, "constructing request body", map[string]any{"resource": TypeName}) + + body := models.NewTags_API_TagInfo() + + body.SetKey(convert.FrameworkToPtrString(data.Key)) + body.SetValue(convert.FrameworkToPtrString(data.Value)) + body.SetColor(convert.FrameworkToPtrString(data.Color)) + body.SetDescription(convert.FrameworkToPtrString(data.Description)) + body.SetIcon(convert.FrameworkToPtrString(data.Icon)) + objectTypeRaw, d := convert.FrameworkToKiotaEnum[models.Tags_API_ObjectType](data.ObjectType, models.ParseTags_API_ObjectType) + diags.Append(d...) + body.SetObjectType(objectTypeRaw) + accessTypeRaw, d := convert.FrameworkToKiotaEnum[models.Tags_API_AccessType](data.AccessType, models.ParseTags_API_AccessType) + diags.Append(d...) + body.SetAccessType(accessTypeRaw) + matchTypeRaw, d := convert.FrameworkToKiotaEnum[models.Tags_API_TagMatchType](data.MatchType, models.ParseTags_API_TagMatchType) + diags.Append(d...) + body.SetMatchType(matchTypeRaw) + typeRaw, d := convert.FrameworkToKiotaEnum[models.Tags_API_Type](data.Type, models.ParseTags_API_Type) + diags.Append(d...) + body.SetTypeEscaped(typeRaw) + filtersRaw, d := expandTagFilters(ctx, data.Filters) + diags.Append(d...) + body.SetFilters(filtersRaw) + + return body, diags +} + +// expandTagFilters converts the configured TagFilterModel values into SDK structs. +func expandTagFilters(ctx context.Context, v types.Set) ([]models.Tags_API_TagFilterable, diag.Diagnostics) { + var diags diag.Diagnostics + + // Null and unknown both yield nil so the field is omitted from the request, + // which is distinct from an empty collection asking the API to clear it. + if v.IsNull() || v.IsUnknown() { + return nil, diags + } + + var elements []TagFilterModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) + if diags.HasError() { + return nil, diags + } + + out := make([]models.Tags_API_TagFilterable, 0, len(elements)) + for _, m := range elements { + item := models.NewTags_API_TagFilter() + + item.SetKey(convert.FrameworkToPtrString(m.Key)) + modeRaw, d := convert.FrameworkToKiotaEnum[models.Tags_API_TagFilterMode](m.Mode, models.ParseTags_API_TagFilterMode) + diags.Append(d...) + item.SetMode(modeRaw) + scopeRaw, d := convert.FrameworkToKiotaEnum[models.Tags_API_TagFilterScope](m.Scope, models.ParseTags_API_TagFilterScope) + diags.Append(d...) + item.SetScope(scopeRaw) + valuesRaw, d := convert.FrameworkSetToStringSlice(ctx, m.Values) + diags.Append(d...) + item.SetValues(valuesRaw) + out = append(out, item) + } + + return out, diags +} diff --git a/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/crud.go b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/crud.go new file mode 100644 index 00000000..01b54625 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/crud.go @@ -0,0 +1,298 @@ +// Code generated by tfpfgen from blueprints/thousandeyes-kiota +// (sha256:82a8701a0f77258aba49054f577921f206b8fcaecd42c5fb57874c3fd6799173). DO NOT EDIT. + +package tag + +import ( + "context" + "time" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-log/tflog" + + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/services/common/convert" + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/services/common/crud" + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/services/common/errors" +) + +// Create provisions a new thousandeyes_tag. +func (r *TagResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan TagResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, "creating resource", map[string]any{"resource": TypeName}) + + timeout := crud.Timeout(ctx, plan.Timeouts, crud.PhaseCreate, CreateTimeout*time.Second, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + body, constructDiags := constructResource(ctx, &plan) + resp.Diagnostics.Append(constructDiags...) + if resp.Diagnostics.HasError() { + return + } + + created, err := r.client.Tags().Post(ctx, body, nil) + if err != nil { + errors.Handle(&resp.Diagnostics, TypeName, errors.OpCreate, err) + return + } + + // An empty success body cannot be recorded: without a response there is no + // identifier, and inventing one would orphan whatever the API just created. + if created == nil { + errors.Handle(&resp.Diagnostics, TypeName, errors.OpCreate, errors.ErrEmptyResponse) + return + } + + // The identifier is assigned first, so that a partially-successful create still + // leaves a resource Terraform can find and delete rather than an orphan it has no + // record of. + plan.ID = convert.PtrStringerToFramework(created.GetId()) + + // State comes from a read, not from the create response. The two are not + // interchangeable: a field the API only returns when asked to expand it is absent + // from a create response, so mapping that response would write it null and the next + // read would contradict it -- a diff no configuration change can resolve. + resp.Diagnostics.Append(r.readAfterWrite(ctx, &plan, errors.OpCreate)...) + if resp.Diagnostics.HasError() { + return + } + + // The framework refuses a resource that declares an identity schema and then returns no + // identity data, so this is not optional decoration. It must also be stable: Read and + // Update reject an identity that differs from the one Terraform is holding. + resp.Diagnostics.Append(resp.Identity.Set(ctx, identityOf(&plan))...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +// Read refreshes thousandeyes_tag state from the API. +func (r *TagResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state TagResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, "reading resource", map[string]any{"resource": TypeName}) + + timeout := crud.Timeout(ctx, state.Timeouts, crud.PhaseRead, ReadTimeout*time.Second, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + found, diags := r.readState(ctx, &state, errors.OpRead) + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Something deleted outside Terraform must produce a plan that recreates it, not an + // error the practitioner has no way to clear. + if !found { + tflog.Debug(ctx, "resource no longer exists, removing it from state", map[string]any{ + "resource": TypeName, + }) + resp.State.RemoveResource(ctx) + return + } + + // The framework refuses a resource that declares an identity schema and then returns no + // identity data, so this is not optional decoration. It must also be stable: Read and + // Update reject an identity that differs from the one Terraform is holding. + resp.Diagnostics.Append(resp.Identity.Set(ctx, identityOf(&state))...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +// readState fetches thousandeyes_tag and maps it onto state. +// +// The single call site of mapRemoteStateToTerraform. Create, Update and Read all arrive +// here, so a change to state mapping is one edit rather than three that have to agree. +// +// The bool distinguishes "not there" from "the request failed", which need different +// handling and used to be conflated: Read turns absence into a recreate, while a read +// after a write treats it as not-yet-consistent and tries again. +func (r *TagResource) readState( + ctx context.Context, + state *TagResourceModel, + op errors.Operation, +) (bool, diag.Diagnostics) { + var diags diag.Diagnostics + + remote, err := r.client.Tags().ById(state.ID.ValueString()).Get(ctx, nil) + if err != nil { + if errors.IsNotFound(err) { + return false, diags + } + + errors.Handle(&diags, TypeName, op, err) + + return false, diags + } + + // An empty success body means the object is gone as far as this provider can + // tell; treat it exactly like a not-found rather than mapping nothing. + if remote == nil { + return false, diags + } + diags.Append(mapRemoteStateToTerraform(ctx, state, remote)...) + + return true, diags +} + +// readAfterWrite refreshes state from the API after a create or an update, retrying while +// the object is not yet readable. +// +// Why a read at all, rather than mapping the write's own response: the two are not +// interchangeable. A field the API only returns when asked to expand it is absent from a +// write response, so mapping that response writes it null and the next read contradicts +// it. The pilot's own probe evidence says so. +// +// Why a retry: an API that is not read-your-writes consistent returns 404 for an object it +// has just created, and a create that reports success on state the next plan disagrees with +// is worse than one that waits. +// +// The budget is the built-in default: this API's consistency has not been measured, so +// there is no observation to size it from. An API that is consistent succeeds on the first +// attempt and pays nothing for the loop. +func (r *TagResource) readAfterWrite( + ctx context.Context, + state *TagResourceModel, + op errors.Operation, +) diag.Diagnostics { + var diags diag.Diagnostics + + opts := crud.ReadBackOptions{ + MaxRetries: 10, + Interval: 2000 * time.Millisecond, + ResourceType: TypeName, + Operation: string(op), + } + + err := crud.ReadBack(ctx, opts, func(ctx context.Context) (bool, error) { + found, d := r.readState(ctx, state, op) + diags.Append(d...) + + if d.HasError() { + return false, crud.ErrStopRetrying + } + + // The hand-written predicate in predicate.go decides whether the write has landed, + // not merely whether the object is readable. That distinction matters for an API + // that returns a stale object rather than a 404, and it cannot be generated: which + // field changes on write is API-specific. + if found && !readBackLanded(ctx, state) { + return false, nil + } + + return found, nil + }) + + // A stop signal means readState has already produced the diagnostic, so reporting + // here as well would say the same thing twice. + if err != nil && !crud.IsStopRetrying(err) { + errors.Handle(&diags, TypeName, op, err) + } + + return diags +} + +// Update applies configuration changes to an existing thousandeyes_tag. +func (r *TagResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan, state TagResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, "updating resource", map[string]any{"resource": TypeName}) + + timeout := crud.Timeout(ctx, plan.Timeouts, crud.PhaseUpdate, UpdateTimeout*time.Second, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + body, constructDiags := constructResource(ctx, &plan) + resp.Diagnostics.Append(constructDiags...) + if resp.Diagnostics.HasError() { + return + } + + _, err := r.client.Tags().ById(state.ID.ValueString()).Put(ctx, body, nil) + if err != nil { + errors.Handle(&resp.Diagnostics, TypeName, errors.OpUpdate, err) + return + } + + // The plan carries no identifier, since it is computed. Carrying it over from + // prior state keeps it in the model in case the response omits it. + plan.ID = state.ID + + // As with create: state comes from a read rather than from the update response. + resp.Diagnostics.Append(r.readAfterWrite(ctx, &plan, errors.OpUpdate)...) + if resp.Diagnostics.HasError() { + return + } + + // The framework refuses a resource that declares an identity schema and then returns no + // identity data, so this is not optional decoration. It must also be stable: Read and + // Update reject an identity that differs from the one Terraform is holding. + resp.Diagnostics.Append(resp.Identity.Set(ctx, identityOf(&plan))...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +// Delete removes a thousandeyes_tag. +func (r *TagResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + var state TagResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + tflog.Debug(ctx, "deleting resource", map[string]any{"resource": TypeName}) + + timeout := crud.Timeout(ctx, state.Timeouts, crud.PhaseDelete, DeleteTimeout*time.Second, &resp.Diagnostics) + if resp.Diagnostics.HasError() { + return + } + ctx, cancel := context.WithTimeout(ctx, timeout) + defer cancel() + + err := r.client.Tags().ById(state.ID.ValueString()).Delete(ctx, nil) + if err != nil { + // Already gone is the outcome delete was asking for. + if errors.IsNotFound(err) { + return + } + errors.Handle(&resp.Diagnostics, TypeName, errors.OpDelete, err) + return + } + + // State is removed by the framework once Delete returns without error. +} diff --git a/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/model.go b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/model.go new file mode 100644 index 00000000..197d975e --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/model.go @@ -0,0 +1,100 @@ +// Code generated by tfpfgen from blueprints/thousandeyes-kiota +// (sha256:82a8701a0f77258aba49054f577921f206b8fcaecd42c5fb57874c3fd6799173). DO NOT EDIT. + +// REF: https://developer.cisco.com/docs/thousandeyes/list-tags/ +package tag + +import ( + "github.com/hashicorp/terraform-plugin-framework-timeouts/resource/timeouts" + "github.com/hashicorp/terraform-plugin-framework/attr" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +// TagResourceModel is the Terraform state for a thousandeyes_tag. +// +// Field order follows the schema, and the timeouts value is last. +type TagResourceModel struct { + ID types.String `tfsdk:"id"` + Key types.String `tfsdk:"key"` + Value types.String `tfsdk:"value"` + Color types.String `tfsdk:"color"` + Description types.String `tfsdk:"description"` + Icon types.String `tfsdk:"icon"` + ObjectType types.String `tfsdk:"object_type"` + AccessType types.String `tfsdk:"access_type"` + MatchType types.String `tfsdk:"match_type"` + Type types.String `tfsdk:"type"` + BuiltIn types.Bool `tfsdk:"built_in"` + AccountGroupID types.Int64 `tfsdk:"account_group_id"` + CreateDate types.String `tfsdk:"create_date"` + ModifiedDate types.String `tfsdk:"modified_date"` + LegacyID types.Float64 `tfsdk:"legacy_id"` + Assignments types.Set `tfsdk:"assignments"` + Filters types.Set `tfsdk:"filters"` + Timeouts timeouts.Value `tfsdk:"timeouts"` +} + +// TagResourceIdentity is the identity Terraform addresses this resource by. +// +// Plain Go scalars rather than framework values, because that is what the framework decodes +// an identity into. One type, shared by the resource and by its list facet, so the two +// cannot disagree about the shape. +type TagResourceIdentity struct { + ID string `tfsdk:"id"` +} + +// identityOf reads the identity out of the resource model. +// +// From the model rather than from the API: the values are by definition already in state, +// so a request to learn what Terraform is holding would be one that can fail for no reason. +func identityOf(state *TagResourceModel) TagResourceIdentity { + var identity TagResourceIdentity + + identity.ID = state.ID.ValueString() + + return identity +} + +// TagAssignmentModel is one element of a nested attribute. +// +// It is a sibling of the resource model rather than an inner type, because the +// framework decodes collection elements into a named type. +type TagAssignmentModel struct { + ID types.String `tfsdk:"id"` + Type types.String `tfsdk:"type"` +} + +// tagAssignmentAttrTypes describes TagAssignmentModel to the framework. +// +// It is declared once and referenced by both conversion helpers, so the shape +// cannot drift between the direction that reads it and the direction that writes it. +var tagAssignmentAttrTypes = map[string]attr.Type{ + "id": types.StringType, + "type": types.StringType, +} + +var tagAssignmentObjectType = types.ObjectType{AttrTypes: tagAssignmentAttrTypes} + +// TagFilterModel is one element of a nested attribute. +// +// It is a sibling of the resource model rather than an inner type, because the +// framework decodes collection elements into a named type. +type TagFilterModel struct { + Key types.String `tfsdk:"key"` + Mode types.String `tfsdk:"mode"` + Scope types.String `tfsdk:"scope"` + Values types.Set `tfsdk:"values"` +} + +// tagFilterAttrTypes describes TagFilterModel to the framework. +// +// It is declared once and referenced by both conversion helpers, so the shape +// cannot drift between the direction that reads it and the direction that writes it. +var tagFilterAttrTypes = map[string]attr.Type{ + "key": types.StringType, + "mode": types.StringType, + "scope": types.StringType, + "values": types.SetType{ElemType: types.StringType}, +} + +var tagFilterObjectType = types.ObjectType{AttrTypes: tagFilterAttrTypes} diff --git a/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/modify_plan.go b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/modify_plan.go new file mode 100644 index 00000000..d3d7207f --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/modify_plan.go @@ -0,0 +1,46 @@ +// This file is yours. +// +// It was scaffolded once, carries no generated marker, and `emit` will not write over it +// again -- so the drift check does not police it and your edits survive regeneration. Deleting +// it means removing `hooks.modifyPlan` from the blueprint too, because the generated resource +// asserts resource.ResourceWithModifyPlan when that flag is set. +// +// # What belongs here +// +// Plan-time judgement no specification carries. The usual cases: +// +// - marking a resource for replacement on a change the API cannot apply in place, when +// RequiresReplace on one attribute is too blunt because it depends on another +// - suppressing a diff the API creates by normalising a value, which is what the probe's +// write.normalisation fact reports +// - enforcing a cardinality or a dependency that spans attributes in a way +// configValidators cannot state +// +// A stub that does nothing is a perfectly good state for this file. The reference provider +// carries one in 93 of its 167 resource packages, and many are exactly that. + +package tag + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/resource" +) + +// ModifyPlan adjusts the planned state for a thousandeyes_tag. +// +// Called on create, update and destroy. Distinguish them before doing anything: on destroy the +// plan is null, and on create the prior state is. +func (r *TagResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { + // Destroy: nothing planned, so there is nothing to modify. + if req.Plan.Raw.IsNull() { + return + } + + // Create: no prior state to compare against. + if req.State.Raw.IsNull() { + return + } + + // An update. Read plan and state into TagResourceModel and adjust resp.Plan as needed. +} diff --git a/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/predicate.go b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/predicate.go new file mode 100644 index 00000000..fd97c23f --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/predicate.go @@ -0,0 +1,41 @@ +// This file is yours. +// +// It was scaffolded once, carries no generated marker, and `emit` will not write over it +// again. Deleting it means removing `hooks.readBackPredicate` from the blueprint too, because +// the generated read-after-write calls this function when that flag is set. +// +// # Why this is not generated +// +// The generated retry asks whether the object is *readable*, which every API answers the same +// way -- a 404 means not yet. That is enough for an API whose only inconsistency is a delay +// before the object appears. +// +// It is not enough for an API that returns a *stale* object rather than a 404. There the write +// has not landed until some particular field changes, and which field that is depends on the +// API and sometimes on the resource: a modification timestamp, a revision counter, a status +// moving out of "pending". No specification states it, so it cannot be generated -- 28 of the +// reference provider's 167 resources carry a file like this and the rest do not need one. + +package tag + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +// readBackLanded reports whether a write has taken effect in the object just read. +// +// Returning false asks the retry loop to read again; returning true accepts the state as +// mapped. Returning true unconditionally -- as this scaffold does -- reproduces the behaviour +// you would get with no predicate at all, so it is a safe starting point. +// +// state is the model as just populated from the API. Compare it against whatever your API +// touches on write. +func readBackLanded(ctx context.Context, state *TagResourceModel) bool { + tflog.Trace(ctx, "read-back predicate not implemented, accepting the first read", map[string]any{ + "resource": TypeName, + }) + + return true +} diff --git a/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/resource.go b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/resource.go new file mode 100644 index 00000000..cc232a9c --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/resource.go @@ -0,0 +1,299 @@ +// Code generated by tfpfgen from blueprints/thousandeyes-kiota +// (sha256:82a8701a0f77258aba49054f577921f206b8fcaecd42c5fb57874c3fd6799173). DO NOT EDIT. + +package tag + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/identityschema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/client" + sdk "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk" + commonschema "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/services/common/schema" +) + +const ( + // TypeName is the Terraform type this resource registers as. + TypeName = "thousandeyes_tag" + + // Default per-operation timeouts, in seconds. A practitioner overrides them + // with the resource's timeouts block. + CreateTimeout = 180 + ReadTimeout = 180 + UpdateTimeout = 180 + DeleteTimeout = 180 +) + +// Compile-time assertions that this resource implements what it claims to. They +// turn a missing method into a build failure rather than a runtime surprise. +var ( + _ resource.Resource = (*TagResource)(nil) + _ resource.ResourceWithConfigure = (*TagResource)(nil) + _ resource.ResourceWithImportState = (*TagResource)(nil) + _ resource.ResourceWithIdentity = (*TagResource)(nil) + _ resource.ResourceWithConfigValidators = (*TagResource)(nil) + _ resource.ResourceWithModifyPlan = (*TagResource)(nil) +) + +// NewTagResource returns the thousandeyes_tag resource. +func NewTagResource() resource.Resource { + return &TagResource{} +} + +// TagResource implements the thousandeyes_tag resource. +type TagResource struct { + client *sdk.ThousandEyesClient +} + +// Metadata returns the resource type name. +func (r *TagResource) Metadata(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = TypeName +} + +// Configure receives the configured SDK client from the provider. +func (r *TagResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + r.client = client.ForResource(ctx, req, resp, TypeName) +} + +// ImportState maps a terraform import into state. +func (r *TagResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) +} + +// IdentitySchema returns the resource identity schema. +// +// Terraform addresses an object by its identity independently of configuration, which is +// what makes import by identity and list resources possible. Declaring it is what makes +// this resource satisfy resource.ResourceWithIdentity. +func (r *TagResource) IdentitySchema(_ context.Context, _ resource.IdentitySchemaRequest, resp *resource.IdentitySchemaResponse) { + resp.IdentitySchema = identityschema.Schema{ + Attributes: map[string]identityschema.Attribute{ + "id": identityschema.StringAttribute{ + RequiredForImport: true, + Description: "The tag's unique identifier, which Terraform addresses it by.", + }, + }, + } +} + +// ConfigValidators returns the cross-attribute rules for this resource. +// +// On the resource rather than on an attribute because that is what they are about: a rule +// relating two fields has no single field to hang off, and the diagnostic should name the block. +func (r *TagResource) ConfigValidators(_ context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + // The prober recorded that the API enforces type when object_type is "endpoint-agent"; + // the specification does not declare it. + requiredWhen{gate: path.Root("object_type"), equals: "endpoint-agent", target: path.Root("type")}, + } +} + +// Schema returns the resource schema. +func (r *TagResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "Manages a ThousandEyes tag. Tags are key/value labels that can be assigned to tests, agents and dashboards.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "The tag's unique identifier, assigned by the API.", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "key": schema.StringAttribute{ + Required: true, + MarkdownDescription: "The tag's key. Together with `value` this forms the label applied to assigned objects.", + }, + "value": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "The tag's value. The API enforces this field's " + + "presence, which the specification does not declare. ", + }, + "color": schema.StringAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "The tag's display colour as a hex string. Computed as well as optional because the API " + + "assigns one when it is omitted; this has not yet been confirmed by probing. Observed: the API assigns \"#A7EB10\" when this is omitted. " + + "", + }, + "description": schema.StringAttribute{ + Optional: true, + MarkdownDescription: "A human-readable description of the tag.", + }, + "icon": schema.StringAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "The tag's icon. Observed: the API assigns \"LABEL\" " + + "when this is omitted. ", + }, + "object_type": schema.StringAttribute{ + Required: true, + MarkdownDescription: "The kind of object the tag may be assigned to. Documented values are `test`, `v-agent`, " + + "`endpoint-test`, `dashboard` and `connected-devices-test`. The generated validator permits " + + "the whole documented set, which is wider than what any one tenant accepts, so a stale " + + "specification surfaces as the API's own error rather than as a plan failure. Values accepted here: `test`, `dashboard`, " + + "`endpoint-test`, `v-agent`, `connected-devices-test`. The API enforces this field's " + + "presence, which the specification does not declare. The SDK's encoding drops this type's zero value (omitempty), so configuring false, 0 " + + "or \"\" silently becomes an omission. ", + Validators: []validator.String{ + stringvalidator.OneOf("test", "dashboard", "endpoint-test", "v-agent", "connected-devices-test", "endpoint-agent"), + }, + PlanModifiers: []planmodifier.String{ + // RequiresReplace: the prober corroborated that the API refuses in-place + // changes to this field. + stringplanmodifier.RequiresReplace(), + }, + }, + "access_type": schema.StringAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "The tag's access level. Documented values are `all`, `partner` and `system`. Values accepted here: `all`. The specification documents " + + "`system`, which the API rejected. The API enforces this field's presence, which the " + + "specification does not declare. The SDK's encoding " + + "drops this type's zero value (omitempty), so configuring false, 0 or \"\" silently becomes " + + "an omission. ", + // The API refused "system" from the documented set when probed. Still permitted here: a value + // one tenant rejects may be licence-gated rather than nonexistent. + Validators: []validator.String{ + stringvalidator.OneOf("all", "partner", "system"), + }, + }, + "match_type": schema.StringAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "How the tag's filters combine when it is assigned dynamically. Values accepted here: `and`, `or`. Conditionally, when " + + "objectType is \"endpoint-agent\" and type is \"dynamic\": a create omitting this field was " + + "refused with 400 and the error named the field, when objectType is \"endpoint-agent\" and " + + "type is \"dynamic\". Applied as a behaviour variant. Conditionally, when objectType is " + + "\"endpoint-agent\": the field appeared in the read after 1 create(s), when objectType is " + + "\"endpoint-agent\". Applied as a behaviour variant. Conditionally, when objectType is " + + "\"test\": the field was sent on 1 create(s) and appeared in no read response, including " + + "every expansion the plan declares, when objectType is \"test\". Applied as a behaviour " + + "variant. The SDK's encoding drops this type's zero " + + "value (omitempty), so configuring false, 0 or \"\" silently becomes an omission. ", + Validators: []validator.String{ + stringvalidator.OneOf("and", "or"), + }, + }, + "type": schema.StringAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "The tag's type, assigned by the API. Conditionally, " + + "when objectType is \"endpoint-agent\": a create omitting this field was refused with 400 and " + + "the error named the field, when objectType is \"endpoint-agent\". Applied as a behaviour " + + "variant. The SDK's encoding drops this type's zero " + + "value (omitempty), so configuring false, 0 or \"\" silently becomes an omission. ", + Validators: []validator.String{ + stringvalidator.OneOf("static", "dynamic"), + }, + }, + "built_in": schema.BoolAttribute{ + Computed: true, + MarkdownDescription: "Whether the tag is built in rather than user-created.", + }, + "account_group_id": schema.Int64Attribute{ + Computed: true, + MarkdownDescription: "The account group the tag belongs to. Computed rather than configurable: the provider " + + "scopes every request through its own `account_group_id` setting, so accepting a second " + + "value here would let the two disagree.", + }, + "create_date": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "When the tag was created.", + }, + "modified_date": schema.StringAttribute{ + Computed: true, + MarkdownDescription: "When the tag was last modified.", + }, + "legacy_id": schema.Float64Attribute{ + Computed: true, + MarkdownDescription: "The tag's identifier in the v6 API. Typed as a number because the specification declares " + + "it as one, although observed values are integral; probing will settle whether this should " + + "be an integer.", + }, + "assignments": schema.SetNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Required: true, + MarkdownDescription: "The identifier of the object the tag is assigned to.", + }, + "type": schema.StringAttribute{ + Required: true, + MarkdownDescription: "The kind of object assigned. Documented values are `test`, `v-agent`, `endpoint-test`, " + + "`dashboard` and `connected-devices-test`.", + }, + }, + }, + Computed: true, + MarkdownDescription: "Objects this tag is assigned to. A set rather than a list because the API does not " + + "preserve ordering.", + }, + "filters": schema.SetNestedAttribute{ + NestedObject: schema.NestedAttributeObject{ + Attributes: map[string]schema.Attribute{ + "key": schema.StringAttribute{ + Required: true, + MarkdownDescription: "The filter key used for matching. Conditionally, " + + "when objectType is \"endpoint-agent\" and type is \"dynamic\": the field appeared in the read " + + "after 1 create(s), when objectType is \"endpoint-agent\" and type is \"dynamic\". Applied as a " + + "behaviour variant. ", + }, + "mode": schema.StringAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "How the filter values are matched. Conditionally, " + + "when objectType is \"endpoint-agent\" and type is \"dynamic\": the field appeared in the read " + + "after 1 create(s), when objectType is \"endpoint-agent\" and type is \"dynamic\". Applied as a " + + "behaviour variant. ", + }, + "scope": schema.StringAttribute{ + Optional: true, + Computed: true, + MarkdownDescription: "The scope the filter applies within. Conditionally, " + + "when objectType is \"endpoint-agent\" and type is \"dynamic\": the field appeared in the read " + + "after 1 create(s), when objectType is \"endpoint-agent\" and type is \"dynamic\". Applied as a " + + "behaviour variant. ", + }, + "values": schema.SetAttribute{ + ElementType: types.StringType, + Required: true, + MarkdownDescription: "The values the filter matches against. " + + "Conditionally, when objectType is \"endpoint-agent\" and type is \"dynamic\": the field " + + "appeared in the read after 1 create(s), when objectType is \"endpoint-agent\" and type is " + + "\"dynamic\". Applied as a behaviour variant. ", + }, + }, + }, + Optional: true, + Computed: true, + MarkdownDescription: "Filters that dynamically assign this tag to endpoint agents. Conditionally, when objectType is \"endpoint-agent\" and " + + "type is \"dynamic\": a create omitting this field was refused with 400 and the error named " + + "the field, when objectType is \"endpoint-agent\" and type is \"dynamic\". Applied as a " + + "behaviour variant. Conditionally, when objectType is \"endpoint-agent\" and type is " + + "\"dynamic\": the field appeared in the read after 1 create(s), when objectType is " + + "\"endpoint-agent\" and type is \"dynamic\". Applied as a behaviour variant. ", + }, + }, + Blocks: map[string]schema.Block{ + "timeouts": commonschema.ResourceTimeouts(ctx), + }, + } +} diff --git a/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/resource_acceptance_test.go b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/resource_acceptance_test.go new file mode 100644 index 00000000..5de89b1f --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/resource_acceptance_test.go @@ -0,0 +1,130 @@ +// Code generated by tfpfgen from blueprints/thousandeyes-kiota +// (sha256:82a8701a0f77258aba49054f577921f206b8fcaecd42c5fb57874c3fd6799173). DO NOT EDIT. + +package tag_test + +import ( + "os" + "path/filepath" + "testing" + "time" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/acceptance" + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/acceptance/check" + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/acceptance/destroy" + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag" +) + +// The resource under test, and the helper that asks the API whether it is really there. +const address = "thousandeyes_tag.test" + +var testResource = tag.TagTestResource{} + +// TestAccResourceTag_01_Lifecycle exercises the whole lifecycle against a live API. +// +// Guarded by TF_ACC, which terraform-plugin-testing requires, so `go test ./...` never reaches a +// live tenant by accident. The credential comes from the environment; see internal/acceptance. +// +// Every step ends with an implicit plan, and terraform-plugin-testing fails the step if that plan +// is not empty. That is the single strongest signal available that state mapping is right: an +// attribute the provider stores differently from how the API reports it shows up here as a diff on +// a resource nobody touched, and no amount of compiling or schema checking can see it. +func TestAccResourceTag_01_Lifecycle(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { acceptance.PreCheck(t) }, + ProtoV6ProviderFactories: acceptance.ProtoV6ProviderFactories, + // Destroy is verified against the API rather than against state, because Terraform + // considers a destroy done when the provider's Delete returns no error -- which is a + // statement about the request, not about the object. + // + // The wait is the built-in default; this API's consistency after delete was never measured, + // so this is a budget rather than an observation + CheckDestroy: destroy.CheckDestroyed(testResource, "thousandeyes_tag", 20*time.Second), + Steps: []resource.TestStep{ + // Create, from the smallest configuration the schema permits. + { + Config: config(t, "minimal.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + check.That(address).ExistsInAPI(testResource), + check.That(address).Key("id").Exists(), + check.That(address).Key("key").HasValue("tfacc-key"), + check.That(address).Key("value").HasValue("tfacc-value"), + check.That(address).Key("color").HasValue("#A7EB10"), + check.That(address).Key("icon").HasValue("LABEL"), + check.That(address).Key("object_type").HasValue("test"), + check.That(address).Key("access_type").HasValue("all"), + ), + }, + // Import, then compare every attribute against the state the apply produced. + // + // This is what catches a read that populates state differently from a create, which + // is the most common way a generated provider is subtly wrong. + { + ResourceName: address, + ImportState: true, + ImportStateVerify: true, + // Skipped, with the reason for each. An attribute the API never returns is + // absent after an import and present before it, so comparing it fails for a + // reason that has nothing to do with import being broken. + // match_type: the API never returns it, so it is absent after an import + ImportStateVerifyIgnore: []string{"match_type"}, + }, + // Update in place, to a configuration that sets the optional attributes too. + { + Config: config(t, "maximal.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + check.That(address).ExistsInAPI(testResource), + check.That(address).Key("id").Exists(), + check.That(address).Key("key").HasValue("tfacc-key"), + check.That(address).Key("value").HasValue("tfacc-value"), + check.That(address).Key("color").HasValue("#A7EB10"), + check.That(address).Key("description").HasValue("tfacc-description"), + check.That(address).Key("icon").HasValue("LABEL"), + check.That(address).Key("object_type").HasValue("test"), + check.That(address).Key("access_type").HasValue("all"), + check.That(address).Key("type").HasValue("static"), + ), + }, + // Flip object_type, which the API refuses to change in place. The plan + // must say so before anything is destroyed -- this is the generated + // RequiresReplace proven live, and without the plan check the step would pass + // by replacing whether or not the modifier exists. + { + Config: config(t, "replace.tf"), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(address, plancheck.ResourceActionReplace), + }, + }, + Check: resource.ComposeAggregateTestCheckFunc( + check.That(address).ExistsInAPI(testResource), + check.That(address).Key("id").Exists(), + check.That(address).Key("key").HasValue("tfacc-key"), + check.That(address).Key("value").HasValue("tfacc-value"), + check.That(address).Key("color").HasValue("#A7EB10"), + check.That(address).Key("icon").HasValue("LABEL"), + check.That(address).Key("object_type").HasValue("dashboard"), + check.That(address).Key("access_type").HasValue("all"), + ), + }, + }, + }) +} + +// config reads one of the committed fixtures. +// +// From testdata rather than from a string constant in this file, so the configuration can be +// edited and re-run without recompiling, and so `terraform fmt` can police it. +func config(t *testing.T, name string) string { + t.Helper() + + body, err := os.ReadFile(filepath.Join("testdata", name)) + if err != nil { + t.Fatalf("reading fixture %s: %v", name, err) + } + + return string(body) +} diff --git a/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/state.go b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/state.go new file mode 100644 index 00000000..4e5998b8 --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/state.go @@ -0,0 +1,118 @@ +// Code generated by tfpfgen from blueprints/thousandeyes-kiota +// (sha256:82a8701a0f77258aba49054f577921f206b8fcaecd42c5fb57874c3fd6799173). DO NOT EDIT. + +package tag + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" + + models "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk/models" + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/services/common/convert" +) + +// mapRemoteStateToTerraform maps the API's response onto Terraform state. +// +// This function replaces what a reflection-based provider does at runtime. Being +// generated code, a field whose conversion is wrong is a compile error rather +// than a surprise during apply. +func mapRemoteStateToTerraform(ctx context.Context, data *TagResourceModel, remote models.Tags_API_Tagable) diag.Diagnostics { + var ( + diags diag.Diagnostics + d diag.Diagnostics + ) + + if remote == nil { + tflog.Debug(ctx, "remote resource is nil, leaving state untouched", map[string]any{ + "resource": TypeName, + }) + return diags + } + + data.ID = convert.PtrStringerToFramework(remote.GetId()) + data.Key = convert.PtrStringToFramework(remote.GetKey()) + data.Value = convert.PtrStringToFramework(remote.GetValue()) + data.Color = convert.PtrStringToFramework(remote.GetColor()) + data.Description = convert.PtrStringToFramework(remote.GetDescription()) + data.Icon = convert.PtrStringToFramework(remote.GetIcon()) + data.ObjectType = convert.PtrStringerToFramework(remote.GetObjectType()) + data.AccessType = convert.PtrStringerToFramework(remote.GetAccessType()) + // match_type is deliberately not read back: the API accepts it and never returns + // it, so flattening would blank the configured value on every read. An + // unset value still has to resolve, or the apply is rejected for leaving + // it unknown. + if data.MatchType.IsUnknown() { + data.MatchType = types.StringNull() + } + data.Type = convert.PtrStringerToFramework(remote.GetTypeEscaped()) + data.BuiltIn = convert.PtrBoolToFramework(remote.GetBuiltIn()) + data.AccountGroupID = convert.PtrInt64ToFramework(remote.GetAid()) + data.CreateDate = convert.PtrStringToFramework(remote.GetCreateDate()) + data.ModifiedDate = convert.PtrTimeToFramework(remote.GetModifiedDate()) + data.LegacyID = convert.PtrFloat64ToFramework(remote.GetLegacyId()) + data.Assignments, d = flattenTagAssignments(ctx, remote.GetAssignments()) + diags.Append(d...) + data.Filters, d = flattenTagFilters(ctx, remote.GetFilters()) + diags.Append(d...) + + tflog.Debug(ctx, "mapped remote state", map[string]any{"resource": TypeName}) + + return diags +} + +// flattenTagAssignments converts SDK structs into the framework value state holds. +func flattenTagAssignments(ctx context.Context, in []models.Tags_API_Assignmentable) (types.Set, diag.Diagnostics) { + var diags diag.Diagnostics + + // A nil slice is null and an empty slice is empty. Conflating them produces a + // diff no configuration change can resolve. + if in == nil { + return types.SetNull(tagAssignmentObjectType), diags + } + + models := make([]TagAssignmentModel, 0, len(in)) + for _, item := range in { + var m TagAssignmentModel + + m.ID = convert.PtrStringToFramework(item.GetId()) + m.Type = convert.PtrStringerToFramework(item.GetTypeEscaped()) + models = append(models, m) + } + + out, d2 := types.SetValueFrom(ctx, tagAssignmentObjectType, models) + diags.Append(d2...) + + return out, diags +} + +// flattenTagFilters converts SDK structs into the framework value state holds. +func flattenTagFilters(ctx context.Context, in []models.Tags_API_TagFilterable) (types.Set, diag.Diagnostics) { + var diags diag.Diagnostics + var d diag.Diagnostics + + // A nil slice is null and an empty slice is empty. Conflating them produces a + // diff no configuration change can resolve. + if in == nil { + return types.SetNull(tagFilterObjectType), diags + } + + models := make([]TagFilterModel, 0, len(in)) + for _, item := range in { + var m TagFilterModel + + m.Key = convert.PtrStringToFramework(item.GetKey()) + m.Mode = convert.PtrStringerToFramework(item.GetMode()) + m.Scope = convert.PtrStringerToFramework(item.GetScope()) + m.Values, d = convert.StringSliceToFrameworkSet(ctx, item.GetValues()) + diags.Append(d...) + models = append(models, m) + } + + out, d2 := types.SetValueFrom(ctx, tagFilterObjectType, models) + diags.Append(d2...) + + return out, diags +} diff --git a/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/test_helper_test.go b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/test_helper_test.go new file mode 100644 index 00000000..74590f9d --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/test_helper_test.go @@ -0,0 +1,40 @@ +// Code generated by tfpfgen from blueprints/thousandeyes-kiota +// (sha256:82a8701a0f77258aba49054f577921f206b8fcaecd42c5fb57874c3fd6799173). DO NOT EDIT. + +package tag + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/acceptance/exists" + sdk "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/sdk" +) + +// TagTestResource answers whether a thousandeyes_tag is really in the API. +// +// In a _test.go file rather than an ordinary one, so no test scaffolding reaches a built +// provider. An external _test package still sees the exported identifiers of a package's own +// test files, which is what lets the generated acceptance test refer to this. +type TagTestResource struct{} + +// Exists reads the object back through the same operation the resource's Read uses. +// +// Deliberately the same call rather than an equivalent one. A check that asked a list endpoint +// instead could report an object present while the read the provider actually depends on was +// broken, which is the failure an acceptance test exists to catch. +func (TagTestResource) Exists( + ctx context.Context, + state *terraform.InstanceState, +) (*bool, error) { + return exists.Check(ctx, state, func( + ctx context.Context, + client *sdk.ThousandEyesClient, + state *terraform.InstanceState, + ) error { + _, err := client.Tags().ById(state.ID).Get(ctx, nil) + + return err + }) +} diff --git a/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/testdata/maximal.tf b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/testdata/maximal.tf new file mode 100644 index 00000000..2281591f --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/testdata/maximal.tf @@ -0,0 +1,42 @@ +# Code generated by tfpfgen from blueprints/thousandeyes-kiota +# (sha256:82a8701a0f77258aba49054f577921f206b8fcaecd42c5fb57874c3fd6799173). DO NOT EDIT. + +# The widest configuration the evidence supports: every writable attribute with a derivable +# value, chosen by the same derivation the rehearsal probe sends to the live API. +# +# Regenerated and policed, deliberately. This file used to be a hand-owned scaffold, and +# hand edits are how its values drifted from the generated assertions -- the checks derive +# from the blueprint, so the blueprint is where a value is corrected: an accFixture hint for +# something only a human knows, recorded behaviour for everything the probe observed. What +# could not be derived is listed inside the block, each with the reason. + +resource "thousandeyes_tag" "test" { + # Where a value came from. + # + # color: the API's own default, so a value it is known to accept + # + # icon: the API's own default, so a value it is known to accept + # + # object_type: observed accepted + # + # access_type: observed accepted; the API refused system + # + # match_type: observed accepted + # + # type: documented; unprobed + # + # Not filled in, because no correct value could be derived. Each of these is optional, so this + # configuration is valid as it stands -- but it is testing less than it could. + # + # filters: a nested object's members may be identifiers of objects that must already exist, + # and nothing in the specification says whether they are + key = "tfacc-key" + value = "tfacc-value" + color = "#A7EB10" + description = "tfacc-description" + icon = "LABEL" + object_type = "test" + access_type = "all" + match_type = "and" + type = "static" +} diff --git a/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/testdata/minimal.tf b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/testdata/minimal.tf new file mode 100644 index 00000000..d038a54e --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/testdata/minimal.tf @@ -0,0 +1,23 @@ +# Code generated by tfpfgen from blueprints/thousandeyes-kiota +# (sha256:82a8701a0f77258aba49054f577921f206b8fcaecd42c5fb57874c3fd6799173). DO NOT EDIT. + +# The smallest configuration this resource accepts: its required attributes and nothing else. +# +# Regenerated, so it follows the schema. If the API starts requiring a field, this file gains it +# on the next emit rather than an acceptance run discovering it. + +resource "thousandeyes_tag" "test" { + # Where a value came from. A fixture takes what the API was observed to accept, not the first + # value the specification documents -- which is the opposite of how a generated validator is + # built. A validator errs toward permitting, because blocking a configuration the API would + # have taken is a harm nobody can work around; a fixture errs toward working, because it is an + # input rather than a contract. + # + # object_type: observed accepted + # + # access_type: observed accepted; the API refused system + key = "tfacc-key" + value = "tfacc-value" + object_type = "test" + access_type = "all" +} diff --git a/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/testdata/replace.tf b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/testdata/replace.tf new file mode 100644 index 00000000..7cc8ff5e --- /dev/null +++ b/pilot/thousandeyes-kiota/internal/services/resources/tags/v7/tag/testdata/replace.tf @@ -0,0 +1,24 @@ +# Code generated by tfpfgen from blueprints/thousandeyes-kiota +# (sha256:82a8701a0f77258aba49054f577921f206b8fcaecd42c5fb57874c3fd6799173). DO NOT EDIT. + +# The smallest configuration this resource accepts: its required attributes and nothing else. +# +# Regenerated, so it follows the schema. If the API starts requiring a field, this file gains it +# on the next emit rather than an acceptance run discovering it. + +resource "thousandeyes_tag" "test" { + # Where a value came from. A fixture takes what the API was observed to accept, not the first + # value the specification documents -- which is the opposite of how a generated validator is + # built. A validator errs toward permitting, because blocking a configuration the API would + # have taken is a harm nobody can work around; a fixture errs toward working, because it is an + # input rather than a contract. + # + # object_type: flipped from the create configuration; the API refuses in-place changes here, + # so this must plan as a replacement + # + # access_type: observed accepted; the API refused system + key = "tfacc-key" + value = "tfacc-value" + object_type = "dashboard" + access_type = "all" +} diff --git a/pilot/thousandeyes-kiota/main.go b/pilot/thousandeyes-kiota/main.go new file mode 100644 index 00000000..07dcecca --- /dev/null +++ b/pilot/thousandeyes-kiota/main.go @@ -0,0 +1,39 @@ +// Command terraform-provider-thousandeyes serves the ThousandEyes provider. +// +// Most of this provider is generated by tfpfgen from the blueprints in the +// parent repository. Files carrying a "Code generated ... DO NOT EDIT." header are +// rewritten on every generation; change the blueprint and regenerate instead. +// Registry documentation is rendered from the provider schema plus the examples/ tree. +// The examples are scaffolds -- generated once, then owned -- and this directive is +// what CI re-runs to prove the committed docs/ match the schema they document. +// +//go:generate go tool tfplugindocs generate --provider-name thousandeyes --rendered-provider-name ThousandEyes + +package main + +import ( + "context" + "flag" + "log" + + "github.com/hashicorp/terraform-plugin-framework/providerserver" + + "github.com/deploymenttheory/terraform-plugin-framework-codegen/pilot/thousandeyes-kiota/internal/provider" +) + +// version is overridden at release time via -ldflags. +var version = "dev" + +func main() { + var debug bool + flag.BoolVar(&debug, "debug", false, "run with support for debuggers such as delve") + flag.Parse() + + err := providerserver.Serve(context.Background(), provider.New(version), providerserver.ServeOpts{ + Address: "registry.terraform.io/deploymenttheory/thousandeyes", + Debug: debug, + }) + if err != nil { + log.Fatalf("error: %v", err) + } +} diff --git a/pilot/thousandeyes/.tfpfgen/manifest.json b/pilot/thousandeyes/.tfpfgen/manifest.json index 61c5521f..fb92033e 100644 --- a/pilot/thousandeyes/.tfpfgen/manifest.json +++ b/pilot/thousandeyes/.tfpfgen/manifest.json @@ -204,7 +204,7 @@ }, { "path": "internal/services/resources/agent_to_agent_tests/v7/tests_agent_to_agent/construct.go", - "sha256": "0fd995518da26d0ff963d248e46eac9cb2f61b9c1a90825f6571c8dcfa949a63", + "sha256": "abc9ec344172d79f448c8cce71c3826da76838391243c5012d14551abc7d9ae9", "blueprint": "blueprints/thousandeyes" }, { @@ -234,7 +234,7 @@ }, { "path": "internal/services/resources/agent_to_server_tests/v7/tests_agent_to_server/construct.go", - "sha256": "3b050993fc213251cbdd490904c865215aaa8c7769c5f0f24b2e6763a4d4dfd8", + "sha256": "6e7654dbd622a879296f75ab5ac3174cd39d1ba7c588c66388f94d22c228521b", "blueprint": "blueprints/thousandeyes" }, { @@ -369,7 +369,7 @@ }, { "path": "internal/services/resources/api_tests/v7/tests_api/construct.go", - "sha256": "26b32edc35e88c9f61c88c5a6cdd4a75513691ac74a6a8cce3f1939547967670", + "sha256": "472564cdc087dd0c2e7542a53784ca70b761c11d63c78a21e32d135c9df99651", "blueprint": "blueprints/thousandeyes" }, { @@ -549,7 +549,7 @@ }, { "path": "internal/services/resources/dashboards_filters/v7/dashboard_filter/construct.go", - "sha256": "b38aec2ec399ce83ce76353eee876cfc4e111cd4d036317b34c8a12ddf53a793", + "sha256": "99f16e4550c3247fbeb46feb749dea2cb11d0759edd6f7c8854b36156c85c7cc", "blueprint": "blueprints/thousandeyes" }, { @@ -594,7 +594,7 @@ }, { "path": "internal/services/resources/dns_server_tests/v7/tests_dns_server/construct.go", - "sha256": "dcc0c58081824a353c1e46baaca8e454f63b53ca817b4606e89e3d3631f2b23d", + "sha256": "cefea4d3d3e5169ad577dabe748ea5fc490cb82f28879fdcf188c69baa9e2968", "blueprint": "blueprints/thousandeyes" }, { @@ -639,7 +639,7 @@ }, { "path": "internal/services/resources/dns_trace_tests/v7/tests_dns_trace/construct.go", - "sha256": "44686874b4128e18d1c82975f1ea7f0a33f0b87c02b30aca2b984ba43a546eed", + "sha256": "6198c3bacc770013b1aaaabb467e1e8c5d4651ce637a8a8bb9ff39f04885c993", "blueprint": "blueprints/thousandeyes" }, { @@ -684,7 +684,7 @@ }, { "path": "internal/services/resources/dnssec_tests/v7/tests_dnssec/construct.go", - "sha256": "933500cacc03159585217e0e23027fb3071af596223216cdf1980395fcf54fc5", + "sha256": "de8a6d3ba01f90533f21b88fcc29e88ba0cd86597f3761bbb4f093b3e8a34bb2", "blueprint": "blueprints/thousandeyes" }, { @@ -729,7 +729,7 @@ }, { "path": "internal/services/resources/endpoint_agent_labels/v7/endpoint_label/construct.go", - "sha256": "b6fbca8ea109a3369e0021605059d779e81dd2a82e24afbf6a89806f83fbe29e", + "sha256": "e0e7ab684be43c6f4127ceeb3835e9866e88b3d106684c39bf5d2643a380e50c", "blueprint": "blueprints/thousandeyes" }, { @@ -774,7 +774,7 @@ }, { "path": "internal/services/resources/ftp_server_tests/v7/tests_ftp_server/construct.go", - "sha256": "303ccc449592203d6d85f88d2367923e1ee0c787c6d9a84179319d984a56cf68", + "sha256": "f060f0a89bd0823c75e284d2996ead662a3f5ed88aa5d8ca7fc9e8d917828c0e", "blueprint": "blueprints/thousandeyes" }, { @@ -819,7 +819,7 @@ }, { "path": "internal/services/resources/http_server_tests/v7/tests_http_server/construct.go", - "sha256": "70219dfac4a5647e30c56ce08e1a06c7f04f52635407411bd83f2fbdcdc97041", + "sha256": "c20f9367e5ee31c0a56dc17ae233ee088f908e20f92f44b0ed695e91b98b85a5", "blueprint": "blueprints/thousandeyes" }, { @@ -864,7 +864,7 @@ }, { "path": "internal/services/resources/page_load_tests/v7/tests_page_load/construct.go", - "sha256": "ab22ba88d6db84c13b75b257abe5dfd2e967cb10d200aa741cd705166a138b50", + "sha256": "f4c97035aa45d660ce1530ccd8d832885d6bf50073a2ee28b1a693ddbf92aa1e", "blueprint": "blueprints/thousandeyes" }, { @@ -954,7 +954,7 @@ }, { "path": "internal/services/resources/sip_server_tests/v7/tests_sip_server/construct.go", - "sha256": "4b50febbf8287b0de17844283f2e9895a7f23eaf2a23aa50b6ed9f42712d89d1", + "sha256": "feb6defb2d1be541da66d79e61ab2426d38375edb55d77fd27e828aefaabcad8", "blueprint": "blueprints/thousandeyes" }, { @@ -1004,7 +1004,7 @@ }, { "path": "internal/services/resources/tags/v7/tag/construct.go", - "sha256": "03f1b7a751860d3533bf2cb7943b816d78e487eacd906d221543c58f8174c26d", + "sha256": "de81ae0264a4797cf8cc06ae739d1f3c1c5b4ea16aa3b60826495577e7d6fc91", "blueprint": "blueprints/thousandeyes" }, { @@ -1069,7 +1069,7 @@ }, { "path": "internal/services/resources/users/v7/user/construct.go", - "sha256": "97602ddd922da7925e8bc30faf1fd6d358417ac20310488ffcbb4a2be83062d2", + "sha256": "aa896611314b9d5abdacdca3b6a88b96af0c34d03c71b7245fcfb1c745525728", "blueprint": "blueprints/thousandeyes" }, { @@ -1114,7 +1114,7 @@ }, { "path": "internal/services/resources/voice_tests/v7/tests_voice/construct.go", - "sha256": "2945451baba24be66c13701683fa5f2b81998c3b967af3531ee033a081fd28b0", + "sha256": "c719645dc2bf51a4285ce2a3d556a0371177d09240e5b3b367a322357680477c", "blueprint": "blueprints/thousandeyes" }, { @@ -1144,7 +1144,7 @@ }, { "path": "internal/services/resources/web_transaction_tests/v7/tests_web_transaction/construct.go", - "sha256": "d38c187a5385f34aaa62d1322889a9272a97605fc5fbdd546107ca61a64d8661", + "sha256": "f977ce26f8630fada10d6fc7f6e12b1374d43834d9ba887fc04e940920fd754c", "blueprint": "blueprints/thousandeyes" }, { diff --git a/pilot/thousandeyes/internal/services/resources/agent_to_agent_tests/v7/tests_agent_to_agent/construct.go b/pilot/thousandeyes/internal/services/resources/agent_to_agent_tests/v7/tests_agent_to_agent/construct.go index 8f3a5e15..d7d72ec0 100644 --- a/pilot/thousandeyes/internal/services/resources/agent_to_agent_tests/v7/tests_agent_to_agent/construct.go +++ b/pilot/thousandeyes/internal/services/resources/agent_to_agent_tests/v7/tests_agent_to_agent/construct.go @@ -63,14 +63,14 @@ func expandAgentToAgentAgents(ctx context.Context, v types.Set) ([]agent_to_agen return nil, diags } - var models []AgentToAgentAgentModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []AgentToAgentAgentModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]agent_to_agent_tests.TestAgentRequest, 0, len(models)) - for _, m := range models { + out := make([]agent_to_agent_tests.TestAgentRequest, 0, len(elements)) + for _, m := range elements { var item agent_to_agent_tests.TestAgentRequest item.AgentID = convert.FrameworkToString(m.AgentID) diff --git a/pilot/thousandeyes/internal/services/resources/agent_to_server_tests/v7/tests_agent_to_server/construct.go b/pilot/thousandeyes/internal/services/resources/agent_to_server_tests/v7/tests_agent_to_server/construct.go index c10d477c..c7c22f05 100644 --- a/pilot/thousandeyes/internal/services/resources/agent_to_server_tests/v7/tests_agent_to_server/construct.go +++ b/pilot/thousandeyes/internal/services/resources/agent_to_server_tests/v7/tests_agent_to_server/construct.go @@ -65,14 +65,14 @@ func expandAgentToServerAgents(ctx context.Context, v types.Set) ([]agent_to_ser return nil, diags } - var models []AgentToServerAgentModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []AgentToServerAgentModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]agent_to_server_tests.TestAgentRequest, 0, len(models)) - for _, m := range models { + out := make([]agent_to_server_tests.TestAgentRequest, 0, len(elements)) + for _, m := range elements { var item agent_to_server_tests.TestAgentRequest item.AgentID = convert.FrameworkToString(m.AgentID) diff --git a/pilot/thousandeyes/internal/services/resources/api_tests/v7/tests_api/construct.go b/pilot/thousandeyes/internal/services/resources/api_tests/v7/tests_api/construct.go index c19ac5bb..01f25328 100644 --- a/pilot/thousandeyes/internal/services/resources/api_tests/v7/tests_api/construct.go +++ b/pilot/thousandeyes/internal/services/resources/api_tests/v7/tests_api/construct.go @@ -76,14 +76,14 @@ func expandTestsAPIAPIPredefinedVariables(ctx context.Context, v types.Set) ([]a return nil, diags } - var models []TestsAPIAPIPredefinedVariableModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []TestsAPIAPIPredefinedVariableModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]api_tests.ApiPredefinedVariable, 0, len(models)) - for _, m := range models { + out := make([]api_tests.ApiPredefinedVariable, 0, len(elements)) + for _, m := range elements { var item api_tests.ApiPredefinedVariable item.Name = convert.FrameworkToPtrString(m.Name) @@ -105,14 +105,14 @@ func expandTestsAPIAPIRequests(ctx context.Context, v types.Set) ([]api_tests.Ap return nil, diags } - var models []TestsAPIAPIRequestModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []TestsAPIAPIRequestModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]api_tests.ApiRequest, 0, len(models)) - for _, m := range models { + out := make([]api_tests.ApiRequest, 0, len(elements)) + for _, m := range elements { var item api_tests.ApiRequest item.Assertions, d = expandTestsAPIAPIRequestAssertions(ctx, m.Assertions) @@ -153,14 +153,14 @@ func expandTestsAPIAPIRequestAssertions(ctx context.Context, v types.Set) ([]api return nil, diags } - var models []TestsAPIAPIRequestAssertionModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []TestsAPIAPIRequestAssertionModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]api_tests.ApiRequestAssertion, 0, len(models)) - for _, m := range models { + out := make([]api_tests.ApiRequestAssertion, 0, len(elements)) + for _, m := range elements { var item api_tests.ApiRequestAssertion item.Name = convert.FrameworkToEnum[api_tests.ApiRequestAssertionName](m.Name) @@ -182,14 +182,14 @@ func expandTestsAPIAPIRequestHeaders(ctx context.Context, v types.Set) ([]api_te return nil, diags } - var models []TestsAPIAPIRequestHeaderModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []TestsAPIAPIRequestHeaderModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]api_tests.ApiRequestHeader, 0, len(models)) - for _, m := range models { + out := make([]api_tests.ApiRequestHeader, 0, len(elements)) + for _, m := range elements { var item api_tests.ApiRequestHeader item.Key = convert.FrameworkToPtrString(m.Key) @@ -210,14 +210,14 @@ func expandTestsAPIAPIRequestVariables(ctx context.Context, v types.Set) ([]api_ return nil, diags } - var models []TestsAPIAPIRequestVariableModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []TestsAPIAPIRequestVariableModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]api_tests.ApiRequestVariable, 0, len(models)) - for _, m := range models { + out := make([]api_tests.ApiRequestVariable, 0, len(elements)) + for _, m := range elements { var item api_tests.ApiRequestVariable item.Name = convert.FrameworkToPtrString(m.Name) @@ -238,14 +238,14 @@ func expandTestsAPITestVaultCredentials(ctx context.Context, v types.Set) ([]api return nil, diags } - var models []TestsAPITestVaultCredentialModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []TestsAPITestVaultCredentialModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]api_tests.TestVaultCredential, 0, len(models)) - for _, m := range models { + out := make([]api_tests.TestVaultCredential, 0, len(elements)) + for _, m := range elements { var item api_tests.TestVaultCredential item.SecretID = convert.FrameworkToPtrString(m.SecretID) @@ -266,14 +266,14 @@ func expandApiAgents(ctx context.Context, v types.Set) ([]api_tests.TestAgentReq return nil, diags } - var models []ApiAgentModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []ApiAgentModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]api_tests.TestAgentRequest, 0, len(models)) - for _, m := range models { + out := make([]api_tests.TestAgentRequest, 0, len(elements)) + for _, m := range elements { var item api_tests.TestAgentRequest item.AgentID = convert.FrameworkToString(m.AgentID) diff --git a/pilot/thousandeyes/internal/services/resources/dashboards_filters/v7/dashboard_filter/construct.go b/pilot/thousandeyes/internal/services/resources/dashboards_filters/v7/dashboard_filter/construct.go index d2823a8b..29c77eb2 100644 --- a/pilot/thousandeyes/internal/services/resources/dashboards_filters/v7/dashboard_filter/construct.go +++ b/pilot/thousandeyes/internal/services/resources/dashboards_filters/v7/dashboard_filter/construct.go @@ -47,14 +47,14 @@ func expandDashboardFilterApiContextFilterses(ctx context.Context, v types.Set) return nil, diags } - var models []DashboardFilterApiContextFiltersModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []DashboardFilterApiContextFiltersModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]dashboards_filters.ApiDataSourceFilters, 0, len(models)) - for _, m := range models { + out := make([]dashboards_filters.ApiDataSourceFilters, 0, len(elements)) + for _, m := range elements { var item dashboards_filters.ApiDataSourceFilters item.DataSourceID = convert.FrameworkToString(m.DataSourceID) @@ -77,14 +77,14 @@ func expandDashboardFilterApiDataSourceFilters(ctx context.Context, v types.Set) return nil, diags } - var models []DashboardFilterApiDataSourceFilterModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []DashboardFilterApiDataSourceFilterModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]dashboards_filters.ApiDataSourceFilter, 0, len(models)) - for _, m := range models { + out := make([]dashboards_filters.ApiDataSourceFilter, 0, len(elements)) + for _, m := range elements { var item dashboards_filters.ApiDataSourceFilter item.FilterID = convert.FrameworkToString(m.FilterID) diff --git a/pilot/thousandeyes/internal/services/resources/dns_server_tests/v7/tests_dns_server/construct.go b/pilot/thousandeyes/internal/services/resources/dns_server_tests/v7/tests_dns_server/construct.go index 9ac98381..46a45d02 100644 --- a/pilot/thousandeyes/internal/services/resources/dns_server_tests/v7/tests_dns_server/construct.go +++ b/pilot/thousandeyes/internal/services/resources/dns_server_tests/v7/tests_dns_server/construct.go @@ -65,14 +65,14 @@ func expandDnsServerAgents(ctx context.Context, v types.Set) ([]dns_server_tests return nil, diags } - var models []DnsServerAgentModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []DnsServerAgentModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]dns_server_tests.TestAgentRequest, 0, len(models)) - for _, m := range models { + out := make([]dns_server_tests.TestAgentRequest, 0, len(elements)) + for _, m := range elements { var item dns_server_tests.TestAgentRequest item.AgentID = convert.FrameworkToString(m.AgentID) diff --git a/pilot/thousandeyes/internal/services/resources/dns_trace_tests/v7/tests_dns_trace/construct.go b/pilot/thousandeyes/internal/services/resources/dns_trace_tests/v7/tests_dns_trace/construct.go index cf2074bd..80e7be1c 100644 --- a/pilot/thousandeyes/internal/services/resources/dns_trace_tests/v7/tests_dns_trace/construct.go +++ b/pilot/thousandeyes/internal/services/resources/dns_trace_tests/v7/tests_dns_trace/construct.go @@ -52,14 +52,14 @@ func expandDnsTraceAgents(ctx context.Context, v types.Set) ([]dns_trace_tests.T return nil, diags } - var models []DnsTraceAgentModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []DnsTraceAgentModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]dns_trace_tests.TestAgentRequest, 0, len(models)) - for _, m := range models { + out := make([]dns_trace_tests.TestAgentRequest, 0, len(elements)) + for _, m := range elements { var item dns_trace_tests.TestAgentRequest item.AgentID = convert.FrameworkToString(m.AgentID) diff --git a/pilot/thousandeyes/internal/services/resources/dnssec_tests/v7/tests_dnssec/construct.go b/pilot/thousandeyes/internal/services/resources/dnssec_tests/v7/tests_dnssec/construct.go index 8c9d86ad..ec91ad98 100644 --- a/pilot/thousandeyes/internal/services/resources/dnssec_tests/v7/tests_dnssec/construct.go +++ b/pilot/thousandeyes/internal/services/resources/dnssec_tests/v7/tests_dnssec/construct.go @@ -51,14 +51,14 @@ func expandDnssecAgents(ctx context.Context, v types.Set) ([]dnssec_tests.TestAg return nil, diags } - var models []DnssecAgentModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []DnssecAgentModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]dnssec_tests.TestAgentRequest, 0, len(models)) - for _, m := range models { + out := make([]dnssec_tests.TestAgentRequest, 0, len(elements)) + for _, m := range elements { var item dnssec_tests.TestAgentRequest item.AgentID = convert.FrameworkToString(m.AgentID) diff --git a/pilot/thousandeyes/internal/services/resources/endpoint_agent_labels/v7/endpoint_label/construct.go b/pilot/thousandeyes/internal/services/resources/endpoint_agent_labels/v7/endpoint_label/construct.go index 1c75a374..6600515d 100644 --- a/pilot/thousandeyes/internal/services/resources/endpoint_agent_labels/v7/endpoint_label/construct.go +++ b/pilot/thousandeyes/internal/services/resources/endpoint_agent_labels/v7/endpoint_label/construct.go @@ -71,14 +71,14 @@ func expandEndpointLabelFilters(ctx context.Context, v types.Set) ([]endpoint_ag return nil, diags } - var models []EndpointLabelFilterModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []EndpointLabelFilterModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]endpoint_agent_labels.Filter, 0, len(models)) - for _, m := range models { + out := make([]endpoint_agent_labels.Filter, 0, len(elements)) + for _, m := range elements { var item endpoint_agent_labels.Filter item.Key = convert.FrameworkToEnum[endpoint_agent_labels.FilterType](m.Key) diff --git a/pilot/thousandeyes/internal/services/resources/ftp_server_tests/v7/tests_ftp_server/construct.go b/pilot/thousandeyes/internal/services/resources/ftp_server_tests/v7/tests_ftp_server/construct.go index e602a23a..ad216932 100644 --- a/pilot/thousandeyes/internal/services/resources/ftp_server_tests/v7/tests_ftp_server/construct.go +++ b/pilot/thousandeyes/internal/services/resources/ftp_server_tests/v7/tests_ftp_server/construct.go @@ -68,14 +68,14 @@ func expandFtpServerAgents(ctx context.Context, v types.Set) ([]ftp_server_tests return nil, diags } - var models []FtpServerAgentModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []FtpServerAgentModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]ftp_server_tests.TestAgentRequest, 0, len(models)) - for _, m := range models { + out := make([]ftp_server_tests.TestAgentRequest, 0, len(elements)) + for _, m := range elements { var item ftp_server_tests.TestAgentRequest item.AgentID = convert.FrameworkToString(m.AgentID) diff --git a/pilot/thousandeyes/internal/services/resources/http_server_tests/v7/tests_http_server/construct.go b/pilot/thousandeyes/internal/services/resources/http_server_tests/v7/tests_http_server/construct.go index b188043c..4b35ba41 100644 --- a/pilot/thousandeyes/internal/services/resources/http_server_tests/v7/tests_http_server/construct.go +++ b/pilot/thousandeyes/internal/services/resources/http_server_tests/v7/tests_http_server/construct.go @@ -88,14 +88,14 @@ func expandTestsHTTPServerTestVaultCredentials(ctx context.Context, v types.Set) return nil, diags } - var models []TestsHTTPServerTestVaultCredentialModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []TestsHTTPServerTestVaultCredentialModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]http_server_tests.TestVaultCredential, 0, len(models)) - for _, m := range models { + out := make([]http_server_tests.TestVaultCredential, 0, len(elements)) + for _, m := range elements { var item http_server_tests.TestVaultCredential item.SecretID = convert.FrameworkToPtrString(m.SecretID) @@ -116,14 +116,14 @@ func expandHttpServerAgents(ctx context.Context, v types.Set) ([]http_server_tes return nil, diags } - var models []HttpServerAgentModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []HttpServerAgentModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]http_server_tests.TestAgentRequest, 0, len(models)) - for _, m := range models { + out := make([]http_server_tests.TestAgentRequest, 0, len(elements)) + for _, m := range elements { var item http_server_tests.TestAgentRequest item.AgentID = convert.FrameworkToString(m.AgentID) diff --git a/pilot/thousandeyes/internal/services/resources/page_load_tests/v7/tests_page_load/construct.go b/pilot/thousandeyes/internal/services/resources/page_load_tests/v7/tests_page_load/construct.go index 59ccf301..24959360 100644 --- a/pilot/thousandeyes/internal/services/resources/page_load_tests/v7/tests_page_load/construct.go +++ b/pilot/thousandeyes/internal/services/resources/page_load_tests/v7/tests_page_load/construct.go @@ -96,14 +96,14 @@ func expandTestsPageLoadTestVaultCredentials(ctx context.Context, v types.Set) ( return nil, diags } - var models []TestsPageLoadTestVaultCredentialModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []TestsPageLoadTestVaultCredentialModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]page_load_tests.TestVaultCredential, 0, len(models)) - for _, m := range models { + out := make([]page_load_tests.TestVaultCredential, 0, len(elements)) + for _, m := range elements { var item page_load_tests.TestVaultCredential item.SecretID = convert.FrameworkToPtrString(m.SecretID) @@ -124,14 +124,14 @@ func expandPageLoadAgents(ctx context.Context, v types.Set) ([]page_load_tests.T return nil, diags } - var models []PageLoadAgentModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []PageLoadAgentModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]page_load_tests.TestAgentRequest, 0, len(models)) - for _, m := range models { + out := make([]page_load_tests.TestAgentRequest, 0, len(elements)) + for _, m := range elements { var item page_load_tests.TestAgentRequest item.AgentID = convert.FrameworkToString(m.AgentID) diff --git a/pilot/thousandeyes/internal/services/resources/sip_server_tests/v7/tests_sip_server/construct.go b/pilot/thousandeyes/internal/services/resources/sip_server_tests/v7/tests_sip_server/construct.go index 9dd88d53..0453108e 100644 --- a/pilot/thousandeyes/internal/services/resources/sip_server_tests/v7/tests_sip_server/construct.go +++ b/pilot/thousandeyes/internal/services/resources/sip_server_tests/v7/tests_sip_server/construct.go @@ -62,14 +62,14 @@ func expandSipServerAgents(ctx context.Context, v types.Set) ([]sip_server_tests return nil, diags } - var models []SipServerAgentModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []SipServerAgentModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]sip_server_tests.TestAgentRequest, 0, len(models)) - for _, m := range models { + out := make([]sip_server_tests.TestAgentRequest, 0, len(elements)) + for _, m := range elements { var item sip_server_tests.TestAgentRequest item.AgentID = convert.FrameworkToString(m.AgentID) diff --git a/pilot/thousandeyes/internal/services/resources/tags/v7/tag/construct.go b/pilot/thousandeyes/internal/services/resources/tags/v7/tag/construct.go index 158aaca9..e3bda9b2 100644 --- a/pilot/thousandeyes/internal/services/resources/tags/v7/tag/construct.go +++ b/pilot/thousandeyes/internal/services/resources/tags/v7/tag/construct.go @@ -53,14 +53,14 @@ func expandTagFilters(ctx context.Context, v types.Set) ([]tags.TagFilter, diag. return nil, diags } - var models []TagFilterModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []TagFilterModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]tags.TagFilter, 0, len(models)) - for _, m := range models { + out := make([]tags.TagFilter, 0, len(elements)) + for _, m := range elements { var item tags.TagFilter item.Key = convert.FrameworkToPtrString(m.Key) diff --git a/pilot/thousandeyes/internal/services/resources/users/v7/user/construct.go b/pilot/thousandeyes/internal/services/resources/users/v7/user/construct.go index 9536740e..a18fbd53 100644 --- a/pilot/thousandeyes/internal/services/resources/users/v7/user/construct.go +++ b/pilot/thousandeyes/internal/services/resources/users/v7/user/construct.go @@ -49,14 +49,14 @@ func expandUserAccountGroupRoles(ctx context.Context, v types.Set) ([]users.User return nil, diags } - var models []UserAccountGroupRoleModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []UserAccountGroupRoleModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]users.UserAccountGroupRole, 0, len(models)) - for _, m := range models { + out := make([]users.UserAccountGroupRole, 0, len(elements)) + for _, m := range elements { var item users.UserAccountGroupRole item.AccountGroupID = convert.FrameworkToPtrString(m.AccountGroupID) diff --git a/pilot/thousandeyes/internal/services/resources/voice_tests/v7/tests_voice/construct.go b/pilot/thousandeyes/internal/services/resources/voice_tests/v7/tests_voice/construct.go index f85a4e03..1e75912b 100644 --- a/pilot/thousandeyes/internal/services/resources/voice_tests/v7/tests_voice/construct.go +++ b/pilot/thousandeyes/internal/services/resources/voice_tests/v7/tests_voice/construct.go @@ -58,14 +58,14 @@ func expandVoiceAgents(ctx context.Context, v types.Set) ([]voice_tests.TestAgen return nil, diags } - var models []VoiceAgentModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []VoiceAgentModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]voice_tests.TestAgentRequest, 0, len(models)) - for _, m := range models { + out := make([]voice_tests.TestAgentRequest, 0, len(elements)) + for _, m := range elements { var item voice_tests.TestAgentRequest item.AgentID = convert.FrameworkToString(m.AgentID) diff --git a/pilot/thousandeyes/internal/services/resources/web_transaction_tests/v7/tests_web_transaction/construct.go b/pilot/thousandeyes/internal/services/resources/web_transaction_tests/v7/tests_web_transaction/construct.go index 841cd8c1..fb94c581 100644 --- a/pilot/thousandeyes/internal/services/resources/web_transaction_tests/v7/tests_web_transaction/construct.go +++ b/pilot/thousandeyes/internal/services/resources/web_transaction_tests/v7/tests_web_transaction/construct.go @@ -98,14 +98,14 @@ func expandTestsWebTransactionTestVaultCredentials(ctx context.Context, v types. return nil, diags } - var models []TestsWebTransactionTestVaultCredentialModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []TestsWebTransactionTestVaultCredentialModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]web_transaction_tests.TestVaultCredential, 0, len(models)) - for _, m := range models { + out := make([]web_transaction_tests.TestVaultCredential, 0, len(elements)) + for _, m := range elements { var item web_transaction_tests.TestVaultCredential item.SecretID = convert.FrameworkToPtrString(m.SecretID) @@ -126,14 +126,14 @@ func expandWebTransactionAgents(ctx context.Context, v types.Set) ([]web_transac return nil, diags } - var models []WebTransactionAgentModel - diags.Append(v.ElementsAs(ctx, &models, false)...) + var elements []WebTransactionAgentModel + diags.Append(v.ElementsAs(ctx, &elements, false)...) if diags.HasError() { return nil, diags } - out := make([]web_transaction_tests.TestAgentRequest, 0, len(models)) - for _, m := range models { + out := make([]web_transaction_tests.TestAgentRequest, 0, len(elements)) + for _, m := range elements { var item web_transaction_tests.TestAgentRequest item.AgentID = convert.FrameworkToString(m.AgentID) diff --git a/tfpfgen b/tfpfgen new file mode 100755 index 00000000..1051b47d Binary files /dev/null and b/tfpfgen differ