diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index deecb6e13..8c7d4ba4a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,7 +26,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v2 with: - go-version: "1.20" + go-version: "1.21" - name: Import GPG key id: import_gpg uses: crazy-max/ghaction-import-gpg@v5 @@ -41,7 +41,7 @@ jobs: - name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 with: - version: v1.20.0 + version: v1.21.0 args: release --rm-dist env: GPG_FINGERPRINT: ${{ steps.import_gpg.outputs.fingerprint }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 0367433f8..8b555a894 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,82 @@ # RELEASE NOTES +## 5.6.0 (Feb 19, 2024) + +#### FEATURES/ENHANCEMENTS: + +* Appsec + * Added retries in `akamai_appsec_activations` and `akamai_networklist_activations` resources ([I#471](https://github.com/akamai/terraform-provider-akamai/issues/471)) + * Added reactivation support for `akamai_appsec_activations` if the config was deactivated manually ([I#441](https://github.com/akamai/terraform-provider-akamai/issues/441) and [I#442](https://github.com/akamai/terraform-provider-akamai/issues/442)) + +* Cloudlets + * Added support for Shared Cloudlets Policies. To use it, provide `is_shared` field in `akamai_cloudlets_policy` resource as `true`. ([I#276](https://github.com/akamai/terraform-provider-akamai/issues/276)) + * Added validation to prevent changing immutable `cloudlet_code` field in `akamai_cloudlets_policy` resource + * Added support for importing policies without any version + * Added new data source: + * `akamai_cloudlets_policy_activation` - read + * `akamai_cloudlets_shared_policy` - read + * Changes for `akamai_cloudlets_policy_activation` resource + * Added support for shared (V3) policies + * Added import for `akamai_cloudlets_policy_activation` + * Field `associated_properties` was changed to optional but is still required for non-shared policies + * Added `is_shared` computed field to indicate if processing policy is shared + +* DNS + * Enhanced handling of `akamai_dns_zone` resource when no `group` is provided: + * When there is only one group present, the processing should continue with a descriptive warning + * When there are more than one group present, the processing will fail with descriptive error asking to provide group in the configuration + +* Edgeworkers + * Added `note` attribute to `resource_akamai_edgeworkers_activation` resource + +* GTM + * Added data sources: + * `akamai_gtm_asmap` - reads information for a specific GTM asmap + * `akamai_gtm_resources` - reads information for a specific GTM resources under given domain + * `akamai_gtm_resource` - reads information for a specific GTM resource + * `akamai_gtm_domain` - reads information for a specific GTM domain + * `akamai_gtm_domains` - reads list of GTM domains under a given contract + * `akamai_gtm_cidrmap` - reads information for a specific GTM cidrmap + +* IVM + * Extended `akamai_imaging_policy_image` with new fields: + * `serve_stale_duration` available under `policy` + * `allow_pristine_on_downsize` and `prefer_modern_formats` available under `policy.output` + +* PAPI + * Added new resource: + * `akamai_property_bootstrap` - create, read, update and delete property without specifying rules or edgehostnames. To be used with `akamai_property` resource and its new field `property_id` ([I#466](https://github.com/akamai/terraform-provider-akamai/issues/466)) + * Added `version_notes`, `rule_warnings` and `property_id` attributes to `akamai_property` resource ([I#494](https://github.com/akamai/terraform-provider-akamai/issues/494)) + * Added support for new rule format v2024-01-09 in `data_akamai_property_rules_builder` + * Improved errors for `akamai_contract` and `akamai_group` datasources when there are multiple groups or contracts + * Added `name` validation for `akamai_property_include` resource + +* Updated various dependencies + +#### BUG FIXES: + +* Appsec + * Fixed provider plugin crash in `appsec_attack_group` and `appsec_eval_group` after executing terraform plan ([I#480](https://github.com/akamai/terraform-provider-akamai/issues/480)) + * Fixed drift for struct and list reordering in `akamai_appsec_match_target` + +* Cloudlets + * Fixed handling of version drift for cloudlets policies ([I#478](https://github.com/akamai/terraform-provider-akamai/issues/478)) + +* CPS + * Changed `organizational_unit` inside `csr` attribute for `akamai_cps_third_party_enrollment` and `akamai_cps_dv_enrollment` + resources from required to optional. ([PR#513](https://github.com/akamai/terraform-provider-akamai/pull/513)) + * Changed `state` inside `csr` attribute for `akamai_cps_third_party_enrollment` and `akamai_cps_dv_enrollment` resources from required to optional. + +* GTM + * Fixed 'Inconsistent Final Plan' error for `akamai_gtm_property` resource + * The diff when reordering `traffic_target` in `akamai_gtm_property` resource at the same time as changing any attribute value inside `traffic_target` will be extensive + * Added `ForceNew` to the `name` attribute for `akamai_gtm_property` resource as it is not possible to rename it using API + ## 5.5.0 (Dec 07, 2023) #### FEATURES/ENHANCEMENTS: -* APSSEC +* Appsec * Updated resource: * `akamai_appsec_ip_geo` - added `asn_network_lists` attribute to support blocking by ASN client lists * Updated data source: diff --git a/GNUmakefile b/GNUmakefile index 9e1fe5839..fe152216c 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -10,23 +10,31 @@ build_dir = .build TF_PLUGIN_DIR ?= ~/.terraform.d/plugins install_path = $(TF_PLUGIN_DIR)/$(registry_name)/$(namespace)/$(PKG_NAME)/$(version)/$$(go env GOOS)_$$(go env GOARCH) -# Developer tools -TOOLS_MOD_FILE := $(CURDIR)/tools/go.mod -TOOLS_BIN_DIR := $(CURDIR)/tools/bin -TOOL_PKGS := $(shell go list -f '{{join .Imports " "}}' tools/tools.go) -TOOLS := $(foreach TOOL,$(notdir $(TOOL_PKGS)),$(TOOLS_BIN_DIR)/$(TOOL)) -$(foreach TOOL,$(TOOLS),$(eval $(notdir $(TOOL)) := $(TOOL))) # Allows to use e.g. $(golangci-lint) instead of $(TOOLS_BIN_DIR)/golangci-lint - -$(TOOLS_BIN_DIR): - @mkdir -p $(TOOLS_BIN_DIR) - -$(TOOLS_MOD_FILE): tidy - -$(TOOLS): $(TOOLS_MOD_FILE) | $(TOOLS_BIN_DIR) - $(eval TOOL := $(filter %/$(@F),$(TOOL_PKGS))) - $(eval TOOL_VERSION := $(shell grep -m 1 $(shell echo $(TOOL) | cut -d/ -f 1-3) $(TOOLS_MOD_FILE) | cut -d' ' -f2)) - @echo "Installing $(TOOL)@$(TOOL_VERSION)" - @GOBIN=$(TOOLS_BIN_DIR) go install -modfile=$(TOOLS_MOD_FILE) $(TOOL) +BIN = $(CURDIR)/bin +GOCMD = go +GOTEST = $(GOCMD) test +GOBUILD = $(GOCMD) build +M = $(shell echo ">") +TFLINT = $(BIN)/tflint +$(BIN)/tflint: $(BIN) ; $(info $(M) Installing tflint...) + @export TFLINT_INSTALL_PATH=$(BIN); \ + curl -sSfL https://raw.githubusercontent.com/terraform-linters/tflint/master/install_linux.sh | bash + +$(BIN): + @mkdir -p $@ +$(BIN)/%: | $(BIN) ; $(info $(M) Installing $(PACKAGE)...) + @tmp=$$(mktemp -d); \ + env GO111MODULE=off GOPATH=$$tmp GOBIN=$(BIN) $(GOCMD) get $(PACKAGE) \ + || ret=$$?; \ + rm -rf $$tmp ; exit $$ret + +GOIMPORTS = $(BIN)/goimports +$(BIN)/goimports: PACKAGE=golang.org/x/tools/cmd/goimports + +GOLANGCI_LINT_VERSION = v1.55.2 +GOLANGCILINT = $(BIN)/golangci-lint +$(BIN)/golangci-lint: ; $(info $(M) Installing golangci-lint...) @ + $Q curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(BIN) $(GOLANGCI_LINT_VERSION) # Targets default: build @@ -55,8 +63,17 @@ testacc: TF_ACC=1 go test $(TEST) -v $(TESTARGS) -timeout 300m .PHONY: fmt -fmt: $(goimports) - $(goimports) -w . +fmt: | $(GOIMPORTS); $(info $(M) Running goimports...) @ ## Run goimports on all source files + $Q $(GOIMPORTS) -w . + +.PHONY: fmt-check +fmt-check: | $(GOIMPORTS); $(info $(M) Running format and imports check...) @ ## Run goimports on all source files + $(eval OUTPUT = $(shell $(GOIMPORTS) -l .)) + @if [ "$(OUTPUT)" != "" ]; then\ + echo "Found following files with incorrect format and/or imports:";\ + echo "$(OUTPUT)";\ + false;\ + fi .PHONY: terraform-fmtcheck terraform-fmtcheck: @@ -67,12 +84,13 @@ terraform-fmt: terraform fmt -recursive .PHONY: lint -lint: $(golangci-lint) - $(golangci-lint) run +lint: | $(GOLANGCILINT) ; $(info $(M) Running golangci-lint...) @ + go mod tidy + $Q $(BIN)/golangci-lint run .PHONY: terraform-lint -terraform-lint: $(tflint) - @find ./examples -type f -name "*.tf" | xargs -I % dirname % | sort -u | xargs -I @ sh -c "echo @ && $(tflint) @" +terraform-lint: | $(TFLINT) ; $(info $(M) Checking source code against tflint...) @ ## Run tflint on all HCL files in the project + @find ./examples -type f -name "*.tf" | xargs -I % dirname % | sort -u | xargs -I @ sh -c "echo @ && $(TFLINT) --filter @" .PHONY: test-compile test-compile: diff --git a/build/internal/package/Dockerfile b/build/internal/package/Dockerfile index 3e89bdcb3..fe048b6c0 100644 --- a/build/internal/package/Dockerfile +++ b/build/internal/package/Dockerfile @@ -1,4 +1,4 @@ -FROM golang:1.20-alpine3.18 +FROM golang:1.21-alpine3.18 ENV PROVIDER_VERSION="1.0.0" \ CGO_ENABLED=0 \ diff --git a/docs/data-sources/data-sources.md b/docs/data-sources/data-sources.md index 290cff761..119f576f9 100644 --- a/docs/data-sources/data-sources.md +++ b/docs/data-sources/data-sources.md @@ -8,17 +8,17 @@ We’ve moved our documentation to the Akamai TechDocs site. Use the table to fi | Subprovider | Description | |---------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------| -| [Application Security](https://techdocs.akamai.com/terraform/v5.5/docs/appsec-datasources) | Manage security configurations, security policies, match targets, rate policies, and firewall rules. | -| [Bot Manager](https://techdocs.akamai.com/terraform/v5.5/docs/botman-datasources) | Identify, track, and respond to bot activity on your domain or in your app. | -| [Certificates](https://techdocs.akamai.com/terraform/v5.5/docs/cps-datasources) | Full life cycle management of SSL certificates for your ​Akamai​ CDN applications. | -| [Client Lists](https://techdocs.akamai.com/terraform/v5.5/docs/cli-data-sources) | Reduce harmful security attacks by allowing only trusted IP/CIDRs, locations, autonomous system numbers, and TLS fingerprints to access your services and content.| -| [Cloud Wrapper](https://techdocs.akamai.com/terraform/v5.5/docs/cw-data-sources) | Provide your customers with a more consistent user experience by adding a custom caching layer that improves the connection between your cloud infrastructure and the Akamai platform.| -| [Cloudlets](https://techdocs.akamai.com/terraform/v5.5/docs/cl-datasources) | Solve specific business challenges using value-added apps that complement ​Akamai​'s core solutions. | -| [DataStream](https://techdocs.akamai.com/terraform/v5.5/docs/ds-datasources) | Monitor activity on the ​Akamai​ platform and send live log data to a destination of your choice. | -| [Edge DNS](https://techdocs.akamai.com/terraform/v5.5/docs/edns-datasources) | Replace or augment your DNS infrastructure with a cloud-based authoritative DNS solution. | -| [EdgeWorkers](https://techdocs.akamai.com/terraform/v5.5/docs/ew-datasources) | Execute JavaScript functions at the edge to optimize site performance and customize web experiences. | -| [Global Traffic Management](https://techdocs.akamai.com/terraform/v5.5/docs/gtm-datasources) | Use load balancing to manage website and mobile performance demands. | -| [Identity and Access Management](https://techdocs.akamai.com/terraform/v5.5/docs/iam-datasources) | Create users and groups, and define policies that manage access to your Akamai applications. | -| [Image and Video Manager](https://techdocs.akamai.com/terraform/v5.5/docs/ivm-datasources) | Automate image and video delivery optimizations for your website visitors. | -| [Network Lists](https://techdocs.akamai.com/terraform/v5.5/docs/nl-datasources) | Automate the creation, deployment, and management of lists used in ​Akamai​ security products. | -| [Property](https://techdocs.akamai.com/terraform/v5.5/docs/pm-datasources) | Define rules and behaviors that govern your website delivery based on match criteria. | +| [Application Security](https://techdocs.akamai.com/terraform/v5.6/docs/appsec-datasources) | Manage security configurations, security policies, match targets, rate policies, and firewall rules. | +| [Bot Manager](https://techdocs.akamai.com/terraform/v5.6/docs/botman-datasources) | Identify, track, and respond to bot activity on your domain or in your app. | +| [Certificates](https://techdocs.akamai.com/terraform/v5.6/docs/cps-datasources) | Full life cycle management of SSL certificates for your ​Akamai​ CDN applications. | +| [Client Lists](https://techdocs.akamai.com/terraform/v5.6/docs/cli-data-sources) | Reduce harmful security attacks by allowing only trusted IP/CIDRs, locations, autonomous system numbers, and TLS fingerprints to access your services and content.| +| [Cloud Wrapper](https://techdocs.akamai.com/terraform/v5.6/docs/cw-data-sources) | Provide your customers with a more consistent user experience by adding a custom caching layer that improves the connection between your cloud infrastructure and the Akamai platform.| +| [Cloudlets](https://techdocs.akamai.com/terraform/v5.6/docs/cl-datasources) | Solve specific business challenges using value-added apps that complement ​Akamai​'s core solutions. | +| [DataStream](https://techdocs.akamai.com/terraform/v5.6/docs/ds-datasources) | Monitor activity on the ​Akamai​ platform and send live log data to a destination of your choice. | +| [Edge DNS](https://techdocs.akamai.com/terraform/v5.6/docs/edns-datasources) | Replace or augment your DNS infrastructure with a cloud-based authoritative DNS solution. | +| [EdgeWorkers](https://techdocs.akamai.com/terraform/v5.6/docs/ew-datasources) | Execute JavaScript functions at the edge to optimize site performance and customize web experiences. | +| [Global Traffic Management](https://techdocs.akamai.com/terraform/v5.6/docs/gtm-datasources) | Use load balancing to manage website and mobile performance demands. | +| [Identity and Access Management](https://techdocs.akamai.com/terraform/v5.6/docs/iam-datasources) | Create users and groups, and define policies that manage access to your Akamai applications. | +| [Image and Video Manager](https://techdocs.akamai.com/terraform/v5.6/docs/ivm-datasources) | Automate image and video delivery optimizations for your website visitors. | +| [Network Lists](https://techdocs.akamai.com/terraform/v5.6/docs/nl-datasources) | Automate the creation, deployment, and management of lists used in ​Akamai​ security products. | +| [Property](https://techdocs.akamai.com/terraform/v5.6/docs/pm-datasources) | Define rules and behaviors that govern your website delivery based on match criteria. | diff --git a/docs/guides/get-started.md b/docs/guides/get-started.md index 23e12e13b..c53875fbb 100644 --- a/docs/guides/get-started.md +++ b/docs/guides/get-started.md @@ -21,7 +21,7 @@ Your Akamai Terraform configuration starts with listing us as a required provide required_providers { akamai = { source = "akamai/akamai" - version = "5.2.0" + version = "5.6.0" } } } @@ -99,20 +99,20 @@ Use the table to find information about the subprovider you’re using. | Subprovider | Description | |----------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------| -| [Application Security](https://techdocs.akamai.com/terraform/v5.5/docs/configure-appsec) | Manage security configurations, security policies, match targets, rate policies, and firewall rules. | -| [Bot Manager](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-botman) | Identify, track, and respond to bot activity on your domain or in your app. | -| [Certificates](https://techdocs.akamai.com/terraform/v5.5/docs/cps-integration-guide) | Full life cycle management of SSL certificates for your ​Akamai​ CDN applications. | -| [Client Lists](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-client-lists) | Reduce harmful security attacks by allowing only trusted IP/CIDRs, locations, autonomous system numbers, and TLS fingerprints to access your services and content.| -| [Cloud Wrapper](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-cloud-wrapper) | Provide your customers with a more consistent user experience by adding a custom caching layer that improves the connection between your cloud infrastructure and the Akamai platform.| -| [Cloudlets](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-cloudlets) | Solve specific business challenges using value-added apps that complement ​Akamai​'s core solutions. | -| [DataStream](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-datastream) | Monitor activity on the ​Akamai​ platform and send live log data to a destination of your choice. | -| [Edge DNS](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-edgedns) | Replace or augment your DNS infrastructure with a cloud-based authoritative DNS solution. | -| [EdgeWorkers](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-edgeworkers) | Execute JavaScript functions at the edge to optimize site performance and customize web experiences. | -| [Global Traffic Management](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-gtm) | Use load balancing to manage website and mobile performance demands. | -| [Identity and Access Management](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-iam) | Create users and groups, and define policies that manage access to your Akamai applications. | -| [Image and Video Manager](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-ivm) | Automate image and video delivery optimizations for your website visitors. | -| [Network Lists](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-network-lists) | Automate the creation, deployment, and management of lists used in ​Akamai​ security products. | -| [Property](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-property-provisioning) | Define rules and behaviors that govern your website delivery based on match criteria. | +| [Application Security](https://techdocs.akamai.com/terraform/v5.6/docs/configure-appsec) | Manage security configurations, security policies, match targets, rate policies, and firewall rules. | +| [Bot Manager](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-botman) | Identify, track, and respond to bot activity on your domain or in your app. | +| [Certificates](https://techdocs.akamai.com/terraform/v5.6/docs/cps-integration-guide) | Full life cycle management of SSL certificates for your ​Akamai​ CDN applications. | +| [Client Lists](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-client-lists) | Reduce harmful security attacks by allowing only trusted IP/CIDRs, locations, autonomous system numbers, and TLS fingerprints to access your services and content.| +| [Cloud Wrapper](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-cloud-wrapper) | Provide your customers with a more consistent user experience by adding a custom caching layer that improves the connection between your cloud infrastructure and the Akamai platform.| +| [Cloudlets](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-cloudlets) | Solve specific business challenges using value-added apps that complement ​Akamai​'s core solutions. | +| [DataStream](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-datastream) | Monitor activity on the ​Akamai​ platform and send live log data to a destination of your choice. | +| [Edge DNS](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-edgedns) | Replace or augment your DNS infrastructure with a cloud-based authoritative DNS solution. | +| [EdgeWorkers](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-edgeworkers) | Execute JavaScript functions at the edge to optimize site performance and customize web experiences. | +| [Global Traffic Management](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-gtm) | Use load balancing to manage website and mobile performance demands. | +| [Identity and Access Management](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-iam) | Create users and groups, and define policies that manage access to your Akamai applications. | +| [Image and Video Manager](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-ivm) | Automate image and video delivery optimizations for your website visitors. | +| [Network Lists](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-network-lists) | Automate the creation, deployment, and management of lists used in ​Akamai​ security products. | +| [Property](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-property-provisioning) | Define rules and behaviors that govern your website delivery based on match criteria. | ### Get contract and group IDs diff --git a/docs/index.md b/docs/index.md index ae8e6e7c0..f37ba3c93 100644 --- a/docs/index.md +++ b/docs/index.md @@ -35,20 +35,20 @@ We’ve moved our documentation to the Akamai TechDocs site. Use the table to fi | Subprovider | Description | |----------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------| -| [Application Security](https://techdocs.akamai.com/terraform/v5.5/docs/configure-appsec) | Manage security configurations, security policies, match targets, rate policies, and firewall rules. | -| [Bot Manager](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-botman) | Identify, track, and respond to bot activity on your domain or in your app. | -| [Certificates](https://techdocs.akamai.com/terraform/v5.5/docs/cps-integration-guide) | Full life cycle management of SSL certificates for your ​Akamai​ CDN applications. | -| [Client Lists](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-client-lists) | Reduce harmful security attacks by allowing only trusted IP/CIDRs, locations, autonomous system numbers, and TLS fingerprints to access your services and content.| -| [Cloud Wrapper](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-cloud-wrapper) | Provide your customers with a more consistent user experience by adding a custom caching layer that improves the connection between your cloud infrastructure and the Akamai platform.| -| [Cloudlets](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-cloudlets) | Solve specific business challenges using value-added apps that complement ​Akamai​'s core solutions. | -| [DataStream](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-datastream) | Monitor activity on the ​Akamai​ platform and send live log data to a destination of your choice. | -| [Edge DNS](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-edgedns) | Replace or augment your DNS infrastructure with a cloud-based authoritative DNS solution. | -| [EdgeWorkers](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-edgeworkers) | Execute JavaScript functions at the edge to optimize site performance and customize web experiences. | -| [Global Traffic Management](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-gtm) | Use load balancing to manage website and mobile performance demands. | -| [Identity and Access Management](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-iam) | Create users and groups, and define policies that manage access to your Akamai applications. | -| [Image and Video Manager](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-ivm) | Automate image and video delivery optimizations for your website visitors. | -| [Network Lists](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-network-lists) | Automate the creation, deployment, and management of lists used in ​Akamai​ security products. | -| [Property](https://techdocs.akamai.com/terraform/v5.5/docs/set-up-property-provisioning) | Define rules and behaviors that govern your website delivery based on match criteria. | +| [Application Security](https://techdocs.akamai.com/terraform/v5.6/docs/configure-appsec) | Manage security configurations, security policies, match targets, rate policies, and firewall rules. | +| [Bot Manager](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-botman) | Identify, track, and respond to bot activity on your domain or in your app. | +| [Certificates](https://techdocs.akamai.com/terraform/v5.6/docs/cps-integration-guide) | Full life cycle management of SSL certificates for your ​Akamai​ CDN applications. | +| [Client Lists](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-client-lists) | Reduce harmful security attacks by allowing only trusted IP/CIDRs, locations, autonomous system numbers, and TLS fingerprints to access your services and content.| +| [Cloud Wrapper](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-cloud-wrapper) | Provide your customers with a more consistent user experience by adding a custom caching layer that improves the connection between your cloud infrastructure and the Akamai platform.| +| [Cloudlets](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-cloudlets) | Solve specific business challenges using value-added apps that complement ​Akamai​'s core solutions. | +| [DataStream](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-datastream) | Monitor activity on the ​Akamai​ platform and send live log data to a destination of your choice. | +| [Edge DNS](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-edgedns) | Replace or augment your DNS infrastructure with a cloud-based authoritative DNS solution. | +| [EdgeWorkers](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-edgeworkers) | Execute JavaScript functions at the edge to optimize site performance and customize web experiences. | +| [Global Traffic Management](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-gtm) | Use load balancing to manage website and mobile performance demands. | +| [Identity and Access Management](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-iam) | Create users and groups, and define policies that manage access to your Akamai applications. | +| [Image and Video Manager](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-ivm) | Automate image and video delivery optimizations for your website visitors. | +| [Network Lists](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-network-lists) | Automate the creation, deployment, and management of lists used in ​Akamai​ security products. | +| [Property](https://techdocs.akamai.com/terraform/v5.6/docs/set-up-property-provisioning) | Define rules and behaviors that govern your website delivery based on match criteria. | ## Links to resources diff --git a/docs/resources/resources.md b/docs/resources/resources.md index 33d9d7f27..784475c48 100644 --- a/docs/resources/resources.md +++ b/docs/resources/resources.md @@ -8,17 +8,17 @@ We’ve moved our documentation to the Akamai TechDocs site. Use the table to fi | Subprovider | Description | |-------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------| -| [Application Security](https://techdocs.akamai.com/terraform/v5.5/docs/appsec-resources) | Manage security configurations, security policies, match targets, rate policies, and firewall rules. | -| [Bot Manager](https://techdocs.akamai.com/terraform/v5.5/docs/botman-resources) | Identify, track, and respond to bot activity on your domain or in your app. | -| [Certificates](https://techdocs.akamai.com/terraform/v5.5/docs/cps-resources) | Full life cycle management of SSL certificates for your ​Akamai​ CDN applications. | -| [Client Lists](https://techdocs.akamai.com/terraform/v5.5/docs/cli-resources) |Reduce harmful security attacks by allowing only trusted IP/CIDRs, locations, autonomous system numbers, and TLS fingerprints to access your services and content.| -| [Cloud Wrapper](https://techdocs.akamai.com/terraform/v5.5/docs/cw-resources) | Provide your customers with a more consistent user experience by adding a custom caching layer that improves the connection between your cloud infrastructure and the Akamai platform.| -| [Cloudlets](https://techdocs.akamai.com/terraform/v5.5/docs/cl-resources) | Solve specific business challenges using value-added apps that complement ​Akamai​'s core solutions. | -| [DataStream](https://techdocs.akamai.com/terraform/v5.5/docs/ds-resources) | Monitor activity on the ​Akamai​ platform and send live log data to a destination of your choice. | -| [Edge DNS](https://techdocs.akamai.com/terraform/v5.5/docs/edns-resources) | Replace or augment your DNS infrastructure with a cloud-based authoritative DNS solution. | -| [EdgeWorkers](https://techdocs.akamai.com/terraform/v5.5/docs/ew-resources) | Execute JavaScript functions at the edge to optimize site performance and customize web experiences. | -| [Global Traffic Management](https://techdocs.akamai.com/terraform/v5.5/docs/gtm-resources) | Use load balancing to manage website and mobile performance demands. | -| [Identity and Access Management](https://techdocs.akamai.com/terraform/v5.5/docs/iam-resources) | Create users and groups, and define policies that manage access to your Akamai applications. | -| [Image and Video Manager](https://techdocs.akamai.com/terraform/v5.5/docs/ivm-resources) | Automate image and video delivery optimizations for your website visitors. | -| [Network Lists](https://techdocs.akamai.com/terraform/v5.5/docs/nl-resources) | Automate the creation, deployment, and management of lists used in ​Akamai​ security products. | -| [Property](https://techdocs.akamai.com/terraform/v5.5/docs/pm-resources) | Define rules and behaviors that govern your website delivery based on match criteria. | +| [Application Security](https://techdocs.akamai.com/terraform/v5.6/docs/appsec-resources) | Manage security configurations, security policies, match targets, rate policies, and firewall rules. | +| [Bot Manager](https://techdocs.akamai.com/terraform/v5.6/docs/botman-resources) | Identify, track, and respond to bot activity on your domain or in your app. | +| [Certificates](https://techdocs.akamai.com/terraform/v5.6/docs/cps-resources) | Full life cycle management of SSL certificates for your ​Akamai​ CDN applications. | +| [Client Lists](https://techdocs.akamai.com/terraform/v5.6/docs/cli-resources) |Reduce harmful security attacks by allowing only trusted IP/CIDRs, locations, autonomous system numbers, and TLS fingerprints to access your services and content.| +| [Cloud Wrapper](https://techdocs.akamai.com/terraform/v5.6/docs/cw-resources) | Provide your customers with a more consistent user experience by adding a custom caching layer that improves the connection between your cloud infrastructure and the Akamai platform.| +| [Cloudlets](https://techdocs.akamai.com/terraform/v5.6/docs/cl-resources) | Solve specific business challenges using value-added apps that complement ​Akamai​'s core solutions. | +| [DataStream](https://techdocs.akamai.com/terraform/v5.6/docs/ds-resources) | Monitor activity on the ​Akamai​ platform and send live log data to a destination of your choice. | +| [Edge DNS](https://techdocs.akamai.com/terraform/v5.6/docs/edns-resources) | Replace or augment your DNS infrastructure with a cloud-based authoritative DNS solution. | +| [EdgeWorkers](https://techdocs.akamai.com/terraform/v5.6/docs/ew-resources) | Execute JavaScript functions at the edge to optimize site performance and customize web experiences. | +| [Global Traffic Management](https://techdocs.akamai.com/terraform/v5.6/docs/gtm-resources) | Use load balancing to manage website and mobile performance demands. | +| [Identity and Access Management](https://techdocs.akamai.com/terraform/v5.6/docs/iam-resources) | Create users and groups, and define policies that manage access to your Akamai applications. | +| [Image and Video Manager](https://techdocs.akamai.com/terraform/v5.6/docs/ivm-resources) | Automate image and video delivery optimizations for your website visitors. | +| [Network Lists](https://techdocs.akamai.com/terraform/v5.6/docs/nl-resources) | Automate the creation, deployment, and management of lists used in ​Akamai​ security products. | +| [Property](https://techdocs.akamai.com/terraform/v5.6/docs/pm-resources) | Define rules and behaviors that govern your website delivery based on match criteria. | diff --git a/go.mod b/go.mod index c71cc5013..23b7dfed6 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/akamai/terraform-provider-akamai/v5 require ( - github.com/akamai/AkamaiOPEN-edgegrid-golang/v7 v7.5.0 + github.com/akamai/AkamaiOPEN-edgegrid-golang/v7 v7.6.1 github.com/allegro/bigcache/v2 v2.2.5 github.com/apex/log v1.9.0 github.com/dlclark/regexp2 v1.10.0 @@ -73,10 +73,10 @@ require ( github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/zclconf/go-cty v1.13.2 // indirect go.uber.org/ratelimit v0.2.0 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.15.0 // indirect + golang.org/x/crypto v0.18.0 // indirect + golang.org/x/mod v0.14.0 // indirect + golang.org/x/net v0.20.0 // indirect + golang.org/x/sys v0.16.0 // indirect golang.org/x/text v0.14.0 // indirect google.golang.org/appengine v1.6.7 // indirect google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect diff --git a/go.sum b/go.sum index 8f8b717fb..aa102c134 100644 --- a/go.sum +++ b/go.sum @@ -4,8 +4,8 @@ github.com/ProtonMail/go-crypto v0.0.0-20230217124315-7d5c6f04bbb8/go.mod h1:I0g github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/akamai/AkamaiOPEN-edgegrid-golang/v7 v7.5.0 h1:rxtfltEe15EyTrAdkKpd7/R8GDmMQXbMmh4OX7acO5o= -github.com/akamai/AkamaiOPEN-edgegrid-golang/v7 v7.5.0/go.mod h1:+8Nc6CkB/hiTEvoxxZwvY3whU5QsXxW4RhSARTMnAfI= +github.com/akamai/AkamaiOPEN-edgegrid-golang/v7 v7.6.1 h1:KrYkNvCKBGPs/upjgJCojZnnmt5XdEPWS4L2zRQm7+o= +github.com/akamai/AkamaiOPEN-edgegrid-golang/v7 v7.6.1/go.mod h1:gajRk0oNRQj4bHUc2SGAvAp/gPestSpuvK4QXU1QtPA= github.com/allegro/bigcache/v2 v2.2.5 h1:mRc8r6GQjuJsmSKQNPsR5jQVXc8IJ1xsW5YXUYMLfqI= github.com/allegro/bigcache/v2 v2.2.5/go.mod h1:FppZsIO+IZk7gCuj5FiIDHGygD9xvWQcqg1uIPMb6tY= github.com/andres-erbsen/clock v0.0.0-20160526145045-9e14626cd129 h1:MzBOUgng9orim59UnfUTLRjMpd09C5uEVQ6RPGeCaVI= @@ -220,19 +220,19 @@ go.uber.org/ratelimit v0.2.0/go.mod h1:YYBV4e4naJvhpitQrWJu1vCpgB7CboMe0qhltKt6m golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190426145343-a29dc8fdc734/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= +golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= 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.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= @@ -250,8 +250,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/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-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= diff --git a/pkg/common/framework/modifiers/prevent_string_update.go b/pkg/common/framework/modifiers/prevent_string_update.go new file mode 100644 index 000000000..26bcab52f --- /dev/null +++ b/pkg/common/framework/modifiers/prevent_string_update.go @@ -0,0 +1,36 @@ +package modifiers + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" +) + +// PreventStringUpdate returns a plan modifier that ensures given field cannot be updated after creation. +func PreventStringUpdate() planmodifier.String { + return preventStringUpdateModifier{} +} + +// preventStringUpdateModifier implements the plan modifier. +type preventStringUpdateModifier struct{} + +// Description returns a human-readable description of the plan modifier. +func (m preventStringUpdateModifier) Description(_ context.Context) string { + return "Use if you want to ensure that no update is available for given field." +} + +// MarkdownDescription returns a markdown description of the plan modifier. +func (m preventStringUpdateModifier) MarkdownDescription(ctx context.Context) string { + return m.Description(ctx) +} + +// PlanModifyString implements the plan modification logic. +func (m preventStringUpdateModifier) PlanModifyString(_ context.Context, req planmodifier.StringRequest, resp *planmodifier.StringResponse) { + if req.StateValue.IsNull() { + return + } + if req.PlanValue != req.StateValue { + resp.Diagnostics.AddError("Update not Supported", fmt.Sprintf("updating field `%s` is not possible", req.Path.String())) + } +} diff --git a/pkg/common/tf/diff_suppress.go b/pkg/common/tf/diff_suppress.go index 623e903ad..c9fc2e0ce 100644 --- a/pkg/common/tf/diff_suppress.go +++ b/pkg/common/tf/diff_suppress.go @@ -6,9 +6,9 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -// ComposeDiffSuppress aggregates all given schema.SchemaDiffSuppressFunc into one -// Aggregated function returns true if any of the functions returns true -func ComposeDiffSuppress(fncs ...schema.SchemaDiffSuppressFunc) schema.SchemaDiffSuppressFunc { +// DiffSuppressAny aggregates all given schema.SchemaDiffSuppressFunc into one. +// Aggregated function returns true if any of the functions returns true. +func DiffSuppressAny(fncs ...schema.SchemaDiffSuppressFunc) schema.SchemaDiffSuppressFunc { return func(k, oldValue, newValue string, d *schema.ResourceData) bool { for _, fn := range fncs { if fn(k, oldValue, newValue, d) { diff --git a/pkg/common/tf/provider_schema.go b/pkg/common/tf/provider_schema.go index 1dfab5860..3631b8503 100644 --- a/pkg/common/tf/provider_schema.go +++ b/pkg/common/tf/provider_schema.go @@ -22,9 +22,15 @@ var ( ErrEmptyPath = errors.New("path cannot be empty") ) -// ResourceDataFetcher ... +// ResourceDataFetcher allows getting values from resource data. type ResourceDataFetcher interface { - GetOk(string) (interface{}, bool) + GetOk(string) (any, bool) +} + +// ResourceChangeFetcher allows getting changes to the resource data. +type ResourceChangeFetcher interface { + GetChange(string) (any, any) + HasChange(string) bool } // GetSchemaFieldNameFromPath returns schema field name from given path @@ -53,7 +59,7 @@ func GetStringValue(key string, rd ResourceDataFetcher) (string, error) { } value, ok := rd.GetOk(key) - if ok { + if value != nil && ok { str, ok := value.(string) if !ok { return "", fmt.Errorf("%w: %s, %q", ErrInvalidType, key, "string") @@ -75,7 +81,7 @@ func GetInterfaceArrayValue(key string, rd ResourceDataFetcher) ([]interface{}, } value, ok := rd.GetOk(key) - if ok { + if value != nil && ok { interf, ok := value.([]interface{}) if !ok { return nil, fmt.Errorf("%w: %s, %q", ErrInvalidType, key, "[]interface{}") @@ -96,7 +102,7 @@ func GetIntValue(key string, rd ResourceDataFetcher) (int, error) { return 0, fmt.Errorf("%w: %s", ErrEmptyKey, key) } value, ok := rd.GetOk(key) - if !ok { + if value == nil || !ok { return 0, fmt.Errorf("%w: %s", ErrNotFound, key) } var num int @@ -106,6 +112,18 @@ func GetIntValue(key string, rd ResourceDataFetcher) (int, error) { return num, nil } +// GetIntValueAsInt64 fetches value with given key from ResourceData object and attempts type cast to int, if succeed, it returns value as int64 +// +// if value is not present on provided resource, ErrNotFound is returned +// if casting is not successful, ErrInvalidType is returned +func GetIntValueAsInt64(key string, rd ResourceDataFetcher) (int64, error) { + num, err := GetIntValue(key, rd) + if err != nil { + return 0, err + } + return int64(num), nil +} + // GetInt64Value fetches value with given key from ResourceData object and attempts type cast to int64 // // if value is not present on provided resource, ErrNotFound is returned @@ -115,7 +133,7 @@ func GetInt64Value(key string, rd ResourceDataFetcher) (int64, error) { return 0, fmt.Errorf("%w: %s", ErrEmptyKey, key) } value, ok := rd.GetOk(key) - if !ok { + if value == nil || !ok { return 0, fmt.Errorf("%w: %s", ErrNotFound, key) } var num int64 @@ -134,7 +152,7 @@ func GetFloat64Value(key string, rd ResourceDataFetcher) (float64, error) { return 0, fmt.Errorf("%w: %s", ErrEmptyKey, key) } value, ok := rd.GetOk(key) - if !ok { + if value == nil || !ok { return 0, fmt.Errorf("%w: %s", ErrNotFound, key) } var num float64 @@ -153,7 +171,7 @@ func GetFloat32Value(key string, rd ResourceDataFetcher) (float32, error) { return 0, fmt.Errorf("%w: %s", ErrEmptyKey, key) } value, ok := rd.GetOk(key) - if !ok { + if value == nil || !ok { return 0, fmt.Errorf("%w: %s", ErrNotFound, key) } var num float32 @@ -172,6 +190,9 @@ func GetBoolValue(key string, rd ResourceDataFetcher) (bool, error) { return false, fmt.Errorf("%w: %s", ErrEmptyKey, key) } value, _ := rd.GetOk(key) + if value == nil { + return false, fmt.Errorf("%w: %s", ErrNotFound, key) + } val, ok := value.(bool) if !ok { @@ -190,7 +211,7 @@ func GetSetValue(key string, rd ResourceDataFetcher) (*schema.Set, error) { } val := new(schema.Set) value, ok := rd.GetOk(key) - if !ok { + if value == nil || !ok { return val, fmt.Errorf("%w: %s", ErrNotFound, key) } if val, ok = value.(*schema.Set); !ok { @@ -209,7 +230,7 @@ func GetListValue(key string, rd ResourceDataFetcher) ([]interface{}, error) { } value, ok := rd.GetOk(key) val := make([]interface{}, 0) - if !ok { + if value == nil || !ok { return val, fmt.Errorf("%w: %s", ErrNotFound, key) } if val, ok = value.([]interface{}); !ok { @@ -251,7 +272,7 @@ func GetMapValue(key string, rd ResourceDataFetcher) (map[string]interface{}, er } val := make(map[string]interface{}, 0) value, ok := rd.GetOk(key) - if !ok { + if value == nil || !ok { return val, fmt.Errorf("%w: %s", ErrNotFound, key) } if val, ok = value.(map[string]interface{}); !ok { @@ -269,7 +290,7 @@ func FindStringValues(rd ResourceDataFetcher, keys ...string) []string { for _, key := range keys { value, ok := rd.GetOk(key) - if ok { + if value != nil && ok { str, ok := value.(string) if !ok { continue diff --git a/pkg/common/tf/raw_config.go b/pkg/common/tf/raw_config.go index 148b4ff29..60a62f168 100644 --- a/pkg/common/tf/raw_config.go +++ b/pkg/common/tf/raw_config.go @@ -6,17 +6,21 @@ import ( "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/go-cty/cty/gocty" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +// RawConfigGetter is used to retrieve raw config. +type RawConfigGetter interface { + GetRawConfig() cty.Value +} + // RawConfig is used to query attributes from the raw config. type RawConfig struct { data cty.Value } // NewRawConfig creates a new RawConfig which uses the raw config retrieved from data. -func NewRawConfig(data *schema.ResourceData) *RawConfig { - return &RawConfig{data: data.GetRawConfig()} +func NewRawConfig(g RawConfigGetter) *RawConfig { + return &RawConfig{data: g.GetRawConfig()} } // GetOk returns the data for the given key and whether or not the key was set to a non-zero value. diff --git a/pkg/providers/appsec/appsec.go b/pkg/providers/appsec/appsec.go index 78486c663..57c0b2c08 100644 --- a/pkg/providers/appsec/appsec.go +++ b/pkg/providers/appsec/appsec.go @@ -1,6 +1,8 @@ package appsec -import "github.com/akamai/terraform-provider-akamai/v5/pkg/providers/registry" +import ( + "github.com/akamai/terraform-provider-akamai/v5/pkg/providers/registry" +) // SubproviderName defines name of the appsec subprovider const SubproviderName = "appsec" diff --git a/pkg/providers/appsec/diff_suppress_funcs.go b/pkg/providers/appsec/diff_suppress_funcs.go index 474d2c9a6..88a42ecae 100644 --- a/pkg/providers/appsec/diff_suppress_funcs.go +++ b/pkg/providers/appsec/diff_suppress_funcs.go @@ -349,5 +349,18 @@ func compareMatchTargets(oldTarget, newTarget *appsec.CreateMatchTargetResponse) p2 := newTarget.Apis[j] return p1.ID < p2.ID || ((p1.ID == p2.ID) && p1.Name < p2.Name) }) + + sort.Slice(oldTarget.BypassNetworkLists, func(i, j int) bool { + p1 := oldTarget.BypassNetworkLists[i] + p2 := oldTarget.BypassNetworkLists[j] + return p1.ID < p2.ID || ((p1.ID == p2.ID) && p1.Name < p2.Name) + }) + + sort.Slice(newTarget.BypassNetworkLists, func(i, j int) bool { + p1 := newTarget.BypassNetworkLists[i] + p2 := newTarget.BypassNetworkLists[j] + return p1.ID < p2.ID || ((p1.ID == p2.ID) && p1.Name < p2.Name) + }) + return reflect.DeepEqual(oldTarget, newTarget) } diff --git a/pkg/providers/appsec/resource_akamai_appsec_activations.go b/pkg/providers/appsec/resource_akamai_appsec_activations.go index bcaae1957..48dcbd0d8 100644 --- a/pkg/providers/appsec/resource_akamai_appsec_activations.go +++ b/pkg/providers/appsec/resource_akamai_appsec_activations.go @@ -10,6 +10,7 @@ import ( "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/appsec" "github.com/akamai/terraform-provider-akamai/v5/pkg/common/tf" "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -58,7 +59,8 @@ func resourceActivations() *schema.Resource { Required: true, Elem: &schema.Schema{Type: schema.TypeString}, Description: "List of email addresses to be notified with the results of the activation", - }, "status": { + }, + "status": { Type: schema.TypeString, Computed: true, Description: "The results of the activation", @@ -81,6 +83,9 @@ var ( // AppsecResourceTimeout is the default timeout for the resource operations AppsecResourceTimeout = time.Minute * 90 + + // CreateActivationRetry poll wait time code waits between retries for activation creation + CreateActivationRetry = 10 * time.Second ) func resourceActivationsCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { @@ -119,7 +124,7 @@ func resourceActivationsCreate(ctx context.Context, d *schema.ResourceData, m in notificationEmails := tf.SetToStringSlice(notificationEmailsSet) createActivationRequest := appsec.CreateActivationsRequest{ - Action: "ACTIVATE", + Action: string(appsec.ActivationTypeActivate), Network: network, Note: note, NotificationEmails: notificationEmails, @@ -129,40 +134,28 @@ func resourceActivationsCreate(ctx context.Context, d *schema.ResourceData, m in ConfigVersion: version, }) - postresp, err := client.CreateActivations(ctx, createActivationRequest, true) + activationResp, err := createActivation(ctx, client, createActivationRequest) if err != nil { - logger.Errorf("calling 'createActivations': %s", err.Error()) return diag.FromErr(err) } - d.SetId(strconv.Itoa(postresp.ActivationID)) + d.SetId(strconv.Itoa(activationResp.ActivationID)) - if err := d.Set("status", postresp.Status); err != nil { + if err := d.Set("status", activationResp.Status); err != nil { return diag.Errorf("%s: %s", tf.ErrValueSet, err.Error()) } getActivationRequest := appsec.GetActivationsRequest{ - ActivationID: postresp.ActivationID, + ActivationID: activationResp.ActivationID, } activation, err := lookupActivation(ctx, client, getActivationRequest) if err != nil { return diag.FromErr(err) } - for activation.Status != appsec.StatusActive && activation.Status != appsec.StatusAborted && activation.Status != appsec.StatusFailed { - select { - case <-time.After(tf.MaxDuration(ActivationPollInterval, ActivationPollMinimum)): - act, err := client.GetActivations(ctx, getActivationRequest) - if err != nil { - return diag.FromErr(err) - } - activation = act - - case <-ctx.Done(): - return diag.Errorf("activation context terminated: %s", ctx.Err()) - } + if err = pollActivation(ctx, client, activation.Status, getActivationRequest); err != nil { + return diag.FromErr(err) } - return resourceActivationsRead(ctx, d, m) } @@ -187,6 +180,18 @@ func resourceActivationsRead(ctx context.Context, d *schema.ResourceData, m inte return diag.FromErr(err) } + network, err := tf.GetStringValue("network", d) + if err != nil && !errors.Is(err, tf.ErrNotFound) { + return diag.FromErr(err) + } + + if activations.Action == string(appsec.ActivationTypeActivate) && + activations.Status == appsec.StatusDeactivated && + (string(activations.Network) == network) { + d.SetId("") + return nil + } + if err := d.Set("status", activations.Status); err != nil { return diag.Errorf("%s: %s", tf.ErrValueSet, err.Error()) } @@ -230,7 +235,7 @@ func resourceActivationsUpdate(ctx context.Context, d *schema.ResourceData, m in notificationEmails := tf.SetToStringSlice(notificationEmailsSet) createActivationRequest := appsec.CreateActivationsRequest{ - Action: "ACTIVATE", + Action: string(appsec.ActivationTypeActivate), Network: network, Note: note, NotificationEmails: notificationEmails, @@ -240,39 +245,27 @@ func resourceActivationsUpdate(ctx context.Context, d *schema.ResourceData, m in ConfigVersion: version, }) - postresp, err := client.CreateActivations(ctx, createActivationRequest, true) + activationResp, err := createActivation(ctx, client, createActivationRequest) if err != nil { - logger.Errorf("calling 'createActivations': %s", err.Error()) return diag.FromErr(err) } - d.SetId(strconv.Itoa(postresp.ActivationID)) + d.SetId(strconv.Itoa(activationResp.ActivationID)) - if err := d.Set("status", postresp.Status); err != nil { + if err := d.Set("status", activationResp.Status); err != nil { return diag.Errorf("%s: %s", tf.ErrValueSet, err.Error()) } getActivationRequest := appsec.GetActivationsRequest{ - ActivationID: postresp.ActivationID, + ActivationID: activationResp.ActivationID, } activation, err := lookupActivation(ctx, client, getActivationRequest) if err != nil { return diag.FromErr(err) } - for activation.Status != appsec.StatusActive && activation.Status != appsec.StatusAborted && activation.Status != appsec.StatusFailed { - select { - case <-time.After(tf.MaxDuration(ActivationPollInterval, ActivationPollMinimum)): - act, err := client.GetActivations(ctx, getActivationRequest) - - if err != nil { - return diag.FromErr(err) - } - activation = act - - case <-ctx.Done(): - return diag.Errorf("activation context terminated: %s", ctx.Err()) - } + if err = pollActivation(ctx, client, activation.Status, getActivationRequest); err != nil { + return diag.FromErr(err) } return resourceActivationsRead(ctx, d, m) @@ -320,7 +313,7 @@ func resourceActivationsDelete(ctx context.Context, d *schema.ResourceData, m in removeActivationRequest := appsec.RemoveActivationsRequest{ ActivationID: activationID, - Action: "DEACTIVATE", + Action: string(appsec.ActivationTypeDeactivate), Network: network, Note: note, NotificationEmails: notificationEmails, @@ -458,3 +451,93 @@ func suppressNoteFieldForAppSecActivation(_, oldValue, newValue string, d *schem } return true } + +func createActivation(ctx context.Context, client appsec.APPSEC, request appsec.CreateActivationsRequest) (*appsec.CreateActivationsResponse, error) { + log := hclog.FromContext(ctx) + + errMsg := "create failed" + switch request.Action { + case string(appsec.ActivationTypeActivate): + errMsg = "create activation failed" + case string(appsec.ActivationTypeDeactivate): + errMsg = "create deactivation failed" + } + + createActivationRetry := CreateActivationRetry + + for { + log.Debug("creating activation") + create, err := client.CreateActivations(ctx, request, true) + + if err == nil { + return create, nil + } + log.Debug("%s: retrying: %w", errMsg, err) + + if !isCreateActivationErrorRetryable(err) { + return nil, fmt.Errorf("%s: %s", errMsg, err) + } + + select { + case <-time.After(createActivationRetry): + createActivationRetry = capDuration(createActivationRetry*2, 5*time.Minute) + continue + + case <-ctx.Done(): + return nil, fmt.Errorf("activation context terminated: %w", ctx.Err()) + } + } + +} + +func pollActivation(ctx context.Context, client appsec.APPSEC, activationStatus appsec.StatusValue, getActivationRequest appsec.GetActivationsRequest) error { + retriesMax := 5 + retries5xx := 0 + + for activationStatus != appsec.StatusActive && activationStatus != appsec.StatusAborted && activationStatus != appsec.StatusFailed { + select { + case <-time.After(tf.MaxDuration(ActivationPollInterval, ActivationPollMinimum)): + act, err := client.GetActivations(ctx, getActivationRequest) + if err != nil { + var target = &appsec.Error{} + if !errors.As(err, &target) { + return fmt.Errorf("error has unexpected type: %T", err) + } + if isCreateActivationErrorRetryable(target) { + retries5xx = retries5xx + 1 + if retries5xx > retriesMax { + return fmt.Errorf("reached max number of 5xx retries: %d", retries5xx) + } + continue + } + return err + } + retries5xx = 0 + activationStatus = act.Status + + case <-ctx.Done(): + return fmt.Errorf("activation context terminated: %s", ctx.Err()) + } + } + return nil +} + +func isCreateActivationErrorRetryable(err error) bool { + var responseErr = &appsec.Error{} + if !errors.As(err, &responseErr) { + return false + } + if responseErr.StatusCode < 500 && + responseErr.StatusCode != 422 && + responseErr.StatusCode != 409 { + return false + } + return true +} + +func capDuration(t time.Duration, tMax time.Duration) time.Duration { + if t > tMax { + return tMax + } + return t +} diff --git a/pkg/providers/appsec/resource_akamai_appsec_activations_test.go b/pkg/providers/appsec/resource_akamai_appsec_activations_test.go index 0c17f43c7..074e12269 100644 --- a/pkg/providers/appsec/resource_akamai_appsec_activations_test.go +++ b/pkg/providers/appsec/resource_akamai_appsec_activations_test.go @@ -313,4 +313,225 @@ func TestAkamaiActivations_res_basic(t *testing.T) { client.AssertExpectations(t) }) + t.Run("Retry create activation on 500x error", func(t *testing.T) { + + err500x := &appsec.Error{StatusCode: 502} + + client := &appsec.Mock{} + + removeActivationsResponse := appsec.RemoveActivationsResponse{} + err := json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResActivations/ActivationsDelete.json"), &removeActivationsResponse) + require.NoError(t, err) + + getActivationsResponse := appsec.GetActivationsResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResActivations/Activations.json"), &getActivationsResponse) + require.NoError(t, err) + + getActivationsDeleteResponse := appsec.GetActivationsResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResActivations/ActivationsDelete.json"), &getActivationsDeleteResponse) + require.NoError(t, err) + + createActivationsResponse := appsec.CreateActivationsResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResActivations/Activations.json"), &createActivationsResponse) + require.NoError(t, err) + + client.On("GetActivations", + mock.Anything, + appsec.GetActivationsRequest{ActivationID: 547694}, + ).Return(&getActivationsResponse, nil).Times(3) + + client.On("CreateActivations", + mock.Anything, + appsec.CreateActivationsRequest{ + Action: "ACTIVATE", + Network: "STAGING", + Note: "Test Notes", + NotificationEmails: []string{"user@example.com"}, + ActivationConfigs: []struct { + ConfigID int `json:"configId"` + ConfigVersion int `json:"configVersion"` + }{{ConfigID: 43253, ConfigVersion: 7}}}, + ).Return(nil, err500x).Once() + + client.On("GetActivations", + mock.Anything, + appsec.GetActivationsRequest{ActivationID: 547694}, + ).Return(&getActivationsDeleteResponse, nil) + + client.On("RemoveActivations", + mock.Anything, + appsec.RemoveActivationsRequest{ + ActivationID: 547694, + Action: "DEACTIVATE", + Network: "STAGING", + Note: "Test Notes", + NotificationEmails: []string{"user@example.com"}, + ActivationConfigs: []struct { + ConfigID int `json:"configId"` + ConfigVersion int `json:"configVersion"` + }{{ConfigID: 43253, ConfigVersion: 7}}}, + ).Return(&removeActivationsResponse, nil) + + client.On("GetActivations", + mock.Anything, + appsec.GetActivationsRequest{ActivationID: 547695}, + ).Return(&getActivationsDeleteResponse, nil).Once() + + client.On("CreateActivations", + mock.Anything, + appsec.CreateActivationsRequest{ + Action: "ACTIVATE", + Network: "STAGING", + Note: "Test Notes", + NotificationEmails: []string{"user@example.com"}, + ActivationConfigs: []struct { + ConfigID int `json:"configId"` + ConfigVersion int `json:"configVersion"` + }{{ConfigID: 43253, ConfigVersion: 7}}}, + ).Return(&createActivationsResponse, nil).Once() + + useClient(client, func() { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "testdata/TestResActivations/match_by_id.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("akamai_appsec_activations.test", "config_id", "43253"), + resource.TestCheckResourceAttr("akamai_appsec_activations.test", "network", "STAGING"), + resource.TestCheckResourceAttr("akamai_appsec_activations.test", "note", "Test Notes"), + ), + }, + }, + }) + }) + + client.AssertExpectations(t) + + }) + + t.Run("reactivate config when manually deactivated from UI", func(t *testing.T) { + client := &appsec.Mock{} + + removeActivationsResponse := appsec.RemoveActivationsResponse{} + err := json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResActivations/ActivationsDelete.json"), &removeActivationsResponse) + require.NoError(t, err) + + getActivationsResponse := appsec.GetActivationsResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResActivations/Activations.json"), &getActivationsResponse) + require.NoError(t, err) + + getActivationsResponseDelete := appsec.GetActivationsResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResActivations/ActivationsDelete.json"), &getActivationsResponseDelete) + require.NoError(t, err) + + createActivationsResponse := appsec.CreateActivationsResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResActivations/Activations.json"), &createActivationsResponse) + require.NoError(t, err) + + getActivationsUpdatedResponse := appsec.GetActivationsResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResActivations/Activations_Production.json"), &getActivationsUpdatedResponse) + require.NoError(t, err) + + getActivationsResponseDeleteUpdated := appsec.GetActivationsResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResActivations/Deactivations_Production.json"), &getActivationsResponseDeleteUpdated) + require.NoError(t, err) + + getActivationsResponseDeactivated := appsec.GetActivationsResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResActivations/Manual_Deactivate.json"), &getActivationsResponseDeactivated) + require.NoError(t, err) + + // First step - create and read + + client.On("CreateActivations", + mock.Anything, + appsec.CreateActivationsRequest{ + Action: "ACTIVATE", + Network: "STAGING", + Note: "Test Notes", + NotificationEmails: []string{"user@example.com"}, + ActivationConfigs: []struct { + ConfigID int `json:"configId"` + ConfigVersion int `json:"configVersion"` + }{{ConfigID: 43253, ConfigVersion: 7}}}, + ).Return(&createActivationsResponse, nil).Once() + + client.On("GetActivations", + mock.Anything, + appsec.GetActivationsRequest{ActivationID: 547694}, + ).Return(&getActivationsResponse, nil).Times(3) + + // Second Step : Config deactivated from UI + + client.On("GetActivations", + mock.Anything, + appsec.GetActivationsRequest{ActivationID: 547694}, + ).Return(&getActivationsResponseDeactivated, nil).Once() + + client.On("CreateActivations", + mock.Anything, + appsec.CreateActivationsRequest{ + Action: "ACTIVATE", + Network: "STAGING", + Note: "Test Notes", + NotificationEmails: []string{"user@example.com"}, + ActivationConfigs: []struct { + ConfigID int `json:"configId"` + ConfigVersion int `json:"configVersion"` + }{{ConfigID: 43253, ConfigVersion: 7}}}, + ).Return(&createActivationsResponse, nil).Once() + + client.On("GetActivations", + mock.Anything, + appsec.GetActivationsRequest{ActivationID: 547694}, + ).Return(&getActivationsResponse, nil).Times(3) + + client.On("RemoveActivations", + mock.Anything, + appsec.RemoveActivationsRequest{ + ActivationID: 547694, + Action: "DEACTIVATE", + Network: "STAGING", + Note: "Test Notes", + NotificationEmails: []string{"user@example.com"}, + ActivationConfigs: []struct { + ConfigID int `json:"configId"` + ConfigVersion int `json:"configVersion"` + }{{ConfigID: 43253, ConfigVersion: 7}}}, + ).Return(&removeActivationsResponse, nil).Once() + + client.On("GetActivations", + mock.Anything, + appsec.GetActivationsRequest{ActivationID: 547695}, + ).Return(&getActivationsResponseDelete, nil).Times(1) + + useClient(client, func() { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "testdata/TestResActivations/match_by_id.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("akamai_appsec_activations.test", "config_id", "43253"), + resource.TestCheckResourceAttr("akamai_appsec_activations.test", "note", "Test Notes"), + resource.TestCheckResourceAttr("akamai_appsec_activations.test", "version", "7"), + ), + }, + { + Config: testutils.LoadFixtureString(t, "testdata/TestResActivations/match_by_id.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("akamai_appsec_activations.test", "config_id", "43253"), + resource.TestCheckResourceAttr("akamai_appsec_activations.test", "note", "Test Notes"), + resource.TestCheckResourceAttr("akamai_appsec_activations.test", "version", "7"), + ), + }, + }, + }) + }) + + client.AssertExpectations(t) + }) + } diff --git a/pkg/providers/appsec/resource_akamai_appsec_attack_group.go b/pkg/providers/appsec/resource_akamai_appsec_attack_group.go index 2bf776579..49bb0cb2d 100644 --- a/pkg/providers/appsec/resource_akamai_appsec_attack_group.go +++ b/pkg/providers/appsec/resource_akamai_appsec_attack_group.go @@ -152,6 +152,7 @@ func resourceAttackGroupRead(ctx context.Context, d *schema.ResourceData, m inte attackgroup, err := client.GetAttackGroup(ctx, getAttackGroup) if err != nil { logger.Warnf("calling 'getAttackGroup': %s", err.Error()) + return diag.FromErr(err) } if err := d.Set("config_id", getAttackGroup.ConfigID); err != nil { diff --git a/pkg/providers/appsec/resource_akamai_appsec_eval_group.go b/pkg/providers/appsec/resource_akamai_appsec_eval_group.go index 73124f74f..c989d08ff 100644 --- a/pkg/providers/appsec/resource_akamai_appsec_eval_group.go +++ b/pkg/providers/appsec/resource_akamai_appsec_eval_group.go @@ -153,6 +153,7 @@ func resourceEvalGroupRead(ctx context.Context, d *schema.ResourceData, m interf attackgroup, err := client.GetEvalGroup(ctx, getAttackGroup) if err != nil { logger.Warnf("calling 'getEvalGroup': %s", err.Error()) + return diag.FromErr(err) } if err := d.Set("config_id", getAttackGroup.ConfigID); err != nil { diff --git a/pkg/providers/appsec/resource_akamai_appsec_match_target.go b/pkg/providers/appsec/resource_akamai_appsec_match_target.go index b1bd4fedc..df284427b 100644 --- a/pkg/providers/appsec/resource_akamai_appsec_match_target.go +++ b/pkg/providers/appsec/resource_akamai_appsec_match_target.go @@ -4,6 +4,8 @@ import ( "context" "encoding/json" "fmt" + "reflect" + "sort" "strconv" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/appsec" @@ -121,6 +123,18 @@ func resourceMatchTargetRead(ctx context.Context, d *schema.ResourceData, m inte logger.Errorf("calling 'getMatchTarget': %s", err.Error()) return diag.FromErr(err) } + matchTargetConfigVal, err := tf.GetStringValue("match_target", d) + if err != nil { + return diag.FromErr(err) + } + var response *appsec.GetMatchTargetResponse + if err := json.Unmarshal([]byte(matchTargetConfigVal), &response); err != nil { + return diag.FromErr(err) + } + + if err := compareMatchTargetsOrder(matchtarget, response); err != nil { + return diag.FromErr(err) + } jsonBody, err := json.Marshal(matchtarget) if err != nil { @@ -219,3 +233,73 @@ func resourceMatchTargetDelete(ctx context.Context, d *schema.ResourceData, m in } return nil } + +func compareMatchTargetsOrder(oldTarget, newTarget *appsec.GetMatchTargetResponse) error { + + oldJSONStr, err := json.Marshal(oldTarget) + if err != nil { + return fmt.Errorf("%s", err.Error()) + } + + var oldJSON appsec.GetMatchTargetResponse + if err = json.Unmarshal(oldJSONStr, &oldJSON); err != nil { + return fmt.Errorf("%s", err.Error()) + } + + newJSONStr, err := json.Marshal(newTarget) + if err != nil { + return fmt.Errorf("%s", err.Error()) + } + + var newJSON appsec.GetMatchTargetResponse + if err = json.Unmarshal(newJSONStr, &newJSON); err != nil { + return fmt.Errorf("%s", err.Error()) + } + + sort.Strings(oldJSON.FilePaths) + sort.Strings(newJSON.FilePaths) + if reflect.DeepEqual(oldJSON.FilePaths, newJSON.FilePaths) { + oldTarget.FilePaths = newTarget.FilePaths + } + + sort.Strings(oldJSON.FileExtensions) + sort.Strings(newJSON.FileExtensions) + if reflect.DeepEqual(oldJSON.FileExtensions, newJSON.FileExtensions) { + oldTarget.FileExtensions = newTarget.FileExtensions + } + + sort.Strings(oldJSON.Hostnames) + sort.Strings(newJSON.Hostnames) + if reflect.DeepEqual(oldJSON.Hostnames, newJSON.Hostnames) { + oldTarget.Hostnames = newTarget.Hostnames + } + + sort.Slice(oldJSON.Apis, func(i, j int) bool { + p1 := oldJSON.Apis[i] + p2 := oldJSON.Apis[j] + return p1.ID < p2.ID || ((p1.ID == p2.ID) && p1.Name < p2.Name) + }) + sort.Slice(newJSON.Apis, func(i, j int) bool { + p1 := newJSON.Apis[i] + p2 := newJSON.Apis[j] + return p1.ID < p2.ID || ((p1.ID == p2.ID) && p1.Name < p2.Name) + }) + if reflect.DeepEqual(oldJSON.Apis, newJSON.Apis) { + oldTarget.Apis = newTarget.Apis + } + + sort.Slice(oldJSON.BypassNetworkLists, func(i, j int) bool { + p1 := oldJSON.BypassNetworkLists[i] + p2 := oldJSON.BypassNetworkLists[j] + return p1.ID < p2.ID || ((p1.ID == p2.ID) && p1.Name < p2.Name) + }) + sort.Slice(newJSON.BypassNetworkLists, func(i, j int) bool { + p1 := newJSON.BypassNetworkLists[i] + p2 := newJSON.BypassNetworkLists[j] + return p1.ID < p2.ID || ((p1.ID == p2.ID) && p1.Name < p2.Name) + }) + if reflect.DeepEqual(oldJSON.BypassNetworkLists, newJSON.BypassNetworkLists) { + oldTarget.BypassNetworkLists = newTarget.BypassNetworkLists + } + return nil +} diff --git a/pkg/providers/appsec/resource_akamai_appsec_match_target_test.go b/pkg/providers/appsec/resource_akamai_appsec_match_target_test.go index 8bd5748c8..cf7cacd9b 100644 --- a/pkg/providers/appsec/resource_akamai_appsec_match_target_test.go +++ b/pkg/providers/appsec/resource_akamai_appsec_match_target_test.go @@ -1,6 +1,7 @@ package appsec import ( + "bytes" "encoding/json" "testing" @@ -95,4 +96,145 @@ func TestAkamaiMatchTarget_res_basic(t *testing.T) { client.AssertExpectations(t) }) + t.Run("no drift on match target lists sequence mismatch", func(t *testing.T) { + client := &appsec.Mock{} + + getMatchTargetResponse := appsec.GetMatchTargetResponse{} + err := json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResMatchTarget/MatchTargetSequenceChanged.json"), &getMatchTargetResponse) + require.NoError(t, err) + + createMatchTargetResponse := appsec.CreateMatchTargetResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResMatchTarget/MatchTargetCreated.json"), &createMatchTargetResponse) + require.NoError(t, err) + + removeMatchTargetResponse := appsec.RemoveMatchTargetResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResMatchTarget/MatchTargetCreated.json"), &removeMatchTargetResponse) + require.NoError(t, err) + + config := appsec.GetConfigurationResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResConfiguration/LatestConfiguration.json"), &config) + require.NoError(t, err) + + client.On("GetConfiguration", + mock.Anything, + appsec.GetConfigurationRequest{ConfigID: 43253}, + ).Return(&config, nil) + + client.On("GetMatchTarget", + mock.Anything, + appsec.GetMatchTargetRequest{ConfigID: 43253, ConfigVersion: 7, TargetID: 3008967}, + ).Return(&getMatchTargetResponse, nil).Times(2) + + createMatchTargetJSON := testutils.LoadFixtureBytes(t, "testdata/TestResMatchTarget/CreateMatchTarget.json") + client.On("CreateMatchTarget", + mock.Anything, + appsec.CreateMatchTargetRequest{ConfigID: 43253, ConfigVersion: 7, JsonPayloadRaw: createMatchTargetJSON}, + ).Return(&createMatchTargetResponse, nil) + + client.On("RemoveMatchTarget", + mock.Anything, + appsec.RemoveMatchTargetRequest{ConfigID: 43253, ConfigVersion: 7, TargetID: 3008967}, + ).Return(&removeMatchTargetResponse, nil) + + getMatchTargetJSON := `{"type":"website","defaultFile":"NO_MATCH","hostnames":["m.example.com","www.example.net","example.com"],"isNegativePathMatch":false,"filePaths":["/cache/aaabbc*"],"fileExtensions":["carb","pct","pdf","swf","cct","jpeg","js","wmls","hdml","pws"],"securityPolicy":{"policyId":"AAAA_81230"},"targetId":3008967}` + + useClient(client, func() { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "testdata/TestResMatchTarget/match_by_id.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("akamai_appsec_match_target.test", "id", "43253:3008967"), + resource.TestCheckResourceAttr("akamai_appsec_match_target.test", "match_target", compactJSON(getMatchTargetJSON)), + ), + }, + }, + }) + }) + + client.AssertExpectations(t) + }) + + t.Run("some values should not drift on non changed list or struct values sequence mismatch", func(t *testing.T) { + client := &appsec.Mock{} + + getMatchTargetResponse := appsec.GetMatchTargetResponse{} + err := json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResMatchTarget/MatchTargetSequenceChanged.json"), &getMatchTargetResponse) + require.NoError(t, err) + + getMatchTargetResponseChanged := appsec.GetMatchTargetResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResMatchTarget/MatchTargetSequenceOrderChanged.json"), &getMatchTargetResponse) + require.NoError(t, err) + + createMatchTargetResponse := appsec.CreateMatchTargetResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResMatchTarget/MatchTargetCreated.json"), &createMatchTargetResponse) + require.NoError(t, err) + + removeMatchTargetResponse := appsec.RemoveMatchTargetResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResMatchTarget/MatchTargetCreated.json"), &removeMatchTargetResponse) + require.NoError(t, err) + + config := appsec.GetConfigurationResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResConfiguration/LatestConfiguration.json"), &config) + require.NoError(t, err) + + client.On("GetConfiguration", + mock.Anything, + appsec.GetConfigurationRequest{ConfigID: 43253}, + ).Return(&config, nil) + + client.On("GetMatchTarget", + mock.Anything, + appsec.GetMatchTargetRequest{ConfigID: 43253, ConfigVersion: 7, TargetID: 3008967}, + ).Return(&getMatchTargetResponse, nil).Times(1) + + client.On("GetMatchTarget", + mock.Anything, + appsec.GetMatchTargetRequest{ConfigID: 43253, ConfigVersion: 7, TargetID: 3008967}, + ).Return(&getMatchTargetResponseChanged, nil).Times(1) + + createMatchTargetJSON := testutils.LoadFixtureBytes(t, "testdata/TestResMatchTarget/CreateMatchTarget.json") + client.On("CreateMatchTarget", + mock.Anything, + appsec.CreateMatchTargetRequest{ConfigID: 43253, ConfigVersion: 7, JsonPayloadRaw: createMatchTargetJSON}, + ).Return(&createMatchTargetResponse, nil) + + client.On("RemoveMatchTarget", + mock.Anything, + appsec.RemoveMatchTargetRequest{ConfigID: 43253, ConfigVersion: 7, TargetID: 3008967}, + ).Return(&removeMatchTargetResponse, nil) + + getMatchTargetJSON := `{"type":"website","defaultFile":"NO_MATCH","hostnames":["m.example.com","www.example.net","examplenew.com"],"isNegativePathMatch":false,"filePaths":["/cache/aaabbc*"],"fileExtensions":["carb","pct","pdf","swf","cct","jpeg","js","wmls","hdml","pws"],"securityPolicy":{"policyId":"AAAA_81230"},"targetId":3008967}` + + useClient(client, func() { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "testdata/TestResMatchTarget/match_by_id.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("akamai_appsec_match_target.test", "id", "43253:3008967"), + resource.TestCheckResourceAttr("akamai_appsec_match_target.test", "match_target", compactJSON(getMatchTargetJSON)), + ), + ExpectNonEmptyPlan: true, + }, + }, + }) + }) + + client.AssertExpectations(t) + }) + +} + +func compactJSON(message string) string { + var dst bytes.Buffer + err := json.Compact(&dst, []byte(message)) + if err != nil { + panic(err) + } + return dst.String() } diff --git a/pkg/providers/appsec/testdata/TestResActivations/Manual_Deactivate.json b/pkg/providers/appsec/testdata/TestResActivations/Manual_Deactivate.json new file mode 100644 index 000000000..66e79102e --- /dev/null +++ b/pkg/providers/appsec/testdata/TestResActivations/Manual_Deactivate.json @@ -0,0 +1,18 @@ + +{ + "action": "ACTIVATE", + "activationConfigs": [ + { + "configId": 43253, + "configName": "Akamai Tools", + "configVersion": 4 + } + ], + "activationId": 547694, + "createDate": "2020-10-07T12:30:49Z", + "createdBy": "lap2lreucgguhekn", + "dispatchCount": 1, + "network": "STAGING", + "reasons": [], + "status": "DEACTIVATED" +} diff --git a/pkg/providers/appsec/testdata/TestResMatchTarget/CreateMatchTarget.json b/pkg/providers/appsec/testdata/TestResMatchTarget/CreateMatchTarget.json index c0e87fa60..caedaed54 100644 --- a/pkg/providers/appsec/testdata/TestResMatchTarget/CreateMatchTarget.json +++ b/pkg/providers/appsec/testdata/TestResMatchTarget/CreateMatchTarget.json @@ -1,40 +1 @@ - { - "type": "website", - "configId": 43253, - "configVersion": 15, - "defaultFile": "NO_MATCH", - "effectiveSecurityControls": { - "applyApplicationLayerControls": false, - "applyBotmanControls": false, - "applyNetworkLayerControls": false, - "applyRateControls": false, - "applyReputationControls": false, - "applySlowPostControls": false - }, - "fileExtensions": [ - "carb", - "pct", - "pdf", - "swf", - "cct", - "jpeg", - "js", - "wmls", - "hdml", - "pws" - ], - "filePaths": [ - "/cache/aaabbc*" - ], - "hostnames": [ - "m.example.com", - "www.example.net", - "example.com" - ], - "isNegativeFileExtensionMatch": false, - "isNegativePathMatch": false, - "securityPolicy": { - "policyId": "AAAA_81230" - }, - "sequence": 1 -} +{"configId":43253,"configVersion":15,"defaultFile":"NO_MATCH","effectiveSecurityControls":{"applyApplicationLayerControls":false,"applyBotmanControls":false,"applyNetworkLayerControls":false,"applyRateControls":false,"applyReputationControls":false,"applySlowPostControls":false},"fileExtensions":["carb","pct","pdf","swf","cct","jpeg","js","wmls","hdml","pws"],"filePaths":["/cache/aaabbc*"],"hostnames":["m.example.com","www.example.net","example.com"],"isNegativeFileExtensionMatch":false,"isNegativePathMatch":false,"securityPolicy":{"policyId":"AAAA_81230"},"sequence":1,"type":"website"} \ No newline at end of file diff --git a/pkg/providers/appsec/testdata/TestResMatchTarget/MatchTargetSequenceChanged.json b/pkg/providers/appsec/testdata/TestResMatchTarget/MatchTargetSequenceChanged.json new file mode 100644 index 000000000..74dd59456 --- /dev/null +++ b/pkg/providers/appsec/testdata/TestResMatchTarget/MatchTargetSequenceChanged.json @@ -0,0 +1,33 @@ +{ + "type": "website", + "configId": 43253, + "configVersion": 15, + "defaultFile": "NO_MATCH", + "fileExtensions": [ + "pct", + "carb", + "pdf", + "swf", + "cct", + "jpeg", + "js", + "wmls", + "hdml", + "pws" + ], + "filePaths": [ + "/cache/aaabbc*" + ], + "hostnames": [ + "m.example.com", + "www.example.net", + "example.com" + ], + "isNegativeFileExtensionMatch": false, + "isNegativePathMatch": false, + "securityPolicy": { + "policyId": "AAAA_81230" + }, + "sequence": 1, + "targetId": 3008967 +} \ No newline at end of file diff --git a/pkg/providers/appsec/testdata/TestResMatchTarget/MatchTargetSequenceOrderChanged.json b/pkg/providers/appsec/testdata/TestResMatchTarget/MatchTargetSequenceOrderChanged.json new file mode 100644 index 000000000..e6eee7c6e --- /dev/null +++ b/pkg/providers/appsec/testdata/TestResMatchTarget/MatchTargetSequenceOrderChanged.json @@ -0,0 +1,33 @@ +{ + "type": "website", + "configId": 43253, + "configVersion": 15, + "defaultFile": "NO_MATCH", + "fileExtensions": [ + "pct", + "carb", + "pdf", + "swf", + "cct", + "jpeg", + "js", + "wmls", + "hdml", + "pws" + ], + "filePaths": [ + "/cache/aaabbc*" + ], + "hostnames": [ + "m.example.com", + "www.example.net", + "examplenew.com" + ], + "isNegativeFileExtensionMatch": false, + "isNegativePathMatch": false, + "securityPolicy": { + "policyId": "AAAA_81230" + }, + "sequence": 1, + "targetId": 3008967 +} \ No newline at end of file diff --git a/pkg/providers/appsec/testdata/TestResMatchTarget/MatchTargetUpdated.json b/pkg/providers/appsec/testdata/TestResMatchTarget/MatchTargetUpdated.json index 948a76b3b..24bb9805e 100644 --- a/pkg/providers/appsec/testdata/TestResMatchTarget/MatchTargetUpdated.json +++ b/pkg/providers/appsec/testdata/TestResMatchTarget/MatchTargetUpdated.json @@ -1,41 +1 @@ -{ - "type": "website", - "configId": 43253, - "configVersion": 15, - "defaultFile": "NO_MATCH", - "effectiveSecurityControls": { - "applyApplicationLayerControls": false, - "applyBotmanControls": false, - "applyNetworkLayerControls": false, - "applyRateControls": false, - "applyReputationControls": false, - "applySlowPostControls": false - }, - "fileExtensions": [ - "carb", - "pct", - "pdf", - "swf", - "cct", - "jpeg", - "js", - "wmls", - "hdml", - "pws" - ], - "filePaths": [ - "/cache/aaabbc*" - ], - "hostnames": [ - "m1.example.com", - "www.example.net", - "example.com" - ], - "isNegativeFileExtensionMatch": false, - "isNegativePathMatch": false, - "securityPolicy": { - "policyId": "AAAA_81230" - }, - "sequence": 1, - "targetId": 3008967 -} \ No newline at end of file +{"configId":43253,"configVersion":15,"defaultFile":"NO_MATCH","effectiveSecurityControls":{"applyApplicationLayerControls":false,"applyBotmanControls":false,"applyNetworkLayerControls":false,"applyRateControls":false,"applyReputationControls":false,"applySlowPostControls":false},"fileExtensions":["carb","pct","pdf","swf","cct","jpeg","js","hdml","wmls","pws"],"filePaths":["/cache/aaabbc*"],"hostnames":["m1.example.com","www.example.net","example.com","www.test.com"],"isNegativeFileExtensionMatch":false,"isNegativePathMatch":false,"securityPolicy":{"policyId":"AAAA_81230"},"sequence":1,"targetId":3008967,"type":"website"} \ No newline at end of file diff --git a/pkg/providers/appsec/testdata/TestResMatchTarget/UpdateMatchTarget.json b/pkg/providers/appsec/testdata/TestResMatchTarget/UpdateMatchTarget.json index 6af423687..5f08fba95 100644 --- a/pkg/providers/appsec/testdata/TestResMatchTarget/UpdateMatchTarget.json +++ b/pkg/providers/appsec/testdata/TestResMatchTarget/UpdateMatchTarget.json @@ -1,40 +1 @@ - { - "type": "website", - "configId": 43253, - "configVersion": 15, - "defaultFile": "NO_MATCH", - "effectiveSecurityControls": { - "applyApplicationLayerControls": false, - "applyBotmanControls": false, - "applyNetworkLayerControls": false, - "applyRateControls": false, - "applyReputationControls": false, - "applySlowPostControls": false - }, - "fileExtensions": [ - "carb", - "pct", - "pdf", - "swf", - "cct", - "jpeg", - "js", - "wmls", - "hdml", - "pws" - ], - "filePaths": [ - "/cache/aaabbc*" - ], - "hostnames": [ - "m1.example.com", - "www.example.net", - "example.com" - ], - "isNegativeFileExtensionMatch": false, - "isNegativePathMatch": false, - "securityPolicy": { - "policyId": "AAAA_81230" - }, - "sequence": 1 -} +{"configId":43253,"configVersion":15,"defaultFile":"NO_MATCH","effectiveSecurityControls":{"applyApplicationLayerControls":false,"applyBotmanControls":false,"applyNetworkLayerControls":false,"applyRateControls":false,"applyReputationControls":false,"applySlowPostControls":false},"fileExtensions":["carb","pct","pdf","swf","cct","jpeg","js","wmls","hdml","pws"],"filePaths":["/cache/aaabbc*"],"hostnames":["m1.example.com","www.example.net","example.com","www.test.com"],"isNegativeFileExtensionMatch":false,"isNegativePathMatch":false,"securityPolicy":{"policyId":"AAAA_81230"},"sequence":1,"type":"website"} \ No newline at end of file diff --git a/pkg/providers/appsec/testdata/TestResMatchTarget/match_by_id.tf b/pkg/providers/appsec/testdata/TestResMatchTarget/match_by_id.tf index 3e59caa3c..fc555284c 100644 --- a/pkg/providers/appsec/testdata/TestResMatchTarget/match_by_id.tf +++ b/pkg/providers/appsec/testdata/TestResMatchTarget/match_by_id.tf @@ -4,49 +4,47 @@ provider "akamai" { } resource "akamai_appsec_match_target" "test" { - config_id = 43253 - match_target = <<-EOF - { - "type": "website", - "configId": 43253, - "configVersion": 15, - "defaultFile": "NO_MATCH", - "effectiveSecurityControls": { - "applyApplicationLayerControls": false, - "applyBotmanControls": false, - "applyNetworkLayerControls": false, - "applyRateControls": false, - "applyReputationControls": false, - "applySlowPostControls": false + config_id = 43253 + match_target = jsonencode({ + "type" : "website", + "configId" : 43253, + "configVersion" : 15, + "defaultFile" : "NO_MATCH", + "effectiveSecurityControls" : { + "applyApplicationLayerControls" : false, + "applyBotmanControls" : false, + "applyNetworkLayerControls" : false, + "applyRateControls" : false, + "applyReputationControls" : false, + "applySlowPostControls" : false }, - "fileExtensions": [ - "carb", - "pct", - "pdf", - "swf", - "cct", - "jpeg", - "js", - "wmls", - "hdml", - "pws" + "fileExtensions" : [ + "carb", + "pct", + "pdf", + "swf", + "cct", + "jpeg", + "js", + "wmls", + "hdml", + "pws" ], - "filePaths": [ - "/cache/aaabbc*" + "filePaths" : [ + "/cache/aaabbc*" ], - "hostnames": [ - "m.example.com", - "www.example.net", - "example.com" + "hostnames" : [ + "m.example.com", + "www.example.net", + "example.com" ], - "isNegativeFileExtensionMatch": false, - "isNegativePathMatch": false, - "securityPolicy": { - "policyId": "AAAA_81230" + "isNegativeFileExtensionMatch" : false, + "isNegativePathMatch" : false, + "securityPolicy" : { + "policyId" : "AAAA_81230" }, - "sequence": 1 -} -EOF + "sequence" : 1 + }) } diff --git a/pkg/providers/appsec/testdata/TestResMatchTarget/update_by_id.tf b/pkg/providers/appsec/testdata/TestResMatchTarget/update_by_id.tf index 89345b769..c246124d7 100644 --- a/pkg/providers/appsec/testdata/TestResMatchTarget/update_by_id.tf +++ b/pkg/providers/appsec/testdata/TestResMatchTarget/update_by_id.tf @@ -4,22 +4,21 @@ provider "akamai" { } resource "akamai_appsec_match_target" "test" { - config_id = 43253 - match_target = <<-EOF - { - "type": "website", - "configId": 43253, - "configVersion": 15, - "defaultFile": "NO_MATCH", - "effectiveSecurityControls": { - "applyApplicationLayerControls": false, - "applyBotmanControls": false, - "applyNetworkLayerControls": false, - "applyRateControls": false, - "applyReputationControls": false, - "applySlowPostControls": false - }, - "fileExtensions": [ + config_id = 43253 + match_target = jsonencode( + { + "configId" : 43253, + "configVersion" : 15, + "defaultFile" : "NO_MATCH", + "effectiveSecurityControls" : { + "applyApplicationLayerControls" : false, + "applyBotmanControls" : false, + "applyNetworkLayerControls" : false, + "applyRateControls" : false, + "applyReputationControls" : false, + "applySlowPostControls" : false + }, + "fileExtensions" : [ "carb", "pct", "pdf", @@ -30,23 +29,24 @@ resource "akamai_appsec_match_target" "test" { "wmls", "hdml", "pws" - ], - "filePaths": [ + ], + "filePaths" : [ "/cache/aaabbc*" - ], - "hostnames": [ + ], + "hostnames" : [ "m1.example.com", "www.example.net", - "example.com" - ], - "isNegativeFileExtensionMatch": false, - "isNegativePathMatch": false, - "securityPolicy": { - "policyId": "AAAA_81230" - }, - "sequence": 1 -} -EOF + "example.com", + "www.test.com" + ], + "isNegativeFileExtensionMatch" : false, + "isNegativePathMatch" : false, + "securityPolicy" : { + "policyId" : "AAAA_81230" + }, + "sequence" : 1, + "type" : "website", + }) } diff --git a/pkg/providers/cloudlets/cloudlets.go b/pkg/providers/cloudlets/cloudlets.go index a9a434f0e..5e63118c8 100644 --- a/pkg/providers/cloudlets/cloudlets.go +++ b/pkg/providers/cloudlets/cloudlets.go @@ -3,5 +3,6 @@ package cloudlets import "github.com/akamai/terraform-provider-akamai/v5/pkg/providers/registry" func init() { - registry.RegisterPluginSubprovider(NewSubprovider()) + registry.RegisterPluginSubprovider(NewPluginSubprovider()) + registry.RegisterFrameworkSubprovider(NewFrameworkSubprovider()) } diff --git a/pkg/providers/cloudlets/data_akamai_cloudlets_api_prioritization_match_rule_test.go b/pkg/providers/cloudlets/data_akamai_cloudlets_api_prioritization_match_rule_test.go index f7091b99a..696b6cc0b 100644 --- a/pkg/providers/cloudlets/data_akamai_cloudlets_api_prioritization_match_rule_test.go +++ b/pkg/providers/cloudlets/data_akamai_cloudlets_api_prioritization_match_rule_test.go @@ -42,7 +42,7 @@ func TestDataCloudletsAPIPrioritizationMatchRule(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, test.configPath), @@ -107,7 +107,7 @@ func TestIncorrectDataCloudletsAPIPrioritizationMatchRule(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, test.configPath), diff --git a/pkg/providers/cloudlets/data_akamai_cloudlets_application_load_balancer.go b/pkg/providers/cloudlets/data_akamai_cloudlets_application_load_balancer.go index fb378bee5..944497bd0 100644 --- a/pkg/providers/cloudlets/data_akamai_cloudlets_application_load_balancer.go +++ b/pkg/providers/cloudlets/data_akamai_cloudlets_application_load_balancer.go @@ -229,7 +229,7 @@ func dataSourceCloudletsApplicationLoadBalancer() *schema.Resource { func dataApplicationLoadBalancerRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { meta := meta.Must(m) - client := inst.Client(meta) + client := Client(meta) log := meta.Log("Cloudlets", "dataApplicationLoadBalancerRead") log.Debug("Reading Load Balancer") diff --git a/pkg/providers/cloudlets/data_akamai_cloudlets_application_load_balancer_match_rule_test.go b/pkg/providers/cloudlets/data_akamai_cloudlets_application_load_balancer_match_rule_test.go index e57bc0c03..24db0a3ae 100644 --- a/pkg/providers/cloudlets/data_akamai_cloudlets_application_load_balancer_match_rule_test.go +++ b/pkg/providers/cloudlets/data_akamai_cloudlets_application_load_balancer_match_rule_test.go @@ -39,7 +39,7 @@ func TestDataCloudletsLoadBalancerMatchRule(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, test.configPath), @@ -94,7 +94,7 @@ func TestIncorrectDataCloudletsLoadBalancerMatchRule(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, test.configPath), diff --git a/pkg/providers/cloudlets/data_akamai_cloudlets_application_load_balancer_test.go b/pkg/providers/cloudlets/data_akamai_cloudlets_application_load_balancer_test.go index 3cd6fbd19..4d7ad3c0b 100644 --- a/pkg/providers/cloudlets/data_akamai_cloudlets_application_load_balancer_test.go +++ b/pkg/providers/cloudlets/data_akamai_cloudlets_application_load_balancer_test.go @@ -164,7 +164,7 @@ func TestDataApplicationLoadBalancer(t *testing.T) { test.init(client) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, test.configPath), diff --git a/pkg/providers/cloudlets/data_akamai_cloudlets_audience_segmentation_match_rule_test.go b/pkg/providers/cloudlets/data_akamai_cloudlets_audience_segmentation_match_rule_test.go index 7fb68751f..9f21b6a0a 100644 --- a/pkg/providers/cloudlets/data_akamai_cloudlets_audience_segmentation_match_rule_test.go +++ b/pkg/providers/cloudlets/data_akamai_cloudlets_audience_segmentation_match_rule_test.go @@ -42,7 +42,7 @@ func TestDataCloudletsAudienceSegmentationMatchRule(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, test.configPath), @@ -105,7 +105,7 @@ func TestIncorrectDataCloudletsAudienceSegmentationMatchRule(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, test.configPath), diff --git a/pkg/providers/cloudlets/data_akamai_cloudlets_edge_redirector_match_rule_test.go b/pkg/providers/cloudlets/data_akamai_cloudlets_edge_redirector_match_rule_test.go index 576d8eef2..d3e120846 100644 --- a/pkg/providers/cloudlets/data_akamai_cloudlets_edge_redirector_match_rule_test.go +++ b/pkg/providers/cloudlets/data_akamai_cloudlets_edge_redirector_match_rule_test.go @@ -45,7 +45,7 @@ func TestDataCloudletsEdgeRedirectorMatchRule(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, test.configPath), @@ -104,7 +104,7 @@ func TestIncorrectDataCloudletsEdgeRedirectorMatchRule(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, test.configPath), diff --git a/pkg/providers/cloudlets/data_akamai_cloudlets_forward_rewrite_match_rule_test.go b/pkg/providers/cloudlets/data_akamai_cloudlets_forward_rewrite_match_rule_test.go index 2093be2bb..689dc8daf 100644 --- a/pkg/providers/cloudlets/data_akamai_cloudlets_forward_rewrite_match_rule_test.go +++ b/pkg/providers/cloudlets/data_akamai_cloudlets_forward_rewrite_match_rule_test.go @@ -35,7 +35,7 @@ func TestDataCloudletsForwardRewriteMatchRule(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, test.configPath), @@ -86,7 +86,7 @@ func TestIncorrectDataCloudletsForwardRewriteMatchRule(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, test.configPath), diff --git a/pkg/providers/cloudlets/data_akamai_cloudlets_phased_release_match_rule_test.go b/pkg/providers/cloudlets/data_akamai_cloudlets_phased_release_match_rule_test.go index 2caf34148..25ea200eb 100644 --- a/pkg/providers/cloudlets/data_akamai_cloudlets_phased_release_match_rule_test.go +++ b/pkg/providers/cloudlets/data_akamai_cloudlets_phased_release_match_rule_test.go @@ -41,7 +41,7 @@ func TestDataCloudletsPhasedReleaseMatchRule(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, test.configPath), @@ -98,7 +98,7 @@ func TestIncorrectDataPhasedReleaseDeploymentMatchRule(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, test.configPath), diff --git a/pkg/providers/cloudlets/data_akamai_cloudlets_policy.go b/pkg/providers/cloudlets/data_akamai_cloudlets_policy.go index b7ddadcb3..8f16c0901 100644 --- a/pkg/providers/cloudlets/data_akamai_cloudlets_policy.go +++ b/pkg/providers/cloudlets/data_akamai_cloudlets_policy.go @@ -3,11 +3,11 @@ package cloudlets import ( "context" "encoding/json" - "errors" "fmt" "github.com/akamai/terraform-provider-akamai/v5/pkg/common/tf" "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" + "github.com/akamai/terraform-provider-akamai/v5/pkg/tools" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -287,41 +287,41 @@ func populateSchemaFieldsWithPolicyVersion(p *cloudlets.PolicyVersion, d *schema func dataSourceCloudletsPolicyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { meta := meta.Must(m) log := meta.Log("Cloudlets", "dataSourceCloudletsPolicyRead") - client := inst.Client(meta) + client := Client(meta) policyID, err := tf.GetIntValue("policy_id", d) if err != nil { return diag.FromErr(err) } - var version int64 + var version *int64 if v, err := tf.GetIntValue("version", d); err != nil { - if !errors.Is(err, tf.ErrNotFound) { - return diag.FromErr(err) - } - version, err = findLatestPolicyVersion(ctx, int64(policyID), client) + policyVersionStrategy := v2VersionStrategy{Client(meta)} + version, err = policyVersionStrategy.findLatestPolicyVersion(ctx, int64(policyID)) if err != nil { return diag.FromErr(err) } } else { - version = int64(v) + version = tools.Int64Ptr(int64(v)) } - log.Debug("Getting Policy Version") - policyVersion, err := client.GetPolicyVersion(ctx, cloudlets.GetPolicyVersionRequest{ - PolicyID: int64(policyID), - Version: version, - }) - if err != nil { - return diag.FromErr(err) - } - if policyVersion.Deleted { - return diag.Errorf("specified policy version is deleted: version = %d", version) - } + if version != nil { + log.Debug("Getting Policy Version") + policyVersion, err := client.GetPolicyVersion(ctx, cloudlets.GetPolicyVersionRequest{ + PolicyID: int64(policyID), + Version: *version, + }) + if err != nil { + return diag.FromErr(err) + } + if policyVersion.Deleted { + return diag.Errorf("specified policy version is deleted: version = %d", *version) + } - err = populateSchemaFieldsWithPolicyVersion(policyVersion, d) - if err != nil { - return diag.FromErr(err) + err = populateSchemaFieldsWithPolicyVersion(policyVersion, d) + if err != nil { + return diag.FromErr(err) + } } log.Debug("Getting Policy") @@ -338,7 +338,11 @@ func dataSourceCloudletsPolicyRead(ctx context.Context, d *schema.ResourceData, return diag.FromErr(err) } - d.SetId(fmt.Sprintf("%d:%d", policyID, version)) + if version != nil { + d.SetId(fmt.Sprintf("%d:%d", policyID, *version)) + } else { + d.SetId(fmt.Sprintf("%d", policyID)) + } return nil } diff --git a/pkg/providers/cloudlets/data_akamai_cloudlets_policy_activation.go b/pkg/providers/cloudlets/data_akamai_cloudlets_policy_activation.go new file mode 100644 index 000000000..c34fa41c6 --- /dev/null +++ b/pkg/providers/cloudlets/data_akamai_cloudlets_policy_activation.go @@ -0,0 +1,194 @@ +package cloudlets + +import ( + "context" + "fmt" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets" + v3 "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets/v3" + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/session" + "github.com/akamai/terraform-provider-akamai/v5/pkg/common/tf" + "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type policyActivationDataSourceModel struct { + ID types.String `tfsdk:"id"` + PolicyID types.Int64 `tfsdk:"policy_id"` + Network types.String `tfsdk:"network"` + Version types.Int64 `tfsdk:"version"` + Status types.String `tfsdk:"status"` + AssociatedProperties types.Set `tfsdk:"associated_properties"` +} + +var ( + _ datasource.DataSource = &policyActivationDataSource{} + _ datasource.DataSourceWithConfigure = &policyActivationDataSource{} +) + +type policyActivationDataSource struct { + meta meta.Meta +} + +// NewPolicyActivationDataSource returns a new capacity data source +func NewPolicyActivationDataSource() datasource.DataSource { + return &policyActivationDataSource{} +} + +// Metadata configures data source's meta information +func (d *policyActivationDataSource) Metadata(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = d.name() +} + +func (d *policyActivationDataSource) name() string { + return "akamai_cloudlets_policy_activation" +} + +// Configure configures data source at the beginning of the lifecycle +func (d *policyActivationDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + + m, ok := req.ProviderData.(meta.Meta) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected meta.Meta, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + } + d.meta = m +} + +// Schema is used to define data source's terraform schema +func (d *policyActivationDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + Description: "Cloudlets Policy Activation", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + DeprecationMessage: "Required by the terraform plugin testing framework.", + Description: "ID of the data source.", + }, + "policy_id": schema.Int64Attribute{ + Required: true, + Description: "Identifies the policy.", + }, + "network": schema.StringAttribute{ + Required: true, + Description: "The networks where you can get activated policy version (options are Staging and Production).", + Validators: []validator.String{stringvalidator.OneOfCaseInsensitive("production", "prod", "p", "staging", "stag", "s")}, + }, + "version": schema.Int64Attribute{ + Computed: true, + Description: "Policy version that is activated on provided network.", + }, + "status": schema.StringAttribute{ + Computed: true, + Description: "Activation status for this Cloudlets policy.", + }, + "associated_properties": schema.SetAttribute{ + ElementType: types.StringType, + Optional: true, + Description: "List of associated properties for non-shared cloudlets activation policy.", + }, + }, + } +} + +// Read is called when the provider must read data source values in order to update state +func (d *policyActivationDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + logger := d.meta.Log("Cloudlets", "Read") + logger.Debug("Cloudlets Policy Activation DataSource Read") + ctx = session.ContextWithOptions(ctx, session.WithContextLog(logger)) + + var data policyActivationDataSourceModel + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + policyID := data.PolicyID.ValueInt64() + network := data.Network.ValueString() + + strategy, _, err := discoverActivationStrategy(ctx, policyID, d.meta, logger) + if err != nil { + resp.Diagnostics.AddError("Reading Policy Failed", err.Error()) + return + } + + dat, err := strategy.getPolicyActivation(ctx, policyID, network) + if err != nil { + resp.Diagnostics.AddError("Reading Policy Failed", err.Error()) + return + } + data = *dat + data.ID = types.StringValue(fmt.Sprintf("%s:%s", data.PolicyID, data.Network)) + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) +} + +func (strategy *v2ActivationStrategy) getPolicyActivation(ctx context.Context, policyID int64, network string) (*policyActivationDataSourceModel, error) { + listPolicyActivationsRequest := cloudlets.ListPolicyActivationsRequest{ + PolicyID: policyID, + Network: cloudlets.PolicyActivationNetwork(network), + } + + activations, err := strategy.client.ListPolicyActivations(ctx, listPolicyActivationsRequest) + if err != nil { + return nil, fmt.Errorf("%v read: reading list of policy activations failed. %s", ErrPolicyActivation, err.Error()) + } + + if len(activations) == 0 { + return nil, fmt.Errorf("%v read: cannot find any activation for the given policy '%d' and network '%s'", ErrPolicyActivation, policyID, network) + } + + activations = sortPolicyActivationsByDate(activations) + associatedProperties := getActiveProperties(activations) + + ap, d := types.SetValueFrom(ctx, types.StringType, associatedProperties) + if d.HasError() { + return nil, fmt.Errorf(d.Errors()[0].Summary()) + } + data := policyActivationDataSourceModel{ + PolicyID: types.Int64Value(policyID), + Network: types.StringValue(network), + Version: types.Int64Value(activations[0].PolicyInfo.Version), + Status: types.StringValue(string(activations[0].PolicyInfo.Status)), + AssociatedProperties: ap, + } + return &data, nil +} + +func (strategy *v3ActivationStrategy) getPolicyActivation(ctx context.Context, policyID int64, network string) (*policyActivationDataSourceModel, error) { + getPolicyRequest := v3.GetPolicyRequest{ + PolicyID: policyID, + } + + policy, err := strategy.client.GetPolicy(ctx, getPolicyRequest) + if err != nil { + return nil, fmt.Errorf("%v read: reading policy failed. %s", ErrPolicyActivation, err.Error()) + } + + var effective *v3.PolicyActivation + switch tf.StateNetwork(network) { + case "staging": + effective = policy.CurrentActivations.Staging.Effective + case "production": + effective = policy.CurrentActivations.Production.Effective + } + if effective == nil || effective.Operation != v3.OperationActivation { + return nil, fmt.Errorf("%v read: cannot find any activation for the given policy '%d' and network '%s'", ErrPolicyActivation, policyID, network) + } + data := policyActivationDataSourceModel{ + PolicyID: types.Int64Value(policyID), + Network: types.StringValue(network), + Version: types.Int64Value(effective.PolicyVersion), + Status: types.StringValue(string(effective.Status)), + AssociatedProperties: types.SetNull(types.StringType), + } + return &data, nil +} diff --git a/pkg/providers/cloudlets/data_akamai_cloudlets_policy_activation_test.go b/pkg/providers/cloudlets/data_akamai_cloudlets_policy_activation_test.go new file mode 100644 index 000000000..23fe32a84 --- /dev/null +++ b/pkg/providers/cloudlets/data_akamai_cloudlets_policy_activation_test.go @@ -0,0 +1,469 @@ +package cloudlets + +import ( + "fmt" + "net/http" + "time" + + "regexp" + "testing" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets" + v3 "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets/v3" + "github.com/akamai/terraform-provider-akamai/v5/pkg/common/testutils" + "github.com/akamai/terraform-provider-akamai/v5/pkg/tools" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/stretchr/testify/mock" +) + +type testDataForNonSharedPolicyActivation struct { + id string + policyID int64 + version int64 + groupID int64 + name string + description string + matchRules cloudlets.MatchRules + properties []string + network cloudlets.PolicyActivationNetwork +} + +type testDataForSharedPolicyActivation struct { + id string + policyID int64 + version int64 + groupID int64 + name string + cloudletType v3.CloudletType + description string + matchRules v3.MatchRules + warnings []v3.MatchRulesWarning + activations v3.CurrentActivations +} + +func TestNonSharedPolicyActivationDataSource(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + config string + data testDataForNonSharedPolicyActivation + expectError *regexp.Regexp + init func(*cloudlets.Mock, testDataForNonSharedPolicyActivation) + check resource.TestCheckFunc + }{ + "no property id": { + config: "no_property_id.tf", + expectError: regexp.MustCompile(`Error: Missing required argument`), + }, + "no network": { + config: "no_network.tf", + expectError: regexp.MustCompile(`Error: Missing required argument`), + }, + "policy without activation": { + config: "activation.tf", + data: testDataForNonSharedPolicyActivation{ + id: "akamai_cloudlets_shared_policy", + policyID: 1, + version: 2, + groupID: 12, + name: "TestName", + description: "Description", + network: cloudlets.PolicyActivationNetworkStaging, + properties: []string{"prp_0", "prp_1"}, + }, + init: func(m2 *cloudlets.Mock, data testDataForNonSharedPolicyActivation) { + mockGetPolicyV2(m2, data, nil, 1) + expectListPolicyActivations(m2, data.policyID, data.version, data.network, data.properties, cloudlets.PolicyActivationStatusActive, "", 0, nil).Times(1) + }, + expectError: regexp.MustCompile(`(?s)policy activation read: cannot find any activation for the given policy '1'.+and network 'staging'`), + }, + "policy with activation": { + config: "activation.tf", + data: testDataForNonSharedPolicyActivation{ + id: "akamai_cloudlets_shared_policy", + policyID: 1, + version: 2, + groupID: 12, + name: "TestName", + description: "Description", + network: cloudlets.PolicyActivationNetworkStaging, + properties: []string{"prp_0", "prp_1"}, + }, + init: func(m2 *cloudlets.Mock, data testDataForNonSharedPolicyActivation) { + activations := make([]cloudlets.PolicyActivation, len(data.properties)) + for _, p := range data.properties { + activations = append(activations, cloudlets.PolicyActivation{APIVersion: "1.0", Network: data.network, PolicyInfo: cloudlets.PolicyInfo{ + PolicyID: data.policyID, Version: data.version, Status: cloudlets.PolicyActivationStatusInactive, + }, PropertyInfo: cloudlets.PropertyInfo{Name: p}}) + } + mockGetPolicyV2(m2, data, nil, 5) + expectListPolicyActivations(m2, data.policyID, data.version, data.network, data.properties, cloudlets.PolicyActivationStatusActive, "", 1, nil).Times(5) + }, + check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.akamai_cloudlets_policy_activation.test", "policy_id", "1"), + resource.TestCheckResourceAttr("data.akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("data.akamai_cloudlets_policy_activation.test", "version", "2"), + resource.TestCheckResourceAttr("data.akamai_cloudlets_policy_activation.test", "status", "active"), + ), + }, + "api error": { + config: "activation.tf", + data: testDataForNonSharedPolicyActivation{ + policyID: 1, + version: 2, + groupID: 12, + name: "Name", + description: "Description", + matchRules: cloudlets.MatchRules{ + cloudlets.MatchRuleER{ + Name: "Name", + Type: cloudlets.MatchRuleTypeER, + Start: 1, + End: 2, + ID: 123, + UseRelativeURL: "/url1", + StatusCode: 200, + RedirectURL: "/url2", + MatchURL: "/url3", + }, + }, + }, + expectError: regexp.MustCompile(`Error: Reading Policy Failed`), + init: func(m2 *cloudlets.Mock, data testDataForNonSharedPolicyActivation) { + mockGetPolicyV2WithError(m2, data.policyID, &cloudlets.Error{StatusCode: http.StatusNotFound}, 1) + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + clientV2 := &cloudlets.Mock{} + if test.init != nil { + test.init(clientV2, test.data) + } + useClient(clientV2, func() { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{{ + Config: testutils.LoadFixtureString(t, fmt.Sprintf("testdata/TestDataCloudletsPolicyActivation/%s", test.config)), + Check: test.check, + ExpectError: test.expectError, + }}, + }) + }) + clientV2.AssertExpectations(t) + }) + } +} + +func TestSharedPolicyActivationDataSource(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + config string + data testDataForSharedPolicyActivation + expectError *regexp.Regexp + init func(*cloudlets.Mock, *v3.Mock, testDataForSharedPolicyActivation) + check resource.TestCheckFunc + }{ + "no property id": { + config: "no_property_id.tf", + expectError: regexp.MustCompile(`Error: Missing required argument`), + }, + "no network": { + config: "no_network.tf", + expectError: regexp.MustCompile(`Error: Missing required argument`), + }, + "no shared policy activation": { + config: "activation.tf", + data: testDataForSharedPolicyActivation{ + id: "akamai_cloudlets_shared_policy", + policyID: 1, + version: 0, + groupID: 12, + name: "TestName", + cloudletType: v3.CloudletTypeAP, + description: "Description", + }, + init: func(m2 *cloudlets.Mock, m3 *v3.Mock, data testDataForSharedPolicyActivation) { + mockGetPolicyV2WithError(m2, data.policyID, &cloudlets.Error{StatusCode: http.StatusNotFound}, 1) + mockGetPolicyV3(m3, data, 2) + }, + expectError: regexp.MustCompile(`(?s)policy activation read: cannot find any activation for the given policy '1'.+and network 'staging'`), + }, + "no shared policy": { + config: "activation.tf", + data: testDataForSharedPolicyActivation{ + id: "akamai_cloudlets_shared_policy", + policyID: 1, + version: 0, + groupID: 12, + name: "TestName", + cloudletType: v3.CloudletTypeAP, + description: "Description", + }, + init: func(m2 *cloudlets.Mock, m3 *v3.Mock, data testDataForSharedPolicyActivation) { + mockGetPolicyV2WithError(m2, data.policyID, &cloudlets.Error{StatusCode: http.StatusNotFound}, 1) + mockGetPolicyV3WithError(m3, data.policyID, &cloudlets.Error{StatusCode: http.StatusNotFound}, 1) + }, + expectError: regexp.MustCompile(`Error: Reading Policy Failed`), + }, + "shared policy activation on staging": { + config: "activation.tf", + data: testDataForSharedPolicyActivation{ + //id: "akamai_cloudlets_shared_policy", + policyID: 1, + version: 2, + groupID: 12, + name: "Name", + cloudletType: v3.CloudletTypeAP, + description: "Description", + matchRules: v3.MatchRules{ + v3.MatchRuleER{ + Name: "Name", + Type: v3.MatchRuleTypeER, + Start: 1, + End: 2, + ID: 123, + UseRelativeURL: "/url1", + StatusCode: 200, + RedirectURL: "/url2", + MatchURL: "/url3", + }, + }, + warnings: []v3.MatchRulesWarning{ + { + Detail: "TestDetail", + JSONPointer: "TestPointer", + Title: "TestTitle", + Type: "TestType", + }, + }, + activations: v3.CurrentActivations{ + Production: v3.ActivationInfo{ + Effective: &v3.PolicyActivation{ + CreatedBy: "TestUser", + CreatedDate: time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC), + FinishDate: &time.Time{}, + ID: 111, + Network: v3.ProductionNetwork, + Operation: v3.OperationActivation, + PolicyID: 1, + Status: v3.ActivationStatusSuccess, + PolicyVersion: 2, + PolicyVersionDeleted: false, + }, + Latest: &v3.PolicyActivation{ + CreatedBy: "TestUser", + CreatedDate: time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC), + FinishDate: &time.Time{}, + ID: 112, + Network: v3.ProductionNetwork, + Operation: v3.OperationActivation, + PolicyID: 1, + Status: v3.ActivationStatusSuccess, + PolicyVersion: 2, + PolicyVersionDeleted: false, + }, + }, + Staging: v3.ActivationInfo{ + Effective: &v3.PolicyActivation{ + CreatedBy: "TestUser", + CreatedDate: time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC), + FinishDate: &time.Time{}, + ID: 113, + Network: v3.StagingNetwork, + Operation: v3.OperationActivation, + PolicyID: 1, + Status: v3.ActivationStatusSuccess, + PolicyVersion: 2, + PolicyVersionDeleted: false, + }, + Latest: &v3.PolicyActivation{ + CreatedBy: "TestUser", + CreatedDate: time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC), + FinishDate: &time.Time{}, + ID: 114, + Network: v3.StagingNetwork, + Operation: v3.OperationActivation, + PolicyID: 1, + Status: v3.ActivationStatusSuccess, + PolicyVersion: 2, + PolicyVersionDeleted: false, + }, + }, + }, + }, + check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.akamai_cloudlets_policy_activation.test", "policy_id", "1"), + resource.TestCheckResourceAttr("data.akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("data.akamai_cloudlets_policy_activation.test", "version", "2"), + resource.TestCheckResourceAttr("data.akamai_cloudlets_policy_activation.test", "status", "SUCCESS"), + ), + init: func(m2 *cloudlets.Mock, m3 *v3.Mock, data testDataForSharedPolicyActivation) { + mockGetPolicyV2WithError(m2, data.policyID, &cloudlets.Error{StatusCode: http.StatusNotFound}, 5) + mockGetPolicyV3(m3, data, 10) + }, + }, + "api error": { + config: "activation.tf", + data: testDataForSharedPolicyActivation{ + //id: "akamai_cloudlets_shared_policy", + policyID: 1, + version: 2, + groupID: 12, + name: "Name", + cloudletType: v3.CloudletTypeAP, + description: "Description", + matchRules: v3.MatchRules{ + v3.MatchRuleER{ + Name: "Name", + Type: v3.MatchRuleTypeER, + Start: 1, + End: 2, + ID: 123, + UseRelativeURL: "/url1", + StatusCode: 200, + RedirectURL: "/url2", + MatchURL: "/url3", + }, + }, + warnings: []v3.MatchRulesWarning{ + { + Detail: "TestDetail", + JSONPointer: "TestPointer", + Title: "TestTitle", + Type: "TestType", + }, + }, + activations: v3.CurrentActivations{ + Production: v3.ActivationInfo{ + Effective: &v3.PolicyActivation{ + CreatedBy: "TestUser", + CreatedDate: time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC), + FinishDate: &time.Time{}, + ID: 111, + Network: v3.ProductionNetwork, + Operation: v3.OperationActivation, + PolicyID: 1, + Status: v3.ActivationStatusSuccess, + PolicyVersion: 2, + PolicyVersionDeleted: false, + }, + Latest: &v3.PolicyActivation{ + CreatedBy: "TestUser", + CreatedDate: time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC), + FinishDate: &time.Time{}, + ID: 112, + Network: v3.ProductionNetwork, + Operation: v3.OperationActivation, + PolicyID: 1, + Status: v3.ActivationStatusSuccess, + PolicyVersion: 2, + PolicyVersionDeleted: false, + }, + }, + Staging: v3.ActivationInfo{ + Effective: &v3.PolicyActivation{ + CreatedBy: "TestUser", + CreatedDate: time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC), + FinishDate: &time.Time{}, + ID: 113, + Network: v3.StagingNetwork, + Operation: v3.OperationActivation, + PolicyID: 1, + Status: v3.ActivationStatusSuccess, + PolicyVersion: 2, + PolicyVersionDeleted: false, + }, + Latest: &v3.PolicyActivation{ + CreatedBy: "TestUser", + CreatedDate: time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC), + FinishDate: &time.Time{}, + ID: 114, + Network: v3.StagingNetwork, + Operation: v3.OperationActivation, + PolicyID: 1, + Status: v3.ActivationStatusSuccess, + PolicyVersion: 2, + PolicyVersionDeleted: false, + }, + }, + }, + }, + expectError: regexp.MustCompile(`policy activation read: reading policy failed`), + init: func(m2 *cloudlets.Mock, m3 *v3.Mock, data testDataForSharedPolicyActivation) { + mockGetPolicyV2WithError(m2, data.policyID, &cloudlets.Error{StatusCode: http.StatusNotFound}, 1) + mockGetPolicyV3(m3, data, 1) + mockGetPolicyV3WithError(m3, data.policyID, &cloudlets.Error{StatusCode: http.StatusInternalServerError}, 1) + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + clientV2 := &cloudlets.Mock{} + clientV3 := &v3.Mock{} + if test.init != nil { + test.init(clientV2, clientV3, test.data) + } + useClientV2AndV3(clientV2, clientV3, func() { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{{ + Config: testutils.LoadFixtureString(t, fmt.Sprintf("testdata/TestDataCloudletsPolicyActivation/%s", test.config)), + Check: test.check, + ExpectError: test.expectError, + }}, + }) + }) + clientV2.AssertExpectations(t) + clientV3.AssertExpectations(t) + }) + } +} + +func mockGetPolicyV2WithError(m *cloudlets.Mock, policyID int64, err error, times int) { + m.On("GetPolicy", mock.Anything, cloudlets.GetPolicyRequest{ + PolicyID: policyID, + }).Return(nil, err).Times(times) + return +} + +func mockGetPolicyV2(m *cloudlets.Mock, data testDataForNonSharedPolicyActivation, err error, times int) { + if err != nil { + m.On("GetPolicy", mock.Anything, cloudlets.GetPolicyRequest{ + PolicyID: data.policyID, + }).Return(nil, err).Times(times) + return + } + m.On("GetPolicy", mock.Anything, cloudlets.GetPolicyRequest{ + PolicyID: data.policyID, + }).Return(&cloudlets.Policy{ + Description: data.description, + GroupID: data.groupID, + PolicyID: data.policyID, + Name: data.name, + }, nil).Times(times) +} + +func mockGetPolicyV3(m *v3.Mock, data testDataForSharedPolicyActivation, times int) { + m.On("GetPolicy", mock.Anything, v3.GetPolicyRequest{ + PolicyID: data.policyID, + }).Return(&v3.Policy{ + CloudletType: data.cloudletType, + CurrentActivations: data.activations, + Description: tools.StringPtr(data.description), + GroupID: data.groupID, + ID: data.policyID, + Name: data.name, + }, nil).Times(times) +} + +func mockGetPolicyV3WithError(m *v3.Mock, policyID int64, err error, times int) { + m.On("GetPolicy", mock.Anything, v3.GetPolicyRequest{ + PolicyID: policyID, + }).Return(nil, err).Times(times) +} diff --git a/pkg/providers/cloudlets/data_akamai_cloudlets_policy_test.go b/pkg/providers/cloudlets/data_akamai_cloudlets_policy_test.go index 1fdb70498..3ddaf49a0 100644 --- a/pkg/providers/cloudlets/data_akamai_cloudlets_policy_test.go +++ b/pkg/providers/cloudlets/data_akamai_cloudlets_policy_test.go @@ -238,6 +238,41 @@ func TestDataCloudletsPolicy(t *testing.T) { }, withError: regexp.MustCompile("specified policy is deleted"), }, + "no version for a policy": { + configPath: "testdata/TestDataCloudletsPolicy/policy.tf", + listPolicyVersionsReturn: []cloudlets.PolicyVersion{}, + getPolicyReturn: cloudlets.Policy{ + Location: "/cloudlets/api/v2/policies/1234", + PolicyID: 1234, + GroupID: 2345, + Name: "SomeName", + Description: "Fancy Description", + CreatedBy: "jsmith", + CreateDate: 1631190136928, + LastModifiedBy: "jsmith", + LastModifiedDate: 1631190136928, + CloudletID: 0, + CloudletCode: "ER", + APIVersion: "2.0", + Deleted: false, + }, + checkFuncs: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("data.akamai_cloudlets_policy.test", "id", "1234"), + resource.TestCheckNoResourceAttr("data.akamai_cloudlets_policy.test", "version"), + resource.TestCheckResourceAttr("data.akamai_cloudlets_policy.test", "group_id", "2345"), + resource.TestCheckResourceAttr("data.akamai_cloudlets_policy.test", "name", "SomeName"), + resource.TestCheckResourceAttr("data.akamai_cloudlets_policy.test", "description", "Fancy Description"), + resource.TestCheckNoResourceAttr("data.akamai_cloudlets_policy.test", "version_description"), + resource.TestCheckResourceAttr("data.akamai_cloudlets_policy.test", "cloudlet_id", "0"), + resource.TestCheckResourceAttr("data.akamai_cloudlets_policy.test", "cloudlet_code", "ER"), + resource.TestCheckResourceAttr("data.akamai_cloudlets_policy.test", "api_version", "2.0"), + resource.TestCheckNoResourceAttr("data.akamai_cloudlets_policy.test", "revision_id"), + resource.TestCheckNoResourceAttr("data.akamai_cloudlets_policy.test", "rules_locked"), + resource.TestCheckNoResourceAttr("data.akamai_cloudlets_policy.test", "match_rules"), + resource.TestCheckNoResourceAttr("data.akamai_cloudlets_policy.test", "match_rule_format"), + resource.TestCheckNoResourceAttr("data.akamai_cloudlets_policy.test", "warnings"), + }, + }, } for testName, test := range tests { @@ -248,7 +283,7 @@ func TestDataCloudletsPolicy(t *testing.T) { client.On("GetPolicy", mock.Anything, mock.Anything).Return(&test.getPolicyReturn, nil) client.On("GetPolicyVersion", mock.Anything, mock.Anything).Return(&test.getPolicyVersionReturn, nil) resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, test.configPath), diff --git a/pkg/providers/cloudlets/data_akamai_cloudlets_request_control_match_rule_test.go b/pkg/providers/cloudlets/data_akamai_cloudlets_request_control_match_rule_test.go index 6aa8b188a..e8903f6dc 100644 --- a/pkg/providers/cloudlets/data_akamai_cloudlets_request_control_match_rule_test.go +++ b/pkg/providers/cloudlets/data_akamai_cloudlets_request_control_match_rule_test.go @@ -38,7 +38,7 @@ func TestDataCloudletsRequestControlMatchRule(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, test.configPath), @@ -101,7 +101,7 @@ func TestIncorrectDataCloudletsRequestControlMatchRule(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, test.configPath), diff --git a/pkg/providers/cloudlets/data_akamai_cloudlets_shared_policy.go b/pkg/providers/cloudlets/data_akamai_cloudlets_shared_policy.go new file mode 100644 index 000000000..ff516a90c --- /dev/null +++ b/pkg/providers/cloudlets/data_akamai_cloudlets_shared_policy.go @@ -0,0 +1,330 @@ +package cloudlets + +import ( + "context" + "encoding/json" + "errors" + "fmt" + + v3 "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets/v3" + "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +type sharedPolicyModel struct { + ID types.String `tfsdk:"id"` + PolicyID types.Int64 `tfsdk:"policy_id"` + Version types.Int64 `tfsdk:"version"` + VersionDescription types.String `tfsdk:"version_description"` + GroupID types.Int64 `tfsdk:"group_id"` + Name types.String `tfsdk:"name"` + CloudletType types.String `tfsdk:"cloudlet_type"` + Description types.String `tfsdk:"description"` + MatchRules types.String `tfsdk:"match_rules"` + Warnings types.String `tfsdk:"warnings"` + Activations *activationModel `tfsdk:"activations"` +} + +type activationModel struct { + Production activationInfoModel `tfsdk:"production"` + Staging activationInfoModel `tfsdk:"staging"` +} + +type activationInfoModel struct { + Effective *policyActivationModel `tfsdk:"effective"` + Latest *policyActivationModel `tfsdk:"latest"` +} + +type policyActivationModel struct { + ActivationID types.Int64 `tfsdk:"activation_id"` + CreatedBy types.String `tfsdk:"created_by"` + CreatedDate types.String `tfsdk:"created_date"` + FinishDate types.String `tfsdk:"finish_date"` + Network types.String `tfsdk:"network"` + Operation types.String `tfsdk:"operation"` + PolicyID types.Int64 `tfsdk:"policy_id"` + PolicyVersion types.Int64 `tfsdk:"policy_version"` + Status types.String `tfsdk:"status"` + PolicyVersionDeleted types.Bool `tfsdk:"policy_version_deleted"` +} + +var ( + _ datasource.DataSource = &sharedPolicyDataSource{} + _ datasource.DataSourceWithConfigure = &sharedPolicyDataSource{} +) + +type sharedPolicyDataSource struct { + meta meta.Meta +} + +// NewSharedPolicyDataSource returns a new cloudlets shared policy data source +func NewSharedPolicyDataSource() datasource.DataSource { + return &sharedPolicyDataSource{} +} + +func (d *sharedPolicyDataSource) name() string { + return "akamai_cloudlets_shared_policy" +} + +// Metadata configures data source's meta information +func (d *sharedPolicyDataSource) Metadata(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = d.name() +} + +// Configure configures data source at the beginning of the lifecycle +func (d *sharedPolicyDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + // ProviderData is nil when Configure is run first time as part of ValidateDataSourceConfig in framework provider + return + } + + defer func() { + if r := recover(); r != nil { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected meta.Meta, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + } + }() + + d.meta = meta.Must(req.ProviderData) +} + +// Schema is used to define data source's terraform schema +func (d *sharedPolicyDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + policyActivationAttributes := map[string]schema.Attribute{ + "activation_id": schema.Int64Attribute{ + Computed: true, + Description: "Identifies the activation.", + }, + "created_by": schema.StringAttribute{ + Computed: true, + Description: "The username who created the activation.", + }, + "created_date": schema.StringAttribute{ + Computed: true, + Description: "ISO 8601 timestamp indicating when the activation was created.", + }, + "finish_date": schema.StringAttribute{ + Computed: true, + Description: "ISO 8601 timestamp indicating when the activation ended, either successfully or unsuccessfully. You can check details of unsuccessful attempts in 'failureDetails'.", + }, + "network": schema.StringAttribute{ + Computed: true, + Description: "The networks where you can activate or deactivate the policy version, either 'PRODUCTION' or 'STAGING'.", + }, + "operation": schema.StringAttribute{ + Computed: true, + Description: "The operations that you can perform on a policy version, either 'ACTIVATION' or 'DEACTIVATION'.", + }, + "policy_id": schema.Int64Attribute{ + Computed: true, + Description: "Identifies the shared policy.", + }, + "policy_version": schema.Int64Attribute{ + Computed: true, + Description: "The number of the policy version.", + }, + "status": schema.StringAttribute{ + Computed: true, + Description: "The status of the operation, either 'IN_PROGRESS', 'SUCCESS', or 'FAILED'.", + }, + "policy_version_deleted": schema.BoolAttribute{ + Computed: true, + Description: "Indicates if the policy version is deleted.", + }, + } + activationBlock := map[string]schema.Block{ + "effective": schema.SingleNestedBlock{ + Description: "The status of the activation that's currently in use on this network, or null if the policy has no activations.", + Attributes: policyActivationAttributes, + }, + "latest": schema.SingleNestedBlock{ + Description: "The status of the latest activation or null if the policy has no activations.", + Attributes: policyActivationAttributes, + }, + } + + resp.Schema = schema.Schema{ + Description: "Cloudlets Shared Policy", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + DeprecationMessage: "Required by the terraform plugin testing framework, always set to `akamai_cloudlets_shared_policy`.", + Description: "ID of the data source.", + }, + "policy_id": schema.Int64Attribute{ + Required: true, + Description: "An integer ID that is associated with a policy.", + }, + "version": schema.Int64Attribute{ + Optional: true, + Description: "The number of the policy version.", + }, + "version_description": schema.StringAttribute{ + Computed: true, + Description: "A human-readable label for the policy version.", + }, + "group_id": schema.Int64Attribute{ + Computed: true, + Description: "Identifies the group where to which policy is assigned.", + }, + "name": schema.StringAttribute{ + Computed: true, + Description: "The name of the policy.", + }, + "cloudlet_type": schema.StringAttribute{ + Computed: true, + Description: "The two-letter code of the Cloudlet that the shared policy is for (AP, AS, CD, ER, FR, IG).", + }, + "description": schema.StringAttribute{ + Computed: true, + Description: "A human-readable label for the policy.", + }, + "match_rules": schema.StringAttribute{ + Computed: true, + Description: "A list of Cloudlet-specific match rules for this shared policy as a JSON", + }, + "warnings": schema.StringAttribute{ + Computed: true, + Description: "A JSON encoded list of warnings.", + }, + }, + Blocks: map[string]schema.Block{ + "activations": schema.SingleNestedBlock{ + Description: "Information about the active policy version that's currently in use and the status of the most recent activation or deactivation operation on the policy's versions for the production and staging networks.", + Blocks: map[string]schema.Block{ + "production": schema.SingleNestedBlock{ + Description: "The policy version number that's currently in use on this network and the status of the most recent activation or deactivation operation for this policy's versions.", + Blocks: activationBlock, + }, + "staging": schema.SingleNestedBlock{ + Description: "The policy version number that's currently in use on this network and the status of the most recent activation or deactivation operation for this policy's versions.", + Blocks: activationBlock, + }, + }, + }, + }, + } +} + +// Read is called when the provider must read data source values in order to update state +func (d *sharedPolicyDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + tflog.Debug(ctx, "Cloudlets Shared Policy DataSource Read") + + var data sharedPolicyModel + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + client := ClientV3(d.meta) + policy, err := client.GetPolicy(ctx, v3.GetPolicyRequest{ + PolicyID: data.PolicyID.ValueInt64(), + }) + if err != nil { + if errors.Is(err, v3.ErrPolicyNotFound) { + resp.Diagnostics.AddError("Policy does not exist or is not of 'SHARED' type", err.Error()) + return + } + resp.Diagnostics.AddError("Reading Cloudlets Shared Policy Failed", err.Error()) + return + } + + version := data.Version.ValueInt64() + if version == 0 { + policyVersions, err := client.ListPolicyVersions(ctx, v3.ListPolicyVersionsRequest{ + PolicyID: data.PolicyID.ValueInt64(), + }) + if err != nil { + resp.Diagnostics.AddError("Reading Cloudlets Shared Policy Failed", err.Error()) + return + } + + if len(policyVersions.PolicyVersions) != 0 { + version = policyVersions.PolicyVersions[0].PolicyVersion + } + } + + var policyVersion *v3.PolicyVersion + if version != 0 { + policyVersion, err = client.GetPolicyVersion(ctx, v3.GetPolicyVersionRequest{ + PolicyID: data.PolicyID.ValueInt64(), + PolicyVersion: version, + }) + if err != nil { + resp.Diagnostics.AddError("Reading Cloudlets Shared Policy Failed", err.Error()) + return + } + + matchRulesWarnings, err := json.Marshal(policyVersion.MatchRulesWarnings) + if err != nil { + resp.Diagnostics.AddError("Reading Cloudlets Shared Policy Failed", err.Error()) + return + } + data.Warnings = types.StringValue(string(matchRulesWarnings)) + matchRules, err := json.Marshal(policyVersion.MatchRules) + if err != nil { + resp.Diagnostics.AddError("Reading Cloudlets Shared Policy Failed", err.Error()) + return + } + data.MatchRules = types.StringValue(string(matchRules)) + data.Version = types.Int64Value(version) + if policyVersion.Description != nil { + data.VersionDescription = types.StringValue(*policyVersion.Description) + } + } + + data.setPolicyData(policy) + data.setActivations(policy.CurrentActivations) + + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) +} + +func (m *sharedPolicyModel) setPolicyData(policy *v3.Policy) { + m.CloudletType = types.StringValue(string(policy.CloudletType)) + m.Name = types.StringValue(policy.Name) + m.ID = types.StringValue("akamai_cloudlets_shared_policy") + m.GroupID = types.Int64Value(policy.GroupID) + if policy.Description != nil { + m.Description = types.StringValue(*policy.Description) + } +} + +func (m *sharedPolicyModel) setActivations(activations v3.CurrentActivations) { + actMod := &activationModel{ + Production: activationInfoModel{}, + Staging: activationInfoModel{}, + } + if activations.Production.Effective != nil { + actMod.Production.Effective = getActivationModel(activations.Production.Effective) + } + if activations.Production.Latest != nil { + actMod.Production.Latest = getActivationModel(activations.Production.Latest) + } + if activations.Staging.Effective != nil { + actMod.Staging.Effective = getActivationModel(activations.Staging.Effective) + } + if activations.Staging.Latest != nil { + actMod.Staging.Latest = getActivationModel(activations.Staging.Latest) + } + m.Activations = actMod +} + +func getActivationModel(activation *v3.PolicyActivation) *policyActivationModel { + return &policyActivationModel{ + ActivationID: types.Int64Value(activation.ID), + CreatedBy: types.StringValue(activation.CreatedBy), + CreatedDate: types.StringValue(activation.CreatedDate.String()), + FinishDate: types.StringValue(activation.FinishDate.String()), + Network: types.StringValue(string(activation.Network)), + Operation: types.StringValue(string(activation.Operation)), + PolicyID: types.Int64Value(activation.PolicyID), + PolicyVersion: types.Int64Value(activation.PolicyVersion), + Status: types.StringValue(string(activation.Status)), + PolicyVersionDeleted: types.BoolValue(activation.PolicyVersionDeleted), + } +} diff --git a/pkg/providers/cloudlets/data_akamai_cloudlets_shared_policy_test.go b/pkg/providers/cloudlets/data_akamai_cloudlets_shared_policy_test.go new file mode 100644 index 000000000..2135e8945 --- /dev/null +++ b/pkg/providers/cloudlets/data_akamai_cloudlets_shared_policy_test.go @@ -0,0 +1,400 @@ +package cloudlets + +import ( + "encoding/json" + "fmt" + "regexp" + "strconv" + "testing" + "time" + + v3 "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets/v3" + "github.com/akamai/terraform-provider-akamai/v5/pkg/common/testutils" + "github.com/akamai/terraform-provider-akamai/v5/pkg/tools" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/stretchr/testify/mock" +) + +type testDataForSharedPolicy struct { + id string + policyID int64 + version int64 + versionDescription *string + groupID int64 + name string + cloudletType v3.CloudletType + description string + matchRules v3.MatchRules + warnings []v3.MatchRulesWarning + activations v3.CurrentActivations +} + +func TestSharedPolicyDataSource(t *testing.T) { + t.Parallel() + + tests := map[string]struct { + config string + data testDataForSharedPolicy + expectError *regexp.Regexp + init func(*v3.Mock, testDataForSharedPolicy) + }{ + "success with version attribute - no activations": { + config: "with_version.tf", + data: testDataForSharedPolicy{ + id: "akamai_cloudlets_shared_policy", + policyID: 1, + version: 2, + versionDescription: tools.StringPtr("version 2 description"), + groupID: 12, + name: "TestName", + cloudletType: v3.CloudletTypeAP, + description: "TestDescription", + matchRules: v3.MatchRules{ + v3.MatchRuleER{ + Name: "TestName", + Type: v3.MatchRuleTypeER, + Start: 7, + End: 8, + ID: 789, + Matches: []v3.MatchCriteriaER{ + { + MatchType: "TestType", + MatchValue: "TestValue", + MatchOperator: "TestOperator", + CaseSensitive: true, + Negate: true, + CheckIPs: "1.1.1.1", + ObjectMatchValue: "1", + }, + }, + MatchesAlways: true, + UseRelativeURL: "/url1", + StatusCode: 200, + RedirectURL: "/url2", + MatchURL: "/url3", + UseIncomingQueryString: true, + UseIncomingSchemeAndHost: true, + Disabled: true, + }, + }, + warnings: []v3.MatchRulesWarning{ + { + Detail: "TestDetail", + JSONPointer: "TestPointer", + Title: "TestTitle", + Type: "TestType", + }, + }, + }, + init: func(m *v3.Mock, data testDataForSharedPolicy) { + mockGetPolicy(m, data, 5) + mockGetPolicyVersion(m, data, 5) + }, + }, + "success with no version attribute - no activations and no match rules and warnings": { + config: "no_version.tf", + data: testDataForSharedPolicy{ + id: "akamai_cloudlets_shared_policy", + policyID: 1, + version: 2, + groupID: 12, + name: "TestName", + cloudletType: v3.CloudletTypeAP, + description: "Description", + }, + init: func(m *v3.Mock, data testDataForSharedPolicy) { + mockGetPolicy(m, data, 5) + mockListPolicyVersions(m, data, 2, 5) + mockGetPolicyVersion(m, data, 5) + }, + }, + "success with no version attribute - no shared policy versions": { + config: "no_version.tf", + data: testDataForSharedPolicy{ + id: "akamai_cloudlets_shared_policy", + policyID: 1, + version: 0, + groupID: 12, + name: "TestName", + cloudletType: v3.CloudletTypeAP, + description: "Description", + }, + init: func(m *v3.Mock, data testDataForSharedPolicy) { + mockGetPolicy(m, data, 5) + mockListPolicyVersions(m, data, 2, 5) + }, + }, + "success with version attribute - all activations": { + config: "with_version.tf", + data: testDataForSharedPolicy{ + id: "akamai_cloudlets_shared_policy", + policyID: 1, + version: 2, + groupID: 12, + name: "Name", + cloudletType: v3.CloudletTypeAP, + description: "Description", + matchRules: v3.MatchRules{ + v3.MatchRuleER{ + Name: "Name", + Type: v3.MatchRuleTypeER, + Start: 1, + End: 2, + ID: 123, + UseRelativeURL: "/url1", + StatusCode: 200, + RedirectURL: "/url2", + MatchURL: "/url3", + }, + }, + warnings: []v3.MatchRulesWarning{ + { + Detail: "TestDetail", + JSONPointer: "TestPointer", + Title: "TestTitle", + Type: "TestType", + }, + }, + activations: v3.CurrentActivations{ + Production: v3.ActivationInfo{ + Effective: &v3.PolicyActivation{ + CreatedBy: "TestUser", + CreatedDate: time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC), + FinishDate: &time.Time{}, + ID: 111, + Network: v3.ProductionNetwork, + Operation: v3.OperationActivation, + PolicyID: 1, + Status: v3.ActivationStatusSuccess, + PolicyVersion: 2, + PolicyVersionDeleted: false, + }, + Latest: &v3.PolicyActivation{ + CreatedBy: "TestUser", + CreatedDate: time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC), + FinishDate: &time.Time{}, + ID: 112, + Network: v3.ProductionNetwork, + Operation: v3.OperationActivation, + PolicyID: 1, + Status: v3.ActivationStatusSuccess, + PolicyVersion: 2, + PolicyVersionDeleted: false, + }, + }, + Staging: v3.ActivationInfo{ + Effective: &v3.PolicyActivation{ + CreatedBy: "TestUser", + CreatedDate: time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC), + FinishDate: &time.Time{}, + ID: 113, + Network: v3.StagingNetwork, + Operation: v3.OperationActivation, + PolicyID: 1, + Status: v3.ActivationStatusSuccess, + PolicyVersion: 2, + PolicyVersionDeleted: false, + }, + Latest: &v3.PolicyActivation{ + CreatedBy: "TestUser", + CreatedDate: time.Date(2009, 11, 17, 20, 34, 58, 651387237, time.UTC), + FinishDate: &time.Time{}, + ID: 114, + Network: v3.StagingNetwork, + Operation: v3.OperationActivation, + PolicyID: 1, + Status: v3.ActivationStatusSuccess, + PolicyVersion: 2, + PolicyVersionDeleted: false, + }, + }, + }, + }, + init: func(m *v3.Mock, data testDataForSharedPolicy) { + mockGetPolicy(m, data, 5) + mockGetPolicyVersion(m, data, 5) + }, + }, + "expect error on ListPolicyVersions": { + config: "no_version.tf", + data: testDataForSharedPolicy{ + id: "akamai_cloudlets_shared_policy", + policyID: 1, + version: 2, + groupID: 12, + name: "TestName", + cloudletType: v3.CloudletTypeAP, + description: "Description", + }, + init: func(m *v3.Mock, data testDataForSharedPolicy) { + mockGetPolicy(m, data, 1) + m.On("ListPolicyVersions", mock.Anything, v3.ListPolicyVersionsRequest{ + PolicyID: data.policyID, + }).Return(nil, fmt.Errorf("API error")).Once() + }, + expectError: regexp.MustCompile(`Error: Reading Cloudlets Shared Policy Failed`), + }, + "expect ErrNotFound error on GetPolicy": { + config: "with_version.tf", + data: testDataForSharedPolicy{ + policyID: 1, + }, + init: func(m *v3.Mock, data testDataForSharedPolicy) { + m.On("GetPolicy", mock.Anything, v3.GetPolicyRequest{ + PolicyID: data.policyID, + }).Return(nil, fmt.Errorf("%s: %w: %s", v3.ErrGetPolicy, v3.ErrPolicyNotFound, "oops")).Once() + }, + expectError: regexp.MustCompile(`Error: Policy does not exist or is not of 'SHARED' type`), + }, + "expect error - missing required attribute": { + config: "no_policy_id.tf", + data: testDataForSharedPolicy{}, + init: func(m *v3.Mock, data testDataForSharedPolicy) {}, + expectError: regexp.MustCompile(`The argument "policy_id" is required, but no definition was found.`), + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + client := &v3.Mock{} + if test.init != nil { + test.init(client, test.data) + } + useClientV3(client, func() { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{{ + Config: testutils.LoadFixtureString(t, fmt.Sprintf("testdata/TestDataCloudletsSharedPolicy/%s", test.config)), + Check: checkAttrsForSharedPolicy(test.data), + ExpectError: test.expectError, + }}, + }) + }) + client.AssertExpectations(t) + }) + } +} + +func checkAttrsForSharedPolicy(data testDataForSharedPolicy) resource.TestCheckFunc { + var checkFuncs []resource.TestCheckFunc + matchRules, err := json.Marshal(data.matchRules) + if err != nil { + panic(err) + } + matchWarnings, err := json.Marshal(data.warnings) + if err != nil { + panic(err) + } + if data.version == 0 { + checkFuncs = append(checkFuncs, + resource.TestCheckNoResourceAttr("data.akamai_cloudlets_shared_policy.test", "version"), + resource.TestCheckNoResourceAttr("data.akamai_cloudlets_shared_policy.test", "match_rules"), + resource.TestCheckNoResourceAttr("data.akamai_cloudlets_shared_policy.test", "warnings")) + } else { + if data.versionDescription != nil { + checkFuncs = append(checkFuncs, + resource.TestCheckResourceAttr("data.akamai_cloudlets_shared_policy.test", "version_description", *data.versionDescription)) + } else { + checkFuncs = append(checkFuncs, + resource.TestCheckNoResourceAttr("data.akamai_cloudlets_shared_policy.test", "version_description")) + } + checkFuncs = append(checkFuncs, + resource.TestCheckResourceAttr("data.akamai_cloudlets_shared_policy.test", "version", strconv.FormatInt(data.version, 10)), + resource.TestCheckResourceAttr("data.akamai_cloudlets_shared_policy.test", "match_rules", string(matchRules)), + resource.TestCheckResourceAttr("data.akamai_cloudlets_shared_policy.test", "warnings", string(matchWarnings))) + } + + checkFuncs = append(checkFuncs, + resource.TestCheckResourceAttr("data.akamai_cloudlets_shared_policy.test", "name", data.name), + resource.TestCheckResourceAttr("data.akamai_cloudlets_shared_policy.test", "id", data.id), + resource.TestCheckResourceAttr("data.akamai_cloudlets_shared_policy.test", "group_id", strconv.FormatInt(data.groupID, 10)), + resource.TestCheckResourceAttr("data.akamai_cloudlets_shared_policy.test", "description", data.description), + ) + + if data.activations.Production.Effective != nil { + checkFuncs = append(checkFuncs, checkActivationAttributesForSharedPolicy("production", "effective", data.activations.Production.Effective)) + } + if data.activations.Production.Latest != nil { + checkFuncs = append(checkFuncs, checkActivationAttributesForSharedPolicy("production", "latest", data.activations.Production.Latest)) + } + if data.activations.Staging.Effective != nil { + checkFuncs = append(checkFuncs, checkActivationAttributesForSharedPolicy("staging", "effective", data.activations.Staging.Effective)) + } + if data.activations.Staging.Latest != nil { + checkFuncs = append(checkFuncs, checkActivationAttributesForSharedPolicy("staging", "latest", data.activations.Staging.Latest)) + } + + return resource.ComposeAggregateTestCheckFunc(checkFuncs...) +} + +func checkActivationAttributesForSharedPolicy(actNetwork, actInfo string, actData *v3.PolicyActivation) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.akamai_cloudlets_shared_policy.test", fmt.Sprintf("activations.%s.%s.created_by", actNetwork, actInfo), actData.CreatedBy), + resource.TestCheckResourceAttr("data.akamai_cloudlets_shared_policy.test", fmt.Sprintf("activations.%s.%s.created_date", actNetwork, actInfo), actData.CreatedDate.String()), + resource.TestCheckResourceAttr("data.akamai_cloudlets_shared_policy.test", fmt.Sprintf("activations.%s.%s.network", actNetwork, actInfo), string(actData.Network)), + resource.TestCheckResourceAttr("data.akamai_cloudlets_shared_policy.test", fmt.Sprintf("activations.%s.%s.status", actNetwork, actInfo), string(actData.Status)), + resource.TestCheckResourceAttr("data.akamai_cloudlets_shared_policy.test", fmt.Sprintf("activations.%s.%s.policy_version", actNetwork, actInfo), strconv.FormatInt(actData.PolicyVersion, 10)), + resource.TestCheckResourceAttr("data.akamai_cloudlets_shared_policy.test", fmt.Sprintf("activations.%s.%s.finish_date", actNetwork, actInfo), actData.FinishDate.String()), + resource.TestCheckResourceAttr("data.akamai_cloudlets_shared_policy.test", fmt.Sprintf("activations.%s.%s.operation", actNetwork, actInfo), string(actData.Operation)), + resource.TestCheckResourceAttr("data.akamai_cloudlets_shared_policy.test", fmt.Sprintf("activations.%s.%s.policy_id", actNetwork, actInfo), strconv.FormatInt(actData.PolicyID, 10)), + resource.TestCheckResourceAttr("data.akamai_cloudlets_shared_policy.test", fmt.Sprintf("activations.%s.%s.activation_id", actNetwork, actInfo), strconv.FormatInt(actData.ID, 10))) +} + +func mockGetPolicy(m *v3.Mock, data testDataForSharedPolicy, times int) { + m.On("GetPolicy", mock.Anything, v3.GetPolicyRequest{ + PolicyID: data.policyID, + }).Return(&v3.Policy{ + CloudletType: data.cloudletType, + CurrentActivations: data.activations, + Description: tools.StringPtr(data.description), + GroupID: data.groupID, + ID: data.policyID, + Name: data.name, + }, nil).Times(times) +} + +func mockGetPolicyVersion(m *v3.Mock, data testDataForSharedPolicy, times int) { + m.On("GetPolicyVersion", mock.Anything, v3.GetPolicyVersionRequest{ + PolicyID: data.policyID, + PolicyVersion: data.version, + }).Return(&v3.PolicyVersion{ + PolicyID: data.policyID, + PolicyVersion: data.version, + Description: data.versionDescription, + ID: 123, + Immutable: false, + MatchRules: data.matchRules, + MatchRulesWarnings: data.warnings, + }, nil).Times(times) +} + +func createPolicyVersions(policyID int64, numberOfVersions, pageNumber int) *v3.ListPolicyVersions { + var policyVersions v3.ListPolicyVersions + for i := numberOfVersions; i > 0; i-- { + policyVersions.PolicyVersions = append(policyVersions.PolicyVersions, v3.ListPolicyVersionsItem{ + Description: tools.StringPtr(fmt.Sprintf("Description%d", i)), + ID: int64(i), + Immutable: true, + PolicyID: policyID, + PolicyVersion: int64(i), + }) + } + policyVersions.Page.Number = pageNumber + + return &policyVersions +} + +func mockListPolicyVersions(m *v3.Mock, data testDataForSharedPolicy, numberOfActivations, times int) { + if data.version == 0 { + m.On("ListPolicyVersions", mock.Anything, v3.ListPolicyVersionsRequest{ + PolicyID: data.policyID, + }).Return(&v3.ListPolicyVersions{ + PolicyVersions: []v3.ListPolicyVersionsItem{}, + }, nil).Times(times) + } else { + m.On("ListPolicyVersions", mock.Anything, v3.ListPolicyVersionsRequest{ + PolicyID: data.policyID, + }).Return(createPolicyVersions(data.policyID, numberOfActivations, 0), nil).Times(times) + } +} diff --git a/pkg/providers/cloudlets/data_akamai_cloudlets_visitor_prioritization_match_rule_test.go b/pkg/providers/cloudlets/data_akamai_cloudlets_visitor_prioritization_match_rule_test.go index b4d03dc6f..8e8a99768 100644 --- a/pkg/providers/cloudlets/data_akamai_cloudlets_visitor_prioritization_match_rule_test.go +++ b/pkg/providers/cloudlets/data_akamai_cloudlets_visitor_prioritization_match_rule_test.go @@ -42,7 +42,7 @@ func TestDataCloudletsVisitorPrioritizationMatchRule(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, test.configPath), @@ -104,7 +104,7 @@ func TestIncorrectDataCloudletsVisitorPrioritizationMatchRule(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, test.configPath), diff --git a/pkg/providers/cloudlets/policy_version.go b/pkg/providers/cloudlets/policy_version.go index b34358dcd..e1b50fa6e 100644 --- a/pkg/providers/cloudlets/policy_version.go +++ b/pkg/providers/cloudlets/policy_version.go @@ -2,51 +2,24 @@ package cloudlets import ( "context" - "fmt" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets" + "github.com/akamai/terraform-provider-akamai/v5/pkg/common/tf" + "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func getAllPolicyVersions(ctx context.Context, policyID int64, client cloudlets.Cloudlets) ([]cloudlets.PolicyVersion, error) { - pageSize, offset := 1000, 0 - allPolicyVersions := make([]cloudlets.PolicyVersion, 0) - - for { - versions, err := client.ListPolicyVersions(ctx, cloudlets.ListPolicyVersionsRequest{ - PolicyID: policyID, - IncludeRules: false, - PageSize: &pageSize, - Offset: offset, - }) - if err != nil { - return nil, err - } - - allPolicyVersions = append(allPolicyVersions, versions...) - if len(versions) < pageSize { - break - } - offset += pageSize - } - - return allPolicyVersions, nil -} - -func findLatestPolicyVersion(ctx context.Context, policyID int64, client cloudlets.Cloudlets) (int64, error) { - var version int64 - versions, err := getAllPolicyVersions(ctx, policyID, client) +func getPolicyVersionExecutionStrategy(d *schema.ResourceData, meta meta.Meta) (versionStrategy, error) { + isV3, err := tf.GetBoolValue("is_shared", d) if err != nil { - return version, err - } - if len(versions) == 0 { - return version, fmt.Errorf("no policy version found") + return nil, err } - for _, v := range versions { - if v.Version > version { - version = v.Version - } + if isV3 { + return v3VersionStrategy{ClientV3(meta)}, nil } + return v2VersionStrategy{Client(meta)}, nil +} - return version, nil +type versionStrategy interface { + findLatestPolicyVersion(ctx context.Context, policyID int64) (*int64, error) } diff --git a/pkg/providers/cloudlets/policy_version_test.go b/pkg/providers/cloudlets/policy_version_test.go index 0b0cf43c9..69044691b 100644 --- a/pkg/providers/cloudlets/policy_version_test.go +++ b/pkg/providers/cloudlets/policy_version_test.go @@ -6,6 +6,8 @@ import ( "testing" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets" + v3 "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets/v3" + "github.com/akamai/terraform-provider-akamai/v5/pkg/tools" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/tj/assert" @@ -25,7 +27,7 @@ func TestFindingLatestPolicyVersion(t *testing.T) { tests := map[string]struct { init func(m *cloudlets.Mock) - expected int64 + expected *int64 withError bool }{ "last policy version on 1st page found": { @@ -41,7 +43,7 @@ func TestFindingLatestPolicyVersion(t *testing.T) { Offset: 1000, }).Return([]cloudlets.PolicyVersion{}, nil).Once() }, - expected: 999, + expected: tools.Int64Ptr(0), }, "first policy version on 1st page found": { init: func(m *cloudlets.Mock) { @@ -53,44 +55,113 @@ func TestFindingLatestPolicyVersion(t *testing.T) { Offset: 0, }).Return(policyVersionsPage, nil).Once() }, - expected: 500, + expected: tools.Int64Ptr(500), }, - "policy version on 3rd page found": { + "no policy versions found": { init: func(m *cloudlets.Mock) { m.On("ListPolicyVersions", mock.Anything, cloudlets.ListPolicyVersionsRequest{ PolicyID: policyID, PageSize: &pageSize, Offset: 0, - }).Return(preparePolicyVersionsPage(1000, 0), nil).Once() - m.On("ListPolicyVersions", mock.Anything, cloudlets.ListPolicyVersionsRequest{ - PolicyID: policyID, - PageSize: &pageSize, - Offset: 1000, - }).Return(preparePolicyVersionsPage(1000, 1000), nil).Once() - m.On("ListPolicyVersions", mock.Anything, cloudlets.ListPolicyVersionsRequest{ - PolicyID: policyID, - PageSize: &pageSize, - Offset: 2000, - }).Return(preparePolicyVersionsPage(500, 2000), nil).Once() + }).Return([]cloudlets.PolicyVersion{}, nil).Once() }, - expected: 2499, + expected: nil, }, - "no policy versions found": { + "error listing policy versions": { init: func(m *cloudlets.Mock) { m.On("ListPolicyVersions", mock.Anything, cloudlets.ListPolicyVersionsRequest{ PolicyID: policyID, PageSize: &pageSize, Offset: 0, - }).Return([]cloudlets.PolicyVersion{}, nil).Once() + }).Return(nil, fmt.Errorf("oops")).Once() }, withError: true, }, + } + + for name, test := range tests { + m := new(cloudlets.Mock) + test.init(m) + useClient(m, func() { + t.Run(name, func(t *testing.T) { + versionStrategy := v2VersionStrategy{client: m} + version, err := versionStrategy.findLatestPolicyVersion(context.Background(), policyID) + m.AssertExpectations(t) + if test.withError { + assert.Error(t, err) + } else { + require.NoError(t, err) + if test.expected != nil { + assert.Equal(t, *test.expected, *version) + } else { + assert.Nil(t, version) + } + } + }) + }) + } +} + +func TestFindingLatestPolicyVersionV3(t *testing.T) { + preparePolicyVersionsPage := func(pageSize, startingVersion int64) []v3.ListPolicyVersionsItem { + versions := make([]v3.ListPolicyVersionsItem, 0, pageSize) + for i := startingVersion; i < startingVersion+pageSize; i++ { + versions = append(versions, v3.ListPolicyVersionsItem{PolicyVersion: i}) + } + return versions + } + + var policyID int64 = 123 + pageSize := 1000 + + tests := map[string]struct { + init func(m *v3.Mock) + expected *int64 + withError bool + }{ + "last policy version on 1st page found": { + init: func(m *v3.Mock) { + m.On("ListPolicyVersions", mock.Anything, v3.ListPolicyVersionsRequest{ + PolicyID: policyID, + Size: pageSize, + Page: 0, + }).Return(&v3.ListPolicyVersions{PolicyVersions: preparePolicyVersionsPage(1000, 0)}, nil).Once() + m.On("ListPolicyVersions", mock.Anything, v3.ListPolicyVersionsRequest{ + PolicyID: policyID, + Size: pageSize, + Page: 1, + }).Return(&v3.ListPolicyVersions{PolicyVersions: []v3.ListPolicyVersionsItem{}}, nil).Once() + }, + expected: tools.Int64Ptr(0), + }, + "first policy version on 1st page found": { + init: func(m *v3.Mock) { + policyVersionsPage := preparePolicyVersionsPage(500, 0) + policyVersionsPage[0] = v3.ListPolicyVersionsItem{PolicyVersion: 500} + m.On("ListPolicyVersions", mock.Anything, v3.ListPolicyVersionsRequest{ + PolicyID: policyID, + Size: pageSize, + Page: 0, + }).Return(&v3.ListPolicyVersions{PolicyVersions: policyVersionsPage}, nil).Once() + }, + expected: tools.Int64Ptr(500), + }, + "no policy versions found": { + init: func(m *v3.Mock) { + m.On("ListPolicyVersions", mock.Anything, v3.ListPolicyVersionsRequest{ + PolicyID: policyID, + Size: pageSize, + Page: 0, + }).Return(&v3.ListPolicyVersions{PolicyVersions: []v3.ListPolicyVersionsItem{}}, nil).Once() + }, + expected: nil, + }, "error listing policy versions": { - init: func(m *cloudlets.Mock) { - m.On("ListPolicyVersions", mock.Anything, cloudlets.ListPolicyVersionsRequest{ + init: func(m *v3.Mock) { + m.On("ListPolicyVersions", mock.Anything, v3.ListPolicyVersionsRequest{ PolicyID: policyID, - PageSize: &pageSize, - Offset: 0, + Size: pageSize, + Page: 0, }).Return(nil, fmt.Errorf("oops")).Once() }, withError: true, @@ -98,16 +169,24 @@ func TestFindingLatestPolicyVersion(t *testing.T) { } for name, test := range tests { - t.Run(name, func(t *testing.T) { - m := &cloudlets.Mock{} - test.init(m) - version, err := findLatestPolicyVersion(context.Background(), policyID, m) - if test.withError { - assert.Error(t, err) - } else { - require.NoError(t, err) - assert.Equal(t, test.expected, version) - } + m := new(v3.Mock) + test.init(m) + useClientV3(m, func() { + t.Run(name, func(t *testing.T) { + checker := v3VersionStrategy{client: m} + version, err := checker.findLatestPolicyVersion(context.Background(), policyID) + m.AssertExpectations(t) + if test.withError { + assert.Error(t, err) + } else { + require.NoError(t, err) + if test.expected != nil { + assert.Equal(t, *test.expected, *version) + } else { + assert.Nil(t, version) + } + } + }) }) } } diff --git a/pkg/providers/cloudlets/policy_version_v2.go b/pkg/providers/cloudlets/policy_version_v2.go new file mode 100644 index 000000000..3d79284d4 --- /dev/null +++ b/pkg/providers/cloudlets/policy_version_v2.go @@ -0,0 +1,48 @@ +package cloudlets + +import ( + "context" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets" +) + +type v2VersionStrategy struct { + client cloudlets.Cloudlets +} + +func getAllV2PolicyVersions(ctx context.Context, policyID int64, client cloudlets.Cloudlets) ([]cloudlets.PolicyVersion, error) { + pageSize, offset := 1000, 0 + allPolicyVersions := make([]cloudlets.PolicyVersion, 0) + + for { + versions, err := client.ListPolicyVersions(ctx, cloudlets.ListPolicyVersionsRequest{ + PolicyID: policyID, + PageSize: &pageSize, + Offset: offset, + }) + if err != nil { + return nil, err + } + + allPolicyVersions = append(allPolicyVersions, versions...) + if len(versions) < pageSize { + break + } + offset += pageSize + } + + return allPolicyVersions, nil + +} + +func (v2 v2VersionStrategy) findLatestPolicyVersion(ctx context.Context, policyID int64) (*int64, error) { + versions, err := getAllV2PolicyVersions(ctx, policyID, v2.client) + if err != nil { + return nil, err + } + if len(versions) == 0 { + return nil, nil + } + //API returns list of versions sorted in descending order, and it can be assumed that first element is the latest version + return &versions[0].Version, nil +} diff --git a/pkg/providers/cloudlets/policy_version_v3.go b/pkg/providers/cloudlets/policy_version_v3.go new file mode 100644 index 000000000..cf575a5b7 --- /dev/null +++ b/pkg/providers/cloudlets/policy_version_v3.go @@ -0,0 +1,47 @@ +package cloudlets + +import ( + "context" + + cloudlets "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets/v3" +) + +type v3VersionStrategy struct { + client cloudlets.Cloudlets +} + +func getAllPolicyVersionsV3(ctx context.Context, policyID int64, client cloudlets.Cloudlets) ([]cloudlets.ListPolicyVersionsItem, error) { + size, page := 1000, 0 + allPolicyVersions := make([]cloudlets.ListPolicyVersionsItem, 0) + + for { + versions, err := client.ListPolicyVersions(ctx, cloudlets.ListPolicyVersionsRequest{ + PolicyID: policyID, + Page: page, + Size: size, + }) + if err != nil { + return nil, err + } + + allPolicyVersions = append(allPolicyVersions, versions.PolicyVersions...) + if len(versions.PolicyVersions) < size { + break + } + page++ + } + + return allPolicyVersions, nil +} + +func (v3 v3VersionStrategy) findLatestPolicyVersion(ctx context.Context, policyID int64) (*int64, error) { + versions, err := getAllPolicyVersionsV3(ctx, policyID, v3.client) + if err != nil { + return nil, err + } + if len(versions) == 0 { + return nil, nil + } + //API returns list of versions sorted in descending order, and it can be assumed that first element is the latest version + return &versions[0].PolicyVersion, nil +} diff --git a/pkg/providers/cloudlets/provider.go b/pkg/providers/cloudlets/provider.go index 108169185..97799597c 100644 --- a/pkg/providers/cloudlets/provider.go +++ b/pkg/providers/cloudlets/provider.go @@ -2,61 +2,60 @@ package cloudlets import ( - "sync" - + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets" + v3 "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets/v3" "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" "github.com/akamai/terraform-provider-akamai/v5/pkg/subprovider" ) type ( - // Subprovider gathers cloudlets resources and data sources - Subprovider struct { - client cloudlets.Cloudlets - } + // PluginSubprovider gathers property resources and data sources written using terraform-plugin-sdk + PluginSubprovider struct{} - option func(p *Subprovider) + // FrameworkSubprovider gathers property resources and data sources written using terraform-plugin-framework + FrameworkSubprovider struct{} ) var ( - once sync.Once - - inst *Subprovider + client cloudlets.Cloudlets + v3Client v3.Cloudlets ) -var _ subprovider.Plugin = &Subprovider{} - -// NewSubprovider returns a core sub provider -func NewSubprovider(opts ...option) *Subprovider { - once.Do(func() { - inst = &Subprovider{} +var _ subprovider.Plugin = &PluginSubprovider{} +var _ subprovider.Framework = &FrameworkSubprovider{} - for _, opt := range opts { - opt(inst) - } - }) +// NewPluginSubprovider returns a core SDKv2 based sub provider +func NewPluginSubprovider() *PluginSubprovider { + return &PluginSubprovider{} +} - return inst +// NewFrameworkSubprovider returns a core Framework based sub provider +func NewFrameworkSubprovider() *FrameworkSubprovider { + return &FrameworkSubprovider{} } -func withClient(c cloudlets.Cloudlets) option { - return func(p *Subprovider) { - p.client = c +// Client returns the cloudlets interface +func Client(meta meta.Meta) cloudlets.Cloudlets { + if client != nil { + return client } + return cloudlets.Client(meta.Session()) } -// Client returns the Cloudlets interface -func (p *Subprovider) Client(meta meta.Meta) cloudlets.Cloudlets { - if p.client != nil { - return p.client +// ClientV3 returns the cloudlets v3 interface +func ClientV3(meta meta.Meta) v3.Cloudlets { + if v3Client != nil { + return v3Client } - return cloudlets.Client(meta.Session()) + return v3.Client(meta.Session()) } // Resources returns terraform resources for cloudlets -func (p *Subprovider) Resources() map[string]*schema.Resource { +func (p *PluginSubprovider) Resources() map[string]*schema.Resource { return map[string]*schema.Resource{ "akamai_cloudlets_application_load_balancer": resourceCloudletsApplicationLoadBalancer(), "akamai_cloudlets_application_load_balancer_activation": resourceCloudletsApplicationLoadBalancerActivation(), @@ -66,7 +65,7 @@ func (p *Subprovider) Resources() map[string]*schema.Resource { } // DataSources returns terraform data sources for cloudlets -func (p *Subprovider) DataSources() map[string]*schema.Resource { +func (p *PluginSubprovider) DataSources() map[string]*schema.Resource { return map[string]*schema.Resource{ "akamai_cloudlets_api_prioritization_match_rule": dataSourceCloudletsAPIPrioritizationMatchRule(), "akamai_cloudlets_application_load_balancer": dataSourceCloudletsApplicationLoadBalancer(), @@ -80,3 +79,16 @@ func (p *Subprovider) DataSources() map[string]*schema.Resource { "akamai_cloudlets_policy": dataSourceCloudletsPolicy(), } } + +// Resources returns terraform resources for cloudlets +func (p *FrameworkSubprovider) Resources() []func() resource.Resource { + return []func() resource.Resource{} +} + +// DataSources returns terraform data sources for cloudlets +func (p *FrameworkSubprovider) DataSources() []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewPolicyActivationDataSource, + NewSharedPolicyDataSource, + } +} diff --git a/pkg/providers/cloudlets/provider_test.go b/pkg/providers/cloudlets/provider_test.go index b96be1de6..58a915600 100644 --- a/pkg/providers/cloudlets/provider_test.go +++ b/pkg/providers/cloudlets/provider_test.go @@ -1,26 +1,51 @@ package cloudlets import ( + "context" "log" "os" "sync" "testing" + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" + "github.com/akamai/terraform-provider-akamai/v5/pkg/akamai" "github.com/akamai/terraform-provider-akamai/v5/pkg/common/testutils" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets" + v3 "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets/v3" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -var testAccProviders map[string]func() (*schema.Provider, error) -var testAccProvider *schema.Provider +var ( + testAccProviders map[string]func() (tfprotov5.ProviderServer, error) + testAccPluginProvider *schema.Provider + testAccFrameworkProvider provider.Provider +) func TestMain(m *testing.M) { - testAccProvider = akamai.NewPluginProvider(NewSubprovider())() - testAccProviders = map[string]func() (*schema.Provider, error){ - "akamai": func() (*schema.Provider, error) { - return testAccProvider, nil + testAccPluginProvider = akamai.NewPluginProvider(NewPluginSubprovider())() + testAccFrameworkProvider = akamai.NewFrameworkProvider(NewFrameworkSubprovider())() + + testAccProviders = map[string]func() (tfprotov5.ProviderServer, error){ + "akamai": func() (tfprotov5.ProviderServer, error) { + ctx := context.Background() + providers := []func() tfprotov5.ProviderServer{ + testAccPluginProvider.GRPCProvider, + providerserver.NewProtocol5( + testAccFrameworkProvider, + ), + } + + muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) + if err != nil { + return nil, err + } + + return muxServer.ProviderServer(), nil }, } @@ -38,13 +63,44 @@ func TestMain(m *testing.M) { var clientLock sync.Mutex // useClient swaps out the client on the global instance for the duration of the given func -func useClient(client cloudlets.Cloudlets, f func()) { +func useClient(cloudletsClient cloudlets.Cloudlets, f func()) { + clientLock.Lock() + orig := client + client = cloudletsClient + + defer func() { + client = orig + clientLock.Unlock() + }() + + f() +} + +// useClientV3 swaps out the client v3 on the global instance for the duration of the given func +func useClientV3(cloudletsV3Client v3.Cloudlets, f func()) { + clientLock.Lock() + orig := v3Client + v3Client = cloudletsV3Client + + defer func() { + v3Client = orig + clientLock.Unlock() + }() + + f() +} + +// useClientV2AndV3 swaps out both client (v2) and client v3 on the global instances for the duration of the given func. To be used in by tests for data sources and resources that use both V2 & V3 cloudlets +func useClientV2AndV3(cloudletsV2Client cloudlets.Cloudlets, cloudletsV3Client v3.Cloudlets, f func()) { clientLock.Lock() - orig := inst.client - inst.client = client + origV2 := client + client = cloudletsV2Client + origV3 := v3Client + v3Client = cloudletsV3Client defer func() { - inst.client = orig + client = origV2 + v3Client = origV3 clientLock.Unlock() }() diff --git a/pkg/providers/cloudlets/resource_akamai_cloudlets_application_load_balancer.go b/pkg/providers/cloudlets/resource_akamai_cloudlets_application_load_balancer.go index c0f1c6eb5..3fec5c73a 100644 --- a/pkg/providers/cloudlets/resource_akamai_cloudlets_application_load_balancer.go +++ b/pkg/providers/cloudlets/resource_akamai_cloudlets_application_load_balancer.go @@ -309,7 +309,7 @@ func resourceALBCreate(ctx context.Context, d *schema.ResourceData, m interface{ ctx, session.WithContextLog(logger), ) - client := inst.Client(meta) + client := Client(meta) logger.Debug("Creating load balancer configuration") originID, err := tf.GetStringValue("origin_id", d) if err != nil { @@ -357,7 +357,7 @@ func resourceALBRead(ctx context.Context, d *schema.ResourceData, m interface{}) ctx, session.WithContextLog(logger), ) - client := inst.Client(meta) + client := Client(meta) logger.Debug("Reading load balancer configuration") originID := d.Id() loadBalancerConfigAttrs := map[string]interface{}{ @@ -416,7 +416,7 @@ func resourceALBUpdate(ctx context.Context, d *schema.ResourceData, m interface{ ctx, session.WithContextLog(logger), ) - client := inst.Client(meta) + client := Client(meta) logger.Debug("Updating load balancer configuration") originID := d.Id() @@ -501,7 +501,7 @@ func resourceALBImport(ctx context.Context, d *schema.ResourceData, m interface{ logger := meta.Log("Cloudlets", "resourceALBImport") logger.Debug("Import ALB") - client := inst.Client(meta) + client := Client(meta) logger.Debug("Importing load balancer configuration") originID := d.Id() diff --git a/pkg/providers/cloudlets/resource_akamai_cloudlets_application_load_balancer_activation.go b/pkg/providers/cloudlets/resource_akamai_cloudlets_application_load_balancer_activation.go index 67fddebb6..a25fe6c7f 100644 --- a/pkg/providers/cloudlets/resource_akamai_cloudlets_application_load_balancer_activation.go +++ b/pkg/providers/cloudlets/resource_akamai_cloudlets_application_load_balancer_activation.go @@ -118,7 +118,7 @@ func resourceApplicationLoadBalancerActivationUpdate(ctx context.Context, rd *sc logger.Debugf("version number or network has changed: proceeding to update application load balancer activation version") ctx = session.ContextWithOptions(ctx, session.WithContextLog(logger)) - client := inst.Client(meta) + client := Client(meta) activation, err := resourceApplicationLoadBalancerActivationChange(ctx, rd, logger, client) if err != nil { @@ -132,7 +132,7 @@ func resourceApplicationLoadBalancerActivationCreate(ctx context.Context, rd *sc meta := meta.Must(m) logger := meta.Log("Cloudlets", "resourceApplicationLoadBalancerActivationCreate") ctx = session.ContextWithOptions(ctx, session.WithContextLog(logger)) - client := inst.Client(meta) + client := Client(meta) logger.Debug("Creating application load balancer activation") @@ -244,7 +244,7 @@ func resourceApplicationLoadBalancerActivationRead(ctx context.Context, rd *sche meta := meta.Must(m) logger := meta.Log("Cloudlets", "resourceApplicationLoadBalancerActivationRead") ctx = session.ContextWithOptions(ctx, session.WithContextLog(logger)) - client := inst.Client(meta) + client := Client(meta) logger.Debug("Reading application load balancer activations") diff --git a/pkg/providers/cloudlets/resource_akamai_cloudlets_application_load_balancer_activation_test.go b/pkg/providers/cloudlets/resource_akamai_cloudlets_application_load_balancer_activation_test.go index 542fec83d..f45d487f1 100644 --- a/pkg/providers/cloudlets/resource_akamai_cloudlets_application_load_balancer_activation_test.go +++ b/pkg/providers/cloudlets/resource_akamai_cloudlets_application_load_balancer_activation_test.go @@ -474,9 +474,9 @@ func TestResourceCloudletsApplicationLoadBalancerActivation(t *testing.T) { test.init(client) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, - IsUnitTest: true, - Steps: test.steps, + ProtoV5ProviderFactories: testAccProviders, + IsUnitTest: true, + Steps: test.steps, }) }) client.AssertExpectations(t) diff --git a/pkg/providers/cloudlets/resource_akamai_cloudlets_application_load_balancer_test.go b/pkg/providers/cloudlets/resource_akamai_cloudlets_application_load_balancer_test.go index ed9e48ee8..489d343b6 100644 --- a/pkg/providers/cloudlets/resource_akamai_cloudlets_application_load_balancer_test.go +++ b/pkg/providers/cloudlets/resource_akamai_cloudlets_application_load_balancer_test.go @@ -235,7 +235,7 @@ func TestResourceApplicationLoadBalancer(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/alb_create.tf", testDir)), @@ -275,7 +275,7 @@ func TestResourceApplicationLoadBalancer(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/alb_create.tf", testDir)), @@ -315,7 +315,7 @@ func TestResourceApplicationLoadBalancer(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/alb_create.tf", testDir)), @@ -355,7 +355,7 @@ func TestResourceApplicationLoadBalancer(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/alb_create.tf", testDir)), @@ -402,7 +402,7 @@ func TestResourceApplicationLoadBalancer(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/alb_create.tf", testDir)), @@ -447,7 +447,7 @@ func TestResourceApplicationLoadBalancer(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/alb_create.tf", testDir)), @@ -513,7 +513,7 @@ func TestResourceApplicationLoadBalancer(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/alb_create.tf", testDir)), @@ -539,7 +539,7 @@ func TestResourceApplicationLoadBalancer(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/alb_create.tf", testDir)), @@ -580,7 +580,7 @@ func TestResourceApplicationLoadBalancer(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/alb_create.tf", testDir)), @@ -626,7 +626,7 @@ func TestResourceApplicationLoadBalancer(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/alb_create.tf", testDir)), @@ -650,7 +650,7 @@ func TestResourceApplicationLoadBalancer(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/alb_create.tf", testDir)), @@ -679,7 +679,7 @@ func TestResourceApplicationLoadBalancer(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/alb_create.tf", testDir)), @@ -706,7 +706,7 @@ func TestResourceApplicationLoadBalancer(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/alb_create.tf", testDir)), @@ -737,7 +737,7 @@ func TestResourceApplicationLoadBalancer(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/alb_create.tf", testDir)), @@ -771,7 +771,7 @@ func TestResourceApplicationLoadBalancer(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/alb_create.tf", testDir)), @@ -805,7 +805,7 @@ func TestResourceApplicationLoadBalancer(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/alb_create.tf", testDir)), @@ -827,7 +827,7 @@ func TestResourceApplicationLoadBalancer(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/alb_create.tf", testDir)), diff --git a/pkg/providers/cloudlets/resource_akamai_cloudlets_policy.go b/pkg/providers/cloudlets/resource_akamai_cloudlets_policy.go index 696906b08..9dea11dbf 100644 --- a/pkg/providers/cloudlets/resource_akamai_cloudlets_policy.go +++ b/pkg/providers/cloudlets/resource_akamai_cloudlets_policy.go @@ -11,6 +11,7 @@ import ( "time" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets" + v3 "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets/v3" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/session" "github.com/akamai/terraform-provider-akamai/v5/pkg/common/tf" @@ -22,7 +23,6 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" ) var ( @@ -31,20 +31,6 @@ var ( // DeletionPolicyTimeout is the default timeout for the policy deletion DeletionPolicyTimeout = time.Minute * 90 - - cloudletIDs = map[string]int{ - "ER": 0, - "VP": 1, - "FR": 3, - "IG": 4, - "AP": 5, - "AS": 6, - "CD": 7, - "IV": 8, - "ALB": 9, - "MMB": 10, - "MMA": 11, - } ) func resourceCloudletsPolicy() *schema.Resource { @@ -52,6 +38,9 @@ func resourceCloudletsPolicy() *schema.Resource { CustomizeDiff: customdiff.All( EnforcePolicyVersionChange, EnforceMatchRulesChange, + cloudletTypeChangesValidation, + cloudletCodeValidation, + cloudletCodeChangeValidation, ), CreateContext: resourcePolicyCreate, ReadContext: resourcePolicyRead, @@ -64,10 +53,9 @@ func resourceCloudletsPolicy() *schema.Resource { Description: "The name of the policy. The name must be unique", }, "cloudlet_code": { - Type: schema.TypeString, - Required: true, - ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ALB", "AP", "AS", "CD", "ER", "FR", "IG", "VP"}, true)), - Description: "Code for the type of Cloudlet (ALB, AP, AS, CD, ER, FR, IG, or VP)", + Type: schema.TypeString, + Required: true, + Description: "Code for the type of Cloudlet (ALB, AP, AS, CD, ER, FR, IG, or VP)", }, "description": { Type: schema.TypeString, @@ -92,10 +80,16 @@ func resourceCloudletsPolicy() *schema.Resource { DiffSuppressFunc: diffSuppressMatchRules, Description: "A JSON structure that defines the rules for this policy", }, + "is_shared": { + Type: schema.TypeBool, + Optional: true, + Default: false, + Description: "The type of policy that you want to create", + }, "cloudlet_id": { Type: schema.TypeInt, Computed: true, - Description: "An integer that corresponds to a Cloudlets policy type (0 or 9)", + Description: "An integer that corresponds to a non-shared Cloudlets policy type (0 to 9). Not used for shared policies", }, "version": { Type: schema.TypeInt, @@ -132,13 +126,52 @@ func resourceCloudletsPolicy() *schema.Resource { } } +func cloudletCodeChangeValidation(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { + if diff.Id() != "" && diff.HasChange("cloudlet_code") { + return fmt.Errorf("cloudlet code cannot be changed after creation, please destroy policy and create new one with modified `cloudlet_code`") + } + return nil +} + +func cloudletCodeValidation(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { + isShared := diff.Get("is_shared").(bool) + providedCode := diff.Get("cloudlet_code").(string) + if isShared { + possibleValues := []string{"AP", "AS", "CD", "ER", "FR", "IG"} + for _, code := range possibleValues { + if strings.ToLower(providedCode) == strings.ToLower(code) { + return nil + } + } + return fmt.Errorf("provided cloudlet code %s cannot be used in shared policy - use one of %s", providedCode, possibleValues) + } + + possibleValues := []string{"ALB", "AP", "AS", "CD", "ER", "FR", "IG", "VP"} + for _, code := range possibleValues { + if strings.ToLower(providedCode) == strings.ToLower(code) { + return nil + } + } + return fmt.Errorf("provided cloudlet code %s cannot be used in legacy policy - use one of %s", providedCode, possibleValues) +} + +// cloudletTypeChangesValidation is used to run validation for v2 -> v3 (or vice versa) related migrations +func cloudletTypeChangesValidation(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { + if diff.Id() != "" { + if diff.HasChange("is_shared") { + return fmt.Errorf("it is impossible to convert shared cloudlet to legacy one or vice versa; create new policy with modified named for target policy type") + } + if diff.Get("is_shared").(bool) && diff.HasChange("name") { + return fmt.Errorf("it is impossible to rename shared policy") + } + } + + return nil +} + // EnforcePolicyVersionChange enforces that change to any field will most likely result in creating a new version func EnforcePolicyVersionChange(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { - if diff.HasChange("name") || - diff.HasChange("description") || - diff.HasChange("cloudlet_id") || - diff.HasChange("match_rule_format") || - diff.HasChange("version") { + if diff.HasChanges("name", "description", "match_rule_format", "version") { return diff.SetNewComputed("version") } return nil @@ -164,13 +197,11 @@ func resourcePolicyCreate(ctx context.Context, d *schema.ResourceData, m interfa ctx, session.WithContextLog(logger), ) - client := inst.Client(meta) logger.Debug("Creating policy") cloudletCode, err := tf.GetStringValue("cloudlet_code", d) if err != nil { return diag.FromErr(err) } - cloudletID := cloudletIDs[cloudletCode] name, err := tf.GetStringValue("name", d) if err != nil { return diag.FromErr(err) @@ -183,58 +214,47 @@ func resourcePolicyCreate(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.Errorf("invalid group_id provided: %s", err) } - createPolicyReq := cloudlets.CreatePolicyRequest{ - Name: name, - CloudletID: int64(cloudletID), - GroupID: int64(groupIDNum), - } - createPolicyResp, err := client.CreatePolicy(ctx, createPolicyReq) + + executionStrategy, err := getPolicyExecutionStrategy(d, meta) if err != nil { return diag.FromErr(err) } - d.SetId(strconv.FormatInt(createPolicyResp.PolicyID, 10)) - if err := d.Set("version", 1); err != nil { - return diag.FromErr(fmt.Errorf("%w: %s", tf.ErrValueSet, err.Error())) + + policyID, err := executionStrategy.createPolicy(ctx, name, cloudletCode, int64(groupIDNum)) + if err != nil { + return diag.FromErr(err) } - matchRuleFormat, err := tf.GetStringValue("match_rule_format", d) + + d.SetId(strconv.FormatInt(policyID, 10)) + + description, err := tf.GetStringValue("description", d) if err != nil && !errors.Is(err, tf.ErrNotFound) { return diag.FromErr(err) } + matchRulesJSON, err := tf.GetStringValue("match_rules", d) if err != nil { if errors.Is(err, tf.ErrNotFound) { - return resourcePolicyRead(ctx, d, m) + if description == "" { + return resourcePolicyRead(ctx, d, m) + } + } else { + return diag.FromErr(err) } - return diag.FromErr(err) - } - var matchRules cloudlets.MatchRules - if err := json.Unmarshal([]byte(matchRulesJSON), &matchRules); err != nil { - return diag.Errorf("unmarshalling match rules JSON: %s", err) } - description, err := tf.GetStringValue("description", d) - if err != nil && !errors.Is(err, tf.ErrNotFound) { + err, updateError := executionStrategy.updatePolicyVersion(ctx, d, policyID, 1, description, matchRulesJSON, !executionStrategy.isFirstVersionCreated()) + if err != nil { return diag.FromErr(err) } - updateVersionRequest := cloudlets.UpdatePolicyVersionRequest{ - UpdatePolicyVersion: cloudlets.UpdatePolicyVersion{ - MatchRuleFormat: cloudlets.MatchRuleFormat(matchRuleFormat), - MatchRules: matchRules, - Description: description, - }, - PolicyID: createPolicyResp.PolicyID, - Version: 1, - } - updateVersionResp, err := client.UpdatePolicyVersion(ctx, updateVersionRequest) - if err != nil { + if updateError != nil { + // The resource will be created as tainted (because the setId was executed). So on next plan it'll delete it and create again. + // We still want to have actual (server's) values in state. Otherwise, the values from config would be put into the state as default. if errPolicyRead := resourcePolicyRead(ctx, d, m); errPolicyRead != nil { - return append(errPolicyRead, diag.FromErr(err)...) + return append(errPolicyRead, diag.FromErr(updateError)...) } - return diag.FromErr(err) - } - if err := setWarnings(d, updateVersionResp.Warnings); err != nil { - return err + return diag.FromErr(updateError) } return resourcePolicyRead(ctx, d, m) } @@ -246,43 +266,32 @@ func resourcePolicyRead(ctx context.Context, d *schema.ResourceData, m interface ctx, session.WithContextLog(logger), ) - client := inst.Client(meta) logger.Debug("Reading policy") policyID, err := strconv.ParseInt(d.Id(), 10, 0) if err != nil { return diag.FromErr(err) } - policy, err := client.GetPolicy(ctx, cloudlets.GetPolicyRequest{PolicyID: policyID}) + + policyVersionStrategy, err := getPolicyVersionExecutionStrategy(d, meta) if err != nil { return diag.FromErr(err) } - version, err := tf.GetIntValue("version", d) + + policyVersion, err := policyVersionStrategy.findLatestPolicyVersion(ctx, policyID) if err != nil { return diag.FromErr(err) } - policyVersion, err := client.GetPolicyVersion(ctx, cloudlets.GetPolicyVersionRequest{ - PolicyID: policyID, - Version: int64(version), - }) + + executionStrategy, err := getPolicyExecutionStrategy(d, meta) if err != nil { return diag.FromErr(err) } - attrs := make(map[string]interface{}) - attrs["name"] = policy.Name - attrs["group_id"] = strconv.FormatInt(policy.GroupID, 10) - attrs["cloudlet_code"] = policy.CloudletCode - attrs["cloudlet_id"] = policy.CloudletID - attrs["description"] = policyVersion.Description - attrs["match_rule_format"] = policyVersion.MatchRuleFormat - var matchRulesJSON []byte - if len(policyVersion.MatchRules) > 0 { - matchRulesJSON, err = json.MarshalIndent(policyVersion.MatchRules, "", " ") - if err != nil { - return diag.FromErr(err) - } + + attrs, err := executionStrategy.readPolicy(ctx, policyID, policyVersion) + if err != nil { + return diag.FromErr(err) } - attrs["match_rules"] = string(matchRulesJSON) - attrs["version"] = policyVersion.Version + if err := tf.SetAttrs(d, attrs); err != nil { return diag.FromErr(err) } @@ -296,7 +305,6 @@ func resourcePolicyUpdate(ctx context.Context, d *schema.ResourceData, m interfa ctx, session.WithContextLog(logger), ) - client := inst.Client(meta) logger.Debug("Updating policy") if !d.HasChangeExcept("timeouts") { @@ -304,6 +312,11 @@ func resourcePolicyUpdate(ctx context.Context, d *schema.ResourceData, m interfa return nil } + executionStrategy, err := getPolicyExecutionStrategy(d, meta) + if err != nil { + return diag.FromErr(err) + } + policyID, err := strconv.ParseInt(d.Id(), 10, 0) if err != nil { return diag.FromErr(err) @@ -321,88 +334,47 @@ func resourcePolicyUpdate(ctx context.Context, d *schema.ResourceData, m interfa if err != nil { return diag.FromErr(err) } - updatePolicyReq := cloudlets.UpdatePolicyRequest{ - UpdatePolicy: cloudlets.UpdatePolicy{ - Name: name, - GroupID: int64(groupIDNum), - }, - PolicyID: policyID, - } - _, err = client.UpdatePolicy(ctx, updatePolicyReq) + + err = executionStrategy.updatePolicy(ctx, policyID, int64(groupIDNum), name) if err != nil { return diag.FromErr(err) } } if d.HasChanges("description", "match_rules", "match_rule_format") { + var isNewVersionNeeded bool version, err := tf.GetIntValue("version", d) if err != nil { - return diag.FromErr(err) - } - versionResp, err := client.GetPolicyVersion(ctx, cloudlets.GetPolicyVersionRequest{ - PolicyID: policyID, - Version: int64(version), - OmitRules: true, - }) - if err != nil { - return diag.FromErr(err) - } - matchRuleFormat, err := tf.GetStringValue("match_rule_format", d) - if err != nil && !errors.Is(err, tf.ErrNotFound) { - return diag.FromErr(err) + if errors.Is(err, tf.ErrNotFound) { + isNewVersionNeeded = true + } else { + return diag.FromErr(err) + } + } else { + isNewVersionNeeded, err = executionStrategy.newPolicyVersionIsNeeded(ctx, policyID, int64(version)) + if err != nil { + return diag.FromErr(err) + } } matchRulesJSON, err := tf.GetStringValue("match_rules", d) if err != nil && !errors.Is(err, tf.ErrNotFound) { return diag.FromErr(err) } - matchRules := make(cloudlets.MatchRules, 0) - if matchRulesJSON != "" { - if err := json.Unmarshal([]byte(matchRulesJSON), &matchRules); err != nil { - return diag.FromErr(err) - } - } description, err := tf.GetStringValue("description", d) if err != nil && !errors.Is(err, tf.ErrNotFound) { return diag.FromErr(err) } - if len(versionResp.Activations) > 0 { - createVersionRequest := cloudlets.CreatePolicyVersionRequest{ - CreatePolicyVersion: cloudlets.CreatePolicyVersion{ - MatchRuleFormat: cloudlets.MatchRuleFormat(matchRuleFormat), - MatchRules: matchRules, - Description: description, - }, - PolicyID: policyID, - } - createVersionResp, err := client.CreatePolicyVersion(ctx, createVersionRequest) - if err != nil { - return diag.FromErr(err) - } - if err := d.Set("version", createVersionResp.Version); err != nil { - return diag.FromErr(fmt.Errorf("%w: %s", tf.ErrValueSet, err.Error())) - } - if err := setWarnings(d, createVersionResp.Warnings); err != nil { - return err - } - return resourcePolicyRead(ctx, d, m) - } - updateVersionReq := cloudlets.UpdatePolicyVersionRequest{ - UpdatePolicyVersion: cloudlets.UpdatePolicyVersion{ - MatchRuleFormat: cloudlets.MatchRuleFormat(matchRuleFormat), - MatchRules: matchRules, - Description: description, - }, - PolicyID: policyID, - Version: int64(version), - } - updateVersionResp, err := client.UpdatePolicyVersion(ctx, updateVersionReq) + + err, updateVersionErr := executionStrategy.updatePolicyVersion(ctx, d, policyID, int64(version), description, matchRulesJSON, isNewVersionNeeded) if err != nil { - if errPolicyRead := resourcePolicyRead(ctx, d, m); errPolicyRead != nil { - return append(errPolicyRead, diag.FromErr(err)...) - } return diag.FromErr(err) } - if err := setWarnings(d, updateVersionResp.Warnings); err != nil { - return err + + if updateVersionErr != nil { + // We still want to have actual (server's) values in state. Otherwise, the values from config would be put into the state as default. + if errPolicyRead := resourcePolicyRead(ctx, d, m); errPolicyRead != nil { + return append(errPolicyRead, diag.FromErr(updateVersionErr)...) + } + return diag.FromErr(updateVersionErr) } } return resourcePolicyRead(ctx, d, m) @@ -415,41 +387,21 @@ func resourcePolicyDelete(ctx context.Context, d *schema.ResourceData, m interfa ctx, session.WithContextLog(logger), ) - client := inst.Client(meta) logger.Debug("Deleting policy") - policyID, err := strconv.ParseInt(d.Id(), 10, 0) + + executionStrategy, err := getPolicyExecutionStrategy(d, meta) if err != nil { return diag.FromErr(err) } - policyVersions, err := getAllPolicyVersions(ctx, policyID, client) + + policyID, err := strconv.ParseInt(d.Id(), 10, 0) if err != nil { return diag.FromErr(err) } - for _, ver := range policyVersions { - if err := client.DeletePolicyVersion(ctx, cloudlets.DeletePolicyVersionRequest{ - PolicyID: policyID, - Version: ver.Version, - }); err != nil { - return diag.FromErr(err) - } - } - activationPending := true - for activationPending { - select { - case <-time.After(DeletionPolicyPollInterval): - if err = client.RemovePolicy(ctx, cloudlets.RemovePolicyRequest{PolicyID: policyID}); err != nil { - statusErr := new(cloudlets.Error) - // if error does not contain information about pending activations, return it as it is not expected - if errors.As(err, &statusErr) && !strings.Contains(statusErr.Detail, "Unable to delete policy because an activation for this policy is still pending") { - return diag.Errorf("remove policy error: %s", err) - } - continue - } - activationPending = false - case <-ctx.Done(): - return diag.Errorf("retry timeout reached: %s", ctx.Err()) - } + err = executionStrategy.deletePolicy(ctx, policyID) + if err != nil { + return diag.FromErr(err) } d.SetId("") @@ -461,58 +413,24 @@ func resourcePolicyImport(ctx context.Context, d *schema.ResourceData, m interfa logger := meta.Log("Cloudlets", "resourcePolicyImport") logger.Debugf("Import Policy") - client := inst.Client(meta) - name := d.Id() if name == "" { return nil, fmt.Errorf("policy name cannot be empty") } - policy, err := findPolicyByName(ctx, name, client) + policyStrategy, policyID, err := discoverPolicyExecutionStrategy(ctx, meta, name) if err != nil { return nil, err } - - d.SetId(strconv.FormatInt(policy.PolicyID, 10)) - - version, err := findLatestPolicyVersion(ctx, policy.PolicyID, client) - if err != nil { + if err = policyStrategy.setPolicyType(d); err != nil { return nil, err } - err = d.Set("version", version) - if err != nil { - return nil, err - } + d.SetId(strconv.FormatInt(policyID, 10)) return []*schema.ResourceData{d}, nil } -func findPolicyByName(ctx context.Context, name string, client cloudlets.Cloudlets) (*cloudlets.Policy, error) { - pageSize, offset := 1000, 0 - var policy *cloudlets.Policy - for { - policies, err := client.ListPolicies(ctx, cloudlets.ListPoliciesRequest{ - Offset: offset, - PageSize: &pageSize, - }) - if err != nil { - return nil, err - } - for _, p := range policies { - if p.Name == name { - policy = &p - return policy, nil - } - } - if len(policies) < pageSize { - break - } - offset += pageSize - } - return nil, fmt.Errorf("policy '%s' does not exist", name) -} - func diffSuppressGroupID(_, old, new string, _ *schema.ResourceData) bool { return strings.TrimPrefix(old, "grp_") == strings.TrimPrefix(new, "grp_") } @@ -550,7 +468,7 @@ func diffMatchRules(old, new string) bool { return reflect.DeepEqual(oldRules, newRules) } -func warningsToJSON(warnings []cloudlets.Warning) ([]byte, error) { +func warningsToJSON[W cloudlets.Warning | v3.MatchRulesWarning](warnings []W) ([]byte, error) { var warningsJSON []byte if len(warnings) == 0 { return warningsJSON, nil @@ -564,14 +482,138 @@ func warningsToJSON(warnings []cloudlets.Warning) ([]byte, error) { return warningsJSON, nil } -func setWarnings(d *schema.ResourceData, warnings []cloudlets.Warning) diag.Diagnostics { +func setWarnings[W cloudlets.Warning | v3.MatchRulesWarning](d *schema.ResourceData, warnings []W) error { warningsJSON, err := warningsToJSON(warnings) if err != nil { - return diag.FromErr(err) + return err } - if err := d.Set("warnings", string(warningsJSON)); err != nil { - return diag.FromErr(err) + return d.Set("warnings", string(warningsJSON)) +} + +func getPolicyExecutionStrategy(d *schema.ResourceData, meta meta.Meta) (policyExecutionStrategy, error) { + var executionStrategy policyExecutionStrategy + isV3, err := tf.GetBoolValue("is_shared", d) + if err != nil { + return nil, err } - return nil + + if isV3 { + executionStrategy = v3PolicyStrategy{ClientV3(meta)} + } else { + executionStrategy = v2PolicyStrategy{Client(meta)} + } + return executionStrategy, nil +} + +type policyExecutionStrategy interface { + createPolicy(ctx context.Context, cloudletName, cloudletCode string, groupID int64) (int64, error) + updatePolicyVersion(ctx context.Context, d *schema.ResourceData, policyID, version int64, description, matchRulesJSON string, newVersionRequired bool) (error, error) + updatePolicy(ctx context.Context, policyID, groupID int64, cloudletName string) error + newPolicyVersionIsNeeded(ctx context.Context, policyID, version int64) (bool, error) + readPolicy(ctx context.Context, policyID int64, version *int64) (map[string]any, error) + deletePolicy(ctx context.Context, policyID int64) error + getVersionStrategy(meta meta.Meta) versionStrategy + setPolicyType(d *schema.ResourceData) error + isFirstVersionCreated() bool +} + +func discoverPolicyExecutionStrategy(ctx context.Context, meta meta.Meta, policyName string) (policyExecutionStrategy, int64, error) { + + strategy, policyID, errV2 := checkForV2Policy(ctx, meta, policyName) + if strategy != nil { + return strategy, policyID, nil + } + + strategy, policyID, errV3 := checkForV3Policy(ctx, meta, policyName) + if strategy != nil { + return strategy, policyID, nil + } + + var errMessage string + if errV2 != nil { + errMessage += fmt.Sprintf("could not list V2 policies: %s\n", errV2) + } + if errV3 != nil { + errMessage += fmt.Sprintf("could not list V3 policies: %s", errV3) + } + if errMessage != "" { + return nil, 0, fmt.Errorf(errMessage) + } + + return nil, 0, fmt.Errorf("policy '%s' does not exist", policyName) +} + +func checkForV2Policy(ctx context.Context, meta meta.Meta, policyName string) (policyExecutionStrategy, int64, error) { + v2Client := Client(meta) + size, offset := 1000, 0 + var errV2 error + for { + policies, err := v2Client.ListPolicies(ctx, cloudlets.ListPoliciesRequest{ + Offset: offset, + PageSize: tools.IntPtr(size), + }) + if err == nil { + if policyID := findPolicyV2ByName(policies, policyName); policyID != 0 { + return v2PolicyStrategy{ + client: v2Client, + }, policyID, nil + } + if len(policies) < size { + break + } + offset++ + } else { + errV2 = err + break + } + } + + return nil, 0, errV2 +} + +func checkForV3Policy(ctx context.Context, meta meta.Meta, policyName string) (policyExecutionStrategy, int64, error) { + v3Client := ClientV3(meta) + size, page := 1000, 0 + var errV3 error + for { + policiesV3, err := v3Client.ListPolicies(ctx, v3.ListPoliciesRequest{ + Page: page, + Size: size, + }) + if err == nil { + if policyID := findPolicyV3ByName(policiesV3.Content, policyName); policyID != 0 { + return v3PolicyStrategy{ + client: v3Client, + }, policyID, nil + } + if len(policiesV3.Content) < size { + break + } + page++ + } else { + errV3 = err + break + } + } + + return nil, 0, errV3 +} + +func findPolicyV3ByName(policies []v3.Policy, policyName string) int64 { + for _, policy := range policies { + if policy.Name == policyName { + return policy.ID + } + } + return 0 +} + +func findPolicyV2ByName(policies []cloudlets.Policy, policyName string) int64 { + for _, policy := range policies { + if policy.Name == policyName { + return policy.PolicyID + } + } + return 0 } diff --git a/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_activation.go b/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_activation.go index fca93fc1c..f056888bb 100644 --- a/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_activation.go +++ b/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_activation.go @@ -4,15 +4,16 @@ import ( "context" "errors" "fmt" - "reflect" "regexp" "sort" + "strconv" "strings" "time" "github.com/apex/log" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets" + v3 "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets/v3" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/session" "github.com/akamai/terraform-provider-akamai/v5/pkg/common/tf" "github.com/akamai/terraform-provider-akamai/v5/pkg/common/timeouts" @@ -27,7 +28,10 @@ func resourceCloudletsPolicyActivation() *schema.Resource { ReadContext: resourcePolicyActivationRead, UpdateContext: resourcePolicyActivationUpdate, DeleteContext: resourcePolicyActivationDelete, - Schema: resourceCloudletsPolicyActivationSchema(), + Importer: &schema.ResourceImporter{ + StateContext: resourcePolicyActivationImport, + }, + Schema: resourceCloudletsPolicyActivationSchema(), Timeouts: &schema.ResourceTimeout{ Default: &PolicyActivationResourceTimeout, }, @@ -67,10 +71,15 @@ func resourceCloudletsPolicyActivationSchema() map[string]*schema.Schema { }, "associated_properties": { Type: schema.TypeSet, - Required: true, + Optional: true, Elem: &schema.Schema{Type: schema.TypeString}, MinItems: 1, - Description: "Set of property IDs to link to this Cloudlets policy", + Description: "Set of property IDs to link to this Cloudlets policy. It is required for non-shared policies", + }, + "is_shared": { + Type: schema.TypeBool, + Computed: true, + Description: "Indicates if policy that is being activated is a shared policy", }, "timeouts": { Type: schema.TypeList, @@ -120,59 +129,28 @@ func resourcePolicyActivationDelete(ctx context.Context, rd *schema.ResourceData logger := meta.Log("Cloudlets", "resourcePolicyActivationDelete") logger.Debug("Deleting cloudlets policy activation") ctx = session.ContextWithOptions(ctx, session.WithContextLog(logger)) - client := inst.Client(meta) - pID, err := tf.GetIntValue("policy_id", rd) + strategy := getActivationStrategy(rd, meta, logger) + + policyID, err := tf.GetIntValueAsInt64("policy_id", rd) if err != nil { return diag.FromErr(err) } - policyID := int64(pID) - network, err := getPolicyActivationNetwork(strings.Split(rd.Id(), ":")[1]) + network, err := tf.GetStringValue("network", rd) if err != nil { return diag.FromErr(err) } - policyProperties, err := client.GetPolicyProperties(ctx, cloudlets.GetPolicyPropertiesRequest{PolicyID: policyID}) - if err != nil { - return diag.Errorf("%s: cannot find policy %d properties: %s", ErrPolicyActivation.Error(), policyID, err.Error()) - } - activations, err := waitForListPolicyActivations(ctx, client, cloudlets.ListPolicyActivationsRequest{ - PolicyID: policyID, - Network: network, - }) + version, err := tf.GetIntValueAsInt64("version", rd) if err != nil { return diag.FromErr(err) } - logger.Debugf("Removing all policy (ID=%d) properties", policyID) - for propertyName, policyProperty := range policyProperties { - // filter out property by network - validProperty := false - for _, act := range activations { - if act.PropertyInfo.Name == propertyName { - validProperty = true - break - } - } - if !validProperty { - continue - } - // wait for removal until there aren't any pending activations - if err = waitForNotPendingPolicyActivation(ctx, logger, client, policyID, network); err != nil { - return diag.FromErr(err) - } - - // proceed to delete property from policy - err = client.DeletePolicyProperty(ctx, cloudlets.DeletePolicyPropertyRequest{ - PolicyID: policyID, - PropertyID: policyProperty.ID, - Network: network, - }) - if err != nil { - return diag.Errorf("%s: cannot delete property '%s' from policy ID %d and network '%s'. Please, try once again later.\n%s", ErrPolicyActivation.Error(), propertyName, policyID, network, err.Error()) - } + if err = strategy.deactivatePolicy(ctx, policyID, version, network); err != nil { + return diag.FromErr(err) } + logger.Debugf("All properties have been removed from policy ID %d", policyID) rd.SetId("") return nil @@ -183,7 +161,7 @@ func resourcePolicyActivationUpdate(ctx context.Context, rd *schema.ResourceData logger := meta.Log("Cloudlets", "resourcePolicyActivationUpdate") ctx = session.ContextWithOptions(ctx, session.WithContextLog(logger)) - client := inst.Client(meta) + strategy := getActivationStrategy(rd, meta, logger) if !rd.HasChangeExcept("timeouts") { logger.Debug("Only timeouts were updated, skipping") @@ -191,7 +169,7 @@ func resourcePolicyActivationUpdate(ctx context.Context, rd *schema.ResourceData } // 2. In such case, create a new version to activate (for creation, look into resource policy) - policyID, err := tf.GetIntValue("policy_id", rd) + policyID, err := tf.GetIntValueAsInt64("policy_id", rd) if err != nil { return diag.FromErr(err) } @@ -200,97 +178,45 @@ func resourcePolicyActivationUpdate(ctx context.Context, rd *schema.ResourceData if err != nil { return diag.FromErr(err) } - activationNetwork, err := getPolicyActivationNetwork(network) - if err != nil { - return diag.FromErr(err) - } - v, err := tf.GetIntValue("version", rd) + version, err := tf.GetIntValueAsInt64("version", rd) if err != nil { return diag.FromErr(err) } - version := int64(v) - // policy version validation - _, err = client.GetPolicyVersion(ctx, cloudlets.GetPolicyVersionRequest{ - PolicyID: int64(policyID), - Version: version, - OmitRules: true, - }) - if err != nil { - if diagnostics := diag.FromErr(tf.RestoreOldValues(rd, []string{"version", "associated_properties"})); diagnostics != nil { - return diagnostics - } - return diag.Errorf("%s: cannot find the given policy version (%d): %s", ErrPolicyActivation.Error(), version, err.Error()) - } - associatedProps, err := tf.GetSetValue("associated_properties", rd) - if err != nil { + if err = strategy.setupCloudletSpecificData(rd, network); err != nil { return diag.FromErr(err) } - var newPolicyProperties []string - for _, prop := range associatedProps.List() { - newPolicyProperties = append(newPolicyProperties, prop.(string)) - } - sort.Strings(newPolicyProperties) - // 3. look for activations with this version which is active in the given network - activations, err := waitForListPolicyActivations(ctx, client, cloudlets.ListPolicyActivationsRequest{ - PolicyID: int64(policyID), - Network: activationNetwork, - }) + isAlreadyActive, id, err := strategy.isReactivationNotNeeded(ctx, policyID, version, rd.HasChange("version")) if err != nil { - return diag.Errorf("%v update: %s", ErrPolicyActivation, err.Error()) + if restoreDiags := diag.FromErr(tf.RestoreOldValues(rd, []string{"version", "associated_properties"})); restoreDiags != nil && len(restoreDiags) > 0 { + return append(restoreDiags, diag.FromErr(err)...) + } + return diag.FromErr(err) } - // activations, at this point, contains old and new activations - - // sort by activation date, reverse. To find out the state of the latest activations - activations = sortPolicyActivationsByDate(activations) - - // find out which properties are activated in those activations - // version does not matter at this point - activeProps := getActiveProperties(activations) - // 4. all "additional_properties" are active for the given version, policyID and network, proceed to read stage - if reflect.DeepEqual(activeProps, newPolicyProperties) && !rd.HasChanges("version") && activations[0].PolicyInfo.Version == version { - // in such case, return + if isAlreadyActive { + // all is active for the given version, policyID and network, proceed to read stage logger.Debugf("This policy (ID=%d, version=%d) is already active.", policyID, version) - rd.SetId(formatPolicyActivationID(int64(policyID), activationNetwork)) + rd.SetId(id) return resourcePolicyActivationRead(ctx, rd, m) } - // 5. Activate policy version. This will include new associated_properties + the ones which need to be removed - // it will fail if any of the associated_properties are not valid - logger.Debugf("Proceeding to activate the policy ID=%d (version=%d, properties=[%s], network='%s') is not active.", - policyID, version, strings.Join(newPolicyProperties, ", "), activationNetwork) - - _, err = client.ActivatePolicyVersion(ctx, cloudlets.ActivatePolicyVersionRequest{ - PolicyID: int64(policyID), - Async: true, - Version: version, - PolicyVersionActivation: cloudlets.PolicyVersionActivation{ - Network: activationNetwork, - AdditionalPropertyNames: newPolicyProperties, - }, - }) - if err != nil { - if diagnostics := diag.FromErr(tf.RestoreOldValues(rd, []string{"version", "associated_properties"})); diagnostics != nil { - return diagnostics + // something has changed, we need to reactivate it + if err = strategy.reactivateVersion(ctx, policyID, version); err != nil { + if restoreDiags := diag.FromErr(tf.RestoreOldValues(rd, []string{"version", "associated_properties"})); restoreDiags != nil { + return append(restoreDiags, diag.FromErr(err)...) } return diag.Errorf("%v update: %s", ErrPolicyActivation, err.Error()) } - // 6. remove from the server all unnecessary policy associated_properties - removedProperties, err := syncToServerRemovedProperties(ctx, logger, client, int64(policyID), activationNetwork, activeProps, newPolicyProperties) - if err != nil { - return diag.FromErr(err) - } - - // 7. poll until active - _, err = waitForPolicyActivation(ctx, client, int64(policyID), version, activationNetwork, newPolicyProperties, removedProperties) + // poll until active + id, err = strategy.waitForActivation(ctx, policyID, version) if err != nil { return diag.Errorf("%v update: %s", ErrPolicyActivation, err.Error()) } - rd.SetId(formatPolicyActivationID(int64(policyID), activationNetwork)) + rd.SetId(id) return resourcePolicyActivationRead(ctx, rd, m) } @@ -299,11 +225,10 @@ func resourcePolicyActivationCreate(ctx context.Context, rd *schema.ResourceData meta := meta.Must(m) logger := meta.Log("Cloudlets", "resourcePolicyActivationCreate") ctx = session.ContextWithOptions(ctx, session.WithContextLog(logger)) - client := inst.Client(meta) logger.Debug("Creating policy activation") - policyID, err := tf.GetIntValue("policy_id", rd) + policyID, err := tf.GetIntValueAsInt64("policy_id", rd) if err != nil { return diag.FromErr(err) } @@ -311,67 +236,40 @@ func resourcePolicyActivationCreate(ctx context.Context, rd *schema.ResourceData if err != nil { return diag.FromErr(err) } - versionActivationNetwork, err := getPolicyActivationNetwork(network) + + version, err := tf.GetIntValueAsInt64("version", rd) if err != nil { return diag.FromErr(err) } - associatedProps, err := tf.GetSetValue("associated_properties", rd) + + strategy, isShared, err := discoverActivationStrategy(ctx, policyID, meta, logger) if err != nil { return diag.FromErr(err) } - var associatedProperties []string - for _, prop := range associatedProps.List() { - associatedProperties = append(associatedProperties, prop.(string)) - } - sort.Strings(associatedProperties) - v, err := tf.GetIntValue("version", rd) - if err != nil { + if err = strategy.setupCloudletSpecificData(rd, network); err != nil { return diag.FromErr(err) } - version := int64(v) - logger.Debugf("checking if policy version %d is active", version) - policyVersion, err := client.GetPolicyVersion(ctx, cloudlets.GetPolicyVersionRequest{ - Version: version, - PolicyID: int64(policyID), - OmitRules: true, - }) + isActive, id, err := strategy.isVersionAlreadyActive(ctx, policyID, version) if err != nil { - return diag.Errorf("%s: cannot find the given policy version (%d): %s", ErrPolicyActivation.Error(), version, err.Error()) + return diag.FromErr(err) } - policyActivations := sortPolicyActivationsByDate(policyVersion.Activations) - // just the first activations must correspond to the given properties - var activeProperties []string - for _, act := range policyActivations { - if act.Network == versionActivationNetwork && - act.PolicyInfo.Status == cloudlets.PolicyActivationStatusActive { - activeProperties = append(activeProperties, act.PropertyInfo.Name) - } - } - sort.Strings(activeProperties) - if reflect.DeepEqual(activeProperties, associatedProperties) { + if isActive { // if the given version is active, just refresh status and quit - logger.Debugf("policy %d, with version %d and properties [%s], is already active in %s. Fetching all details from server", policyID, version, strings.Join(associatedProperties, ", "), string(versionActivationNetwork)) - rd.SetId(formatPolicyActivationID(int64(policyID), cloudlets.PolicyActivationNetwork(network))) + rd.SetId(id) + if err = rd.Set("is_shared", isShared); err != nil { + return diag.Errorf("was not able to set `is_shared` computed field: %s", err) + } return resourcePolicyActivationRead(ctx, rd, m) } // at this point, we are sure that the given version is not active - logger.Debugf("activating policy %d version %d, network %s and properties [%s]", policyID, version, string(versionActivationNetwork), strings.Join(associatedProperties, ", ")) pollingActivationTries := PolicyActivationRetryPollMinimum for { - _, err = client.ActivatePolicyVersion(ctx, cloudlets.ActivatePolicyVersionRequest{ - PolicyID: int64(policyID), - Version: version, - Async: true, - PolicyVersionActivation: cloudlets.PolicyVersionActivation{ - Network: versionActivationNetwork, - AdditionalPropertyNames: associatedProperties, - }, - }) + err = strategy.activateVersion(ctx, policyID, version) if err == nil { break } @@ -379,7 +277,7 @@ func resourcePolicyActivationCreate(ctx context.Context, rd *schema.ResourceData select { case <-time.After(pollingActivationTries): logger.Debugf("retrying policy activation after %d minutes", pollingActivationTries.Minutes()) - if pollingActivationTries > PolicyActivationRetryTimeout || !policyActivationRetryRegexp.MatchString(strings.ToLower(err.Error())) { + if pollingActivationTries > PolicyActivationRetryTimeout || !strategy.shouldRetryActivation(err) { return diag.Errorf("%v create: %s", ErrPolicyActivation, err.Error()) } @@ -392,16 +290,21 @@ func resourcePolicyActivationCreate(ctx context.Context, rd *schema.ResourceData if errors.Is(ctx.Err(), context.Canceled) { return diag.Errorf("operation canceled while waiting for retrying policy activation, last error: %s", err) } - return diag.FromErr(fmt.Errorf("operation context terminated: %w", ctx.Err())) + } } // wait until policy activation is done - act, err := waitForPolicyActivation(ctx, client, int64(policyID), version, versionActivationNetwork, associatedProperties, nil) + id, err = strategy.waitForActivation(ctx, policyID, version) if err != nil { return diag.Errorf("%v create: %s", ErrPolicyActivation, err.Error()) } - rd.SetId(formatPolicyActivationID(act[0].PolicyInfo.PolicyID, act[0].Network)) + + rd.SetId(id) + + if err = rd.Set("is_shared", isShared); err != nil { + return diag.Errorf("was not able to set `is_shared` computed field: %s", err) + } return resourcePolicyActivationRead(ctx, rd, m) } @@ -410,11 +313,11 @@ func resourcePolicyActivationRead(ctx context.Context, rd *schema.ResourceData, meta := meta.Must(m) logger := meta.Log("Cloudlets", "resourcePolicyActivationRead") ctx = session.ContextWithOptions(ctx, session.WithContextLog(logger)) - client := inst.Client(meta) + strategy := getActivationStrategy(rd, meta, logger) logger.Debug("Reading policy activations") - policyID, err := tf.GetIntValue("policy_id", rd) + policyID, err := tf.GetIntValueAsInt64("policy_id", rd) if err != nil { return diag.FromErr(err) } @@ -423,38 +326,67 @@ func resourcePolicyActivationRead(ctx context.Context, rd *schema.ResourceData, if err != nil { return diag.FromErr(err) } - net, err := getPolicyActivationNetwork(network) + + attrs, err := strategy.readActivationFromServer(ctx, policyID, network) if err != nil { + return diag.Errorf("policy activation read: %s", err.Error()) + } + + if attrs == nil { + rd.SetId("") + return nil + } + + if err = tf.SetAttrs(rd, attrs); err != nil { return diag.FromErr(err) } - activations, err := waitForListPolicyActivations(ctx, client, cloudlets.ListPolicyActivationsRequest{ - PolicyID: int64(policyID), - Network: net, - }) - if err != nil { - return diag.Errorf("%v read: %s", ErrPolicyActivation, err.Error()) + return nil +} + +func getActivationStrategy(rd *schema.ResourceData, m meta.Meta, logger log.Interface) activationStrategy { + if rd.Get("is_shared").(bool) { + return &v3ActivationStrategy{client: ClientV3(m), logger: logger} } + return &v2ActivationStrategy{client: Client(m), logger: logger} +} - if len(activations) == 0 { - return diag.Errorf("%v read: cannot find any activation for the given policy (%d) and network ('%s')", ErrPolicyActivation, policyID, net) +func resourcePolicyActivationImport(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + meta := meta.Must(m) + logger := meta.Log("Cloudlets", "resourcePolicyActivationImport") + logger.Debugf("Import Policy Activation") + + resID := d.Id() + parts := strings.Split(resID, ":") + + if len(parts) != 2 { + return nil, fmt.Errorf("import id should be of format: :, for example: 1234:staging") } - activations = sortPolicyActivationsByDate(activations) + policyID, err := strconv.ParseInt(parts[0], 10, 64) + if err != nil { + return nil, err + } + network := parts[1] - if err := rd.Set("status", activations[0].PolicyInfo.Status); err != nil { - return diag.Errorf("%v: %s", tf.ErrValueSet, err.Error()) + strategy, _, err := discoverActivationStrategy(ctx, policyID, meta, logger) + if err != nil { + return nil, err } - if err := rd.Set("version", activations[0].PolicyInfo.Version); err != nil { - return diag.Errorf("%v: %s", tf.ErrValueSet, err.Error()) + + attrs, id, err := strategy.fetchValuesForImport(ctx, policyID, network) + if err != nil { + return nil, err } - associatedProperties := getActiveProperties(activations) - if err := rd.Set("associated_properties", associatedProperties); err != nil { - return diag.Errorf("%v: %s", tf.ErrValueSet, err.Error()) + err = tf.SetAttrs(d, attrs) + if err != nil { + return nil, err } - return nil + d.SetId(id) + + return []*schema.ResourceData{d}, nil } func formatPolicyActivationID(policyID int64, network cloudlets.PolicyActivationNetwork) string { @@ -727,3 +659,34 @@ func waitForListPolicyActivations(ctx context.Context, client cloudlets.Cloudlet return activations, nil } + +func discoverActivationStrategy(ctx context.Context, policyID int64, meta meta.Meta, logger log.Interface) (activationStrategy, bool, error) { + v2Client := Client(meta) + _, v2Err := v2Client.GetPolicy(ctx, cloudlets.GetPolicyRequest{PolicyID: policyID}) + if v2Err == nil { + return &v2ActivationStrategy{client: v2Client, logger: logger}, false, nil + } + + v3Client := ClientV3(meta) + _, V3err := v3Client.GetPolicy(ctx, v3.GetPolicyRequest{PolicyID: policyID}) + if V3err == nil { + return &v3ActivationStrategy{client: v3Client, logger: logger}, true, nil + } + + return nil, false, fmt.Errorf("could not get policy %d: neither as V2 (%s) nor as V3 (%s)", policyID, v2Err, V3err) + +} + +type activationStrategy interface { + isVersionAlreadyActive(ctx context.Context, policyID, version int64) (bool, string, error) + setupCloudletSpecificData(rd *schema.ResourceData, network string) error + activateVersion(ctx context.Context, policyID, version int64) error + shouldRetryActivation(err error) bool + reactivateVersion(ctx context.Context, policyID, version int64) error + waitForActivation(ctx context.Context, policyID, version int64) (string, error) + readActivationFromServer(ctx context.Context, policyID int64, network string) (map[string]any, error) + isReactivationNotNeeded(ctx context.Context, policyID, version int64, hasVersionChange bool) (bool, string, error) + deactivatePolicy(ctx context.Context, policyID, version int64, network string) error + getPolicyActivation(ctx context.Context, policyID int64, network string) (*policyActivationDataSourceModel, error) + fetchValuesForImport(ctx context.Context, policyID int64, network string) (map[string]any, string, error) +} diff --git a/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_activation_test.go b/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_activation_test.go index 7089d17e1..060fa0b10 100644 --- a/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_activation_test.go +++ b/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_activation_test.go @@ -2,11 +2,13 @@ package cloudlets import ( "fmt" + "net/http" "regexp" "testing" "time" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets" + v3 "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets/v3" "github.com/akamai/terraform-provider-akamai/v5/pkg/common/testutils" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/stretchr/testify/mock" @@ -19,6 +21,7 @@ func TestResourceCloudletsPolicyActivation(t *testing.T) { }{ "try to create activation with a non existing policy version": { init: func(m *cloudlets.Mock) { + expectToDiscoverPolicyAsV2(m, 1234) m.On("GetPolicyVersion", mock.Anything, cloudlets.GetPolicyVersionRequest{PolicyID: 1234, Version: 1, OmitRules: true}).Return(nil, fmt.Errorf("an error")) }, steps: []resource.TestStep{ @@ -52,6 +55,7 @@ func TestResourceCloudletsPolicyActivation(t *testing.T) { policyID, version, network, properties := int64(1234), int64(1), cloudlets.PolicyActivationNetworkStaging, []string{"prp_0", "prp_1"} propertyNotFoundError := fmt.Errorf(`"detail": "Requested propertyName \"test.property.name\" does not exist"`) // create + expectToDiscoverPolicyAsV2(m, 1234) activations := make([]cloudlets.PolicyActivation, len(properties)) for _, p := range properties { activations = append(activations, cloudlets.PolicyActivation{APIVersion: "1.0", Network: network, PolicyInfo: cloudlets.PolicyInfo{ @@ -85,6 +89,7 @@ func TestResourceCloudletsPolicyActivation(t *testing.T) { init: func(m *cloudlets.Mock) { policyID, version, network, properties := int64(1234), int64(1), cloudlets.PolicyActivationNetworkStaging, []string{"prp_0", "prp_1"} // create + expectToDiscoverPolicyAsV2(m, 1234) activations := make([]cloudlets.PolicyActivation, len(properties)) for _, p := range properties { activations = append(activations, cloudlets.PolicyActivation{APIVersion: "1.0", Network: network, PolicyInfo: cloudlets.PolicyInfo{ @@ -120,6 +125,7 @@ func TestResourceCloudletsPolicyActivation(t *testing.T) { init: func(m *cloudlets.Mock) { policyID, version, network, properties := int64(1234), int64(1), cloudlets.PolicyActivationNetworkStaging, []string{"prp_0", "prp_1"} // create + expectToDiscoverPolicyAsV2(m, 1234) activations := make([]cloudlets.PolicyActivation, len(properties)) for _, p := range properties { activations = append(activations, cloudlets.PolicyActivation{APIVersion: "1.0", Network: network, PolicyInfo: cloudlets.PolicyInfo{ @@ -157,6 +163,7 @@ func TestResourceCloudletsPolicyActivation(t *testing.T) { init: func(m *cloudlets.Mock) { policyID, version, network, properties := int64(1234), int64(1), cloudlets.PolicyActivationNetworkStaging, []string{"prp_0", "prp_1"} // create + expectToDiscoverPolicyAsV2(m, policyID) activations := make([]cloudlets.PolicyActivation, len(properties)) for _, p := range properties { activations = append(activations, cloudlets.PolicyActivation{APIVersion: "1.0", Network: network, PolicyInfo: cloudlets.PolicyInfo{ @@ -190,6 +197,7 @@ func TestResourceCloudletsPolicyActivation(t *testing.T) { init: func(m *cloudlets.Mock) { policyID, version, staging, properties := int64(1234), int64(1), cloudlets.PolicyActivationNetworkStaging, []string{"prp_0", "prp_1"} // create + expectToDiscoverPolicyAsV2(m, policyID) activations := make([]cloudlets.PolicyActivation, len(properties)) for _, p := range properties { activations = append(activations, cloudlets.PolicyActivation{APIVersion: "1.0", Network: staging, PolicyInfo: cloudlets.PolicyInfo{ @@ -212,6 +220,7 @@ func TestResourceCloudletsPolicyActivation(t *testing.T) { init: func(m *cloudlets.Mock) { policyID, version, staging, properties := int64(1234), int64(1), cloudlets.PolicyActivationNetworkStaging, []string{"prp_0", "prp_1"} // create + expectToDiscoverPolicyAsV2(m, policyID) activations := make([]cloudlets.PolicyActivation, len(properties)) for _, p := range properties { activations = append(activations, cloudlets.PolicyActivation{APIVersion: "1.0", Network: staging, PolicyInfo: cloudlets.PolicyInfo{ @@ -245,6 +254,7 @@ func TestResourceCloudletsPolicyActivation(t *testing.T) { init: func(m *cloudlets.Mock) { properties, policyID, v1, staging, times := []string{"prp_0", "prp_1"}, int64(1234), int64(1), cloudlets.PolicyActivationNetworkStaging, 1 // create + expectToDiscoverPolicyAsV2(m, policyID) activations := make([]cloudlets.PolicyActivation, len(properties)) for _, p := range properties { activations = append(activations, cloudlets.PolicyActivation{APIVersion: "1.0", Network: staging, PolicyInfo: cloudlets.PolicyInfo{ @@ -310,6 +320,7 @@ func TestResourceCloudletsPolicyActivation(t *testing.T) { "create and read activation, version == 1, inactive -> activate -> error": { init: func(m *cloudlets.Mock) { // create + expectToDiscoverPolicyAsV2(m, 1234) expectGetPolicyVersion(m, 1234, 1, []cloudlets.PolicyActivation{ {APIVersion: "1.0", Network: "prod", PolicyInfo: cloudlets.PolicyInfo{ PolicyID: 1234, Version: 1, Status: cloudlets.PolicyActivationStatusInactive, @@ -327,6 +338,7 @@ func TestResourceCloudletsPolicyActivation(t *testing.T) { "create and read activation, version == 1, inactive -> activate -> get active policy activation -> error": { init: func(m *cloudlets.Mock) { // create + expectToDiscoverPolicyAsV2(m, 1234) expectGetPolicyVersion(m, 1234, 1, []cloudlets.PolicyActivation{ {APIVersion: "1.0", Network: "staging", PolicyInfo: cloudlets.PolicyInfo{ PolicyID: 1234, Version: 1, Status: cloudlets.PolicyActivationStatusInactive, @@ -347,6 +359,7 @@ func TestResourceCloudletsPolicyActivation(t *testing.T) { init: func(m *cloudlets.Mock) { staging, properties, policyID, v1, active := cloudlets.PolicyActivationNetworkStaging, []string{"prp_0", "prp_1"}, int64(1234), int64(1), cloudlets.PolicyActivationStatusActive // create, policy active so no need to activate + expectToDiscoverPolicyAsV2(m, policyID) expectGetPolicyVersion(m, policyID, v1, []cloudlets.PolicyActivation{ {APIVersion: "1.0", Network: staging, PolicyInfo: cloudlets.PolicyInfo{ PolicyID: policyID, Version: v1, Status: active, @@ -375,6 +388,7 @@ func TestResourceCloudletsPolicyActivation(t *testing.T) { init: func(m *cloudlets.Mock) { staging, properties, policyID, v1, active := cloudlets.PolicyActivationNetworkStaging, []string{"prp_0", "prp_1"}, int64(1234), int64(1), cloudlets.PolicyActivationStatusActive // create + expectToDiscoverPolicyAsV2(m, policyID) expectGetPolicyVersion(m, policyID, v1, []cloudlets.PolicyActivation{}, nil).Once() expectActivatePolicyVersion(m, policyID, v1, staging, properties, cloudlets.PolicyActivationStatusPending, "", 1, nil).Once() // poll until active -> waitForPolicyActivation() @@ -396,6 +410,7 @@ func TestResourceCloudletsPolicyActivation(t *testing.T) { staging, properties, policyID, v1, active := cloudlets.PolicyActivationNetworkStaging, []string{"prp_0", "prp_1"}, int64(1234), int64(1), cloudlets.PolicyActivationStatusActive // 1 - for policy_activation_version1.tf // create + expectToDiscoverPolicyAsV2(m, policyID) expectGetPolicyVersion(m, policyID, v1, []cloudlets.PolicyActivation{}, nil).Once() expectActivatePolicyVersion(m, policyID, v1, staging, properties, cloudlets.PolicyActivationStatusPending, "", 1, nil).Once() // poll until active -> waitForPolicyActivation() @@ -433,6 +448,7 @@ func TestResourceCloudletsPolicyActivation(t *testing.T) { "create activation - failed activation while polling": { init: func(m *cloudlets.Mock) { // create + expectToDiscoverPolicyAsV2(m, 1234) expectGetPolicyVersion(m, 1234, 1, []cloudlets.PolicyActivation{}, nil).Once() expectActivatePolicyVersion(m, 1234, 1, "staging", []string{"prp_0", "prp_1"}, cloudlets.PolicyActivationStatusPending, "", 1, nil).Once() // poll until active -> waitForPolicyActivation() @@ -448,6 +464,7 @@ func TestResourceCloudletsPolicyActivation(t *testing.T) { "create activation - failed activation while polling with no failed status": { init: func(m *cloudlets.Mock) { // create + expectToDiscoverPolicyAsV2(m, 1234) expectGetPolicyVersion(m, 1234, 1, []cloudlets.PolicyActivation{}, nil).Once() expectActivatePolicyVersion(m, 1234, 1, "staging", []string{"prp_0", "prp_1"}, cloudlets.PolicyActivationStatusPending, "", 1, nil).Once() // poll until active -> waitForPolicyActivation() @@ -796,6 +813,936 @@ func TestResourceCloudletsPolicyActivation(t *testing.T) { }, }, }, + "import - success": { + init: func(m *cloudlets.Mock) { + // create + properties, policyID, version, network := []string{"prp_0", "prp_1"}, int64(1234), int64(1), cloudlets.PolicyActivationNetworkStaging + expectToDiscoverPolicyAsV2(m, policyID) + activations := make([]cloudlets.PolicyActivation, len(properties)) + for _, p := range properties { + activations = append(activations, cloudlets.PolicyActivation{APIVersion: "1.0", Network: network, PolicyInfo: cloudlets.PolicyInfo{ + PolicyID: policyID, Version: version, Status: cloudlets.PolicyActivationStatusInactive, + }, PropertyInfo: cloudlets.PropertyInfo{Name: p}}) + } + expectGetPolicyVersion(m, policyID, version, activations, nil).Times(1) + expectActivatePolicyVersion(m, policyID, version, network, properties, cloudlets.PolicyActivationStatusActive, "", 1, nil).Times(1) + // already active - no need to poll + expectListPolicyActivations(m, policyID, version, network, properties, cloudlets.PolicyActivationStatusActive, "", 1, nil).Times(1) + // read + expectListPolicyActivations(m, policyID, version, network, properties, cloudlets.PolicyActivationStatusActive, "", 1, nil).Times(2) + // import + expectToDiscoverPolicyAsV2(m, policyID) + expectListPolicyActivations(m, policyID, version, network, properties, cloudlets.PolicyActivationStatusActive, "", 1, nil).Times(1) + // read + expectListPolicyActivations(m, policyID, version, network, properties, cloudlets.PolicyActivationStatusActive, "", 1, nil).Times(1) + // delete + expectDeletePhase(m, 1234, []string{"prp_0", "prp_1"}, nil, cloudlets.PolicyActivationNetworkStaging, nil, nil) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyActivation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(cloudlets.PolicyActivationStatusActive)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.#", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.0.default", "2h"), + ), + }, + { + ImportState: true, + ImportStateId: "1234:staging", + ResourceName: "akamai_cloudlets_policy_activation.test", + ImportStateVerify: true, + }, + }, + }, + "import - only deactivated activation - expect an error": { + init: func(m *cloudlets.Mock) { + // create + properties, policyID, version, network := []string{"prp_0", "prp_1"}, int64(1234), int64(1), cloudlets.PolicyActivationNetworkStaging + expectToDiscoverPolicyAsV2(m, policyID) + activations := make([]cloudlets.PolicyActivation, len(properties)) + for _, p := range properties { + activations = append(activations, cloudlets.PolicyActivation{APIVersion: "1.0", Network: network, PolicyInfo: cloudlets.PolicyInfo{ + PolicyID: policyID, Version: version, Status: cloudlets.PolicyActivationStatusInactive, + }, PropertyInfo: cloudlets.PropertyInfo{Name: p}}) + } + expectGetPolicyVersion(m, policyID, version, activations, nil).Times(1) + expectActivatePolicyVersion(m, policyID, version, network, properties, cloudlets.PolicyActivationStatusActive, "", 1, nil).Times(1) + // already active - no need to poll + expectListPolicyActivations(m, policyID, version, network, properties, cloudlets.PolicyActivationStatusActive, "", 1, nil).Times(1) + // read + expectListPolicyActivations(m, policyID, version, network, properties, cloudlets.PolicyActivationStatusActive, "", 1, nil).Times(2) + // import + expectToDiscoverPolicyAsV2(m, policyID) + policyActivations := []cloudlets.PolicyActivation{ + { + APIVersion: "1.0", + Network: "staging", + PolicyInfo: cloudlets.PolicyInfo{ + PolicyID: 1234, + Name: "Policy1", + Version: 2, + Status: cloudlets.PolicyActivationStatusDeactivated, + }, + PropertyInfo: cloudlets.PropertyInfo{ + Name: "prp_0", + }, + }, + { + APIVersion: "1.0", + Network: "staging", + PolicyInfo: cloudlets.PolicyInfo{ + PolicyID: 1234, + Name: "Policy1", + Version: 3, + Status: cloudlets.PolicyActivationStatusDeactivated, + }, + PropertyInfo: cloudlets.PropertyInfo{ + Name: "prp_1", + }, + }, + } + m.On("ListPolicyActivations", mock.Anything, cloudlets.ListPolicyActivationsRequest{ + PolicyID: policyID, + Network: network, + }).Return(policyActivations, nil).Times(1) + // read + expectListPolicyActivations(m, policyID, version, network, properties, cloudlets.PolicyActivationStatusActive, "", 1, nil).Times(1) + // delete + expectDeletePhase(m, 1234, []string{"prp_0", "prp_1"}, nil, cloudlets.PolicyActivationNetworkStaging, nil, nil) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyActivation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(cloudlets.PolicyActivationStatusActive)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.#", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.0.default", "2h"), + ), + }, + { + ImportState: true, + ImportStateId: "1234:staging", + ResourceName: "akamai_cloudlets_policy_activation.test", + ImportStateVerify: true, + ExpectError: regexp.MustCompile(`Error: no active activation has been found for policy_id: '1234' and network: 'staging'`), + }, + }, + }, + "import - empty activations - expect an error": { + init: func(m *cloudlets.Mock) { + // create + properties, policyID, version, network := []string{"prp_0", "prp_1"}, int64(1234), int64(1), cloudlets.PolicyActivationNetworkStaging + expectToDiscoverPolicyAsV2(m, policyID) + activations := make([]cloudlets.PolicyActivation, len(properties)) + for _, p := range properties { + activations = append(activations, cloudlets.PolicyActivation{APIVersion: "1.0", Network: network, PolicyInfo: cloudlets.PolicyInfo{ + PolicyID: policyID, Version: version, Status: cloudlets.PolicyActivationStatusInactive, + }, PropertyInfo: cloudlets.PropertyInfo{Name: p}}) + } + expectGetPolicyVersion(m, policyID, version, activations, nil).Times(1) + expectActivatePolicyVersion(m, policyID, version, network, properties, cloudlets.PolicyActivationStatusActive, "", 1, nil).Times(1) + // already active - no need to poll + expectListPolicyActivations(m, policyID, version, network, properties, cloudlets.PolicyActivationStatusActive, "", 1, nil).Times(1) + // read + expectListPolicyActivations(m, policyID, version, network, properties, cloudlets.PolicyActivationStatusActive, "", 1, nil).Times(2) + // import - expect an error + expectToDiscoverPolicyAsV2(m, policyID) + expectListPolicyActivations(m, policyID, version, network, properties, cloudlets.PolicyActivationStatusActive, "", 0, nil).Times(1) + // delete + expectDeletePhase(m, 1234, []string{"prp_0", "prp_1"}, nil, cloudlets.PolicyActivationNetworkStaging, nil, nil) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyActivation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(cloudlets.PolicyActivationStatusActive)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.#", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.0.default", "2h"), + ), + }, + { + ImportState: true, + ImportStateId: "1234:staging", + ResourceName: "akamai_cloudlets_policy_activation.test", + ImportStateVerify: true, + ExpectError: regexp.MustCompile(`Error: no active activation has been found for policy_id: '1234' and network: 'staging'`), + }, + }, + }, + "import - wrong import ID - expect an error": { + init: func(m *cloudlets.Mock) { + // create + properties, policyID, version, network := []string{"prp_0", "prp_1"}, int64(1234), int64(1), cloudlets.PolicyActivationNetworkStaging + expectToDiscoverPolicyAsV2(m, policyID) + activations := make([]cloudlets.PolicyActivation, len(properties)) + for _, p := range properties { + activations = append(activations, cloudlets.PolicyActivation{APIVersion: "1.0", Network: network, PolicyInfo: cloudlets.PolicyInfo{ + PolicyID: policyID, Version: version, Status: cloudlets.PolicyActivationStatusInactive, + }, PropertyInfo: cloudlets.PropertyInfo{Name: p}}) + } + expectGetPolicyVersion(m, policyID, version, activations, nil).Times(1) + expectActivatePolicyVersion(m, policyID, version, network, properties, cloudlets.PolicyActivationStatusActive, "", 1, nil).Times(1) + // already active - no need to poll + expectListPolicyActivations(m, policyID, version, network, properties, cloudlets.PolicyActivationStatusActive, "", 1, nil).Times(1) + // read + expectListPolicyActivations(m, policyID, version, network, properties, cloudlets.PolicyActivationStatusActive, "", 1, nil).Times(2) + // delete + expectDeletePhase(m, 1234, []string{"prp_0", "prp_1"}, nil, cloudlets.PolicyActivationNetworkStaging, nil, nil) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyActivation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(cloudlets.PolicyActivationStatusActive)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.#", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.0.default", "2h"), + ), + }, + { + ImportState: true, + ImportStateId: "wrong_import_id", + ResourceName: "akamai_cloudlets_policy_activation.test", + ImportStateVerify: true, + ExpectError: regexp.MustCompile(`Error: import id should be of format: :, for example: 1234:staging`), + }, + }, + }, + } + + // redefining times to accelerate tests + ActivationPollMinimum, ActivationPollInterval, PolicyActivationRetryPollMinimum = time.Millisecond, time.Millisecond, time.Millisecond + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + client := &cloudlets.Mock{} + test.init(client) + useClient(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + IsUnitTest: true, + Steps: test.steps, + }) + }) + client.AssertExpectations(t) + }) + } +} + +func TestResourceV3CloudletsPolicyActivation(t *testing.T) { + tests := map[string]struct { + init func(*cloudlets.Mock, *v3.Mock) + steps []resource.TestStep + }{ + "try to create activation with a non existing policy": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + policyID := int64(1234) + m2.On("GetPolicy", mock.Anything, cloudlets.GetPolicyRequest{PolicyID: policyID}).Return(nil, &cloudlets.Error{StatusCode: http.StatusNotFound}).Once() + m3.On("GetPolicy", mock.Anything, v3.GetPolicyRequest{PolicyID: policyID}).Return(nil, &v3.Error{Status: http.StatusNotFound}).Once() + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + ExpectError: regexp.MustCompile(`could not get policy 1234`), + }, + }, + }, + + "create and read activation, version == 1, inactive -> activate": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + policyID, version, network := int64(1234), int64(1), v3.StagingNetwork + expectFullV3Activation(m2, m3, policyID, version, network) + // delete + expectV3DeletePhase(m3, policyID, version, network) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.#", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.0.default", "2h"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + }, + }, + + "(V3 policy but V2 throws 500s) create and read activation, version == 1, inactive -> activate": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + policyID, version, network := int64(1234), int64(1), v3.StagingNetwork + // create + //discover + m2.On("GetPolicy", mock.Anything, cloudlets.GetPolicyRequest{PolicyID: policyID}).Return(nil, &cloudlets.Error{StatusCode: http.StatusInternalServerError}).Once() + m3.On("GetPolicy", mock.Anything, v3.GetPolicyRequest{PolicyID: policyID}).Return(&v3.Policy{ID: policyID}, nil).Once() + //rest of create + expectGetV3Policy(m3, policyID, v3.CurrentActivations{Production: v3.ActivationInfo{}, Staging: v3.ActivationInfo{}}, nil).Once() + expectActivateV3PolicyVersion(m3, policyID, version, 111, network, nil).Once() + // poll until active -> waitForPolicyActivation() + expectWaitForV3Activation(m3, policyID, 111, []v3.ActivationStatus{v3.ActivationStatusSuccess}, nil) + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Once() + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Once() + // delete + expectV3DeletePhase(m3, policyID, version, network) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.#", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.0.default", "2h"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + }, + }, + + "create and read activation, version == 1, pending activations on activate -> retry -> activated": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + policyID, version, activationID, network := int64(1234), int64(1), int64(111), v3.StagingNetwork + // create + expectToDiscoverPolicyAsV3(m2, m3, policyID) + expectGetV3Policy(m3, policyID, v3.CurrentActivations{Production: v3.ActivationInfo{}, Staging: v3.ActivationInfo{}}, nil).Once() + expectActivateV3PolicyVersion(m3, policyID, version, activationID, network, nil).Once() + // poll until active -> waitForPolicyActivation() + expectWaitForV3Activation(m3, policyID, activationID, []v3.ActivationStatus{v3.ActivationStatusInProgress, v3.ActivationStatusSuccess}, nil) + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Once() + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Once() + // delete + expectV3DeletePhase(m3, policyID, version, network) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + }, + }, + + "create and read activation, version == 1, active activations on activate -> retry twice -> activated": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + policyID, version, activationID, network := int64(1234), int64(1), int64(111), v3.StagingNetwork + // create + expectToDiscoverPolicyAsV3(m2, m3, policyID) + expectGetV3Policy(m3, policyID, v3.CurrentActivations{Production: v3.ActivationInfo{}, Staging: v3.ActivationInfo{}}, nil).Once() + expectActivateV3PolicyVersion(m3, policyID, version, activationID, network, nil).Once() + // poll until active -> waitForPolicyActivation() + expectWaitForV3Activation(m3, policyID, activationID, []v3.ActivationStatus{v3.ActivationStatusInProgress, v3.ActivationStatusInProgress, v3.ActivationStatusSuccess}, nil) + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Once() + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Once() + // delete + expectV3DeletePhase(m3, policyID, version, network) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + }, + }, + + "create and read activation, version == 1, inactive -> activate -> wait -> failed": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + policyID, version, activationID, network := int64(1234), int64(1), int64(111), v3.StagingNetwork + // create + expectToDiscoverPolicyAsV3(m2, m3, policyID) + expectGetV3Policy(m3, policyID, v3.CurrentActivations{Production: v3.ActivationInfo{}, Staging: v3.ActivationInfo{}}, nil).Once() + expectActivateV3PolicyVersion(m3, policyID, version, activationID, network, nil).Once() + // poll until active -> waitForPolicyActivation() + expectWaitForV3Activation(m3, policyID, activationID, []v3.ActivationStatus{v3.ActivationStatusFailed}, nil) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/shared_policy_activation_version1.tf"), + ExpectError: regexp.MustCompile("policy activation create: activation failed for policy 1234"), + }, + }, + }, + "cannot modify 'associated_properties' for shared policy": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + policyID := int64(1234) + expectToDiscoverPolicyAsV3(m2, m3, policyID) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version_invalid.tf"), + ExpectError: regexp.MustCompile("cannot provide 'associated_properties' for shared policy"), + }, + }, + }, + + "create and read activation, version == 1, production, inactive -> activate": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + policyID, version, network := int64(1234), int64(1), v3.ProductionNetwork + expectFullV3Activation(m2, m3, policyID, version, network) + // delete + expectV3DeletePhase(m3, policyID, version, network) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1_production.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "prod"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.#", "0"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + }, + }, + + "create and read activation, version == 1, prod, inactive -> activate": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + policyID, version, network := int64(1234), int64(1), v3.ProductionNetwork + expectFullV3Activation(m2, m3, policyID, version, network) + // delete + expectV3DeletePhase(m3, policyID, version, network) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1_prod.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "prod"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.#", "0"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + }, + }, + + "create and read activation, version == 1, inactive -> activate -> error": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + // create + policyID, version, activationID, network := int64(1234), int64(1), int64(111), v3.StagingNetwork + expectToDiscoverPolicyAsV3(m2, m3, policyID) + expectGetV3Policy(m3, policyID, v3.CurrentActivations{Production: v3.ActivationInfo{}, Staging: v3.ActivationInfo{}}, nil).Once() + expectActivateV3PolicyVersion(m3, policyID, version, activationID, network, fmt.Errorf("an error")).Once() + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + ExpectError: regexp.MustCompile("policy activation create: an error"), + }, + }, + }, + + "create and read activation, version == 1, inactive -> activate -> 500 error (retry) -> activated": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + // create + policyID, version, activationID, network := int64(1234), int64(1), int64(111), v3.StagingNetwork + + expectToDiscoverPolicyAsV3(m2, m3, policyID) + expectGetV3Policy(m3, policyID, v3.CurrentActivations{Production: v3.ActivationInfo{}, Staging: v3.ActivationInfo{}}, nil).Once() + expectActivateV3PolicyVersion(m3, policyID, version, activationID, network, &v3.Error{Status: http.StatusInternalServerError, Title: "something broke"}).Once() + expectActivateV3PolicyVersion(m3, policyID, version, activationID, network, nil).Once() + // poll until active -> waitForPolicyActivation() + expectWaitForV3Activation(m3, policyID, activationID, []v3.ActivationStatus{v3.ActivationStatusSuccess}, nil) + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Once() + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Once() + // delete + expectV3DeletePhase(m3, policyID, version, network) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + }, + }, + + "create and read activation, version == 1, inactive -> activate -> get active policy activation -> error": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + // create + policyID, version, activationID, network := int64(1234), int64(1), int64(111), v3.StagingNetwork + expectToDiscoverPolicyAsV3(m2, m3, policyID) + expectGetV3Policy(m3, policyID, v3.CurrentActivations{Production: v3.ActivationInfo{}, Staging: v3.ActivationInfo{}}, nil).Once() + expectActivateV3PolicyVersion(m3, policyID, version, activationID, network, nil).Once() + // poll until active -> waitForPolicyActivation() + expectWaitForV3Activation(m3, policyID, activationID, []v3.ActivationStatus{}, fmt.Errorf("an error")) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + ExpectError: regexp.MustCompile("policy activation create: an error"), + }, + }, + }, + + "create and read activation, version == 1, active -> read (version is already active)": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + policyID, version, network := int64(1234), int64(1), v3.StagingNetwork + + // create, policy active so no need to activate + expectToDiscoverPolicyAsV3(m2, m3, policyID) + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Once() + + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Times(2) + // delete + expectV3DeletePhase(m3, policyID, version, network) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + }, + }, + + "create and read activation, version == 1, inactive -> activate -> read error": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + policyID, version, activationID, network := int64(1234), int64(1), int64(111), v3.StagingNetwork + + // create + expectToDiscoverPolicyAsV3(m2, m3, policyID) + expectGetV3Policy(m3, policyID, v3.CurrentActivations{Production: v3.ActivationInfo{}, Staging: v3.ActivationInfo{}}, nil).Once() + expectActivateV3PolicyVersion(m3, policyID, version, activationID, network, nil).Once() + // poll until active -> waitForPolicyActivation() + expectWaitForV3Activation(m3, policyID, activationID, []v3.ActivationStatus{v3.ActivationStatusSuccess}, nil) + // read + expectGetV3Policy(m3, policyID, v3.CurrentActivations{}, fmt.Errorf("an error")).Once() + // delete + expectV3DeletePhase(m3, policyID, version, network) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + ExpectError: regexp.MustCompile("policy activation read: an error"), + }, + }, + }, + + "create and read activation, update - no changes, so skip update": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + policyID, version, activationID, network := int64(1234), int64(1), int64(111), v3.StagingNetwork + // 1 - for policy_activation_version1.tf + // create + expectToDiscoverPolicyAsV3(m2, m3, policyID) + expectGetV3Policy(m3, policyID, v3.CurrentActivations{Production: v3.ActivationInfo{}, Staging: v3.ActivationInfo{}}, nil).Once() + expectActivateV3PolicyVersion(m3, policyID, version, activationID, network, nil).Once() + // poll until active -> waitForPolicyActivation() + expectWaitForV3Activation(m3, policyID, activationID, []v3.ActivationStatus{v3.ActivationStatusSuccess}, nil) + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Twice() + // 2 - for policy_activation_version1.tf + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Twice() + // delete + expectV3DeletePhase(m3, policyID, version, network) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + }, + }, + + "Create and read activation. Update: version not active, activate": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + network, policyID, version1, version2, reactivationID := v3.StagingNetwork, int64(1234), int64(1), int64(2), int64(222) + // 1 - for policy_activation_version1.tf + expectFullV3Activation(m2, m3, policyID, version1, network) + // 2 - for policy_activation_update_version2.tf + // refresh read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version1, network), nil).Once() + // update + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version1, network), nil).Once() + expectActivateV3PolicyVersion(m3, policyID, version2, reactivationID, network, nil).Once() + // poll until active -> waitForPolicyActivation() + expectWaitForV3Activation(m3, policyID, reactivationID, []v3.ActivationStatus{v3.ActivationStatusSuccess}, nil) + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version2, network), nil).Once() + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version2, network), nil).Once() + // delete + expectV3DeletePhase(m3, policyID, version2, network) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_update_version2.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "2"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + }, + }, + + "Create and read activation. Update: change version from staging to prod, activate": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + network1, network2, policyID, version, reactivationID := v3.StagingNetwork, v3.ProductionNetwork, int64(1234), int64(1), int64(222) + // 1 - for policy_activation_version1.tf + expectFullV3Activation(m2, m3, policyID, version, network1) + // 2 - for policy_activation_update_version2.tf + // refresh read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network1), nil).Once() + // update + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network1), nil).Once() + expectActivateV3PolicyVersion(m3, policyID, version, reactivationID, network2, nil).Once() + // poll until active -> waitForPolicyActivation() + expectWaitForV3Activation(m3, policyID, reactivationID, []v3.ActivationStatus{v3.ActivationStatusSuccess}, nil) + // read + bothNetworks := v3.CurrentActivations{ + Production: v3.ActivationInfo{Effective: preparePolicyActivation(policyID, version, network2)}, + Staging: v3.ActivationInfo{Effective: preparePolicyActivation(policyID, version, network1)}, + } + expectGetV3Policy(m3, policyID, bothNetworks, nil).Once() + // read + expectGetV3Policy(m3, policyID, bothNetworks, nil).Once() + // delete + expectV3DeletePhase(m3, policyID, version, network2) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1_prod.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "prod"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + }, + }, + + "Create and read activation. Update: isReactivationNeeded error": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + network, policyID, version := v3.StagingNetwork, int64(1234), int64(1) + // 1 - for policy_activation_version1.tf + expectFullV3Activation(m2, m3, policyID, version, network) + // 2 - for policy_activation_update_version2.tf + // refresh read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Once() + // update + expectGetV3Policy(m3, policyID, v3.CurrentActivations{}, fmt.Errorf("an error")).Once() + // delete + expectV3DeletePhase(m3, policyID, version, network) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_update_version2.tf"), + ExpectError: regexp.MustCompile("policy activation update: an error"), + }, + }, + }, + + "Create and read activation. Update: activate policy version error": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + network, policyID, version1, version2, reactivationID := v3.StagingNetwork, int64(1234), int64(1), int64(2), int64(222) + // 1 - for policy_activation_version1.tf + expectFullV3Activation(m2, m3, policyID, version1, network) + // 2 - for policy_activation_update_version2.tf + // refresh read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version1, network), nil).Once() + // update + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version1, network), nil).Once() + expectActivateV3PolicyVersion(m3, policyID, version2, reactivationID, network, fmt.Errorf("an error")).Once() + // delete + expectV3DeletePhase(m3, policyID, version1, network) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_update_version2.tf"), + ExpectError: regexp.MustCompile("policy activation update: an error"), + }, + }, + }, + + "Create and read activation. Update: error while polling": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + network, policyID, version1, version2, reactivationID := v3.StagingNetwork, int64(1234), int64(1), int64(2), int64(222) + // 1 - for policy_activation_version1.tf + expectFullV3Activation(m2, m3, policyID, version1, network) + // 2 - for policy_activation_update_version2.tf + // refresh read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version1, network), nil).Once() + // update + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version1, network), nil).Once() + expectActivateV3PolicyVersion(m3, policyID, version2, reactivationID, network, nil).Once() + // poll until active -> waitForPolicyActivation() + expectWaitForV3Activation(m3, policyID, reactivationID, []v3.ActivationStatus{}, fmt.Errorf("an error")) + // delete + expectV3DeletePhase(m3, policyID, version2, network) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_update_version2.tf"), + ExpectError: regexp.MustCompile("policy activation update: an error"), + }, + }, + }, + + "import - success": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + policyID, version, network := int64(1234), int64(1), v3.StagingNetwork + + // create, policy active so no need to activate + expectToDiscoverPolicyAsV3(m2, m3, policyID) + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Once() + + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Times(2) + // import + expectToDiscoverPolicyAsV3(m2, m3, policyID) + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Once() + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Once() + // delete + expectV3DeletePhase(m3, policyID, version, network) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.#", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.0.default", "2h"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + { + ImportState: true, + ImportStateId: "1234:staging", + ResourceName: "akamai_cloudlets_policy_activation.test", + ImportStateVerify: true, + }, + }, + }, + + "import - only deactivated activation - expect an error": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + policyID, version, network := int64(1234), int64(1), v3.StagingNetwork + + // create, policy active so no need to activate + expectToDiscoverPolicyAsV3(m2, m3, policyID) + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Once() + + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Times(2) + // import + expectToDiscoverPolicyAsV3(m2, m3, policyID) + expectGetV3Policy(m3, policyID, v3.CurrentActivations{ + Production: v3.ActivationInfo{}, + Staging: v3.ActivationInfo{ + Effective: &v3.PolicyActivation{ + PolicyID: policyID, + PolicyVersion: version, + Network: network, + Operation: v3.OperationDeactivation, + Status: v3.ActivationStatusSuccess, + }, + }, + }, nil).Once() + // delete + expectV3DeletePhase(m3, policyID, version, network) + + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.#", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.0.default", "2h"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + { + ImportState: true, + ImportStateId: "1234:staging", + ResourceName: "akamai_cloudlets_policy_activation.test", + ImportStateVerify: true, + ExpectError: regexp.MustCompile(`Error: no active activation has been found for policy_id: '1234' and network: 'staging'`), + }, + }, + }, + + "import - empty activations - expect an error": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + policyID, version, network := int64(1234), int64(1), v3.StagingNetwork + + // create, policy active so no need to activate + expectToDiscoverPolicyAsV3(m2, m3, policyID) + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Once() + + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Times(2) + // import + expectToDiscoverPolicyAsV3(m2, m3, policyID) + expectGetV3Policy(m3, policyID, v3.CurrentActivations{ + Production: v3.ActivationInfo{}, + Staging: v3.ActivationInfo{}, + }, nil).Once() + // delete + expectV3DeletePhase(m3, policyID, version, network) + + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.#", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.0.default", "2h"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + { + ImportState: true, + ImportStateId: "1234:staging", + ResourceName: "akamai_cloudlets_policy_activation.test", + ImportStateVerify: true, + ExpectError: regexp.MustCompile(`Error: no active activation has been found for policy_id: '1234' and network: 'staging'`), + }, + }, + }, + + "import - wrong import ID - expect an error": { + init: func(m2 *cloudlets.Mock, m3 *v3.Mock) { + policyID, version, network := int64(1234), int64(1), v3.StagingNetwork + + // create, policy active so no need to activate + expectToDiscoverPolicyAsV3(m2, m3, policyID) + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Once() + + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Times(2) + // delete + expectV3DeletePhase(m3, policyID, version, network) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "./testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckOutput("status", string(v3.ActivationStatusSuccess)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "version", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "network", "staging"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.#", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "timeouts.0.default", "2h"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy_activation.test", "is_shared", "true"), + ), + }, + { + ImportState: true, + ImportStateId: "wrong_import_id", + ResourceName: "akamai_cloudlets_policy_activation.test", + ImportStateVerify: true, + ExpectError: regexp.MustCompile(`Error: import id should be of format: :, for example: 1234:staging`), + }, + }, + }, } // redefining times to accelerate tests @@ -803,16 +1750,18 @@ func TestResourceCloudletsPolicyActivation(t *testing.T) { for name, test := range tests { t.Run(name, func(t *testing.T) { - client := &cloudlets.Mock{} - test.init(client) - useClient(client, func() { + v2Client := &cloudlets.Mock{} + v3client := &v3.Mock{} + test.init(v2Client, v3client) + useClientV2AndV3(v2Client, v3client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, - IsUnitTest: true, - Steps: test.steps, + ProtoV5ProviderFactories: testAccProviders, + IsUnitTest: true, + Steps: test.steps, }) }) - client.AssertExpectations(t) + v2Client.AssertExpectations(t) + v3client.AssertExpectations(t) }) } } @@ -820,6 +1769,7 @@ func TestResourceCloudletsPolicyActivation(t *testing.T) { // expect full activation of a policy (creation) func expectFullActivation(m *cloudlets.Mock, policyID, version int64, properties []string, network cloudlets.PolicyActivationNetwork, times int) { // create + expectToDiscoverPolicyAsV2(m, policyID) activations := make([]cloudlets.PolicyActivation, len(properties)) for _, p := range properties { activations = append(activations, cloudlets.PolicyActivation{APIVersion: "1.0", Network: network, PolicyInfo: cloudlets.PolicyInfo{ @@ -836,6 +1786,15 @@ func expectFullActivation(m *cloudlets.Mock, policyID, version int64, properties expectListPolicyActivations(m, policyID, version, network, properties, cloudlets.PolicyActivationStatusActive, "", 1, nil).Times(times) } +func expectToDiscoverPolicyAsV2(m *cloudlets.Mock, policyID int64) { + m.On("GetPolicy", mock.Anything, cloudlets.GetPolicyRequest{PolicyID: policyID}).Return(&cloudlets.Policy{PolicyID: policyID}, nil).Once() +} + +func expectToDiscoverPolicyAsV3(m2 *cloudlets.Mock, m3 *v3.Mock, policyID int64) { + m2.On("GetPolicy", mock.Anything, cloudlets.GetPolicyRequest{PolicyID: policyID}).Return(nil, &cloudlets.Error{StatusCode: http.StatusNotFound}).Once() + m3.On("GetPolicy", mock.Anything, v3.GetPolicyRequest{PolicyID: policyID}).Return(&v3.Policy{ID: policyID}, nil).Once() +} + // expect delete step func expectDeletePhase(m *cloudlets.Mock, policyID int64, deletedProperties, remainingProperties []string, network cloudlets.PolicyActivationNetwork, errGetPolicy, errDeleteProperty error) { expectGetPolicyProperties(m, policyID, deletedProperties, errGetPolicy).Once() @@ -943,3 +1902,125 @@ func createPolicyActivations(policyID, version int64, network cloudlets.PolicyAc return policyActivations } + +func expectFullV3Activation(m2 *cloudlets.Mock, m3 *v3.Mock, policyID, version int64, network v3.Network) { + // create + expectToDiscoverPolicyAsV3(m2, m3, policyID) + expectGetV3Policy(m3, policyID, v3.CurrentActivations{Production: v3.ActivationInfo{}, Staging: v3.ActivationInfo{}}, nil).Once() + expectActivateV3PolicyVersion(m3, policyID, version, 111, network, nil).Once() + // poll until active -> waitForPolicyActivation() + expectWaitForV3Activation(m3, policyID, 111, []v3.ActivationStatus{v3.ActivationStatusSuccess}, nil) + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Once() + // read + expectGetV3Policy(m3, policyID, prepareActivatedResponseForNetwork(policyID, version, network), nil).Once() +} + +func prepareActivatedResponseForNetwork(policyID, version int64, network v3.Network) v3.CurrentActivations { + activations := v3.CurrentActivations{Production: v3.ActivationInfo{}, Staging: v3.ActivationInfo{}} + if network == v3.StagingNetwork { + activations.Staging.Effective = preparePolicyActivation(policyID, version, network) + } else { + activations.Production.Effective = preparePolicyActivation(policyID, version, network) + } + return activations +} + +func preparePolicyActivation(policyID, version int64, network v3.Network) *v3.PolicyActivation { + return &v3.PolicyActivation{ + PolicyID: policyID, + PolicyVersion: version, + Network: network, + Status: v3.ActivationStatusSuccess, + Operation: v3.OperationActivation, + } +} + +func expectGetV3Policy(m *v3.Mock, policyID int64, activations v3.CurrentActivations, err error) *mock.Call { + if err != nil { + return m.On( + "GetPolicy", + mock.Anything, + v3.GetPolicyRequest{PolicyID: policyID}, + ).Return(nil, err) + } + return m.On( + "GetPolicy", + mock.Anything, + v3.GetPolicyRequest{PolicyID: policyID}, + ).Return( + &v3.Policy{ + ID: policyID, + CurrentActivations: activations, + }, nil) +} + +func expectActivateV3PolicyVersion(m *v3.Mock, policyID, version, activationID int64, network v3.Network, err error) *mock.Call { + if err != nil { + return m.On("ActivatePolicy", mock.Anything, v3.ActivatePolicyRequest{ + PolicyID: policyID, + Network: network, + PolicyVersion: version, + }).Return(nil, err) + } + + return m.On("ActivatePolicy", mock.Anything, v3.ActivatePolicyRequest{ + PolicyID: policyID, + Network: network, + PolicyVersion: version, + }).Return(&v3.PolicyActivation{ + ID: activationID, + Status: v3.ActivationStatusInProgress, + Network: network, + Operation: v3.OperationActivation, + }, err) +} + +func expectWaitForV3Activation(m *v3.Mock, policyID, activationID int64, activations []v3.ActivationStatus, error error) { + if error != nil { + m.On("GetPolicyActivation", + mock.Anything, + v3.GetPolicyActivationRequest{ + PolicyID: policyID, + ActivationID: activationID, + }).Return(nil, error).Once() + return + } + for idx := range activations { + m.On("GetPolicyActivation", + mock.Anything, + v3.GetPolicyActivationRequest{ + PolicyID: policyID, + ActivationID: activationID, + }).Return(&v3.PolicyActivation{ + PolicyID: policyID, + Status: activations[idx], + }, nil).Once() + } +} + +func expectDeactivateV3PolicyVersion(m *v3.Mock, policyID, version, activationID int64, network v3.Network, err error) *mock.Call { + if err != nil { + return m.On("DeactivatePolicy", mock.Anything, v3.DeactivatePolicyRequest{ + PolicyID: policyID, + Network: network, + PolicyVersion: version, + }).Return(nil, err) + } + + return m.On("DeactivatePolicy", mock.Anything, v3.DeactivatePolicyRequest{ + PolicyID: policyID, + Network: network, + PolicyVersion: version, + }).Return(&v3.PolicyActivation{ + ID: activationID, + Status: v3.ActivationStatusInProgress, + Network: network, + Operation: v3.OperationActivation, + }, err) +} + +func expectV3DeletePhase(m *v3.Mock, policyID, version int64, network v3.Network) { + expectDeactivateV3PolicyVersion(m, policyID, version, 333, network, nil).Once() + expectWaitForV3Activation(m, policyID, 333, []v3.ActivationStatus{v3.ActivationStatusSuccess}, nil) +} diff --git a/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_activation_v2.go b/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_activation_v2.go new file mode 100644 index 000000000..852d7b873 --- /dev/null +++ b/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_activation_v2.go @@ -0,0 +1,255 @@ +package cloudlets + +import ( + "context" + "errors" + "fmt" + "reflect" + "sort" + "strings" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets" + "github.com/akamai/terraform-provider-akamai/v5/pkg/common/tf" + "github.com/apex/log" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type v2ActivationStrategy struct { + client cloudlets.Cloudlets + logger log.Interface + network cloudlets.PolicyActivationNetwork + associatedProperties []string + activeProps []string + removedProps []string +} + +func (strategy *v2ActivationStrategy) setupCloudletSpecificData(rd *schema.ResourceData, network string) error { + versionActivationNetwork, err := getPolicyActivationNetwork(network) + if err != nil { + return err + } + associatedProps, err := tf.GetSetValue("associated_properties", rd) + if err != nil { + if errors.Is(err, tf.ErrNotFound) { + return fmt.Errorf("'associated_properties' is required for non-shared policies") + } + return err + } + var associatedProperties []string + for _, prop := range associatedProps.List() { + associatedProperties = append(associatedProperties, prop.(string)) + } + sort.Strings(associatedProperties) + + strategy.network = versionActivationNetwork + strategy.associatedProperties = associatedProperties + return nil +} + +func (strategy *v2ActivationStrategy) isVersionAlreadyActive(ctx context.Context, policyID, version int64) (bool, string, error) { + + strategy.logger.Debugf("checking if policy version %d is active", version) + policyVersion, err := strategy.client.GetPolicyVersion(ctx, cloudlets.GetPolicyVersionRequest{ + Version: version, + PolicyID: policyID, + OmitRules: true, + }) + if err != nil { + return false, "", fmt.Errorf("%s: cannot find the given policy version (%d): %s", ErrPolicyActivation.Error(), version, err.Error()) + } + policyActivations := sortPolicyActivationsByDate(policyVersion.Activations) + + // just the first activations must correspond to the given properties + var activeProperties []string + for _, act := range policyActivations { + if act.Network == strategy.network && + act.PolicyInfo.Status == cloudlets.PolicyActivationStatusActive { + activeProperties = append(activeProperties, act.PropertyInfo.Name) + } + } + sort.Strings(activeProperties) + + isActive := reflect.DeepEqual(activeProperties, strategy.associatedProperties) + if isActive { + strategy.logger.Debugf("policy %d, with version %d and properties [%s], is already active in %s. Fetching all details from server", policyID, version, strings.Join(strategy.associatedProperties, ", "), string(strategy.network)) + } + return isActive, formatPolicyActivationID(policyID, strategy.network), nil +} + +func (strategy *v2ActivationStrategy) activateVersion(ctx context.Context, policyID, version int64) error { + strategy.logger.Debugf("activating policy %d version %d, network %s and properties [%s]", policyID, version, string(strategy.network), strings.Join(strategy.associatedProperties, ", ")) + _, err := strategy.client.ActivatePolicyVersion(ctx, cloudlets.ActivatePolicyVersionRequest{ + PolicyID: policyID, + Version: version, + Async: true, + PolicyVersionActivation: cloudlets.PolicyVersionActivation{ + Network: strategy.network, + AdditionalPropertyNames: strategy.associatedProperties, + }, + }) + return err +} + +func (strategy *v2ActivationStrategy) reactivateVersion(ctx context.Context, policyID, version int64) error { + // Activate policy version. This will include new associated_properties + the ones which need to be removed + // it will fail if any of the associated_properties are not valid + if err := strategy.activateVersion(ctx, policyID, version); err != nil { + return err + } + + // 6. remove from the server all unnecessary policy associated_properties + removedProperties, err := syncToServerRemovedProperties(ctx, strategy.logger, strategy.client, policyID, strategy.network, strategy.activeProps, strategy.associatedProperties) + strategy.removedProps = removedProperties + return err +} + +func (strategy *v2ActivationStrategy) waitForActivation(ctx context.Context, policyID, version int64) (string, error) { + act, err := waitForPolicyActivation(ctx, strategy.client, policyID, version, strategy.network, strategy.associatedProperties, strategy.removedProps) + if err != nil { + return "", err + } + return formatPolicyActivationID(act[0].PolicyInfo.PolicyID, act[0].Network), nil +} + +func (strategy *v2ActivationStrategy) readActivationFromServer(ctx context.Context, policyID int64, network string) (map[string]any, error) { + net, err := getPolicyActivationNetwork(network) + if err != nil { + return nil, err + } + + activations, err := waitForListPolicyActivations(ctx, strategy.client, cloudlets.ListPolicyActivationsRequest{ + PolicyID: policyID, + Network: net, + }) + if err != nil { + return nil, fmt.Errorf("%v read: %s", ErrPolicyActivation, err.Error()) + } + + if len(activations) == 0 { + return nil, fmt.Errorf("%v read: cannot find any activation for the given policy (%d) and network ('%s')", ErrPolicyActivation, policyID, net) + } + + activations = sortPolicyActivationsByDate(activations) + associatedProperties := getActiveProperties(activations) + + attrs := map[string]any{ + "status": activations[0].PolicyInfo.Status, + "version": activations[0].PolicyInfo.Version, + "associated_properties": associatedProperties, + } + + return attrs, nil +} + +func (strategy *v2ActivationStrategy) isReactivationNotNeeded(ctx context.Context, policyID, version int64, hasVersionChange bool) (bool, string, error) { + // policy version validation + _, err := strategy.client.GetPolicyVersion(ctx, cloudlets.GetPolicyVersionRequest{ + PolicyID: policyID, + Version: version, + OmitRules: true, + }) + if err != nil { + return false, "", fmt.Errorf("%s: cannot find the given policy version (%d): %s", ErrPolicyActivation.Error(), version, err.Error()) + } + + // look for activations with this version which is active in the given network + activations, err := waitForListPolicyActivations(ctx, strategy.client, cloudlets.ListPolicyActivationsRequest{ + PolicyID: policyID, + Network: strategy.network, + }) + if err != nil { + return false, "", fmt.Errorf("%v update: %s", ErrPolicyActivation, err.Error()) + } + // activations, at this point, contains old and new activations + + // sort by activation date, reverse. To find out the state of the latest activations + activations = sortPolicyActivationsByDate(activations) + + // find out which properties are activated in those activations + // version does not matter at this point + activeProps := getActiveProperties(activations) + strategy.activeProps = activeProps + + isAlreadyActive := reflect.DeepEqual(activeProps, strategy.associatedProperties) && !hasVersionChange && activations[0].PolicyInfo.Version == version + return isAlreadyActive, formatPolicyActivationID(policyID, strategy.network), nil +} + +func (strategy *v2ActivationStrategy) deactivatePolicy(ctx context.Context, policyID, _ int64, net string) error { + network, err := getPolicyActivationNetwork(net) + if err != nil { + return err + } + + policyProperties, err := strategy.client.GetPolicyProperties(ctx, cloudlets.GetPolicyPropertiesRequest{PolicyID: policyID}) + if err != nil { + return fmt.Errorf("%s: cannot find policy %d properties: %s", ErrPolicyActivation.Error(), policyID, err.Error()) + } + activations, err := waitForListPolicyActivations(ctx, strategy.client, cloudlets.ListPolicyActivationsRequest{ + PolicyID: policyID, + Network: network, + }) + if err != nil { + return err + } + + strategy.logger.Debugf("Removing all policy (ID=%d) properties", policyID) + for propertyName, policyProperty := range policyProperties { + // filter out property by network + validProperty := false + for _, act := range activations { + if act.PropertyInfo.Name == propertyName { + validProperty = true + break + } + } + if !validProperty { + continue + } + // wait for removal until there aren't any pending activations + if err = waitForNotPendingPolicyActivation(ctx, strategy.logger, strategy.client, policyID, network); err != nil { + return err + } + + // proceed to delete property from policy + err = strategy.client.DeletePolicyProperty(ctx, cloudlets.DeletePolicyPropertyRequest{ + PolicyID: policyID, + PropertyID: policyProperty.ID, + Network: network, + }) + if err != nil { + return fmt.Errorf("%s: cannot delete property '%s' from policy ID %d and network '%s'. Please, try once again later.\n%s", ErrPolicyActivation.Error(), propertyName, policyID, network, err.Error()) + } + } + return nil +} + +func (strategy *v2ActivationStrategy) shouldRetryActivation(err error) bool { + return err != nil && policyActivationRetryRegexp.MatchString(strings.ToLower(err.Error())) +} + +func (strategy *v2ActivationStrategy) fetchValuesForImport(ctx context.Context, policyID int64, network string) (map[string]any, string, error) { + activations, err := strategy.client.ListPolicyActivations(ctx, cloudlets.ListPolicyActivationsRequest{ + PolicyID: policyID, + Network: cloudlets.PolicyActivationNetwork(network), + }) + if err != nil { + return nil, "", err + } + + var activation *cloudlets.PolicyActivation + for _, act := range activations { + if string(act.Network) == network && act.PolicyInfo.Status == cloudlets.PolicyActivationStatusActive { + activation = &act + break + } + } + if activation == nil || len(activations) == 0 { + return nil, "", fmt.Errorf("no active activation has been found for policy_id: '%d' and network: '%s'", policyID, network) + } + + return map[string]any{ + "network": activation.Network, + "policy_id": activation.PolicyInfo.PolicyID, + "is_shared": false, + }, fmt.Sprintf("%d:%s", policyID, activation.Network), nil +} diff --git a/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_activation_v3.go b/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_activation_v3.go new file mode 100644 index 000000000..7fbe4212a --- /dev/null +++ b/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_activation_v3.go @@ -0,0 +1,226 @@ +package cloudlets + +import ( + "context" + "errors" + "fmt" + "strings" + "time" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets" + v3 "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets/v3" + "github.com/akamai/terraform-provider-akamai/v5/pkg/common/tf" + "github.com/apex/log" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type v3ActivationStrategy struct { + client v3.Cloudlets + logger log.Interface + network v3.Network + activationID int64 +} + +func (strategy *v3ActivationStrategy) setupCloudletSpecificData(rd *schema.ResourceData, network string) error { + if rd.HasChange("associated_properties") { + return fmt.Errorf("cannot provide 'associated_properties' for shared policy") + } + net, err := strategy.parseNetwork(network) + if err != nil { + return err + } + strategy.network = net + return nil +} + +func (strategy *v3ActivationStrategy) parseNetwork(network string) (v3.Network, error) { + switch tf.StateNetwork(strings.ToLower(network)) { + case "staging": + return v3.StagingNetwork, nil + case "production": + return v3.ProductionNetwork, nil + } + + return v3.StagingNetwork, fmt.Errorf("'%s' is an invalid network value: should be 'production', 'prod', 'p', 'staging', 'stag' or 's'", network) +} +func (strategy *v3ActivationStrategy) isVersionAlreadyActive(ctx context.Context, policyID, version int64) (bool, string, error) { + policy, err := strategy.client.GetPolicy(ctx, v3.GetPolicyRequest{ + PolicyID: policyID, + }) + if err != nil { + return false, "", err + } + if strategy.network == v3.StagingNetwork { + return policy.CurrentActivations.Staging.Effective != nil && + policy.CurrentActivations.Staging.Effective.PolicyVersion == version && + policy.CurrentActivations.Staging.Effective.Status == v3.ActivationStatusSuccess && + policy.CurrentActivations.Staging.Effective.Operation == v3.OperationActivation, strategy.getID(policyID, strategy.network), nil + } + return policy.CurrentActivations.Production.Effective != nil && + policy.CurrentActivations.Production.Effective.PolicyVersion == version && + policy.CurrentActivations.Production.Effective.Status == v3.ActivationStatusSuccess && + policy.CurrentActivations.Production.Effective.Operation == v3.OperationActivation, strategy.getID(policyID, strategy.network), nil +} + +func (strategy *v3ActivationStrategy) activateVersion(ctx context.Context, policyID, version int64) error { + activation, err := strategy.client.ActivatePolicy(ctx, v3.ActivatePolicyRequest{ + PolicyID: policyID, + PolicyVersion: version, + Network: strategy.network, + }) + if activation != nil { + strategy.activationID = activation.ID + } + return err +} + +func (strategy *v3ActivationStrategy) reactivateVersion(ctx context.Context, policyID, version int64) error { + return strategy.activateVersion(ctx, policyID, version) +} + +func (strategy *v3ActivationStrategy) waitForActivation(ctx context.Context, policyID, _ int64) (string, error) { + for { + select { + case <-time.After(tf.MaxDuration(ActivationPollInterval, ActivationPollMinimum)): + activation, err := strategy.client.GetPolicyActivation(ctx, v3.GetPolicyActivationRequest{ + PolicyID: policyID, + ActivationID: strategy.activationID, + }) + if err != nil { + return "", err + } + if activation != nil { + switch activation.Status { + case v3.ActivationStatusSuccess: + return strategy.getID(policyID, strategy.network), nil + case v3.ActivationStatusFailed: + return "", fmt.Errorf("activation failed for policy %d", policyID) + } + } + case <-ctx.Done(): + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return "", ErrPolicyActivationTimeout + } + if errors.Is(ctx.Err(), context.Canceled) { + return "", ErrPolicyActivationCanceled + } + return "", fmt.Errorf("%v: %w", ErrPolicyActivationContextTerminated, ctx.Err()) + } + } +} + +func (strategy *v3ActivationStrategy) getID(policyID int64, network v3.Network) string { + return fmt.Sprintf("%d:%s", policyID, network) +} + +func (strategy *v3ActivationStrategy) readActivationFromServer(ctx context.Context, policyID int64, network string) (map[string]any, error) { + policy, err := strategy.client.GetPolicy(ctx, v3.GetPolicyRequest{ + PolicyID: policyID, + }) + if err != nil { + return nil, err + } + + net, err := strategy.parseNetwork(network) + if err != nil { + return nil, err + } + + switch net { + case v3.StagingNetwork: + if policy.CurrentActivations.Staging.Effective != nil { + return extractAttrsForActivation(policy.CurrentActivations.Staging.Effective), nil + } + case v3.ProductionNetwork: + if policy.CurrentActivations.Production.Effective != nil { + return extractAttrsForActivation(policy.CurrentActivations.Production.Effective), nil + } + } + + return nil, nil +} + +func extractAttrsForActivation(effective *v3.PolicyActivation) map[string]any { + return map[string]any{ + "policy_id": effective.PolicyID, + "network": mapNetworkToV2(effective.Network), + "version": effective.PolicyVersion, + "status": effective.Status, + } +} + +func mapNetworkToV2(network v3.Network) cloudlets.PolicyActivationNetwork { + if network == v3.StagingNetwork { + return cloudlets.PolicyActivationNetworkStaging + } + return cloudlets.PolicyActivationNetworkProduction +} + +func (strategy *v3ActivationStrategy) isReactivationNotNeeded(ctx context.Context, policyID, version int64, _ bool) (bool, string, error) { + isActive, id, err := strategy.isVersionAlreadyActive(ctx, policyID, version) + if err != nil { + return false, "", fmt.Errorf("policy activation update: %w", err) + } + return isActive, id, nil +} + +func (strategy *v3ActivationStrategy) deactivatePolicy(ctx context.Context, policyID, version int64, network string) error { + net, err := strategy.parseNetwork(network) + if err != nil { + return err + } + deactivation, err := strategy.client.DeactivatePolicy(ctx, v3.DeactivatePolicyRequest{ + PolicyID: policyID, + PolicyVersion: version, + Network: net, + }) + if err != nil { + return err + } + if deactivation != nil { + strategy.activationID = deactivation.ID + } + + _, err = strategy.waitForActivation(ctx, policyID, -1) + return err +} + +func (strategy *v3ActivationStrategy) shouldRetryActivation(err error) bool { + if err == nil { + return false + } + v3Error := new(v3.Error) + if errors.As(err, &v3Error) && v3Error.Status >= 500 { + return true + } + return false +} + +func (strategy *v3ActivationStrategy) fetchValuesForImport(ctx context.Context, policyID int64, network string) (map[string]any, string, error) { + net, err := strategy.parseNetwork(network) + if err != nil { + return nil, "", err + } + policy, err := strategy.client.GetPolicy(ctx, v3.GetPolicyRequest{PolicyID: policyID}) + if err != nil { + return nil, "", err + } + var activationToCheck *v3.PolicyActivation + + switch net { + case v3.StagingNetwork: + activationToCheck = policy.CurrentActivations.Staging.Effective + case v3.ProductionNetwork: + activationToCheck = policy.CurrentActivations.Production.Effective + } + + if activationToCheck == nil || activationToCheck.Operation != v3.OperationActivation || activationToCheck.Status != v3.ActivationStatusSuccess { + return nil, "", fmt.Errorf("no active activation has been found for policy_id: '%d' and network: '%s'", policyID, network) + } + + return map[string]any{ + "network": activationToCheck.Network, + "policy_id": policy.ID, + "is_shared": true, + }, strategy.getID(policyID, net), nil +} diff --git a/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_test.go b/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_test.go index 63aa9b12c..92266e10d 100644 --- a/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_test.go +++ b/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_test.go @@ -1,12 +1,12 @@ package cloudlets import ( - "context" "fmt" "regexp" "testing" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets" + v3 "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets/v3" "github.com/akamai/terraform-provider-akamai/v5/pkg/common/testutils" "github.com/akamai/terraform-provider-akamai/v5/pkg/tools" "github.com/hashicorp/terraform-plugin-testing/helper/resource" @@ -15,17 +15,19 @@ import ( "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" "github.com/tj/assert" + "golang.org/x/exp/slices" ) -func TestResourcePolicy(t *testing.T) { +func TestResourcePolicyV2(t *testing.T) { type policyAttributes struct { name, version, matchRulesPath string + description string timeouts string } var ( - expectCreatePolicy = func(_ *testing.T, client *cloudlets.Mock, policyID int64, policyName string, matchRules cloudlets.MatchRules) (*cloudlets.Policy, *cloudlets.PolicyVersion) { + expectCreatePolicy = func(_ *testing.T, client *cloudlets.Mock, policyID int64, policyName string, matchRules cloudlets.MatchRules, description string) (*cloudlets.Policy, *cloudlets.PolicyVersion) { policy := &cloudlets.Policy{ PolicyID: policyID, GroupID: 123, @@ -37,7 +39,7 @@ func TestResourcePolicy(t *testing.T) { Location: "/version/1", PolicyID: policyID, Version: 1, - Description: "test policy description", + Description: description, MatchRules: matchRules, MatchRuleFormat: "1.0", Warnings: []cloudlets.Warning{ @@ -54,31 +56,60 @@ func TestResourcePolicy(t *testing.T) { CloudletID: 0, GroupID: 123, }).Return(policy, nil).Once() - if matchRules == nil { + if matchRules == nil && description == "" { return policy, version } - client.On("UpdatePolicyVersion", mock.Anything, cloudlets.UpdatePolicyVersionRequest{ - UpdatePolicyVersion: cloudlets.UpdatePolicyVersion{ - Description: "test policy description", - MatchRules: matchRules, - }, - PolicyID: policyID, - Version: 1, - }).Return(version, nil).Once() + if matchRules != nil { + client.On("UpdatePolicyVersion", mock.Anything, cloudlets.UpdatePolicyVersionRequest{ + UpdatePolicyVersion: cloudlets.UpdatePolicyVersion{ + Description: description, + MatchRules: matchRules, + }, + PolicyID: policyID, + Version: 1, + }).Return(version, nil).Once() + } else { + client.On("UpdatePolicyVersion", mock.Anything, cloudlets.UpdatePolicyVersionRequest{ + UpdatePolicyVersion: cloudlets.UpdatePolicyVersion{ + Description: description, + MatchRules: make(cloudlets.MatchRules, 0), + }, + PolicyID: policyID, + Version: 1, + }).Return(version, nil).Once() + } + return policy, version } - expectReadPolicy = func(t *testing.T, client *cloudlets.Mock, policy *cloudlets.Policy, version *cloudlets.PolicyVersion, times int) { + expectListPolicyVersions = func(t *testing.T, client *cloudlets.Mock, policyId int64, versions []cloudlets.PolicyVersion, times int) { + client.On("ListPolicyVersions", mock.Anything, cloudlets.ListPolicyVersionsRequest{ + PolicyID: policyId, + Offset: 0, + PageSize: tools.IntPtr(1000), + }).Return(versions, nil).Times(times) + } + + expectReadPolicy = func(t *testing.T, client *cloudlets.Mock, policy *cloudlets.Policy, versions []cloudlets.PolicyVersion, times int) { + expectListPolicyVersions(t, client, policy.PolicyID, versions, times) + var latestVersion cloudlets.PolicyVersion + for _, version := range versions { + if latestVersion.Version < version.Version { + latestVersion = version + } + } client.On("GetPolicy", mock.Anything, cloudlets.GetPolicyRequest{PolicyID: policy.PolicyID}).Return(policy, nil).Times(times) - var versionWithoutWarnings cloudlets.PolicyVersion - err := copier.CopyWithOption(&versionWithoutWarnings, version, copier.Option{DeepCopy: true}) - require.NoError(t, err) - versionWithoutWarnings.Warnings = []cloudlets.Warning{} - versionWithoutWarnings.MatchRules = version.MatchRules - client.On("GetPolicyVersion", mock.Anything, cloudlets.GetPolicyVersionRequest{ - PolicyID: policy.PolicyID, - Version: version.Version, - }).Return(&versionWithoutWarnings, nil).Times(times) + if versions != nil && len(versions) > 0 { + var versionWithoutWarnings cloudlets.PolicyVersion + err := copier.CopyWithOption(&versionWithoutWarnings, latestVersion, copier.Option{DeepCopy: true}) + require.NoError(t, err) + versionWithoutWarnings.Warnings = []cloudlets.Warning{} + versionWithoutWarnings.MatchRules = latestVersion.MatchRules + client.On("GetPolicyVersion", mock.Anything, cloudlets.GetPolicyVersionRequest{ + PolicyID: policy.PolicyID, + Version: latestVersion.Version, + }).Return(&versionWithoutWarnings, nil).Times(times) + } } expectUpdatePolicy = func(t *testing.T, client *cloudlets.Mock, policy *cloudlets.Policy, updatedName string) *cloudlets.Policy { @@ -147,13 +178,9 @@ func TestResourcePolicy(t *testing.T) { expectRemovePolicy = func(_ *testing.T, client *cloudlets.Mock, policyID int64, numVersions, numDeleteRetries int) { var versionList []cloudlets.PolicyVersion for i := 1; i <= numVersions; i++ { - versionList = append(versionList, cloudlets.PolicyVersion{PolicyID: policyID, Version: int64(i)}) + versionList = slices.Insert(versionList, 0, cloudlets.PolicyVersion{PolicyID: policyID, Version: int64(i)}) } - client.On("ListPolicyVersions", mock.Anything, cloudlets.ListPolicyVersionsRequest{ - PolicyID: policyID, - PageSize: tools.IntPtr(1000), - Offset: 0, - }).Return(versionList, nil).Once() + expectListPolicyVersions(t, client, policyID, versionList, 1) for _, ver := range versionList { client.On("DeletePolicyVersion", mock.Anything, cloudlets.DeletePolicyVersionRequest{ PolicyID: ver.PolicyID, @@ -168,17 +195,7 @@ func TestResourcePolicy(t *testing.T) { client.On("RemovePolicy", mock.Anything, cloudlets.RemovePolicyRequest{PolicyID: policyID}).Return(nil).Once() } - expectImportPolicy = func(_ *testing.T, client *cloudlets.Mock, policyID int64, policyName string, numVersions int) { - var versionList []cloudlets.PolicyVersion - for i := 1; i <= numVersions; i++ { - versionList = append(versionList, cloudlets.PolicyVersion{PolicyID: policyID, Version: int64(i)}) - } - client.On("ListPolicyVersions", mock.Anything, cloudlets.ListPolicyVersionsRequest{ - PolicyID: policyID, - PageSize: tools.IntPtr(1000), - Offset: 0, - }).Return(versionList, nil).Once() - + expectImportPolicy = func(_ *testing.T, client *cloudlets.Mock, policyID int64, policyName string) { client.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{PageSize: tools.IntPtr(1000), Offset: 0}).Return([]cloudlets.Policy{ { PolicyID: policyID, Name: policyName, @@ -195,11 +212,12 @@ func TestResourcePolicy(t *testing.T) { resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "cloudlet_code", "ER"), resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "cloudlet_id", "0"), resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "group_id", "123"), - resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "description", "test policy description"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "description", attrs.description), resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "match_rule_format", "1.0"), resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "name", attrs.name), resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "version", attrs.version), resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "match_rules", matchRulesPath), + resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "is_shared", "false"), } if attrs.timeouts != "" { @@ -248,16 +266,18 @@ func TestResourcePolicy(t *testing.T) { UseIncomingSchemeAndHost: true, }, } - policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules) - expectReadPolicy(t, client, policy, version, 3) + policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules, "test policy description") + policyVersions := []cloudlets.PolicyVersion{*version} + expectReadPolicy(t, client, policy, policyVersions, 3) policy = expectUpdatePolicy(t, client, policy, "test_policy_updated") version = expectCreatePolicyVersion(t, client, policy.PolicyID, version, matchRules[:1]) - expectReadPolicy(t, client, policy, version, 2) + policyVersions = slices.Insert(policyVersions, 0, *version) + expectReadPolicy(t, client, policy, policyVersions, 2) expectRemovePolicy(t, client, policy.PolicyID, 2, 0) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -265,6 +285,7 @@ func TestResourcePolicy(t *testing.T) { name: "test_policy", version: "1", matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_create.json", testDir), + description: "test policy description", }), }, { @@ -273,7 +294,83 @@ func TestResourcePolicy(t *testing.T) { name: "test_policy_updated", version: "2", matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_update.json", testDir), + description: "test policy description", + }), + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("policy lifecycle detects new version drift", func(t *testing.T) { + testDir := "testdata/TestResPolicy/lifecycle_with_drift" + + client := new(cloudlets.Mock) + matchRules := cloudlets.MatchRules{ + &cloudlets.MatchRuleER{ + Name: "r1", + Type: "erMatchRule", + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 301, + RedirectURL: "/ddd", + MatchURL: "abc.com", + UseIncomingSchemeAndHost: true, + }, + &cloudlets.MatchRuleER{ + Name: "r3", + Type: "erMatchRule", + Matches: []cloudlets.MatchCriteriaER{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + }, + }, + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 307, + RedirectURL: "/abc/sss", + UseIncomingSchemeAndHost: true, + }, + } + policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules, "test policy description") + policyVersions := []cloudlets.PolicyVersion{*version} + expectReadPolicy(t, client, policy, policyVersions, 1) + // new version which causes drift + versionWithDrift := &cloudlets.PolicyVersion{ + Location: "/version/2", + PolicyID: 2, + Version: 2, + Description: "new description after drift", + MatchRules: matchRules, + MatchRuleFormat: "1.0", + Warnings: []cloudlets.Warning{ + { + Detail: "test warning details", + JSONPointer: "/matchRules/1/matches/0", + Title: "test warning", + Type: "test type", + }, + }, + } + policyVersions = slices.Insert(policyVersions, 0, *versionWithDrift) + expectReadPolicy(t, client, policy, policyVersions, 1) + expectRemovePolicy(t, client, policy.PolicyID, 2, 0) + + useClient(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + name: "test_policy", + version: "1", + matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_create.json", testDir), + description: "test policy description", }), + ExpectNonEmptyPlan: true, }, }, }) @@ -312,13 +409,13 @@ func TestResourcePolicy(t *testing.T) { UseIncomingSchemeAndHost: true, }, } - policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules) - expectReadPolicy(t, client, policy, version, 2) + policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules, "test policy description") + expectReadPolicy(t, client, policy, []cloudlets.PolicyVersion{*version}, 2) expectRemovePolicy(t, client, policy.PolicyID, 1, 1) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -326,6 +423,7 @@ func TestResourcePolicy(t *testing.T) { name: "test_policy", version: "1", matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_create.json", testDir), + description: "test policy description", }), }, }, @@ -365,16 +463,17 @@ func TestResourcePolicy(t *testing.T) { UseIncomingSchemeAndHost: true, }, } - policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules) - expectReadPolicy(t, client, policy, version, 3) + policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules, "test policy description") + policyVersions := []cloudlets.PolicyVersion{*version} + expectReadPolicy(t, client, policy, policyVersions, 3) policy = expectUpdatePolicy(t, client, policy, "test_policy_updated") version = expectUpdatePolicyVersion(t, client, policy.PolicyID, version, matchRules[:1]) - expectReadPolicy(t, client, policy, version, 2) + expectReadPolicy(t, client, policy, []cloudlets.PolicyVersion{*version}, 2) expectRemovePolicy(t, client, policy.PolicyID, 1, 0) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -382,6 +481,7 @@ func TestResourcePolicy(t *testing.T) { name: "test_policy", version: "1", matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_create.json", testDir), + description: "test policy description", }), }, { @@ -390,6 +490,7 @@ func TestResourcePolicy(t *testing.T) { name: "test_policy_updated", version: "1", matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_update.json", testDir), + description: "test policy description", }), }, }, @@ -429,15 +530,17 @@ func TestResourcePolicy(t *testing.T) { UseIncomingSchemeAndHost: true, }, } - policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules) - expectReadPolicy(t, client, policy, version, 3) + policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules, "test policy description") + policyVersions := []cloudlets.PolicyVersion{*version} + expectReadPolicy(t, client, policy, policyVersions, 3) policy = expectUpdatePolicy(t, client, policy, "test_policy_updated") - expectReadPolicy(t, client, policy, version, 2) + policyVersions = slices.Insert(policyVersions, 0, *version) + expectReadPolicy(t, client, policy, policyVersions, 2) expectRemovePolicy(t, client, policy.PolicyID, 1, 0) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -445,6 +548,7 @@ func TestResourcePolicy(t *testing.T) { name: "test_policy", version: "1", matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules.json", testDir), + description: "test policy description", }), }, { @@ -453,6 +557,7 @@ func TestResourcePolicy(t *testing.T) { name: "test_policy_updated", version: "1", matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules.json", testDir), + description: "test policy description", }), }, }, @@ -492,15 +597,17 @@ func TestResourcePolicy(t *testing.T) { UseIncomingSchemeAndHost: true, }, } - policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules) - expectReadPolicy(t, client, policy, version, 3) + policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules, "test policy description") + policyVersions := []cloudlets.PolicyVersion{*version} + expectReadPolicy(t, client, policy, policyVersions, 3) version = expectUpdatePolicyVersion(t, client, policy.PolicyID, version, matchRules[:1]) - expectReadPolicy(t, client, policy, version, 2) + policyVersions = []cloudlets.PolicyVersion{*version} + expectReadPolicy(t, client, policy, policyVersions, 2) expectRemovePolicy(t, client, policy.PolicyID, 1, 0) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -508,6 +615,7 @@ func TestResourcePolicy(t *testing.T) { name: "test_policy", version: "1", matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_create.json", testDir), + description: "test policy description", }), }, { @@ -516,6 +624,7 @@ func TestResourcePolicy(t *testing.T) { name: "test_policy", version: "1", matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_update.json", testDir), + description: "test policy description", }), }, }, @@ -555,10 +664,13 @@ func TestResourcePolicy(t *testing.T) { UseIncomingSchemeAndHost: true, }, } - policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules) - expectReadPolicy(t, client, policy, version, 3) + policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules, "test policy description") + policyVersions := []cloudlets.PolicyVersion{*version} + expectReadPolicy(t, client, policy, policyVersions, 3) version = expectUpdatePolicyVersion(t, client, policy.PolicyID, version, matchRules[:1]) - expectReadPolicy(t, client, policy, version, 4) + // update existing version in slice by deleting old policyVersions and defining new one + policyVersions = []cloudlets.PolicyVersion{*version} + expectReadPolicy(t, client, policy, policyVersions, 4) expectRemovePolicy(t, client, policy.PolicyID, 1, 0) warningsJSON, err := warningsToJSON(version.Warnings) @@ -571,7 +683,7 @@ func TestResourcePolicy(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -622,15 +734,18 @@ func TestResourcePolicy(t *testing.T) { UseIncomingSchemeAndHost: true, }, } - policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules) - expectReadPolicy(t, client, policy, version, 3) + policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules, "test policy description") + policyVersions := []cloudlets.PolicyVersion{*version} + expectReadPolicy(t, client, policy, policyVersions, 3) version = expectUpdatePolicyVersion(t, client, policy.PolicyID, version, cloudlets.MatchRules{}) - expectReadPolicy(t, client, policy, version, 2) + // update existing version in slice by deleting old policyVersions and defining new one + policyVersions = []cloudlets.PolicyVersion{*version} + expectReadPolicy(t, client, policy, policyVersions, 2) expectRemovePolicy(t, client, policy.PolicyID, 1, 0) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -638,6 +753,7 @@ func TestResourcePolicy(t *testing.T) { name: "test_policy", version: "1", matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_create.json", testDir), + description: "test policy description", }), }, { @@ -646,6 +762,34 @@ func TestResourcePolicy(t *testing.T) { name: "test_policy", version: "1", matchRulesPath: "", + description: "test policy description", + }), + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("create policy without match rules or description", func(t *testing.T) { + testDir := "testdata/TestResPolicy/create_no_match_rules_no_description" + + client := new(cloudlets.Mock) + policy, version := expectCreatePolicy(t, client, 2, "test_policy", nil, "") + policyVersions := []cloudlets.PolicyVersion{*version} + expectReadPolicy(t, client, policy, policyVersions, 2) + expectRemovePolicy(t, client, 2, 1, 0) + useClient(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + name: "test_policy", + version: "1", + matchRulesPath: "", + description: "", }), }, }, @@ -654,16 +798,17 @@ func TestResourcePolicy(t *testing.T) { client.AssertExpectations(t) }) - t.Run("create policy without match rules", func(t *testing.T) { + t.Run("create policy without match rules with description", func(t *testing.T) { testDir := "testdata/TestResPolicy/create_no_match_rules" client := new(cloudlets.Mock) - policy, version := expectCreatePolicy(t, client, 2, "test_policy", nil) - expectReadPolicy(t, client, policy, version, 2) + policy, version := expectCreatePolicy(t, client, 2, "test_policy", nil, "test policy description") + policyVersions := []cloudlets.PolicyVersion{*version} + expectReadPolicy(t, client, policy, policyVersions, 2) expectRemovePolicy(t, client, 2, 1, 0) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -671,6 +816,7 @@ func TestResourcePolicy(t *testing.T) { name: "test_policy", version: "1", matchRulesPath: "", + description: "test policy description", }), }, }, @@ -695,12 +841,13 @@ func TestResourcePolicy(t *testing.T) { } client := new(cloudlets.Mock) - policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules) - expectReadPolicy(t, client, policy, version, 2) + policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules, "test policy description") + policyVersions := []cloudlets.PolicyVersion{*version} + expectReadPolicy(t, client, policy, policyVersions, 2) expectRemovePolicy(t, client, 2, 1, 0) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -709,6 +856,7 @@ func TestResourcePolicy(t *testing.T) { version: "1", matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules.json", testDir), timeouts: "4h", + description: "test policy description", }), }, }, @@ -729,7 +877,7 @@ func TestResourcePolicy(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -793,7 +941,6 @@ func TestResourcePolicy(t *testing.T) { PolicyID: 2, Version: 1, }).Return(nil, fmt.Errorf("UpdatePolicyVersionError")) - expectRemovePolicy(t, client, 2, 0, 0) } testCases := []struct { @@ -803,17 +950,24 @@ func TestResourcePolicy(t *testing.T) { { Expectations: func(client *cloudlets.Mock) { expectErrorCreatingVersion(client) - expectReadPolicy(t, client, policy, &cloudlets.PolicyVersion{ + expectReadPolicy(t, client, policy, []cloudlets.PolicyVersion{{ PolicyID: 2, Version: 1, - }, 1) + }}, 1) + expectRemovePolicy(t, client, 2, 1, 0) + }, ExpectedError: regexp.MustCompile("UpdatePolicyVersionError"), }, { Expectations: func(client *cloudlets.Mock) { expectErrorCreatingVersion(client) + expectListPolicyVersions(t, client, policy.PolicyID, []cloudlets.PolicyVersion{{ + PolicyID: 2, + Version: 1, + }}, 1) client.On("GetPolicy", mock.Anything, cloudlets.GetPolicyRequest{PolicyID: policy.PolicyID}).Return(nil, fmt.Errorf("GetPolicyError")) + expectRemovePolicy(t, client, 2, 1, 0) }, ExpectedError: regexp.MustCompile("(?s)GetPolicyError.*UpdatePolicyVersionError"), }, @@ -824,7 +978,7 @@ func TestResourcePolicy(t *testing.T) { testCases[i].Expectations(client) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -841,12 +995,14 @@ func TestResourcePolicy(t *testing.T) { testDir := "testdata/TestResPolicy/create_no_match_rules" client := new(cloudlets.Mock) - policy, _ := expectCreatePolicy(t, client, 2, "test_policy", nil) + policy, version := expectCreatePolicy(t, client, 2, "test_policy", nil, "test policy description") + policyVersions := []cloudlets.PolicyVersion{*version} + expectListPolicyVersions(t, client, policy.PolicyID, policyVersions, 1) client.On("GetPolicy", mock.Anything, cloudlets.GetPolicyRequest{PolicyID: policy.PolicyID}).Return(nil, fmt.Errorf("oops")) expectRemovePolicy(t, client, 2, 1, 0) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -862,7 +1018,9 @@ func TestResourcePolicy(t *testing.T) { testDir := "testdata/TestResPolicy/create_no_match_rules" client := new(cloudlets.Mock) - policy, version := expectCreatePolicy(t, client, 2, "test_policy", nil) + policy, version := expectCreatePolicy(t, client, 2, "test_policy", nil, "test policy description") + policyVersions := []cloudlets.PolicyVersion{*version} + expectListPolicyVersions(t, client, policy.PolicyID, policyVersions, 1) client.On("GetPolicy", mock.Anything, cloudlets.GetPolicyRequest{PolicyID: policy.PolicyID}).Return(policy, nil) client.On("GetPolicyVersion", mock.Anything, cloudlets.GetPolicyVersionRequest{ PolicyID: policy.PolicyID, @@ -871,7 +1029,7 @@ func TestResourcePolicy(t *testing.T) { expectRemovePolicy(t, client, 2, 1, 0) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -914,8 +1072,9 @@ func TestResourcePolicy(t *testing.T) { UseIncomingSchemeAndHost: true, }, } - policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules) - expectReadPolicy(t, client, policy, version, 3) + policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules, "test policy description") + policyVersions := []cloudlets.PolicyVersion{*version} + expectReadPolicy(t, client, policy, policyVersions, 3) client.On("UpdatePolicy", mock.Anything, cloudlets.UpdatePolicyRequest{ UpdatePolicy: cloudlets.UpdatePolicy{ Name: "test_policy_updated", @@ -927,7 +1086,7 @@ func TestResourcePolicy(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -974,8 +1133,8 @@ func TestResourcePolicy(t *testing.T) { } expectErrorUpdatingVersion := func(client *cloudlets.Mock, expectReadPolicyTimes int) (policy *cloudlets.Policy) { - policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules) - expectReadPolicy(t, client, policy, version, expectReadPolicyTimes) + policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules, "test policy description") + expectReadPolicy(t, client, policy, []cloudlets.PolicyVersion{*version}, expectReadPolicyTimes) client.On("GetPolicyVersion", mock.Anything, cloudlets.GetPolicyVersionRequest{ PolicyID: policy.PolicyID, Version: version.Version, @@ -1008,6 +1167,14 @@ func TestResourcePolicy(t *testing.T) { Expectations: func(client *cloudlets.Mock) { policy := expectErrorUpdatingVersion(client, 3) client.On("GetPolicy", mock.Anything, cloudlets.GetPolicyRequest{PolicyID: policy.PolicyID}).Return(nil, fmt.Errorf("GetPolicyError")) + client.On("ListPolicyVersions", mock.Anything, cloudlets.ListPolicyVersionsRequest{PolicyID: policy.PolicyID, + Offset: 0, + PageSize: tools.IntPtr(1000), + }).Return([]cloudlets.PolicyVersion{{ + PolicyID: 2, + Version: 1, + }, + }, nil) }, ExpectedError: regexp.MustCompile("(?s)GetPolicyError.*UpdatePolicyVersionError"), }, @@ -1018,7 +1185,7 @@ func TestResourcePolicy(t *testing.T) { testCases[i].Expectations(client) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -1040,7 +1207,7 @@ func TestResourcePolicy(t *testing.T) { useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -1084,14 +1251,15 @@ func TestResourcePolicy(t *testing.T) { }, } - policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules) - expectReadPolicy(t, client, policy, version, 3) - expectImportPolicy(t, client, 2, "test_policy", 1) + policy, version := expectCreatePolicy(t, client, 2, "test_policy", matchRules, "test policy description") + policyVersions := []cloudlets.PolicyVersion{*version} + expectReadPolicy(t, client, policy, policyVersions, 3) + expectImportPolicy(t, client, 2, "test_policy") expectRemovePolicy(t, client, 2, 1, 0) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -1109,19 +1277,101 @@ func TestResourcePolicy(t *testing.T) { client.AssertExpectations(t) }) - t.Run("error importing policy not found", func(t *testing.T) { - testDir := "testdata/TestResPolicy/import_no_match_rules" + t.Run("import policy - test checkForV2Policy()", func(t *testing.T) { + testDir := "testdata/TestResPolicy/import" client := new(cloudlets.Mock) + policyID := int64(2) - policy, version := expectCreatePolicy(t, client, 2, "test_policy", nil) - expectReadPolicy(t, client, policy, version, 2) - client.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{PageSize: tools.IntPtr(1000), Offset: 0}). - Return([]cloudlets.Policy{}, nil).Once() + matchRules := cloudlets.MatchRules{ + &cloudlets.MatchRuleER{ + Name: "r1", + Type: "erMatchRule", + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 301, + RedirectURL: "/ddd", + MatchURL: "abc.com", + UseIncomingSchemeAndHost: true, + }, + &cloudlets.MatchRuleER{ + Name: "r3", + Type: "erMatchRule", + Matches: []cloudlets.MatchCriteriaER{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + }, + }, + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 307, + RedirectURL: "/abc/sss", + UseIncomingSchemeAndHost: true, + }, + } + + policy, version := expectCreatePolicy(t, client, policyID, "test_policy", matchRules, "test policy description") + policyVersions := []cloudlets.PolicyVersion{*version} + expectReadPolicy(t, client, policy, policyVersions, 3) + // custom import mocks + // mock that 1000 policies are returned, desired not found + client.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{ + PageSize: tools.IntPtr(1000), + Offset: 0, + }).Return(make([]cloudlets.Policy, 1000), nil).Once() + // mock that desired policy is on the next page + client.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{ + PageSize: tools.IntPtr(1000), + Offset: 1, + }).Return([]cloudlets.Policy{ + { + PolicyID: policyID, + Name: "test_policy", + }, + }, nil).Once() expectRemovePolicy(t, client, 2, 1, 0) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + }, + { + ImportState: true, + ImportStateId: "test_policy", + ResourceName: "akamai_cloudlets_policy.policy", + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"warnings"}, + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("error importing policy not found", func(t *testing.T) { + testDir := "testdata/TestResPolicy/import_no_match_rules" + clientV2 := new(cloudlets.Mock) + clientV3 := new(v3.Mock) + + policy, version := expectCreatePolicy(t, clientV2, 2, "test_policy", nil, "test policy description") + policyVersions := []cloudlets.PolicyVersion{*version} + expectReadPolicy(t, clientV2, policy, policyVersions, 2) + clientV2.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{PageSize: tools.IntPtr(1000), Offset: 0}). + Return([]cloudlets.Policy{}, nil).Once() + clientV3.On("ListPolicies", mock.Anything, v3.ListPoliciesRequest{ + Page: 0, + Size: 1000, + }).Return(&v3.ListPoliciesResponse{ + Content: []v3.Policy{}, + }, nil).Once() + expectRemovePolicy(t, clientV2, 2, 1, 0) + + useClientV2AndV3(clientV2, clientV3, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -1135,21 +1385,24 @@ func TestResourcePolicy(t *testing.T) { }, }) }) - client.AssertExpectations(t) + clientV2.AssertExpectations(t) + clientV3.AssertExpectations(t) }) - t.Run("error importing policy no version found", func(t *testing.T) { - testDir := "testdata/TestResPolicy/import_no_match_rules" + t.Run("importing policy no version found", func(t *testing.T) { + testDir := "testdata/TestResPolicy/import_no_version" client := new(cloudlets.Mock) - policy, version := expectCreatePolicy(t, client, 2, "test_policy", nil) - expectReadPolicy(t, client, policy, version, 2) - expectImportPolicy(t, client, 2, "test_policy", 0) + policy, _ := expectCreatePolicy(t, client, 2, "test_policy", nil, "") + expectReadPolicy(t, client, policy, nil, 2) + expectImportPolicy(t, client, 2, "test_policy") + //read after import + expectReadPolicy(t, client, policy, nil, 1) expectRemovePolicy(t, client, 2, 1, 0) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -1158,7 +1411,12 @@ func TestResourcePolicy(t *testing.T) { ImportState: true, ImportStateId: "test_policy", ResourceName: "akamai_cloudlets_policy.policy", - ExpectError: regexp.MustCompile("no policy version found"), + Check: checkPolicyAttributes(policyAttributes{ + name: "test_policy", + version: "", + matchRulesPath: "", + description: "", + }), }, }, }) @@ -1170,13 +1428,14 @@ func TestResourcePolicy(t *testing.T) { testDir := "testdata/TestResPolicy/import_no_match_rules" client := new(cloudlets.Mock) - policy, version := expectCreatePolicy(t, client, 2, "test_policy", nil) - expectReadPolicy(t, client, policy, version, 2) + policy, version := expectCreatePolicy(t, client, 2, "test_policy", nil, "test policy description") + policyVersions := []cloudlets.PolicyVersion{*version} + expectReadPolicy(t, client, policy, policyVersions, 2) expectRemovePolicy(t, client, 2, 1, 0) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ - ProviderFactories: testAccProviders, + ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{ { Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), @@ -1196,125 +1455,1699 @@ func TestResourcePolicy(t *testing.T) { }) } -func TestDiffSuppressMatchRules(t *testing.T) { - basePath := "testdata/TestResPolicy/diff_suppress" - tests := map[string]struct { - oldPath, newPath string - expected bool - }{ - "identical JSON": { - oldPath: "rules.json", - newPath: "rules.json", - expected: true, - }, - "different formatting, same content": { - oldPath: "rules.json", - newPath: "different_format.json", - expected: true, - }, - "difference in location and akaRuleId": { - oldPath: "with_location.json", - newPath: "rules.json", - expected: true, - }, - "invalid 'old' json": { - oldPath: "invalid.json", - newPath: "rules.json", - expected: false, - }, - "invalid 'new' json": { - oldPath: "rules.json", - newPath: "invalid.json", - expected: false, - }, - } - - for name, test := range tests { - t.Run(name, func(t *testing.T) { - oldJSON := testutils.LoadFixtureString(t, fmt.Sprintf("%s/%s", basePath, test.oldPath)) - newJSON := testutils.LoadFixtureString(t, fmt.Sprintf("%s/%s", basePath, test.newPath)) - res := diffSuppressMatchRules("", oldJSON, newJSON, nil) - assert.Equal(t, test.expected, res) - }) - } -} +func TestResourcePolicyV3(t *testing.T) { -func TestFindPolicyByName(t *testing.T) { - preparePoliciesPage := func(pageSize, startingID int64) []cloudlets.Policy { - policies := make([]cloudlets.Policy, 0, pageSize) - for i := startingID; i < startingID+pageSize; i++ { - policies = append(policies, cloudlets.Policy{PolicyID: i, Name: fmt.Sprintf("%d", i)}) - } - return policies + type policyAttributes struct { + version, matchRulesPath string + description string + timeouts string + groupID int64 } - tests := map[string]struct { - policyName string - init func(m *cloudlets.Mock) - expectedID int64 - withError bool - }{ - "policy found in first iteration": { - policyName: "test_policy", - init: func(m *cloudlets.Mock) { - m.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{PageSize: tools.IntPtr(1000), Offset: 0}).Return([]cloudlets.Policy{ - {PolicyID: 9999999, Name: "some_policy"}, - {PolicyID: 1234567, Name: "test_policy"}, - }, nil).Once() - }, - expectedID: 1234567, - }, - "policy found on 3rd page": { - policyName: "test_policy", - init: func(m *cloudlets.Mock) { - m.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{PageSize: tools.IntPtr(1000), Offset: 0}). - Return(preparePoliciesPage(1000, 0), nil).Once() - m.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{PageSize: tools.IntPtr(1000), Offset: 1000}). - Return(preparePoliciesPage(1000, 1000), nil).Once() - m.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{PageSize: tools.IntPtr(1000), Offset: 2000}).Return([]cloudlets.Policy{ - {PolicyID: 9999999, Name: "some_policy"}, - {PolicyID: 1234567, Name: "test_policy"}, - }, nil).Once() - - }, - expectedID: 1234567, - }, - "policy not found": { - policyName: "test_policy", - init: func(m *cloudlets.Mock) { - m.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{PageSize: tools.IntPtr(1000), Offset: 0}). - Return(preparePoliciesPage(1000, 0), nil).Once() - m.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{PageSize: tools.IntPtr(1000), Offset: 1000}). - Return(preparePoliciesPage(1000, 1000), nil).Once() - m.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{PageSize: tools.IntPtr(1000), Offset: 2000}). - Return(preparePoliciesPage(250, 2000), nil).Once() - - }, - withError: true, - }, - "error listing policies": { - policyName: "test_policy", - init: func(m *cloudlets.Mock) { - m.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{PageSize: tools.IntPtr(1000), Offset: 0}). - Return(preparePoliciesPage(1000, 0), nil).Once() - m.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{PageSize: tools.IntPtr(1000), Offset: 1000}). - Return(nil, fmt.Errorf("oops")).Once() - }, - withError: true, - }, - } - for name, test := range tests { - t.Run(name, func(t *testing.T) { - m := new(cloudlets.Mock) - test.init(m) - policy, err := findPolicyByName(context.Background(), test.policyName, m) - m.AssertExpectations(t) - if test.withError { - assert.Error(t, err) - return + var ( + expectCreatePolicy = func(_ *testing.T, client *v3.Mock, policyID int64, groupID int64, matchRules v3.MatchRules, description string) (*v3.Policy, *v3.PolicyVersion) { + policy := &v3.Policy{ + ID: policyID, + GroupID: groupID, + Name: "test_policy", + CloudletType: "ER", } - require.NoError(t, err) - assert.Equal(t, test.expectedID, policy.PolicyID) + version := &v3.PolicyVersion{ + PolicyID: policyID, + PolicyVersion: 1, + Description: tools.StringPtr(description), + MatchRules: matchRules, + MatchRulesWarnings: []v3.MatchRulesWarning{ + { + Detail: "test warning details", + JSONPointer: "/matchRules/1/matches/0", + Title: "test warning", + Type: "test type", + }, + }, + } + client.On("CreatePolicy", mock.Anything, v3.CreatePolicyRequest{ + Name: "test_policy", + CloudletType: v3.CloudletTypeER, + GroupID: groupID, + }).Return(policy, nil).Once() + if matchRules == nil && description == "" { + return policy, nil + } + if matchRules != nil { + client.On("CreatePolicyVersion", mock.Anything, v3.CreatePolicyVersionRequest{ + PolicyID: policyID, + CreatePolicyVersion: v3.CreatePolicyVersion{ + MatchRules: matchRules, + Description: tools.StringPtr(description), + }, + }).Return(version, nil).Once() + } else { + client.On("CreatePolicyVersion", mock.Anything, v3.CreatePolicyVersionRequest{ + PolicyID: policyID, + CreatePolicyVersion: v3.CreatePolicyVersion{ + MatchRules: make(v3.MatchRules, 0), + Description: tools.StringPtr(description), + }, + }).Return(version, nil).Once() + } + + return policy, version + } + + expectListPolicyVersions = func(t *testing.T, client *v3.Mock, policyId int64, versions v3.ListPolicyVersions, times int) { + client.On("ListPolicyVersions", mock.Anything, v3.ListPolicyVersionsRequest{ + PolicyID: policyId, + Page: 0, + Size: 1000, + }).Return(&versions, nil).Times(times) + } + + expectReadPolicy = func(t *testing.T, client *v3.Mock, policy *v3.Policy, version *v3.PolicyVersion, times int) { + if version != nil { + var versions v3.ListPolicyVersions + versions.PolicyVersions = slices.Insert(versions.PolicyVersions, 0, v3.ListPolicyVersionsItem{PolicyVersion: version.PolicyVersion, PolicyID: policy.ID}) + expectListPolicyVersions(t, client, policy.ID, versions, times) + } else { + expectListPolicyVersions(t, client, policy.ID, v3.ListPolicyVersions{PolicyVersions: make([]v3.ListPolicyVersionsItem, 0)}, times) + } + client.On("GetPolicy", mock.Anything, v3.GetPolicyRequest{PolicyID: policy.ID}).Return(policy, nil).Times(times) + if version != nil { + var versionWithoutWarnings v3.PolicyVersion + err := copier.CopyWithOption(&versionWithoutWarnings, version, copier.Option{DeepCopy: true}) + require.NoError(t, err) + versionWithoutWarnings.MatchRulesWarnings = []v3.MatchRulesWarning{} + versionWithoutWarnings.MatchRules = version.MatchRules + client.On("GetPolicyVersion", mock.Anything, v3.GetPolicyVersionRequest{ + PolicyID: policy.ID, + PolicyVersion: version.PolicyVersion, + }).Return(&versionWithoutWarnings, nil).Times(times) + } + } + + expectUpdatePolicy = func(t *testing.T, client *v3.Mock, policy *v3.Policy, updatedGroup int64) *v3.Policy { + var policyUpdate v3.Policy + err := copier.CopyWithOption(&policyUpdate, policy, copier.Option{DeepCopy: true}) + require.NoError(t, err) + policyUpdate.GroupID = updatedGroup + client.On("UpdatePolicy", mock.Anything, v3.UpdatePolicyRequest{ + BodyParams: v3.UpdatePolicyBodyParams{ + GroupID: updatedGroup, + }, + PolicyID: policyUpdate.ID, + }).Return(&policyUpdate, nil).Once() + return &policyUpdate + } + + expectCreatePolicyVersion = func(t *testing.T, client *v3.Mock, policyID int64, version *v3.PolicyVersion, newMatchRules v3.MatchRules) *v3.PolicyVersion { + var activatedVersion v3.PolicyVersion + err := copier.CopyWithOption(&activatedVersion, version, copier.Option{DeepCopy: true}) + require.NoError(t, err) + activatedVersion.Immutable = true + + client.On("GetPolicyVersion", mock.Anything, v3.GetPolicyVersionRequest{ + PolicyID: policyID, + PolicyVersion: version.PolicyVersion, + }).Return(&activatedVersion, nil).Once() + + var versionUpdate v3.PolicyVersion + err = copier.CopyWithOption(&versionUpdate, activatedVersion, copier.Option{DeepCopy: true}) + require.NoError(t, err) + versionUpdate.MatchRules = newMatchRules + versionUpdate.PolicyVersion = version.PolicyVersion + 1 + versionUpdate.Immutable = false + + client.On("CreatePolicyVersion", mock.Anything, v3.CreatePolicyVersionRequest{ + CreatePolicyVersion: v3.CreatePolicyVersion{ + Description: tools.StringPtr("test policy description"), + MatchRules: newMatchRules, + }, + PolicyID: policyID, + }).Return(&versionUpdate, nil).Once() + return &versionUpdate + } + + expectUpdatePolicyVersion = func(t *testing.T, client *v3.Mock, policyID int64, version *v3.PolicyVersion, newMatchRules v3.MatchRules) *v3.PolicyVersion { + client.On("GetPolicyVersion", mock.Anything, v3.GetPolicyVersionRequest{ + PolicyID: policyID, + PolicyVersion: version.PolicyVersion, + }).Return(version, nil).Once() + + var versionUpdate v3.PolicyVersion + err := copier.CopyWithOption(&versionUpdate, version, copier.Option{DeepCopy: true}) + require.NoError(t, err) + versionUpdate.MatchRules = newMatchRules + client.On("UpdatePolicyVersion", mock.Anything, v3.UpdatePolicyVersionRequest{ + UpdatePolicyVersion: v3.UpdatePolicyVersion{ + Description: tools.StringPtr("test policy description"), + MatchRules: newMatchRules, + }, + PolicyID: policyID, + PolicyVersion: version.PolicyVersion, + }).Return(&versionUpdate, nil).Once() + return &versionUpdate + } + + expectRemovePolicy = func(_ *testing.T, client *v3.Mock, policyID int64) { + client.On("GetPolicy", mock.Anything, v3.GetPolicyRequest{ + PolicyID: policyID, + }).Return(&v3.Policy{ + CurrentActivations: v3.CurrentActivations{Production: v3.ActivationInfo{}, Staging: v3.ActivationInfo{}}, + ID: policyID, + }, nil).Once() + + client.On("DeletePolicy", mock.Anything, v3.DeletePolicyRequest{PolicyID: policyID}).Return(nil).Once() + } + + expectImportPolicy = func(_ *testing.T, clientV3 *v3.Mock, clientV2 *cloudlets.Mock, policyID int64, policyName string) { + listPoliciesV2Resp := []cloudlets.Policy{ + { + Name: "other-name", + }, + } + clientV2.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{ + PageSize: tools.IntPtr(1000), + Offset: 0, + }).Return(listPoliciesV2Resp, nil).Once() + clientV3.On("ListPolicies", mock.Anything, v3.ListPoliciesRequest{ + Size: 1000, Page: 0, + }).Return(&v3.ListPoliciesResponse{ + Content: []v3.Policy{ + { + ID: policyID, Name: policyName, + }, + }, + }, nil).Once() + } + + checkPolicyAttributes = func(attrs policyAttributes) resource.TestCheckFunc { + var matchRulesPath string + if attrs.matchRulesPath != "" { + matchRulesPath = testutils.LoadFixtureString(t, attrs.matchRulesPath) + } + checkFunc := []resource.TestCheckFunc{ + resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "id", "2"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "cloudlet_code", "ER"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "group_id", fmt.Sprintf("%d", attrs.groupID)), + resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "description", attrs.description), + resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "name", "test_policy"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "match_rules", matchRulesPath), + resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "is_shared", "true"), + } + + if attrs.version != "" { + checkFunc = append(checkFunc, + resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "version", attrs.version), + ) + } else { + checkFunc = append(checkFunc, + resource.TestCheckNoResourceAttr("akamai_cloudlets_policy.policy", "version"), + ) + } + + if attrs.timeouts != "" { + checkFunc = append(checkFunc, + resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "timeouts.#", "1"), + resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "timeouts.0.default", attrs.timeouts), + ) + } else { + checkFunc = append(checkFunc, + resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "timeouts.#", "0"), + ) + } + + return resource.ComposeAggregateTestCheckFunc(checkFunc...) + } + ) + + t.Run("policy v3 lifecycle with create new version", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/lifecycle" + + client := new(v3.Mock) + matchRules := v3.MatchRules{ + &v3.MatchRuleER{ + Name: "r1", + Type: "erMatchRule", + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 301, + RedirectURL: "/ddd", + MatchURL: "abc.com", + UseIncomingSchemeAndHost: true, + }, + &v3.MatchRuleER{ + Name: "r3", + Type: "erMatchRule", + Matches: []v3.MatchCriteriaER{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + }, + }, + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 307, + RedirectURL: "/abc/sss", + UseIncomingSchemeAndHost: true, + }, + } + policy, version := expectCreatePolicy(t, client, 2, 123, matchRules, "test policy description") + expectReadPolicy(t, client, policy, version, 3) + policy = expectUpdatePolicy(t, client, policy, 321) + version = expectCreatePolicyVersion(t, client, policy.ID, version, matchRules[:1]) + expectReadPolicy(t, client, policy, version, 2) + expectRemovePolicy(t, client, policy.ID) + + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "1", + matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_create.json", testDir), + description: "test policy description", + }), + }, + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_update.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 321, + version: "2", + matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_update.json", testDir), + description: "test policy description", + }), + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("policy v3 create policy and update with version drift", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/lifecycle_with_drift" + + client := new(v3.Mock) + matchRules := v3.MatchRules{ + &v3.MatchRuleER{ + Name: "r1", + Type: "erMatchRule", + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 301, + RedirectURL: "/ddd", + MatchURL: "abc.com", + UseIncomingSchemeAndHost: true, + }, + &v3.MatchRuleER{ + Name: "r3", + Type: "erMatchRule", + Matches: []v3.MatchCriteriaER{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + }, + }, + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 307, + RedirectURL: "/abc/sss", + UseIncomingSchemeAndHost: true, + }, + } + policy, version := expectCreatePolicy(t, client, 2, 123, matchRules, "test policy description") + expectReadPolicy(t, client, policy, version, 1) + version = &v3.PolicyVersion{ + PolicyID: 2, + PolicyVersion: 2, + Description: tools.StringPtr("new description after drift"), + MatchRules: matchRules, + MatchRulesWarnings: []v3.MatchRulesWarning{ + { + Detail: "test warning details", + JSONPointer: "/matchRules/1/matches/0", + Title: "test warning", + Type: "test type", + }, + }, + } + expectReadPolicy(t, client, policy, version, 1) + expectRemovePolicy(t, client, policy.ID) + + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "1", + matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_create.json", testDir), + description: "test policy description", + }), + ExpectNonEmptyPlan: true, + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("policy V3 lifecycle, deactivation before delete", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/lifecycle" + + client := new(v3.Mock) + matchRules := v3.MatchRules{ + &v3.MatchRuleER{ + Name: "r1", + Type: "erMatchRule", + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 301, + RedirectURL: "/ddd", + MatchURL: "abc.com", + UseIncomingSchemeAndHost: true, + }, + &v3.MatchRuleER{ + Name: "r3", + Type: "erMatchRule", + Matches: []v3.MatchCriteriaER{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + }, + }, + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 307, + RedirectURL: "/abc/sss", + UseIncomingSchemeAndHost: true, + }, + } + policy, version := expectCreatePolicy(t, client, 2, 123, matchRules, "test policy description") + expectReadPolicy(t, client, policy, version, 2) + + client.On("GetPolicy", mock.Anything, v3.GetPolicyRequest{ + PolicyID: policy.ID, + }).Return(&v3.Policy{ + CurrentActivations: v3.CurrentActivations{Production: v3.ActivationInfo{}, Staging: v3.ActivationInfo{ + Effective: &v3.PolicyActivation{ + Network: v3.StagingNetwork, + Operation: v3.OperationActivation, + PolicyID: policy.ID, + PolicyVersion: version.PolicyVersion, + Status: v3.ActivationStatusSuccess, + }, + Latest: &v3.PolicyActivation{ + Network: v3.StagingNetwork, + Operation: v3.OperationActivation, + PolicyID: policy.ID, + PolicyVersion: version.PolicyVersion, + Status: v3.ActivationStatusSuccess, + }, + }}, + ID: policy.ID, + GroupID: 123, + }, nil).Once() + + client.On("DeactivatePolicy", mock.Anything, v3.DeactivatePolicyRequest{ + PolicyID: policy.ID, + Network: v3.StagingNetwork, + PolicyVersion: version.PolicyVersion, + }).Return(&v3.PolicyActivation{ + PolicyID: policy.ID, + PolicyVersion: version.PolicyVersion, + Status: v3.ActivationStatusInProgress, + }, nil).Once() + + client.On("GetPolicy", mock.Anything, v3.GetPolicyRequest{PolicyID: policy.ID}).Return(&v3.Policy{ + CurrentActivations: v3.CurrentActivations{Production: v3.ActivationInfo{}, Staging: v3.ActivationInfo{ + Effective: &v3.PolicyActivation{ + Network: v3.StagingNetwork, + Operation: v3.OperationDeactivation, + PolicyID: policy.ID, + PolicyVersion: version.PolicyVersion, + Status: v3.ActivationStatusSuccess, + }, + Latest: &v3.PolicyActivation{ + Network: v3.StagingNetwork, + Operation: v3.OperationDeactivation, + PolicyID: policy.ID, + PolicyVersion: version.PolicyVersion, + Status: v3.ActivationStatusSuccess, + }, + }}, + ID: policy.ID, + GroupID: 123, + }, nil).Once() + + client.On("DeletePolicy", mock.Anything, v3.DeletePolicyRequest{PolicyID: policy.ID}).Return(nil).Once() + + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "1", + matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_create.json", testDir), + description: "test policy description", + }), + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("policy V3 lifecycle, in progress deactivation during delete", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/lifecycle" + + client := new(v3.Mock) + matchRules := v3.MatchRules{ + &v3.MatchRuleER{ + Name: "r1", + Type: "erMatchRule", + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 301, + RedirectURL: "/ddd", + MatchURL: "abc.com", + UseIncomingSchemeAndHost: true, + }, + &v3.MatchRuleER{ + Name: "r3", + Type: "erMatchRule", + Matches: []v3.MatchCriteriaER{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + }, + }, + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 307, + RedirectURL: "/abc/sss", + UseIncomingSchemeAndHost: true, + }, + } + policy, version := expectCreatePolicy(t, client, 2, 123, matchRules, "test policy description") + expectReadPolicy(t, client, policy, version, 2) + + client.On("GetPolicy", mock.Anything, v3.GetPolicyRequest{ + PolicyID: policy.ID, + }).Return(&v3.Policy{ + CurrentActivations: v3.CurrentActivations{Production: v3.ActivationInfo{}, Staging: v3.ActivationInfo{ + Effective: &v3.PolicyActivation{ + Network: v3.StagingNetwork, + Operation: v3.OperationActivation, + PolicyID: policy.ID, + PolicyVersion: version.PolicyVersion, + Status: v3.ActivationStatusSuccess, + }, + Latest: &v3.PolicyActivation{ + Network: v3.StagingNetwork, + Operation: v3.OperationDeactivation, + PolicyID: policy.ID, + PolicyVersion: version.PolicyVersion, + Status: v3.ActivationStatusInProgress, + }, + }}, + ID: policy.ID, + GroupID: 123, + }, nil).Once() + + client.On("GetPolicy", mock.Anything, v3.GetPolicyRequest{ + PolicyID: policy.ID, + }).Return(&v3.Policy{ + CurrentActivations: v3.CurrentActivations{Production: v3.ActivationInfo{}, Staging: v3.ActivationInfo{ + Effective: &v3.PolicyActivation{ + Network: v3.StagingNetwork, + Operation: v3.OperationDeactivation, + PolicyID: policy.ID, + PolicyVersion: version.PolicyVersion, + Status: v3.ActivationStatusSuccess, + }, + Latest: &v3.PolicyActivation{ + Network: v3.StagingNetwork, + Operation: v3.OperationDeactivation, + PolicyID: policy.ID, + PolicyVersion: version.PolicyVersion, + Status: v3.ActivationStatusSuccess, + }, + }}, + ID: policy.ID, + GroupID: 123, + }, nil).Once() + + client.On("DeletePolicy", mock.Anything, v3.DeletePolicyRequest{PolicyID: policy.ID}).Return(nil).Once() + + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "1", + matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_create.json", testDir), + description: "test policy description", + }), + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("policy v3 lifecycle with update existing version", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/lifecycle" + + client := new(v3.Mock) + matchRules := v3.MatchRules{ + &v3.MatchRuleER{ + Name: "r1", + Type: "erMatchRule", + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 301, + RedirectURL: "/ddd", + MatchURL: "abc.com", + UseIncomingSchemeAndHost: true, + }, + &v3.MatchRuleER{ + Name: "r3", + Type: "erMatchRule", + Matches: []v3.MatchCriteriaER{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + }, + }, + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 307, + RedirectURL: "/abc/sss", + UseIncomingSchemeAndHost: true, + }, + } + policy, version := expectCreatePolicy(t, client, 2, 123, matchRules, "test policy description") + expectReadPolicy(t, client, policy, version, 3) + policy = expectUpdatePolicy(t, client, policy, 321) + version = expectUpdatePolicyVersion(t, client, policy.ID, version, matchRules[:1]) + expectReadPolicy(t, client, policy, version, 2) + expectRemovePolicy(t, client, policy.ID) + + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "1", + matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_create.json", testDir), + description: "test policy description", + }), + }, + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_update.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 321, + version: "1", + matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_update.json", testDir), + description: "test policy description", + }), + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("update only policy v3", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/lifecycle_policy_update" + + client := new(v3.Mock) + matchRules := v3.MatchRules{ + &v3.MatchRuleER{ + Name: "r1", + Type: "erMatchRule", + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 301, + RedirectURL: "/ddd", + MatchURL: "abc.com", + UseIncomingSchemeAndHost: true, + }, + &v3.MatchRuleER{ + Name: "r3", + Type: "erMatchRule", + Matches: []v3.MatchCriteriaER{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + }, + }, + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 307, + RedirectURL: "/abc/sss", + UseIncomingSchemeAndHost: true, + }, + } + policy, version := expectCreatePolicy(t, client, 2, 123, matchRules, "test policy description") + expectReadPolicy(t, client, policy, version, 3) + policy = expectUpdatePolicy(t, client, policy, 321) + expectReadPolicy(t, client, policy, version, 2) + expectRemovePolicy(t, client, policy.ID) + + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "1", + matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules.json", testDir), + description: "test policy description", + }), + }, + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_update.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 321, + version: "1", + matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules.json", testDir), + description: "test policy description", + }), + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("update only version for v3 policy", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/lifecycle_version_update" + + client := new(v3.Mock) + matchRules := v3.MatchRules{ + &v3.MatchRuleER{ + Name: "r1", + Type: "erMatchRule", + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 301, + RedirectURL: "/ddd", + MatchURL: "abc.com", + UseIncomingSchemeAndHost: true, + }, + &v3.MatchRuleER{ + Name: "r3", + Type: "erMatchRule", + Matches: []v3.MatchCriteriaER{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + }, + }, + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 307, + RedirectURL: "/abc/sss", + UseIncomingSchemeAndHost: true, + }, + } + policy, version := expectCreatePolicy(t, client, 2, 123, matchRules, "test policy description") + expectReadPolicy(t, client, policy, version, 3) + version = expectUpdatePolicyVersion(t, client, policy.ID, version, matchRules[:1]) + expectReadPolicy(t, client, policy, version, 2) + expectRemovePolicy(t, client, policy.ID) + + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "1", + matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_create.json", testDir), + description: "test policy description", + }), + }, + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_update.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "1", + matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_update.json", testDir), + description: "test policy description", + }), + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("warnings creating and updating version for v3 policy", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/lifecycle_version_update" + + client := new(v3.Mock) + matchRules := v3.MatchRules{ + &v3.MatchRuleER{ + Name: "r1", + Type: "erMatchRule", + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 301, + RedirectURL: "/ddd", + MatchURL: "abc.com", + UseIncomingSchemeAndHost: true, + }, + &v3.MatchRuleER{ + Name: "r3", + Type: "erMatchRule", + Matches: []v3.MatchCriteriaER{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + }, + }, + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 307, + RedirectURL: "/abc/sss", + UseIncomingSchemeAndHost: true, + }, + } + policy, version := expectCreatePolicy(t, client, 2, 123, matchRules, "test policy description") + expectReadPolicy(t, client, policy, version, 3) + version = expectUpdatePolicyVersion(t, client, policy.ID, version, matchRules[:1]) + expectReadPolicy(t, client, policy, version, 4) + expectRemovePolicy(t, client, policy.ID) + + warningsJSON, err := warningsToJSON(version.MatchRulesWarnings) + require.NoError(t, err) + + checkWarnings := resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("akamai_cloudlets_policy.policy", "warnings", string(warningsJSON)), + resource.TestMatchOutput("policy_output", regexp.MustCompile("test warning")), + ) + + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + Check: checkWarnings, + }, + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_update.tf", testDir)), + Check: checkWarnings, + }, + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_update.tf", testDir)), + Check: checkWarnings, + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("remove match rules from version for v3 policy", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/lifecycle_remove_match_rules" + + client := new(v3.Mock) + matchRules := v3.MatchRules{ + &v3.MatchRuleER{ + Name: "r1", + Type: "erMatchRule", + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 301, + RedirectURL: "/ddd", + MatchURL: "abc.com", + UseIncomingSchemeAndHost: true, + }, + &v3.MatchRuleER{ + Name: "r3", + Type: "erMatchRule", + Matches: []v3.MatchCriteriaER{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + }, + }, + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 307, + RedirectURL: "/abc/sss", + UseIncomingSchemeAndHost: true, + }, + } + policy, version := expectCreatePolicy(t, client, 2, 123, matchRules, "test policy description") + expectReadPolicy(t, client, policy, version, 3) + version = expectUpdatePolicyVersion(t, client, policy.ID, version, v3.MatchRules{}) + expectReadPolicy(t, client, policy, version, 2) + expectRemovePolicy(t, client, policy.ID) + + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "1", + matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules_create.json", testDir), + description: "test policy description", + }), + }, + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_update.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "1", + matchRulesPath: "", + description: "test policy description", + }), + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("create v3 policy without match rules or description", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/create_no_match_rules_no_description" + + client := new(v3.Mock) + policy, version := expectCreatePolicy(t, client, 2, 123, nil, "") + expectReadPolicy(t, client, policy, version, 2) + expectRemovePolicy(t, client, 2) + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "", + matchRulesPath: "", + description: "", + }), + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("create v3 policy without version, update to create new version", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/lifecycle_no_version" + + client := new(v3.Mock) + policy, version := expectCreatePolicy(t, client, 2, 123, nil, "") + expectReadPolicy(t, client, policy, version, 3) + + version = &v3.PolicyVersion{ + Description: tools.StringPtr("test policy description"), + PolicyID: 2, + PolicyVersion: 1, + } + client.On("CreatePolicyVersion", mock.Anything, v3.CreatePolicyVersionRequest{ + CreatePolicyVersion: v3.CreatePolicyVersion{ + Description: tools.StringPtr("test policy description"), + MatchRules: make(v3.MatchRules, 0), + }, + PolicyID: 2, + }).Return(version, nil).Once() + expectReadPolicy(t, client, policy, version, 2) + expectRemovePolicy(t, client, 2) + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "", + matchRulesPath: "", + description: "", + }), + }, + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_update.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "1", + matchRulesPath: "", + description: "test policy description", + }), + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("create V3 policy without match rules with description", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/create_no_match_rules" + + client := new(v3.Mock) + policy, version := expectCreatePolicy(t, client, 2, 123, nil, "test policy description") + expectReadPolicy(t, client, policy, version, 2) + expectRemovePolicy(t, client, 2) + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "1", + matchRulesPath: "", + description: "test policy description", + }), + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("create V3 policy with timeout", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/timeouts" + + matchRules := v3.MatchRules{ + &v3.MatchRuleER{ + Name: "r1", + Type: "erMatchRule", + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 301, + RedirectURL: "/ddd", + MatchURL: "abc.com", + UseIncomingSchemeAndHost: true, + }, + } + + client := new(v3.Mock) + policy, version := expectCreatePolicy(t, client, 2, 123, matchRules, "test policy description") + expectReadPolicy(t, client, policy, version, 2) + expectRemovePolicy(t, client, 2) + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "1", + matchRulesPath: fmt.Sprintf("%s/match_rules/match_rules.json", testDir), + timeouts: "4h", + description: "test policy description", + }), + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("error creating V3 policy", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/lifecycle" + + client := new(v3.Mock) + client.On("CreatePolicy", mock.Anything, v3.CreatePolicyRequest{ + Name: "test_policy", + CloudletType: v3.CloudletTypeER, + GroupID: 123, + }).Return(nil, fmt.Errorf("oops")).Once() + + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + ExpectError: regexp.MustCompile("oops"), + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("error first update v3 policy version", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/lifecycle" + + policy := &v3.Policy{ + ID: 2, + GroupID: 123, + Name: "test_policy", + CloudletType: "ER", + } + + matchRules := v3.MatchRules{ + &v3.MatchRuleER{ + Name: "r1", + Type: "erMatchRule", + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 301, + RedirectURL: "/ddd", + MatchURL: "abc.com", + UseIncomingSchemeAndHost: true, + }, + &v3.MatchRuleER{ + Name: "r3", + Type: "erMatchRule", + Matches: []v3.MatchCriteriaER{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + }, + }, + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 307, + RedirectURL: "/abc/sss", + UseIncomingSchemeAndHost: true, + }, + } + + expectErrorCreatingVersion := func(client *v3.Mock) { + client.On("CreatePolicy", mock.Anything, v3.CreatePolicyRequest{ + Name: "test_policy", + CloudletType: v3.CloudletTypeER, + GroupID: 123, + }).Return(policy, nil) + client.On("CreatePolicyVersion", mock.Anything, v3.CreatePolicyVersionRequest{ + CreatePolicyVersion: v3.CreatePolicyVersion{ + Description: tools.StringPtr("test policy description"), + MatchRules: matchRules, + }, + PolicyID: 2, + }).Return(nil, fmt.Errorf("CreatePolicyVersionError")) + } + + testCases := []struct { + Expectations func(client *v3.Mock) + ExpectedError *regexp.Regexp + }{ + { + Expectations: func(client *v3.Mock) { + expectErrorCreatingVersion(client) + expectReadPolicy(t, client, policy, &v3.PolicyVersion{ + PolicyID: 2, + PolicyVersion: 1, + }, 1) + expectRemovePolicy(t, client, 2) + }, + ExpectedError: regexp.MustCompile("CreatePolicyVersionError"), + }, + { + Expectations: func(client *v3.Mock) { + expectErrorCreatingVersion(client) + expectListPolicyVersions(t, client, policy.ID, v3.ListPolicyVersions{ + PolicyVersions: []v3.ListPolicyVersionsItem{ + {PolicyVersion: 1, PolicyID: policy.ID}, + }}, 1) + client.On("GetPolicy", mock.Anything, v3.GetPolicyRequest{PolicyID: policy.ID}).Return(nil, fmt.Errorf("GetPolicyError")).Once() + expectRemovePolicy(t, client, 2) + }, + ExpectedError: regexp.MustCompile("(?s)GetPolicyError.*CreatePolicyVersionError"), + }, + } + + for i := range testCases { + client := new(v3.Mock) + testCases[i].Expectations(client) + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + ExpectError: testCases[i].ExpectedError, + }, + }, + }) + }) + client.AssertExpectations(t) + } + }) + + t.Run("error fetching V3 policy", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/create_no_match_rules" + + client := new(v3.Mock) + policy, version := expectCreatePolicy(t, client, 2, 123, nil, "test policy description") + expectListPolicyVersions(t, client, policy.ID, v3.ListPolicyVersions{ + PolicyVersions: []v3.ListPolicyVersionsItem{ + {PolicyVersion: version.PolicyVersion, PolicyID: policy.ID}, + }}, 1) + client.On("GetPolicy", mock.Anything, v3.GetPolicyRequest{PolicyID: policy.ID}).Return(nil, fmt.Errorf("oops")).Once() + expectRemovePolicy(t, client, 2) + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + ExpectError: regexp.MustCompile("oops"), + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("error fetching V3 policy version", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/create_no_match_rules" + + client := new(v3.Mock) + policy, version := expectCreatePolicy(t, client, 2, 123, nil, "test policy description") + expectListPolicyVersions(t, client, policy.ID, v3.ListPolicyVersions{ + PolicyVersions: []v3.ListPolicyVersionsItem{ + {PolicyVersion: version.PolicyVersion, PolicyID: policy.ID}, + }}, 1) + client.On("GetPolicy", mock.Anything, v3.GetPolicyRequest{PolicyID: policy.ID}).Return(policy, nil).Once() + client.On("GetPolicyVersion", mock.Anything, v3.GetPolicyVersionRequest{ + PolicyID: policy.ID, + PolicyVersion: version.PolicyVersion, + }).Return(nil, fmt.Errorf("oops")).Once() + expectRemovePolicy(t, client, 2) + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + ExpectError: regexp.MustCompile("oops"), + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("error updating V3 policy", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/lifecycle_policy_update" + + client := new(v3.Mock) + matchRules := v3.MatchRules{ + &v3.MatchRuleER{ + Name: "r1", + Type: "erMatchRule", + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 301, + RedirectURL: "/ddd", + MatchURL: "abc.com", + UseIncomingSchemeAndHost: true, + }, + &v3.MatchRuleER{ + Name: "r3", + Type: "erMatchRule", + Matches: []v3.MatchCriteriaER{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + }, + }, + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 307, + RedirectURL: "/abc/sss", + UseIncomingSchemeAndHost: true, + }, + } + policy, version := expectCreatePolicy(t, client, 2, 123, matchRules, "test policy description") + expectReadPolicy(t, client, policy, version, 3) + client.On("UpdatePolicy", mock.Anything, v3.UpdatePolicyRequest{ + BodyParams: v3.UpdatePolicyBodyParams{ + GroupID: 321, + }, + PolicyID: policy.ID, + }).Return(nil, fmt.Errorf("oops")).Once() + expectRemovePolicy(t, client, policy.ID) + + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + }, + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_update.tf", testDir)), + ExpectError: regexp.MustCompile("oops"), + }, + }, + }) + }) + client.AssertExpectations(t) + }) + + t.Run("error updating version in v3 policy", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3/lifecycle_version_update" + + matchRules := v3.MatchRules{ + &v3.MatchRuleER{ + Name: "r1", + Type: "erMatchRule", + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 301, + RedirectURL: "/ddd", + MatchURL: "abc.com", + UseIncomingSchemeAndHost: true, + }, + &v3.MatchRuleER{ + Name: "r3", + Type: "erMatchRule", + Matches: []v3.MatchCriteriaER{ + { + MatchType: "hostname", + MatchValue: "3333.dom", + MatchOperator: "equals", + CaseSensitive: true, + }, + }, + UseRelativeURL: "copy_scheme_hostname", + StatusCode: 307, + RedirectURL: "/abc/sss", + UseIncomingSchemeAndHost: true, + }, + } + + expectErrorUpdatingVersion := func(client *v3.Mock, expectReadPolicyTimes int) (policy *v3.Policy) { + policy, version := expectCreatePolicy(t, client, 2, 123, matchRules, "test policy description") + expectReadPolicy(t, client, policy, version, expectReadPolicyTimes) + client.On("GetPolicyVersion", mock.Anything, v3.GetPolicyVersionRequest{ + PolicyID: policy.ID, + PolicyVersion: version.PolicyVersion, + }).Return(version, nil).Once() + client.On("UpdatePolicyVersion", mock.Anything, v3.UpdatePolicyVersionRequest{ + UpdatePolicyVersion: v3.UpdatePolicyVersion{ + Description: tools.StringPtr("test policy description"), + MatchRules: matchRules[:1], + }, + PolicyID: policy.ID, + PolicyVersion: version.PolicyVersion, + }).Return(nil, fmt.Errorf("UpdatePolicyVersionError")).Once() + return + } + + testCases := []struct { + Expectations func(client *v3.Mock) + ExpectedError *regexp.Regexp + }{ + { + Expectations: func(client *v3.Mock) { + policy := expectErrorUpdatingVersion(client, 4) + expectRemovePolicy(t, client, policy.ID) + }, + ExpectedError: regexp.MustCompile("UpdatePolicyVersionError"), + }, + { + Expectations: func(client *v3.Mock) { + policy := expectErrorUpdatingVersion(client, 3) + expectListPolicyVersions(t, client, policy.ID, v3.ListPolicyVersions{ + PolicyVersions: []v3.ListPolicyVersionsItem{ + {PolicyVersion: 1, PolicyID: policy.ID}, + }}, 1) + client.On("GetPolicy", mock.Anything, v3.GetPolicyRequest{PolicyID: policy.ID}).Return(nil, fmt.Errorf("GetPolicyError")).Once() + expectRemovePolicy(t, client, policy.ID) + }, + ExpectedError: regexp.MustCompile("(?s)GetPolicyError.*UpdatePolicyVersionError"), + }, + } + + for i := range testCases { + client := new(v3.Mock) + testCases[i].Expectations(client) + useClientV3(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + }, + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_update.tf", testDir)), + ExpectError: testCases[i].ExpectedError, + }, + }, + }) + }) + client.AssertExpectations(t) + } + }) + + t.Run("import policy v3", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3" + clientV2 := new(cloudlets.Mock) + clientV3 := new(v3.Mock) + + policy, version := expectCreatePolicy(t, clientV3, 2, 123, nil, "test policy description") + expectReadPolicy(t, clientV3, policy, version, 3) + expectImportPolicy(t, clientV3, clientV2, policy.ID, "test_policy") + expectRemovePolicy(t, clientV3, policy.ID) + + useClientV2AndV3(clientV2, clientV3, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/create_no_match_rules/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "1", + description: "test policy description", + }), + }, + { + ImportState: true, + ImportStateId: "test_policy", + ResourceName: "akamai_cloudlets_policy.policy", + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"warnings"}, + }, + }, + }) + }) + clientV2.AssertExpectations(t) + clientV3.AssertExpectations(t) + }) + + t.Run("import policy v3 without version", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3" + clientV2 := new(cloudlets.Mock) + clientV3 := new(v3.Mock) + + policy, version := expectCreatePolicy(t, clientV3, 2, 123, nil, "") + expectReadPolicy(t, clientV3, policy, version, 3) + expectImportPolicy(t, clientV3, clientV2, policy.ID, "test_policy") + expectRemovePolicy(t, clientV3, policy.ID) + + useClientV2AndV3(clientV2, clientV3, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/create_no_match_rules_no_description/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "", + description: "", + }), + }, + { + ImportState: true, + ImportStateId: "test_policy", + ResourceName: "akamai_cloudlets_policy.policy", + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"warnings"}, + }, + }, + }) + }) + clientV2.AssertExpectations(t) + clientV3.AssertExpectations(t) + }) + + t.Run("import policy v3 - no policy found", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3" + clientV2 := new(cloudlets.Mock) + clientV3 := new(v3.Mock) + + policy, version := expectCreatePolicy(t, clientV3, 2, 123, nil, "test policy description") + expectReadPolicy(t, clientV3, policy, version, 2) + clientV2.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{ + PageSize: tools.IntPtr(1000), + Offset: 0, + }).Return([]cloudlets.Policy{}, nil).Once() + clientV3.On("ListPolicies", mock.Anything, v3.ListPoliciesRequest{ + Size: 1000, Page: 0, + }).Return(&v3.ListPoliciesResponse{ + Content: []v3.Policy{}, + }, nil).Once() + expectRemovePolicy(t, clientV3, policy.ID) + + useClientV2AndV3(clientV2, clientV3, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/create_no_match_rules/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "1", + description: "test policy description", + }), + }, + { + ImportState: true, + ImportStateId: "test_policy", + ResourceName: "akamai_cloudlets_policy.policy", + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"warnings"}, + ExpectError: regexp.MustCompile("policy 'test_policy' does not exist"), + }, + }, + }) + }) + clientV2.AssertExpectations(t) + clientV3.AssertExpectations(t) + }) + + t.Run("import policy v3 - v2 api error, v3 policy found", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3" + clientV2 := new(cloudlets.Mock) + clientV3 := new(v3.Mock) + + policy, version := expectCreatePolicy(t, clientV3, 2, 123, nil, "test policy description") + expectReadPolicy(t, clientV3, policy, version, 3) + clientV2.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{ + PageSize: tools.IntPtr(1000), + Offset: 0, + }).Return(nil, fmt.Errorf("v2 api error")).Once() + clientV3.On("ListPolicies", mock.Anything, v3.ListPoliciesRequest{ + Size: 1000, Page: 0, + }).Return(&v3.ListPoliciesResponse{ + Content: []v3.Policy{ + { + ID: policy.ID, Name: policy.Name, + }, + }, + }, nil).Once() + expectRemovePolicy(t, clientV3, policy.ID) + + useClientV2AndV3(clientV2, clientV3, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/create_no_match_rules/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "1", + description: "test policy description", + }), + }, + { + ImportState: true, + ImportStateId: "test_policy", + ResourceName: "akamai_cloudlets_policy.policy", + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"warnings"}, + }, + }, + }) + }) + clientV2.AssertExpectations(t) + clientV3.AssertExpectations(t) + }) + + t.Run("import policy v3 - v2 and v3 api error", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3" + clientV2 := new(cloudlets.Mock) + clientV3 := new(v3.Mock) + + policy, version := expectCreatePolicy(t, clientV3, 2, 123, nil, "test policy description") + expectReadPolicy(t, clientV3, policy, version, 2) + clientV2.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{ + PageSize: tools.IntPtr(1000), + Offset: 0, + }).Return(nil, fmt.Errorf("v2 api error")).Once() + clientV3.On("ListPolicies", mock.Anything, v3.ListPoliciesRequest{ + Size: 1000, Page: 0, + }).Return(nil, fmt.Errorf("v3 api error")).Once() + expectRemovePolicy(t, clientV3, policy.ID) + + useClientV2AndV3(clientV2, clientV3, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/create_no_match_rules/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "1", + description: "test policy description", + }), + }, + { + ImportState: true, + ImportStateId: "test_policy", + ResourceName: "akamai_cloudlets_policy.policy", + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"warnings"}, + ExpectError: regexp.MustCompile("could not list V2 policies: v2 api error\ncould not list V3 policies: v3 api error"), + }, + }, + }) + }) + clientV2.AssertExpectations(t) + clientV3.AssertExpectations(t) + }) + + t.Run("import policy v3 - test checkForV3Policy()", func(t *testing.T) { + testDir := "testdata/TestResPolicyV3" + clientV2 := new(cloudlets.Mock) + clientV3 := new(v3.Mock) + policyID := int64(2) + + policy, version := expectCreatePolicy(t, clientV3, policyID, 123, nil, "test policy description") + expectReadPolicy(t, clientV3, policy, version, 3) + // custom import mocks + listPoliciesV2Resp := []cloudlets.Policy{ + { + Name: "other-name", + }, + } + clientV2.On("ListPolicies", mock.Anything, cloudlets.ListPoliciesRequest{ + PageSize: tools.IntPtr(1000), + Offset: 0, + }).Return(listPoliciesV2Resp, nil).Once() + // mock that 1000 policies are returned, desired one not found + clientV3.On("ListPolicies", mock.Anything, v3.ListPoliciesRequest{ + Size: 1000, Page: 0, + }).Return(&v3.ListPoliciesResponse{ + Content: make([]v3.Policy, 1000), + Page: v3.Page{ + Number: 0, + Size: 1000, + TotalElements: 1001, + TotalPages: 2, + }, + }, nil).Once() + // mock that desired policy is on the next page + clientV3.On("ListPolicies", mock.Anything, v3.ListPoliciesRequest{ + Size: 1000, Page: 1, + }).Return(&v3.ListPoliciesResponse{ + Content: []v3.Policy{ + { + Name: "test_policy", + ID: policyID, + }, + }, + Page: v3.Page{ + Number: 1, + Size: 1000, + TotalElements: 1001, + TotalPages: 2, + }, + }, nil).Once() + expectRemovePolicy(t, clientV3, policy.ID) + + useClientV2AndV3(clientV2, clientV3, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/create_no_match_rules/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + groupID: 123, + version: "1", + description: "test policy description", + }), + }, + { + ImportState: true, + ImportStateId: "test_policy", + ResourceName: "akamai_cloudlets_policy.policy", + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"warnings"}, + }, + }, + }) + }) + clientV2.AssertExpectations(t) + clientV3.AssertExpectations(t) + }) +} + +func TestDiffSuppressMatchRules(t *testing.T) { + basePath := "testdata/TestResPolicy/diff_suppress" + tests := map[string]struct { + oldPath, newPath string + expected bool + }{ + "identical JSON": { + oldPath: "rules.json", + newPath: "rules.json", + expected: true, + }, + "different formatting, same content": { + oldPath: "rules.json", + newPath: "different_format.json", + expected: true, + }, + "difference in location and akaRuleId": { + oldPath: "with_location.json", + newPath: "rules.json", + expected: true, + }, + "invalid 'old' json": { + oldPath: "invalid.json", + newPath: "rules.json", + expected: false, + }, + "invalid 'new' json": { + oldPath: "rules.json", + newPath: "invalid.json", + expected: false, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + oldJSON := testutils.LoadFixtureString(t, fmt.Sprintf("%s/%s", basePath, test.oldPath)) + newJSON := testutils.LoadFixtureString(t, fmt.Sprintf("%s/%s", basePath, test.newPath)) + res := diffSuppressMatchRules("", oldJSON, newJSON, nil) + assert.Equal(t, test.expected, res) }) } } diff --git a/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_v2.go b/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_v2.go new file mode 100644 index 000000000..beda0a153 --- /dev/null +++ b/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_v2.go @@ -0,0 +1,209 @@ +package cloudlets + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" + "time" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets" + "github.com/akamai/terraform-provider-akamai/v5/pkg/common/tf" + "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type v2PolicyStrategy struct { + client cloudlets.Cloudlets +} + +var cloudletIDs = map[string]int{ + "ER": 0, + "VP": 1, + "FR": 3, + "IG": 4, + "AP": 5, + "AS": 6, + "CD": 7, + "IV": 8, + "ALB": 9, + "MMB": 10, + "MMA": 11, +} + +func (strategy v2PolicyStrategy) createPolicy(ctx context.Context, cloudletName, cloudletCode string, groupID int64) (int64, error) { + createPolicyReq := cloudlets.CreatePolicyRequest{ + Name: cloudletName, + CloudletID: int64(cloudletIDs[cloudletCode]), + GroupID: groupID, + } + createPolicyResp, err := strategy.client.CreatePolicy(ctx, createPolicyReq) + if err != nil { + return 0, err + } + return createPolicyResp.PolicyID, nil +} + +func (strategy v2PolicyStrategy) updatePolicyVersion(ctx context.Context, d *schema.ResourceData, policyID, version int64, description, matchRulesJSON string, newVersionRequired bool) (error, error) { + matchRules := make(cloudlets.MatchRules, 0) + if matchRulesJSON != "" { + if err := json.Unmarshal([]byte(matchRulesJSON), &matchRules); err != nil { + return fmt.Errorf("unmarshalling match rules JSON: %s", err), nil + } + } + + matchRuleFormat, err := tf.GetStringValue("match_rule_format", d) + if err != nil && !errors.Is(err, tf.ErrNotFound) { + return err, nil + } + + if newVersionRequired { + createVersionRequest := cloudlets.CreatePolicyVersionRequest{ + CreatePolicyVersion: cloudlets.CreatePolicyVersion{ + MatchRuleFormat: cloudlets.MatchRuleFormat(matchRuleFormat), + MatchRules: matchRules, + Description: description, + }, + PolicyID: policyID, + } + createVersionResp, err := strategy.client.CreatePolicyVersion(ctx, createVersionRequest) + if err != nil { + return err, nil + } + if err := d.Set("version", createVersionResp.Version); err != nil { + return fmt.Errorf("%w: %s", tf.ErrValueSet, err.Error()), nil + } + return setWarnings(d, createVersionResp.Warnings), nil + } + + updateVersionRequest := cloudlets.UpdatePolicyVersionRequest{ + UpdatePolicyVersion: cloudlets.UpdatePolicyVersion{ + MatchRuleFormat: cloudlets.MatchRuleFormat(matchRuleFormat), + MatchRules: matchRules, + Description: description, + }, + PolicyID: policyID, + Version: version, + } + + updateVersionResp, err := strategy.client.UpdatePolicyVersion(ctx, updateVersionRequest) + if err != nil { + return nil, err + } + return setWarnings(d, updateVersionResp.Warnings), nil +} + +func (strategy v2PolicyStrategy) updatePolicy(ctx context.Context, policyID, groupID int64, cloudletName string) error { + updatePolicyReq := cloudlets.UpdatePolicyRequest{ + UpdatePolicy: cloudlets.UpdatePolicy{ + Name: cloudletName, + GroupID: groupID, + }, + PolicyID: policyID, + } + _, err := strategy.client.UpdatePolicy(ctx, updatePolicyReq) + return err +} + +func (strategy v2PolicyStrategy) newPolicyVersionIsNeeded(ctx context.Context, policyID, version int64) (bool, error) { + versionResp, err := strategy.client.GetPolicyVersion(ctx, cloudlets.GetPolicyVersionRequest{ + PolicyID: policyID, + Version: version, + OmitRules: true, + }) + if err != nil { + return false, err + } + + return len(versionResp.Activations) > 0, nil +} + +func (strategy v2PolicyStrategy) readPolicy(ctx context.Context, policyID int64, version *int64) (map[string]any, error) { + + policy, err := strategy.client.GetPolicy(ctx, cloudlets.GetPolicyRequest{PolicyID: policyID}) + if err != nil { + return nil, err + } + + attrs := make(map[string]interface{}) + attrs["name"] = policy.Name + attrs["group_id"] = strconv.FormatInt(policy.GroupID, 10) + attrs["cloudlet_code"] = policy.CloudletCode + attrs["cloudlet_id"] = policy.CloudletID + attrs["is_shared"] = false + + if version == nil { + return attrs, nil + } + + policyVersion, err := strategy.client.GetPolicyVersion(ctx, cloudlets.GetPolicyVersionRequest{ + PolicyID: policyID, + Version: *version, + }) + if err != nil { + return nil, err + } + + attrs["description"] = policyVersion.Description + attrs["match_rule_format"] = policyVersion.MatchRuleFormat + var matchRulesJSON []byte + if len(policyVersion.MatchRules) > 0 { + matchRulesJSON, err = json.MarshalIndent(policyVersion.MatchRules, "", " ") + if err != nil { + return nil, err + } + } + attrs["match_rules"] = string(matchRulesJSON) + attrs["version"] = policyVersion.Version + + return attrs, nil + +} + +func (strategy v2PolicyStrategy) deletePolicy(ctx context.Context, policyID int64) error { + policyVersions, err := getAllV2PolicyVersions(ctx, policyID, strategy.client) + if err != nil { + return err + } + for _, ver := range policyVersions { + if err := strategy.client.DeletePolicyVersion(ctx, cloudlets.DeletePolicyVersionRequest{ + PolicyID: policyID, + Version: ver.Version, + }); err != nil { + return err + } + } + + activationPending := true + for activationPending { + select { + case <-time.After(DeletionPolicyPollInterval): + if err = strategy.client.RemovePolicy(ctx, cloudlets.RemovePolicyRequest{PolicyID: policyID}); err != nil { + statusErr := new(cloudlets.Error) + // if error does not contain information about pending activations, return it as it is not expected + if errors.As(err, &statusErr) && !strings.Contains(statusErr.Detail, "Unable to delete policy because an activation for this policy is still pending") { + return fmt.Errorf("remove policy error: %s", err) + } + continue + } + activationPending = false + case <-ctx.Done(): + return fmt.Errorf("retry timeout reached: %s", ctx.Err()) + } + } + return err +} + +func (strategy v2PolicyStrategy) getVersionStrategy(meta meta.Meta) versionStrategy { + return v2VersionStrategy{Client(meta)} +} + +func (strategy v2PolicyStrategy) setPolicyType(d *schema.ResourceData) error { + return d.Set("is_shared", false) +} + +func (strategy v2PolicyStrategy) isFirstVersionCreated() bool { + return true +} diff --git a/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_v3.go b/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_v3.go new file mode 100644 index 000000000..4c2eb72b1 --- /dev/null +++ b/pkg/providers/cloudlets/resource_akamai_cloudlets_policy_v3.go @@ -0,0 +1,255 @@ +package cloudlets + +import ( + "context" + "encoding/json" + "fmt" + "strconv" + "time" + + v3 "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/cloudlets/v3" + "github.com/akamai/terraform-provider-akamai/v5/pkg/common/tf" + "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" + "github.com/akamai/terraform-provider-akamai/v5/pkg/tools" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +type v3PolicyStrategy struct { + client v3.Cloudlets +} + +func (strategy v3PolicyStrategy) createPolicy(ctx context.Context, cloudletName, cloudletCode string, groupID int64) (int64, error) { + createPolicyReq := v3.CreatePolicyRequest{ + Name: cloudletName, + CloudletType: v3.CloudletType(cloudletCode), + GroupID: groupID, + } + createPolicyResp, err := strategy.client.CreatePolicy(ctx, createPolicyReq) + if err != nil { + return 0, err + } + + return createPolicyResp.ID, nil +} + +func (strategy v3PolicyStrategy) updatePolicyVersion(ctx context.Context, d *schema.ResourceData, policyID, version int64, description, matchRulesJSON string, newVersionRequired bool) (error, error) { + matchRules := make(v3.MatchRules, 0) + if matchRulesJSON != "" { + if err := json.Unmarshal([]byte(matchRulesJSON), &matchRules); err != nil { + return fmt.Errorf("unmarshalling match rules JSON: %s", err), nil + } + } + + if newVersionRequired { + createPolicyReq := v3.CreatePolicyVersionRequest{ + CreatePolicyVersion: v3.CreatePolicyVersion{ + MatchRules: matchRules, + Description: tools.StringPtr(description), + }, + PolicyID: policyID, + } + + createPolicyRes, err := strategy.client.CreatePolicyVersion(ctx, createPolicyReq) + if err != nil { + return nil, err + } + if err = d.Set("version", createPolicyRes.PolicyVersion); err != nil { + return fmt.Errorf("%w: %s", tf.ErrValueSet, err.Error()), nil + } + return setWarnings(d, createPolicyRes.MatchRulesWarnings), nil + } + + updatePolicyVersionReq := v3.UpdatePolicyVersionRequest{ + UpdatePolicyVersion: v3.UpdatePolicyVersion{ + MatchRules: matchRules, + Description: tools.StringPtr(description), + }, + PolicyID: policyID, + PolicyVersion: version, + } + updatePolicyRes, err := strategy.client.UpdatePolicyVersion(ctx, updatePolicyVersionReq) + if err != nil { + return nil, err + } + return setWarnings(d, updatePolicyRes.MatchRulesWarnings), nil +} + +func (strategy v3PolicyStrategy) updatePolicy(ctx context.Context, policyID, groupID int64, _ string) error { + updatePolicyReq := v3.UpdatePolicyRequest{ + PolicyID: policyID, + BodyParams: v3.UpdatePolicyBodyParams{ + GroupID: groupID, + }, + } + _, err := strategy.client.UpdatePolicy(ctx, updatePolicyReq) + return err +} + +func (strategy v3PolicyStrategy) newPolicyVersionIsNeeded(ctx context.Context, policyID, version int64) (bool, error) { + policyVersion, err := strategy.client.GetPolicyVersion(ctx, v3.GetPolicyVersionRequest{ + PolicyID: policyID, + PolicyVersion: version, + }) + if err != nil { + return false, err + } + + return policyVersion.Immutable, nil + +} + +func (strategy v3PolicyStrategy) readPolicy(ctx context.Context, policyID int64, version *int64) (map[string]any, error) { + policy, err := strategy.client.GetPolicy(ctx, v3.GetPolicyRequest{PolicyID: policyID}) + if err != nil { + return nil, err + } + + attrs := make(map[string]any) + attrs["name"] = policy.Name + attrs["group_id"] = strconv.FormatInt(policy.GroupID, 10) + attrs["cloudlet_code"] = policy.CloudletType + attrs["is_shared"] = true + + if version == nil { + attrs["description"] = "" + attrs["match_rules"] = "" + return attrs, nil + } + + policyVersion, err := strategy.client.GetPolicyVersion(ctx, v3.GetPolicyVersionRequest{ + PolicyID: policyID, + PolicyVersion: *version, + }) + if err != nil { + return nil, err + } + + attrs["description"] = policyVersion.Description + var matchRulesJSON []byte + if len(policyVersion.MatchRules) > 0 { + matchRulesJSON, err = json.MarshalIndent(policyVersion.MatchRules, "", " ") + if err != nil { + return nil, err + } + } + attrs["match_rules"] = string(matchRulesJSON) + attrs["version"] = policyVersion.PolicyVersion + + return attrs, nil +} + +func (strategy v3PolicyStrategy) deletePolicy(ctx context.Context, policyID int64) error { + err := deactivatePolicyVersions(ctx, policyID, strategy.client) + if err != nil { + return err + } + + err = strategy.client.DeletePolicy(ctx, v3.DeletePolicyRequest{PolicyID: policyID}) + + return err +} + +func (strategy v3PolicyStrategy) getVersionStrategy(meta meta.Meta) versionStrategy { + return v3VersionStrategy{ClientV3(meta)} +} + +func (strategy v3PolicyStrategy) setPolicyType(d *schema.ResourceData) error { + return d.Set("is_shared", true) +} + +func deactivatePolicyVersions(ctx context.Context, policyID int64, client v3.Cloudlets) error { + policy, err := client.GetPolicy(ctx, v3.GetPolicyRequest{ + PolicyID: policyID, + }) + if err != nil { + return err + } + + if policyHasOngoingActivations(policy) { + policy, err = waitForOngoingActivations(ctx, policyID, client) + if err != nil { + return err + } + } + + anyDeactivationTriggered := false + if policy.CurrentActivations.Staging.Effective != nil && policy.CurrentActivations.Staging.Effective.Operation == v3.OperationActivation { + _, err := client.DeactivatePolicy(ctx, v3.DeactivatePolicyRequest{ + PolicyID: policyID, + Network: policy.CurrentActivations.Staging.Effective.Network, + PolicyVersion: policy.CurrentActivations.Staging.Effective.PolicyVersion, + }) + if err != nil { + return err + } + anyDeactivationTriggered = true + } + if policy.CurrentActivations.Production.Effective != nil && policy.CurrentActivations.Production.Effective.Operation == v3.OperationActivation { + _, err := client.DeactivatePolicy(ctx, v3.DeactivatePolicyRequest{ + PolicyID: policyID, + Network: policy.CurrentActivations.Production.Effective.Network, + PolicyVersion: policy.CurrentActivations.Production.Effective.PolicyVersion, + }) + if err != nil { + return err + } + anyDeactivationTriggered = true + } + + if !anyDeactivationTriggered { + return nil + } + + for { + select { + case <-time.After(DeletionPolicyPollInterval): + everythingDeactivated, err := verifyVersionDeactivated(ctx, policyID, client) + if err != nil { + return err + } + if everythingDeactivated { + return nil + } + case <-ctx.Done(): + return fmt.Errorf("retry timeout reached: %s", ctx.Err()) + } + } +} + +func policyHasOngoingActivations(policy *v3.Policy) bool { + return (policy.CurrentActivations.Staging.Latest != nil && policy.CurrentActivations.Staging.Latest.Status == v3.ActivationStatusInProgress) || + (policy.CurrentActivations.Production.Latest != nil && policy.CurrentActivations.Production.Latest.Status == v3.ActivationStatusInProgress) +} + +func waitForOngoingActivations(ctx context.Context, policyID int64, client v3.Cloudlets) (*v3.Policy, error) { + for { + select { + case <-time.After(DeletionPolicyPollInterval): + policy, err := client.GetPolicy(ctx, v3.GetPolicyRequest{PolicyID: policyID}) + if err != nil { + return nil, err + } + if !policyHasOngoingActivations(policy) { + return policy, nil + } + case <-ctx.Done(): + return nil, fmt.Errorf("retry timeout reached: %s", ctx.Err()) + } + } +} + +func verifyVersionDeactivated(ctx context.Context, policyID int64, client v3.Cloudlets) (bool, error) { + policy, err := client.GetPolicy(ctx, v3.GetPolicyRequest{ + PolicyID: policyID, + }) + if err != nil { + return false, err + } + + return (policy.CurrentActivations.Staging.Effective == nil || policy.CurrentActivations.Staging.Effective.Operation == v3.OperationDeactivation) && + (policy.CurrentActivations.Production.Effective == nil || policy.CurrentActivations.Production.Effective.Operation == v3.OperationDeactivation), nil +} + +func (strategy v3PolicyStrategy) isFirstVersionCreated() bool { + return false +} diff --git a/pkg/providers/cloudlets/testdata/TestDataCloudletsPolicyActivation/activation.tf b/pkg/providers/cloudlets/testdata/TestDataCloudletsPolicyActivation/activation.tf new file mode 100644 index 000000000..78486d474 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestDataCloudletsPolicyActivation/activation.tf @@ -0,0 +1,8 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_cloudlets_policy_activation" "test" { + policy_id = 1 + network = "staging" +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestDataCloudletsPolicyActivation/no_network.tf b/pkg/providers/cloudlets/testdata/TestDataCloudletsPolicyActivation/no_network.tf new file mode 100644 index 000000000..7a17c83b2 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestDataCloudletsPolicyActivation/no_network.tf @@ -0,0 +1,7 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_cloudlets_policy_activation" "test" { + policy_id = 1 +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestDataCloudletsPolicyActivation/no_property_id.tf b/pkg/providers/cloudlets/testdata/TestDataCloudletsPolicyActivation/no_property_id.tf new file mode 100644 index 000000000..ddf7a7211 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestDataCloudletsPolicyActivation/no_property_id.tf @@ -0,0 +1,7 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_cloudlets_policy_activation" "test" { + network = "staging" +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestDataCloudletsSharedPolicy/no_policy_id.tf b/pkg/providers/cloudlets/testdata/TestDataCloudletsSharedPolicy/no_policy_id.tf new file mode 100644 index 000000000..fc7481d54 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestDataCloudletsSharedPolicy/no_policy_id.tf @@ -0,0 +1,5 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_cloudlets_shared_policy" "test" {} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestDataCloudletsSharedPolicy/no_version.tf b/pkg/providers/cloudlets/testdata/TestDataCloudletsSharedPolicy/no_version.tf new file mode 100644 index 000000000..1ddb4c757 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestDataCloudletsSharedPolicy/no_version.tf @@ -0,0 +1,7 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_cloudlets_shared_policy" "test" { + policy_id = 1 +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestDataCloudletsSharedPolicy/with_version.tf b/pkg/providers/cloudlets/testdata/TestDataCloudletsSharedPolicy/with_version.tf new file mode 100644 index 000000000..62c4009c2 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestDataCloudletsSharedPolicy/with_version.tf @@ -0,0 +1,8 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_cloudlets_shared_policy" "test" { + policy_id = 1 + version = 2 +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResCloudletsPolicyV3Activation/policy_activation_update_version2.tf b/pkg/providers/cloudlets/testdata/TestResCloudletsPolicyV3Activation/policy_activation_update_version2.tf new file mode 100644 index 000000000..3bb836c5d --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResCloudletsPolicyV3Activation/policy_activation_update_version2.tf @@ -0,0 +1,13 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy_activation" "test" { + policy_id = 1234 + network = "staging" + version = 2 +} + +output "status" { + value = akamai_cloudlets_policy_activation.test.status +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf b/pkg/providers/cloudlets/testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf new file mode 100644 index 000000000..9e1dbff76 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1.tf @@ -0,0 +1,16 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy_activation" "test" { + policy_id = 1234 + network = "staging" + version = 1 + timeouts { + default = "2h" + } +} + +output "status" { + value = akamai_cloudlets_policy_activation.test.status +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1_prod.tf b/pkg/providers/cloudlets/testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1_prod.tf new file mode 100644 index 000000000..85df6c194 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1_prod.tf @@ -0,0 +1,13 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy_activation" "test" { + policy_id = 1234 + network = "prod" + version = 1 +} + +output "status" { + value = akamai_cloudlets_policy_activation.test.status +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1_production.tf b/pkg/providers/cloudlets/testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1_production.tf new file mode 100644 index 000000000..472145dd0 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResCloudletsPolicyV3Activation/policy_activation_version1_production.tf @@ -0,0 +1,13 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy_activation" "test" { + policy_id = 1234 + network = "production" + version = 1 +} + +output "status" { + value = akamai_cloudlets_policy_activation.test.status +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResCloudletsPolicyV3Activation/policy_activation_version_invalid.tf b/pkg/providers/cloudlets/testdata/TestResCloudletsPolicyV3Activation/policy_activation_version_invalid.tf new file mode 100644 index 000000000..1aae5270e --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResCloudletsPolicyV3Activation/policy_activation_version_invalid.tf @@ -0,0 +1,17 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy_activation" "test" { + policy_id = 1234 + network = "staging" + version = 1 + associated_properties = ["prp_0", "prp_1"] + timeouts { + default = "2h" + } +} + +output "status" { + value = akamai_cloudlets_policy_activation.test.status +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResCloudletsPolicyV3Activation/shared_policy_activation_version1.tf b/pkg/providers/cloudlets/testdata/TestResCloudletsPolicyV3Activation/shared_policy_activation_version1.tf new file mode 100644 index 000000000..9e1dbff76 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResCloudletsPolicyV3Activation/shared_policy_activation_version1.tf @@ -0,0 +1,16 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy_activation" "test" { + policy_id = 1234 + network = "staging" + version = 1 + timeouts { + default = "2h" + } +} + +output "status" { + value = akamai_cloudlets_policy_activation.test.status +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicy/create_no_match_rules_no_description/policy_create.tf b/pkg/providers/cloudlets/testdata/TestResPolicy/create_no_match_rules_no_description/policy_create.tf new file mode 100644 index 000000000..3fc76ee11 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicy/create_no_match_rules_no_description/policy_create.tf @@ -0,0 +1,9 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy" "policy" { + name = "test_policy" + cloudlet_code = "ER" + group_id = "grp_123" +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicy/import_no_version/policy_create.tf b/pkg/providers/cloudlets/testdata/TestResPolicy/import_no_version/policy_create.tf new file mode 100644 index 000000000..3fc76ee11 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicy/import_no_version/policy_create.tf @@ -0,0 +1,9 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy" "policy" { + name = "test_policy" + cloudlet_code = "ER" + group_id = "grp_123" +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicy/lifecycle_with_drift/match_rules/match_rules_create.json b/pkg/providers/cloudlets/testdata/TestResPolicy/lifecycle_with_drift/match_rules/match_rules_create.json new file mode 100644 index 000000000..1c39bf37e --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicy/lifecycle_with_drift/match_rules/match_rules_create.json @@ -0,0 +1,30 @@ +[ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + }, + { + "name": "r3", + "type": "erMatchRule", + "matches": [ + { + "matchType": "hostname", + "matchValue": "3333.dom", + "matchOperator": "equals", + "caseSensitive": true, + "negate": false + } + ], + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 307, + "redirectURL": "/abc/sss", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicy/lifecycle_with_drift/policy_create.tf b/pkg/providers/cloudlets/testdata/TestResPolicy/lifecycle_with_drift/policy_create.tf new file mode 100644 index 000000000..9f9118414 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicy/lifecycle_with_drift/policy_create.tf @@ -0,0 +1,42 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy" "policy" { + name = "test_policy" + cloudlet_code = "ER" + description = "test policy description" + group_id = "grp_123" + match_rules = <<-EOF + [ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + }, + { + "name": "r3", + "type": "erMatchRule", + "matches": [ + { + "matchType": "hostname", + "matchValue": "3333.dom", + "matchOperator": "equals", + "caseSensitive": true, + "negate": false + } + ], + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 307, + "redirectURL": "/abc/sss", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] +EOF +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicy/lifecycle_with_drift/warnings.json b/pkg/providers/cloudlets/testdata/TestResPolicy/lifecycle_with_drift/warnings.json new file mode 100644 index 000000000..c30de3c81 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicy/lifecycle_with_drift/warnings.json @@ -0,0 +1,8 @@ +[ + { + "detail": "test warning details", + "jsonPointer": "/matchRules/1/matches/0", + "title": "test warning", + "type": "test type" + } +] \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/create_no_match_rules/policy_create.tf b/pkg/providers/cloudlets/testdata/TestResPolicyV3/create_no_match_rules/policy_create.tf new file mode 100644 index 000000000..137618a94 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/create_no_match_rules/policy_create.tf @@ -0,0 +1,11 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy" "policy" { + name = "test_policy" + cloudlet_code = "ER" + description = "test policy description" + group_id = "grp_123" + is_shared = true +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/create_no_match_rules_no_description/policy_create.tf b/pkg/providers/cloudlets/testdata/TestResPolicyV3/create_no_match_rules_no_description/policy_create.tf new file mode 100644 index 000000000..571570215 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/create_no_match_rules_no_description/policy_create.tf @@ -0,0 +1,10 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy" "policy" { + name = "test_policy" + cloudlet_code = "ER" + group_id = "grp_123" + is_shared = true +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle/match_rules/match_rules_create.json b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle/match_rules/match_rules_create.json new file mode 100644 index 000000000..1c39bf37e --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle/match_rules/match_rules_create.json @@ -0,0 +1,30 @@ +[ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + }, + { + "name": "r3", + "type": "erMatchRule", + "matches": [ + { + "matchType": "hostname", + "matchValue": "3333.dom", + "matchOperator": "equals", + "caseSensitive": true, + "negate": false + } + ], + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 307, + "redirectURL": "/abc/sss", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle/match_rules/match_rules_update.json b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle/match_rules/match_rules_update.json new file mode 100644 index 000000000..c1c058b7b --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle/match_rules/match_rules_update.json @@ -0,0 +1,12 @@ +[ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle/policy_create.tf b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle/policy_create.tf new file mode 100644 index 000000000..7838b5f20 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle/policy_create.tf @@ -0,0 +1,43 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy" "policy" { + name = "test_policy" + cloudlet_code = "ER" + description = "test policy description" + group_id = "grp_123" + is_shared = true + match_rules = <<-EOF + [ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + }, + { + "name": "r3", + "type": "erMatchRule", + "matches": [ + { + "matchType": "hostname", + "matchValue": "3333.dom", + "matchOperator": "equals", + "caseSensitive": true, + "negate": false + } + ], + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 307, + "redirectURL": "/abc/sss", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] +EOF +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle/policy_update.tf b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle/policy_update.tf new file mode 100644 index 000000000..e2f6a961d --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle/policy_update.tf @@ -0,0 +1,25 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy" "policy" { + name = "test_policy" + cloudlet_code = "ER" + description = "test policy description" + group_id = "grp_321" + is_shared = true + match_rules = <<-EOF + [ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] +EOF +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_no_version/policy_create.tf b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_no_version/policy_create.tf new file mode 100644 index 000000000..571570215 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_no_version/policy_create.tf @@ -0,0 +1,10 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy" "policy" { + name = "test_policy" + cloudlet_code = "ER" + group_id = "grp_123" + is_shared = true +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_no_version/policy_update.tf b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_no_version/policy_update.tf new file mode 100644 index 000000000..137618a94 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_no_version/policy_update.tf @@ -0,0 +1,11 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy" "policy" { + name = "test_policy" + cloudlet_code = "ER" + description = "test policy description" + group_id = "grp_123" + is_shared = true +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_policy_update/match_rules/match_rules.json b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_policy_update/match_rules/match_rules.json new file mode 100644 index 000000000..1c39bf37e --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_policy_update/match_rules/match_rules.json @@ -0,0 +1,30 @@ +[ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + }, + { + "name": "r3", + "type": "erMatchRule", + "matches": [ + { + "matchType": "hostname", + "matchValue": "3333.dom", + "matchOperator": "equals", + "caseSensitive": true, + "negate": false + } + ], + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 307, + "redirectURL": "/abc/sss", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_policy_update/policy_create.tf b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_policy_update/policy_create.tf new file mode 100644 index 000000000..7838b5f20 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_policy_update/policy_create.tf @@ -0,0 +1,43 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy" "policy" { + name = "test_policy" + cloudlet_code = "ER" + description = "test policy description" + group_id = "grp_123" + is_shared = true + match_rules = <<-EOF + [ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + }, + { + "name": "r3", + "type": "erMatchRule", + "matches": [ + { + "matchType": "hostname", + "matchValue": "3333.dom", + "matchOperator": "equals", + "caseSensitive": true, + "negate": false + } + ], + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 307, + "redirectURL": "/abc/sss", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] +EOF +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_policy_update/policy_update.tf b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_policy_update/policy_update.tf new file mode 100644 index 000000000..dc150314d --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_policy_update/policy_update.tf @@ -0,0 +1,43 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy" "policy" { + name = "test_policy" + cloudlet_code = "ER" + description = "test policy description" + group_id = "grp_321" + is_shared = true + match_rules = <<-EOF + [ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + }, + { + "name": "r3", + "type": "erMatchRule", + "matches": [ + { + "matchType": "hostname", + "matchValue": "3333.dom", + "matchOperator": "equals", + "caseSensitive": true, + "negate": false + } + ], + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 307, + "redirectURL": "/abc/sss", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] +EOF +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_remove_match_rules/match_rules/match_rules_create.json b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_remove_match_rules/match_rules/match_rules_create.json new file mode 100644 index 000000000..1c39bf37e --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_remove_match_rules/match_rules/match_rules_create.json @@ -0,0 +1,30 @@ +[ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + }, + { + "name": "r3", + "type": "erMatchRule", + "matches": [ + { + "matchType": "hostname", + "matchValue": "3333.dom", + "matchOperator": "equals", + "caseSensitive": true, + "negate": false + } + ], + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 307, + "redirectURL": "/abc/sss", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_remove_match_rules/policy_create.tf b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_remove_match_rules/policy_create.tf new file mode 100644 index 000000000..7838b5f20 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_remove_match_rules/policy_create.tf @@ -0,0 +1,43 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy" "policy" { + name = "test_policy" + cloudlet_code = "ER" + description = "test policy description" + group_id = "grp_123" + is_shared = true + match_rules = <<-EOF + [ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + }, + { + "name": "r3", + "type": "erMatchRule", + "matches": [ + { + "matchType": "hostname", + "matchValue": "3333.dom", + "matchOperator": "equals", + "caseSensitive": true, + "negate": false + } + ], + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 307, + "redirectURL": "/abc/sss", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] +EOF +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_remove_match_rules/policy_update.tf b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_remove_match_rules/policy_update.tf new file mode 100644 index 000000000..137618a94 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_remove_match_rules/policy_update.tf @@ -0,0 +1,11 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy" "policy" { + name = "test_policy" + cloudlet_code = "ER" + description = "test policy description" + group_id = "grp_123" + is_shared = true +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_version_update/match_rules/match_rules_create.json b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_version_update/match_rules/match_rules_create.json new file mode 100644 index 000000000..1c39bf37e --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_version_update/match_rules/match_rules_create.json @@ -0,0 +1,30 @@ +[ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + }, + { + "name": "r3", + "type": "erMatchRule", + "matches": [ + { + "matchType": "hostname", + "matchValue": "3333.dom", + "matchOperator": "equals", + "caseSensitive": true, + "negate": false + } + ], + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 307, + "redirectURL": "/abc/sss", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_version_update/match_rules/match_rules_update.json b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_version_update/match_rules/match_rules_update.json new file mode 100644 index 000000000..c1c058b7b --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_version_update/match_rules/match_rules_update.json @@ -0,0 +1,12 @@ +[ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_version_update/policy_create.tf b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_version_update/policy_create.tf new file mode 100644 index 000000000..6d675688a --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_version_update/policy_create.tf @@ -0,0 +1,47 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy" "policy" { + name = "test_policy" + cloudlet_code = "ER" + description = "test policy description" + group_id = "grp_123" + is_shared = true + match_rules = <<-EOF + [ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + }, + { + "name": "r3", + "type": "erMatchRule", + "matches": [ + { + "matchType": "hostname", + "matchValue": "3333.dom", + "matchOperator": "equals", + "caseSensitive": true, + "negate": false + } + ], + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 307, + "redirectURL": "/abc/sss", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] +EOF +} + +output "policy_output" { + value = akamai_cloudlets_policy.policy.warnings +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_version_update/policy_update.tf b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_version_update/policy_update.tf new file mode 100644 index 000000000..2639749b6 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_version_update/policy_update.tf @@ -0,0 +1,29 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy" "policy" { + name = "test_policy" + cloudlet_code = "ER" + description = "test policy description" + group_id = "grp_123" + is_shared = true + match_rules = <<-EOF + [ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] +EOF +} + +output "policy_output" { + value = akamai_cloudlets_policy.policy.warnings +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_with_drift/match_rules/match_rules_create.json b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_with_drift/match_rules/match_rules_create.json new file mode 100644 index 000000000..1c39bf37e --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_with_drift/match_rules/match_rules_create.json @@ -0,0 +1,30 @@ +[ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + }, + { + "name": "r3", + "type": "erMatchRule", + "matches": [ + { + "matchType": "hostname", + "matchValue": "3333.dom", + "matchOperator": "equals", + "caseSensitive": true, + "negate": false + } + ], + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 307, + "redirectURL": "/abc/sss", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_with_drift/policy_create.tf b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_with_drift/policy_create.tf new file mode 100644 index 000000000..7838b5f20 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/lifecycle_with_drift/policy_create.tf @@ -0,0 +1,43 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy" "policy" { + name = "test_policy" + cloudlet_code = "ER" + description = "test policy description" + group_id = "grp_123" + is_shared = true + match_rules = <<-EOF + [ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + }, + { + "name": "r3", + "type": "erMatchRule", + "matches": [ + { + "matchType": "hostname", + "matchValue": "3333.dom", + "matchOperator": "equals", + "caseSensitive": true, + "negate": false + } + ], + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 307, + "redirectURL": "/abc/sss", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] +EOF +} \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/timeouts/match_rules/match_rules.json b/pkg/providers/cloudlets/testdata/TestResPolicyV3/timeouts/match_rules/match_rules.json new file mode 100644 index 000000000..c1c058b7b --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/timeouts/match_rules/match_rules.json @@ -0,0 +1,12 @@ +[ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] \ No newline at end of file diff --git a/pkg/providers/cloudlets/testdata/TestResPolicyV3/timeouts/policy_create.tf b/pkg/providers/cloudlets/testdata/TestResPolicyV3/timeouts/policy_create.tf new file mode 100644 index 000000000..fa544efb5 --- /dev/null +++ b/pkg/providers/cloudlets/testdata/TestResPolicyV3/timeouts/policy_create.tf @@ -0,0 +1,28 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_cloudlets_policy" "policy" { + name = "test_policy" + cloudlet_code = "ER" + description = "test policy description" + group_id = "grp_123" + is_shared = true + match_rules = <<-EOF + [ + { + "name": "r1", + "type": "erMatchRule", + "useRelativeUrl": "copy_scheme_hostname", + "statusCode": 301, + "redirectURL": "/ddd", + "matchURL": "abc.com", + "useIncomingQueryString": false, + "useIncomingSchemeAndHost": true + } +] +EOF + timeouts { + default = "4h" + } +} \ No newline at end of file diff --git a/pkg/providers/cps/enrollments.go b/pkg/providers/cps/enrollments.go index d5c76914f..cb80dad59 100644 --- a/pkg/providers/cps/enrollments.go +++ b/pkg/providers/cps/enrollments.go @@ -244,7 +244,7 @@ var ( }, "state": { Type: schema.TypeString, - Required: true, + Optional: true, Description: "State or province of organization location", }, }, diff --git a/pkg/providers/dns/internal/txtrecord/jparse.go b/pkg/providers/dns/internal/txtrecord/jparse.go index de2b28b6c..d5aaa6612 100644 --- a/pkg/providers/dns/internal/txtrecord/jparse.go +++ b/pkg/providers/dns/internal/txtrecord/jparse.go @@ -393,9 +393,8 @@ func (t *tokenizer) get(wantWhitespace bool, wantComment bool) (*token, error) { return nil, errors.New("EOF in quoted string") } else if t.sb.Len() == 0 { return t.setCurrentToken(EOF, nil), nil - } else { - return t.setCurrentToken(tokenType, t.sb), nil } + return t.setCurrentToken(tokenType, t.sb), nil } if t.sb.Len() == 0 && tokenType != QUOTEDSTRING { if c == '(' { @@ -455,15 +454,12 @@ func (t *tokenizer) get(wantWhitespace bool, wantComment bool) (*token, error) { } t.sb.Reset() continue - } else { - return t.setCurrentToken(EOL, nil), nil } - } else { - return nil, errors.New("Illegal state") + return t.setCurrentToken(EOL, nil), nil } - } else { - t.ungetChar(c) + return nil, errors.New("Illegal state") } + t.ungetChar(c) break } else if c == '\\' { c, _ = t.getChar() diff --git a/pkg/providers/dns/resource_akamai_dns_zone.go b/pkg/providers/dns/resource_akamai_dns_zone.go index 18d0e9a6a..9722c7df2 100644 --- a/pkg/providers/dns/resource_akamai_dns_zone.go +++ b/pkg/providers/dns/resource_akamai_dns_zone.go @@ -6,6 +6,7 @@ import ( "fmt" "net/http" "os" + "strconv" "strings" "time" @@ -152,12 +153,31 @@ func resourceDNSv2ZoneCreate(ctx context.Context, d *schema.ResourceData, m inte if err != nil { return diag.FromErr(err) } - groupStr, err := tf.GetStringValue("group", d) + + group, err := tf.GetStringValue("group", d) if err != nil { - return diag.FromErr(err) + if errors.Is(err, tf.ErrNotFound) { + groupList, err := inst.Client(meta).ListGroups(ctx, dns.ListGroupRequest{}) + if err != nil { + return diag.FromErr(err) + } + if len(groupList.Groups) == 0 { + return diag.Errorf("no group found. Please provide the group.") + } + if len(groupList.Groups) == 1 { + group = strconv.Itoa(groupList.Groups[0].GroupID) + logger.Warnf("Please modify configuration and provide group identifier. It will be required in the future version of the resource.") + } + if len(groupList.Groups) > 1 { + return diag.Errorf("group is a required field when there is more than one group present.") + } + } else { + return diag.FromErr(err) + } } + contract := strings.TrimPrefix(contractStr, "ctr_") - group := strings.TrimPrefix(groupStr, "grp_") + group = strings.TrimPrefix(group, "grp_") zoneQueryString := dns.ZoneQueryString{Contract: contract, Group: group} zoneCreate := &dns.ZoneCreate{Zone: hostname, Type: zoneType} if err := populateDNSv2ZoneObject(d, zoneCreate, logger); err != nil { diff --git a/pkg/providers/dns/resource_akamai_dns_zone_test.go b/pkg/providers/dns/resource_akamai_dns_zone_test.go index c779309c4..d8b685dbc 100644 --- a/pkg/providers/dns/resource_akamai_dns_zone_test.go +++ b/pkg/providers/dns/resource_akamai_dns_zone_test.go @@ -3,6 +3,7 @@ package dns import ( "net/http" "os" + "regexp" "testing" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/dns" @@ -23,19 +24,185 @@ func TestResDnsZone(t *testing.T) { } recordsetsResp := &dns.RecordSetResponse{Recordsets: make([]dns.Recordset, 2, 2)} + t.Run("when group is not provided and there is no group for the user ", func(t *testing.T) { + client := &dns.Mock{} + + client.On("ListGroups", + mock.Anything, + mock.AnythingOfType("dns.ListGroupRequest"), + ).Return(&dns.ListGroupResponse{}, nil) + + // work around to skip Delete which fails intentionally + err := os.Setenv("DNS_ZONE_SKIP_DELETE", "") + require.NoError(t, err) + defer func() { + err = os.Unsetenv("DNS_ZONE_SKIP_DELETE") + require.NoError(t, err) + }() + useClient(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "testdata/TestResDnsZone/create_without_group.tf"), + ExpectError: regexp.MustCompile("no group found. Please provide the group."), + }, + }, + }) + }) + + client.AssertExpectations(t) + }) + + // This test performs a full life-cycle (CRUD) test + t.Run("lifecycle test when group is not found and no. of group is 1", func(t *testing.T) { + client := &dns.Mock{} + groupListResponse := &dns.ListGroupResponse{ + Groups: []dns.Group{ + { + GroupID: 1, + GroupName: "name", + ContractIDs: []string{ + "1", "2", + }, + Permissions: []string{ + "DELETE", "READ", "WRITE", "ADD", + }, + }, + }, + } + + client.On("ListGroups", + mock.Anything, + mock.AnythingOfType("dns.ListGroupRequest"), + ).Return(groupListResponse, nil) + + getCall := client.On("GetZone", + mock.Anything, + zone.Zone, + ).Return(nil, &dns.Error{ + StatusCode: http.StatusNotFound, + }) + + client.On("CreateZone", + mock.Anything, + mock.AnythingOfType("*dns.ZoneCreate"), + mock.AnythingOfType("dns.ZoneQueryString"), + true, + ).Return(nil).Run(func(args mock.Arguments) { + getCall.ReturnArguments = mock.Arguments{zone, nil} + }) + + client.On("SaveChangelist", + mock.Anything, + mock.AnythingOfType("*dns.ZoneCreate"), + ).Return(nil) + + client.On("SubmitChangelist", + mock.Anything, + mock.AnythingOfType("*dns.ZoneCreate"), + ).Return(nil) + + client.On("GetRecordsets", + mock.Anything, + zone.Zone, + mock.AnythingOfType("[]dns.RecordsetQueryArgs"), + ).Return(recordsetsResp, nil) + + dataSourceName := "akamai_dns_zone.test_without_group" + + // work around to skip Delete which fails intentionally + err := os.Setenv("DNS_ZONE_SKIP_DELETE", "") + require.NoError(t, err) + defer func() { + err = os.Unsetenv("DNS_ZONE_SKIP_DELETE") + require.NoError(t, err) + }() + useClient(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "testdata/TestResDnsZone/create_without_group.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(dataSourceName, "zone", "primaryexampleterraform.io"), + resource.TestCheckResourceAttr(dataSourceName, "contract", "ctr1"), + ), + }, + }, + }) + }) + + client.AssertExpectations(t) + }) + + t.Run("when group is not provided and no. of group is more than 1 for the user ", func(t *testing.T) { + client := &dns.Mock{} + groupListResponse := &dns.ListGroupResponse{ + Groups: []dns.Group{ + { + GroupID: 1, + GroupName: "name", + ContractIDs: []string{ + "1", "2", + }, + Permissions: []string{ + "DELETE", "READ", "WRITE", "ADD", + }, + }, + { + GroupID: 2, + GroupName: "name", + ContractIDs: []string{ + "1", "2", + }, + Permissions: []string{ + "DELETE", "READ", "WRITE", "ADD", + }, + }, + }, + } + + client.On("ListGroups", + mock.Anything, + mock.AnythingOfType("dns.ListGroupRequest"), + ).Return(groupListResponse, nil) + + // work around to skip Delete which fails intentionally + err := os.Setenv("DNS_ZONE_SKIP_DELETE", "") + require.NoError(t, err) + defer func() { + err = os.Unsetenv("DNS_ZONE_SKIP_DELETE") + require.NoError(t, err) + }() + useClient(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "testdata/TestResDnsZone/create_without_group.tf"), + ExpectError: regexp.MustCompile("group is a required field when there is more than one group present."), + }, + }, + }) + }) + + client.AssertExpectations(t) + }) + // This test performs a full life-cycle (CRUD) test - t.Run("lifecycle test", func(t *testing.T) { + t.Run("lifecycle test with group", func(t *testing.T) { client := &dns.Mock{} getCall := client.On("GetZone", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, zone.Zone, ).Return(nil, &dns.Error{ StatusCode: http.StatusNotFound, }) client.On("CreateZone", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, mock.AnythingOfType("*dns.ZoneCreate"), mock.AnythingOfType("dns.ZoneQueryString"), true, @@ -44,7 +211,7 @@ func TestResDnsZone(t *testing.T) { }) client.On("UpdateZone", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, mock.AnythingOfType("*dns.ZoneCreate"), mock.AnythingOfType("dns.ZoneQueryString"), ).Return(nil).Run(func(args mock.Arguments) { @@ -52,17 +219,17 @@ func TestResDnsZone(t *testing.T) { }) client.On("SaveChangelist", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, mock.AnythingOfType("*dns.ZoneCreate"), ).Return(nil) client.On("SubmitChangelist", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, mock.AnythingOfType("*dns.ZoneCreate"), ).Return(nil) client.On("GetRecordsets", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, zone.Zone, mock.AnythingOfType("[]dns.RecordsetQueryArgs"), ).Return(recordsetsResp, nil) @@ -84,6 +251,9 @@ func TestResDnsZone(t *testing.T) { Config: testutils.LoadFixtureString(t, "testdata/TestResDnsZone/create_primary.tf"), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(dataSourceName, "zone", "primaryexampleterraform.io"), + resource.TestCheckResourceAttr(dataSourceName, "contract", "ctr1"), + resource.TestCheckResourceAttr(dataSourceName, "comment", "This is a test primary zone"), + resource.TestCheckResourceAttr(dataSourceName, "group", "grp1"), ), }, { diff --git a/pkg/providers/dns/testdata/TestResDnsZone/create_secondary.tf b/pkg/providers/dns/testdata/TestResDnsZone/create_secondary.tf deleted file mode 100644 index 3c066fc7c..000000000 --- a/pkg/providers/dns/testdata/TestResDnsZone/create_secondary.tf +++ /dev/null @@ -1,12 +0,0 @@ -provider "akamai" { - edgerc = "../../test/edgerc" -} - -resource "akamai_dns_zone" "test_secondary_zone" { - contract = "ctr1" - zone = "secondaryexampleterraform.io" - masters = ["1.2.3.4", "1.2.3.5"] - type = "secondary" - comment = "This is a secondary test zone" - sign_and_serve = false -} \ No newline at end of file diff --git a/pkg/providers/dns/testdata/TestResDnsZone/create_without_group.tf b/pkg/providers/dns/testdata/TestResDnsZone/create_without_group.tf new file mode 100644 index 000000000..b66d9fadb --- /dev/null +++ b/pkg/providers/dns/testdata/TestResDnsZone/create_without_group.tf @@ -0,0 +1,11 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_dns_zone" "test_without_group" { + contract = "ctr1" + zone = "primaryexampleterraform.io" + type = "primary" + comment = "This is a test primary zone" + sign_and_serve = false +} \ No newline at end of file diff --git a/pkg/providers/edgeworkers/resource_akamai_edgeworkers_activation.go b/pkg/providers/edgeworkers/resource_akamai_edgeworkers_activation.go index 855fc3a29..740c511b3 100644 --- a/pkg/providers/edgeworkers/resource_akamai_edgeworkers_activation.go +++ b/pkg/providers/edgeworkers/resource_akamai_edgeworkers_activation.go @@ -67,6 +67,12 @@ func resourceEdgeworkersActivationSchema() map[string]*schema.Schema { Computed: true, Description: "A unique identifier of the activation", }, + "note": { + Type: schema.TypeString, + Optional: true, + Description: "Assigns a log message to the activation request", + DiffSuppressFunc: suppressNoteFieldForEdgeWorkersActivation, + }, "timeouts": { Type: schema.TypeList, Optional: true, @@ -154,6 +160,10 @@ func resourceEdgeworkersActivationRead(ctx context.Context, rd *schema.ResourceD if err := rd.Set("activation_id", activation.ActivationID); err != nil { return diag.Errorf("%v: %s", tf.ErrValueSet, err.Error()) } + + if err := rd.Set("note", activation.Note); err != nil { + return diag.Errorf("%v: %s", tf.ErrValueSet, err.Error()) + } return nil } @@ -196,11 +206,17 @@ func resourceEdgeworkersActivationDelete(ctx context.Context, rd *schema.Resourc return diag.FromErr(err) } + note, err := tf.GetStringValue("note", rd) + if err != nil && !errors.Is(err, tf.ErrNotFound) { + return diag.FromErr(err) + } + deactivation, err := client.DeactivateVersion(ctx, edgeworkers.DeactivateVersionRequest{ EdgeWorkerID: edgeworkerID, DeactivateVersion: edgeworkers.DeactivateVersion{ Version: version, Network: edgeworkers.ActivationNetwork(network), + Note: note, }, }) if err != nil { @@ -306,11 +322,17 @@ func upsertActivation(ctx context.Context, rd *schema.ResourceData, m interface{ return resourceEdgeworkersActivationRead(ctx, rd, m) } + note, err := tf.GetStringValue("note", rd) + if err != nil && !errors.Is(err, tf.ErrNotFound) { + return diag.FromErr(err) + } + activation, err := client.ActivateVersion(ctx, edgeworkers.ActivateVersionRequest{ EdgeWorkerID: edgeworkerID, ActivateVersion: edgeworkers.ActivateVersion{ Network: edgeworkers.ActivationNetwork(network), Version: version, + Note: note, }, }) @@ -574,3 +596,10 @@ func checkEdgeworkerExistsOnDiff(ctx context.Context, rd *schema.ResourceDiff, m return fmt.Errorf("%w: edgeworker with id=%d was not found", ErrEdgeworkerActivation, edgeworkerID) } + +func suppressNoteFieldForEdgeWorkersActivation(_, oldValue, newValue string, d *schema.ResourceData) bool { + if oldValue != newValue && d.HasChanges("version", "network") { + return false + } + return true +} diff --git a/pkg/providers/edgeworkers/resource_akamai_edgeworkers_activation_test.go b/pkg/providers/edgeworkers/resource_akamai_edgeworkers_activation_test.go index 3074acba2..360e10e17 100644 --- a/pkg/providers/edgeworkers/resource_akamai_edgeworkers_activation_test.go +++ b/pkg/providers/edgeworkers/resource_akamai_edgeworkers_activation_test.go @@ -26,6 +26,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { net := edgeworkers.ActivationNetworkStaging version := "test" activationID := 1 + note := "note for edgeworkers activation" // version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -33,15 +34,15 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Once() // create - expectFullActivation(m, edgeworkerID, activationID, net, version) + expectFullActivation(m, edgeworkerID, activationID, net, version, note) // read expectFullRead(m, edgeworkerID, version, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 2) // test cleanup - destroy - expectFullDeactivation(m, edgeworkerID, 1, net, version) + expectFullDeactivation(m, edgeworkerID, 1, net, version, note) }, steps: []resource.TestStep{ { @@ -50,6 +51,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "timeouts.#", "0"), ), }, @@ -60,6 +62,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { net := edgeworkers.ActivationNetworkStaging version := "test" activationID := 1 + note := "note for edgeworkers activation" // version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -67,15 +70,15 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Once() // create - expectFullActivation(m, edgeworkerID, activationID, net, version) + expectFullActivation(m, edgeworkerID, activationID, net, version, note) // read expectFullRead(m, edgeworkerID, version, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 2) // test cleanup - destroy - expectFullDeactivation(m, edgeworkerID, 1, net, version) + expectFullDeactivation(m, edgeworkerID, 1, net, version, note) }, steps: []resource.TestStep{ { @@ -84,6 +87,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "timeouts.#", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "timeouts.0.default", "2h"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "timeouts.0.delete", "3h"), @@ -96,14 +100,15 @@ func TestResourceEdgeworkersActivation(t *testing.T) { net := edgeworkers.ActivationNetworkStaging version := "test" activationID := 8 + note := "note for edgeworkers activation" activations := []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, 7, edgeworkers.ActivationNetworkProduction, "current", activationStatusComplete, "2022-01-25T12:30:06Z"), - *createStubActivation(edgeworkerID, 6, net, "past2", activationStatusComplete, "2022-01-25T12:30:06Z"), - *createStubActivation(edgeworkerID, 5, net, "past1", activationStatusComplete, "2022-01-24T12:30:06Z"), - *createStubActivation(edgeworkerID, 4, edgeworkers.ActivationNetworkProduction, "past1", activationStatusComplete, "2022-01-23T12:30:06Z"), - *createStubActivation(edgeworkerID, 3, net, "past2", activationStatusComplete, "2022-01-23T18:30:06Z"), - *createStubActivation(edgeworkerID, 2, net, "past2", activationStatusComplete, "2022-01-23T12:30:06Z"), - *createStubActivation(edgeworkerID, 1, net, "past1", activationStatusComplete, "2022-01-22T12:30:06Z"), + *createStubActivation(edgeworkerID, 7, edgeworkers.ActivationNetworkProduction, "current", activationStatusComplete, "2022-01-25T12:30:06Z", note), + *createStubActivation(edgeworkerID, 6, net, "past2", activationStatusComplete, "2022-01-25T12:30:06Z", note), + *createStubActivation(edgeworkerID, 5, net, "past1", activationStatusComplete, "2022-01-24T12:30:06Z", note), + *createStubActivation(edgeworkerID, 4, edgeworkers.ActivationNetworkProduction, "past1", activationStatusComplete, "2022-01-23T12:30:06Z", note), + *createStubActivation(edgeworkerID, 3, net, "past2", activationStatusComplete, "2022-01-23T18:30:06Z", note), + *createStubActivation(edgeworkerID, 2, net, "past2", activationStatusComplete, "2022-01-23T12:30:06Z", note), + *createStubActivation(edgeworkerID, 1, net, "past1", activationStatusComplete, "2022-01-22T12:30:06Z", note), } // version verification @@ -122,17 +127,17 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Once() // activate - expectActivateVersion(m, edgeworkerID, activationID, net, version, nil).Once() + expectActivateVersion(m, edgeworkerID, activationID, net, version, note, nil).Once() expectGetActivation(m, edgeworkerID, activationID, net, version, activationStatusComplete, nil).Once() // read expectFullRead(m, edgeworkerID, version, append([]edgeworkers.Activation{ - *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, "")}, + *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, "", note)}, activations..., ), []edgeworkers.Deactivation{}, 2) // test cleanup - destroy - expectFullDeactivation(m, edgeworkerID, 4, net, version) + expectFullDeactivation(m, edgeworkerID, 4, net, version, note) }, steps: []resource.TestStep{ { @@ -141,6 +146,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "8"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, }, @@ -150,6 +156,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { net := edgeworkers.ActivationNetworkStaging version := "test" activationID := 1 + note := "note for edgeworkers activation" // version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -158,17 +165,17 @@ func TestResourceEdgeworkersActivation(t *testing.T) { // get current activation expectListActivations(m, edgeworkerID, "", []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, "", note), }, nil).Once() expectListDeactivations(m, edgeworkerID, version, []edgeworkers.Deactivation{}, nil).Once() // read expectFullRead(m, edgeworkerID, version, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 2) // test cleanup - destroy - expectFullDeactivation(m, edgeworkerID, 1, net, version) + expectFullDeactivation(m, edgeworkerID, 1, net, version, note) }, steps: []resource.TestStep{ { @@ -177,6 +184,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, }, @@ -186,6 +194,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { net := edgeworkers.ActivationNetworkStaging version := "test" activationID := 1 + note := "note for edgeworkers activation" // version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -196,7 +205,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { expectListActivations(m, edgeworkerID, "", []edgeworkers.Activation{}, nil).Once() // activate - expectActivateVersion(m, edgeworkerID, activationID, net, version, nil).Once() + expectActivateVersion(m, edgeworkerID, activationID, net, version, note, nil).Once() expectGetActivation(m, edgeworkerID, activationID, net, version, activationStatusPresubmit, nil).Times(2) expectGetActivation(m, edgeworkerID, activationID, net, version, activationStatusPending, nil).Times(2) expectGetActivation(m, edgeworkerID, activationID, net, version, activationStatusInProgress, nil).Times(2) @@ -204,11 +213,11 @@ func TestResourceEdgeworkersActivation(t *testing.T) { // read expectFullRead(m, edgeworkerID, version, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 2) // test cleanup - destroy - expectFullDeactivation(m, edgeworkerID, 1, net, version) + expectFullDeactivation(m, edgeworkerID, 1, net, version, note) }, steps: []resource.TestStep{ { @@ -217,6 +226,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, }, @@ -226,6 +236,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { net := edgeworkers.ActivationNetworkStaging version := "test" activationID := 1 + note := "note for edgeworkers activation" // version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -234,7 +245,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { // get current activation expectListActivations(m, edgeworkerID, "", []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, activationID, net, version, activationStatusPending, ""), + *createStubActivation(edgeworkerID, activationID, net, version, activationStatusPending, "", note), }, nil).Once() expectGetActivation(m, edgeworkerID, activationID, net, version, activationStatusPending, nil).Once() expectGetActivation(m, edgeworkerID, activationID, net, version, activationStatusInProgress, nil).Once() @@ -242,11 +253,11 @@ func TestResourceEdgeworkersActivation(t *testing.T) { // read expectFullRead(m, edgeworkerID, version, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 2) // test cleanup - destroy - expectFullDeactivation(m, edgeworkerID, 1, net, version) + expectFullDeactivation(m, edgeworkerID, 1, net, version, note) }, steps: []resource.TestStep{ { @@ -255,6 +266,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, }, @@ -264,6 +276,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { createNet, updateNet := edgeworkers.ActivationNetworkStaging, edgeworkers.ActivationNetworkProduction version := "test" createActivationID, updateActivationID := 1, 2 + note := "note for edgeworkers activation" // create + update - version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -271,25 +284,25 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Times(2) // create - expectFullActivation(m, edgeworkerID, createActivationID, createNet, version) + expectFullActivation(m, edgeworkerID, createActivationID, createNet, version, note) // read + plan + refresh activations := []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, createActivationID, createNet, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, createActivationID, createNet, version, activationStatusComplete, "", note), } expectFullRead(m, edgeworkerID, version, activations, []edgeworkers.Deactivation{}, 3) // update - activate - expectFullUpdate(m, edgeworkerID, updateActivationID, updateNet, version, "", activations) + expectFullUpdate(m, edgeworkerID, updateActivationID, updateNet, version, "", note, activations) // read + plan expectFullRead(m, edgeworkerID, version, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, updateActivationID, updateNet, version, activationStatusComplete, ""), - *createStubActivation(edgeworkerID, createActivationID, createNet, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, updateActivationID, updateNet, version, activationStatusComplete, "", note), + *createStubActivation(edgeworkerID, createActivationID, createNet, version, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 2) // test cleanup - destroy - expectFullDeactivation(m, edgeworkerID, 1, updateNet, version) + expectFullDeactivation(m, edgeworkerID, 1, updateNet, version, note) }, steps: []resource.TestStep{ { @@ -298,6 +311,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, { @@ -306,6 +320,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "2"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", productionNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, }, @@ -315,6 +330,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { net := edgeworkers.ActivationNetworkStaging createVersion, updateVersion := "test", "test1" createActivationID, updateActivationID := 1, 2 + note := "note for edgeworkers activation" // create + update - version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -323,25 +339,25 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Times(2) // create - expectFullActivation(m, edgeworkerID, createActivationID, net, createVersion) + expectFullActivation(m, edgeworkerID, createActivationID, net, createVersion, note) // read + plan + refresh activations := []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, "", note), } expectFullRead(m, edgeworkerID, createVersion, activations, []edgeworkers.Deactivation{}, 3) // update - activate - expectFullUpdate(m, edgeworkerID, updateActivationID, net, updateVersion, createVersion, activations) + expectFullUpdate(m, edgeworkerID, updateActivationID, net, updateVersion, createVersion, note, activations) // read + plan expectFullRead(m, edgeworkerID, updateVersion, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, updateActivationID, net, updateVersion, activationStatusComplete, ""), - *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, updateActivationID, net, updateVersion, activationStatusComplete, "", note), + *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 2) // test cleanup - destroy - expectFullDeactivation(m, edgeworkerID, 1, net, updateVersion) + expectFullDeactivation(m, edgeworkerID, 1, net, updateVersion, note) }, steps: []resource.TestStep{ { @@ -350,6 +366,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, { @@ -358,6 +375,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "2"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, }, @@ -367,6 +385,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { net := edgeworkers.ActivationNetworkStaging createVersion, updateVersion := "test", "test1" createActivationID, updateActivationID := 1, 3 + note := "note for edgeworkers activation" // create + update - version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -376,32 +395,32 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Times(2) // create - expectFullActivation(m, edgeworkerID, createActivationID, net, createVersion) + expectFullActivation(m, edgeworkerID, createActivationID, net, createVersion, note) // read + plan expectFullRead(m, edgeworkerID, createVersion, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 2) // refresh activations := []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, 2, net, "someOtherVersion", activationStatusComplete, ""), - *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, 2, net, "someOtherVersion", activationStatusComplete, "", note), + *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, "", note), } expectFullRead(m, edgeworkerID, "someOtherVersion", activations, []edgeworkers.Deactivation{}, 1) // update - activate - expectFullUpdate(m, edgeworkerID, updateActivationID, net, updateVersion, "someOtherVersion", activations) + expectFullUpdate(m, edgeworkerID, updateActivationID, net, updateVersion, "someOtherVersion", note, activations) // read + plan expectFullRead(m, edgeworkerID, updateVersion, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, updateActivationID, net, updateVersion, activationStatusComplete, ""), - *createStubActivation(edgeworkerID, 2, net, "someOtherVersion", activationStatusComplete, ""), - *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, updateActivationID, net, updateVersion, activationStatusComplete, "", note), + *createStubActivation(edgeworkerID, 2, net, "someOtherVersion", activationStatusComplete, "", note), + *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 2) // test cleanup - destroy - expectFullDeactivation(m, edgeworkerID, 1, net, updateVersion) + expectFullDeactivation(m, edgeworkerID, 1, net, updateVersion, note) }, steps: []resource.TestStep{ { @@ -410,6 +429,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, { @@ -418,6 +438,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "3"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, }, @@ -427,6 +448,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { net := edgeworkers.ActivationNetworkStaging createVersion, updateVersion := "test", "test1" createActivationID, updateActivationID := 1, 2 + note := "note for edgeworkers activation" // create + update - version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -435,21 +457,21 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Once() // create - expectFullActivation(m, edgeworkerID, createActivationID, net, createVersion) + expectFullActivation(m, edgeworkerID, createActivationID, net, createVersion, note) // read + plan expectFullRead(m, edgeworkerID, createVersion, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 2) // refresh expectFullRead(m, edgeworkerID, updateVersion, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, updateActivationID, net, updateVersion, activationStatusComplete, ""), - *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, updateActivationID, net, updateVersion, activationStatusComplete, "", note), + *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 2) // test cleanup - destroy - expectFullDeactivation(m, edgeworkerID, 1, net, updateVersion) + expectFullDeactivation(m, edgeworkerID, 1, net, updateVersion, note) }, steps: []resource.TestStep{ { @@ -458,6 +480,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, { @@ -466,6 +489,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "2"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, }, @@ -475,6 +499,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { createNet, updateNet := edgeworkers.ActivationNetworkStaging, edgeworkers.ActivationNetworkProduction version := "test" createActivationID, updateActivationID := 1, 2 + note := "note for edgeworkers activation" // create + update - version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -482,17 +507,17 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Times(2) // create - expectFullActivation(m, edgeworkerID, createActivationID, createNet, version) + expectFullActivation(m, edgeworkerID, createActivationID, createNet, version, note) // read + plan expectFullRead(m, edgeworkerID, version, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, createActivationID, createNet, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, createActivationID, createNet, version, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 2) // refresh activations := []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, updateActivationID, updateNet, version, activationStatusComplete, ""), - *createStubActivation(edgeworkerID, createActivationID, createNet, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, updateActivationID, updateNet, version, activationStatusComplete, "", note), + *createStubActivation(edgeworkerID, createActivationID, createNet, version, activationStatusComplete, "", note), } expectFullRead(m, edgeworkerID, version, activations, []edgeworkers.Deactivation{}, 1) @@ -504,7 +529,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { expectFullRead(m, edgeworkerID, version, activations, []edgeworkers.Deactivation{}, 2) // test cleanup - destroy - expectFullDeactivation(m, edgeworkerID, 2, updateNet, version) + expectFullDeactivation(m, edgeworkerID, 2, updateNet, version, note) }, steps: []resource.TestStep{ { @@ -513,6 +538,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, { @@ -521,6 +547,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "2"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", productionNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, }, @@ -531,6 +558,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { version := "test" activationID := 1 updateEdgeworkerID := 4321 + note := "note for edgeworkers activation" expectListEdgeWorkersID(m, nil, edgeworkerID, updateEdgeworkerID) @@ -540,15 +568,15 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Once() // create - expectFullActivation(m, edgeworkerID, activationID, net, version) + expectFullActivation(m, edgeworkerID, activationID, net, version, note) // read + plan + refresh expectFullRead(m, edgeworkerID, version, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 3) // destroy - expectFullDeactivation(m, edgeworkerID, 1, net, version) + expectFullDeactivation(m, edgeworkerID, 1, net, version, note) // create - version verification expectListEdgeWorkerVersions(m, updateEdgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -556,15 +584,15 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Once() // create - expectFullActivation(m, updateEdgeworkerID, activationID, net, version) + expectFullActivation(m, updateEdgeworkerID, activationID, net, version, note) // read + plan expectFullRead(m, updateEdgeworkerID, version, []edgeworkers.Activation{ - *createStubActivation(updateEdgeworkerID, activationID, net, version, activationStatusComplete, ""), + *createStubActivation(updateEdgeworkerID, activationID, net, version, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 2) // test cleanup - destroy - expectFullDeactivation(m, updateEdgeworkerID, 1, net, version) + expectFullDeactivation(m, updateEdgeworkerID, 1, net, version, note) }, steps: []resource.TestStep{ { @@ -588,11 +616,112 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, omitDefaultMock: true, }, + "update note - diff suppressed when other fields not changed": { + init: func(m *edgeworkers.Mock) { + createNet := edgeworkers.ActivationNetworkStaging + version := "test1" + createActivationID := 1 + note := "note for edgeworkers activation" + + // create - version verification + expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ + *createStubEdgeworkerVersion(edgeworkerID, version), + }, nil).Times(1) + + // create + expectFullActivation(m, edgeworkerID, createActivationID, createNet, version, note) + + // read + plan + refresh + activations := []edgeworkers.Activation{ + *createStubActivation(edgeworkerID, createActivationID, createNet, version, activationStatusComplete, "", note), + } + expectFullRead(m, edgeworkerID, version, activations, []edgeworkers.Deactivation{}, 4) + + // test cleanup - destroy + expectFullDeactivation(m, edgeworkerID, 1, createNet, version, note) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/edgeworkers_activation_version_test1_stag.tf", workdir)), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test1"), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), + ), + }, + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/edgeworkers_activation_note_update_no_activation.tf", workdir)), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test1"), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), + ), + }, + }, + }, + "update note - when other fields changed it does not update but creates new activation ": { + init: func(m *edgeworkers.Mock) { + createNet, updateNet := edgeworkers.ActivationNetworkStaging, edgeworkers.ActivationNetworkProduction + version := "test1" + createActivationID, updateActivationID := 1, 2 + note, updatedNote := "note for edgeworkers activation", "note for edgeworkers activation updated" + + // create - version verification + expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ + *createStubEdgeworkerVersion(edgeworkerID, version), + }, nil).Times(2) + + // create + expectFullActivation(m, edgeworkerID, createActivationID, createNet, version, note) + + // read + plan + refresh + activations := []edgeworkers.Activation{ + *createStubActivation(edgeworkerID, createActivationID, createNet, version, activationStatusComplete, "", note), + } + expectFullRead(m, edgeworkerID, version, activations, []edgeworkers.Deactivation{}, 3) + + // update - activate + expectFullUpdate(m, edgeworkerID, updateActivationID, updateNet, version, "", updatedNote, activations) + + // read + plan + expectFullRead(m, edgeworkerID, version, []edgeworkers.Activation{ + *createStubActivation(edgeworkerID, updateActivationID, updateNet, version, activationStatusComplete, "", updatedNote), + *createStubActivation(edgeworkerID, createActivationID, createNet, version, activationStatusComplete, "", note), + }, []edgeworkers.Deactivation{}, 2) + + // test cleanup - destroy + expectFullDeactivation(m, edgeworkerID, 1, updateNet, version, updatedNote) + + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/edgeworkers_activation_version_test1_stag.tf", workdir)), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test1"), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), + ), + }, + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/edgeworkers_activation_note_update.tf", workdir)), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "2"), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test1"), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", productionNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation updated"), + ), + }, + }, + }, "destroy - version already deactivated": { init: func(m *edgeworkers.Mock) { net := edgeworkers.ActivationNetworkStaging version := "test" activationID := 1 + note := "note for edgeworkers activation" // version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -600,15 +729,15 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Once() // create - expectFullActivation(m, edgeworkerID, activationID, net, version) + expectFullActivation(m, edgeworkerID, activationID, net, version, note) // read expectFullRead(m, edgeworkerID, version, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 2) // test cleanup - destroy - expectDeactivateVersion(m, edgeworkerID, 1, net, version, + expectDeactivateVersion(m, edgeworkerID, 1, net, version, note, fmt.Errorf("%w: %s", &edgeworkers.Error{ErrorCode: errorCodeVersionAlreadyDeactivated}, "oops")) }, steps: []resource.TestStep{ @@ -618,6 +747,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, }, @@ -627,6 +757,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { net := edgeworkers.ActivationNetworkStaging version := "test" activationID := 1 + note := "note for edgeworkers activation" // version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -634,15 +765,15 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Once() // create - expectFullActivation(m, edgeworkerID, activationID, net, version) + expectFullActivation(m, edgeworkerID, activationID, net, version, note) // read expectFullRead(m, edgeworkerID, version, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 2) // test cleanup - destroy - expectDeactivateVersion(m, edgeworkerID, 1, net, version, + expectDeactivateVersion(m, edgeworkerID, 1, net, version, note, fmt.Errorf("%w: %s", &edgeworkers.Error{ErrorCode: errorCodeVersionIsBeingDeactivated}, "oops")) expectListDeactivations(m, edgeworkerID, version, []edgeworkers.Deactivation{ *createStubDeactivation(edgeworkerID, 1, net, version, activationStatusInProgress, ""), @@ -657,6 +788,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, }, @@ -666,6 +798,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { net := edgeworkers.ActivationNetworkStaging version := "test" activationID := 1 + note := "note for edgeworkers activation" // version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -673,15 +806,15 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Once() // create - expectFullActivation(m, edgeworkerID, activationID, net, version) + expectFullActivation(m, edgeworkerID, activationID, net, version, note) // read expectFullRead(m, edgeworkerID, version, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 2) // test cleanup - destroy - expectDeactivateVersion(m, edgeworkerID, 1, net, version, nil) + expectDeactivateVersion(m, edgeworkerID, 1, net, version, note, nil) expectGetDeactivation(m, edgeworkerID, 1, net, version, activationStatusPresubmit, nil).Times(2) expectGetDeactivation(m, edgeworkerID, 1, net, version, activationStatusPending, nil).Times(2) expectGetDeactivation(m, edgeworkerID, 1, net, version, activationStatusInProgress, nil).Times(2) @@ -694,6 +827,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, }, @@ -703,6 +837,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { net := edgeworkers.ActivationNetworkStaging version := "test" activationID := 1 + note := "note for edgeworkers activation" // version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -710,16 +845,16 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Once() // create - expectFullActivation(m, edgeworkerID, activationID, net, version) + expectFullActivation(m, edgeworkerID, activationID, net, version, note) // read expectFullRead(m, edgeworkerID, version, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 2) // test cleanup - destroy // A bit hack to simulate timeout is returning ErrEdgeworkerDeactivationTimeout on GetDeactivation - expectDeactivateVersion(m, edgeworkerID, 1, net, version, nil) + expectDeactivateVersion(m, edgeworkerID, 1, net, version, note, nil) expectGetDeactivation(m, edgeworkerID, 1, net, version, activationStatusPresubmit, nil).Times(2) expectGetDeactivation(m, edgeworkerID, 1, net, version, activationStatusPending, nil).Times(2) expectGetDeactivation(m, edgeworkerID, 1, net, version, activationStatusInProgress, nil).Times(2) @@ -732,6 +867,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, }, @@ -748,14 +884,14 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Once() // create - expectFullActivation(m, edgeworkerID, activationID, net, version) + expectFullActivation(m, edgeworkerID, activationID, net, version, "") expectFullRead(m, edgeworkerID, version, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, "", ""), }, []edgeworkers.Deactivation{}, 3) // test cleanup - destroy - expectFullDeactivation(m, edgeworkerID, 1, net, version) + expectFullDeactivation(m, edgeworkerID, 1, net, version, "") }, steps: []resource.TestStep{ { @@ -769,6 +905,40 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, }, }, + "import activation on staging with note": { + init: func(m *edgeworkers.Mock) { + net := edgeworkers.ActivationNetworkStaging + version := "test1" + activationID := 1 + note := "note for edgeworkers activation updated" + + // version verification + expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ + *createStubEdgeworkerVersion(edgeworkerID, version), + }, nil).Once() + + // create + expectFullActivation(m, edgeworkerID, activationID, net, version, note) + + expectFullRead(m, edgeworkerID, version, []edgeworkers.Activation{ + *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, "", note), + }, []edgeworkers.Deactivation{}, 3) + + // test cleanup - destroy + expectFullDeactivation(m, edgeworkerID, 1, net, version, note) + }, + steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/edgeworkers_activation_note_update_no_activation.tf", workdir)), + }, + { + ImportState: true, + ImportStateId: fmt.Sprintf("%d:STAGING", edgeworkerID), + ResourceName: "akamai_edgeworkers_activation.test", + ImportStateVerify: true, + }, + }, + }, "error on create - missing required arguments": { init: func(m *edgeworkers.Mock) {}, steps: []resource.TestStep{ @@ -826,7 +996,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Once() expectListActivations(m, edgeworkerID, "", []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, 1, edgeworkers.ActivationNetworkStaging, "test", activationStatusComplete, ""), + *createStubActivation(edgeworkerID, 1, edgeworkers.ActivationNetworkStaging, "test", activationStatusComplete, "", ""), }, nil) expectListDeactivations(m, edgeworkerID, "test", []edgeworkers.Deactivation{}, fmt.Errorf("oops")) }, @@ -857,7 +1027,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Once() expectListActivations(m, edgeworkerID, "", []edgeworkers.Activation{}, nil) - expectActivateVersion(m, edgeworkerID, 1, edgeworkers.ActivationNetworkStaging, "test", fmt.Errorf("oops")) + expectActivateVersion(m, edgeworkerID, 1, edgeworkers.ActivationNetworkStaging, "test", "note for edgeworkers activation", fmt.Errorf("oops")) }, steps: []resource.TestStep{ { @@ -871,13 +1041,15 @@ func TestResourceEdgeworkersActivation(t *testing.T) { version := "test" net := edgeworkers.ActivationNetworkStaging activationID := 1 + note := "note for edgeworkers activation" + // create - version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ *createStubEdgeworkerVersion(edgeworkerID, version), }, nil).Once() expectListActivations(m, edgeworkerID, "", []edgeworkers.Activation{}, nil) - expectActivateVersion(m, edgeworkerID, activationID, net, version, nil) + expectActivateVersion(m, edgeworkerID, activationID, net, version, note, nil) expectGetActivation(m, edgeworkerID, activationID, net, version, activationStatusPresubmit, nil).Once() expectGetActivation(m, edgeworkerID, activationID, net, version, activationStatusPending, nil).Once() expectGetActivation(m, edgeworkerID, activationID, net, version, activationStatusInProgress, nil).Once() @@ -895,13 +1067,15 @@ func TestResourceEdgeworkersActivation(t *testing.T) { version := "test" net := edgeworkers.ActivationNetworkStaging activationID := 1 + note := "note for edgeworkers activation" + // create - version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ *createStubEdgeworkerVersion(edgeworkerID, version), }, nil).Once() expectListActivations(m, edgeworkerID, "", []edgeworkers.Activation{}, nil) - expectActivateVersion(m, edgeworkerID, activationID, net, version, nil) + expectActivateVersion(m, edgeworkerID, activationID, net, version, note, nil) expectGetActivation(m, edgeworkerID, activationID, net, version, activationStatusPresubmit, nil).Once() expectGetActivation(m, edgeworkerID, activationID, net, version, activationStatusPending, nil).Once() expectGetActivation(m, edgeworkerID, activationID, net, version, activationStatusInProgress, nil).Once() @@ -919,6 +1093,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { net := edgeworkers.ActivationNetworkStaging createVersion, updateVersion := "test", "test1" createActivationID := 1 + note := "note for edgeworkers activation" // create + update - version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -926,15 +1101,15 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Times(2) // create - expectFullActivation(m, edgeworkerID, createActivationID, net, createVersion) + expectFullActivation(m, edgeworkerID, createActivationID, net, createVersion, note) // read + plan + refresh expectFullRead(m, edgeworkerID, createVersion, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 3) // test cleanup - destroy - expectFullDeactivation(m, edgeworkerID, 1, net, updateVersion) + expectFullDeactivation(m, edgeworkerID, 1, net, updateVersion, note) }, steps: []resource.TestStep{ { @@ -943,6 +1118,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, { @@ -956,6 +1132,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { net := edgeworkers.ActivationNetworkStaging version := "test" activationID := 1 + note := "note for edgeworkers activation" // create version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -963,15 +1140,15 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Once() // create - expectFullActivation(m, edgeworkerID, activationID, net, version) + expectFullActivation(m, edgeworkerID, activationID, net, version, note) // read + plan expectFullRead(m, edgeworkerID, version, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 3) // test cleanup - destroy - expectFullDeactivation(m, edgeworkerID, 1, net, version) + expectFullDeactivation(m, edgeworkerID, 1, net, version, note) }, steps: []resource.TestStep{ { @@ -980,6 +1157,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, { @@ -993,6 +1171,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { net := edgeworkers.ActivationNetworkStaging createVersion, updateVersion := "test", "test1" createActivationID := 1 + note := "note for edgeworkers activation" // create + update - version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -1001,22 +1180,22 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Once() // create - expectFullActivation(m, edgeworkerID, createActivationID, net, createVersion) + expectFullActivation(m, edgeworkerID, createActivationID, net, createVersion, note) // read + plan expectFullRead(m, edgeworkerID, createVersion, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, "", note), }, []edgeworkers.Deactivation{}, 2) // refresh expectFullRead(m, edgeworkerID, createVersion, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, "2022-01-25T12:30:06Z"), + *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, "2022-01-25T12:30:06Z", note), }, []edgeworkers.Deactivation{ *createStubDeactivation(edgeworkerID, 1, net, createVersion, activationStatusComplete, "2022-01-26T12:30:06Z"), }, 1) // test cleanup - destroy - expectFullDeactivation(m, edgeworkerID, 1, net, createVersion) + expectFullDeactivation(m, edgeworkerID, 1, net, createVersion, note) }, steps: []resource.TestStep{ { @@ -1025,6 +1204,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, { @@ -1038,6 +1218,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { net := edgeworkers.ActivationNetworkStaging createVersion, updateVersion := "test", "test1" createActivationID := 1 + note := "note for edgeworkers activation" // create + update - version verification expectListEdgeWorkerVersions(m, edgeworkerID, []edgeworkers.EdgeWorkerVersion{ @@ -1046,11 +1227,11 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Times(2) // create - expectFullActivation(m, edgeworkerID, createActivationID, net, createVersion) + expectFullActivation(m, edgeworkerID, createActivationID, net, createVersion, note) // read + plan + refresh activations := []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, createActivationID, net, createVersion, activationStatusComplete, "", note), } expectFullRead(m, edgeworkerID, createVersion, activations, []edgeworkers.Deactivation{}, 3) @@ -1063,7 +1244,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { expectGetDeactivation(m, edgeworkerID, 1, net, createVersion, "", fmt.Errorf("oops")).Once() // test cleanup - destroy - expectFullDeactivation(m, edgeworkerID, 1, net, updateVersion) + expectFullDeactivation(m, edgeworkerID, 1, net, updateVersion, note) }, steps: []resource.TestStep{ { @@ -1072,6 +1253,7 @@ func TestResourceEdgeworkersActivation(t *testing.T) { resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "activation_id", "1"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "version", "test"), resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "network", stagingNetwork), + resource.TestCheckResourceAttr("akamai_edgeworkers_activation.test", "note", "note for edgeworkers activation"), ), }, { @@ -1105,14 +1287,14 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Once() // create - expectFullActivation(m, edgeworkerID, activationID, net, version) + expectFullActivation(m, edgeworkerID, activationID, net, version, "") expectFullRead(m, edgeworkerID, version, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, "", ""), }, []edgeworkers.Deactivation{}, 2) // test cleanup - destroy - expectFullDeactivation(m, edgeworkerID, 1, net, version) + expectFullDeactivation(m, edgeworkerID, 1, net, version, "") }, steps: []resource.TestStep{ { @@ -1138,14 +1320,14 @@ func TestResourceEdgeworkersActivation(t *testing.T) { }, nil).Once() // create - expectFullActivation(m, edgeworkerID, activationID, net, version) + expectFullActivation(m, edgeworkerID, activationID, net, version, "") expectFullRead(m, edgeworkerID, version, []edgeworkers.Activation{ - *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, ""), + *createStubActivation(edgeworkerID, activationID, net, version, activationStatusComplete, "", ""), }, []edgeworkers.Deactivation{}, 2) // test cleanup - destroy - expectFullDeactivation(m, edgeworkerID, 1, net, version) + expectFullDeactivation(m, edgeworkerID, 1, net, version, "") }, steps: []resource.TestStep{ { @@ -1184,19 +1366,20 @@ func TestResourceEdgeworkersActivation(t *testing.T) { } } -func expectActivateVersion(m *edgeworkers.Mock, edgeworkerID, activationID int, net edgeworkers.ActivationNetwork, version string, e error) *mock.Call { +func expectActivateVersion(m *edgeworkers.Mock, edgeworkerID, activationID int, net edgeworkers.ActivationNetwork, version, note string, e error) *mock.Call { req := edgeworkers.ActivateVersionRequest{ EdgeWorkerID: edgeworkerID, ActivateVersion: edgeworkers.ActivateVersion{ Network: net, Version: version, + Note: note, }, } if e != nil { return m.On("ActivateVersion", mock.Anything, req).Return(nil, e) } - return m.On("ActivateVersion", mock.Anything, req).Return(createStubActivation(edgeworkerID, activationID, net, version, activationStatusPresubmit, ""), nil) + return m.On("ActivateVersion", mock.Anything, req).Return(createStubActivation(edgeworkerID, activationID, net, version, activationStatusPresubmit, "", note), nil) } func expectGetActivation(m *edgeworkers.Mock, edgeworkerID, activationID int, net edgeworkers.ActivationNetwork, version, status string, e error) *mock.Call { @@ -1208,7 +1391,7 @@ func expectGetActivation(m *edgeworkers.Mock, edgeworkerID, activationID int, ne return m.On("GetActivation", mock.Anything, req).Return(nil, e) } - return m.On("GetActivation", mock.Anything, req).Return(createStubActivation(edgeworkerID, activationID, net, version, status, ""), nil) + return m.On("GetActivation", mock.Anything, req).Return(createStubActivation(edgeworkerID, activationID, net, version, status, "", ""), nil) } func expectListActivations(m *edgeworkers.Mock, edgeworkerID int, version string, activations []edgeworkers.Activation, e error) *mock.Call { @@ -1239,12 +1422,13 @@ func expectListDeactivations(m *edgeworkers.Mock, edgeworkerID int, version stri }, nil) } -func expectDeactivateVersion(m *edgeworkers.Mock, edgeworkerID, deactivationID int, net edgeworkers.ActivationNetwork, version string, e error) *mock.Call { +func expectDeactivateVersion(m *edgeworkers.Mock, edgeworkerID, deactivationID int, net edgeworkers.ActivationNetwork, version, note string, e error) *mock.Call { req := edgeworkers.DeactivateVersionRequest{ EdgeWorkerID: edgeworkerID, DeactivateVersion: edgeworkers.DeactivateVersion{ Network: net, Version: version, + Note: note, }, } if e != nil { @@ -1293,15 +1477,15 @@ func expectListEdgeWorkersID(m *edgeworkers.Mock, e error, ewIDs ...int) *mock.C }, nil) } -func expectFullActivation(m *edgeworkers.Mock, edgeworkerID, activationID int, net edgeworkers.ActivationNetwork, version string) { +func expectFullActivation(m *edgeworkers.Mock, edgeworkerID, activationID int, net edgeworkers.ActivationNetwork, version, note string) { expectListActivations(m, edgeworkerID, "", []edgeworkers.Activation{}, nil).Once() - expectActivateVersion(m, edgeworkerID, activationID, net, version, nil).Once() + expectActivateVersion(m, edgeworkerID, activationID, net, version, note, nil).Once() expectGetActivation(m, edgeworkerID, activationID, net, version, activationStatusComplete, nil).Once() } -func expectFullUpdate(m *edgeworkers.Mock, edgeworkerID, activationID int, net edgeworkers.ActivationNetwork, version, listDeactivationsVersion string, activations []edgeworkers.Activation) { +func expectFullUpdate(m *edgeworkers.Mock, edgeworkerID, activationID int, net edgeworkers.ActivationNetwork, version, listDeactivationsVersion, note string, activations []edgeworkers.Activation) { expectListActivations(m, edgeworkerID, "", activations, nil).Once() - expectActivateVersion(m, edgeworkerID, activationID, net, version, nil).Once() + expectActivateVersion(m, edgeworkerID, activationID, net, version, note, nil).Once() if listDeactivationsVersion != "" { expectListDeactivations(m, edgeworkerID, listDeactivationsVersion, []edgeworkers.Deactivation{}, nil).Once() } @@ -1313,12 +1497,12 @@ func expectFullRead(m *edgeworkers.Mock, edgeworkerID int, version string, acts expectListDeactivations(m, edgeworkerID, version, deacts, nil).Times(times) } -func expectFullDeactivation(m *edgeworkers.Mock, edgeworkerID, deactivationID int, net edgeworkers.ActivationNetwork, version string) { - expectDeactivateVersion(m, edgeworkerID, deactivationID, net, version, nil).Once() +func expectFullDeactivation(m *edgeworkers.Mock, edgeworkerID, deactivationID int, net edgeworkers.ActivationNetwork, version, note string) { + expectDeactivateVersion(m, edgeworkerID, deactivationID, net, version, note, nil).Once() expectGetDeactivation(m, edgeworkerID, deactivationID, net, version, activationStatusComplete, nil).Once() } -func createStubActivation(edgeworkerID, activationID int, net edgeworkers.ActivationNetwork, version, status, time string) *edgeworkers.Activation { +func createStubActivation(edgeworkerID, activationID int, net edgeworkers.ActivationNetwork, version, status, time, note string) *edgeworkers.Activation { if time == "" { time = "2022-01-25T12:30:06Z" } @@ -1332,6 +1516,7 @@ func createStubActivation(edgeworkerID, activationID int, net edgeworkers.Activa Network: string(net), Status: status, Version: version, + Note: note, } } diff --git a/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_different_edgeworker_id.tf b/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_different_edgeworker_id.tf index 28e1dc99a..ae808d429 100644 --- a/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_different_edgeworker_id.tf +++ b/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_different_edgeworker_id.tf @@ -6,4 +6,5 @@ resource "akamai_edgeworkers_activation" "test" { edgeworker_id = 4321 network = "STAGING" version = "test" + note = "note for edgeworkers activation" } \ No newline at end of file diff --git a/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_note_update.tf b/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_note_update.tf new file mode 100644 index 000000000..d178f8099 --- /dev/null +++ b/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_note_update.tf @@ -0,0 +1,10 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_edgeworkers_activation" "test" { + edgeworker_id = 1234 + network = "PRODUCTION" + version = "test1" + note = "note for edgeworkers activation updated" +} \ No newline at end of file diff --git a/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_note_update_no_activation.tf b/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_note_update_no_activation.tf new file mode 100644 index 000000000..b1432867c --- /dev/null +++ b/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_note_update_no_activation.tf @@ -0,0 +1,10 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_edgeworkers_activation" "test" { + edgeworker_id = 1234 + network = "STAGING" + version = "test1" + note = "note for edgeworkers activation updated" +} \ No newline at end of file diff --git a/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_version_test1_stag.tf b/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_version_test1_stag.tf index 4620b5850..bd2b73d76 100644 --- a/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_version_test1_stag.tf +++ b/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_version_test1_stag.tf @@ -6,4 +6,5 @@ resource "akamai_edgeworkers_activation" "test" { edgeworker_id = 1234 network = "STAGING" version = "test1" + note = "note for edgeworkers activation" } \ No newline at end of file diff --git a/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_version_test_prod.tf b/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_version_test_prod.tf index bfd667add..bbc4a1428 100644 --- a/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_version_test_prod.tf +++ b/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_version_test_prod.tf @@ -6,4 +6,5 @@ resource "akamai_edgeworkers_activation" "test" { edgeworker_id = 1234 network = "PRODUCTION" version = "test" + note = "note for edgeworkers activation" } \ No newline at end of file diff --git a/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_version_test_stag.tf b/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_version_test_stag.tf index 284069251..ed6c5320e 100644 --- a/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_version_test_stag.tf +++ b/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_version_test_stag.tf @@ -6,4 +6,5 @@ resource "akamai_edgeworkers_activation" "test" { edgeworker_id = 1234 network = "STAGING" version = "test" + note = "note for edgeworkers activation" } \ No newline at end of file diff --git a/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_version_test_with_timeout.tf b/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_version_test_with_timeout.tf index e6b217f8a..f82b57a92 100644 --- a/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_version_test_with_timeout.tf +++ b/pkg/providers/edgeworkers/testdata/TestResourceEdgeWorkersActivation/edgeworkers_activation_version_test_with_timeout.tf @@ -6,6 +6,7 @@ resource "akamai_edgeworkers_activation" "test" { edgeworker_id = 1234 network = "STAGING" version = "test" + note = "note for edgeworkers activation" timeouts { default = "2h" delete = "3h" diff --git a/pkg/providers/gtm/common.go b/pkg/providers/gtm/common.go new file mode 100644 index 000000000..8f6fafe6a --- /dev/null +++ b/pkg/providers/gtm/common.go @@ -0,0 +1,18 @@ +package gtm + +import ( + "github.com/hashicorp/terraform-plugin-framework/types" +) + +type resourceInstance struct { + DataCenterID types.Int64 `tfsdk:"datacenter_id"` + UseDefaultLoadObject types.Bool `tfsdk:"use_default_load_object"` + LoadObject types.String `tfsdk:"load_object"` + LoadObjectPort types.Int64 `tfsdk:"load_object_port"` + LoadServers []types.String `tfsdk:"load_servers"` +} + +type link struct { + Rel types.String `tfsdk:"rel"` + Href types.String `tfsdk:"href"` +} diff --git a/pkg/providers/gtm/data_akamai_gtm_asmap.go b/pkg/providers/gtm/data_akamai_gtm_asmap.go new file mode 100644 index 000000000..33bf03090 --- /dev/null +++ b/pkg/providers/gtm/data_akamai_gtm_asmap.go @@ -0,0 +1,204 @@ +package gtm + +import ( + "context" + "fmt" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/gtm" + "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-framework/types/basetypes" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +var ( + _ datasource.DataSource = &asmapDataSource{} + _ datasource.DataSourceWithConfigure = &asmapDataSource{} +) + +// NewGTMAsmapDataSource returns a new GTM asmap data source +func NewGTMAsmapDataSource() datasource.DataSource { + return &asmapDataSource{} +} + +type asmapDataSourceModel struct { + ID types.String `tfsdk:"id"` + Domain types.String `tfsdk:"domain"` + Name types.String `tfsdk:"map_name"` + DefaultDatacenter *defaultDatacenter `tfsdk:"default_datacenter"` + Assignments []asMapAssignment `tfsdk:"assignments"` + Links []link `tfsdk:"links"` +} + +type asmapDataSource struct { + meta meta.Meta +} + +// Configure configures data source at the beginning of the lifecycle. +func (d *asmapDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + defer func() { + if r := recover(); r != nil { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected meta.Meta, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + } + }() + + d.meta = meta.Must(req.ProviderData) +} + +// Metadata configures data source's meta information. +func (*asmapDataSource) Metadata(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "akamai_gtm_asmap" +} + +var ( + asmapBlock = map[string]schema.Block{ + "assignments": schema.ListNestedBlock{ + Description: "Contains information about the AS zone groupings of AS IDs.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "datacenter_id": schema.Int64Attribute{ + Computed: true, + Description: "A unique identifier for an existing data center in the domain.", + }, + "as_numbers": schema.SetAttribute{ + Computed: true, + Description: "Specifies an array of AS numbers.", + ElementType: types.Int64Type, + }, + "nickname": schema.StringAttribute{ + Computed: true, + Description: "A descriptive label for the group.", + }, + }, + }, + }, + "default_datacenter": schema.SingleNestedBlock{ + Description: "A placeholder for all other AS zones, AS IDs not found in these AS zones.", + Attributes: map[string]schema.Attribute{ + "datacenter_id": schema.Int64Attribute{ + Computed: true, + Description: "For each property, an identifier for all other AS zones", + }, + "nickname": schema.StringAttribute{ + Computed: true, + Description: "A descriptive label for all other AS zones", + }, + }, + }, + "links": schema.ListNestedBlock{ + Description: "Specifies the URL path that allows direct navigation to the AS map.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "rel": schema.StringAttribute{ + Computed: true, + Description: "Indicates the link relationship of the object.", + }, + "href": schema.StringAttribute{ + Computed: true, + Description: "A hypermedia link to the complete URL that uniquely defines a resource.", + }, + }, + }, + }, + } +) + +// Schema is used to define data source's terraform schema. +func (d *asmapDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "GTM AS map data source.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Computed: true, + DeprecationMessage: "Required by the terraform plugin testing framework, always set to `gtm_asmap`.", + MarkdownDescription: "Identifier of the data source.", + }, + "domain": schema.StringAttribute{ + Required: true, + Description: "A descriptive label for the AS map.", + }, + "map_name": schema.StringAttribute{ + Required: true, + Description: "A descriptive label for the AS map", + }, + }, + Blocks: asmapBlock, + } +} + +// Read is called when the provider must read data source values in order to update state. +func (d *asmapDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + tflog.Debug(ctx, "GTM AS map DataSource Read") + + var data asmapDataSourceModel + + if resp.Diagnostics.Append(req.Config.Get(ctx, &data)...); resp.Diagnostics.HasError() { + return + } + + client := frameworkInst.Client(d.meta) + asMap, err := client.GetAsMap(ctx, data.Name.ValueString(), data.Domain.ValueString()) + if err != nil { + resp.Diagnostics.AddError("fetching GTM ASmap failed: ", err.Error()) + return + } + + data.setAttributes(asMap) + + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) + +} + +func (m *asmapDataSourceModel) setAttributes(asmap *gtm.AsMap) { + m.Name = types.StringValue(asmap.Name) + m.setDefaultDatacenter(asmap.DefaultDatacenter) + m.setAssignments(asmap.Assignments) + m.setLinks(asmap.Links) + m.ID = types.StringValue("gtm_asmap") + +} + +func (m *asmapDataSourceModel) setDefaultDatacenter(d *gtm.DatacenterBase) { + m.DefaultDatacenter = &defaultDatacenter{ + DatacenterID: types.Int64Value(int64(d.DatacenterId)), + Nickname: types.StringValue(d.Nickname), + } +} + +func (m *asmapDataSourceModel) setAssignments(assignments []*gtm.AsAssignment) { + toBasetypesInt64Slice := func(n []int64) []basetypes.Int64Value { + out := make([]basetypes.Int64Value, 0, len(n)) + for _, number := range n { + out = append(out, types.Int64Value(number)) + } + return out + } + + for _, a := range assignments { + assignmentObject := asMapAssignment{ + DatacenterID: types.Int64Value(int64(a.DatacenterId)), + Nickname: types.StringValue(a.Nickname), + ASNumbers: toBasetypesInt64Slice(a.AsNumbers), + } + m.Assignments = append(m.Assignments, assignmentObject) + } +} + +func (m *asmapDataSourceModel) setLinks(links []*gtm.Link) { + for _, l := range links { + linkObject := link{ + Rel: types.StringValue(l.Rel), + Href: types.StringValue(l.Href), + } + + m.Links = append(m.Links, linkObject) + } +} diff --git a/pkg/providers/gtm/data_akamai_gtm_asmap_test.go b/pkg/providers/gtm/data_akamai_gtm_asmap_test.go new file mode 100644 index 000000000..f72f774de --- /dev/null +++ b/pkg/providers/gtm/data_akamai_gtm_asmap_test.go @@ -0,0 +1,134 @@ +package gtm + +import ( + "fmt" + "regexp" + "testing" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/gtm" + "github.com/akamai/terraform-provider-akamai/v5/pkg/common/testutils" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/stretchr/testify/mock" +) + +func TestDataGTMASmap(t *testing.T) { + tests := map[string]struct { + givenTF string + init func(*gtm.Mock) + expectedAttributes map[string]string + expectError *regexp.Regexp + }{ + "happy path": { + givenTF: "valid.tf", + init: func(m *gtm.Mock) { + m.On("GetAsMap", mock.Anything, "map1", "test.domain.net").Return(>m.AsMap{ + Name: "TestName", + DefaultDatacenter: >m.DatacenterBase{ + Nickname: "TestDefaultDatacenterNickname", + DatacenterId: 1, + }, + Assignments: []*gtm.AsAssignment{{ + DatacenterBase: gtm.DatacenterBase{ + Nickname: "TestAssignmentNickname", + DatacenterId: 1, + }, + AsNumbers: []int64{ + 1, + 2, + 3, + }, + }}, + Links: []*gtm.Link{{ + Href: "href.test", + Rel: "TestRel", + }}, + }, nil) + + }, + expectedAttributes: map[string]string{ + "domain": "test.domain.net", + "map_name": "TestName", + "default_datacenter.datacenter_id": "1", + "default_datacenter.nickname": "TestDefaultDatacenterNickname", + "assignments.0.datacenter_id": "1", + "assignments.0.nickname": "TestAssignmentNickname", + "assignments.0.as_numbers.0": "1", + "assignments.0.as_numbers.1": "2", + "assignments.0.as_numbers.2": "3", + "links.0.rel": "TestRel", + "links.0.href": "href.test", + }, + expectError: nil, + }, + "missing required argument domain": { + givenTF: "missing_domain.tf", + expectError: regexp.MustCompile(`The argument "domain" is required, but no definition was found`), + }, + "missing required argument map_name": { + givenTF: "missing_map_name.tf", + expectError: regexp.MustCompile(`The argument "map_name" is required, but no definition was found`), + }, + "error response from api": { + givenTF: "valid.tf", + init: func(m *gtm.Mock) { + m.On("GetAsMap", mock.Anything, "map1", "test.domain.net").Return( + nil, fmt.Errorf("test error")) + }, + expectError: regexp.MustCompile("test error"), + }, + "no assignments": { + givenTF: "valid.tf", + init: func(m *gtm.Mock) { + m.On("GetAsMap", mock.Anything, "map1", "test.domain.net").Return(>m.AsMap{ + Name: "TestName", + DefaultDatacenter: >m.DatacenterBase{ + Nickname: "TestDefaultDatacenterNickname", + DatacenterId: 1, + }, + Assignments: []*gtm.AsAssignment{}, + Links: []*gtm.Link{{ + Href: "href.test", + Rel: "TestRel", + }}, + }, nil) + + }, + expectedAttributes: map[string]string{ + "map_name": "TestName", + "default_datacenter.datacenter_id": "1", + "default_datacenter.nickname": "TestDefaultDatacenterNickname", + "assignments.#": "0", + "links.0.rel": "TestRel", + "links.0.href": "href.test", + }, + expectError: nil, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + client := >m.Mock{} + if test.init != nil { + test.init(client) + } + var checkFuncs []resource.TestCheckFunc + for k, v := range test.expectedAttributes { + checkFuncs = append(checkFuncs, resource.TestCheckResourceAttr("data.akamai_gtm_asmap.my_gtm_asmap", k, v)) + } + + useClient(client, func() { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV5ProviderFactories: testAccProvidersProtoV5, + Steps: []resource.TestStep{{ + Config: testutils.LoadFixtureString(t, fmt.Sprintf("testdata/TestDataGtmAsmap/%s", test.givenTF)), + Check: resource.ComposeAggregateTestCheckFunc(checkFuncs...), + ExpectError: test.expectError, + }}, + }) + }) + + client.AssertExpectations(t) + }) + } +} diff --git a/pkg/providers/gtm/data_akamai_gtm_cidrmap.go b/pkg/providers/gtm/data_akamai_gtm_cidrmap.go new file mode 100644 index 000000000..d636ee8a4 --- /dev/null +++ b/pkg/providers/gtm/data_akamai_gtm_cidrmap.go @@ -0,0 +1,203 @@ +package gtm + +import ( + "context" + "fmt" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/gtm" + "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +type cidrmapDataSource struct { + meta meta.Meta +} + +type cidrmapDataSourceModel struct { + ID types.String `tfsdk:"id"` + Domain types.String `tfsdk:"domain"` + Name types.String `tfsdk:"map_name"` + DefaultDatacenter *defaultDatacenter `tfsdk:"default_datacenter"` + Assignments []cidrMapAssignment `tfsdk:"assignments"` + Links []link `tfsdk:"links"` +} + +var ( + _ datasource.DataSource = &cidrmapDataSource{} + _ datasource.DataSourceWithConfigure = &cidrmapDataSource{} +) + +// NewGTMCidrmapDataSource returns a new GTM cidrmap data source +func NewGTMCidrmapDataSource() datasource.DataSource { return &cidrmapDataSource{} } + +func (d *cidrmapDataSource) Metadata(_ context.Context, _ datasource.MetadataRequest, response *datasource.MetadataResponse) { + response.TypeName = "akamai_gtm_cidrmap" +} + +func (d *cidrmapDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + return + } + defer func() { + if r := recover(); r != nil { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected meta.Meta, got: %T. Please report this issue to the provider developers.", + req.ProviderData)) + } + }() + d.meta = meta.Must(req.ProviderData) +} + +var ( + cidrmapBlocks = map[string]schema.Block{ + "assignments": schema.ListNestedBlock{ + Description: "Contains information about the CIDR zone groupings of CIDR blocks.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "datacenter_id": schema.Int64Attribute{ + Computed: true, + Description: "A unique identifier for an existing data center in the domain.", + }, + "blocks": schema.SetAttribute{ + Computed: true, + Description: "Specifies an array of CIDR blocks.", + ElementType: types.StringType, + }, + "nickname": schema.StringAttribute{ + Computed: true, + Description: "A descriptive label for the CIDR zone group.", + }, + }, + }, + }, + "default_datacenter": schema.SingleNestedBlock{ + Description: "A placeholder for all other CIDR zones, CIDR blocks not found in these CIDR zones.", + Attributes: map[string]schema.Attribute{ + "datacenter_id": schema.Int64Attribute{ + Computed: true, + Description: "For each property, an identifier for all other CIDR zones' CNAME.", + }, + "nickname": schema.StringAttribute{ + Computed: true, + Description: "A descriptive label for all other CIDR blocks.", + }, + }, + }, + "links": schema.SetNestedBlock{ + Description: "Specifies the URL path that allows direct navigation to the CIDR map.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "rel": schema.StringAttribute{ + Computed: true, + Description: "Indicates the link relationship of the object.", + }, + "href": schema.StringAttribute{ + Computed: true, + Description: "A hypermedia link to the complete URL that uniquely defines a resource.", + }, + }, + }, + }, + } +) + +func (d *cidrmapDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "GTM CIDR map data source.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "Identifier of the data source.", + DeprecationMessage: "Required by the terraform plugin testing framework, always set to `gtm_cidrmap`.", + Computed: true, + }, + "domain": schema.StringAttribute{ + Required: true, + Description: "GTM domain name.", + }, + "map_name": schema.StringAttribute{ + Required: true, + Description: "A descriptive label for the CIDR map.", + }, + }, + Blocks: cidrmapBlocks, + } +} + +func (d *cidrmapDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + tflog.Debug(ctx, "GTM Cidrmap DataSource Read") + + var data cidrmapDataSourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + client := frameworkInst.Client(d.meta) + cidrMap, err := client.GetCidrMap(ctx, data.Name.ValueString(), data.Domain.ValueString()) + if err != nil { + resp.Diagnostics.AddError("fetching GTM CIDRmap failed: ", err.Error()) + return + } + + diags := data.setAttributes(ctx, cidrMap) + + if diags.HasError() { + resp.Diagnostics.Append(diags...) + return + } + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func (m *cidrmapDataSourceModel) setAttributes(ctx context.Context, cidrmap *gtm.CidrMap) diag.Diagnostics { + m.Name = types.StringValue(cidrmap.Name) + m.setDefaultDatacenter(cidrmap.DefaultDatacenter) + m.setLinks(cidrmap.Links) + diags := m.setAssignments(ctx, cidrmap.Assignments) + if diags.HasError() { + return diags + } + m.ID = types.StringValue("gtm_cidrmap") + + return nil +} + +func (m *cidrmapDataSourceModel) setDefaultDatacenter(b *gtm.DatacenterBase) { + m.DefaultDatacenter = &defaultDatacenter{ + DatacenterID: types.Int64Value(int64(b.DatacenterId)), + Nickname: types.StringValue(b.Nickname), + } +} + +func (m *cidrmapDataSourceModel) setLinks(links []*gtm.Link) { + for _, l := range links { + linkObj := link{ + Rel: types.StringValue(l.Rel), + Href: types.StringValue(l.Href), + } + + m.Links = append(m.Links, linkObj) + } +} + +func (m *cidrmapDataSourceModel) setAssignments(ctx context.Context, assignments []*gtm.CidrAssignment) diag.Diagnostics { + for _, a := range assignments { + blocks, diags := types.SetValueFrom(ctx, types.StringType, a.Blocks) + if diags.HasError() { + return diags + } + assignmentObj := cidrMapAssignment{ + DatacenterID: types.Int64Value(int64(a.DatacenterId)), + Nickname: types.StringValue(a.Nickname), + Blocks: blocks, + } + + m.Assignments = append(m.Assignments, assignmentObj) + } + return nil +} diff --git a/pkg/providers/gtm/data_akamai_gtm_cidrmap_test.go b/pkg/providers/gtm/data_akamai_gtm_cidrmap_test.go new file mode 100644 index 000000000..41071c117 --- /dev/null +++ b/pkg/providers/gtm/data_akamai_gtm_cidrmap_test.go @@ -0,0 +1,131 @@ +package gtm + +import ( + "fmt" + "regexp" + "testing" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/gtm" + "github.com/akamai/terraform-provider-akamai/v5/pkg/common/testutils" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/stretchr/testify/mock" +) + +func TestDataGTMCidrmap(t *testing.T) { + tests := map[string]struct { + givenTF string + init func(mock *gtm.Mock) + expectedAttributes map[string]string + expectError *regexp.Regexp + }{ + "happy path": { + givenTF: "valid.tf", + init: func(m *gtm.Mock) { + m.On("GetCidrMap", mock.Anything, "mapTest", "test.cidrmap.domain.net").Return(>m.CidrMap{ + Name: "TestName", + DefaultDatacenter: >m.DatacenterBase{ + Nickname: "TestNickname", + DatacenterId: 1, + }, + Assignments: []*gtm.CidrAssignment{{ + DatacenterBase: gtm.DatacenterBase{ + Nickname: "TestNicknameAssignments", + DatacenterId: 1, + }, + Blocks: []string{ + "test1", + "test2", + }, + }}, + Links: []*gtm.Link{{ + Rel: "TestRel", + Href: "TestHref", + }}, + }, nil) + + }, + expectedAttributes: map[string]string{ + "domain": "test.cidrmap.domain.net", + "map_name": "TestName", + "default_datacenter.datacenter_id": "1", + "default_datacenter.nickname": "TestNickname", + "assignments.0.datacenter_id": "1", + "assignments.0.nickname": "TestNicknameAssignments", + "assignments.0.blocks.0": "test1", + "assignments.0.blocks.1": "test2", + "links.0.rel": "TestRel", + "links.0.href": "TestHref", + }, + expectError: nil, + }, + "missing required argument domain": { + givenTF: "missing_domain.tf", + expectError: regexp.MustCompile(`The argument "domain" is required, but no definition was found.`), + }, + "missing required argument map_name": { + givenTF: "missing_map_name.tf", + expectError: regexp.MustCompile(`The argument "map_name" is required, but no definition was found.`), + }, + "error response from api": { + givenTF: "valid.tf", + init: func(m *gtm.Mock) { + m.On("GetCidrMap", mock.Anything, "mapTest", "test.cidrmap.domain.net").Return( + nil, fmt.Errorf("error")) + }, + expectError: regexp.MustCompile("error"), + }, + "no assignments": { + givenTF: "valid.tf", + init: func(m *gtm.Mock) { + m.On("GetCidrMap", mock.Anything, "mapTest", "test.cidrmap.domain.net").Return(>m.CidrMap{ + Name: "TestName", + DefaultDatacenter: >m.DatacenterBase{ + Nickname: "TestNickname", + DatacenterId: 1, + }, + Assignments: []*gtm.CidrAssignment{}, + Links: []*gtm.Link{{ + Rel: "TestRel", + Href: "TestHref", + }}, + }, nil) + }, + expectedAttributes: map[string]string{ + "domain": "test.cidrmap.domain.net", + "map_name": "TestName", + "default_datacenter.datacenter_id": "1", + "default_datacenter.nickname": "TestNickname", + "assignments.#": "0", + "links.0.rel": "TestRel", + "links.0.href": "TestHref", + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + client := >m.Mock{} + if test.init != nil { + test.init(client) + } + var checkFuncs []resource.TestCheckFunc + for k, v := range test.expectedAttributes { + checkFuncs = append(checkFuncs, resource.TestCheckResourceAttr("data.akamai_gtm_cidrmap.gtm_cidrmap", k, v)) + } + + useClient(client, func() { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV5ProviderFactories: testAccProvidersProtoV5, + Steps: []resource.TestStep{{ + Config: testutils.LoadFixtureString(t, fmt.Sprintf("testdata/TestDataGtmCidrmap/%s", test.givenTF)), + Check: resource.ComposeAggregateTestCheckFunc(checkFuncs...), + ExpectError: test.expectError, + }}, + }) + }) + + client.AssertExpectations(t) + }) + } +} diff --git a/pkg/providers/gtm/data_akamai_gtm_domain.go b/pkg/providers/gtm/data_akamai_gtm_domain.go new file mode 100644 index 000000000..f1aa74c41 --- /dev/null +++ b/pkg/providers/gtm/data_akamai_gtm_domain.go @@ -0,0 +1,1680 @@ +package gtm + +import ( + "context" + "fmt" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/gtm" + "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +var _ datasource.DataSource = &domainDataSource{} + +var _ datasource.DataSourceWithConfigure = &domainDataSource{} + +// NewGTMDomainDataSource returns a new GTM domain data source +func NewGTMDomainDataSource() datasource.DataSource { + return &domainDataSource{} +} + +var ( + domainBlock = map[string]schema.Block{ + "status": schema.SingleNestedBlock{ + Description: "Status information for the configuration.", + Attributes: map[string]schema.Attribute{ + "message": schema.StringAttribute{ + Computed: true, + Description: "A notification generated when a change occurs to the domain.", + }, + "change_id": schema.StringAttribute{ + Computed: true, + Description: "A unique identifier generated when a change occurs to the domain.", + }, + "propagation_status": schema.StringAttribute{ + Computed: true, + Description: "Tracks the status of the domain's propagation state.", + }, + "propagation_status_date": schema.StringAttribute{ + Computed: true, + Description: "An ISO 8601 timestamp indicating when a change occurs to the domain.", + }, + "passing_validation": schema.BoolAttribute{ + Computed: true, + Description: "Indicates if the domain validates.", + }, + }, + Blocks: map[string]schema.Block{ + "links": &schema.SetNestedBlock{ + Description: "Specifies the URL path that allows direct navigation to the domain.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "rel": schema.StringAttribute{ + Computed: true, + Description: "Indicates the link relationship of the object.", + }, + "href": schema.StringAttribute{ + Computed: true, + Description: "A hypermedia link to the complete URL that uniquely defines a resource.", + }, + }, + }, + }, + }, + }, + "resources": schema.SetNestedBlock{ + Description: "List of resources associated with the domain.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "aggregation_type": schema.StringAttribute{ + Computed: true, + Description: "Specifies how GTM handles different load numbers when multiple load servers are used for a data center or property.", + }, + "constrained_property": schema.StringAttribute{ + Computed: true, + Description: "Specifies the name of the property that this resource constraints.", + }, + "decay_rate": schema.Float64Attribute{ + Computed: true, + Description: "For internal use only.", + }, + "description": schema.StringAttribute{ + Computed: true, + Description: "A descriptive note to help you track what the resource constraints.", + }, + "host_header": schema.StringAttribute{ + Computed: true, + Description: "Specifies the host header used when fetching the load object.", + }, + "leader_string": schema.StringAttribute{ + Computed: true, + Description: "Specifies the text that comes before the loadObject.", + }, + "least_squares_decay": schema.Float64Attribute{ + Computed: true, + Description: "For internal use only.", + }, + "load_imbalance_percentage": schema.Float64Attribute{ + Computed: true, + Description: "Indicates the percent of load imbalance factor for the domain.", + }, + "max_u_multiplicative_increment": schema.Float64Attribute{ + Computed: true, + Description: "For internal use only.", + }, + "name": schema.StringAttribute{ + Computed: true, + Description: "A descriptive label for the resource.", + }, + "type": schema.StringAttribute{ + Computed: true, + Description: "Indicates the kind of loadObject format used to determine the load on the resource.", + }, + "upper_bound": schema.Int64Attribute{ + Computed: true, + Description: "An optional sanity check that specifies the maximum allowed value for any component of the load object.", + }, + }, + Blocks: map[string]schema.Block{ + "resource_instances": &schema.SetNestedBlock{ + Description: "List of resource instances.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "load_object": schema.StringAttribute{ + Computed: true, + Description: "Identifies the load object file used to report real-time information about the current load, maximum allowable load and target load on each resource.", + }, + "load_object_port": schema.Int64Attribute{ + Computed: true, + Description: "Specifies the TCP port of the loadObject.", + }, + "load_servers": schema.ListAttribute{ + Description: "Specifies the list of servers to requests the load object from.", + Computed: true, + ElementType: types.StringType, + }, + "datacenter_id": schema.Int64Attribute{ + Computed: true, + Description: "A unique identifier for an existing data center in the domain.", + }, + "use_default_load_object": schema.BoolAttribute{ + Computed: true, + Description: "Whether to use default loadObject.", + }, + }, + }, + }, + "links": &schema.SetNestedBlock{ + Description: "Specifies the URL path that allows direct navigation to the resource.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "rel": schema.StringAttribute{ + Computed: true, + Description: "Indicates the link relationship of the object.", + }, + "href": schema.StringAttribute{ + Computed: true, + Description: "A hypermedia link to the complete URL that uniquely defines a resource.", + }, + }, + }, + }, + }, + }, + }, + "properties": schema.SetNestedBlock{ + Description: "List of properties associated with the domain.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "backup_cname": schema.StringAttribute{ + Computed: true, + Description: "Specifies a backup CNAME.", + }, + "backup_ip": schema.StringAttribute{ + Computed: true, + Description: "Specifies a backup IP.", + }, + "balance_by_download_score": schema.BoolAttribute{ + Computed: true, + Description: "Indicates whether download score based load balancing is enabled.", + }, + "cname": schema.StringAttribute{ + Computed: true, + Description: "Indicates the fully qualified name aliased to a particular property.", + }, + "comments": schema.StringAttribute{ + Computed: true, + Description: "Descriptive comments for the property.", + }, + "dynamic_ttl": schema.Int64Attribute{ + Computed: true, + Description: "Indicates the TTL in seconds for records that might change dynamically based on liveness and load balancing.", + }, + "failover_delay": schema.Int64Attribute{ + Computed: true, + Description: "Specifies the failover delay in seconds.", + }, + "failback_delay": schema.Int64Attribute{ + Computed: true, + Description: "Specifies the failback delay in seconds.", + }, + "ghost_demand_reporting": schema.BoolAttribute{ + Computed: true, + Description: "Whether an alternate way to collect load feedback from a GTM Performance domain is enabled.", + }, + "handout_mode": schema.StringAttribute{ + Computed: true, + Description: "Specifies how IPs are returned when more than one IP is alive and available.", + }, + "handout_limit": schema.Int64Attribute{ + Computed: true, + Description: "Indicates the limit for the number of live IPs handed out to a DNS request.", + }, + "health_max": schema.Float64Attribute{ + Computed: true, + Description: "Defines the absolute limit beyond which IPs are declared unhealthy.", + }, + "health_multiplier": schema.Float64Attribute{ + Computed: true, + Description: "Configures a cutoff value that is computed from the median scores.", + }, + "health_threshold": schema.Float64Attribute{ + Computed: true, + Description: "Configures a cutoff value that is computed from the median scores.", + }, + "last_modified": schema.StringAttribute{ + Computed: true, + Description: "An ISO 8601 timestamp that indicates when the property was last changed.", + }, + "load_imbalance_percentage": schema.Float64Attribute{ + Computed: true, + Description: "Indicates the percent of load imbalance factor for the domain.", + }, + "map_name": schema.StringAttribute{ + Computed: true, + Description: "A descriptive label for a geographic or a CIDR map that's required if the property is either geographic or cidrmapping.", + }, + "max_unreachable_penalty": schema.Int64Attribute{ + Computed: true, + Description: "For performance domains, this specifies a penalty value that's added to liveness test scores when data centers show an aggregated loss fraction higher than the penalty value.", + }, + "min_live_fraction": schema.Float64Attribute{ + Computed: true, + Description: "Specifies what fraction of the servers need to respond to requests so GTM considers the data center up and able to receive traffic.", + }, + "name": schema.StringAttribute{ + Computed: true, + Description: "A descriptive label for the property.", + }, + "score_aggregation_type": schema.StringAttribute{ + Computed: true, + Description: "Specifies how GTM aggregates liveness test scores across different tests, when multiple tests are configured.", + }, + "stickness_bonus_constant": schema.Int64Attribute{ + Computed: true, + Description: "Specifies a percentage used to configure data center affinity.", + }, + "stickness_bonus_percentage": schema.Int64Attribute{ + Computed: true, + Description: "Specifies a percentage used to configure data center affinity.", + }, + "static_ttl": schema.Int64Attribute{ + Computed: true, + Description: "Specifies the TTL in seconds for static resource records that don't change based on the requesting name server IP.", + }, + "type": schema.StringAttribute{ + Computed: true, + Description: "Specifies the load balancing behvior for the property.", + }, + "unreachable_threshold": schema.Float64Attribute{ + Computed: true, + Description: "For performance domains, this specifies a penalty value that's added to liveness test scores when data centers have an aggregated loss fraction higher than this value.", + }, + "use_computed_targets": schema.BoolAttribute{ + Computed: true, + Description: "For load-feedback domains only, this specifies that you want GTM to automatically compute target load.", + }, + "ipv6": schema.BoolAttribute{ + Computed: true, + Description: "Indicates the type of IP address handed out by a property.", + }, + "weighted_hash_bits_for_ipv4": schema.Int64Attribute{ + Computed: true, + Description: "For weighted hashed properties, how many leading bits of the client nameserver IP address to include when computing a hash for picking a datacenter for a client nameserver using IPv4; the default value is 32 (the entire address).", + }, + "weighted_hash_bits_for_ipv6": schema.Int64Attribute{ + Computed: true, + Description: "For weighted hashed properties, how many leading bits of the client nameserver IP address to include when computing a hash for picking a datacenter for a client nameserver using IPv6; the default value is 128 (the entire address).", + }, + }, + Blocks: map[string]schema.Block{ + "links": schema.SetNestedBlock{ + Description: "Provides a URL path that allows direct navigation to the property.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "rel": schema.StringAttribute{ + Computed: true, + Description: "Indicates the link relationship of the object.", + }, + "href": schema.StringAttribute{ + Computed: true, + Description: "A hypermedia link to the complete URL that uniquely defines a resource.", + }, + }, + }, + }, + "static_rr_sets": schema.SetNestedBlock{ + Description: "Contains static recordsets.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "type": schema.StringAttribute{ + Computed: true, + Description: "The record type.", + }, + "ttl": schema.Int64Attribute{ + Computed: true, + Description: "The number of seconds that this record should live in a resolver's cache before being refetched.", + }, + "rdata": schema.ListAttribute{ + Description: "An array of data strings, representing multiple records within a set.", + Computed: true, + ElementType: types.StringType, + }, + }, + }, + }, + "traffic_targets": schema.SetNestedBlock{ + Description: "Traffic targets for the property.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "datacenter_id": schema.Int64Attribute{ + Computed: true, + Description: "A unique identifier for an existing data center in the domain.", + }, + "enabled": schema.BoolAttribute{ + Computed: true, + Description: "Indicates whether the traffic target is used.", + }, + "weight": schema.Float64Attribute{ + Computed: true, + Description: "Specifies the traffic target weight for the target.", + }, + "handout_cname": schema.StringAttribute{ + Computed: true, + Description: "Specifies an optional data center for the property.", + }, + "name": schema.StringAttribute{ + Computed: true, + Description: "An alternative label for the traffic target.", + }, + "servers": schema.ListAttribute{ + Description: "Identifies the IP address or the hostnames of the servers.", + Computed: true, + ElementType: types.StringType, + }, + }, + }, + }, + "liveness_tests": schema.SetNestedBlock{ + Description: "Contains information about liveness tests.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "answers_required": schema.BoolAttribute{ + Computed: true, + Description: "If testObjectProtocol is DNS, DOH or DOT, requires an answer to the DNS query to be considered a success.", + }, + "disabled": schema.BoolAttribute{ + Computed: true, + Description: "Disables the liveness test.", + }, + "disable_nonstandard_port_warning": schema.BoolAttribute{ + Computed: true, + Description: "Disables warnings when non-standard ports are used.", + }, + "error_penalty": schema.Float64Attribute{ + Computed: true, + Description: "Specifies the score that's reported if the liveness test encounters an error other than timeout, such as connection refused, and 404.", + }, + "http_error3xx": schema.BoolAttribute{ + Computed: true, + Description: "Treats a 3xx HTTP response as a failure if the testObjectProtocol is http, https or ftp.", + }, + "http_error4xx": schema.BoolAttribute{ + Computed: true, + Description: "Treats a 4xx HTTP response as a failure if the testObjectProtocol is http, https or ftp.", + }, + "http_error5xx": schema.BoolAttribute{ + Computed: true, + Description: "Treats a 5xx HTTP response as a failure if the testObjectProtocol is http, https or ftp.", + }, + "name": schema.StringAttribute{ + Computed: true, + Description: "A descriptive name for the liveness test.", + }, + "peer_certificate_verification": schema.BoolAttribute{ + Computed: true, + Description: "Validates the origin certificate. Applies only to tests with testObjectProtocol of https.", + }, + "request_string": schema.StringAttribute{ + Computed: true, + Description: "Specifies a request string.", + }, + "response_string": schema.StringAttribute{ + Computed: true, + Description: "Specifies a response string.", + }, + "resource_type": schema.StringAttribute{ + Computed: true, + Description: "Specifies the query type, if testObjectProtocol is DNS.", + }, + "recursion_requested": schema.BoolAttribute{ + Computed: true, + Description: "Indicates that if testObjectProtocol is DNS, DOH or DOT, the DNS query is recursive.", + }, + "test_interval": schema.Int64Attribute{ + Computed: true, + Description: "Indicates the interval at which the liveness test is run, in seconds.", + }, + "test_object": schema.StringAttribute{ + Computed: true, + Description: "Specifies the static text that acts as a stand-in for the data that you're sending on the network.", + }, + "test_object_port": schema.Int64Attribute{ + Computed: true, + Description: "Specifies the port number for the testObject.", + }, + "test_object_protocol": schema.StringAttribute{ + Computed: true, + Description: "Specifies the test protocol.", + }, + "test_object_username": schema.StringAttribute{ + Computed: true, + Description: "A descriptive name for the testObject.", + }, + "test_object_password": schema.StringAttribute{ + Computed: true, + Description: "Specifies the test object's password.", + }, + "test_timeout": schema.Float64Attribute{ + Computed: true, + Description: "Specifies the duration of the liveness test before it fails.", + }, + "timeout_penalty": schema.Float64Attribute{ + Computed: true, + Description: "Specifies the timeout penalty score.", + }, + "ssl_client_certificate": schema.StringAttribute{ + Computed: true, + Description: "Indicates a base64-encoded certificate.", + }, + "ssl_client_private_key": schema.StringAttribute{ + Computed: true, + Description: "Indicates a base64-encoded private key.", + }, + }, + Blocks: map[string]schema.Block{ + "http_headers": &schema.SetNestedBlock{ + Description: "List of HTTP headers for the liveness test.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Computed: true, + Description: "Name of the HTTP header.", + }, + "value": schema.StringAttribute{ + Computed: true, + Description: "Value of the HTTP header.", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "datacenters": schema.SetNestedBlock{ + Description: "List of data centers associated with the domain.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "datacenter_id": schema.Int64Attribute{ + Computed: true, + Description: "A unique identifier for an existing data center in the domain.", + }, + "nickname": schema.StringAttribute{ + Computed: true, + Description: "A descriptive label for the datacenter.", + }, + "score_penalty": schema.Int64Attribute{ + Computed: true, + Description: "Influences the score for a datacenter.", + }, + "city": schema.StringAttribute{ + Computed: true, + Description: "The name of the city where the data center is located.", + }, + "state_or_province": schema.StringAttribute{ + Computed: true, + Description: "Specifies a two-letter ISO 3166 country code for the state of province, where the data center is located.", + }, + "country": schema.StringAttribute{ + Computed: true, + Description: "A two-letter ISO 3166 country code that specifies the country where the data center is located.", + }, + "latitude": schema.Float64Attribute{ + Computed: true, + Description: "Specifies the geographic latitude of the data center's position.", + }, + "longitude": schema.Float64Attribute{ + Computed: true, + Description: "Specifies the geographic longitude of the data center's position.", + }, + "clone_of": schema.Int64Attribute{ + Computed: true, + Description: "Identifies the data center's ID of which this data center is a clone.", + }, + "virtual": schema.BoolAttribute{ + Computed: true, + Description: "Indicates whether or not the data center is virtual or physical.", + }, + "continent": schema.StringAttribute{ + Computed: true, + Description: "A two-letter code that specifies the continent where the data center maps to.", + }, + "server_monitor_pool": schema.StringAttribute{ + Computed: true, + Description: "The name of the pool from which servermonitors are drawn for liveness tests in this datacenter. If omitted (null), the domain-wide default is used. (If no domain-wide default is specified, the pool used is all servermonitors in the same continent as the datacenter.).", + }, + "cloud_server_targeting": schema.BoolAttribute{ + Computed: true, + Description: "Balances load between two or more servers in a cloud environment.", + }, + "cloud_server_host_header_override": schema.BoolAttribute{ + Computed: true, + Description: "Balances load between two or more servers in a cloud environment.", + }, + }, + Blocks: map[string]schema.Block{ + "links": schema.SetNestedBlock{ + Description: "Provides a URL path that allows direct navigation to a data center.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "rel": schema.StringAttribute{ + Computed: true, + Description: "Indicates the link relationship of the object.", + }, + "href": schema.StringAttribute{ + Computed: true, + Description: "A hypermedia link to the complete URL that uniquely defines a resource.", + }, + }, + }, + }, + "default_load_object": schema.SetNestedBlock{ + Description: "Specifies the load reporting interface between you and the GTM system.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "load_object": schema.StringAttribute{ + Computed: true, + Description: "Specifies the load object that GTM requests.", + }, + "load_object_port": schema.Int64Attribute{ + Computed: true, + Description: "Specifies the TCP port to connect to when requesting the load object.", + }, + "load_servers": schema.ListAttribute{ + Computed: true, + ElementType: types.StringType, + Description: "Specifies the list of servers to requests the load object from.", + }, + }, + }, + }, + }, + }, + }, + "geographic_maps": schema.SetNestedBlock{ + Description: "List of geographic maps associated with the domain.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Computed: true, + Description: "A descriptive label for the geographic map.", + }, + }, + Blocks: map[string]schema.Block{ + "assignments": schema.SetNestedBlock{ + Description: "Contains information about the geographic zone groupings of countries.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "countries": schema.SetAttribute{ + Computed: true, + ElementType: types.StringType, + Description: "Specifies an array of two-letter ISO 3166 `country` codes.", + }, + "datacenter_id": schema.Int64Attribute{ + Computed: true, + Description: "A unique identifier for an existing data center in the domain.", + }, + "nickname": schema.StringAttribute{ + Computed: true, + Description: "A descriptive label for all other AS zones.", + }, + }, + }, + }, + "links": schema.SetNestedBlock{ + Description: "Specifies the URL path that allows direct navigation to the geographic map.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "rel": schema.StringAttribute{ + Computed: true, + Description: "Indicates the link relationship of the object.", + }, + "href": schema.StringAttribute{ + Computed: true, + Description: "A hypermedia link to the complete URL that uniquely defines a resource.", + }, + }, + }, + }, + "default_datacenter": schema.SingleNestedBlock{ + Description: "A placeholder for all other geographic zones, countries not found in these geographic zones.", + Attributes: map[string]schema.Attribute{ + "datacenter_id": schema.Int64Attribute{ + Computed: true, + Description: "An identifier for all other geographic zones' CNAME.", + }, + "nickname": schema.StringAttribute{ + Computed: true, + Description: "A descriptive label for all other geographic zones.", + }, + }, + }, + }, + }, + }, + "cidr_maps": schema.SetNestedBlock{ + Description: "Contains information about the set of CIDR maps assigned to this domain.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Computed: true, + Description: "Unique name for the CIDR map.", + }, + }, + Blocks: map[string]schema.Block{ + "assignments": schema.SetNestedBlock{ + Description: "Contains information about the CIDR zone groupings of CIDR blocks.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "blocks": schema.SetAttribute{ + Computed: true, + ElementType: types.StringType, + Description: "Specifies an array of CIDR blocks.", + }, + "datacenter_id": schema.Int64Attribute{ + Computed: true, + Description: "A unique identifier for an existing data center in the domain.", + }, + "nickname": schema.StringAttribute{ + Computed: true, + Description: "A descriptive label for all other AS zones.", + }, + }, + }, + }, + "default_datacenter": schema.SingleNestedBlock{ + Description: "A placeholder for all other CIDR zones, CIDR blocks not found in these CIDR zones.", + Attributes: map[string]schema.Attribute{ + "datacenter_id": schema.Int64Attribute{ + Computed: true, + Description: "For each property, an identifier for all other CIDR zones' CNAME.", + }, + "nickname": schema.StringAttribute{ + Computed: true, + Description: "A descriptive label for all other CIDR blocks.", + }, + }, + }, + "links": schema.SetNestedBlock{ + Description: "Specifies the URL path that allows direct navigation to the CIDR map.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "rel": schema.StringAttribute{ + Computed: true, + Description: "Indicates the link relationship of the object.", + }, + "href": schema.StringAttribute{ + Computed: true, + Description: "A hypermedia link to the complete URL that uniquely defines a resource.", + }, + }, + }, + }, + }, + }, + }, + "as_maps": schema.SetNestedBlock{ + Description: "Contains information about the set of AS maps assigned to this domain.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Computed: true, + Description: "A descriptive label for the AS map.", + }, + }, + Blocks: map[string]schema.Block{ + "assignments": schema.SetNestedBlock{ + Description: "Contains information about the AS zone groupings of AS IDs.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "as_numbers": schema.SetAttribute{ + Computed: true, + ElementType: types.Int64Type, + Description: "Specifies an array of AS numbers.", + }, + "datacenter_id": schema.Int64Attribute{ + Computed: true, + Description: "A unique identifier for an existing data center in the domain.", + }, + "nickname": schema.StringAttribute{ + Computed: true, + Description: "A descriptive label for all other AS zones.", + }, + }, + }, + }, + "default_datacenter": schema.SingleNestedBlock{ + Description: "A placeholder for all other AS zones, AS IDs not found in these AS zones.", + Attributes: map[string]schema.Attribute{ + "datacenter_id": schema.Int64Attribute{ + Computed: true, + Description: "For each property, an identifier for all other AS zones' CNAME.", + }, + "nickname": schema.StringAttribute{ + Computed: true, + Description: "A descriptive label for all other AS zones.", + }, + }, + }, + "links": schema.SetNestedBlock{ + Description: "Specifies the URL path that allows direct navigation to the As map.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "rel": schema.StringAttribute{ + Computed: true, + Description: "Indicates the link relationship of the object.", + }, + "href": schema.StringAttribute{ + Computed: true, + Description: "A hypermedia link to the complete URL that uniquely defines a resource.", + }, + }, + }, + }, + }, + }, + }, + "links": schema.SetNestedBlock{ + Description: "Provides a URL path that allows direct navigation to the domain.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "rel": schema.StringAttribute{ + Computed: true, + Description: "Indicates the link relationship of the object.", + }, + "href": schema.StringAttribute{ + Computed: true, + Description: "A hypermedia link to the complete URL that uniquely defines a resource.", + }, + }, + }, + }, + } +) + +type domainDataSource struct { + meta meta.Meta +} + +type ( + domainDataSourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + CNameCoalescingEnabled types.Bool `tfsdk:"cname_coalescing_enabled"` + DefaultErrorPenalty types.Int64 `tfsdk:"default_error_penalty"` + DefaultHealthMax types.Float64 `tfsdk:"default_health_max"` + DefaultHealthMultiplier types.Float64 `tfsdk:"default_health_multiplier"` + DefaultHealthThreshold types.Float64 `tfsdk:"default_health_threshold"` + DefaultMaxUnreachablePenalty types.Int64 `tfsdk:"default_max_unreachable_penalty"` + DefaultSSLClientCertificate types.String `tfsdk:"default_ssl_client_certificate"` + DefaultSSLClientPrivateKey types.String `tfsdk:"default_ssl_client_private_key"` + DefaultTimeoutPenalty types.Int64 `tfsdk:"default_timeout_penalty"` + DefaultUnreachableThreshold types.Float64 `tfsdk:"default_unreachable_threshold"` + EmailNotificationList types.List `tfsdk:"email_notification_list"` + EndUserMappingEnabled types.Bool `tfsdk:"end_user_mapping_enabled"` + LastModified types.String `tfsdk:"last_modified"` + LastModifiedBy types.String `tfsdk:"last_modified_by"` + LoadFeedback types.Bool `tfsdk:"load_feedback"` + MapUpdateInterval types.Int64 `tfsdk:"map_update_interval"` + MaxProperties types.Int64 `tfsdk:"max_properties"` + MaxResources types.Int64 `tfsdk:"max_resources"` + MaxTestTimeout types.Float64 `tfsdk:"max_test_timeout"` + MaxTTL types.Int64 `tfsdk:"max_ttl"` + MinPingableRegionFraction types.Float64 `tfsdk:"min_pingable_region_fraction"` + MinTestInterval types.Int64 `tfsdk:"min_test_interval"` + MinTTL types.Int64 `tfsdk:"min_ttl"` + ModificationComments types.String `tfsdk:"modification_comments"` + RoundRobinPrefix types.String `tfsdk:"round_robin_prefix"` + ServerMonitorPool types.String `tfsdk:"server_monitor_pool"` + Type types.String `tfsdk:"type"` + Status *status `tfsdk:"status"` + LoadImbalancePercentage types.Float64 `tfsdk:"load_imbalance_percentage"` + Resources []domainResource `tfsdk:"resources"` + Properties []property `tfsdk:"properties"` + Datacenters []datacenter `tfsdk:"datacenters"` + GeographicMaps []geographicMap `tfsdk:"geographic_maps"` + CIDRMaps []cidrMap `tfsdk:"cidr_maps"` + ASMaps []asMap `tfsdk:"as_maps"` + Links []link `tfsdk:"links"` + } + + status struct { + Message types.String `tfsdk:"message"` + ChangeID types.String `tfsdk:"change_id"` + PropagationStatus types.String `tfsdk:"propagation_status"` + PropagationStatusDate types.String `tfsdk:"propagation_status_date"` + PassingValidation types.Bool `tfsdk:"passing_validation"` + Links []link `tfsdk:"links"` + } + + domainResource struct { + AggregationType types.String `tfsdk:"aggregation_type"` + ConstrainedProperty types.String `tfsdk:"constrained_property"` + DecayRate types.Float64 `tfsdk:"decay_rate"` + Description types.String `tfsdk:"description"` + HostHeader types.String `tfsdk:"host_header"` + LeaderString types.String `tfsdk:"leader_string"` + LeastSquaresDecay types.Float64 `tfsdk:"least_squares_decay"` + LoadImbalancePercentage types.Float64 `tfsdk:"load_imbalance_percentage"` + MaxUMultiplicativeIncrement types.Float64 `tfsdk:"max_u_multiplicative_increment"` + Name types.String `tfsdk:"name"` + ResourceInstances []resourceInstance `tfsdk:"resource_instances"` + Type types.String `tfsdk:"type"` + UpperBound types.Int64 `tfsdk:"upper_bound"` + Links []link `tfsdk:"links"` + } + + livenessTest struct { + AnswersRequired types.Bool `tfsdk:"answers_required"` + Disabled types.Bool `tfsdk:"disabled"` + DisableNonstandardPortWarning types.Bool `tfsdk:"disable_nonstandard_port_warning"` + ErrorPenalty types.Float64 `tfsdk:"error_penalty"` + HTTPHeaders []httpHeader `tfsdk:"http_headers"` + HTTPError3xx types.Bool `tfsdk:"http_error3xx"` + HTTPError4xx types.Bool `tfsdk:"http_error4xx"` + HTTPError5xx types.Bool `tfsdk:"http_error5xx"` + Name types.String `tfsdk:"name"` + PeerCertificateVerification types.Bool `tfsdk:"peer_certificate_verification"` + RequestString types.String `tfsdk:"request_string"` + ResponseString types.String `tfsdk:"response_string"` + ResourceType types.String `tfsdk:"resource_type"` + RecursionRequested types.Bool `tfsdk:"recursion_requested"` + TestInterval types.Int64 `tfsdk:"test_interval"` + TestObject types.String `tfsdk:"test_object"` + TestObjectPort types.Int64 `tfsdk:"test_object_port"` + TestObjectProtocol types.String `tfsdk:"test_object_protocol"` + TestObjectUsername types.String `tfsdk:"test_object_username"` + TestObjectPassword types.String `tfsdk:"test_object_password"` + TestTimeout types.Float64 `tfsdk:"test_timeout"` + TimeoutPenalty types.Float64 `tfsdk:"timeout_penalty"` + SSLClientCertificate types.String `tfsdk:"ssl_client_certificate"` + SSLClientPrivateKey types.String `tfsdk:"ssl_client_private_key"` + } + + httpHeader struct { + Name types.String `tfsdk:"name"` + Value types.String `tfsdk:"value"` + } + + staticRRSet struct { + Type types.String `tfsdk:"type"` + TTL types.Int64 `tfsdk:"ttl"` + RData types.List `tfsdk:"rdata"` + } + + trafficTarget struct { + DatacenterID types.Int64 `tfsdk:"datacenter_id"` + Enabled types.Bool `tfsdk:"enabled"` + Weight types.Float64 `tfsdk:"weight"` + HandoutCNAME types.String `tfsdk:"handout_cname"` + Name types.String `tfsdk:"name"` + Servers types.List `tfsdk:"servers"` + } + + property struct { + BackupCNAME types.String `tfsdk:"backup_cname"` + BackupIP types.String `tfsdk:"backup_ip"` + BalanceByDownloadScore types.Bool `tfsdk:"balance_by_download_score"` + CName types.String `tfsdk:"cname"` + Comments types.String `tfsdk:"comments"` + DynamicTTL types.Int64 `tfsdk:"dynamic_ttl"` + FailoverDelay types.Int64 `tfsdk:"failover_delay"` + FailbackDelay types.Int64 `tfsdk:"failback_delay"` + GhostDemandReporting types.Bool `tfsdk:"ghost_demand_reporting"` + HandoutMode types.String `tfsdk:"handout_mode"` + HandoutLimit types.Int64 `tfsdk:"handout_limit"` + HealthMax types.Float64 `tfsdk:"health_max"` + HealthMultiplier types.Float64 `tfsdk:"health_multiplier"` + HealthThreshold types.Float64 `tfsdk:"health_threshold"` + LastModified types.String `tfsdk:"last_modified"` + LivenessTests []livenessTest `tfsdk:"liveness_tests"` + LoadImbalancePercentage types.Float64 `tfsdk:"load_imbalance_percentage"` + MapName types.String `tfsdk:"map_name"` + MaxUnreachablePenalty types.Int64 `tfsdk:"max_unreachable_penalty"` + MinLiveFraction types.Float64 `tfsdk:"min_live_fraction"` + Name types.String `tfsdk:"name"` + ScoreAggregationType types.String `tfsdk:"score_aggregation_type"` + StickinessBonusConstant types.Int64 `tfsdk:"stickness_bonus_constant"` + StickinessBonusPercentage types.Int64 `tfsdk:"stickness_bonus_percentage"` + StaticTTL types.Int64 `tfsdk:"static_ttl"` + StaticRRSets []staticRRSet `tfsdk:"static_rr_sets"` + TrafficTargets []trafficTarget `tfsdk:"traffic_targets"` + Type types.String `tfsdk:"type"` + UnreachableThreshold types.Float64 `tfsdk:"unreachable_threshold"` + UseComputedTargets types.Bool `tfsdk:"use_computed_targets"` + IPv6 types.Bool `tfsdk:"ipv6"` + WeightedHashBitsForIPv4 types.Int64 `tfsdk:"weighted_hash_bits_for_ipv4"` + WeightedHashBitsForIPv6 types.Int64 `tfsdk:"weighted_hash_bits_for_ipv6"` + Links []link `tfsdk:"links"` + } + + loadObject struct { + LoadObject types.String `tfsdk:"load_object"` + LoadObjectPort types.Int64 `tfsdk:"load_object_port"` + LoadServers types.List `tfsdk:"load_servers"` + } + + datacenter struct { + DatacenterID types.Int64 `tfsdk:"datacenter_id"` + Nickname types.String `tfsdk:"nickname"` + ScorePenalty types.Int64 `tfsdk:"score_penalty"` + City types.String `tfsdk:"city"` + StateOrProvince types.String `tfsdk:"state_or_province"` + Country types.String `tfsdk:"country"` + Latitude types.Float64 `tfsdk:"latitude"` + Longitude types.Float64 `tfsdk:"longitude"` + CloneOf types.Int64 `tfsdk:"clone_of"` + Virtual types.Bool `tfsdk:"virtual"` + DefaultLoadObject []loadObject `tfsdk:"default_load_object"` + Continent types.String `tfsdk:"continent"` + ServerMonitorPool types.String `tfsdk:"server_monitor_pool"` + CloudServerTargeting types.Bool `tfsdk:"cloud_server_targeting"` + CloudServerHostHeaderOverride types.Bool `tfsdk:"cloud_server_host_header_override"` + Links []link `tfsdk:"links"` + } + + geographicMap struct { + Name types.String `tfsdk:"name"` + Assignments []geographicMapAssignment `tfsdk:"assignments"` + DefaultDatacenter defaultDatacenter `tfsdk:"default_datacenter"` + Links []link `tfsdk:"links"` + } + + geographicMapAssignment struct { + Countries types.Set `tfsdk:"countries"` + DatacenterID types.Int64 `tfsdk:"datacenter_id"` + Nickname types.String `tfsdk:"nickname"` + } + + cidrMap struct { + Name types.String `tfsdk:"name"` + Assignments []cidrMapAssignment `tfsdk:"assignments"` + DefaultDatacenter defaultDatacenter `tfsdk:"default_datacenter"` + Links []link `tfsdk:"links"` + } + + cidrMapAssignment struct { + DatacenterID types.Int64 `tfsdk:"datacenter_id"` + Nickname types.String `tfsdk:"nickname"` + Blocks types.Set `tfsdk:"blocks"` + } + + asMap struct { + Name types.String `tfsdk:"name"` + Assignments []asMapAssignment `tfsdk:"assignments"` + DefaultDatacenter defaultDatacenter `tfsdk:"default_datacenter"` + Links []link `tfsdk:"links"` + } + + asMapAssignment struct { + DatacenterID types.Int64 `tfsdk:"datacenter_id"` + Nickname types.String `tfsdk:"nickname"` + ASNumbers []types.Int64 `tfsdk:"as_numbers"` + } + + defaultDatacenter struct { + DatacenterID types.Int64 `tfsdk:"datacenter_id"` + Nickname types.String `tfsdk:"nickname"` + } +) + +// Schema is used to define data source's terraform schema +func (d *domainDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, response *datasource.SchemaResponse) { + response.Schema = schema.Schema{ + MarkdownDescription: "GTM Domain data source", + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + MarkdownDescription: "The full GTM domain name.", + Required: true, + }, + "cname_coalescing_enabled": schema.BoolAttribute{ + MarkdownDescription: "If enabled, GTM collapses CNAME redirections in DNS answers when it knows the target of the CNAME.", + Computed: true, + }, + "default_error_penalty": schema.Int64Attribute{ + MarkdownDescription: "Specifies the download penalty score.", + Computed: true, + }, + "default_health_max": schema.Float64Attribute{ + MarkdownDescription: "Default value for healthMax if none specified at the property level.", + Computed: true, + }, + "default_health_multiplier": schema.Float64Attribute{ + MarkdownDescription: "Default value for healthMultiplier if none specified at the property level.", + Computed: true, + }, + "default_health_threshold": schema.Float64Attribute{ + MarkdownDescription: "Default value for healthThreshold if none specified at the property level.", + Computed: true, + }, + "default_max_unreachable_penalty": schema.Int64Attribute{ + MarkdownDescription: "Applicable only to Performance Plus domains.", + Computed: true, + }, + "default_ssl_client_certificate": schema.StringAttribute{ + MarkdownDescription: "Specifies an optional Base64-encoded certificate that corresponds with the private key for TLS-based liveness tests (HTTPS, SMTPS, POPS, and TCPS).", + Computed: true, + }, + "default_ssl_client_private_key": schema.StringAttribute{ + MarkdownDescription: "Specifies an optional Base64-encoded private key that corresponds with the TLS certificate for TLS-based liveness tests (HTTPS, SMTPS, POPS, and TCPS).", + Computed: true, + }, + "default_timeout_penalty": schema.Int64Attribute{ + MarkdownDescription: "Specifies the timeout penalty score.", + Computed: true, + }, + "default_unreachable_threshold": schema.Float64Attribute{ + MarkdownDescription: "Applicable only to Performance Plus domains. If the fraction of core points that cannot reach the datacenter (they have 100% packet loss to it) exceeds this threshold, a score penalty is added to liveness scores for servers in the datacenter; the penalty is equal to maxUnreachablePenalty * (fractionUnreachable - unreachableThreshold) / (1 - unreachableThreshold). ", + Computed: true, + }, + "email_notification_list": schema.ListAttribute{ + ElementType: types.StringType, + Computed: true, + MarkdownDescription: "Email addresses where notifications will be sent.", + }, + "end_user_mapping_enabled": schema.BoolAttribute{ + MarkdownDescription: "A boolean indicating whether whether the GTM Domain is using end user client subnet mapping.", + Computed: true, + }, + "last_modified": schema.StringAttribute{ + MarkdownDescription: "An ISO 8601 timestamp that indicates the time of the last domain change.", + Computed: true, + }, + "last_modified_by": schema.StringAttribute{ + MarkdownDescription: "The email address of the administrator who made the last change to the domain.", + Computed: true, + }, + "load_feedback": schema.BoolAttribute{ + MarkdownDescription: "Indicates whether you're using resources to control load balancing.", + Computed: true, + }, + "map_update_interval": schema.Int64Attribute{ + MarkdownDescription: "How often new maps are generated for performance domains. Not applicable to non-performance domains.", + Computed: true, + }, + "max_properties": schema.Int64Attribute{ + MarkdownDescription: "Maximum amount of properties that may be associated with the domain.", + Computed: true, + }, + "max_resources": schema.Int64Attribute{ + MarkdownDescription: "Maximum amount of resources that may be associated with the domain.", + Computed: true, + }, + "max_test_timeout": schema.Float64Attribute{ + MarkdownDescription: "Maximum timeout for a test.", + Computed: true, + }, + "max_ttl": schema.Int64Attribute{ + MarkdownDescription: "The largest TTL allowed. Configurations specifying TTLs greater than this will fail validation.", + Computed: true, + }, + "min_pingable_region_fraction": schema.Float64Attribute{ + MarkdownDescription: "Applicable only to Performance Plus domains. If set (nonzero), any core point that cannot ping more than this fraction of datacenters is rejected and will not be mapped by ping scores.", + Computed: true, + }, + "min_test_interval": schema.Int64Attribute{ + MarkdownDescription: "The smallest allowed liveness test interval. Configurations specifying liveness test intervals smaller than this will fail validation.", + Computed: true, + }, + "min_ttl": schema.Int64Attribute{ + MarkdownDescription: "The smallest TTL allowed. Configurations specifying TTLs smaller than this will fail validation.", + Computed: true, + }, + "modification_comments": schema.StringAttribute{ + MarkdownDescription: "A descriptive note about changes to the domain.", + Computed: true, + }, + "round_robin_prefix": schema.StringAttribute{ + MarkdownDescription: "A string that when configured automatically creates a shadow property for each normal property.", + Computed: true, + }, + "server_monitor_pool": schema.StringAttribute{ + MarkdownDescription: "The name of the pool from which servermonitors are drawn for liveness tests in this datacenter. If omitted (null), the domain-wide default is used. (If no domain-wide default is specified, the pool used is all servermonitors in the same continent as the datacenter.)", + Computed: true, + }, + "type": schema.StringAttribute{ + MarkdownDescription: "Specifies the load balancing behavior for the property. ", + Computed: true, + }, + "load_imbalance_percentage": schema.Float64Attribute{ + MarkdownDescription: "Indicates the percent of load imbalance factor (LIF) for the domain.", + Computed: true, + }, + "id": schema.StringAttribute{ + MarkdownDescription: "Identifier of the data source.", + Computed: true, + }, + }, + Blocks: domainBlock, + } +} + +// Configure configures data source at the beginning of the lifecycle +func (d *domainDataSource) Configure(_ context.Context, request datasource.ConfigureRequest, response *datasource.ConfigureResponse) { + if request.ProviderData == nil { + return + } + meta, ok := request.ProviderData.(meta.Meta) + if !ok { + response.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected meta.Meta, got: %T. Please report this issue to the provider developers.", request.ProviderData), + ) + } + d.meta = meta +} + +// Metadata configures data source's meta information +func (d *domainDataSource) Metadata(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "akamai_gtm_domain" +} + +// Read is called when the provider must read data source values in order to update state +func (d *domainDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + tflog.Debug(ctx, "GTM Domain DataSource Read") + var data *domainDataSourceModel + response.Diagnostics.Append(request.Config.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + client := inst.Client(d.meta) + domain, err := client.GetDomain(ctx, data.Name.ValueString()) + if err != nil { + response.Diagnostics.AddError("fetching domain failed", err.Error()) + return + } + + data, diags := populateDomain(ctx, domain) + if diags.HasError() { + response.Diagnostics.Append(diags...) + return + } + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +func populateDomain(ctx context.Context, domain *gtm.Domain) (*domainDataSourceModel, diag.Diagnostics) { + emailNotificationList, diags := types.ListValueFrom(ctx, types.StringType, domain.EmailNotificationList) + if diags.HasError() { + return nil, diags + } + datacenters, diags := getDatacenters(ctx, domain.Datacenters) + if diags.HasError() { + return nil, diags + } + properties, diags := getProperties(ctx, domain.Properties) + if diags.HasError() { + return nil, diags + } + cidrMaps, diags := getCIDRMaps(ctx, domain.CidrMaps) + if diags.HasError() { + return nil, diags + } + geoMaps, diags := getGeographicMaps(ctx, domain.GeographicMaps) + if diags.HasError() { + return nil, diags + } + return &domainDataSourceModel{ + Name: types.StringValue(domain.Name), + CNameCoalescingEnabled: types.BoolValue(domain.CnameCoalescingEnabled), + DefaultErrorPenalty: types.Int64Value(int64(domain.DefaultErrorPenalty)), + DefaultHealthMax: types.Float64Value(domain.DefaultHealthMax), + DefaultHealthMultiplier: types.Float64Value(domain.DefaultHealthMultiplier), + DefaultHealthThreshold: types.Float64Value(domain.DefaultHealthThreshold), + DefaultMaxUnreachablePenalty: types.Int64Value(int64(domain.DefaultMaxUnreachablePenalty)), + DefaultSSLClientCertificate: types.StringValue(domain.DefaultSslClientCertificate), + DefaultSSLClientPrivateKey: types.StringValue(domain.DefaultSslClientPrivateKey), + DefaultTimeoutPenalty: types.Int64Value(int64(domain.DefaultTimeoutPenalty)), + DefaultUnreachableThreshold: types.Float64Value(float64(domain.DefaultUnreachableThreshold)), + EmailNotificationList: emailNotificationList, + EndUserMappingEnabled: types.BoolValue(domain.EndUserMappingEnabled), + LastModified: types.StringValue(domain.LastModified), + LastModifiedBy: types.StringValue(domain.LastModifiedBy), + LoadFeedback: types.BoolValue(domain.LoadFeedback), + MapUpdateInterval: types.Int64Value(int64(domain.MapUpdateInterval)), + MaxProperties: types.Int64Value(int64(domain.MaxProperties)), + MaxResources: types.Int64Value(int64(domain.MaxResources)), + MaxTestTimeout: types.Float64Value(domain.MaxTestTimeout), + MaxTTL: types.Int64Value(domain.MaxTTL), + MinPingableRegionFraction: types.Float64Value(float64(domain.MinPingableRegionFraction)), + MinTestInterval: types.Int64Value(int64(domain.MinTestInterval)), + MinTTL: types.Int64Value(domain.MinTTL), + ModificationComments: types.StringValue(domain.ModificationComments), + RoundRobinPrefix: types.StringValue(domain.RoundRobinPrefix), + ServerMonitorPool: types.StringValue(domain.ServermonitorPool), + Type: types.StringValue(domain.Type), + LoadImbalancePercentage: types.Float64Value(domain.LoadImbalancePercentage), + ID: types.StringValue(domain.Name), + Status: getStatus(domain.Status), + Resources: getResources(domain.Resources), + Properties: properties, + Datacenters: datacenters, + GeographicMaps: geoMaps, + CIDRMaps: cidrMaps, + ASMaps: getASMaps(domain.AsMaps), + Links: getLinks(domain.Links), + }, nil +} + +func getLinks(links []*gtm.Link) []link { + var result []link + if links != nil { + result = make([]link, len(links)) + for i, l := range links { + result[i] = link{ + Rel: types.StringValue(l.Rel), + Href: types.StringValue(l.Href), + } + } + } + return result +} + +func getASMaps(maps []*gtm.AsMap) []asMap { + var result []asMap + for _, am := range maps { + asMapInstance := asMap{ + Name: types.StringValue(am.Name), + } + + if am.Links != nil { + asMapInstance.Links = populateLinks(am.Links) + } + + if am.DefaultDatacenter != nil { + defaultDataCenter := defaultDatacenter{ + Nickname: types.StringValue(am.DefaultDatacenter.Nickname), + DatacenterID: types.Int64Value(int64(am.DefaultDatacenter.DatacenterId)), + } + asMapInstance.DefaultDatacenter = defaultDataCenter + } + if am.Assignments != nil { + asMapInstance.Assignments = make([]asMapAssignment, len(am.Assignments)) + for i, asg := range am.Assignments { + asMapInstance.Assignments[i] = populateASMapAssignment(asg) + } + } + result = append(result, asMapInstance) + } + return result +} + +func getCIDRMaps(ctx context.Context, maps []*gtm.CidrMap) ([]cidrMap, diag.Diagnostics) { + var result []cidrMap + for _, cm := range maps { + cidrMapInstance := cidrMap{ + Name: types.StringValue(cm.Name), + } + + if cm.Links != nil { + cidrMapInstance.Links = populateLinks(cm.Links) + } + + if cm.DefaultDatacenter != nil { + defaultDataCenter := defaultDatacenter{ + Nickname: types.StringValue(cm.DefaultDatacenter.Nickname), + DatacenterID: types.Int64Value(int64(cm.DefaultDatacenter.DatacenterId)), + } + cidrMapInstance.DefaultDatacenter = defaultDataCenter + } + + if cm.Assignments != nil { + cidrMapInstance.Assignments = make([]cidrMapAssignment, len(cm.Assignments)) + for i, asg := range cm.Assignments { + popCIDRMapAssignment, diags := populateCIDRMapAssignment(ctx, asg) + if diags.HasError() { + return nil, diags + } + cidrMapInstance.Assignments[i] = popCIDRMapAssignment + } + } + result = append(result, cidrMapInstance) + } + return result, nil +} + +func getGeographicMaps(ctx context.Context, maps []*gtm.GeoMap) ([]geographicMap, diag.Diagnostics) { + var result []geographicMap + for _, gm := range maps { + geoMapInstance := geographicMap{ + Name: types.StringValue(gm.Name), + } + + if gm.Links != nil { + geoMapInstance.Links = populateLinks(gm.Links) + } + + if gm.DefaultDatacenter != nil { + defaultDataCenter := defaultDatacenter{ + Nickname: types.StringValue(gm.DefaultDatacenter.Nickname), + DatacenterID: types.Int64Value(int64(gm.DefaultDatacenter.DatacenterId)), + } + geoMapInstance.DefaultDatacenter = defaultDataCenter + } + + if gm.Assignments != nil { + geoMapInstance.Assignments = make([]geographicMapAssignment, len(gm.Assignments)) + for i, asg := range gm.Assignments { + popGeoMap, diags := populateGeographicMapAssignment(ctx, asg) + if diags.HasError() { + return nil, diags + } + geoMapInstance.Assignments[i] = popGeoMap + } + } + result = append(result, geoMapInstance) + } + return result, nil +} + +func getDatacenters(ctx context.Context, datacenters []*gtm.Datacenter) ([]datacenter, diag.Diagnostics) { + var result []datacenter + for _, dc := range datacenters { + dataCenterInstance := datacenter{ + DatacenterID: types.Int64Value(int64(dc.DatacenterId)), + Nickname: types.StringValue(dc.Nickname), + ScorePenalty: types.Int64Value(int64(dc.ScorePenalty)), + City: types.StringValue(dc.City), + StateOrProvince: types.StringValue(dc.StateOrProvince), + Country: types.StringValue(dc.Country), + Latitude: types.Float64Value(dc.Latitude), + Longitude: types.Float64Value(dc.Longitude), + CloneOf: types.Int64Value(int64(dc.CloneOf)), + Virtual: types.BoolValue(dc.Virtual), + Continent: types.StringValue(dc.Continent), + ServerMonitorPool: types.StringValue(dc.ServermonitorPool), + CloudServerTargeting: types.BoolValue(dc.CloudServerTargeting), + CloudServerHostHeaderOverride: types.BoolValue(dc.CloudServerHostHeaderOverride), + } + + if dc.DefaultLoadObject != nil { + loadObj, diags := populateLoadObject(ctx, dc.DefaultLoadObject) + if diags.HasError() { + return nil, diags + } + dataCenterInstance.DefaultLoadObject = []loadObject{loadObj} + } + + if dc.Links != nil { + dataCenterInstance.Links = populateLinks(dc.Links) + } + + result = append(result, dataCenterInstance) + } + return result, nil +} + +func getProperties(ctx context.Context, properties []*gtm.Property) ([]property, diag.Diagnostics) { + var result []property + for _, prop := range properties { + propertyInstance := property{ + BackupCNAME: types.StringValue(prop.BackupCName), + BackupIP: types.StringValue(prop.BackupIp), + BalanceByDownloadScore: types.BoolValue(prop.BalanceByDownloadScore), + CName: types.StringValue(prop.CName), + Comments: types.StringValue(prop.Comments), + DynamicTTL: types.Int64Value(int64(prop.DynamicTTL)), + FailoverDelay: types.Int64Value(int64(prop.FailoverDelay)), + FailbackDelay: types.Int64Value(int64(prop.FailbackDelay)), + GhostDemandReporting: types.BoolValue(prop.GhostDemandReporting), + HandoutMode: types.StringValue(prop.HandoutMode), + HandoutLimit: types.Int64Value(int64(prop.HandoutLimit)), + HealthMax: types.Float64Value(float64(prop.HandoutLimit)), + HealthMultiplier: types.Float64Value(prop.HealthMultiplier), + HealthThreshold: types.Float64Value(prop.HealthThreshold), + LastModified: types.StringValue(prop.LastModified), + LoadImbalancePercentage: types.Float64Value(prop.LoadImbalancePercentage), + MapName: types.StringValue(prop.MapName), + MaxUnreachablePenalty: types.Int64Value(int64(prop.MaxUnreachablePenalty)), + MinLiveFraction: types.Float64Value(prop.MinLiveFraction), + Name: types.StringValue(prop.Name), + ScoreAggregationType: types.StringValue(prop.ScoreAggregationType), + StickinessBonusConstant: types.Int64Value(int64(prop.StickinessBonusConstant)), + StickinessBonusPercentage: types.Int64Value(int64(prop.StickinessBonusPercentage)), + StaticTTL: types.Int64Value(int64(prop.StaticTTL)), + Type: types.StringValue(prop.Type), + UnreachableThreshold: types.Float64Value(prop.UnreachableThreshold), + UseComputedTargets: types.BoolValue(prop.UseComputedTargets), + IPv6: types.BoolValue(prop.Ipv6), + WeightedHashBitsForIPv4: types.Int64Value(int64(prop.WeightedHashBitsForIPv4)), + WeightedHashBitsForIPv6: types.Int64Value(int64(prop.WeightedHashBitsForIPv6)), + } + + if prop.LivenessTests != nil { + propertyInstance.LivenessTests = make([]livenessTest, len(prop.LivenessTests)) + for i, lt := range prop.LivenessTests { + propertyInstance.LivenessTests[i] = populateLivenessTest(lt) + } + } + + if prop.StaticRRSets != nil { + propertyInstance.StaticRRSets = make([]staticRRSet, len(prop.StaticRRSets)) + for i, s := range prop.StaticRRSets { + popStaticRRSet, diags := populateStaticRRSet(ctx, s) + if diags.HasError() { + return nil, diags + } + propertyInstance.StaticRRSets[i] = popStaticRRSet + } + } + + if prop.TrafficTargets != nil { + propertyInstance.TrafficTargets = make([]trafficTarget, len(prop.TrafficTargets)) + for i, t := range prop.TrafficTargets { + popTrafficTarget, diags := populateTrafficTarget(ctx, t) + if diags.HasError() { + return nil, diags + } + propertyInstance.TrafficTargets[i] = popTrafficTarget + } + } + + if prop.Links != nil { + propertyInstance.Links = populateLinks(prop.Links) + } + + result = append(result, propertyInstance) + } + return result, nil +} + +func getStatus(st *gtm.ResponseStatus) *status { + if st == nil { + return nil + } + statusInstance := status{ + Message: types.StringValue(st.Message), + ChangeID: types.StringValue(st.ChangeId), + PropagationStatus: types.StringValue(st.PropagationStatus), + PropagationStatusDate: types.StringValue(st.PropagationStatusDate), + PassingValidation: types.BoolValue(st.PassingValidation), + } + if st.Links != nil { + statusInstance.Links = make([]link, len(*st.Links)) + for i, l := range *st.Links { + statusInstance.Links[i] = link{ + Rel: types.StringValue(l.Rel), + Href: types.StringValue(l.Href), + } + } + } + return &statusInstance +} + +func getResources(resources []*gtm.Resource) []domainResource { + var result []domainResource + for _, res := range resources { + resource := domainResource{ + AggregationType: types.StringValue(res.AggregationType), + ConstrainedProperty: types.StringValue(res.ConstrainedProperty), + DecayRate: types.Float64Value(res.DecayRate), + Description: types.StringValue(res.Description), + HostHeader: types.StringValue(res.HostHeader), + LeaderString: types.StringValue(res.LeaderString), + LeastSquaresDecay: types.Float64Value(res.LeastSquaresDecay), + LoadImbalancePercentage: types.Float64Value(res.LoadImbalancePercentage), + MaxUMultiplicativeIncrement: types.Float64Value(res.MaxUMultiplicativeIncrement), + Name: types.StringValue(res.Name), + Type: types.StringValue(res.Type), + UpperBound: types.Int64Value(int64(res.UpperBound)), + } + + if res.ResourceInstances != nil { + resInstances := make([]resourceInstance, len(res.ResourceInstances)) + for i, ri := range res.ResourceInstances { + resInstances[i] = resourceInstance{ + LoadObject: types.StringValue(ri.LoadObject.LoadObject), + LoadObjectPort: types.Int64Value(int64(ri.LoadObject.LoadObjectPort)), + DataCenterID: types.Int64Value(int64(ri.DatacenterId)), + UseDefaultLoadObject: types.BoolValue(ri.UseDefaultLoadObject), + } + if ri.LoadObject.LoadServers != nil { + loadServers := make([]types.String, len(ri.LoadObject.LoadServers)) + for i, s := range ri.LoadObject.LoadServers { + loadServers[i] = types.StringValue(s) + } + resInstances[i].LoadServers = loadServers + } + } + resource.ResourceInstances = resInstances + } + + if res.Links != nil { + resource.Links = populateLinks(res.Links) + } + + result = append(result, resource) + } + return result +} + +func populateLivenessTest(lt *gtm.LivenessTest) livenessTest { + return livenessTest{ + AnswersRequired: types.BoolValue(lt.AnswersRequired), + Disabled: types.BoolValue(lt.Disabled), + DisableNonstandardPortWarning: types.BoolValue(lt.DisableNonstandardPortWarning), + ErrorPenalty: types.Float64Value(lt.ErrorPenalty), + HTTPError3xx: types.BoolValue(lt.HttpError3xx), + HTTPError4xx: types.BoolValue(lt.HttpError4xx), + HTTPError5xx: types.BoolValue(lt.HttpError5xx), + Name: types.StringValue(lt.Name), + PeerCertificateVerification: types.BoolValue(lt.PeerCertificateVerification), + RequestString: types.StringValue(lt.RequestString), + ResponseString: types.StringValue(lt.ResponseString), + ResourceType: types.StringValue(lt.ResourceType), + RecursionRequested: types.BoolValue(lt.RecursionRequested), + TestInterval: types.Int64Value(int64(lt.TestInterval)), + TestObject: types.StringValue(lt.TestObject), + TestObjectPort: types.Int64Value(int64(lt.TestObjectPort)), + TestObjectProtocol: types.StringValue(lt.TestObjectProtocol), + TestObjectUsername: types.StringValue(lt.TestObjectUsername), + TestObjectPassword: types.StringValue(lt.TestObjectPassword), + TestTimeout: types.Float64Value(float64(lt.TestTimeout)), + TimeoutPenalty: types.Float64Value(lt.TimeoutPenalty), + SSLClientCertificate: types.StringValue(lt.SslClientCertificate), + SSLClientPrivateKey: types.StringValue(lt.SslClientPrivateKey), + HTTPHeaders: populateHTTPHeaders(lt.HttpHeaders), + } +} + +func populateHTTPHeaders(headers []*gtm.HttpHeader) []httpHeader { + result := make([]httpHeader, len(headers)) + for i, h := range headers { + result[i] = httpHeader{ + Name: types.StringValue(h.Name), + Value: types.StringValue(h.Value), + } + } + return result +} + +func populateStaticRRSet(ctx context.Context, s *gtm.StaticRRSet) (staticRRSet, diag.Diagnostics) { + Rdata, diags := types.ListValueFrom(ctx, types.StringType, s.Rdata) + if diags.HasError() { + return staticRRSet{}, diags + } + return staticRRSet{ + Type: types.StringValue(s.Type), + TTL: types.Int64Value(int64(s.TTL)), + RData: Rdata, + }, diags +} + +func populateLinks(links []*gtm.Link) []link { + result := make([]link, len(links)) + for i, l := range links { + result[i] = link{ + Rel: types.StringValue(l.Rel), + Href: types.StringValue(l.Href), + } + } + return result +} + +func populateTrafficTarget(ctx context.Context, t *gtm.TrafficTarget) (trafficTarget, diag.Diagnostics) { + servers, diags := types.ListValueFrom(ctx, types.StringType, t.Servers) + if diags.HasError() { + return trafficTarget{}, diags + } + return trafficTarget{ + DatacenterID: types.Int64Value(int64(t.DatacenterId)), + Enabled: types.BoolValue(t.Enabled), + Weight: types.Float64Value(t.Weight), + HandoutCNAME: types.StringValue(t.HandoutCName), + Name: types.StringValue(t.Name), + Servers: servers, + }, nil +} + +func populateLoadObject(ctx context.Context, lo *gtm.LoadObject) (loadObject, diag.Diagnostics) { + loadObj := loadObject{ + LoadObject: types.StringValue(lo.LoadObject), + LoadObjectPort: types.Int64Value(int64(lo.LoadObjectPort)), + } + if lo.LoadServers != nil { + loadServers, diags := types.ListValueFrom(ctx, types.StringType, lo.LoadServers) + if diags.HasError() { + return loadObj, diags + } + loadObj.LoadServers = loadServers + } + return loadObj, nil +} + +func populateGeographicMapAssignment(ctx context.Context, asg *gtm.GeoAssignment) (geographicMapAssignment, diag.Diagnostics) { + result := geographicMapAssignment{} + + if asg.Countries != nil { + countries, diags := types.SetValueFrom(ctx, types.StringType, asg.Countries) + if diags.HasError() { + return result, diags + } + result.Countries = countries + } + + result.Nickname = types.StringValue(asg.DatacenterBase.Nickname) + result.DatacenterID = types.Int64Value(int64(asg.DatacenterBase.DatacenterId)) + + return result, nil +} + +func populateCIDRMapAssignment(ctx context.Context, asg *gtm.CidrAssignment) (cidrMapAssignment, diag.Diagnostics) { + result := cidrMapAssignment{} + + if asg.Blocks != nil { + lstStr, diags := types.SetValueFrom(ctx, types.StringType, asg.Blocks) + if diags.HasError() { + return result, diags + } + result.Blocks = lstStr + } + + result.Nickname = types.StringValue(asg.DatacenterBase.Nickname) + result.DatacenterID = types.Int64Value(int64(asg.DatacenterBase.DatacenterId)) + + return result, nil +} + +func populateASMapAssignment(asg *gtm.AsAssignment) asMapAssignment { + result := asMapAssignment{} + + if asg.AsNumbers != nil { + asNumbers := make([]types.Int64, len(asg.AsNumbers)) + for i, c := range asg.AsNumbers { + asNumbers[i] = types.Int64Value(c) + } + result.ASNumbers = asNumbers + } + + result.Nickname = types.StringValue(asg.DatacenterBase.Nickname) + result.DatacenterID = types.Int64Value(int64(asg.DatacenterBase.DatacenterId)) + + return result +} diff --git a/pkg/providers/gtm/data_akamai_gtm_domain_test.go b/pkg/providers/gtm/data_akamai_gtm_domain_test.go new file mode 100644 index 000000000..3b6fd6799 --- /dev/null +++ b/pkg/providers/gtm/data_akamai_gtm_domain_test.go @@ -0,0 +1,265 @@ +package gtm + +import ( + "fmt" + "regexp" + "testing" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/gtm" + "github.com/akamai/terraform-provider-akamai/v5/pkg/common/testutils" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/stretchr/testify/mock" +) + +func TestDataGtmDomain(t *testing.T) { + tests := map[string]struct { + givenTF string + init func(*gtm.Mock) + expectedAttributes map[string]string + expectedMissingAttributes []string + expectError *regexp.Regexp + }{ + "success - response is ok": { + givenTF: "valid.tf", + init: func(m *gtm.Mock) { + m.On("GetDomain", mock.Anything, "test.cli.devexp-terraform.akadns.net").Return(>m.Domain{ + Name: "test.cli.devexp-terraform.akadns.net", + CnameCoalescingEnabled: false, + DefaultErrorPenalty: 75, + DefaultHealthMax: 0, + DefaultHealthMultiplier: 0, + DefaultHealthThreshold: 0, + DefaultMaxUnreachablePenalty: 0, + DefaultTimeoutPenalty: 25, + DefaultUnreachableThreshold: 0, + EmailNotificationList: []string{"ckulinsk@akamai.com"}, + EndUserMappingEnabled: false, + LastModified: "2023-01-25T10:21:45.000+00:00", + MaxTTL: 172800, + Status: >m.ResponseStatus{ + ChangeId: "ca7e5b1d-1303-42d3-b6c0-8cb62ae849d4", + Message: "ERROR: zone is child of existing GTM domain devexp-terraform.akadns.net, which is not allowed", + PassingValidation: false, + PropagationStatus: "DENIED", + PropagationStatusDate: "2023-01-25T10:21:00.000+00:00", + }, + Resources: []*gtm.Resource{{ + AggregationType: "latest", + Description: "terraform test resource", + Type: "XML load object via HTTP", + Name: "test resource", + UpperBound: 100, + }, + }, + AsMaps: []*gtm.AsMap{{ + DefaultDatacenter: >m.DatacenterBase{ + DatacenterId: 3133, + Nickname: "Default (all others)", + }, + Assignments: []*gtm.AsAssignment{{ + DatacenterBase: gtm.DatacenterBase{ + Nickname: "New Zone 1", + DatacenterId: 3133, + }, + AsNumbers: []int64{ + 12222, + 17334, + 16702, + }, + }}, + Name: "New Map 1", + Links: []*gtm.Link{{ + Rel: "self", + Href: "https://akaa-ouijhfns55qwgfuc-knsod5nrjl2w2gmt.luna-dev.akamaiapis.net/config-gtm/v1/domains/test.cli.devexp-terraform.akadns.net/as-maps/DevExpAutomatedTest_6Qil38", + }}, + }, + }, + CidrMaps: []*gtm.CidrMap{{ + DefaultDatacenter: >m.DatacenterBase{ + DatacenterId: 3133, + Nickname: "All Other CIDR Blocks", + }, + Assignments: []*gtm.CidrAssignment{{ + DatacenterBase: gtm.DatacenterBase{ + Nickname: "New Zone 1", + DatacenterId: 3133, + }, + Blocks: []string{ + "1.2.3.4/22", + }, + }}, + Name: "New Map 1", + Links: []*gtm.Link{{ + Rel: "self", + Href: "https://akaa-ouijhfns55qwgfuc-knsod5nrjl2w2gmt.luna-dev.akamaiapis.net/config-gtm/v1/domains/test.cli.devexp-terraform.akadns.net/cidr-maps/New%20Map%201", + }}, + }, + }, + GeographicMaps: []*gtm.GeoMap{{ + DefaultDatacenter: >m.DatacenterBase{ + DatacenterId: 3131, + Nickname: "terraform_datacenter_test", + }, + Assignments: []*gtm.GeoAssignment{{ + DatacenterBase: gtm.DatacenterBase{ + Nickname: "terraform_datacenter_test_1", + DatacenterId: 3133, + }, + Countries: []string{ + "GB", + }, + }}, + Name: "tfexample_geo_2", + Links: []*gtm.Link{{ + Rel: "self", + Href: "https://akaa-ouijhfns55qwgfuc-knsod5nrjl2w2gmt.luna-dev.akamaiapis.net/config-gtm/v1/domains/test.cli.devexp-terraform.akadns.net/geographic-maps/tfexample_geo_2", + }}, + }, + }, + Links: []*gtm.Link{{ + Rel: "properties", + Href: "https://akaa-ouijhfns55qwgfuc-knsod5nrjl2w2gmt.luna-dev.akamaiapis.net/config-gtm/v1/domains/test.cli.devexp-terraform.akadns.net/properties", + }, { + Rel: "resources", + Href: "https://akaa-ouijhfns55qwgfuc-knsod5nrjl2w2gmt.luna-dev.akamaiapis.net/config-gtm/v1/domains/test.cli.devexp-terraform.akadns.net/resources"}, + }, + Properties: []*gtm.Property{{ + BalanceByDownloadScore: false, + DynamicTTL: 60, + GhostDemandReporting: false, + HandoutMode: "Normal", + LastModified: "2023-01-25T09:58:09.000+00:00", + Name: "property", + Links: []*gtm.Link{{ + Href: "https://akaa-ouijhfns55qwgfuc-knsod5nrjl2w2gmt.luna-dev.akamaiapis.net/config-gtm/v1/domains/test.cli.devexp-terraform.akadns.net/properties/property", + Rel: "self", + }}, + LivenessTests: []*gtm.LivenessTest{{ + AnswersRequired: false, + DisableNonstandardPortWarning: false, + HttpError3xx: true, + TestObjectProtocol: "HTTP", + }}, + TrafficTargets: []*gtm.TrafficTarget{{ + DatacenterId: 3131, + Enabled: true, + Servers: []string{ + "1.2.3.4", + "2.3.4.5", + }, + Weight: 1, + }}, + }}, + }, nil) + }, + expectedAttributes: map[string]string{ + "name": "test.cli.devexp-terraform.akadns.net", + "cname_coalescing_enabled": "false", + "default_timeout_penalty": "25", + "default_error_penalty": "75", + "email_notification_list.0": "ckulinsk@akamai.com", + "status.change_id": "ca7e5b1d-1303-42d3-b6c0-8cb62ae849d4", + "status.message": "ERROR: zone is child of existing GTM domain devexp-terraform.akadns.net, which is not allowed", + "status.passing_validation": "false", + "status.propagation_status": "DENIED", + "status.propagation_status_date": "2023-01-25T10:21:00.000+00:00", + "end_user_mapping_enabled": "false", + "last_modified": "2023-01-25T10:21:45.000+00:00", + "max_ttl": "172800", + "as_maps.0.name": "New Map 1", + "as_maps.0.default_datacenter.datacenter_id": "3133", + "as_maps.0.default_datacenter.nickname": "Default (all others)", + "as_maps.0.assignments.0.nickname": "New Zone 1", + "as_maps.0.assignments.0.datacenter_id": "3133", + "as_maps.0.assignments.0.as_numbers.0": "12222", + "as_maps.0.assignments.0.as_numbers.1": "16702", + "as_maps.0.assignments.0.as_numbers.2": "17334", + "as_maps.0.links.0.href": "https://akaa-ouijhfns55qwgfuc-knsod5nrjl2w2gmt.luna-dev.akamaiapis.net/config-gtm/v1/domains/test.cli.devexp-terraform.akadns.net/as-maps/DevExpAutomatedTest_6Qil38", + "as_maps.0.links.0.rel": "self", + "cidr_maps.0.name": "New Map 1", + "cidr_maps.0.default_datacenter.datacenter_id": "3133", + "cidr_maps.0.default_datacenter.nickname": "All Other CIDR Blocks", + "cidr_maps.0.assignments.0.nickname": "New Zone 1", + "cidr_maps.0.assignments.0.datacenter_id": "3133", + "cidr_maps.0.assignments.0.blocks.0": "1.2.3.4/22", + "cidr_maps.0.links.0.href": "https://akaa-ouijhfns55qwgfuc-knsod5nrjl2w2gmt.luna-dev.akamaiapis.net/config-gtm/v1/domains/test.cli.devexp-terraform.akadns.net/cidr-maps/New%20Map%201", + "cidr_maps.0.links.0.rel": "self", + "geographic_maps.0.name": "tfexample_geo_2", + "geographic_maps.0.default_datacenter.datacenter_id": "3131", + "geographic_maps.0.default_datacenter.nickname": "terraform_datacenter_test", + "geographic_maps.0.assignments.0.nickname": "terraform_datacenter_test_1", + "geographic_maps.0.assignments.0.datacenter_id": "3133", + "geographic_maps.0.assignments.0.countries.0": "GB", + "geographic_maps.0.links.0.href": "https://akaa-ouijhfns55qwgfuc-knsod5nrjl2w2gmt.luna-dev.akamaiapis.net/config-gtm/v1/domains/test.cli.devexp-terraform.akadns.net/geographic-maps/tfexample_geo_2", + "geographic_maps.0.links.0.rel": "self", + "resources.0.aggregation_type": "latest", + "resources.0.description": "terraform test resource", + "resources.0.name": "test resource", + "resources.0.type": "XML load object via HTTP", + "resources.0.upper_bound": "100", + "properties.0.balance_by_download_score": "false", + "properties.0.dynamic_ttl": "60", + "properties.0.ghost_demand_reporting": "false", + "properties.0.handout_mode": "Normal", + "properties.0.last_modified": "2023-01-25T09:58:09.000+00:00", + "properties.0.name": "property", + "properties.0.links.0.href": "https://akaa-ouijhfns55qwgfuc-knsod5nrjl2w2gmt.luna-dev.akamaiapis.net/config-gtm/v1/domains/test.cli.devexp-terraform.akadns.net/properties/property", + "properties.0.links.0.rel": "self", + "properties.0.liveness_tests.0.answers_required": "false", + "properties.0.liveness_tests.0.disable_nonstandard_port_warning": "false", + "properties.0.liveness_tests.0.http_error3xx": "true", + "properties.0.liveness_tests.0.test_object_protocol": "HTTP", + "properties.0.traffic_targets.0.datacenter_id": "3131", + "properties.0.traffic_targets.0.enabled": "true", + "properties.0.traffic_targets.0.servers.0": "1.2.3.4", + "properties.0.traffic_targets.0.servers.1": "2.3.4.5", + "properties.0.traffic_targets.0.weight": "1", + "links.0.href": "https://akaa-ouijhfns55qwgfuc-knsod5nrjl2w2gmt.luna-dev.akamaiapis.net/config-gtm/v1/domains/test.cli.devexp-terraform.akadns.net/properties", + "links.0.rel": "properties", + "links.1.href": "https://akaa-ouijhfns55qwgfuc-knsod5nrjl2w2gmt.luna-dev.akamaiapis.net/config-gtm/v1/domains/test.cli.devexp-terraform.akadns.net/resources", + "links.1.rel": "resources", + }, + expectedMissingAttributes: nil, + expectError: nil, + }, + "missing required argument name": { + givenTF: "missing_domain_name.tf", + expectError: regexp.MustCompile(`The argument "name" is required, but no definition was found`), + }, + "error response from api": { + givenTF: "valid.tf", + init: func(m *gtm.Mock) { + m.On("GetDomain", mock.Anything, "test.cli.devexp-terraform.akadns.net").Return(nil, fmt.Errorf("oops")) + }, + expectError: regexp.MustCompile("oops"), + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + client := >m.Mock{} + if test.init != nil { + test.init(client) + } + var checkFuncs []resource.TestCheckFunc + for k, v := range test.expectedAttributes { + checkFuncs = append(checkFuncs, resource.TestCheckResourceAttr("data.akamai_gtm_domain.domain", k, v)) + } + for _, v := range test.expectedMissingAttributes { + checkFuncs = append(checkFuncs, resource.TestCheckNoResourceAttr("data.akamai_gtm_domain.domain", v)) + } + useClient(client, func() { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV5ProviderFactories: testAccProvidersProtoV5, + Steps: []resource.TestStep{{ + Config: testutils.LoadFixtureString(t, fmt.Sprintf("testdata/TestDataGtmDomain/%s", test.givenTF)), + Check: resource.ComposeAggregateTestCheckFunc(checkFuncs...), + ExpectError: test.expectError, + }}, + }) + }) + client.AssertExpectations(t) + }) + } +} diff --git a/pkg/providers/gtm/data_akamai_gtm_domains.go b/pkg/providers/gtm/data_akamai_gtm_domains.go new file mode 100644 index 000000000..cb6db8666 --- /dev/null +++ b/pkg/providers/gtm/data_akamai_gtm_domains.go @@ -0,0 +1,199 @@ +package gtm + +import ( + "context" + "fmt" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/gtm" + "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +var _ datasource.DataSource = &domainsDataSource{} +var _ datasource.DataSourceWithConfigure = &domainsDataSource{} + +// NewGTMDomainsDataSource returns a new GTM domains data source +func NewGTMDomainsDataSource() datasource.DataSource { + return &domainsDataSource{} +} + +var ( + domainsBlock = map[string]schema.Block{ + "domains": schema.SetNestedBlock{ + Description: "List of domains under given contract.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Computed: true, + Description: "A unique domain name.", + }, + "status": schema.StringAttribute{ + Computed: true, + Description: "The current status of the domain.", + }, + "acg_id": schema.StringAttribute{ + Computed: true, + Description: "The contract's identifier, with which the domain is associated.", + }, + "last_modified": schema.StringAttribute{ + Computed: true, + Description: "An ISO 8601 timestamp that indicates the time of the last domain change.", + }, + "last_modified_by": schema.StringAttribute{ + Computed: true, + Description: "The email address of the administrator who made the last change to the domain.", + }, + "change_id": schema.StringAttribute{ + Computed: true, + Description: "UUID that identifies a version of the domain configuration.", + }, + "activation_state": schema.StringAttribute{ + Computed: true, + Description: "'PENDING' when a change has been made but not yet propagated; 'COMPLETE' when the last configuration change has propagated successfully; 'DENIED' if the domain configuration failed validation; 'DELETED' if the domain has been deleted.", + }, + "modification_comments": schema.StringAttribute{ + Computed: true, + Description: "A descriptive note about changes to the domain.", + }, + "sign_and_serve": schema.BoolAttribute{ + Computed: true, + Description: "If set (true) we will sign the domain's resource records so that they can be validated by a validating resolver.", + }, + "sign_and_serve_algorithm": schema.StringAttribute{ + Computed: true, + Description: "The signing algorithm to use for signAndServe. One of the following values: RSA_SHA1, RSA_SHA256, RSA_SHA512, ECDSA_P256_SHA256, ECDSA_P384_SHA384, ED25519, ED448.", + }, + "delete_request_id": schema.StringAttribute{ + Computed: true, + Description: "UUID for delete request during domain deletion. Null if the domain is not being deleted.", + }, + }, + Blocks: map[string]schema.Block{ + "links": &schema.SetNestedBlock{ + Description: "Provides a URL path that allows direct navigation to the domain.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "rel": schema.StringAttribute{ + Computed: true, + Description: "Indicates the link relationship of the object.", + }, + "href": schema.StringAttribute{ + Computed: true, + Description: "A hypermedia link to the complete URL that uniquely defines a resource.", + }, + }, + }, + }, + }, + }, + }, + } +) + +type domainsDataSource struct { + meta meta.Meta +} + +type ( + domainsDataSourceModel struct { + ID types.String `tfsdk:"id"` + Domains []domain `tfsdk:"domains"` + } + + domain struct { + Name types.String `tfsdk:"name"` + Status types.String `tfsdk:"status"` + AcgID types.String `tfsdk:"acg_id"` + LastModified types.String `tfsdk:"last_modified"` + LastModifiedBy types.String `tfsdk:"last_modified_by"` + ChangeID types.String `tfsdk:"change_id"` + ActivationState types.String `tfsdk:"activation_state"` + ModificationComments types.String `tfsdk:"modification_comments"` + SignAndServe types.Bool `tfsdk:"sign_and_serve"` + SignAndServeAlgorithm types.String `tfsdk:"sign_and_serve_algorithm"` + DeleteRequestID types.String `tfsdk:"delete_request_id"` + Links []link `tfsdk:"links"` + } +) + +// Schema is used to define data source's terraform schema +func (d *domainsDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, response *datasource.SchemaResponse) { + response.Schema = schema.Schema{ + MarkdownDescription: "List of domains under given contract.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + Description: "Identifier of the data source.", + DeprecationMessage: "Required by the terraform plugin testing framework, always set to `akamai_gtm_domains`.", + Computed: true, + }, + }, + Blocks: domainsBlock, + } +} + +// Configure configures data source at the beginning of the lifecycle +func (d *domainsDataSource) Configure(_ context.Context, request datasource.ConfigureRequest, response *datasource.ConfigureResponse) { + if request.ProviderData == nil { + return + } + meta, ok := request.ProviderData.(meta.Meta) + if !ok { + response.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected meta.Meta, got: %T. Please report this issue to the provider developers.", request.ProviderData), + ) + } + d.meta = meta +} + +// Metadata configures data source's meta information +func (d *domainsDataSource) Metadata(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "akamai_gtm_domains" +} + +// Read is called when the provider must read data source values in order to update state +func (d *domainsDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + tflog.Debug(ctx, "GTM Domains DataSource Read") + var data domainsDataSourceModel + response.Diagnostics.Append(request.Config.Get(ctx, &data)...) + if response.Diagnostics.HasError() { + return + } + + client := inst.Client(d.meta) + domains, err := client.ListDomains(ctx) + if err != nil { + response.Diagnostics.AddError("fetching domains failed", err.Error()) + return + } + + data.Domains = getDomains(domains) + data.ID = types.StringValue("akamai_gtm_domains") + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +func getDomains(domainList []*gtm.DomainItem) []domain { + var result []domain + for _, dom := range domainList { + domain := domain{ + Name: types.StringValue(dom.Name), + LastModified: types.StringValue(dom.LastModified), + Status: types.StringValue(dom.Status), + AcgID: types.StringValue(dom.AcgId), + LastModifiedBy: types.StringValue(dom.LastModifiedBy), + ChangeID: types.StringValue(dom.ChangeID), + ActivationState: types.StringValue(dom.ActivationState), + ModificationComments: types.StringValue(dom.ModificationComments), + SignAndServe: types.BoolValue(dom.SignAndServe), + SignAndServeAlgorithm: types.StringValue(dom.SignAndServeAlgorithm), + DeleteRequestID: types.StringValue(dom.DeleteRequestID), + Links: getLinks(dom.Links), + } + + result = append(result, domain) + } + return result +} diff --git a/pkg/providers/gtm/data_akamai_gtm_domains_test.go b/pkg/providers/gtm/data_akamai_gtm_domains_test.go new file mode 100644 index 000000000..fafe4b942 --- /dev/null +++ b/pkg/providers/gtm/data_akamai_gtm_domains_test.go @@ -0,0 +1,127 @@ +package gtm + +import ( + "fmt" + "regexp" + "testing" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/gtm" + "github.com/akamai/terraform-provider-akamai/v5/pkg/common/testutils" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/stretchr/testify/mock" +) + +func TestDataGTMDomains(t *testing.T) { + tests := map[string]struct { + givenTF string + init func(*gtm.Mock) + expectedAttributes map[string]string + expectedMissingAttributes []string + expectError *regexp.Regexp + }{ + "success - response is ok": { + givenTF: "valid.tf", + init: func(m *gtm.Mock) { + m.On("ListDomains", mock.Anything).Return([]*gtm.DomainItem{ + { + Name: "test1.terraformtesting.net", + LastModified: "2023-02-01T09:36:28.000+00:00", + LastModifiedBy: "test-user", + ChangeID: "ca4de6db-8d69-4980-8e2a-036b655f2e66", + ActivationState: "COMPLETE", + ModificationComments: "Add AS Map New Map 1", + SignAndServe: false, + Status: "2023-02-01 09:47 GMT: Current configuration has been propagated to all GTM nameservers", + AcgId: "TestACGID-1", + Links: []*gtm.Link{{ + Rel: "self", + Href: "https://test-domain.net/config-gtm/v1/domains/test1.terraformtesting.net", + }, + }, + }, + { + Name: "test2.terraformtesting.net", + LastModified: "2023-12-21T08:34:31.463+00:00", + LastModifiedBy: "test-user", + ChangeID: "acca0158-398b-4a03-8886-81adc6328f56", + ActivationState: "COMPLETE", + ModificationComments: "terraform test gtm domain", + SignAndServe: false, + Status: "2023-12-21 08:37 GMT: Current configuration has been propagated to all GTM nameservers", + AcgId: "TestACGID-1", + Links: []*gtm.Link{{ + Rel: "self", + Href: "https://test-domain.net/config-gtm/v1/domains/test2.terraformtesting.net", + }, + }, + }, + { + Name: "test3.terraformtesting.net", + LastModified: "2023-12-22T08:43:47.553+00:00", + LastModifiedBy: "test-user", + ChangeID: "abf5b76f-f9de-4404-bb2c-9d15e7b9ff5d", + ActivationState: "COMPLETE", + ModificationComments: "terraform test gtm domain", + SignAndServe: false, + Status: "2023-12-22 08:46 GMT: Current configuration has been propagated to all GTM nameservers", + AcgId: "TestACGID-1", + Links: []*gtm.Link{{ + Rel: "self", + Href: "https://test-domain.net/config-gtm/v1/domains/test3.terraformtesting.net", + }, + }, + }, + }, nil) + }, + expectedAttributes: map[string]string{ + "domains.0.name": "test3.terraformtesting.net", + "domains.0.sign_and_serve": "false", + "domains.0.links.0.href": "https://test-domain.net/config-gtm/v1/domains/test3.terraformtesting.net", + "domains.0.links.0.rel": "self", + "domains.1.name": "test2.terraformtesting.net", + "domains.2.name": "test1.terraformtesting.net", + }, + }, + "no domains found": { + givenTF: "valid.tf", + init: func(m *gtm.Mock) { + m.On("ListDomains", mock.Anything).Return([]*gtm.DomainItem{}, nil) + }, + }, + "error response from api": { + givenTF: "valid.tf", + init: func(m *gtm.Mock) { + m.On("ListDomains", mock.Anything).Return(nil, fmt.Errorf("oops")) + }, + expectError: regexp.MustCompile("oops"), + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + client := >m.Mock{} + if test.init != nil { + test.init(client) + } + var checkFuncs []resource.TestCheckFunc + for k, v := range test.expectedAttributes { + checkFuncs = append(checkFuncs, resource.TestCheckResourceAttr("data.akamai_gtm_domains.domains", k, v)) + } + for _, v := range test.expectedMissingAttributes { + checkFuncs = append(checkFuncs, resource.TestCheckNoResourceAttr("data.akamai_gtm_domains.domains", v)) + } + useClient(client, func() { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV5ProviderFactories: testAccProvidersProtoV5, + Steps: []resource.TestStep{{ + Config: testutils.LoadFixtureString(t, fmt.Sprintf("testdata/TestDataGtmDomains/%s", test.givenTF)), + Check: resource.ComposeAggregateTestCheckFunc(checkFuncs...), + ExpectError: test.expectError, + }}, + }) + }) + client.AssertExpectations(t) + }) + } +} diff --git a/pkg/providers/gtm/data_akamai_gtm_resource.go b/pkg/providers/gtm/data_akamai_gtm_resource.go new file mode 100644 index 000000000..e8e3447d3 --- /dev/null +++ b/pkg/providers/gtm/data_akamai_gtm_resource.go @@ -0,0 +1,255 @@ +package gtm + +import ( + "context" + "fmt" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/gtm" + "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +var ( + _ datasource.DataSource = &resourceDataSource{} + _ datasource.DataSourceWithConfigure = &resourceDataSource{} +) + +// NewGTMResourceDataSource returns a new GTM resource data source. +func NewGTMResourceDataSource() datasource.DataSource { + return &resourceDataSource{} +} + +// resourceDataSource defines the data source implementation for fetching GTM resource information. +type resourceDataSource struct { + meta meta.Meta +} + +// resourceDataSourceModel describes the data source data model for GTM resource data source. +type resourceDataSourceModel struct { + ID types.String `tfsdk:"id"` + Domain types.String `tfsdk:"domain"` + ResourceName types.String `tfsdk:"resource_name"` + AggregationType types.String `tfsdk:"aggregation_type"` + ConstrainedProperty types.String `tfsdk:"constrained_property"` + DecayRate types.Float64 `tfsdk:"decay_rate"` + Description types.String `tfsdk:"description"` + HostHeader types.String `tfsdk:"host_header"` + LeaderString types.String `tfsdk:"leader_string"` + LeastSquaresDecay types.Float64 `tfsdk:"least_squares_decay"` + LoadImbalancePercentage types.Float64 `tfsdk:"load_imbalance_percentage"` + MaxUMultiplicativeIncrement types.Float64 `tfsdk:"max_u_multiplicative_increment"` + Type types.String `tfsdk:"type"` + UpperBound types.Int64 `tfsdk:"upper_bound"` + Links []link `tfsdk:"links"` + ResourceInstances []resourceInstance `tfsdk:"resource_instances"` +} + +// Metadata configures data source's meta information. +func (d *resourceDataSource) Metadata(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "akamai_gtm_resource" +} + +// Configure configures data source at the beginning of the lifecycle. +func (d *resourceDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + // ProviderData is nil when Configure is run first time as part of ValidateDataSourceConfig in framework provider + return + } + + defer func() { + if r := recover(); r != nil { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected meta.Meta, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + } + }() + + d.meta = meta.Must(req.ProviderData) +} + +var ( + resourceBlocks = map[string]schema.Block{ + "links": schema.SetNestedBlock{ + Description: "Specifies the URL path that allows direct navigation to the resource.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "rel": schema.StringAttribute{ + Computed: true, + Description: "Indicates the link relationship of the object.", + }, + "href": schema.StringAttribute{ + Computed: true, + Description: "A hypermedia link to the complete URL that uniquely defines a resource.", + }, + }, + }, + }, + "resource_instances": schema.SetNestedBlock{ + Description: "Instances of the resource.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "load_object": schema.StringAttribute{ + Computed: true, + Description: "Identifies the load object file used to report real-time information about the current load, maximum allowable load and target load on each resource.", + }, + "load_object_port": schema.Int64Attribute{ + Computed: true, + Description: "Specifies the TCP port of the loadObject.", + }, + "load_servers": schema.SetAttribute{ + Computed: true, + Description: "Specifies the list of servers to requests the load object from.", + ElementType: types.StringType, + }, + "datacenter_id": schema.Int64Attribute{ + Computed: true, + Description: "A unique identifier for an existing data center in the domain.", + }, + "use_default_load_object": schema.BoolAttribute{ + Computed: true, + Description: "Whether to use default loadObject.", + }, + }, + }, + }, + } +) + +// Schema is used to define data source's terraform schema. +func (d *resourceDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "GTM Resource data source.", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "Identifier of the data source.", + Computed: true, + }, + "domain": schema.StringAttribute{ + Required: true, + Description: "GTM domain name.", + }, + "resource_name": schema.StringAttribute{ + Required: true, + Description: "A descriptive label for the resource.", + }, + "aggregation_type": schema.StringAttribute{ + Computed: true, + Description: "Specifies how GTM handles different load numbers when multiple load servers are used for a data center or property.", + }, + "constrained_property": schema.StringAttribute{ + Computed: true, + Description: "Specifies the name of the property that this resource constraints.", + }, + "decay_rate": schema.Float64Attribute{ + Computed: true, + Description: "For internal use only.", + }, + "description": schema.StringAttribute{ + Computed: true, + Description: "A descriptive note to help you track what the resource constraints.", + }, + "host_header": schema.StringAttribute{ + Computed: true, + Description: "Specifies the host header used when fetching the load object.", + }, + "leader_string": schema.StringAttribute{ + Computed: true, + Description: "Specifies the text that comes before the loadObject.", + }, + "least_squares_decay": schema.Float64Attribute{ + Computed: true, + Description: "For internal use only.", + }, + "load_imbalance_percentage": schema.Float64Attribute{ + Computed: true, + Description: "Indicates the percent of load imbalance factor for the domain.", + }, + "max_u_multiplicative_increment": schema.Float64Attribute{ + Computed: true, + Description: "For internal use only.", + }, + "type": schema.StringAttribute{ + Computed: true, + Description: "Indicates the kind of loadObject format used to determine the load on the resource.", + }, + "upper_bound": schema.Int64Attribute{ + Computed: true, + Description: "An optional sanity check that specifies the maximum allowed value for any component of the load object.", + }, + }, + Blocks: resourceBlocks, + } +} + +// Read is called when the provider must read data source values in order to update state. +func (d *resourceDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + tflog.Debug(ctx, "GTM Resource DataSource Read") + + var data resourceDataSourceModel + if response.Diagnostics.Append(request.Config.Get(ctx, &data)...); response.Diagnostics.HasError() { + return + } + + client := frameworkInst.Client(d.meta) + resource, err := client.GetResource(ctx, data.ResourceName.ValueString(), data.Domain.ValueString()) + if err != nil { + response.Diagnostics.AddError("fetching GTM resource failed:", err.Error()) + return + } + + data.setAttributes(resource) + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} + +func (m *resourceDataSourceModel) setAttributes(resource *gtm.Resource) { + + m.AggregationType = types.StringValue(resource.AggregationType) + m.ConstrainedProperty = types.StringValue(resource.ConstrainedProperty) + m.DecayRate = types.Float64Value(float64(resource.DecayRate)) + m.Description = types.StringValue(resource.Description) + m.HostHeader = types.StringValue(resource.HostHeader) + m.LeaderString = types.StringValue(resource.LeaderString) + m.LeastSquaresDecay = types.Float64Value(float64(resource.LeastSquaresDecay)) + m.LoadImbalancePercentage = types.Float64Value(float64(resource.LoadImbalancePercentage)) + m.MaxUMultiplicativeIncrement = types.Float64Value(float64(resource.MaxUMultiplicativeIncrement)) + m.Type = types.StringValue(resource.Type) + m.UpperBound = types.Int64Value(int64(resource.UpperBound)) + m.setLinks(resource.Links) + m.setResourceInstances(resource.ResourceInstances) + m.ID = types.StringValue(resource.Name) +} + +func (m *resourceDataSourceModel) setLinks(links []*gtm.Link) { + + for _, l := range links { + linkObject := link{ + Rel: types.StringValue(l.Rel), + Href: types.StringValue(l.Href), + } + + m.Links = append(m.Links, linkObject) + } +} + +func (m *resourceDataSourceModel) setResourceInstances(resourceInstances []*gtm.ResourceInstance) { + + for _, res := range resourceInstances { + resourceInstanceObject := resourceInstance{ + DataCenterID: types.Int64Value(int64(res.DatacenterId)), + UseDefaultLoadObject: types.BoolValue(res.UseDefaultLoadObject), + LoadObject: types.StringValue(res.LoadObject.LoadObject), + LoadObjectPort: types.Int64Value(int64(res.LoadObject.LoadObjectPort)), + } + + for _, server := range res.LoadServers { + resourceInstanceObject.LoadServers = append(resourceInstanceObject.LoadServers, types.StringValue(server)) + } + + m.ResourceInstances = append(m.ResourceInstances, resourceInstanceObject) + } +} diff --git a/pkg/providers/gtm/data_akamai_gtm_resource_test.go b/pkg/providers/gtm/data_akamai_gtm_resource_test.go new file mode 100644 index 000000000..a8a585362 --- /dev/null +++ b/pkg/providers/gtm/data_akamai_gtm_resource_test.go @@ -0,0 +1,110 @@ +package gtm + +import ( + "fmt" + "regexp" + "testing" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/gtm" + "github.com/akamai/terraform-provider-akamai/v5/pkg/common/testutils" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/stretchr/testify/mock" +) + +func TestDataGTMResource(t *testing.T) { + tests := map[string]struct { + givenTF string + init func(*gtm.Mock) + expectedAttributes map[string]string + expectedMissingAttributes []string + expectError *regexp.Regexp + }{ + "happy path - GTM data resource should be returned": { + givenTF: "valid.tf", + init: func(m *gtm.Mock) { + m.On("GetResource", mock.Anything, "resource1", "test.domain.net").Return(>m.Resource{ + Type: "XML load object via HTTP", + LeastSquaresDecay: 0, + Description: "terraform test resource", + AggregationType: "latest", + LoadImbalancePercentage: 0, + UpperBound: 100, + Name: "property", + MaxUMultiplicativeIncrement: 0, + DecayRate: 0, + Links: []*gtm.Link{{ + Rel: "self", + Href: "https://akaa-ouijhfns55qwgfuc-knsod5nrjl2w2gmt.luna-dev.akamaiapis.net/config-gtm/v1/domains/" + + "test.cli.domain.net/resources/resource1", + }, + }, + ResourceInstances: []*gtm.ResourceInstance{{ + DatacenterId: 3131, + UseDefaultLoadObject: false, + LoadObject: gtm.LoadObject{ + LoadObject: "/test2", + LoadObjectPort: 80, + LoadServers: []string{"2.3.4.5"}, + }, + }}, + }, nil) + }, + expectedAttributes: map[string]string{ + "aggregation_type": "latest", + "description": "terraform test resource", + "type": "XML load object via HTTP", + "upper_bound": "100", + "decay_rate": "0", + "links.#": "1", + "resource_instances.#": "1", + "resource_instances.0.datacenter_id": "3131", + }, + }, + "missing required argument resource_name": { + givenTF: "missing_resource_name.tf", + expectError: regexp.MustCompile(`The argument "resource_name" is required, but no definition was found`), + }, + "missing required argument domain": { + givenTF: "missing_domain.tf", + expectError: regexp.MustCompile(`The argument "domain" is required, but no definition was found`), + }, + "error response from api": { + givenTF: "valid.tf", + init: func(m *gtm.Mock) { + m.On("GetResource", mock.Anything, "resource1", "test.domain.net").Return( + nil, fmt.Errorf("oops")) + }, + expectError: regexp.MustCompile("oops"), + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + client := >m.Mock{} + if test.init != nil { + test.init(client) + } + var checkFuncs []resource.TestCheckFunc + for k, v := range test.expectedAttributes { + checkFuncs = append(checkFuncs, resource.TestCheckResourceAttr("data.akamai_gtm_resource.my_gtm_resource", k, v)) + } + for _, v := range test.expectedMissingAttributes { + checkFuncs = append(checkFuncs, resource.TestCheckNoResourceAttr("data.akamai_gtm_resource.my_gtm_resource", v)) + } + + useClient(client, func() { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV5ProviderFactories: testAccProvidersProtoV5, + Steps: []resource.TestStep{{ + Config: testutils.LoadFixtureString(t, fmt.Sprintf("testdata/TestDataGTMResource/%s", test.givenTF)), + Check: resource.ComposeAggregateTestCheckFunc(checkFuncs...), + ExpectError: test.expectError, + }}, + }) + }) + + client.AssertExpectations(t) + }) + } +} diff --git a/pkg/providers/gtm/data_akamai_gtm_resources.go b/pkg/providers/gtm/data_akamai_gtm_resources.go new file mode 100644 index 000000000..776d1d3d6 --- /dev/null +++ b/pkg/providers/gtm/data_akamai_gtm_resources.go @@ -0,0 +1,160 @@ +package gtm + +import ( + "context" + "fmt" + + "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +var ( + _ datasource.DataSource = &resourcesDataSource{} + _ datasource.DataSourceWithConfigure = &resourcesDataSource{} +) + +// resourcesDataSourceModel describes the data source data model for GTM resources data source. +type resourcesDataSourceModel struct { + ID types.String `tfsdk:"id"` + Domain types.String `tfsdk:"domain"` + Resources []domainResource `tfsdk:"resources"` +} + +var ( + resourcesBlock = map[string]schema.Block{ + "resources": schema.SetNestedBlock{ + Description: "GTM Resources data source.", + NestedObject: schema.NestedBlockObject{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + Description: "A descriptive label for the resource.", + }, + "aggregation_type": schema.StringAttribute{ + Computed: true, + Description: "Specifies how GTM handles different load numbers when multiple load servers are used for a data center or property.", + }, + "constrained_property": schema.StringAttribute{ + Computed: true, + Description: "Specifies the name of the property that this resource constraints.", + }, + "decay_rate": schema.Float64Attribute{ + Computed: true, + Description: "For internal use only.", + }, + "description": schema.StringAttribute{ + Computed: true, + Description: "A descriptive note which allows to track what is constrained by this resource.", + }, + "host_header": schema.StringAttribute{ + Computed: true, + Description: "Specifies the host header used when fetching the load object.", + }, + "leader_string": schema.StringAttribute{ + Computed: true, + Description: "Specifies the text that comes before the load object.", + }, + "least_squares_decay": schema.Float64Attribute{ + Computed: true, + Description: "For internal use only.", + }, + "load_imbalance_percentage": schema.Float64Attribute{ + Computed: true, + Description: "Indicates the percentage of load imbalance for the domain.", + }, + "max_u_multiplicative_increment": schema.Float64Attribute{ + Computed: true, + Description: "For internal use only.", + }, + "type": schema.StringAttribute{ + Computed: true, + Description: "Indicates the type of load object used to determine the load on the resource.", + }, + "upper_bound": schema.Int64Attribute{ + Computed: true, + Description: "An optional sanity check that specifies the maximum allowed value for any component of the load object.", + }, + }, + Blocks: resourceBlocks, + }, + }, + } +) + +// NewGTMResourcesDataSource returns a new GTM resources data source. +func NewGTMResourcesDataSource() datasource.DataSource { + return &resourcesDataSource{} +} + +// resourcesDataSource defines the data source implementation for fetching GTM resources information. +type resourcesDataSource struct { + meta meta.Meta +} + +// Metadata configures data source's meta information. +func (d *resourcesDataSource) Metadata(_ context.Context, _ datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = "akamai_gtm_resources" +} + +// Configure configures data source at the beginning of the lifecycle. +func (d *resourcesDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + if req.ProviderData == nil { + // ProviderData is nil when Configure is run first time as part of ValidateDataSourceConfig in framework provider + return + } + + defer func() { + if r := recover(); r != nil { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected meta.Meta, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + } + }() + + d.meta = meta.Must(req.ProviderData) +} + +// Schema is used to define data source's terraform schema. +func (d *resourcesDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = schema.Schema{ + MarkdownDescription: "GTM Resources data source", + Attributes: map[string]schema.Attribute{ + "id": schema.StringAttribute{ + MarkdownDescription: "Identifier of the data source.", + DeprecationMessage: "Required by the terraform plugin testing framework, always set to `akamai_gtm_resources`.", + Computed: true, + }, + "domain": schema.StringAttribute{ + Required: true, + MarkdownDescription: "GTM domain name.", + }, + }, + Blocks: resourcesBlock, + } +} + +// Read is called when the provider must read data source values in order to update state. +func (d *resourcesDataSource) Read(ctx context.Context, request datasource.ReadRequest, response *datasource.ReadResponse) { + tflog.Debug(ctx, "GTM Resources DataSource Read") + + var data resourcesDataSourceModel + if response.Diagnostics.Append(request.Config.Get(ctx, &data)...); response.Diagnostics.HasError() { + return + } + + client := frameworkInst.Client(d.meta) + resources, err := client.ListResources(ctx, data.Domain.ValueString()) + if err != nil { + response.Diagnostics.AddError("fetching GTM resources failed:", err.Error()) + return + } + + data.ID = types.StringValue("akamai_gtm_resources") + data.Resources = getResources(resources) + + response.Diagnostics.Append(response.State.Set(ctx, &data)...) +} diff --git a/pkg/providers/gtm/data_akamai_gtm_resources_test.go b/pkg/providers/gtm/data_akamai_gtm_resources_test.go new file mode 100644 index 000000000..f08a7bb98 --- /dev/null +++ b/pkg/providers/gtm/data_akamai_gtm_resources_test.go @@ -0,0 +1,134 @@ +package gtm + +import ( + "fmt" + "regexp" + "testing" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/gtm" + "github.com/akamai/terraform-provider-akamai/v5/pkg/common/testutils" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/stretchr/testify/mock" +) + +func TestDataGTMResources(t *testing.T) { + tests := map[string]struct { + givenTF string + init func(*gtm.Mock) + expectedAttributes map[string]string + expectedMissingAttributes []string + expectError *regexp.Regexp + }{ + "happy path - GTM data resources should be returned": { + givenTF: "valid.tf", + init: func(m *gtm.Mock) { + m.On("ListResources", mock.Anything, "test.domain.net").Return([]*gtm.Resource{ + { + Type: "XML load object via HTTP", + LeastSquaresDecay: 0, + Description: "terraform test resource1", + AggregationType: "latest1", + LoadImbalancePercentage: 0, + UpperBound: 100, + Name: "property1", + MaxUMultiplicativeIncrement: 0, + DecayRate: 0, + Links: []*gtm.Link{{ + Rel: "self", + Href: "https://test.domain1.net/config-gtm/v1/domains/" + + "test.cli.domain.net/resources/resource1", + }, + }, + ResourceInstances: []*gtm.ResourceInstance{{ + DatacenterId: 3131, + UseDefaultLoadObject: false, + LoadObject: gtm.LoadObject{ + LoadObject: "/test1", + LoadObjectPort: 80, + LoadServers: []string{"2.3.4.5"}, + }, + }, + }, + }, + { + Type: "XML load object via HTTP", + LeastSquaresDecay: 0, + Description: "terraform test resource2", + AggregationType: "latest2", + LoadImbalancePercentage: 0, + UpperBound: 100, + Name: "property2", + MaxUMultiplicativeIncrement: 0, + DecayRate: 0, + Links: []*gtm.Link{{ + Rel: "self1", + Href: "https://test.domain1.net/config-gtm/v1/domains/" + + "test.cli.domain.net/resources/resource2", + }, + }, + ResourceInstances: []*gtm.ResourceInstance{{ + DatacenterId: 3132, + UseDefaultLoadObject: false, + LoadObject: gtm.LoadObject{ + LoadObject: "/test2", + LoadObjectPort: 80, + LoadServers: []string{"2.3.4.5"}, + }, + }, + }, + }, + }, nil) + }, + expectedAttributes: map[string]string{ + "resources.0.aggregation_type": "latest1", + "resources.0.description": "terraform test resource1", + "resources.0.resource_instances.0.datacenter_id": "3131", + "resources.1.aggregation_type": "latest2", + "resources.1.description": "terraform test resource2", + "resources.1.resource_instances.0.datacenter_id": "3132", + }, + }, + "missing required argument domain": { + givenTF: "missing_domain.tf", + expectError: regexp.MustCompile(`The argument "domain" is required, but no definition was found`), + }, + "error response from api": { + givenTF: "valid.tf", + init: func(m *gtm.Mock) { + m.On("ListResources", mock.Anything, "test.domain.net").Return( + nil, fmt.Errorf("oops")) + }, + expectError: regexp.MustCompile("oops"), + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + client := >m.Mock{} + if test.init != nil { + test.init(client) + } + var checkFuncs []resource.TestCheckFunc + for k, v := range test.expectedAttributes { + checkFuncs = append(checkFuncs, resource.TestCheckResourceAttr("data.akamai_gtm_resources.my_gtm_resources", k, v)) + } + for _, v := range test.expectedMissingAttributes { + checkFuncs = append(checkFuncs, resource.TestCheckNoResourceAttr("data.akamai_gtm_resources.my_gtm_resources", v)) + } + + useClient(client, func() { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProtoV5ProviderFactories: testAccProvidersProtoV5, + Steps: []resource.TestStep{{ + Config: testutils.LoadFixtureString(t, fmt.Sprintf("testdata/TestDataGTMResources/%s", test.givenTF)), + Check: resource.ComposeAggregateTestCheckFunc(checkFuncs...), + ExpectError: test.expectError, + }}, + }) + }) + + client.AssertExpectations(t) + }) + } +} diff --git a/pkg/providers/gtm/gtm.go b/pkg/providers/gtm/gtm.go index 39c51c630..0c584eaaa 100644 --- a/pkg/providers/gtm/gtm.go +++ b/pkg/providers/gtm/gtm.go @@ -4,4 +4,5 @@ import "github.com/akamai/terraform-provider-akamai/v5/pkg/providers/registry" func init() { registry.RegisterPluginSubprovider(NewSubprovider()) + registry.RegisterFrameworkSubprovider(NewFrameworkSubprovider()) } diff --git a/pkg/providers/gtm/provider.go b/pkg/providers/gtm/provider.go index d3b202aa2..2489d5bbc 100644 --- a/pkg/providers/gtm/provider.go +++ b/pkg/providers/gtm/provider.go @@ -7,51 +7,87 @@ import ( "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/gtm" "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" "github.com/akamai/terraform-provider-akamai/v5/pkg/subprovider" + "github.com/hashicorp/terraform-plugin-framework/datasource" + "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) type ( - // Subprovider gathers gtm resources and data sources - Subprovider struct { + // PluginSubprovider gathers gtm resources and data sources + PluginSubprovider struct { client gtm.GTM } - option func(p *Subprovider) + // FrameworkSubprovider gathers property resources and data sources written using terraform-plugin-framework + FrameworkSubprovider struct { + client gtm.GTM + } ) +var _ subprovider.Framework = &FrameworkSubprovider{} +var _ subprovider.Plugin = &PluginSubprovider{} + var ( - once sync.Once + oncePlugin, onceFramework sync.Once - inst *Subprovider -) + inst *PluginSubprovider -var _ subprovider.Plugin = &Subprovider{} + frameworkInst *FrameworkSubprovider +) -// NewSubprovider returns a core sub provider -func NewSubprovider() *Subprovider { - once.Do(func() { - inst = &Subprovider{} +// NewSubprovider returns a new GTM subprovider +func NewSubprovider() *PluginSubprovider { + oncePlugin.Do(func() { + inst = &PluginSubprovider{} }) return inst } -func withClient(c gtm.GTM) option { - return func(p *Subprovider) { - p.client = c - } +// NewFrameworkSubprovider returns a core Framework based sub provider +func NewFrameworkSubprovider() *FrameworkSubprovider { + onceFramework.Do(func() { + frameworkInst = &FrameworkSubprovider{} + }) + + return frameworkInst } -// Client returns the DNS interface -func (p *Subprovider) Client(meta meta.Meta) gtm.GTM { +// Client returns the GTM interface +func (p *PluginSubprovider) Client(meta meta.Meta) gtm.GTM { if p.client != nil { return p.client } return gtm.Client(meta.Session()) } +// Client returns the GTM interface +func (f *FrameworkSubprovider) Client(meta meta.Meta) gtm.GTM { + if f.client != nil { + return f.client + } + return gtm.Client(meta.Session()) +} + +// Resources returns the GTM resources implemented using terraform-plugin-framework +func (f *FrameworkSubprovider) Resources() []func() resource.Resource { + return []func() resource.Resource{} +} + +// DataSources returns the GTM data sources implemented using terraform-plugin-framework +func (f *FrameworkSubprovider) DataSources() []func() datasource.DataSource { + return []func() datasource.DataSource{ + NewGTMAsmapDataSource, + NewGTMCidrmapDataSource, + NewGTMDomainDataSource, + NewGTMDomainsDataSource, + NewGTMResourceDataSource, + NewGTMResourcesDataSource, + } +} + // Resources returns terraform resources for gtm -func (p *Subprovider) Resources() map[string]*schema.Resource { +func (p *PluginSubprovider) Resources() map[string]*schema.Resource { return map[string]*schema.Resource{ "akamai_gtm_domain": resourceGTMv1Domain(), "akamai_gtm_property": resourceGTMv1Property(), @@ -64,7 +100,7 @@ func (p *Subprovider) Resources() map[string]*schema.Resource { } // DataSources returns terraform data sources for gtm -func (p *Subprovider) DataSources() map[string]*schema.Resource { +func (p *PluginSubprovider) DataSources() map[string]*schema.Resource { return map[string]*schema.Resource{ "akamai_gtm_datacenter": dataSourceGTMDatacenter(), "akamai_gtm_datacenters": dataSourceGTMDatacenters(), diff --git a/pkg/providers/gtm/provider_test.go b/pkg/providers/gtm/provider_test.go index b98cf76a5..4a372ce3f 100644 --- a/pkg/providers/gtm/provider_test.go +++ b/pkg/providers/gtm/provider_test.go @@ -1,28 +1,53 @@ package gtm import ( + "context" "log" "os" "sync" "testing" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/gtm" - "github.com/akamai/terraform-provider-akamai/v5/pkg/akamai" "github.com/akamai/terraform-provider-akamai/v5/pkg/common/testutils" + "github.com/hashicorp/terraform-plugin-framework/provider" + "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-go/tfprotov5" + "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +var testAccProvidersProtoV5 map[string]func() (tfprotov5.ProviderServer, error) var testAccProviders map[string]func() (*schema.Provider, error) +var testAccFrameworkProvider provider.Provider var testAccProvider *schema.Provider func TestMain(m *testing.M) { testAccProvider = akamai.NewPluginProvider(NewSubprovider())() + testAccFrameworkProvider = akamai.NewFrameworkProvider(NewFrameworkSubprovider())() + testAccProviders = map[string]func() (*schema.Provider, error){ "akamai": func() (*schema.Provider, error) { return testAccProvider, nil }, } + testAccProvidersProtoV5 = map[string]func() (tfprotov5.ProviderServer, error){ + "akamai": func() (tfprotov5.ProviderServer, error) { + ctx := context.Background() + providers := []func() tfprotov5.ProviderServer{ + providerserver.NewProtocol5( + testAccFrameworkProvider, + ), + } + + muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) + if err != nil { + return nil, err + } + + return muxServer.ProviderServer(), nil + }, + } if err := testutils.TFTestSetup(); err != nil { log.Fatal(err) @@ -41,10 +66,12 @@ var clientLock sync.Mutex func useClient(client gtm.GTM, f func()) { clientLock.Lock() orig := inst.client + origFrameworkClient := frameworkInst.client inst.client = client - + frameworkInst.client = client defer func() { inst.client = orig + frameworkInst.client = origFrameworkClient clientLock.Unlock() }() diff --git a/pkg/providers/gtm/resource_akamai_gtm_domain_test.go b/pkg/providers/gtm/resource_akamai_gtm_domain_test.go index d4810c41a..35689ea9d 100644 --- a/pkg/providers/gtm/resource_akamai_gtm_domain_test.go +++ b/pkg/providers/gtm/resource_akamai_gtm_domain_test.go @@ -24,7 +24,7 @@ func TestResGtmDomain(t *testing.T) { }) dr := gtm.DomainResponse{} - dr.Resource = &domain + dr.Resource = &testDomain dr.Status = &pendingResponseStatus client.On("CreateDomain", mock.Anything, // ctx is irrelevant for this test @@ -38,7 +38,7 @@ func TestResGtmDomain(t *testing.T) { mock.Anything, // ctx is irrelevant for this test mock.AnythingOfType("string"), mock.AnythingOfType("string"), - ).Return(&domain) + ).Return(&testDomain) client.On("GetDomainStatus", mock.Anything, // ctx is irrelevant for this test @@ -96,7 +96,7 @@ func TestResGtmDomain(t *testing.T) { }) dr := gtm.DomainResponse{} - dr.Resource = &domain + dr.Resource = &testDomain dr.Status = &pendingResponseStatus client.On("CreateDomain", mock.Anything, // ctx is irrelevant for this test @@ -110,7 +110,7 @@ func TestResGtmDomain(t *testing.T) { mock.Anything, // ctx is irrelevant for this test mock.AnythingOfType("string"), mock.AnythingOfType("string"), - ).Return(&domain) + ).Return(&testDomain) client.On("GetDomainStatus", mock.Anything, // ctx is irrelevant for this test @@ -160,7 +160,7 @@ func TestResGtmDomain(t *testing.T) { mock.Anything, // ctx is irrelevant for this test mock.AnythingOfType("string"), mock.AnythingOfType("string"), - ).Return(&domain) + ).Return(&testDomain) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ @@ -181,7 +181,7 @@ func TestResGtmDomain(t *testing.T) { client := >m.Mock{} dr := gtm.DomainResponse{} - dr.Resource = &domain + dr.Resource = &testDomain dr.Status = &deniedResponseStatus client.On("CreateDomain", mock.Anything, // ctx is irrelevant for this test @@ -193,7 +193,7 @@ func TestResGtmDomain(t *testing.T) { mock.Anything, // ctx is irrelevant for this test mock.AnythingOfType("string"), mock.AnythingOfType("string"), - ).Return(&domain) + ).Return(&testDomain) useClient(client, func() { resource.UnitTest(t, resource.TestCase{ @@ -221,7 +221,7 @@ func TestResGtmDomain(t *testing.T) { }) dr := gtm.DomainResponse{} - dr.Resource = &domain + dr.Resource = &testDomain dr.Status = &pendingResponseStatus client.On("CreateDomain", mock.Anything, @@ -235,7 +235,7 @@ func TestResGtmDomain(t *testing.T) { mock.Anything, mock.AnythingOfType("string"), mock.AnythingOfType("string"), - ).Return(&domain) + ).Return(&testDomain) client.On("GetDomainStatus", mock.Anything, @@ -348,7 +348,7 @@ func getGTMDomainMocks() *gtm.Mock { mock.Anything, // ctx is irrelevant for this test mock.AnythingOfType("string"), mock.AnythingOfType("string"), - ).Return(&domain) + ).Return(&testDomain) client.On("GetDomainStatus", mock.Anything, // ctx is irrelevant for this test @@ -391,7 +391,7 @@ var ( }, } - // links is gtm.Link structure used in tests + // links is gtm.link structure used in tests links = []*gtm.Link{ { Href: "https://akab-ymtebc45gco3ypzj-apz4yxpek55y7fyv.luna.akamaiapis.net/config-gtm/v1/domains/gtmdomtest.akadns.net", @@ -489,8 +489,8 @@ var ( }, } - // status is gtm.ResponseStatus structure used in tests - status = >m.ResponseStatus{ + // testStatus is gtm.ResponseStatus structure used in tests + testStatus = >m.ResponseStatus{ ChangeId: "40e36abd-bfb2-4635-9fca-62175cf17007", Links: &[]gtm.Link{ { @@ -520,11 +520,11 @@ var ( ModificationComments: "Edit Property test_property", Name: gtmTestDomain, Properties: properties, - Status: status, + Status: testStatus, Type: "weighted", } - domain = gtm.Domain{ + testDomain = gtm.Domain{ Datacenters: datacenters, DefaultErrorPenalty: 75, DefaultSslClientCertificate: "", @@ -539,7 +539,7 @@ var ( ModificationComments: "Edit Property test_property", Name: gtmTestDomain, Properties: properties, - Status: status, + Status: testStatus, Type: "weighted", } diff --git a/pkg/providers/gtm/resource_akamai_gtm_property.go b/pkg/providers/gtm/resource_akamai_gtm_property.go index 38963093a..424d0f3bd 100644 --- a/pkg/providers/gtm/resource_akamai_gtm_property.go +++ b/pkg/providers/gtm/resource_akamai_gtm_property.go @@ -10,6 +10,7 @@ import ( "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/gtm" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/session" "github.com/akamai/terraform-provider-akamai/v5/pkg/common/tf" + "github.com/akamai/terraform-provider-akamai/v5/pkg/logger" "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" @@ -40,6 +41,7 @@ func resourceGTMv1Property() *schema.Resource { "name": { Type: schema.TypeString, Required: true, + ForceNew: true, }, "type": { Type: schema.TypeString, @@ -179,10 +181,10 @@ func resourceGTMv1Property() *schema.Resource { Computed: true, }, "traffic_target": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MinItems: 1, + Type: schema.TypeList, + Optional: true, + MinItems: 1, + DiffSuppressFunc: trafficTargetDiffSuppress, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "datacenter_id": { @@ -388,19 +390,7 @@ func validateTestObject(_ context.Context, d *schema.ResourceDiff, m interface{} } } - trafficTarget, err := tf.GetListValue("traffic_target", d) - if err != nil { - return err - } - sortTrafficTargets(trafficTarget) - err = d.SetNew("traffic_target", trafficTarget) - return err -} - -func sortTrafficTargets(targets []interface{}) { - sort.Slice(targets, func(i, j int) bool { - return targets[i].(map[string]interface{})["datacenter_id"].(int) < targets[j].(map[string]interface{})["datacenter_id"].(int) - }) + return nil } // validateTTL is a SchemaValidateDiagFunc to validate dynamic_ttl and static_ttl. @@ -957,7 +947,9 @@ func populateTerraformPropertyState(d *schema.ResourceData, prop *gtm.Property, } } if strings.ToUpper(prop.Type) != "STATIC" { - populateTerraformTrafficTargetState(d, prop, m) + if err := populateTerraformTrafficTargetState(d, prop, m); err != nil { + logger.Errorf("Error setting traffic target: %s", err) + } } populateTerraformStaticRRSetState(d, prop, m) populateTerraformLivenessTestState(d, prop, m) @@ -996,7 +988,7 @@ func populateTrafficTargetObject(ctx context.Context, d *schema.ResourceData, pr } // create and populate Terraform traffic_targets schema -func populateTerraformTrafficTargetState(d *schema.ResourceData, prop *gtm.Property, m interface{}) { +func populateTerraformTrafficTargetState(d *schema.ResourceData, prop *gtm.Property, m interface{}) error { meta := meta.Must(m) logger := meta.Log("Akamai GTM", "populateTerraformTrafficTargetState") @@ -1037,12 +1029,8 @@ func populateTerraformTrafficTargetState(d *schema.ResourceData, prop *gtm.Prope ttStateList = append(ttStateList, ttNew) } } - sortTrafficTargets(ttStateList) - err := d.Set("traffic_target", ttStateList) - if err != nil { - logger.Errorf("Invalid configuration: %s", err.Error()) - } + return d.Set("traffic_target", ttStateList) } // Populate existing static_rr_sets object from resource data @@ -1303,3 +1291,85 @@ func reconcileTerraformLists(terraList []interface{}, newList []interface{}, m i return updatedList } + +func trafficTargetDiffSuppress(_, _, _ string, d *schema.ResourceData) bool { + logger := logger.Get("Akamai GTM", "trafficTargetDiffSuppress") + oldTarget, newTarget := d.GetChange("traffic_target") + + oldTrafficTarget, ok := oldTarget.([]interface{}) + if !ok { + logger.Warnf("wrong type conversion: expected []interface{}, got %T", oldTrafficTarget) + return false + } + + newTrafficTarget, ok := newTarget.([]interface{}) + if !ok { + logger.Warnf("wrong type conversion: expected []interface{}, got %T", oldTrafficTarget) + return false + } + + if len(oldTrafficTarget) != len(newTrafficTarget) { + return false + } + + sort.Slice(oldTrafficTarget, func(i, j int) bool { + return oldTrafficTarget[i].(map[string]interface{})["datacenter_id"].(int) < oldTrafficTarget[j].(map[string]interface{})["datacenter_id"].(int) + }) + sort.Slice(newTrafficTarget, func(i, j int) bool { + return newTrafficTarget[i].(map[string]interface{})["datacenter_id"].(int) < newTrafficTarget[j].(map[string]interface{})["datacenter_id"].(int) + }) + + length := len(oldTrafficTarget) + for i := 0; i < length; i++ { + for k, v := range oldTrafficTarget[i].(map[string]interface{}) { + if k == "servers" { + oldServers := oldTrafficTarget[i].(map[string]interface{})["servers"] + newServers := newTrafficTarget[i].(map[string]interface{})["servers"] + if !serversEqual(oldServers, newServers) { + return false + } + } else { + if newTrafficTarget[i].(map[string]interface{})[k] != v { + return false + } + } + } + } + + return true +} + +// serversEqual checks whether provided sets of ip addresses contain the same entries +func serversEqual(old, new interface{}) bool { + logger := logger.Get("Akamai GTM", "serversEqual") + + oldServers, ok := old.(*schema.Set) + if !ok { + logger.Warnf("wrong type conversion: expected *schema.Set, got %T", oldServers) + return false + } + + newServers, ok := new.(*schema.Set) + if !ok { + logger.Warnf("wrong type conversion: expected *schema.Set, got %T", newServers) + return false + } + + if oldServers.Len() != newServers.Len() { + return false + } + + addresses := make(map[string]bool, oldServers.Len()) + for _, server := range oldServers.List() { + addresses[server.(string)] = true + } + + for _, server := range newServers.List() { + _, ok := addresses[server.(string)] + if !ok { + return false + } + } + + return true +} diff --git a/pkg/providers/gtm/resource_akamai_gtm_property_test.go b/pkg/providers/gtm/resource_akamai_gtm_property_test.go index 477c29546..1e1ea5c72 100644 --- a/pkg/providers/gtm/resource_akamai_gtm_property_test.go +++ b/pkg/providers/gtm/resource_akamai_gtm_property_test.go @@ -11,71 +11,141 @@ import ( "github.com/stretchr/testify/mock" ) -var prop = gtm.Property{ - BackupCName: "", - BackupIp: "", - BalanceByDownloadScore: false, - CName: "www.boo.wow", - Comments: "", - DynamicTTL: 300, - FailbackDelay: 0, - FailoverDelay: 0, - HandoutMode: "normal", - HealthMax: 0, - HealthMultiplier: 0, - HealthThreshold: 0, - Ipv6: false, - LastModified: "2019-04-25T14:53:12.000+00:00", - Links: []*gtm.Link{ - { - Href: "https://akab-ymtebc45gco3ypzj-apz4yxpek55y7fyv.luna.akamaiapis.net/config-gtm/v1/domains/gtmdomtest.akadns.net/properties/test_property", - Rel: "self", +var ( + prop = gtm.Property{ + BackupCName: "", + BackupIp: "", + BalanceByDownloadScore: false, + CName: "www.boo.wow", + Comments: "", + DynamicTTL: 300, + FailbackDelay: 0, + FailoverDelay: 0, + HandoutMode: "normal", + HealthMax: 0, + HealthMultiplier: 0, + HealthThreshold: 0, + Ipv6: false, + LastModified: "2019-04-25T14:53:12.000+00:00", + Links: []*gtm.Link{ + { + Href: "https://akab-ymtebc45gco3ypzj-apz4yxpek55y7fyv.luna.akamaiapis.net/config-gtm/v1/domains/gtmdomtest.akadns.net/properties/test_property", + Rel: "self", + }, }, - }, - LivenessTests: []*gtm.LivenessTest{ - { - DisableNonstandardPortWarning: false, - HttpError3xx: true, - HttpError4xx: true, - HttpError5xx: true, - Name: "health check", - RequestString: "", - ResponseString: "", - SslClientCertificate: "", - SslClientPrivateKey: "", - TestInterval: 60, - TestObject: "/status", - TestObjectPassword: "", - TestObjectPort: 80, - TestObjectProtocol: "HTTP", - TestObjectUsername: "", - TestTimeout: 25.0, + LivenessTests: []*gtm.LivenessTest{ + { + DisableNonstandardPortWarning: false, + HttpError3xx: true, + HttpError4xx: true, + HttpError5xx: true, + Name: "health check", + RequestString: "", + ResponseString: "", + SslClientCertificate: "", + SslClientPrivateKey: "", + TestInterval: 60, + TestObject: "/status", + TestObjectPassword: "", + TestObjectPort: 80, + TestObjectProtocol: "HTTP", + TestObjectUsername: "", + TestTimeout: 25.0, + }, }, - }, - LoadImbalancePercentage: 10.0, - MapName: "", - MaxUnreachablePenalty: 0, - Name: "tfexample_prop_1", - ScoreAggregationType: "mean", - StaticTTL: 600, - StickinessBonusConstant: 0, - StickinessBonusPercentage: 50, - TrafficTargets: []*gtm.TrafficTarget{ - { - DatacenterId: 3131, - Enabled: true, - HandoutCName: "", - Servers: []string{ - "1.2.3.4", - "1.2.3.5", + LoadImbalancePercentage: 10.0, + MapName: "", + MaxUnreachablePenalty: 0, + Name: "tfexample_prop_1", + ScoreAggregationType: "mean", + StaticTTL: 600, + StickinessBonusConstant: 0, + StickinessBonusPercentage: 50, + TrafficTargets: []*gtm.TrafficTarget{ + { + DatacenterId: 3131, + Enabled: true, + HandoutCName: "", + Servers: []string{ + "1.2.3.4", + "1.2.3.5", + }, + Weight: 50.0, }, - Weight: 50.0, }, - }, - Type: "weighted-round-robin", - UnreachableThreshold: 0, - UseComputedTargets: false, -} + Type: "weighted-round-robin", + UnreachableThreshold: 0, + UseComputedTargets: false, + } + + prop2 = gtm.Property{ + BackupCName: "", + BackupIp: "", + BalanceByDownloadScore: false, + CName: "www.boo.wow", + Comments: "", + DynamicTTL: 300, + FailbackDelay: 0, + FailoverDelay: 0, + HandoutMode: "normal", + HealthMax: 0, + HealthMultiplier: 0, + HealthThreshold: 0, + Ipv6: false, + LastModified: "2019-04-25T14:53:12.000+00:00", + Links: []*gtm.Link{ + { + Href: "https://akab-ymtebc45gco3ypzj-apz4yxpek55y7fyv.luna.akamaiapis.net/config-gtm/v1/domains/gtmdomtest.akadns.net/properties/test_property", + Rel: "self", + }, + }, + LivenessTests: []*gtm.LivenessTest{ + { + DisableNonstandardPortWarning: false, + HttpError3xx: true, + HttpError4xx: true, + HttpError5xx: true, + Name: "health check", + RequestString: "", + ResponseString: "", + SslClientCertificate: "", + SslClientPrivateKey: "", + TestInterval: 60, + TestObject: "/status", + TestObjectPassword: "", + TestObjectPort: 80, + TestObjectProtocol: "HTTP", + TestObjectUsername: "", + TestTimeout: 25.0, + }, + }, + LoadImbalancePercentage: 10.0, + MapName: "", + MaxUnreachablePenalty: 0, + Name: "tfexample_prop_1-updated", + ScoreAggregationType: "mean", + StaticTTL: 600, + StickinessBonusConstant: 0, + StickinessBonusPercentage: 50, + TrafficTargets: []*gtm.TrafficTarget{ + { + DatacenterId: 3131, + Enabled: true, + HandoutCName: "", + Servers: []string{ + "1.2.3.4", + "1.2.3.5", + }, + Weight: 50.0, + }, + }, + Type: "weighted-round-robin", + UnreachableThreshold: 0, + UseComputedTargets: false, + } + + propertyResourceName = "akamai_gtm_property.tfexample_prop_1" +) func TestResGtmProperty(t *testing.T) { @@ -83,7 +153,7 @@ func TestResGtmProperty(t *testing.T) { client := >m.Mock{} getCall := client.On("GetProperty", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, mock.AnythingOfType("string"), mock.AnythingOfType("string"), ).Return(nil, >m.Error{ @@ -94,7 +164,7 @@ func TestResGtmProperty(t *testing.T) { resp.Resource = &prop resp.Status = &pendingResponseStatus client.On("CreateProperty", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, mock.AnythingOfType("*gtm.Property"), mock.AnythingOfType("string"), ).Return(&resp, nil).Run(func(args mock.Arguments) { @@ -102,7 +172,7 @@ func TestResGtmProperty(t *testing.T) { }) client.On("NewProperty", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, mock.AnythingOfType("string"), mock.AnythingOfType("string"), ).Return(>m.Property{ @@ -110,20 +180,20 @@ func TestResGtmProperty(t *testing.T) { }) client.On("GetDomainStatus", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, mock.AnythingOfType("string"), ).Return(&completeResponseStatus, nil) client.On("NewTrafficTarget", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, ).Return(>m.TrafficTarget{}) client.On("NewStaticRRSet", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, ).Return(>m.StaticRRSet{}) liveCall := client.On("NewLivenessTest", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int"), @@ -142,7 +212,7 @@ func TestResGtmProperty(t *testing.T) { } client.On("UpdateProperty", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, mock.AnythingOfType("*gtm.Property"), mock.AnythingOfType("string"), ).Return(&completeResponseStatus, nil).Run(func(args mock.Arguments) { @@ -150,13 +220,11 @@ func TestResGtmProperty(t *testing.T) { }) client.On("DeleteProperty", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, mock.AnythingOfType("*gtm.Property"), mock.AnythingOfType("string"), ).Return(&completeResponseStatus, nil) - dataSourceName := "akamai_gtm_property.tfexample_prop_1" - useClient(client, func() { resource.UnitTest(t, resource.TestCase{ ProviderFactories: testAccProviders, @@ -164,15 +232,15 @@ func TestResGtmProperty(t *testing.T) { { Config: testutils.LoadFixtureString(t, "testdata/TestResGtmProperty/create_basic.tf"), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(dataSourceName, "name", "tfexample_prop_1"), - resource.TestCheckResourceAttr(dataSourceName, "type", "weighted-round-robin"), + resource.TestCheckResourceAttr(propertyResourceName, "name", "tfexample_prop_1"), + resource.TestCheckResourceAttr(propertyResourceName, "type", "weighted-round-robin"), ), }, { Config: testutils.LoadFixtureString(t, "testdata/TestResGtmProperty/update_basic.tf"), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(dataSourceName, "name", "tfexample_prop_1"), - resource.TestCheckResourceAttr(dataSourceName, "type", "weighted-round-robin"), + resource.TestCheckResourceAttr(propertyResourceName, "name", "tfexample_prop_1"), + resource.TestCheckResourceAttr(propertyResourceName, "type", "weighted-round-robin"), ), }, }, @@ -186,7 +254,7 @@ func TestResGtmProperty(t *testing.T) { client := >m.Mock{} client.On("CreateProperty", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, mock.AnythingOfType("*gtm.Property"), gtmTestDomain, ).Return(nil, >m.Error{ @@ -194,7 +262,7 @@ func TestResGtmProperty(t *testing.T) { }) client.On("NewProperty", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, mock.AnythingOfType("string"), mock.AnythingOfType("string"), ).Return(>m.Property{ @@ -202,15 +270,15 @@ func TestResGtmProperty(t *testing.T) { }) client.On("NewTrafficTarget", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, ).Return(>m.TrafficTarget{}) client.On("NewStaticRRSet", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, ).Return(>m.StaticRRSet{}) liveCall := client.On("NewLivenessTest", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int"), @@ -250,13 +318,13 @@ func TestResGtmProperty(t *testing.T) { dr.Resource = &prop dr.Status = &deniedResponseStatus client.On("CreateProperty", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, mock.AnythingOfType("*gtm.Property"), gtmTestDomain, ).Return(&dr, nil) client.On("NewProperty", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, mock.AnythingOfType("string"), mock.AnythingOfType("string"), ).Return(>m.Property{ @@ -264,15 +332,15 @@ func TestResGtmProperty(t *testing.T) { }) client.On("NewTrafficTarget", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, ).Return(>m.TrafficTarget{}) client.On("NewStaticRRSet", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, ).Return(>m.StaticRRSet{}) liveCall := client.On("NewLivenessTest", - mock.Anything, // ctx is irrelevant for this test + mock.Anything, mock.AnythingOfType("string"), mock.AnythingOfType("string"), mock.AnythingOfType("int"), @@ -305,6 +373,113 @@ func TestResGtmProperty(t *testing.T) { client.AssertExpectations(t) }) + t.Run("create property and update name - force new", func(t *testing.T) { + client := >m.Mock{} + + getCall := client.On("GetProperty", + mock.Anything, + mock.AnythingOfType("string"), + mock.AnythingOfType("string"), + ).Return(nil, >m.Error{ + StatusCode: http.StatusNotFound, + }) + + resp := gtm.PropertyResponse{} + resp.Resource = &prop + resp.Status = &pendingResponseStatus + client.On("CreateProperty", + mock.Anything, + mock.AnythingOfType("*gtm.Property"), + mock.AnythingOfType("string"), + ).Return(&resp, nil).Run(func(args mock.Arguments) { + getCall.ReturnArguments = mock.Arguments{args.Get(1).(*gtm.Property), nil} + }).Once() + + client.On("NewProperty", + mock.Anything, + mock.AnythingOfType("string"), + mock.AnythingOfType("string"), + ).Return(>m.Property{ + Name: "tfexample_prop_1", + }).Once() + + client.On("NewTrafficTarget", + mock.Anything, + ).Return(>m.TrafficTarget{}) + + client.On("NewStaticRRSet", + mock.Anything, + ).Return(>m.StaticRRSet{}) + + liveCall := client.On("NewLivenessTest", + mock.Anything, + mock.AnythingOfType("string"), + mock.AnythingOfType("string"), + mock.AnythingOfType("int"), + mock.AnythingOfType("float32"), + ) + + liveCall.RunFn = func(args mock.Arguments) { + liveCall.ReturnArguments = mock.Arguments{ + >m.LivenessTest{ + Name: args.String(1), + TestObjectProtocol: args.String(2), + TestInterval: args.Int(3), + TestTimeout: args.Get(4).(float32), + }, + } + } + + client.On("DeleteProperty", + mock.Anything, + mock.AnythingOfType("*gtm.Property"), + mock.AnythingOfType("string"), + ).Return(&completeResponseStatus, nil) + + // Create new property with updated name + client.On("NewProperty", + mock.Anything, + mock.AnythingOfType("string"), + mock.AnythingOfType("string"), + ).Return(>m.Property{ + Name: "tfexample_prop_1-updated", + }).Once() + + resp2 := gtm.PropertyResponse{ + Resource: &prop2, + Status: &pendingResponseStatus, + } + + client.On("CreateProperty", + mock.Anything, + mock.AnythingOfType("*gtm.Property"), + mock.AnythingOfType("string"), + ).Return(&resp2, nil).Run(func(args mock.Arguments) { + getCall.ReturnArguments = mock.Arguments{args.Get(1).(*gtm.Property), nil} + }).Once() + + useClient(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "testdata/TestResGtmProperty/create_basic.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(propertyResourceName, "name", "tfexample_prop_1"), + ), + }, + { + Config: testutils.LoadFixtureString(t, "testdata/TestResGtmProperty/update_name.tf"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(propertyResourceName, "name", "tfexample_prop_1-updated"), + ), + }, + }, + }) + }) + client.AssertExpectations(t) + }) + t.Run("test_object_protocol different than HTTP, HTTPS or FTP", func(t *testing.T) { client := >m.Mock{} @@ -381,8 +556,6 @@ func TestResGtmProperty(t *testing.T) { mock.AnythingOfType("string"), ).Return(&completeResponseStatus, nil) - dataSourceName := "akamai_gtm_property.tfexample_prop_1" - useClient(client, func() { resource.UnitTest(t, resource.TestCase{ ProviderFactories: testAccProviders, @@ -390,15 +563,15 @@ func TestResGtmProperty(t *testing.T) { { Config: testutils.LoadFixtureString(t, "testdata/TestResGtmProperty/test_object/test_object_not_required.tf"), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(dataSourceName, "name", "tfexample_prop_1"), - resource.TestCheckResourceAttr(dataSourceName, "type", "weighted-round-robin"), + resource.TestCheckResourceAttr(propertyResourceName, "name", "tfexample_prop_1"), + resource.TestCheckResourceAttr(propertyResourceName, "type", "weighted-round-robin"), ), }, { Config: testutils.LoadFixtureString(t, "testdata/TestResGtmProperty/update_basic.tf"), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr(dataSourceName, "name", "tfexample_prop_1"), - resource.TestCheckResourceAttr(dataSourceName, "type", "weighted-round-robin"), + resource.TestCheckResourceAttr(propertyResourceName, "name", "tfexample_prop_1"), + resource.TestCheckResourceAttr(propertyResourceName, "type", "weighted-round-robin"), ), }, }, @@ -471,7 +644,6 @@ func TestResourceGTMTrafficTargetOrder(t *testing.T) { pathForUpdate string nonEmptyPlan bool planOnly bool - expectError *regexp.Regexp }{ "second apply - no diff": { client: getMocks(), @@ -487,14 +659,6 @@ func TestResourceGTMTrafficTargetOrder(t *testing.T) { nonEmptyPlan: false, planOnly: true, }, - "re-ordered traffic targets and weight change - diff": { - client: getMocks(), - pathForCreate: "testdata/TestResGtmProperty/multiple_servers.tf", - pathForUpdate: "testdata/TestResGtmProperty/traffic_target/diff_order_and_weight.tf", - nonEmptyPlan: false, - planOnly: true, - expectError: regexp.MustCompile(`~ traffic_target {\n +~ weight += 200 -> 400\n +# \(4 unchanged attributes hidden\)\n`), - }, "re-ordered traffic target with no datacenter_id - no diff": { client: getMocks(), pathForCreate: "testdata/TestResGtmProperty/traffic_target/no_datacenter_id.tf", @@ -523,13 +687,6 @@ func TestResourceGTMTrafficTargetOrder(t *testing.T) { nonEmptyPlan: true, planOnly: true, }, - "changed 'handout_limit' field outside traffic target - diff": { - client: getMocks(), - pathForCreate: "testdata/TestResGtmProperty/multiple_servers.tf", - pathForUpdate: "testdata/TestResGtmProperty/traffic_target/change_handout_limit.tf", - nonEmptyPlan: true, - planOnly: true, - }, "changed 'enabled' field in re-ordered traffic target - diff (messy)": { client: getMocks(), pathForCreate: "testdata/TestResGtmProperty/multiple_servers.tf", @@ -537,13 +694,6 @@ func TestResourceGTMTrafficTargetOrder(t *testing.T) { nonEmptyPlan: true, planOnly: true, }, - "changed order of servers and re-ordered traffic target - no diff": { - client: getMocks(), - pathForCreate: "testdata/TestResGtmProperty/traffic_target/diff_order_and_servers_1.tf", - pathForUpdate: "testdata/TestResGtmProperty/traffic_target/diff_order_and_servers_2.tf", - nonEmptyPlan: false, - planOnly: true, - }, "re-ordered servers in traffic targets - no diff": { client: getMocks(), pathForCreate: "testdata/TestResGtmProperty/multiple_servers.tf", @@ -595,7 +745,6 @@ func TestResourceGTMTrafficTargetOrder(t *testing.T) { Config: testutils.LoadFixtureString(t, test.pathForUpdate), PlanOnly: test.planOnly, ExpectNonEmptyPlan: test.nonEmptyPlan, - ExpectError: test.expectError, }, }, }) diff --git a/pkg/providers/gtm/testdata/TestDataGTMResource/missing_domain.tf b/pkg/providers/gtm/testdata/TestDataGTMResource/missing_domain.tf new file mode 100644 index 000000000..55b61b602 --- /dev/null +++ b/pkg/providers/gtm/testdata/TestDataGTMResource/missing_domain.tf @@ -0,0 +1,7 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_gtm_resource" "my_gtm_resource" { + resource_name = "resource1" +} \ No newline at end of file diff --git a/pkg/providers/gtm/testdata/TestDataGTMResource/missing_resource_name.tf b/pkg/providers/gtm/testdata/TestDataGTMResource/missing_resource_name.tf new file mode 100644 index 000000000..197c21037 --- /dev/null +++ b/pkg/providers/gtm/testdata/TestDataGTMResource/missing_resource_name.tf @@ -0,0 +1,7 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_gtm_resource" "my_gtm_resource" { + domain = "test.domain.net" +} \ No newline at end of file diff --git a/pkg/providers/gtm/testdata/TestDataGTMResource/valid.tf b/pkg/providers/gtm/testdata/TestDataGTMResource/valid.tf new file mode 100644 index 000000000..cefd8c2fd --- /dev/null +++ b/pkg/providers/gtm/testdata/TestDataGTMResource/valid.tf @@ -0,0 +1,8 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_gtm_resource" "my_gtm_resource" { + domain = "test.domain.net" + resource_name = "resource1" +} \ No newline at end of file diff --git a/pkg/providers/gtm/testdata/TestDataGTMResources/missing_domain.tf b/pkg/providers/gtm/testdata/TestDataGTMResources/missing_domain.tf new file mode 100644 index 000000000..313d039e9 --- /dev/null +++ b/pkg/providers/gtm/testdata/TestDataGTMResources/missing_domain.tf @@ -0,0 +1,6 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_gtm_resources" "my_gtm_resources" { +} \ No newline at end of file diff --git a/pkg/providers/gtm/testdata/TestDataGTMResources/valid.tf b/pkg/providers/gtm/testdata/TestDataGTMResources/valid.tf new file mode 100644 index 000000000..83ec86bf6 --- /dev/null +++ b/pkg/providers/gtm/testdata/TestDataGTMResources/valid.tf @@ -0,0 +1,7 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_gtm_resources" "my_gtm_resources" { + domain = "test.domain.net" +} \ No newline at end of file diff --git a/pkg/providers/gtm/testdata/TestDataGtmAsmap/missing_domain.tf b/pkg/providers/gtm/testdata/TestDataGtmAsmap/missing_domain.tf new file mode 100644 index 000000000..7188f0c5b --- /dev/null +++ b/pkg/providers/gtm/testdata/TestDataGtmAsmap/missing_domain.tf @@ -0,0 +1,7 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_gtm_asmap" "my_gtm_asmap" { + map_name = "map1" +} \ No newline at end of file diff --git a/pkg/providers/gtm/testdata/TestDataGtmAsmap/missing_map_name.tf b/pkg/providers/gtm/testdata/TestDataGtmAsmap/missing_map_name.tf new file mode 100644 index 000000000..2cfbc8e7f --- /dev/null +++ b/pkg/providers/gtm/testdata/TestDataGtmAsmap/missing_map_name.tf @@ -0,0 +1,7 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_gtm_asmap" "my_gtm_asmap" { + domain = "test.domain.net" +} diff --git a/pkg/providers/gtm/testdata/TestDataGtmAsmap/valid.tf b/pkg/providers/gtm/testdata/TestDataGtmAsmap/valid.tf new file mode 100644 index 000000000..46c6e1759 --- /dev/null +++ b/pkg/providers/gtm/testdata/TestDataGtmAsmap/valid.tf @@ -0,0 +1,8 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_gtm_asmap" "my_gtm_asmap" { + domain = "test.domain.net" + map_name = "map1" +} diff --git a/pkg/providers/gtm/testdata/TestDataGtmCidrmap/missing_domain.tf b/pkg/providers/gtm/testdata/TestDataGtmCidrmap/missing_domain.tf new file mode 100644 index 000000000..7a043e40d --- /dev/null +++ b/pkg/providers/gtm/testdata/TestDataGtmCidrmap/missing_domain.tf @@ -0,0 +1,7 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_gtm_cidrmap" "gtm_cidrmap" { + map_name = "mapTest" +} \ No newline at end of file diff --git a/pkg/providers/gtm/testdata/TestDataGtmCidrmap/missing_map_name.tf b/pkg/providers/gtm/testdata/TestDataGtmCidrmap/missing_map_name.tf new file mode 100644 index 000000000..2302d533c --- /dev/null +++ b/pkg/providers/gtm/testdata/TestDataGtmCidrmap/missing_map_name.tf @@ -0,0 +1,7 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_gtm_cidrmap" "gtm_cidrmap" { + domain = "test.cidrmap.domain.net" +} \ No newline at end of file diff --git a/pkg/providers/gtm/testdata/TestDataGtmCidrmap/valid.tf b/pkg/providers/gtm/testdata/TestDataGtmCidrmap/valid.tf new file mode 100644 index 000000000..ba4d3860e --- /dev/null +++ b/pkg/providers/gtm/testdata/TestDataGtmCidrmap/valid.tf @@ -0,0 +1,8 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_gtm_cidrmap" "gtm_cidrmap" { + domain = "test.cidrmap.domain.net" + map_name = "mapTest" +} diff --git a/pkg/providers/gtm/testdata/TestDataGtmDomain/missing_domain_name.tf b/pkg/providers/gtm/testdata/TestDataGtmDomain/missing_domain_name.tf new file mode 100644 index 000000000..8cce06c15 --- /dev/null +++ b/pkg/providers/gtm/testdata/TestDataGtmDomain/missing_domain_name.tf @@ -0,0 +1,6 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_gtm_domain" "domain" { +} \ No newline at end of file diff --git a/pkg/providers/gtm/testdata/TestDataGtmDomain/valid.tf b/pkg/providers/gtm/testdata/TestDataGtmDomain/valid.tf new file mode 100644 index 000000000..4991303a6 --- /dev/null +++ b/pkg/providers/gtm/testdata/TestDataGtmDomain/valid.tf @@ -0,0 +1,7 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_gtm_domain" "domain" { + name = "test.cli.devexp-terraform.akadns.net" +} \ No newline at end of file diff --git a/pkg/providers/gtm/testdata/TestDataGtmDomains/valid.tf b/pkg/providers/gtm/testdata/TestDataGtmDomains/valid.tf new file mode 100644 index 000000000..6b0d7631b --- /dev/null +++ b/pkg/providers/gtm/testdata/TestDataGtmDomains/valid.tf @@ -0,0 +1,6 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_gtm_domains" "domains" { +} \ No newline at end of file diff --git a/pkg/providers/gtm/testdata/TestResGtmProperty/traffic_target/change_handout_limit.tf b/pkg/providers/gtm/testdata/TestResGtmProperty/traffic_target/change_handout_limit.tf deleted file mode 100644 index f6d2bcb14..000000000 --- a/pkg/providers/gtm/testdata/TestResGtmProperty/traffic_target/change_handout_limit.tf +++ /dev/null @@ -1,69 +0,0 @@ -provider "akamai" { - edgerc = "../../test/edgerc" -} - -resource "akamai_gtm_property" "tfexample_prop_1" { - domain = "gtm_terra_testdomain.akadns.net" - name = "tfexample_prop_1" - type = "weighted-round-robin" - score_aggregation_type = "median" - handout_limit = 15 - handout_mode = "normal" - traffic_target { - datacenter_id = 3131 - enabled = true - weight = 200 - servers = ["1.2.3.4", "1.2.3.5"] - handout_cname = "test" - } - - traffic_target { - datacenter_id = 3132 - enabled = true - weight = 200 - servers = ["1.2.3.6"] - handout_cname = "test" - } - - traffic_target { - datacenter_id = 3133 - enabled = true - weight = 200 - servers = ["1.2.3.7", "1.2.3.8"] - handout_cname = "test" - } - - liveness_test { - name = "lt5" - test_interval = 40 - test_object_protocol = "HTTP" - test_timeout = 30 - answers_required = false - disable_nonstandard_port_warning = false - error_penalty = 0 - http_error3xx = false - http_error4xx = false - http_error5xx = false - disabled = false - http_header { - name = "test_name" - value = "test_value" - } - peer_certificate_verification = false - recursion_requested = false - request_string = "" - resource_type = "" - response_string = "" - ssl_client_certificate = "" - ssl_client_private_key = "" - test_object = "/junk" - test_object_password = "" - test_object_port = 1 - test_object_username = "" - timeout_penalty = 0 - } - failover_delay = 0 - failback_delay = 0 - wait_on_complete = false -} - diff --git a/pkg/providers/gtm/testdata/TestResGtmProperty/traffic_target/diff_order_and_servers_1.tf b/pkg/providers/gtm/testdata/TestResGtmProperty/traffic_target/diff_order_and_servers_1.tf deleted file mode 100644 index 67da56855..000000000 --- a/pkg/providers/gtm/testdata/TestResGtmProperty/traffic_target/diff_order_and_servers_1.tf +++ /dev/null @@ -1,69 +0,0 @@ -provider "akamai" { - edgerc = "../../test/edgerc" -} - -resource "akamai_gtm_property" "tfexample_prop_1" { - domain = "gtm_terra_testdomain.akadns.net" - name = "tfexample_prop_1" - type = "weighted-round-robin" - score_aggregation_type = "median" - handout_limit = 5 - handout_mode = "normal" - traffic_target { - datacenter_id = 3132 - enabled = true - weight = 200 - servers = ["1.2.3.6"] - handout_cname = "test" - } - - traffic_target { - datacenter_id = 3133 - enabled = true - weight = 200 - servers = ["1.2.3.8", "1.2.3.7"] - handout_cname = "test" - } - - traffic_target { - datacenter_id = 3131 - enabled = true - weight = 200 - servers = ["1.2.3.5", "1.2.3.4"] - handout_cname = "test" - } - - liveness_test { - name = "lt5" - test_interval = 40 - test_object_protocol = "HTTP" - test_timeout = 30 - answers_required = false - disable_nonstandard_port_warning = false - error_penalty = 0 - http_error3xx = false - http_error4xx = false - http_error5xx = false - disabled = false - http_header { - name = "test_name" - value = "test_value" - } - peer_certificate_verification = false - recursion_requested = false - request_string = "" - resource_type = "" - response_string = "" - ssl_client_certificate = "" - ssl_client_private_key = "" - test_object = "/junk" - test_object_password = "" - test_object_port = 1 - test_object_username = "" - timeout_penalty = 0 - } - failover_delay = 0 - failback_delay = 0 - wait_on_complete = false -} - diff --git a/pkg/providers/gtm/testdata/TestResGtmProperty/traffic_target/diff_order_and_servers_2.tf b/pkg/providers/gtm/testdata/TestResGtmProperty/traffic_target/diff_order_and_servers_2.tf deleted file mode 100644 index abe72d39a..000000000 --- a/pkg/providers/gtm/testdata/TestResGtmProperty/traffic_target/diff_order_and_servers_2.tf +++ /dev/null @@ -1,69 +0,0 @@ -provider "akamai" { - edgerc = "../../test/edgerc" -} - -resource "akamai_gtm_property" "tfexample_prop_1" { - domain = "gtm_terra_testdomain.akadns.net" - name = "tfexample_prop_1" - type = "weighted-round-robin" - score_aggregation_type = "median" - handout_limit = 5 - handout_mode = "normal" - traffic_target { - datacenter_id = 3132 - enabled = true - weight = 200 - servers = ["1.2.3.6"] - handout_cname = "test" - } - - traffic_target { - datacenter_id = 3133 - enabled = true - weight = 200 - servers = ["1.2.3.7", "1.2.3.8"] - handout_cname = "test" - } - - traffic_target { - datacenter_id = 3131 - enabled = true - weight = 200 - servers = ["1.2.3.5", "1.2.3.4"] - handout_cname = "test" - } - - liveness_test { - name = "lt5" - test_interval = 40 - test_object_protocol = "HTTP" - test_timeout = 30 - answers_required = false - disable_nonstandard_port_warning = false - error_penalty = 0 - http_error3xx = false - http_error4xx = false - http_error5xx = false - disabled = false - http_header { - name = "test_name" - value = "test_value" - } - peer_certificate_verification = false - recursion_requested = false - request_string = "" - resource_type = "" - response_string = "" - ssl_client_certificate = "" - ssl_client_private_key = "" - test_object = "/junk" - test_object_password = "" - test_object_port = 1 - test_object_username = "" - timeout_penalty = 0 - } - failover_delay = 0 - failback_delay = 0 - wait_on_complete = false -} - diff --git a/pkg/providers/gtm/testdata/TestResGtmProperty/traffic_target/diff_order_and_weight.tf b/pkg/providers/gtm/testdata/TestResGtmProperty/update_name.tf similarity index 76% rename from pkg/providers/gtm/testdata/TestResGtmProperty/traffic_target/diff_order_and_weight.tf rename to pkg/providers/gtm/testdata/TestResGtmProperty/update_name.tf index b058b0873..4bae3852c 100644 --- a/pkg/providers/gtm/testdata/TestResGtmProperty/traffic_target/diff_order_and_weight.tf +++ b/pkg/providers/gtm/testdata/TestResGtmProperty/update_name.tf @@ -2,34 +2,22 @@ provider "akamai" { edgerc = "../../test/edgerc" } +locals { + gtmTestDomain = "gtm_terra_testdomain.akadns.net" +} + resource "akamai_gtm_property" "tfexample_prop_1" { - domain = "gtm_terra_testdomain.akadns.net" - name = "tfexample_prop_1" + domain = local.gtmTestDomain + name = "tfexample_prop_1-updated" type = "weighted-round-robin" score_aggregation_type = "median" handout_limit = 5 handout_mode = "normal" - traffic_target { - datacenter_id = 3132 - enabled = true - weight = 200 - servers = ["1.2.3.6"] - handout_cname = "test" - } - - traffic_target { - datacenter_id = 3133 - enabled = true - weight = 400 - servers = ["1.2.3.8", "1.2.3.7"] - handout_cname = "test" - } - traffic_target { datacenter_id = 3131 enabled = true weight = 200 - servers = ["1.2.3.5", "1.2.3.4"] + servers = ["1.2.3.9"] handout_cname = "test" } @@ -62,6 +50,18 @@ resource "akamai_gtm_property" "tfexample_prop_1" { test_object_username = "" timeout_penalty = 0 } + liveness_test { + name = "lt2" + test_interval = 30 + test_object_protocol = "HTTP" + test_timeout = 20 + test_object = "/junk" + } + static_rr_set { + type = "MX" + ttl = 300 + rdata = ["100 test_e"] + } failover_delay = 0 failback_delay = 0 wait_on_complete = false diff --git a/pkg/providers/imaging/imagewriter/convert-image.gen.go b/pkg/providers/imaging/imagewriter/convert-image.gen.go index 084f91e00..e16535656 100644 --- a/pkg/providers/imaging/imagewriter/convert-image.gen.go +++ b/pkg/providers/imaging/imagewriter/convert-image.gen.go @@ -23,6 +23,7 @@ func PolicyImageToEdgeGrid(d *schema.ResourceData, key string) imaging.PolicyInp result.Output = getOutputImage(d, getKey(key, 0, "output")) result.PostBreakpointTransformations = getPostBreakpointTransformations(d, getKey(key, 0, "post_breakpoint_transformations")) result.RolloutDuration = intReaderPtr(d, getKey(key, 0, "rollout_duration")) + result.ServeStaleDuration = intReaderPtr(d, getKey(key, 0, "serve_stale_duration")) result.Transformations = getTransformations(d, getKey(key, 0, "transformations")) result.Variables = getVariableList(d, getKey(key, 0, "variables")) } @@ -534,12 +535,14 @@ func getOutputImage(d *schema.ResourceData, key string) *imaging.OutputImage { _, exist := extract(d, key) if exist { result := imaging.OutputImage{ - AdaptiveQuality: intReaderPtr(d, getKey(key, 0, "adaptive_quality")), - AllowedFormats: interfaceSliceToImagingOutputImageAllowedFormatsSlice(d, getKey(key, 0, "allowed_formats")), - ForcedFormats: interfaceSliceToImagingOutputImageForcedFormatsSlice(d, getKey(key, 0, "forced_formats")), - PerceptualQuality: outputImagePerceptualQualityVariableInline(d, getKey(key, 0, "perceptual_quality")), - PerceptualQualityFloor: intReaderPtr(d, getKey(key, 0, "perceptual_quality_floor")), - Quality: integerVariableInline(d, getKey(key, 0, "quality")), + AdaptiveQuality: intReaderPtr(d, getKey(key, 0, "adaptive_quality")), + AllowPristineOnDownsize: boolReaderPtr(d, getKey(key, 0, "allow_pristine_on_downsize")), + AllowedFormats: interfaceSliceToImagingOutputImageAllowedFormatsSlice(d, getKey(key, 0, "allowed_formats")), + ForcedFormats: interfaceSliceToImagingOutputImageForcedFormatsSlice(d, getKey(key, 0, "forced_formats")), + PerceptualQuality: outputImagePerceptualQualityVariableInline(d, getKey(key, 0, "perceptual_quality")), + PerceptualQualityFloor: intReaderPtr(d, getKey(key, 0, "perceptual_quality_floor")), + PreferModernFormats: boolReaderPtr(d, getKey(key, 0, "prefer_modern_formats")), + Quality: integerVariableInline(d, getKey(key, 0, "quality")), } return &result } @@ -1811,6 +1814,17 @@ func stringReaderPtr(d *schema.ResourceData, key string) *string { return nil } +func boolReaderPtr(d *schema.ResourceData, key string) *bool { + value, exist := extract(d, key) + if exist { + if value.(string) == "true" { + return tools.BoolPtr(true) + } + return tools.BoolPtr(false) + } + return nil +} + func interfaceSliceToImagingImQueryAllowedTransformationsSlice(d *schema.ResourceData, key string) []imaging.ImQueryAllowedTransformations { list, exist := extract(d, key) if exist { diff --git a/pkg/providers/imaging/policy_image.gen.go b/pkg/providers/imaging/policy_image.gen.go index 00e4a2251..3b024a7fb 100644 --- a/pkg/providers/imaging/policy_image.gen.go +++ b/pkg/providers/imaging/policy_image.gen.go @@ -653,6 +653,12 @@ func PolicyOutputImage(depth int) map[string]*schema.Schema { Description: "The amount of time in seconds that the policy takes to rollout. During the rollout an increasing proportion of images/videos will begin to use the new policy instead of the cached images/videos from the previous version. This value has no effect on the staging network.", ValidateDiagFunc: stringAsIntBetween(3600, 604800), }, + "serve_stale_duration": { + Type: schema.TypeString, + Optional: true, + Description: "The amount of time in seconds that the policy will serve stale images. During the serve stale period realtime images will attempt to use the offline image from the previous policy version first if possible.", + ValidateDiagFunc: stringAsIntBetween(0, 2592000), + }, "transformations": { Type: schema.TypeList, Optional: true, @@ -2075,6 +2081,12 @@ func outputImage(_ int) map[string]*schema.Schema { Description: "Override the quality of image to serve when Image & Video Manager detects a slow connection. Specifying lower values lets users with slow connections browse your site with reduced load times without impacting the quality of images for users with faster connections.", ValidateDiagFunc: stringAsIntBetween(1, 100), }, + "allow_pristine_on_downsize": { + Type: schema.TypeString, + Optional: true, + Description: "Whether a pristine image wider than the requested breakpoint is allowed as a derivative image if it has the fewest bytes. This will not have an affect if transformations are present.", + ValidateDiagFunc: validateIsTypeBool(), + }, "allowed_formats": { Type: schema.TypeList, Optional: true, @@ -2104,6 +2116,12 @@ func outputImage(_ int) map[string]*schema.Schema { Optional: true, Description: "Mutually exclusive with quality. The perceptual quality to use when comparing resulting images, which overrides the `quality` setting. Perceptual quality tunes each image format's quality parameter dynamically based on the human-perceived quality of the output image. This can result in better byte savings (as compared to using regular quality) as many images can be encoded at a much lower quality without compromising perception of the image. In addition, certain images may need to be encoded at a slightly higher quality in order to maintain human-perceived quality. Values are tiered `high`, `mediumHigh`, `medium`, `mediumLow`, or `low`.", }, + "prefer_modern_formats": { + Type: schema.TypeString, + Optional: true, + Description: "Whether derivative image formats should be selected with a preference for modern formats (such as WebP and Avif) instead the format that results in the fewest bytes.", + ValidateDiagFunc: validateIsTypeBool(), + }, "quality": { Type: schema.TypeString, Optional: true, diff --git a/pkg/providers/imaging/resource_akamai_imaging_policy_image.go b/pkg/providers/imaging/resource_akamai_imaging_policy_image.go index fe054a1eb..fc43c20a1 100644 --- a/pkg/providers/imaging/resource_akamai_imaging_policy_image.go +++ b/pkg/providers/imaging/resource_akamai_imaging_policy_image.go @@ -187,7 +187,16 @@ func resourcePolicyImageRead(ctx context.Context, d *schema.ResourceData, m inte return diag.FromErr(err) } - policyInput.RolloutDuration, err = getRolloutDuration(d) + policyInput.RolloutDuration, err = getNotUpdateableField(d, extractRolloutDuration) + if err != nil { + return diag.FromErr(err) + } + + extractServeStaleDuration := func(input imaging.PolicyInputImage) *int { + return input.ServeStaleDuration + } + + policyInput.ServeStaleDuration, err = getNotUpdateableField(d, extractServeStaleDuration) if err != nil { return diag.FromErr(err) } @@ -207,6 +216,10 @@ func resourcePolicyImageRead(ctx context.Context, d *schema.ResourceData, m inte return nil } +var extractRolloutDuration = func(input imaging.PolicyInputImage) *int { + return input.RolloutDuration +} + func repackPolicyImageOutputToInput(policy *imaging.PolicyOutputImage) (*imaging.PolicyInputImage, error) { policyOutputJSON, err := json.Marshal(policy) if err != nil { @@ -425,7 +438,7 @@ func enforcePolicyImageVersionChange(_ context.Context, diff *schema.ResourceDif return nil } -func getRolloutDuration(d *schema.ResourceData) (*int, error) { +func getNotUpdateableField(d *schema.ResourceData, extractionFunc func(input imaging.PolicyInputImage) *int) (*int, error) { inputJSON, err := tf.GetStringValue("json", d) if err != nil && !errors.Is(err, tf.ErrNotFound) { return nil, err @@ -436,7 +449,7 @@ func getRolloutDuration(d *schema.ResourceData) (*int, error) { if err != nil { return nil, err } - return input.RolloutDuration, nil + return extractionFunc(input), nil } return nil, nil } diff --git a/pkg/providers/imaging/resource_akamai_imaging_policy_image_test.go b/pkg/providers/imaging/resource_akamai_imaging_policy_image_test.go index ed51d8970..357d0b7dc 100644 --- a/pkg/providers/imaging/resource_akamai_imaging_policy_image_test.go +++ b/pkg/providers/imaging/resource_akamai_imaging_policy_image_test.go @@ -571,6 +571,93 @@ func TestResourcePolicyImage(t *testing.T) { }) client.AssertExpectations(t) }) + t.Run("update serve stale duration and ensure no diff", func(t *testing.T) { + testDir := "testdata/TestResPolicyImage/regular_policy_update_serve_stale_duration" + + policyInputWithServeStale := imaging.PolicyInputImage{ + ServeStaleDuration: tools.IntPtr(3600), + Breakpoints: &imaging.Breakpoints{ + Widths: []int{320, 640, 1024, 2048, 5000}, + }, + Output: &imaging.OutputImage{ + AllowPristineOnDownsize: tools.BoolPtr(true), + PerceptualQuality: &imaging.OutputImagePerceptualQualityVariableInline{ + Value: imaging.OutputImagePerceptualQualityPtr(imaging.OutputImagePerceptualQualityMediumHigh), + }, + PreferModernFormats: tools.BoolPtr(false), + }, + Transformations: []imaging.TransformationType{ + &imaging.MaxColors{ + Colors: &imaging.IntegerVariableInline{ + Value: tools.IntPtr(2), + }, + Transformation: imaging.MaxColorsTransformationMaxColors, + }, + }, + } + + client := new(imaging.Mock) + expectUpsertPolicy(t, client, "test_policy", imaging.PolicyNetworkStaging, "test_contract", "test_policy_set", &policyInput) + expectReadPolicy(t, client, "test_policy", imaging.PolicyNetworkStaging, "test_contract", "test_policy_set", &policyOutput, 2) + + policyOutputAfterUpdate := imaging.PolicyOutputImage{ + Breakpoints: &imaging.Breakpoints{ + Widths: []int{320, 640, 1024, 2048, 5000}, + }, + Output: &imaging.OutputImage{ + AllowPristineOnDownsize: tools.BoolPtr(true), + PerceptualQuality: &imaging.OutputImagePerceptualQualityVariableInline{ + Value: imaging.OutputImagePerceptualQualityPtr(imaging.OutputImagePerceptualQualityMediumHigh), + }, + PreferModernFormats: tools.BoolPtr(false), + }, + Transformations: []imaging.TransformationType{ + &imaging.MaxColors{ + Colors: &imaging.IntegerVariableInline{ + Value: tools.IntPtr(2), + }, + Transformation: imaging.MaxColorsTransformationMaxColors, + }, + }, + Version: 1, + Video: tools.BoolPtr(false), + } + + expectUpsertPolicy(t, client, "test_policy", imaging.PolicyNetworkStaging, "test_contract", "test_policy_set", &policyInputWithServeStale) + expectReadPolicy(t, client, "test_policy", imaging.PolicyNetworkStaging, "test_contract", "test_policy_set", &policyOutputAfterUpdate, 3) + + expectDeletePolicy(t, client, "test_policy", imaging.PolicyNetworkStaging, "test_contract", "test_policy_set") + expectDeletePolicy(t, client, "test_policy", imaging.PolicyNetworkProduction, "test_contract", "test_policy_set") + + useClient(client, func() { + resource.UnitTest(t, resource.TestCase{ + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_create.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + version: "1", + policyID: "test_policy", + policySetID: "test_policy_set", + activateOnProduction: "false", + policyPath: fmt.Sprintf("%s/policy/policy_create.json", testDir), + }), + }, + { + Config: testutils.LoadFixtureString(t, fmt.Sprintf("%s/policy_update.tf", testDir)), + Check: checkPolicyAttributes(policyAttributes{ + version: "1", + policyID: "test_policy", + policySetID: "test_policy_set", + activateOnProduction: "false", + policyPath: fmt.Sprintf("%s/policy/policy_update.json", testDir), + }), + }, + }, + }) + }) + client.AssertExpectations(t) + }) t.Run("import policy with activate_on_production=true", func(t *testing.T) { testDir := "testdata/TestResPolicyImage/regular_policy" diff --git a/pkg/providers/imaging/resource_akamai_imaging_policy_video.go b/pkg/providers/imaging/resource_akamai_imaging_policy_video.go index 02f62784e..53177e8d7 100644 --- a/pkg/providers/imaging/resource_akamai_imaging_policy_video.go +++ b/pkg/providers/imaging/resource_akamai_imaging_policy_video.go @@ -180,7 +180,7 @@ func resourcePolicyVideoRead(ctx context.Context, d *schema.ResourceData, m inte return diag.FromErr(err) } - policyInput.RolloutDuration, err = getRolloutDuration(d) + policyInput.RolloutDuration, err = getNotUpdateableField(d, extractRolloutDuration) if err != nil { return diag.FromErr(err) } diff --git a/pkg/providers/imaging/testdata/TestResPolicyImage/regular_policy_update_serve_stale_duration/policy/policy_create.json b/pkg/providers/imaging/testdata/TestResPolicyImage/regular_policy_update_serve_stale_duration/policy/policy_create.json new file mode 100644 index 000000000..5810b57ca --- /dev/null +++ b/pkg/providers/imaging/testdata/TestResPolicyImage/regular_policy_update_serve_stale_duration/policy/policy_create.json @@ -0,0 +1,20 @@ +{ + "breakpoints": { + "widths": [ + 320, + 640, + 1024, + 2048, + 5000 + ] + }, + "output": { + "perceptualQuality": "mediumHigh" + }, + "transformations": [ + { + "colors": 2, + "transformation": "MaxColors" + } + ] +} \ No newline at end of file diff --git a/pkg/providers/imaging/testdata/TestResPolicyImage/regular_policy_update_serve_stale_duration/policy/policy_update.json b/pkg/providers/imaging/testdata/TestResPolicyImage/regular_policy_update_serve_stale_duration/policy/policy_update.json new file mode 100644 index 000000000..487401596 --- /dev/null +++ b/pkg/providers/imaging/testdata/TestResPolicyImage/regular_policy_update_serve_stale_duration/policy/policy_update.json @@ -0,0 +1,23 @@ +{ + "breakpoints": { + "widths": [ + 320, + 640, + 1024, + 2048, + 5000 + ] + }, + "output": { + "allowPristineOnDownsize": true, + "perceptualQuality": "mediumHigh", + "preferModernFormats": false + }, + "serveStaleDuration": 3600, + "transformations": [ + { + "colors": 2, + "transformation": "MaxColors" + } + ] +} \ No newline at end of file diff --git a/pkg/providers/imaging/testdata/TestResPolicyImage/regular_policy_update_serve_stale_duration/policy_create.tf b/pkg/providers/imaging/testdata/TestResPolicyImage/regular_policy_update_serve_stale_duration/policy_create.tf new file mode 100644 index 000000000..43f1f1f8e --- /dev/null +++ b/pkg/providers/imaging/testdata/TestResPolicyImage/regular_policy_update_serve_stale_duration/policy_create.tf @@ -0,0 +1,32 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_imaging_policy_image" "policy" { + policy_id = "test_policy" + contract_id = "test_contract" + policyset_id = "test_policy_set" + json = data.akamai_imaging_policy_image.policy.json +} + +data "akamai_imaging_policy_image" "policy" { + policy { + breakpoints { + widths = [ + 320, + 640, + 1024, + 2048, + 5000 + ] + } + output { + perceptual_quality = "mediumHigh" + } + transformations { + max_colors { + colors = 2 + } + } + } +} \ No newline at end of file diff --git a/pkg/providers/imaging/testdata/TestResPolicyImage/regular_policy_update_serve_stale_duration/policy_update.tf b/pkg/providers/imaging/testdata/TestResPolicyImage/regular_policy_update_serve_stale_duration/policy_update.tf new file mode 100644 index 000000000..2b4f0ca53 --- /dev/null +++ b/pkg/providers/imaging/testdata/TestResPolicyImage/regular_policy_update_serve_stale_duration/policy_update.tf @@ -0,0 +1,35 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_imaging_policy_image" "policy" { + policy_id = "test_policy" + contract_id = "test_contract" + policyset_id = "test_policy_set" + json = data.akamai_imaging_policy_image.policy.json +} + +data "akamai_imaging_policy_image" "policy" { + policy { + serve_stale_duration = 3600 + breakpoints { + widths = [ + 320, + 640, + 1024, + 2048, + 5000 + ] + } + output { + allow_pristine_on_downsize = true + perceptual_quality = "mediumHigh" + prefer_modern_formats = false + } + transformations { + max_colors { + colors = 2 + } + } + } +} \ No newline at end of file diff --git a/pkg/providers/networklists/resource_akamai_networklist_activations.go b/pkg/providers/networklists/resource_akamai_networklist_activations.go index dfd561151..53654d8c5 100644 --- a/pkg/providers/networklists/resource_akamai_networklist_activations.go +++ b/pkg/providers/networklists/resource_akamai_networklist_activations.go @@ -3,6 +3,7 @@ package networklists import ( "context" "errors" + "fmt" "strconv" "sync" "time" @@ -10,6 +11,7 @@ import ( "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/networklists" "github.com/akamai/terraform-provider-akamai/v5/pkg/common/tf" "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" + "github.com/hashicorp/go-hclog" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -76,6 +78,9 @@ const ( var ( // ActivationPollInterval is the interval for polling an activation status on creation ActivationPollInterval = ActivationPollMinimum + + // CreateActivationRetry poll wait time code waits between retries for activation creation + CreateActivationRetry = 10 * time.Second ) func resourceActivationsCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { @@ -101,15 +106,15 @@ func resourceActivationsCreate(ctx context.Context, d *schema.ResourceData, m in return diag.Errorf("Activation Read failed") } - createResponse, err := createActivation(ctx, client, networklists.CreateActivationsRequest{ + createResponse, diagErr := createActivation(ctx, client, networklists.CreateActivationsRequest{ UniqueID: networkListID, Network: network, Comments: comments, - Action: "ACTIVATE", + Action: string(networklists.ActivationTypeActivate), NotificationRecipients: tf.SetToStringSlice(notificationEmails), }) - if err != nil { - return diag.FromErr(err) + if diagErr != nil { + return diagErr } d.SetId(strconv.Itoa(createResponse.ActivationID)) if err := d.Set("status", string(createResponse.ActivationStatus)); err != nil { @@ -121,32 +126,53 @@ func resourceActivationsCreate(ctx context.Context, d *schema.ResourceData, m in return diag.FromErr(err) } - for lookupResponse.ActivationStatus != "ACTIVATED" { - select { - case <-time.After(tf.MaxDuration(ActivationPollInterval, ActivationPollMinimum)): - act, err := client.GetActivation(ctx, networklists.GetActivationRequest{ActivationID: createResponse.ActivationID}) - - if err != nil { - return diag.FromErr(err) - } - lookupResponse = act - - case <-ctx.Done(): - return diag.Errorf("activation context terminated: %s", ctx.Err()) - } + if err = pollActivation(ctx, client, lookupResponse.ActivationStatus, lookupResponse.ActivationID); err != nil { + return diag.FromErr(err) } return resourceActivationsRead(ctx, d, m) } -func createActivation(ctx context.Context, client networklists.NTWRKLISTS, params networklists.CreateActivationsRequest) (*networklists.CreateActivationsResponse, error) { +func createActivation(ctx context.Context, client networklists.NTWRKLISTS, params networklists.CreateActivationsRequest) (*networklists.CreateActivationsResponse, diag.Diagnostics) { createNetworkListActivationMutex.Lock() defer func() { createNetworkListActivationMutex.Unlock() }() - postResp, err := client.CreateActivations(ctx, params) - return postResp, err + log := hclog.FromContext(ctx) + + errMsg := "create failed" + switch params.Action { + case string(networklists.ActivationTypeActivate): + errMsg = "create activation failed" + case string(networklists.ActivationTypeDeactivate): + errMsg = "create deactivation failed" + } + + createActivationRetry := CreateActivationRetry + + for { + log.Debug("creating activation") + create, err := client.CreateActivations(ctx, params) + + if err == nil { + return create, nil + } + log.Debug("%s: retrying: %w", errMsg, err) + + if !isCreateActivationErrorRetryable(err) { + return nil, diag.Errorf("%s: %s", errMsg, err) + } + + select { + case <-time.After(createActivationRetry): + createActivationRetry = capDuration(createActivationRetry*2, 5*time.Minute) + continue + + case <-ctx.Done(): + return nil, diag.Errorf("activation context terminated: %s", ctx.Err()) + } + } } func resourceActivationsRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { @@ -207,40 +233,29 @@ func resourceActivationsUpdate(ctx context.Context, d *schema.ResourceData, m in return diag.FromErr(err) } - response, err := client.CreateActivations(ctx, networklists.CreateActivationsRequest{ + createResponse, diagErr := createActivation(ctx, client, networklists.CreateActivationsRequest{ UniqueID: networkListID, Network: network, Comments: comments, - Action: "ACTIVATE", + Action: string(networklists.ActivationTypeActivate), NotificationRecipients: tf.SetToStringSlice(notificationEmails), }) - if err != nil { - return diag.FromErr(err) + if diagErr != nil { + return diagErr } - d.SetId(strconv.Itoa(response.ActivationID)) - if err := d.Set("status", string(response.ActivationStatus)); err != nil { + d.SetId(strconv.Itoa(createResponse.ActivationID)) + if err := d.Set("status", string(createResponse.ActivationStatus)); err != nil { return diag.FromErr(err) } - lookupRequest := networklists.GetActivationRequest{ActivationID: response.ActivationID} + lookupRequest := networklists.GetActivationRequest{ActivationID: createResponse.ActivationID} lookupResponse, err := lookupActivation(ctx, client, lookupRequest) if err != nil { return diag.FromErr(err) } - for lookupResponse.ActivationStatus != "ACTIVATED" { - select { - case <-time.After(tf.MaxDuration(ActivationPollInterval, ActivationPollMinimum)): - act, err := client.GetActivation(ctx, lookupRequest) - - if err != nil { - return diag.FromErr(err) - } - lookupResponse = act - - case <-ctx.Done(): - return diag.Errorf("activation context terminated: %s", ctx.Err()) - } + if err = pollActivation(ctx, client, lookupResponse.ActivationStatus, lookupResponse.ActivationID); err != nil { + return diag.FromErr(err) } return resourceActivationsRead(ctx, d, m) } @@ -271,3 +286,56 @@ func suppressNoteFieldForNetworkListActivation(_, oldValue, newValue string, d * } return true } + +func pollActivation(ctx context.Context, client networklists.NTWRKLISTS, activationStatus string, activationID int) error { + retriesMax := 5 + retries5xx := 0 + + for activationStatus != string(networklists.StatusActive) { + select { + case <-time.After(tf.MaxDuration(ActivationPollInterval, ActivationPollMinimum)): + act, err := client.GetActivation(ctx, networklists.GetActivationRequest{ActivationID: activationID}) + + if err != nil { + var target = &networklists.Error{} + if !errors.As(err, &target) { + return fmt.Errorf("error has unexpected type: %T", err) + } + if isCreateActivationErrorRetryable(target) { + retries5xx = retries5xx + 1 + if retries5xx > retriesMax { + return fmt.Errorf("reached max number of 5xx retries: %d", retries5xx) + } + continue + } + return err + } + retries5xx = 0 + activationStatus = act.ActivationStatus + + case <-ctx.Done(): + return fmt.Errorf("activation context terminated: %s", ctx.Err()) + } + } + return nil +} + +func isCreateActivationErrorRetryable(err error) bool { + var responseErr = &networklists.Error{} + if !errors.As(err, &responseErr) { + return false + } + if responseErr.StatusCode < 500 && + responseErr.StatusCode != 422 && + responseErr.StatusCode != 409 { + return false + } + return true +} + +func capDuration(t time.Duration, tMax time.Duration) time.Duration { + if t > tMax { + return tMax + } + return t +} diff --git a/pkg/providers/networklists/resource_akamai_networklist_activations_test.go b/pkg/providers/networklists/resource_akamai_networklist_activations_test.go index dab75ac94..4c53ece3a 100644 --- a/pkg/providers/networklists/resource_akamai_networklist_activations_test.go +++ b/pkg/providers/networklists/resource_akamai_networklist_activations_test.go @@ -147,4 +147,78 @@ func TestAccAkamaiActivations_res_basic(t *testing.T) { client.AssertExpectations(t) }) + t.Run("Retry create activation on 500x error", func(t *testing.T) { + client := &networklists.Mock{} + + cu := networklists.RemoveActivationsResponse{} + err := json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResActivations/ActivationsDelete.json"), &cu) + require.NoError(t, err) + + ga := networklists.GetActivationsResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResActivations/Activations.json"), &ga) + require.NoError(t, err) + + cr := networklists.CreateActivationsResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResActivations/Activations.json"), &cr) + require.NoError(t, err) + + ar := networklists.GetActivationResponse{} + err = json.Unmarshal(testutils.LoadFixtureBytes(t, "testdata/TestResActivations/Activations.json"), &ar) + require.NoError(t, err) + + client.On("CreateActivations", + mock.Anything, // ctx is irrelevant for this test + networklists.CreateActivationsRequest{UniqueID: "86093_AGEOLIST", Action: "ACTIVATE", Network: "STAGING", Comments: "Test Notes", NotificationRecipients: []string{"user@example.com"}}, + ).Return(nil, &networklists.Error{StatusCode: 500}).Once() + + client.On("CreateActivations", + mock.Anything, // ctx is irrelevant for this test + networklists.CreateActivationsRequest{UniqueID: "86093_AGEOLIST", Action: "ACTIVATE", Network: "STAGING", Comments: "Test Notes", NotificationRecipients: []string{"user@example.com"}}, + ).Return(&cr, nil) + + client.On("GetActivation", + mock.Anything, + networklists.GetActivationRequest{ActivationID: 547694}, + ).Return(&ar, nil) + + client.On("GetNetworkList", + mock.Anything, + networklists.GetNetworkListRequest{UniqueID: "86093_AGEOLIST"}, + ).Return(&networklists.GetNetworkListResponse{SyncPoint: 0}, nil) + + client.On("CreateActivations", + mock.Anything, + networklists.CreateActivationsRequest{UniqueID: "86093_AGEOLIST", Action: "ACTIVATE", Network: "PRODUCTION", Comments: "Test Notes Updated", NotificationRecipients: []string{"user@example.com"}}, + ).Return(&cr, nil) + + useClient(client, func() { + resource.Test(t, resource.TestCase{ + IsUnitTest: true, + ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "testdata/TestResActivations/match_by_id.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("akamai_networklist_activations.test", "network_list_id", "86093_AGEOLIST"), + resource.TestCheckResourceAttr("akamai_networklist_activations.test", "network", "STAGING"), + resource.TestCheckResourceAttr("akamai_networklist_activations.test", "notes", "Test Notes"), + resource.TestCheckResourceAttr("akamai_networklist_activations.test", "notification_emails.0", "user@example.com"), + ), + }, + { + Config: testutils.LoadFixtureString(t, "testdata/TestResActivations/update_by_id.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("akamai_networklist_activations.test", "network_list_id", "86093_AGEOLIST"), + resource.TestCheckResourceAttr("akamai_networklist_activations.test", "network", "PRODUCTION"), + resource.TestCheckResourceAttr("akamai_networklist_activations.test", "notes", "Test Notes Updated"), + resource.TestCheckResourceAttr("akamai_networklist_activations.test", "notification_emails.0", "user@example.com"), + ), + }, + }, + }) + }) + + client.AssertExpectations(t) + }) + } diff --git a/pkg/providers/property/data_akamai_contracts_test.go b/pkg/providers/property/data_akamai_contracts_test.go index 740936928..8e9643869 100644 --- a/pkg/providers/property/data_akamai_contracts_test.go +++ b/pkg/providers/property/data_akamai_contracts_test.go @@ -11,7 +11,6 @@ import ( ) func TestDataContracts(t *testing.T) { - t.Skip() t.Run("list contracts", func(t *testing.T) { client := &papi.Mock{} ctrs := papi.ContractsItems{Items: []*papi.Contract{ diff --git a/pkg/providers/property/data_akamai_cp_code_test.go b/pkg/providers/property/data_akamai_cp_code_test.go index ef3cc64b0..fac59e7a2 100644 --- a/pkg/providers/property/data_akamai_cp_code_test.go +++ b/pkg/providers/property/data_akamai_cp_code_test.go @@ -12,7 +12,6 @@ import ( ) func TestDSCPCode(t *testing.T) { - t.Skip() t.Run("match by name", func(t *testing.T) { client := &papi.Mock{} diff --git a/pkg/providers/property/data_akamai_properties_search_test.go b/pkg/providers/property/data_akamai_properties_search_test.go index c1aea4b20..af75414c6 100644 --- a/pkg/providers/property/data_akamai_properties_search_test.go +++ b/pkg/providers/property/data_akamai_properties_search_test.go @@ -12,7 +12,6 @@ import ( ) func TestDSPropertiesSearch(t *testing.T) { - t.Skip() t.Run("match by hostname", func(t *testing.T) { client := &papi.Mock{} diff --git a/pkg/providers/property/data_akamai_properties_test.go b/pkg/providers/property/data_akamai_properties_test.go index f5b78855d..646e08369 100644 --- a/pkg/providers/property/data_akamai_properties_test.go +++ b/pkg/providers/property/data_akamai_properties_test.go @@ -12,7 +12,6 @@ import ( ) func TestDataProperties(t *testing.T) { - t.Skip() t.Run("list properties", func(t *testing.T) { client := &papi.Mock{} props := papi.PropertiesItems{Items: buildPapiProperties()} diff --git a/pkg/providers/property/data_akamai_property_activation_test.go b/pkg/providers/property/data_akamai_property_activation_test.go index acc8b0662..c8a055712 100644 --- a/pkg/providers/property/data_akamai_property_activation_test.go +++ b/pkg/providers/property/data_akamai_property_activation_test.go @@ -12,7 +12,6 @@ import ( ) func TestDataSourcePAPIPropertyActivation(t *testing.T) { - t.Skip() tests := map[string]struct { init func(*papi.Mock) steps []resource.TestStep diff --git a/pkg/providers/property/data_akamai_property_hostnames_test.go b/pkg/providers/property/data_akamai_property_hostnames_test.go index 4c86c3f82..54082058e 100644 --- a/pkg/providers/property/data_akamai_property_hostnames_test.go +++ b/pkg/providers/property/data_akamai_property_hostnames_test.go @@ -13,7 +13,6 @@ import ( ) func TestDataPropertyHostnames(t *testing.T) { - t.Skip() t.Run("list hostnames", func(t *testing.T) { client := &papi.Mock{} hostnames := papi.HostnameResponseItems{Items: buildPropertyHostnames()} diff --git a/pkg/providers/property/data_akamai_property_include_activation_test.go b/pkg/providers/property/data_akamai_property_include_activation_test.go index bc43642b1..a9e8271db 100644 --- a/pkg/providers/property/data_akamai_property_include_activation_test.go +++ b/pkg/providers/property/data_akamai_property_include_activation_test.go @@ -14,7 +14,6 @@ import ( ) func TestDataPropertyIncludeActivation(t *testing.T) { - t.Skip() tests := map[string]struct { attrs includeActivationTestAttributes init func(*testing.T, *papi.Mock, includeActivationTestAttributes) @@ -179,7 +178,6 @@ func TestDataPropertyIncludeActivation(t *testing.T) { } func TestFilterActivations(t *testing.T) { - t.Skip() t.Run("test filterActivation - STAGING network", func(t *testing.T) { stagingIncludes := filterIncludeActivationsByNetwork(includeActivationsForTests, stagingNetwork) assert.Equal(t, 2, len(stagingIncludes)) @@ -195,7 +193,6 @@ func TestFilterActivations(t *testing.T) { } func TestFindLatestActivation(t *testing.T) { - t.Skip() activationWithLatestDeactivate := append(includeActivationsForTests, includeActivationTypeDeactivate) noActiveStatusActivations := includeActivationsForTests[:2] manyActivations := append(includeActivationsForTests, createIncludeActivation(includeActivationData{ diff --git a/pkg/providers/property/data_akamai_property_include_parents_test.go b/pkg/providers/property/data_akamai_property_include_parents_test.go index 0119ff718..a0967c016 100644 --- a/pkg/providers/property/data_akamai_property_include_parents_test.go +++ b/pkg/providers/property/data_akamai_property_include_parents_test.go @@ -13,7 +13,6 @@ import ( ) func TestDataPropertyIncludeParents(t *testing.T) { - t.Skip() tests := map[string]struct { givenTF string init func(*papi.Mock) diff --git a/pkg/providers/property/data_akamai_property_include_rules_test.go b/pkg/providers/property/data_akamai_property_include_rules_test.go index e3dd6d056..5ce28b885 100644 --- a/pkg/providers/property/data_akamai_property_include_rules_test.go +++ b/pkg/providers/property/data_akamai_property_include_rules_test.go @@ -91,7 +91,6 @@ var ( ) func TestDataPropertyIncludeRules(t *testing.T) { - t.Skip() tests := map[string]struct { init func(*testing.T, *papi.Mock, testDataPropertyIncludeRules) mockData testDataPropertyIncludeRules diff --git a/pkg/providers/property/data_akamai_property_include_test.go b/pkg/providers/property/data_akamai_property_include_test.go index 5b321dcbb..49ffe3266 100644 --- a/pkg/providers/property/data_akamai_property_include_test.go +++ b/pkg/providers/property/data_akamai_property_include_test.go @@ -14,7 +14,6 @@ import ( ) func TestDataPropertyInclude(t *testing.T) { - t.Skip() tests := map[string]struct { givenTF string init func(*papi.Mock) diff --git a/pkg/providers/property/data_akamai_property_includes_test.go b/pkg/providers/property/data_akamai_property_includes_test.go index 408e41891..9c39ef927 100644 --- a/pkg/providers/property/data_akamai_property_includes_test.go +++ b/pkg/providers/property/data_akamai_property_includes_test.go @@ -16,7 +16,6 @@ import ( ) func TestDataPropertyIncludes(t *testing.T) { - t.Skip() tests := map[string]struct { attrs attributes init func(*testing.T, *papi.Mock, attributes) diff --git a/pkg/providers/property/data_akamai_property_products_test.go b/pkg/providers/property/data_akamai_property_products_test.go index 786f69150..3e996ce97 100644 --- a/pkg/providers/property/data_akamai_property_products_test.go +++ b/pkg/providers/property/data_akamai_property_products_test.go @@ -12,7 +12,6 @@ import ( ) func TestVerifyProductsDataSourceSchema(t *testing.T) { - t.Skip() t.Run("akamai_property_products - test data source required contract", func(t *testing.T) { resource.UnitTest(t, resource.TestCase{ ProtoV5ProviderFactories: testAccProviders, @@ -26,7 +25,6 @@ func TestVerifyProductsDataSourceSchema(t *testing.T) { } func TestOutputProductsDataSource(t *testing.T) { - t.Skip() t.Run("akamai_property_products - input OK - output OK", func(t *testing.T) { client := &papi.Mock{} diff --git a/pkg/providers/property/data_akamai_property_rule_formats_test.go b/pkg/providers/property/data_akamai_property_rule_formats_test.go index 379a0a5b9..165fb09f0 100644 --- a/pkg/providers/property/data_akamai_property_rule_formats_test.go +++ b/pkg/providers/property/data_akamai_property_rule_formats_test.go @@ -11,7 +11,6 @@ import ( ) func Test_readPropertyRuleFormats(t *testing.T) { - t.Skip() t.Run("get datasource property rule formats", func(t *testing.T) { client := &papi.Mock{} ruleFormats := papi.RuleFormatItems{ diff --git a/pkg/providers/property/data_akamai_property_rules_builder_test.go b/pkg/providers/property/data_akamai_property_rules_builder_test.go index 1a4835b26..416f72a0a 100644 --- a/pkg/providers/property/data_akamai_property_rules_builder_test.go +++ b/pkg/providers/property/data_akamai_property_rules_builder_test.go @@ -12,7 +12,6 @@ import ( ) func TestDataPropertyRulesBuilder(t *testing.T) { - t.Skip() t.Run("valid rule with 3 children - v2023-01-05", func(t *testing.T) { useClient(nil, nil, func() { resource.UnitTest(t, resource.TestCase{ @@ -169,6 +168,45 @@ func TestDataPropertyRulesBuilder(t *testing.T) { }) }) }) + t.Run("valid rule with 3 children - v2024-01-09", func(t *testing.T) { + useClient(nil, nil, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{{ + Config: testutils.LoadFixtureString(t, "testdata/TestDSPropertyRulesBuilder/rules_v2024_01_09.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("data.akamai_property_rules_builder.default", + "rule_format", + "v2024-01-09"), + testCheckResourceAttrJSON("data.akamai_property_rules_builder.default", + "json", + testutils.LoadFixtureString(t, "testdata/TestDSPropertyRulesBuilder/default_v2024_01_09.json")), + + resource.TestCheckResourceAttr("data.akamai_property_rules_builder.content_compression", + "rule_format", + "v2024-01-09"), + testCheckResourceAttrJSON("data.akamai_property_rules_builder.content_compression", + "json", + testutils.LoadFixtureString(t, "testdata/TestDSPropertyRulesBuilder/content_compression_v2024_01_09.json")), + + resource.TestCheckResourceAttr("data.akamai_property_rules_builder.static_content", + "rule_format", + "v2024-01-09"), + testCheckResourceAttrJSON("data.akamai_property_rules_builder.static_content", + "json", + testutils.LoadFixtureString(t, "testdata/TestDSPropertyRulesBuilder/static_content_v2024_01_09.json")), + + resource.TestCheckResourceAttr("data.akamai_property_rules_builder.dynamic_content", + "rule_format", + "v2024-01-09"), + testCheckResourceAttrJSON("data.akamai_property_rules_builder.dynamic_content", + "json", + testutils.LoadFixtureString(t, "testdata/TestDSPropertyRulesBuilder/dynamic_content_v2024_01_09.json")), + ), + }}, + }) + }) + }) t.Run("invalid rule with 3 children with different versions", func(t *testing.T) { useClient(nil, nil, func() { resource.UnitTest(t, resource.TestCase{ diff --git a/pkg/providers/property/data_akamai_property_rules_template_test.go b/pkg/providers/property/data_akamai_property_rules_template_test.go index 87ae05520..fd7ababbf 100644 --- a/pkg/providers/property/data_akamai_property_rules_template_test.go +++ b/pkg/providers/property/data_akamai_property_rules_template_test.go @@ -15,7 +15,6 @@ import ( ) func TestDataAkamaiPropertyRulesRead(t *testing.T) { - t.Skip() t.Run("valid nested template with vars map", func(t *testing.T) { client := papi.Mock{} useClient(&client, nil, func() { @@ -272,7 +271,6 @@ func TestDataAkamaiPropertyRulesRead(t *testing.T) { } func TestFormatValue(t *testing.T) { - t.Skip() tests := map[string]struct { given interface{} expected interface{} @@ -313,7 +311,6 @@ func TestFormatValue(t *testing.T) { } func TestGetValuesFromMap(t *testing.T) { - t.Skip() variablesPath := "testdata/TestDSRulesTemplate/variables" tests := map[string]struct { definitionsFile string @@ -380,7 +377,6 @@ func TestGetValuesFromMap(t *testing.T) { } func TestConvertToTypedMap(t *testing.T) { - t.Skip() tests := map[string]struct { givenVars []interface{} expected map[string]interface{} @@ -482,7 +478,6 @@ func TestConvertToTypedMap(t *testing.T) { } func TestFlattenTemplate(t *testing.T) { - t.Skip() tests := map[string]struct { givenList []interface{} expectedData string @@ -563,7 +558,6 @@ func TestFlattenTemplate(t *testing.T) { } func TestConvertToTemplate(t *testing.T) { - t.Skip() templates := "testdata/TestDSRulesTemplate/rules/property-snippets" templatesOut := "testdata/TestDSRulesTemplate/output" tests := map[string]struct { @@ -606,7 +600,6 @@ func TestConvertToTemplate(t *testing.T) { } func TestStringToTemplate(t *testing.T) { - t.Skip() templates := "testdata/TestDSRulesTemplate/rules/property-snippets" templatesOut := "testdata/TestDSRulesTemplate/output" tests := map[string]struct { @@ -647,7 +640,6 @@ func TestStringToTemplate(t *testing.T) { } func TestVariablesNesting(t *testing.T) { - t.Skip() tests := map[string]struct { configPath string expectedPath string @@ -698,7 +690,6 @@ func TestVariablesNesting(t *testing.T) { } func TestVariablesAndIncludesNestingCyclicDependency(t *testing.T) { - t.Skip() tests := map[string]struct { configPath string withError string @@ -731,7 +722,6 @@ func TestVariablesAndIncludesNestingCyclicDependency(t *testing.T) { } func TestMultipleTemplates(t *testing.T) { - t.Skip() t.Run("Multiple templates in one directory", func(t *testing.T) { client := papi.Mock{} useClient(&client, nil, func() { diff --git a/pkg/providers/property/data_akamai_property_rules_test.go b/pkg/providers/property/data_akamai_property_rules_test.go index 8e01bd356..02ec6cd7d 100644 --- a/pkg/providers/property/data_akamai_property_rules_test.go +++ b/pkg/providers/property/data_akamai_property_rules_test.go @@ -13,7 +13,6 @@ import ( ) func TestDSPropertyRulesRead(t *testing.T) { - t.Skip() t.Run("get datasource property rules", func(t *testing.T) { client := &papi.Mock{} mockImpl := func(m *papi.Mock) { @@ -311,7 +310,6 @@ func TestDSPropertyRulesRead(t *testing.T) { } func TestDSPropertyRulesRead_Fail(t *testing.T) { - t.Skip() resource.UnitTest(t, resource.TestCase{ ProtoV5ProviderFactories: testAccProviders, Steps: []resource.TestStep{{ diff --git a/pkg/providers/property/data_akamai_property_test.go b/pkg/providers/property/data_akamai_property_test.go index 60dc4baef..e447cc18d 100644 --- a/pkg/providers/property/data_akamai_property_test.go +++ b/pkg/providers/property/data_akamai_property_test.go @@ -15,7 +15,6 @@ import ( ) func TestDataProperty(t *testing.T) { - t.Skip() tests := map[string]struct { givenTF string init func(*papi.Mock) diff --git a/pkg/providers/property/data_property_akamai_contract.go b/pkg/providers/property/data_property_akamai_contract.go index cec7dd063..628230a60 100644 --- a/pkg/providers/property/data_property_akamai_contract.go +++ b/pkg/providers/property/data_property_akamai_contract.go @@ -46,7 +46,7 @@ func dataPropertyContractRead(ctx context.Context, d *schema.ResourceData, m int // check if one of group_id/group_name exists. group, err := tf.ResolveKeyStringState(d, "group_id", "group_name") if err != nil { - // If no group found, just return the first contract + // If no group found, just return the first contract if only one exists if !errors.Is(err, tf.ErrNotFound) { return diag.FromErr(err) } @@ -57,6 +57,9 @@ func dataPropertyContractRead(ctx context.Context, d *schema.ResourceData, m int if len(contracts.Contracts.Items) == 0 { return diag.Errorf("%v", ErrNoContractsFound) } + if len(contracts.Contracts.Items) > 1 { + return ErrMultipleContractsFound + } d.SetId(contracts.Contracts.Items[0].ContractID) return nil } @@ -84,6 +87,9 @@ func dataPropertyContractRead(ctx context.Context, d *schema.ResourceData, m int if len(foundGroups[0].ContractIDs) == 0 { return diag.Errorf("%v: %v", ErrLookingUpContract, group) } + if len(foundGroups[0].ContractIDs) > 1 { + return ErrMultipleContractsInGroup + } if err = d.Set("group_id", tools.AddPrefix(foundGroups[0].GroupID, "grp_")); err != nil { return diag.Errorf("%v: %s", tf.ErrValueSet, err.Error()) diff --git a/pkg/providers/property/data_property_akamai_contract_test.go b/pkg/providers/property/data_property_akamai_contract_test.go index 263edb8c4..d2e60c00a 100644 --- a/pkg/providers/property/data_property_akamai_contract_test.go +++ b/pkg/providers/property/data_property_akamai_contract_test.go @@ -10,7 +10,6 @@ import ( ) func Test_DSReadContract(t *testing.T) { - t.Skip() tests := map[string]struct { init func(*testing.T, *papi.Mock, testDataForPAPIGroups) mockData testDataForPAPIGroups @@ -157,6 +156,33 @@ func Test_DSReadContract(t *testing.T) { configPath: "testdata/TestDSContractRequired/ds_contract_with_group_id.tf", error: nil, }, + "group with multiple contracts - expect error": { + init: func(t *testing.T, m *papi.Mock, testData testDataForPAPIGroups) { + expectGetGroups(m, testData, 5) + }, + mockData: testDataForPAPIGroups{ + accountID: "act_1-1TJZFB", + accountName: "example.com", + groups: papi.GroupItems{ + Items: []*papi.Group{ + { + GroupID: "grp_12345", + GroupName: "Example.com-1-1TJZH5", + ParentGroupID: "grp_parent", + ContractIDs: []string{"ctr_1234", "ctr_1235"}, + }, + { + GroupID: "grp_12346", + GroupName: "Second-Example.com-1-1TJZH5", + ParentGroupID: "grp_parent", + ContractIDs: []string{"ctr_1234"}, + }, + }, + }, + }, + configPath: "testdata/TestDSContractRequired/ds_contract_with_group_id.tf", + error: regexp.MustCompile("multiple contracts found for given group"), + }, } for name, test := range tests { diff --git a/pkg/providers/property/data_property_akamai_group.go b/pkg/providers/property/data_property_akamai_group.go index cd1f2b206..5ca4ec24d 100644 --- a/pkg/providers/property/data_property_akamai_group.go +++ b/pkg/providers/property/data_property_akamai_group.go @@ -71,9 +71,6 @@ func dataPropertyGroupRead(ctx context.Context, d *schema.ResourceData, m interf return diag.Errorf("%v: %s", tf.ErrValueSet, err.Error()) } - if len(group.ContractIDs) != 0 { - contractID = group.ContractIDs[0] - } if err = d.Set("contract_id", contractID); err != nil { return diag.Errorf("%v: %s", tf.ErrValueSet, err.Error()) } diff --git a/pkg/providers/property/data_property_akamai_group_test.go b/pkg/providers/property/data_property_akamai_group_test.go index 13a371d70..eb9d33373 100644 --- a/pkg/providers/property/data_property_akamai_group_test.go +++ b/pkg/providers/property/data_property_akamai_group_test.go @@ -11,7 +11,6 @@ import ( ) func Test_DSReadGroup(t *testing.T) { - t.Skip() tests := map[string]struct { init func(*testing.T, *papi.Mock, testDataForPAPIGroups) mockData testDataForPAPIGroups diff --git a/pkg/providers/property/data_property_akamai_groups_test.go b/pkg/providers/property/data_property_akamai_groups_test.go index 358f52e83..1a39ad8bd 100644 --- a/pkg/providers/property/data_property_akamai_groups_test.go +++ b/pkg/providers/property/data_property_akamai_groups_test.go @@ -12,7 +12,6 @@ import ( ) func TestDataSourceMultipleGroups_basic(t *testing.T) { - t.Skip() t.Run("test output", func(t *testing.T) { client := &papi.Mock{} contractIDs := []string{"ctr_1234"} @@ -52,7 +51,6 @@ func TestDataSourceMultipleGroups_basic(t *testing.T) { } func TestGroup_ContractNotFoundInState(t *testing.T) { - t.Skip() t.Run("contractId not found in state", func(t *testing.T) { client := &papi.Mock{} contractIDs := []string{"ctr_contractID"} diff --git a/pkg/providers/property/diff_suppress_funcs.go b/pkg/providers/property/diff_suppress_funcs.go index e203ca336..1eab81dc6 100644 --- a/pkg/providers/property/diff_suppress_funcs.go +++ b/pkg/providers/property/diff_suppress_funcs.go @@ -11,8 +11,8 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -func diffSuppressRules(_, oldRules, newRules string, _ *schema.ResourceData) bool { - rulesEqual, err := rulesJSONEqual(oldRules, newRules) +func diffSuppressPropertyRules(_, oldRulesJSON, newRulesJSON string, _ *schema.ResourceData) bool { + rulesEqual, err := rulesJSONEqual(oldRulesJSON, newRulesJSON) if err != nil { logger.Get("PAPI", "diffSuppressRules").Error(err.Error()) } @@ -21,34 +21,26 @@ func diffSuppressRules(_, oldRules, newRules string, _ *schema.ResourceData) boo } // rulesJSONEqual handles comparison between two papi.RulesUpdate JSON representations. -func rulesJSONEqual(old, new string) (bool, error) { - if old == "" || new == "" { - return old == new, nil +func rulesJSONEqual(oldRulesJSON, newRulesJSON string) (bool, error) { + if oldRulesJSON == "" || newRulesJSON == "" { + return oldRulesJSON == newRulesJSON, nil } - if old == new { + if oldRulesJSON == newRulesJSON { return true, nil } var oldRules papi.RulesUpdate - if err := json.Unmarshal([]byte(old), &oldRules); err != nil { - return false, fmt.Errorf("'old' = %s, unmarshal: %w", old, err) + if err := json.Unmarshal([]byte(oldRulesJSON), &oldRules); err != nil { + return false, fmt.Errorf("'old' = %s, unmarshal: %w", oldRulesJSON, err) } var newRules papi.RulesUpdate - if err := json.Unmarshal([]byte(new), &newRules); err != nil { - return false, fmt.Errorf("'new' = %s, unmarshal: %w", new, err) + if err := json.Unmarshal([]byte(newRulesJSON), &newRules); err != nil { + return false, fmt.Errorf("'new' = %s, unmarshal: %w", newRulesJSON, err) } - return ruleTreesEqual(&oldRules, &newRules), nil -} - -func ruleTreesEqual(old, new *papi.RulesUpdate) bool { - if old.Comments != new.Comments { - return false - } - - return rulesEqual(&old.Rules, &new.Rules) + return oldRules.Comments == newRules.Comments && rulesEqual(&oldRules.Rules, &newRules.Rules), nil } // rulesEqual handles comparison between two papi.Rules objects ignoring the order in diff --git a/pkg/providers/property/diff_suppress_funcs_test.go b/pkg/providers/property/diff_suppress_funcs_test.go index 879dbb2de..32996c198 100644 --- a/pkg/providers/property/diff_suppress_funcs_test.go +++ b/pkg/providers/property/diff_suppress_funcs_test.go @@ -9,7 +9,6 @@ import ( ) func TestRulesEqual(t *testing.T) { - t.Skip() tests := map[string]struct { old *papi.Rules new *papi.Rules @@ -610,7 +609,6 @@ func TestRulesEqual(t *testing.T) { } func TestRemoveNil(t *testing.T) { - t.Skip() input := map[string]any{ "a": "aval", "b": nil, diff --git a/pkg/providers/property/helpers.go b/pkg/providers/property/helpers.go index 226f03b2e..c8a904708 100644 --- a/pkg/providers/property/helpers.go +++ b/pkg/providers/property/helpers.go @@ -122,7 +122,6 @@ var complianceRecordSchema = &schema.Resource{ // Convert given hostnames to the map form that can be stored in a schema.ResourceData // Setting only statuses for default certs if they exist -// TODO Set certstatus object for cps managed certs and default certs once PAPI adds support func flattenHostnames(Hostnames []papi.Hostname) []map[string]interface{} { var res []map[string]interface{} for _, hn := range Hostnames { @@ -148,22 +147,22 @@ func flattenHostnames(Hostnames []papi.Hostname) []map[string]interface{} { } return res } -func papiErrorsToList(Errors []*papi.Error) []interface{} { - if len(Errors) == 0 { + +func papiErrorsToList(errors []*papi.Error) []map[string]interface{} { + if len(errors) == 0 { return nil } - var RuleErrors []interface{} - - for _, err := range Errors { + var ruleErrors []map[string]interface{} + for _, err := range errors { if err == nil { continue } - RuleErrors = append(RuleErrors, papiErrorToMap(err)) + ruleErrors = append(ruleErrors, papiErrorToMap(err)) } - return RuleErrors + return ruleErrors } func papiErrorToMap(err *papi.Error) map[string]interface{} { @@ -184,7 +183,6 @@ func papiErrorToMap(err *papi.Error) map[string]interface{} { // NetworkAlias parses the given network name or alias and returns its full name and any error func NetworkAlias(network string) (string, error) { - networks := map[string]papi.ActivationNetwork{ "STAGING": papi.ActivationNetworkStaging, "STAGE": papi.ActivationNetworkStaging, diff --git a/pkg/providers/property/helpers_test.go b/pkg/providers/property/helpers_test.go index 31d7e7b59..11847d1f0 100644 --- a/pkg/providers/property/helpers_test.go +++ b/pkg/providers/property/helpers_test.go @@ -9,7 +9,6 @@ import ( ) func TestNetworkAlias(t *testing.T) { - t.Skip() tests := map[string]struct { hasNetwork bool addNetwork papi.ActivationNetwork diff --git a/pkg/providers/property/papi_errors.go b/pkg/providers/property/papi_errors.go index a6fcb4597..8b84af9c7 100644 --- a/pkg/providers/property/papi_errors.go +++ b/pkg/providers/property/papi_errors.go @@ -22,8 +22,17 @@ var ( // PAPI Contract errors - // ErrLookingUpContract is returned when fetching contract from API client by contractID returned an error or no contract was found + // ErrLookingUpContract is returned when fetching contract from API client by groupId returned an error or no contract was found ErrLookingUpContract = errors.New("looking up contract for provided group") + // ErrMultipleContractsInGroup is returned when fetching contract from API client by groupId returned multiple different contracts + ErrMultipleContractsInGroup = diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "multiple contracts found for given group", + Detail: "Resource doesn't support groups with multiple contracts. " + + "Make sure your group has only one contract assigned before proceeding.", + }, + } // ErrNoContractProvided is retured when no contract ID was provided but "name" was ErrNoContractProvided = errors.New("'contractId' is required for non-default name") // ErrNoGroupProvided is returned when no "group" property is provided @@ -34,6 +43,15 @@ var ( ErrContractNotFound = errors.New("contract not found") // ErrFetchingContracts represents error while fetching contracts ErrFetchingContracts = errors.New("fetching contracts") + // ErrMultipleContractsFound is returned when more than one contract was found + ErrMultipleContractsFound = diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "multiple contracts found", + Detail: "Resource cannot unambiguously identify the contract. " + + "Please provide either a 'group_id' or 'group_name' for accurate identification.", + }, + } // PAPI Product errors diff --git a/pkg/providers/property/provider.go b/pkg/providers/property/provider.go index a2a66621d..067b6b996 100644 --- a/pkg/providers/property/provider.go +++ b/pkg/providers/property/provider.go @@ -97,7 +97,9 @@ func (p *PluginSubprovider) DataSources() map[string]*schema.Resource { // Resources returns terraform resources for property func (p *FrameworkSubprovider) Resources() []func() resource.Resource { - return []func() resource.Resource{} + return []func() resource.Resource{ + NewBootstrapResource, + } } // DataSources returns terraform data sources for property diff --git a/pkg/providers/property/provider_test.go b/pkg/providers/property/provider_test.go index ff07efdf9..405c14998 100644 --- a/pkg/providers/property/provider_test.go +++ b/pkg/providers/property/provider_test.go @@ -12,17 +12,20 @@ import ( "github.com/akamai/terraform-provider-akamai/v5/pkg/akamai" "github.com/akamai/terraform-provider-akamai/v5/pkg/common/testutils" "github.com/hashicorp/go-hclog" + "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/provider" "github.com/hashicorp/terraform-plugin-framework/providerserver" + "github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-go/tfprotov5" "github.com/hashicorp/terraform-plugin-mux/tf5muxserver" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -var testAccProviders map[string]func() (tfprotov5.ProviderServer, error) - -var testAccPluginProvider *schema.Provider -var testAccFrameworkProvider provider.Provider +var ( + testAccProviders map[string]func() (tfprotov5.ProviderServer, error) + testAccPluginProvider *schema.Provider + testAccFrameworkProvider provider.Provider +) func TestMain(m *testing.M) { testAccPluginProvider = akamai.NewPluginProvider(NewPluginSubprovider())() @@ -106,3 +109,92 @@ type T struct{ *testing.T } func (t T) FailNow() { t.T.Fatalf("FAIL: %s", t.T.Name()) } + +type ( + TestSubprovider struct { + resources []func() resource.Resource + datasources []func() datasource.DataSource + client papi.PAPI + } + + clientSetter interface { + setClient(papi.PAPI) + } + + testSubproviderOption func(*TestSubprovider) +) + +func withMockClient(mock papi.PAPI) testSubproviderOption { + return func(ts *TestSubprovider) { + ts.client = mock + } +} + +func newTestSubprovider(opts ...testSubproviderOption) *TestSubprovider { + s := NewFrameworkSubprovider() + + ts := &TestSubprovider{ + resources: s.Resources(), + datasources: s.DataSources(), + } + + for _, opt := range opts { + opt(ts) + } + + return ts +} + +// Resources returns terraform resources for property +func (ts *TestSubprovider) Resources() []func() resource.Resource { + for i, fn := range ts.resources { + // decorate + fn := fn + ts.resources[i] = func() resource.Resource { + res := fn() + if v, ok := res.(clientSetter); ok { + v.setClient(ts.client) + } + return res + } + } + return ts.resources +} + +// DataSources returns terraform data sources for property +func (ts *TestSubprovider) DataSources() []func() datasource.DataSource { + for i, fn := range ts.datasources { + fn := fn + // decorate + ts.datasources[i] = func() datasource.DataSource { + ds := fn() + if v, ok := ds.(clientSetter); ok { + v.setClient(ts.client) + } + return ds + } + } + return ts.datasources +} + +func newProviderFactory(opts ...testSubproviderOption) map[string]func() (tfprotov5.ProviderServer, error) { + testAccProvider := akamai.NewFrameworkProvider(newTestSubprovider(opts...))() + + return map[string]func() (tfprotov5.ProviderServer, error){ + "akamai": func() (tfprotov5.ProviderServer, error) { + ctx := context.Background() + providers := []func() tfprotov5.ProviderServer{ + providerserver.NewProtocol5( + testAccProvider, + ), + } + + muxServer, err := tf5muxserver.NewMuxServer(ctx, providers...) + if err != nil { + return nil, err + } + + return muxServer.ProviderServer(), nil + }, + } +} diff --git a/pkg/providers/property/resource_akamai_cp_code_test.go b/pkg/providers/property/resource_akamai_cp_code_test.go index 92b14069c..e0efb21cf 100644 --- a/pkg/providers/property/resource_akamai_cp_code_test.go +++ b/pkg/providers/property/resource_akamai_cp_code_test.go @@ -22,7 +22,6 @@ import ( var AnyCTX = mock.Anything func TestResCPCode(t *testing.T) { - t.Skip() // Helper to set up an expected call to mock papi.GetCPCode expectGetCPCode := func(m *papi.Mock, contractID, groupID string, CPCodeID int, CPCodes *[]papi.CPCode, err error) *mock.Call { var call *mock.Call diff --git a/pkg/providers/property/resource_akamai_edge_hostname_test.go b/pkg/providers/property/resource_akamai_edge_hostname_test.go index f82739dae..2160d5492 100644 --- a/pkg/providers/property/resource_akamai_edge_hostname_test.go +++ b/pkg/providers/property/resource_akamai_edge_hostname_test.go @@ -20,7 +20,6 @@ import ( ) func TestResourceEdgeHostname(t *testing.T) { - t.Skip() testDir := "testdata/TestResourceEdgeHostname" tests := map[string]struct { init func(*papi.Mock, *hapi.Mock) @@ -1065,7 +1064,6 @@ func TestResourceEdgeHostname(t *testing.T) { } func TestResourceEdgeHostnames_WithImport(t *testing.T) { - t.Skip() expectGetEdgeHostname := func(m *papi.Mock, edgehostID, ContractID, GroupID string) *mock.Call { return m.On("GetEdgeHostname", mock.Anything, papi.GetEdgeHostnameRequest{ EdgeHostnameID: edgehostID, @@ -1167,7 +1165,6 @@ func TestResourceEdgeHostnames_WithImport(t *testing.T) { } func TestFindEdgeHostname(t *testing.T) { - t.Skip() tests := map[string]struct { hostnames papi.EdgeHostnameItems domain string @@ -1300,7 +1297,6 @@ func TestFindEdgeHostname(t *testing.T) { } func TestDiffSuppressEdgeHostname(t *testing.T) { - t.Skip() tests := map[string]struct { old, new string expected bool @@ -1334,7 +1330,6 @@ func TestDiffSuppressEdgeHostname(t *testing.T) { } func TestSuppressEdgeHostnameUseCases(t *testing.T) { - t.Skip() testDir := "testdata/TestResourceEdgeHostname/use_cases" tests := map[string]struct { oldPath, newPath string @@ -1378,7 +1373,6 @@ func TestSuppressEdgeHostnameUseCases(t *testing.T) { } func TestConvertingUseCases2JSON(t *testing.T) { - t.Skip() testDir := "testdata/TestResourceEdgeHostname/use_cases" tests := map[string]struct { useCases []papi.UseCase diff --git a/pkg/providers/property/resource_akamai_property.go b/pkg/providers/property/resource_akamai_property.go index cf4931133..546862466 100644 --- a/pkg/providers/property/resource_akamai_property.go +++ b/pkg/providers/property/resource_akamai_property.go @@ -10,50 +10,31 @@ import ( "strconv" "strings" - "github.com/apex/log" - "github.com/hashicorp/go-cty/cty" - "github.com/hashicorp/terraform-plugin-sdk/v2/diag" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/papi" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/session" "github.com/akamai/terraform-provider-akamai/v5/pkg/common/tf" "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" "github.com/akamai/terraform-provider-akamai/v5/pkg/tools" + "github.com/apex/log" + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) func resourceProperty() *schema.Resource { papiError := func() *schema.Resource { - return &schema.Resource{Schema: map[string]*schema.Schema{ - "type": {Type: schema.TypeString, Optional: true}, - "title": {Type: schema.TypeString, Optional: true}, - "detail": {Type: schema.TypeString, Optional: true}, - "instance": {Type: schema.TypeString, Optional: true}, - "behavior_name": {Type: schema.TypeString, Optional: true}, - "error_location": {Type: schema.TypeString, Optional: true}, - "status_code": {Type: schema.TypeInt, Optional: true}, - }} - } - - hashHostname := func(v interface{}) int { - m, ok := v.(map[string]interface{}) - if !ok { - return 0 - } - cnameFrom, ok := m["cname_from"] - if !ok { - return 0 - } - cnameTo, ok := m["cname_to"] - if !ok { - return 0 - } - certProvisioningType, ok := m["cert_provisioning_type"] - if !ok { - return 0 + return &schema.Resource{ + Schema: map[string]*schema.Schema{ + "type": {Type: schema.TypeString, Optional: true}, + "title": {Type: schema.TypeString, Optional: true}, + "detail": {Type: schema.TypeString, Optional: true}, + "instance": {Type: schema.TypeString, Optional: true}, + "behavior_name": {Type: schema.TypeString, Optional: true}, + "error_location": {Type: schema.TypeString, Optional: true}, + "status_code": {Type: schema.TypeInt, Optional: true}, + }, } - return schema.HashString(fmt.Sprintf("%s.%s.%s", cnameFrom, cnameTo, certProvisioningType)) } validateRules := func(val interface{}, _ cty.Path) diag.Diagnostics { @@ -73,10 +54,10 @@ func resourceProperty() *schema.Resource { ReadContext: resourcePropertyRead, UpdateContext: resourcePropertyUpdate, DeleteContext: resourcePropertyDelete, - CustomizeDiff: customdiff.All( - rulesCustomDiff, + CustomizeDiff: customdiff.Sequence( hostNamesCustomDiff, - setPropertyVersionsComputedOnRulesChange, + propertyRulesCustomDiff, + setPropertyVersionsComputed, ), Importer: &schema.ResourceImporter{ StateContext: resourcePropertyImport, @@ -92,7 +73,7 @@ func resourceProperty() *schema.Resource { Type: schema.TypeString, Required: true, ForceNew: true, - ValidateDiagFunc: validatePropertyName, + ValidateDiagFunc: validateNameWithBound(1), Description: "Name to give to the Property (must be unique)", }, "group_id": { @@ -113,6 +94,11 @@ func resourceProperty() *schema.Resource { Description: "Product ID to be assigned to the Property", StateFunc: addPrefixToState("prd_"), }, + "property_id": { + Type: schema.TypeString, + Optional: true, + Description: "Property ID", + }, "rule_format": { Type: schema.TypeString, Optional: true, @@ -126,9 +112,16 @@ func resourceProperty() *schema.Resource { Computed: true, Description: "Property Rules as JSON", ValidateDiagFunc: validateRules, - DiffSuppressFunc: diffSuppressRules, + DiffSuppressFunc: diffSuppressPropertyRules, StateFunc: rulesStateFunc, }, + "version_notes": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Property version notes", + DiffSuppressFunc: propertyVersionNotesDiffSupress, + }, "hostnames": { Type: schema.TypeSet, Optional: true, @@ -208,6 +201,12 @@ func resourceProperty() *schema.Resource { Computed: true, Elem: papiError(), }, + "rule_warnings": { + Type: schema.TypeList, + Computed: true, + Elem: papiError(), + Description: "Rule validation warnings", + }, }, } } @@ -220,36 +219,62 @@ var rulesStateFunc = func(v interface{}) string { return v.(string) } -// isValidPropertyName is a function that validates if given string contains only letters, numbers, and these characters: . _ - +// isValidPropertyName is a function that validates if given string contains only letters, numbers, and these characters: '.', '_', '-'. var isValidPropertyName = regexp.MustCompile(`^[A-Za-z0-9.\-_]+$`).MatchString -// validatePropertyName validates if name property contains valid characters -func validatePropertyName(v interface{}, _ cty.Path) diag.Diagnostics { - name := v.(string) - maxPropertyNameLength := 85 +// validateNameWithBound validates if name property contains valid characters and validates length. +func validateNameWithBound(lowerBound int) func(v interface{}, _ cty.Path) diag.Diagnostics { + return func(v interface{}, _ cty.Path) diag.Diagnostics { + name := v.(string) + maxPropertyNameLength := 85 - if len(name) > maxPropertyNameLength { - return diag.Errorf("a name must be shorter than %d characters", maxPropertyNameLength+1) + if len(name) > maxPropertyNameLength || len(name) < lowerBound { + return diag.Errorf("a name must be longer than %d characters and shorter than %d characters", lowerBound-1, maxPropertyNameLength+1) + } + if !isValidPropertyName(name) { + return diag.Errorf("a name must only contain letters, numbers, and these characters: . _ -") + } + return nil } - if !isValidPropertyName(name) { - return diag.Errorf("a name must only contain letters, numbers, and these characters: . _ -") +} + +// ErrCalculatingHostnamesHash is used when calculating hash value for set of hostnames failed. +var ErrCalculatingHostnamesHash = errors.New("calculating hostnames set hash failed") + +func hashHostname(v any) int { + m, ok := v.(map[string]any) + if !ok { + panic(fmt.Errorf("%w: expected map[string]any, got: %T", ErrCalculatingHostnamesHash, v)) } - return nil + cnameFrom, ok := m["cname_from"] + if !ok { + panic(fmt.Errorf("%w: 'cname_from' was not provided", ErrCalculatingHostnamesHash)) + } + cnameTo, ok := m["cname_to"] + if !ok { + panic(fmt.Errorf("%w: 'cname_to' was not provided", ErrCalculatingHostnamesHash)) + } + certProvisioningType, ok := m["cert_provisioning_type"] + if !ok { + panic(fmt.Errorf("%w: 'cert_provisioning_type' was not provided", ErrCalculatingHostnamesHash)) + } + return schema.HashString(fmt.Sprintf("%s.%s.%s", cnameFrom, cnameTo, certProvisioningType)) } -// rulesCustomDiff compares Rules.Criteria and Rules.Children fields from terraform state and from a new configuration. -// If some of these fields are empty lists in the new configuration and are nil in the terraform state, then this function -// returns no difference for these fields -func rulesCustomDiff(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { +// propertyRulesCustomDiff compares Rules.Criteria and Rules.Children fields from terraform state +// and from a new configuration. If some of these fields are empty lists in the new configuration and +// are nil in the terraform state, then this function returns no difference for these fields. +func propertyRulesCustomDiff(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { o, n := diff.GetChange("rules") + oldValue, newValue := o.(string), n.(string) - oldValue := o.(string) - newValue := n.(string) - - var oldRulesUpdate, newRulesUpdate papi.RulesUpdate + var newRulesUpdate papi.RulesUpdate if diff.Id() == "" && newValue != "" { - rules, err := unifyRulesDiff(newValue) + if err := json.Unmarshal([]byte(newValue), &newRulesUpdate); err != nil { + return fmt.Errorf("cannot parse rules JSON from config: %s", err) + } + rules, err := unifyRulesDiff(newRulesUpdate) if err != nil { return err } @@ -263,24 +288,30 @@ func rulesCustomDiff(_ context.Context, diff *schema.ResourceDiff, _ interface{} return nil } - err := json.Unmarshal([]byte(oldValue), &oldRulesUpdate) - if err != nil { + var oldRulesUpdate papi.RulesUpdate + if err := json.Unmarshal([]byte(oldValue), &oldRulesUpdate); err != nil { return fmt.Errorf("cannot parse rules JSON from state: %s", err) } - err = json.Unmarshal([]byte(newValue), &newRulesUpdate) - if err != nil { + if err := json.Unmarshal([]byte(newValue), &newRulesUpdate); err != nil { return fmt.Errorf("cannot parse rules JSON from config: %s", err) } normalizeFields(&oldRulesUpdate, &newRulesUpdate) + if rulesEqual(&oldRulesUpdate.Rules, &newRulesUpdate.Rules) && oldRulesUpdate.Comments == newRulesUpdate.Comments { + return nil + } + + versionNotes, _ := tf.NewRawConfig(diff).GetOk("version_notes") + if versionNotes != nil { + newRulesUpdate.Comments = oldRulesUpdate.Comments + } + rules, err := json.Marshal(newRulesUpdate) if err != nil { return fmt.Errorf("cannot encode rules JSON %s", err) } - if ruleTreesEqual(&oldRulesUpdate, &newRulesUpdate) { - return nil - } + if err = diff.SetNew("rules", string(rules)); err != nil { return fmt.Errorf("cannot set a new diff value for 'rules' %s", err) } @@ -290,12 +321,7 @@ func rulesCustomDiff(_ context.Context, diff *schema.ResourceDiff, _ interface{} // unifyRulesDiff is invoked on first planning for property creation // Its main purpose is to unify the rules JSON with what we expect will be created by PAPI // It is used in order to prevent diffs on output on subsequent terraform applies -func unifyRulesDiff(newValue string) (string, error) { - var newRulesUpdate papi.RulesUpdate - err := json.Unmarshal([]byte(newValue), &newRulesUpdate) - if err != nil { - return "", fmt.Errorf("cannot parse rules JSON from config: %s", err) - } +func unifyRulesDiff(newRulesUpdate papi.RulesUpdate) (string, error) { removeNilOptions(&newRulesUpdate.Rules) rulesBytes, err := json.Marshal(newRulesUpdate) if err != nil { @@ -338,21 +364,53 @@ func hostNamesCustomDiff(_ context.Context, d *schema.ResourceDiff, m interface{ return nil } -// setPropertyVersionsComputedOnRulesChange is a schema.CustomizeDiffFunc for akamai_property resource, -// which sets latest_version, staging_version and production_version fields as computed -// if a new version of the property is expected to be created. -func setPropertyVersionsComputedOnRulesChange(_ context.Context, rd *schema.ResourceDiff, _ interface{}) error { - oldHostnames, newHostnames := rd.GetChange("hostnames") - hostnamesEqual := oldHostnames.(*schema.Set).HashEqual(newHostnames.(*schema.Set)) - ruleFormatChanged := rd.HasChange("rule_format") +// canTriggerNewPropertyVersion is a diff time utility for recognizing if changes +// to the configuration may result in creating a new property version. +// +// Things that might trigger creating a new property version: +// - updating hostnames, +// - updating property rules (excluding comments if version_notes are set) +// - updating rule_format, updating rules. +// +// To properly recognize version_notes being removed from config, use rd +// implementation which uses raw config. +func canTriggerNewPropertyVersion(rc tf.ResourceChangeFetcher, rd tf.ResourceDataFetcher) (bool, error) { + oldHostnames, newHostnames := rc.GetChange("hostnames") + if !oldHostnames.(*schema.Set).HashEqual(newHostnames.(*schema.Set)) { + return true, nil + } - oldRules, newRules := rd.GetChange("rules") - rulesEqual, err := rulesJSONEqual(oldRules.(string), newRules.(string)) - if err != nil { - return err + if rc.HasChange("rule_format") { + return true, nil } - if !ruleFormatChanged && hostnamesEqual && rulesEqual { + o, n := rc.GetChange("rules") + var oldRules papi.RulesUpdate + if err := json.Unmarshal([]byte(o.(string)), &oldRules); err != nil { + return false, fmt.Errorf("'old' = %s, unmarshal: %w", o.(string), err) + } + + var newRules papi.RulesUpdate + if err := json.Unmarshal([]byte(n.(string)), &newRules); err != nil { + return false, fmt.Errorf("'new' = %s, unmarshal: %w", n.(string), err) + } + + versionNotes, _ := rd.GetOk("version_notes") + if versionNotes == nil && oldRules.Comments != newRules.Comments { + return true, nil + } + + return !rulesEqual(&oldRules.Rules, &newRules.Rules), nil +} + +// setPropertyVersionsComputed implements a schema.CustomizeDiffFunc for akamai_property resource. +// +// It sets certain attributes as computed if a new version of the property is expected to be +// created. For latest_version staging_version and production_version attributes it's crucial +// for avoiding inconsistent plan errors if any of them are used in akamai_property_activation resource. +func setPropertyVersionsComputed(_ context.Context, rd *schema.ResourceDiff, _ interface{}) error { + rawData := tf.NewRawConfig(rd) + if ok, err := canTriggerNewPropertyVersion(rd, rawData); err != nil || !ok { return nil } @@ -365,6 +423,15 @@ func setPropertyVersionsComputedOnRulesChange(_ context.Context, rd *schema.Reso return nil } +func propertyVersionNotesDiffSupress(_, _, _ string, rd *schema.ResourceData) bool { + rawData := tf.NewRawConfig(rd) + if ok, err := canTriggerNewPropertyVersion(rd, rawData); ok || err != nil { + return false + } + + return true +} + func resourcePropertyCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { meta := meta.Must(m) logger := meta.Log("PAPI", "resourcePropertyCreate") @@ -392,37 +459,19 @@ func resourcePropertyCreate(ctx context.Context, d *schema.ResourceData, m inter } productID = tools.AddPrefix(productID, "prd_") - ruleFormat := d.Get("rule_format").(string) + propertyID, err := tf.GetStringValue("property_id", d) + if err != nil && !errors.Is(err, tf.ErrNotFound) { + return diag.FromErr(err) + } - rulesJSON := []byte(d.Get("rules").(string)) + ruleFormat := d.Get("rule_format").(string) - propertyID, err := createProperty(ctx, client, propertyName, groupID, contractID, productID, ruleFormat) - if err != nil { - if strings.Contains(err.Error(), "\"statusCode\": 404") { - // find out what is missing from the request - if _, err = getGroup(ctx, meta, groupID); err != nil { - if errors.Is(err, ErrGroupNotFound) { - return diag.Errorf("%v: %s", ErrGroupNotFound, groupID) - } - return diag.FromErr(err) - } - if _, err = getContract(ctx, meta, contractID); err != nil { - if errors.Is(err, ErrContractNotFound) { - return diag.Errorf("%v: %s", ErrContractNotFound, contractID) - } - return diag.FromErr(err) - } - if _, err = getProduct(ctx, meta, productID, contractID); err != nil { - if errors.Is(err, ErrProductNotFound) { - return diag.Errorf("%v: %s", ErrProductNotFound, productID) - } - return diag.FromErr(err) - } - return diag.FromErr(err) + if propertyID == "" { + propertyID, err = createProperty(ctx, client, propertyName, groupID, contractID, productID, ruleFormat) + if err != nil { + return interpretCreatePropertyError(ctx, err, client, groupID, contractID, productID) } - return diag.FromErr(err) } - // Save minimum state BEFORE moving on d.SetId(propertyID) attrs := map[string]interface{}{ @@ -430,7 +479,7 @@ func resourcePropertyCreate(ctx context.Context, d *schema.ResourceData, m inter "contract_id": contractID, "product_id": productID, } - if err := rdSetAttrs(ctx, d, attrs); err != nil { + if err := tf.SetAttrs(d, attrs); err != nil { return diag.FromErr(err) } @@ -442,35 +491,29 @@ func resourcePropertyCreate(ctx context.Context, d *schema.ResourceData, m inter ProductID: productID, LatestVersion: 1, } + hostnameVal, err := tf.GetSetValue("hostnames", d) - if err == nil { + if err != nil { + logger.Warnf("hostnames not set in ResourceData: %s", err.Error()) + } else { hostnames := mapToHostnames(hostnameVal.List()) if len(hostnames) > 0 { if err := updatePropertyHostnames(ctx, client, property, hostnames); err != nil { return diag.FromErr(err) } } - } else { - logger.Warnf("hostnames not set in ResourceData: %s", err.Error()) } - if len(rulesJSON) > 0 { - var rules papi.RulesUpdate - if err := json.Unmarshal(rulesJSON, &rules); err != nil { - logger.WithError(err).Error("failed to unmarshal property rules") - return diag.Errorf("rules are not valid JSON: %s", err) - } - - ctx := ctx - if ruleFormat != "" { - h := http.Header{ - "Content-Type": []string{fmt.Sprintf("application/vnd.akamai.papirules.%s+json", ruleFormat)}, - } - - ctx = session.ContextWithOptions(ctx, session.WithContextHeaders(h)) + rulesJSON := d.Get("rules").(string) + versionNotes := d.Get("version_notes").(string) + if rulesJSON != "" || versionNotes != "" { + rulesUpdate, err := newRulesUpdate(rulesJSON, versionNotes) + if err != nil { + d.Partial(true) + return diag.FromErr(err) } - if err := updatePropertyRules(ctx, client, property, rules); err != nil { + if err := updatePropertyRules(ctx, client, property, rulesUpdate, ruleFormat); err != nil { d.Partial(true) return diag.FromErr(err) } @@ -479,12 +522,36 @@ func resourcePropertyCreate(ctx context.Context, d *schema.ResourceData, m inter return resourcePropertyRead(ctx, d, m) } +func interpretCreatePropertyError(ctx context.Context, err error, client papi.PAPI, groupID string, contractID string, productID string) diag.Diagnostics { + if strings.Contains(err.Error(), "\"statusCode\": 404") { + // find out what is missing from the request + if _, err = getGroup(ctx, client, groupID); err != nil { + if errors.Is(err, ErrGroupNotFound) { + return diag.Errorf("%v: %s", ErrGroupNotFound, groupID) + } + return diag.FromErr(err) + } + if _, err = getContract(ctx, client, contractID); err != nil { + if errors.Is(err, ErrContractNotFound) { + return diag.Errorf("%v: %s", ErrContractNotFound, contractID) + } + return diag.FromErr(err) + } + if _, err = getProduct(ctx, client, productID, contractID); err != nil { + if errors.Is(err, ErrProductNotFound) { + return diag.Errorf("%v: %s", ErrProductNotFound, productID) + } + return diag.FromErr(err) + } + } + return diag.FromErr(err) +} + func resourcePropertyRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { ctx = log.NewContext(ctx, meta.Must(m).Log("PAPI", "resourcePropertyRead")) logger := log.FromContext(ctx) client := Client(meta.Must(m)) - // Schema guarantees group_id, and contract_id are strings propertyID := d.Id() contractID := tools.AddPrefix(d.Get("contract_id").(string), "ctr_") groupID := tools.AddPrefix(d.Get("group_id").(string), "grp_") @@ -516,17 +583,16 @@ func resourcePropertyRead(ctx context.Context, d *schema.ResourceData, m interfa productionVersion = *property.ProductionVersion } - // TODO: Load hostnames asynchronously hostnames, err := fetchPropertyVersionHostnames(ctx, client, *property, v) if err != nil { return diag.FromErr(err) } - // TODO: Load rules asynchronously rules, ruleFormat, ruleErrors, ruleWarnings, err := fetchPropertyVersionRules(ctx, client, *property, v) if err != nil { return diag.FromErr(err) } + if len(ruleErrors) > 0 { if err := d.Set("rule_errors", papiErrorsToList(ruleErrors)); err != nil { return diag.FromErr(fmt.Errorf("%w: %s", tf.ErrValueSet, err.Error())) @@ -536,26 +602,34 @@ func resourcePropertyRead(ctx context.Context, d *schema.ResourceData, m interfa return diag.FromErr(fmt.Errorf("error marshaling API error: %s", err)) } logger.Errorf("property has rule errors %s", msg) + } else { + if err := d.Set("rule_warnings", nil); err != nil { + return diag.FromErr(fmt.Errorf("%w: %s", tf.ErrValueSet, err.Error())) + } } + if len(ruleWarnings) > 0 { - msg, err := json.MarshalIndent(papiErrorsToList(ruleWarnings), "", "\t") - if err != nil { - return diag.FromErr(fmt.Errorf("error marshaling API warnings: %s", err)) + if err := d.Set("rule_warnings", papiErrorsToList(ruleWarnings)); err != nil { + return diag.FromErr(fmt.Errorf("%w: %s", tf.ErrValueSet, err.Error())) + } + } else { + if err := d.Set("rule_warnings", nil); err != nil { + return diag.FromErr(fmt.Errorf("%w: %s", tf.ErrValueSet, err.Error())) } - logger.Warnf("property has rule warnings %s", msg) } - rulesJSON, err := json.Marshal(rules) - if err != nil { - logger.WithError(err).Error("could not render rules as JSON") - return diag.Errorf("received rules that could not be rendered to JSON: %s", err) - } res, err := fetchPropertyVersion(ctx, client, propertyID, groupID, contractID, v) if err != nil { return diag.FromErr(err) } property.ProductID = res.Version.ProductID + rulesJSON, err := json.Marshal(rules) + if err != nil { + logger.WithError(err).Error("could not render rules as JSON") + return diag.Errorf("received rules that could not be rendered to JSON: %s", err) + } + attrs := map[string]interface{}{ "name": property.PropertyName, "group_id": property.GroupID, @@ -568,11 +642,12 @@ func resourcePropertyRead(ctx context.Context, d *schema.ResourceData, m interfa "rule_format": ruleFormat, "rule_errors": papiErrorsToList(ruleErrors), "read_version": readVersionID, + "version_notes": res.Version.Note, } if property.ProductID != "" { attrs["product_id"] = property.ProductID } - if err := rdSetAttrs(ctx, d, attrs); err != nil { + if err := tf.SetAttrs(d, attrs); err != nil { return diag.FromErr(err) } @@ -590,6 +665,7 @@ func resourcePropertyUpdate(ctx context.Context, d *schema.ResourceData, m inter "group_id", "contract_id", "product_id", + "property_id", } for _, attr := range immutable { if d.HasChange(attr) { @@ -609,7 +685,6 @@ func resourcePropertyUpdate(ctx context.Context, d *schema.ResourceData, m inter return nil } - // Schema guarantees these types var stagingVersion, productionVersion *int if v, ok := d.GetOk("staging_version"); ok && v.(int) != 0 { i := v.(int) @@ -632,7 +707,6 @@ func resourcePropertyUpdate(ctx context.Context, d *schema.ResourceData, m inter ProductionVersion: productionVersion, } - // Schema guarantees group_id, and contract_id are strings propertyID := d.Id() contractID := d.Get("contract_id").(string) groupID := d.Get("group_id").(string) @@ -664,7 +738,6 @@ func resourcePropertyUpdate(ctx context.Context, d *schema.ResourceData, m inter } } - // hostnames if d.HasChange("hostnames") { hostnamesVal, err := tf.GetSetValue("hostnames", d) if err == nil { @@ -680,12 +753,33 @@ func resourcePropertyUpdate(ctx context.Context, d *schema.ResourceData, m inter } } - ruleFormat := d.Get("rule_format").(string) - rulesJSON := []byte(d.Get("rules").(string)) - rulesNeedUpdate := len(rulesJSON) > 0 && d.HasChange("rules") - formatNeedsUpdate := len(ruleFormat) > 0 && d.HasChange("rule_format") + if !shouldUpdateRuleTree(d) { + return resourcePropertyRead(ctx, d, m) + } + + ruleFormat, err := tf.GetStringValue("rule_format", d) + if err != nil && !errors.Is(err, tf.ErrNotFound) { + return diag.FromErr(err) + } + + rulesJSON, err := tf.GetStringValue("rules", d) + if err != nil && !errors.Is(err, tf.ErrNotFound) { + return diag.FromErr(err) + } - if err := needsUpdate(ctx, d, formatNeedsUpdate, rulesNeedUpdate, rulesJSON, ruleFormat, client, property); err != nil { + versionNotes, err := tf.GetStringValue("version_notes", tf.NewRawConfig(d)) + if err != nil && !errors.Is(err, tf.ErrNotFound) { + return diag.FromErr(err) + } + + rulesUpdate, err := newRulesUpdate(rulesJSON, versionNotes) + if err != nil { + d.Partial(true) + return diag.FromErr(err) + } + + if err := updatePropertyRules(ctx, client, property, rulesUpdate, ruleFormat); err != nil { + d.Partial(true) return diag.FromErr(err) } @@ -694,9 +788,18 @@ func resourcePropertyUpdate(ctx context.Context, d *schema.ResourceData, m inter func resourcePropertyDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { ctx = log.NewContext(ctx, meta.Must(m).Log("PAPI", "resourcePropertyDelete")) + logger := log.FromContext(ctx) client := Client(meta.Must(m)) - propertyID := d.Id() + propertyID, err := tf.GetStringValue("property_id", d) + if err != nil && !errors.Is(err, tf.ErrNotFound) { + return diag.FromErr(err) + } + if propertyID != "" { + logger.Infof("property is maintained by 'akamai_property_bootstrap' resource.") + return nil + } + propertyID = d.Id() contractID := tools.AddPrefix(d.Get("contract_id").(string), "ctr_") groupID := tools.AddPrefix(d.Get("group_id").(string), "grp_") @@ -713,7 +816,12 @@ func resourcePropertyImport(ctx context.Context, d *schema.ResourceData, m inter // User-supplied import ID is a comma-separated list of propertyID[,groupID[,contractID]] // contractID and groupID are optional as long as the propertyID is sufficient to fetch the property var propertyID, groupID, contractID, version string + var propertyBootstrap bool parts := strings.Split(d.Id(), ",") + if parts[len(parts)-1] == "property-bootstrap" { + propertyBootstrap = true + parts = parts[:len(parts)-1] + } switch len(parts) { case 4: version = parts[3] @@ -739,7 +847,9 @@ func resourcePropertyImport(ctx context.Context, d *schema.ResourceData, m inter "group_id": groupID, "contract_id": contractID, } - + if propertyBootstrap { + attrs["property_id"] = propertyID + } // if we also get the optional version parameter, we need to parse it and set it in the schema if !isDefaultVersion(version) { if v, err := parseVersionNumber(version); err != nil { @@ -747,7 +857,7 @@ func resourcePropertyImport(ctx context.Context, d *schema.ResourceData, m inter if _, err := NetworkAlias(version); err != nil { return nil, ErrPropertyVersionNotFound } - // if we ran validation and we actually have a network name, we still need to fetch the desired version number + // if we ran validation, and we actually have a network name, we still need to fetch the desired version number _, attrs["read_version"], err = fetchProperty(ctx, Client(meta.Must(m)), propertyID, groupID, contractID, version) if err != nil { return nil, err @@ -757,7 +867,7 @@ func resourcePropertyImport(ctx context.Context, d *schema.ResourceData, m inter attrs["read_version"] = v } } - if err := rdSetAttrs(ctx, d, attrs); err != nil { + if err := tf.SetAttrs(d, attrs); err != nil { return nil, err } @@ -782,7 +892,10 @@ func resourcePropertyImport(ctx context.Context, d *schema.ResourceData, m inter "contract_id": property.ContractID, "read_version": v, } - if err := rdSetAttrs(ctx, d, attrs); err != nil { + if propertyBootstrap { + attrs["property_id"] = propertyID + } + if err := tf.SetAttrs(d, attrs); err != nil { return nil, err } @@ -796,7 +909,7 @@ func isDefaultVersion(version string) bool { var versionRegexp = regexp.MustCompile(`^ver_(\d+)$`) -// parse a version number (format "ver_#" or "#") or throw an error +// parseVersionNumber parses a version number (format "ver_#" or "#") or throws an error func parseVersionNumber(version string) (int, error) { v := tools.AddPrefix(version, "ver_") r := versionRegexp @@ -808,7 +921,7 @@ func parseVersionNumber(version string) (int, error) { return versionNumber, err } -func createProperty(ctx context.Context, client papi.PAPI, propertyName, groupID, contractID, productID, ruleFormat string) (propertyID string, err error) { +func createProperty(ctx context.Context, client papi.PAPI, propertyName, groupID, contractID, productID, ruleFormat string) (string, error) { req := papi.CreatePropertyRequest{ ContractID: contractID, GroupID: groupID, @@ -820,17 +933,50 @@ func createProperty(ctx context.Context, client papi.PAPI, propertyName, groupID } logger := log.FromContext(ctx).WithFields(logFields(req)) - logger.Debug("creating property") + res, err := client.CreateProperty(ctx, req) - if err != nil { - logger.WithError(err).Error("could not create property") - return + if err == nil { + logger.WithFields(logFields(*res)).Info("property created") + return res.PropertyID, nil } - propertyID = res.PropertyID - logger.WithFields(logFields(*res)).Info("property created") - return + logger.WithError(err).Error("could not create property") + + var targetErr *papi.Error + if errors.As(err, &targetErr) && targetErr.StatusCode == http.StatusNotFound { + if interpretedErr := interpretCreatePropertyBadRequest(ctx, client, req); interpretedErr != nil { + return "", interpretedErr + } + return "", err + } + + return "", err +} + +func interpretCreatePropertyBadRequest(ctx context.Context, client papi.PAPI, req papi.CreatePropertyRequest) error { + if _, err := getGroup(ctx, client, req.GroupID); err != nil { + if errors.Is(err, ErrGroupNotFound) { + return fmt.Errorf("%v: %s", ErrGroupNotFound, req.GroupID) + } + return err + } + + if _, err := getContract(ctx, client, req.ContractID); err != nil { + if errors.Is(err, ErrContractNotFound) { + return fmt.Errorf("%v: %s", ErrContractNotFound, req.ContractID) + } + return err + } + + if _, err := getProduct(ctx, client, req.Property.ProductID, req.ContractID); err != nil { + if errors.Is(err, ErrProductNotFound) { + return fmt.Errorf("%v: %s", ErrProductNotFound, req.Property.ProductID) + } + return err + } + + return nil } func removeProperty(ctx context.Context, client papi.PAPI, propertyID, groupID, contractID string) error { @@ -880,7 +1026,7 @@ func fetchLatestProperty(ctx context.Context, client papi.PAPI, propertyID, grou return res.Property, nil } -// fetchProperty Retrieves basic info for a Property +// fetchProperty retrieves basic info for a Property func fetchProperty(ctx context.Context, client papi.PAPI, propertyID, groupID, contractID, version string) (*papi.Property, int, error) { req := papi.GetPropertyVersionsRequest{ PropertyID: propertyID, @@ -935,8 +1081,8 @@ func fetchProperty(ctx context.Context, client papi.PAPI, propertyID, groupID, c PropertyID: res.PropertyID, PropertyName: res.PropertyName, LatestVersion: getLatestVersionNumber(res.Versions.Items), - StagingVersion: getNetworkActiveVersionNumber(res.Versions.Items, string(papi.ActivationNetworkStaging)), - ProductionVersion: getNetworkActiveVersionNumber(res.Versions.Items, string(papi.ActivationNetworkProduction)), + StagingVersion: getNetworkActiveVersionNumber(res.Versions.Items, papi.ActivationNetworkStaging), + ProductionVersion: getNetworkActiveVersionNumber(res.Versions.Items, papi.ActivationNetworkProduction), AssetID: res.AssetID, Note: versionItem.Note, ProductID: versionItem.ProductID, @@ -953,7 +1099,7 @@ func fetchProperty(ctx context.Context, client papi.PAPI, propertyID, groupID, c func filterStaging(items []papi.PropertyVersionGetItem) ([]papi.PropertyVersionGetItem, error) { var output []papi.PropertyVersionGetItem for _, it := range items { - if it.StagingStatus == "ACTIVE" { + if it.StagingStatus == papi.VersionStatusActive { output = append(output, it) } } @@ -968,7 +1114,7 @@ func filterStaging(items []papi.PropertyVersionGetItem) ([]papi.PropertyVersionG func filterProduction(items []papi.PropertyVersionGetItem) ([]papi.PropertyVersionGetItem, error) { var output []papi.PropertyVersionGetItem for _, it := range items { - if it.ProductionStatus == "ACTIVE" { + if it.ProductionStatus == papi.VersionStatusActive { output = append(output, it) } } @@ -992,15 +1138,15 @@ func getLatestVersionNumber(items []papi.PropertyVersionGetItem) int { // getNetworkActiveVersionNumber returns from the given list the *papi.PropertyVersionGetItem // active in the given network -func getNetworkActiveVersionNumber(items []papi.PropertyVersionGetItem, network string) *int { +func getNetworkActiveVersionNumber(items []papi.PropertyVersionGetItem, network papi.ActivationNetwork) *int { for _, it := range items { switch network { - case string(papi.ActivationNetworkStaging): - if it.StagingStatus == "ACTIVE" { + case papi.ActivationNetworkStaging: + if it.StagingStatus == papi.VersionStatusActive { return &it.PropertyVersion } - case string(papi.ActivationNetworkProduction): - if it.ProductionStatus == "ACTIVE" { + case papi.ActivationNetworkProduction: + if it.ProductionStatus == papi.VersionStatusActive { return &it.PropertyVersion } } @@ -1017,7 +1163,7 @@ func getVersionItem(items []papi.PropertyVersionGetItem, versionNumber int) (*pa return nil, ErrPropertyVersionNotFound } -// load status for what we currently have as a given property version. GetLatestVersion may also work here. +// fetchPropertyVersion loads status for what we currently have as a given property version. GetLatestVersion may also work here. func fetchPropertyVersion(ctx context.Context, client papi.PAPI, propertyID, groupID, contractID string, propertyVersion int) (*papi.GetPropertyVersionsResponse, error) { req := papi.GetPropertyVersionRequest{ PropertyID: propertyID, @@ -1038,7 +1184,7 @@ func fetchPropertyVersion(ctx context.Context, client papi.PAPI, propertyID, gro return res, err } -// Fetch hostnames for latest version of given property +// fetchPropertyVersionHostnames fetchs hostnames for latest version of given property. func fetchPropertyVersionHostnames(ctx context.Context, client papi.PAPI, property papi.Property, version int) ([]papi.Hostname, error) { req := papi.GetPropertyVersionHostnamesRequest{ PropertyID: property.PropertyID, @@ -1061,7 +1207,6 @@ func fetchPropertyVersionHostnames(ctx context.Context, client papi.PAPI, proper return res.Hostnames.Items, nil } -// Fetch rules for latest version of given property func fetchPropertyVersionRules(ctx context.Context, client papi.PAPI, property papi.Property, version int) (rules papi.RulesUpdate, format string, errors, warnings []*papi.Error, err error) { req := papi.GetRuleTreeRequest{ PropertyID: property.PropertyID, @@ -1092,8 +1237,34 @@ func fetchPropertyVersionRules(ctx context.Context, client papi.PAPI, property p return } -// Set rules for the latest version of the given property -func updatePropertyRules(ctx context.Context, client papi.PAPI, property papi.Property, rules papi.RulesUpdate) error { +func shouldUpdateRuleTree(rd *schema.ResourceData) bool { + rules, _ := rd.GetOk("rules") + format, _ := rd.GetOk("rule_format") + + rulesNeedUpdate := rules != nil && rd.HasChange("rules") + formatNeedsUpdate := format != nil && rd.HasChange("rule_format") + + return rulesNeedUpdate || formatNeedsUpdate +} + +// newRulesUpdate returns new papi.RulesUpdate, created using provided rules in JSON and comments. +// If comments are not empty, it overwrites comments unmarshalled from JSON with them. +func newRulesUpdate(rulesJSON, comments string) (papi.RulesUpdate, error) { + var rules papi.RulesUpdate + if err := json.Unmarshal([]byte(rulesJSON), &rules); err != nil { + return papi.RulesUpdate{}, fmt.Errorf("property rules are not valid JSON: %s", err) + } + + if comments != "" { + rules.Comments = comments + } + + return rules, nil +} + +func updatePropertyRules(ctx context.Context, client papi.PAPI, property papi.Property, rules papi.RulesUpdate, ruleFormat string) error { + logger := log.FromContext(ctx) + req := papi.UpdateRulesRequest{ PropertyID: property.PropertyID, GroupID: property.GroupID, @@ -1103,9 +1274,13 @@ func updatePropertyRules(ctx context.Context, client papi.PAPI, property papi.Pr ValidateRules: true, } - logger := log.FromContext(ctx).WithFields(logFields(req)) + if ruleFormat != "" { + MIME := fmt.Sprintf("application/vnd.akamai.papirules.%s+json", ruleFormat) + h := http.Header{"Content-Type": []string{MIME}} + ctx = session.ContextWithOptions(ctx, session.WithContextHeaders(h)) + } - logger.Debug("fetching property rules") + logger.Debug("updating property rules") res, err := client.UpdateRuleTree(ctx, req) if err != nil { logger.WithError(err).Error("could not update property rules") @@ -1116,7 +1291,7 @@ func updatePropertyRules(ctx context.Context, client papi.PAPI, property papi.Pr return nil } -// Create a new property version based on the latest version of the given property +// createPropertyVersion creates a new property version based on the latest version of the given property. func createPropertyVersion(ctx context.Context, client papi.PAPI, property papi.Property, version int) (newVersion int, err error) { req := papi.CreatePropertyVersionRequest{ PropertyID: property.PropertyID, @@ -1141,7 +1316,7 @@ func createPropertyVersion(ctx context.Context, client papi.PAPI, property papi. return } -// Set hostnames of the latest version of the given property +// updatePropertyHostnames sets hostnames of the latest version of the given property. func updatePropertyHostnames(ctx context.Context, client papi.PAPI, property papi.Property, hostnames []papi.Hostname) error { if hostnames == nil { hostnames = []papi.Hostname{} @@ -1183,17 +1358,17 @@ func updatePropertyHostnames(ctx context.Context, client papi.PAPI, property pap return nil } -// Convert the given map from a schema.ResourceData to a slice of papi.Hostnames /input to papi request +// mapToHostnames converts the given map from a schema.ResourceData to a slice of papi.Hostnames input to papi request. func mapToHostnames(givenList []interface{}) []papi.Hostname { - var Hostnames []papi.Hostname + var hostnames []papi.Hostname for _, givenMap := range givenList { - var r = givenMap.(map[string]interface{}) + r := givenMap.(map[string]interface{}) cnameFrom := r["cname_from"] cnameTo := r["cname_to"] certProvisioningType := r["cert_provisioning_type"] if len(r) != 0 { - Hostnames = append(Hostnames, papi.Hostname{ + hostnames = append(hostnames, papi.Hostname{ CnameType: "EDGE_HOSTNAME", CnameFrom: cnameFrom.(string), CnameTo: cnameTo.(string), // guaranteed by schema to be a string @@ -1201,39 +1376,5 @@ func mapToHostnames(givenList []interface{}) []papi.Hostname { }) } } - return Hostnames -} - -// Set many attributes of a schema.ResourceData in one call -func rdSetAttrs(ctx context.Context, d *schema.ResourceData, AttributeValues map[string]interface{}) error { - logger := log.FromContext(ctx) - - for attr, value := range AttributeValues { - if err := d.Set(attr, value); err != nil { - logger.WithError(err).Errorf("could not set %q", attr) - return err - } - } - - return nil -} - -func needsUpdate(ctx context.Context, d *schema.ResourceData, formatNeedsUpdate, rulesNeedUpdate bool, rulesJSON []byte, ruleFormat string, client papi.PAPI, property papi.Property) error { - if formatNeedsUpdate || rulesNeedUpdate { - var Rules papi.RulesUpdate - if err := json.Unmarshal(rulesJSON, &Rules); err != nil { - d.Partial(true) - return fmt.Errorf("rules are not valid JSON: %s", err) - } - - MIME := fmt.Sprintf("application/vnd.akamai.papirules.%s+json", ruleFormat) - h := http.Header{"Content-Type": []string{MIME}} - ctx := session.ContextWithOptions(ctx, session.WithContextHeaders(h)) - - if err := updatePropertyRules(ctx, client, property, Rules); err != nil { - d.Partial(true) - return err - } - } - return nil + return hostnames } diff --git a/pkg/providers/property/resource_akamai_property_activation.go b/pkg/providers/property/resource_akamai_property_activation.go index 5fc280d03..d910fe43c 100644 --- a/pkg/providers/property/resource_akamai_property_activation.go +++ b/pkg/providers/property/resource_akamai_property_activation.go @@ -285,7 +285,7 @@ func resourcePropertyActivationCreate(ctx context.Context, d *schema.ResourceDat "activation_id": activation.ActivationID, "version": version, } - if err := rdSetAttrs(ctx, d, attrs); err != nil { + if err := tf.SetAttrs(d, attrs); err != nil { return diag.FromErr(err) } @@ -696,7 +696,7 @@ func resourcePropertyActivationUpdate(ctx context.Context, d *schema.ResourceDat "activation_id": propertyActivation.ActivationID, "version": version, } - if err := rdSetAttrs(ctx, d, attrs); err != nil { + if err := tf.SetAttrs(d, attrs); err != nil { return diag.FromErr(err) } diff --git a/pkg/providers/property/resource_akamai_property_activation_test.go b/pkg/providers/property/resource_akamai_property_activation_test.go index 8e8dd2212..575bdd3d3 100644 --- a/pkg/providers/property/resource_akamai_property_activation_test.go +++ b/pkg/providers/property/resource_akamai_property_activation_test.go @@ -13,7 +13,6 @@ import ( ) func TestResourcePAPIPropertyActivation(t *testing.T) { - t.Skip() tests := map[string]struct { init func(*papi.Mock) steps []resource.TestStep diff --git a/pkg/providers/property/resource_akamai_property_activation_unit_test.go b/pkg/providers/property/resource_akamai_property_activation_unit_test.go index 3b3fc9ad7..3e71d3d0e 100644 --- a/pkg/providers/property/resource_akamai_property_activation_unit_test.go +++ b/pkg/providers/property/resource_akamai_property_activation_unit_test.go @@ -38,7 +38,6 @@ func (c ctxt) Value(_ interface{}) interface{} { } func TestResolveVersion(t *testing.T) { - t.Skip() tests := map[string]struct { versionData int versionDataExists bool @@ -110,7 +109,7 @@ func TestResolveVersion(t *testing.T) { } func TestLookupActivation(t *testing.T) { - t.Skip() + tests := map[string]struct { init func(*papi.Mock) query lookupActivationRequest @@ -301,7 +300,7 @@ func TestLookupActivation(t *testing.T) { } func TestCreateActivation(t *testing.T) { - t.Skip() + defer func(t time.Duration) { CreateActivationRetry = t }(CreateActivationRetry) // restore previous value diff --git a/pkg/providers/property/resource_akamai_property_bootstrap.go b/pkg/providers/property/resource_akamai_property_bootstrap.go new file mode 100644 index 000000000..940d9cb1f --- /dev/null +++ b/pkg/providers/property/resource_akamai_property_bootstrap.go @@ -0,0 +1,277 @@ +package property + +import ( + "context" + "errors" + "fmt" + "regexp" + "strings" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/papi" + "github.com/akamai/terraform-provider-akamai/v5/pkg/common/framework/modifiers" + "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" + "github.com/akamai/terraform-provider-akamai/v5/pkg/tools" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/hashicorp/terraform-plugin-log/tflog" +) + +var ( + _ resource.Resource = &BootstrapResource{} + _ resource.ResourceWithImportState = &BootstrapResource{} + _ resource.ResourceWithConfigure = &BootstrapResource{} +) + +// BootstrapResource represents akamai_property_bootstrap resource +type BootstrapResource struct { + client papi.PAPI +} + +// BootstrapResourceModel is a model for akamai_property_bootstrap resource +type BootstrapResourceModel struct { + ID types.String `tfsdk:"id"` + Name types.String `tfsdk:"name"` + GroupID types.String `tfsdk:"group_id"` + ContractID types.String `tfsdk:"contract_id"` + ProductID types.String `tfsdk:"product_id"` +} + +func (r *BootstrapResource) setClient(client papi.PAPI) { + r.client = client +} + +// NewBootstrapResource returns new property bootstrap resource +func NewBootstrapResource() resource.Resource { + return &BootstrapResource{} +} + +// Metadata implements resource.Resource. +func (r *BootstrapResource) Metadata(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = "akamai_property_bootstrap" +} + +// Schema implements resource's Schema +func (r *BootstrapResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "name": schema.StringAttribute{ + Required: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + Description: "Name to give to the Property (must be unique)", + Validators: []validator.String{ + stringvalidator.LengthAtMost(85), + stringvalidator.RegexMatches(regexp.MustCompile(`^[A-Za-z0-9.\-_]+$`), + "a name must only contain letters, numbers, and these characters: . _ -"), + }, + }, + "group_id": schema.StringAttribute{ + Required: true, + Description: "Group ID to be assigned to the Property", + PlanModifiers: []planmodifier.String{ + modifiers.StringUseStateIf(modifiers.EqualUpToPrefixFunc("grp_")), + modifiers.PreventStringUpdate(), + }, + }, + "contract_id": schema.StringAttribute{ + Required: true, + Description: "Contract ID to be assigned to the Property", + PlanModifiers: []planmodifier.String{ + modifiers.StringUseStateIf(modifiers.EqualUpToPrefixFunc("ctr_")), + modifiers.PreventStringUpdate(), + }, + }, + "product_id": schema.StringAttribute{ + Required: true, + Description: "Product ID to be assigned to the Property", + PlanModifiers: []planmodifier.String{ + modifiers.StringUseStateIf(modifiers.EqualUpToPrefixFunc("prd_")), + modifiers.PreventStringUpdate(), + }, + }, + "id": schema.StringAttribute{ + Computed: true, + Description: "ID of the Property", + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + } +} + +// Configure implements resource.ResourceWithConfigure. +func (r *BootstrapResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + if r.client != nil { + return + } + + m, ok := req.ProviderData.(meta.Meta) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *http.Client, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + + r.client = papi.Client(m.Session()) +} + +// Create implements resource's Create method +func (r *BootstrapResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + tflog.Debug(ctx, "Creating Bootstrap Resource") + + var data *BootstrapResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + contractID := tools.AddPrefix(data.ContractID.ValueString(), "ctr_") + groupID := tools.AddPrefix(data.GroupID.ValueString(), "grp_") + productID := tools.AddPrefix(data.ProductID.ValueString(), "prd_") + + propertyID, err := createProperty(ctx, r.client, data.Name.ValueString(), groupID, contractID, productID, "") + if err != nil { + err = interpretCreatePropertyErrorFramework(ctx, err, r.client, groupID, contractID, productID) + if err != nil { + resp.Diagnostics.AddError(err.Error(), "") + return + } + } + + data.ID = types.StringValue(propertyID) + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} + +func interpretCreatePropertyErrorFramework(ctx context.Context, err error, client papi.PAPI, groupID string, contractID string, productID string) error { + if errors.Is(err, papi.ErrNotFound) { + if _, err = getGroup(ctx, client, groupID); err != nil { + if errors.Is(err, ErrGroupNotFound) { + return fmt.Errorf("%v: %s", ErrGroupNotFound, groupID) + } + return err + } + if _, err = getContract(ctx, client, contractID); err != nil { + if errors.Is(err, ErrContractNotFound) { + return fmt.Errorf("%v: %s", ErrContractNotFound, contractID) + } + return err + } + if _, err = getProduct(ctx, client, productID, contractID); err != nil { + if errors.Is(err, ErrProductNotFound) { + return fmt.Errorf("%v: %s", ErrProductNotFound, productID) + } + return err + } + } + return err +} + +// Read implements resource's Read method +func (r *BootstrapResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + tflog.Debug(ctx, "Reading Bootstrap Resource") + + var data *BootstrapResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + propertyID := data.ID.ValueString() + contractID := tools.AddPrefix(data.ContractID.ValueString(), "ctr_") + groupID := tools.AddPrefix(data.GroupID.ValueString(), "grp_") + + _, err := fetchLatestProperty(ctx, r.client, propertyID, groupID, contractID) + if errors.Is(err, papi.ErrNotFound) { + tflog.Warn(ctx, fmt.Sprintf("property %q removed on server. Removing from local state", propertyID)) + resp.State.RemoveResource(ctx) + return + } + if err != nil { + resp.Diagnostics.AddError(err.Error(), "") + } +} + +// Update of group, contract, product is noop, it will return an error before invoking Update. Updating name will result in resource replacement +func (r *BootstrapResource) Update(_ context.Context, _ resource.UpdateRequest, _ *resource.UpdateResponse) { +} + +// Delete implements resource's Delete method +func (r *BootstrapResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + tflog.Debug(ctx, "Deleting Property Bootstrap") + + var data *BootstrapResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &data)...) + if resp.Diagnostics.HasError() { + return + } + + propertyID := data.ID.ValueString() + contractID := tools.AddPrefix(data.ContractID.ValueString(), "ctr_") + groupID := tools.AddPrefix(data.GroupID.ValueString(), "grp_") + + if err := removeProperty(ctx, r.client, propertyID, groupID, contractID); err != nil { + resp.Diagnostics.AddError("removeProperty:", err.Error()) + } +} + +// ImportState implements resource's ImportState method +func (r *BootstrapResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + tflog.Debug(ctx, "Importing Property Bootstrap") + + var propertyID, groupID, contractID string + parts := strings.Split(req.ID, ",") + + switch len(parts) { + case 3: + propertyID = tools.AddPrefix(parts[0], "prp_") + contractID = tools.AddPrefix(parts[1], "ctr_") + groupID = tools.AddPrefix(parts[2], "grp_") + case 2: + resp.Diagnostics.AddError("missing group id or contract id", "") + return + case 1: + propertyID = tools.AddPrefix(parts[0], "prp_") + default: + resp.Diagnostics.AddError(fmt.Sprintf("invalid property identifier: %s", req.ID), "") + return + } + + property, err := fetchLatestProperty(ctx, r.client, propertyID, groupID, contractID) + if err != nil { + resp.Diagnostics.AddError(err.Error(), "") + return + } + + res, err := fetchPropertyVersion(ctx, r.client, property.PropertyID, property.GroupID, property.ContractID, property.LatestVersion) + if err != nil { + resp.Diagnostics.AddError(err.Error(), "") + return + } + + data := BootstrapResourceModel{ + ProductID: types.StringValue(res.Version.ProductID), + ContractID: types.StringValue(property.ContractID), + GroupID: types.StringValue(property.GroupID), + Name: types.StringValue(property.PropertyName), + ID: types.StringValue(propertyID), + } + + resp.Diagnostics.Append(resp.State.Set(ctx, data)...) +} diff --git a/pkg/providers/property/resource_akamai_property_bootstrap_test.go b/pkg/providers/property/resource_akamai_property_bootstrap_test.go new file mode 100644 index 000000000..c7a5a7f70 --- /dev/null +++ b/pkg/providers/property/resource_akamai_property_bootstrap_test.go @@ -0,0 +1,420 @@ +package property + +import ( + "fmt" + "regexp" + "strings" + "testing" + + "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/papi" + "github.com/akamai/terraform-provider-akamai/v5/pkg/common/testutils" + "github.com/akamai/terraform-provider-akamai/v5/pkg/tools" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +type testDataForPropertyBootstrap struct { + propertyID string + name string + groupID string + contractID string + productID string + withoutPrefixes bool +} + +func TestBootstrapResourceCreate(t *testing.T) { + t.Parallel() + tests := map[string]struct { + configPath string + init func(*testing.T, *papi.Mock, testDataForPropertyBootstrap) + mockData testDataForPropertyBootstrap + error *regexp.Regexp + }{ + "create": { + configPath: "testdata/TestResPropertyBootstrap/create.tf", + init: func(t *testing.T, m *papi.Mock, data testDataForPropertyBootstrap) { + ExpectCreateProperty(m, data.name, data.groupID, data.contractID, data.productID, data.propertyID) + prp := &papi.Property{ + ContractID: "ctr_2", + GroupID: "grp_1", + ProductID: "prd_3", + PropertyID: "prp_123", + PropertyName: "property_name", + } + ExpectGetProperty(m, data.propertyID, data.groupID, data.contractID, prp) + ExpectRemoveProperty(m, data.propertyID, data.contractID, data.groupID) + }, + mockData: testDataForPropertyBootstrap{ + propertyID: "prp_123", + name: "property_name", + groupID: "grp_1", + contractID: "ctr_2", + productID: "prd_3", + }, + }, + "create without prefixes": { + configPath: "testdata/TestResPropertyBootstrap/create_without_prefixes.tf", + init: func(t *testing.T, m *papi.Mock, data testDataForPropertyBootstrap) { + ExpectCreateProperty(m, data.name, data.groupID, data.contractID, data.productID, data.propertyID) + prp := &papi.Property{ + ContractID: "ctr_2", + GroupID: "grp_1", + ProductID: "prd_3", + PropertyID: "prp_123", + PropertyName: "property_name", + } + ExpectGetProperty(m, data.propertyID, data.groupID, data.contractID, prp) + ExpectRemoveProperty(m, data.propertyID, data.contractID, data.groupID) + }, + mockData: testDataForPropertyBootstrap{ + propertyID: "prp_123", + name: "property_name", + groupID: "grp_1", + contractID: "ctr_2", + productID: "prd_3", + withoutPrefixes: true, + }, + }, + "create with interpretCreate error - group not found": { + configPath: "testdata/TestResPropertyBootstrap/create.tf", + init: func(t *testing.T, m *papi.Mock, data testDataForPropertyBootstrap) { + req := papi.CreatePropertyRequest{ + GroupID: data.groupID, + ContractID: data.contractID, + Property: papi.PropertyCreate{ + ProductID: data.productID, + PropertyName: data.name, + }, + } + m.On("CreateProperty", AnyCTX, req).Return(nil, fmt.Errorf( + "%s: %w: %s", papi.ErrCreateProperty, papi.ErrNotFound, "not found")).Once() + // mock empty groups - no group has been found, hence the expected error + m.On("GetGroups", AnyCTX).Return(&papi.GetGroupsResponse{ + Groups: papi.GroupItems{ + Items: []*papi.Group{}, + }, + }, nil).Once() + }, + mockData: testDataForPropertyBootstrap{ + propertyID: "prp_123", + name: "property_name", + groupID: "grp_1", + contractID: "ctr_2", + productID: "prd_3", + }, + error: regexp.MustCompile(`Error: group not found: grp_1`), + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + t.Parallel() + + m := &papi.Mock{} + if test.init != nil { + test.init(t, m, test.mockData) + } + + useClient(m, nil, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: newProviderFactory(withMockClient(m)), + IsUnitTest: true, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, test.configPath), + Check: checkPropertyBootstrapAttributes(test.mockData), + ExpectError: test.error, + }, + }, + }) + }) + + m.AssertExpectations(t) + }) + } +} + +func TestBootstrapResourceUpdate(t *testing.T) { + t.Parallel() + tests := map[string]struct { + configPathForCreate string + configPathForUpdate string + init func(*testing.T, *papi.Mock, testDataForPropertyBootstrap) + mockData testDataForPropertyBootstrap + errorForCreate *regexp.Regexp + errorForUpdate *regexp.Regexp + }{ + "create and remove prefixes - no diff": { + configPathForCreate: "testdata/TestResPropertyBootstrap/create.tf", + configPathForUpdate: "testdata/TestResPropertyBootstrap/create_without_prefixes.tf", + init: func(t *testing.T, m *papi.Mock, data testDataForPropertyBootstrap) { + ExpectCreateProperty(m, data.name, data.groupID, data.contractID, data.productID, data.propertyID) + prp := &papi.Property{ + ContractID: "ctr_2", + GroupID: "grp_1", + ProductID: "prd_3", + PropertyID: "prp_123", + PropertyName: "property_name", + } + ExpectGetProperty(m, data.propertyID, data.groupID, data.contractID, prp) + ExpectRemoveProperty(m, data.propertyID, data.contractID, data.groupID) + }, + mockData: testDataForPropertyBootstrap{ + propertyID: "prp_123", + name: "property_name", + groupID: "grp_1", + contractID: "ctr_2", + productID: "prd_3", + }, + }, + "create and update group - error": { + configPathForCreate: "testdata/TestResPropertyBootstrap/create.tf", + configPathForUpdate: "testdata/TestResPropertyBootstrap/update_group.tf", + init: func(t *testing.T, m *papi.Mock, data testDataForPropertyBootstrap) { + ExpectCreateProperty(m, data.name, data.groupID, data.contractID, data.productID, data.propertyID) + prp := &papi.Property{ + ContractID: "ctr_2", + GroupID: "grp_1", + ProductID: "prd_3", + PropertyID: "prp_123", + PropertyName: "property_name", + } + ExpectGetProperty(m, data.propertyID, data.groupID, data.contractID, prp) + ExpectRemoveProperty(m, data.propertyID, data.contractID, data.groupID) + }, + mockData: testDataForPropertyBootstrap{ + propertyID: "prp_123", + name: "property_name", + groupID: "grp_1", + contractID: "ctr_2", + productID: "prd_3", + }, + errorForUpdate: regexp.MustCompile("updating field `group_id` is not possible"), + }, + "create and update contract - error": { + configPathForCreate: "testdata/TestResPropertyBootstrap/create.tf", + configPathForUpdate: "testdata/TestResPropertyBootstrap/update_contract.tf", + init: func(t *testing.T, m *papi.Mock, data testDataForPropertyBootstrap) { + ExpectCreateProperty(m, data.name, data.groupID, data.contractID, data.productID, data.propertyID) + prp := &papi.Property{ + ContractID: "ctr_2", + GroupID: "grp_1", + ProductID: "prd_3", + PropertyID: "prp_123", + PropertyName: "property_name", + } + ExpectGetProperty(m, data.propertyID, data.groupID, data.contractID, prp) + ExpectRemoveProperty(m, data.propertyID, data.contractID, data.groupID) + }, + mockData: testDataForPropertyBootstrap{ + propertyID: "prp_123", + name: "property_name", + groupID: "grp_1", + contractID: "ctr_2", + productID: "prd_3", + }, + errorForUpdate: regexp.MustCompile("updating field `contract_id` is not possible"), + }, + "create and update product - error": { + configPathForCreate: "testdata/TestResPropertyBootstrap/create.tf", + configPathForUpdate: "testdata/TestResPropertyBootstrap/update_product.tf", + init: func(t *testing.T, m *papi.Mock, data testDataForPropertyBootstrap) { + ExpectCreateProperty(m, data.name, data.groupID, data.contractID, data.productID, data.propertyID) + prp := &papi.Property{ + ContractID: "ctr_2", + GroupID: "grp_1", + ProductID: "prd_3", + PropertyID: "prp_123", + PropertyName: "property_name", + } + ExpectGetProperty(m, data.propertyID, data.groupID, data.contractID, prp) + ExpectRemoveProperty(m, data.propertyID, data.contractID, data.groupID) + }, + mockData: testDataForPropertyBootstrap{ + propertyID: "prp_123", + name: "property_name", + groupID: "grp_1", + contractID: "ctr_2", + productID: "prd_3", + }, + errorForUpdate: regexp.MustCompile("updating field `product_id` is not possible"), + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + t.Parallel() + + m := &papi.Mock{} + if test.init != nil { + test.init(t, m, test.mockData) + } + + useClient(m, nil, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: newProviderFactory(withMockClient(m)), + IsUnitTest: true, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, test.configPathForCreate), + Check: checkPropertyBootstrapAttributes(test.mockData), + ExpectError: test.errorForCreate, + }, + { + Config: testutils.LoadFixtureString(t, test.configPathForUpdate), + PlanOnly: true, + ExpectNonEmptyPlan: false, + ExpectError: test.errorForUpdate, + }, + }, + }) + }) + + m.AssertExpectations(t) + }) + } +} + +func TestBootstrapResourceImport(t *testing.T) { + t.Parallel() + tests := map[string]struct { + configPath string + init func(*testing.T, *papi.Mock, testDataForPropertyBootstrap) + mockData testDataForPropertyBootstrap + importStateID string + error *regexp.Regexp + }{ + "import with all attributes": { + configPath: "testdata/TestResPropertyBootstrap/create.tf", + init: func(t *testing.T, m *papi.Mock, data testDataForPropertyBootstrap) { + ExpectCreateProperty(m, data.name, data.groupID, data.contractID, data.productID, data.propertyID) + prp := &papi.Property{ + ContractID: "ctr_2", + GroupID: "grp_1", + ProductID: "prd_3", + PropertyID: "prp_123", + PropertyName: "property_name", + LatestVersion: 1, + } + ExpectGetProperty(m, data.propertyID, data.groupID, data.contractID, prp) + ExpectRemoveProperty(m, data.propertyID, data.contractID, data.groupID) + // import + ExpectGetProperty(m, data.propertyID, data.groupID, data.contractID, prp) + ExpectGetPropertyVersion(m, data.propertyID, data.groupID, data.contractID, 1, papi.VersionStatusActive, papi.VersionStatusActive) + }, + mockData: testDataForPropertyBootstrap{ + propertyID: "prp_123", + name: "property_name", + groupID: "grp_1", + contractID: "ctr_2", + productID: "prd_3", + }, + importStateID: "prp_123,2,1", + }, + "import with only property_id": { + configPath: "testdata/TestResPropertyBootstrap/create.tf", + init: func(t *testing.T, m *papi.Mock, data testDataForPropertyBootstrap) { + ExpectCreateProperty(m, data.name, data.groupID, data.contractID, data.productID, data.propertyID) + prp := &papi.Property{ + ContractID: "ctr_2", + GroupID: "grp_1", + ProductID: "prd_3", + PropertyID: "prp_123", + PropertyName: "property_name", + LatestVersion: 1, + } + ExpectGetProperty(m, data.propertyID, data.groupID, data.contractID, prp) + ExpectRemoveProperty(m, data.propertyID, data.contractID, data.groupID) + // import + ExpectGetProperty(m, data.propertyID, "", "", prp) + ExpectGetPropertyVersion(m, data.propertyID, data.groupID, data.contractID, 1, papi.VersionStatusActive, papi.VersionStatusActive) + }, + mockData: testDataForPropertyBootstrap{ + propertyID: "prp_123", + name: "property_name", + groupID: "grp_1", + contractID: "ctr_2", + productID: "prd_3", + }, + importStateID: "123", + }, + "import with only property_id and contract_id - error": { + configPath: "testdata/TestResPropertyBootstrap/create.tf", + init: func(t *testing.T, m *papi.Mock, data testDataForPropertyBootstrap) { + ExpectCreateProperty(m, data.name, data.groupID, data.contractID, data.productID, data.propertyID) + prp := &papi.Property{ + ContractID: "ctr_2", + GroupID: "grp_1", + ProductID: "prd_3", + PropertyID: "prp_123", + PropertyName: "property_name", + LatestVersion: 1, + } + ExpectGetProperty(m, data.propertyID, data.groupID, data.contractID, prp) + ExpectRemoveProperty(m, data.propertyID, data.contractID, data.groupID) + }, + mockData: testDataForPropertyBootstrap{ + propertyID: "prp_123", + name: "property_name", + groupID: "grp_1", + contractID: "ctr_2", + productID: "prd_3", + }, + importStateID: "123,2", + error: regexp.MustCompile("Error: missing group id or contract id"), + }, + } + + for name, test := range tests { + name, test := name, test + t.Run(name, func(t *testing.T) { + t.Parallel() + + m := &papi.Mock{} + if test.init != nil { + test.init(t, m, test.mockData) + } + + useClient(m, nil, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: newProviderFactory(withMockClient(m)), + IsUnitTest: true, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "testdata/TestResPropertyBootstrap/create.tf"), + Check: checkPropertyBootstrapAttributes(test.mockData), + }, + { + ImportState: true, + ImportStateId: test.importStateID, + ResourceName: "akamai_property_bootstrap.test", + Check: checkPropertyBootstrapAttributes(test.mockData), + ExpectError: test.error, + }, + }, + }) + }) + + m.AssertExpectations(t) + }) + } +} + +func checkPropertyBootstrapAttributes(data testDataForPropertyBootstrap) resource.TestCheckFunc { + if data.withoutPrefixes { + return resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("akamai_property_bootstrap.test", "id", data.propertyID), + resource.TestCheckResourceAttr("akamai_property_bootstrap.test", "group_id", strings.TrimPrefix(data.groupID, "grp_")), + resource.TestCheckResourceAttr("akamai_property_bootstrap.test", "contract_id", strings.TrimPrefix(data.contractID, "ctr_")), + resource.TestCheckResourceAttr("akamai_property_bootstrap.test", "product_id", strings.TrimPrefix(data.productID, "prd_")), + resource.TestCheckResourceAttr("akamai_property_bootstrap.test", "name", data.name)) + } + return resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("akamai_property_bootstrap.test", "id", tools.AddPrefix(data.propertyID, "prp_")), + resource.TestCheckResourceAttr("akamai_property_bootstrap.test", "group_id", tools.AddPrefix(data.groupID, "grp_")), + resource.TestCheckResourceAttr("akamai_property_bootstrap.test", "contract_id", tools.AddPrefix(data.contractID, "ctr_")), + resource.TestCheckResourceAttr("akamai_property_bootstrap.test", "product_id", tools.AddPrefix(data.productID, "prd_")), + resource.TestCheckResourceAttr("akamai_property_bootstrap.test", "name", data.name), + ) +} diff --git a/pkg/providers/property/resource_akamai_property_common.go b/pkg/providers/property/resource_akamai_property_common.go index ddf9806fd..6a4bb938f 100644 --- a/pkg/providers/property/resource_akamai_property_common.go +++ b/pkg/providers/property/resource_akamai_property_common.go @@ -8,14 +8,15 @@ import ( "strconv" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/papi" + "github.com/akamai/terraform-provider-akamai/v5/pkg/logger" "github.com/akamai/terraform-provider-akamai/v5/pkg/meta" "github.com/akamai/terraform-provider-akamai/v5/pkg/tools" ) -func getGroup(ctx context.Context, meta meta.Meta, groupID string) (*papi.Group, error) { - logger := meta.Log("PAPI", "getGroup") - client := Client(meta) - logger.Debugf("Fetching groups") +func getGroup(ctx context.Context, client papi.PAPI, groupID string) (*papi.Group, error) { + log := logger.FromContext(ctx) + log.Debugf("Fetching groups") + res, err := client.GetGroups(ctx) if err != nil { return nil, fmt.Errorf("%w: %s", ErrFetchingGroups, err.Error()) @@ -34,14 +35,14 @@ func getGroup(ctx context.Context, meta meta.Meta, groupID string) (*papi.Group, if !groupFound { return nil, fmt.Errorf("%w: %s", ErrGroupNotFound, groupID) } - logger.Debugf("Group found: %s", group.GroupID) + log.Debugf("Group found: %s", group.GroupID) return group, nil } -func getContract(ctx context.Context, meta meta.Meta, contractID string) (*papi.Contract, error) { - logger := meta.Log("PAPI", "getContract") - client := Client(meta) - logger.Debugf("Fetching contract") +func getContract(ctx context.Context, client papi.PAPI, contractID string) (*papi.Contract, error) { + log := logger.FromContext(ctx) + log.Debugf("Fetching contract") + res, err := client.GetContracts(ctx) if err != nil { return nil, fmt.Errorf("%w: %s", ErrFetchingContracts, err.Error()) @@ -59,17 +60,18 @@ func getContract(ctx context.Context, meta meta.Meta, contractID string) (*papi. return nil, fmt.Errorf("%w: %s", ErrContractNotFound, contractID) } - logger.Debugf("Contract found: %s", contract.ContractID) + log.Debugf("Contract found: %s", contract.ContractID) return contract, nil } -func getProduct(ctx context.Context, meta meta.Meta, productID, contractID string) (*papi.ProductItem, error) { - logger := meta.Log("PAPI", "getProduct") - client := Client(meta) +func getProduct(ctx context.Context, client papi.PAPI, productID, contractID string) (*papi.ProductItem, error) { if contractID == "" { return nil, ErrNoContractProvided } - logger.Debugf("Fetching product") + + log := logger.FromContext(ctx) + log.Debugf("Fetching product") + res, err := client.GetProducts(ctx, papi.GetProductsRequest{ContractID: contractID}) if err != nil { return nil, fmt.Errorf("%w: %s", ErrProductFetch, err.Error()) @@ -87,7 +89,7 @@ func getProduct(ctx context.Context, meta meta.Meta, productID, contractID strin return nil, fmt.Errorf("%w: %s", ErrProductNotFound, productID) } - logger.Debugf("Product found: %s", product.ProductID) + log.Debugf("Product found: %s", product.ProductID) return &product, nil } diff --git a/pkg/providers/property/resource_akamai_property_helpers_test.go b/pkg/providers/property/resource_akamai_property_helpers_test.go index 6f3a59969..2d61c4d2f 100644 --- a/pkg/providers/property/resource_akamai_property_helpers_test.go +++ b/pkg/providers/property/resource_akamai_property_helpers_test.go @@ -217,7 +217,8 @@ func ExpectRemoveProperty(client *papi.Mock, PropertyID, ContractID, GroupID str // Sets up an expected call to papi.GetRuleTree() which returns a value depending on the value of the // pointer to State and FormatState. -func ExpectGetRuleTree(client *papi.Mock, PropertyID, GroupID, ContractID string, PropertyVersion int, State *papi.RulesUpdate, RuleFormatState *string) *mock.Call { +func ExpectGetRuleTree(client *papi.Mock, PropertyID, GroupID, ContractID string, PropertyVersion int, State *papi.RulesUpdate, RuleFormatState *string, + Errors []*papi.Error, Warnings []*papi.Error) *mock.Call { req := papi.GetRuleTreeRequest{ PropertyID: PropertyID, GroupID: GroupID, @@ -234,6 +235,10 @@ func ExpectGetRuleTree(client *papi.Mock, PropertyID, GroupID, ContractID string } res := papi.GetRuleTreeResponse{ + Response: papi.Response{ + Errors: Errors, + Warnings: Warnings, + }, PropertyID: PropertyID, PropertyVersion: PropertyVersion, RuleFormat: *RuleFormatState, diff --git a/pkg/providers/property/resource_akamai_property_include.go b/pkg/providers/property/resource_akamai_property_include.go index a966f8e35..8e969ee2f 100644 --- a/pkg/providers/property/resource_akamai_property_include.go +++ b/pkg/providers/property/resource_akamai_property_include.go @@ -31,7 +31,7 @@ func resourcePropertyInclude() *schema.Resource { StateContext: resourcePropertyIncludeImport, }, CustomizeDiff: customdiff.All( - rulesCustomDiff, + propertyIncludeRulesCustomDiff, setIncludeVersionsComputedOnRulesChange, ), Schema: map[string]*schema.Schema{ @@ -57,10 +57,11 @@ func resourcePropertyInclude() *schema.Resource { StateFunc: addPrefixToState("prd_"), }, "name": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - Description: "A descriptive name for the include", + Type: schema.TypeString, + Required: true, + ForceNew: true, + ValidateDiagFunc: validateNameWithBound(3), + Description: "A descriptive name for the include", }, "rule_format": { Type: schema.TypeString, @@ -81,7 +82,7 @@ func resourcePropertyInclude() *schema.Resource { Computed: true, Description: "Property Rules as JSON", ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsJSON), - DiffSuppressFunc: tf.ComposeDiffSuppress(suppressDefaultRules, diffSuppressRules), + DiffSuppressFunc: tf.DiffSuppressAny(suppressDefaultRules, diffSuppressPropertyRules), StateFunc: rulesStateFunc, }, "rule_errors": { @@ -113,6 +114,57 @@ func resourcePropertyInclude() *schema.Resource { } } +// propertyIncludeRulesCustomDiff compares Rules.Criteria and Rules.Children fields from terraform state +// and from a new configuration. If some of these fields are empty lists in the new configuration and +// are nil in the terraform state, then this function returns no difference for these fields. +// +// TODO: reuse propertyRulesCustomDiff when version_notes attr is added to akamai_property_include resource. +func propertyIncludeRulesCustomDiff(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { + o, n := diff.GetChange("rules") + oldValue, newValue := o.(string), n.(string) + + handleCreate := diff.Id() == "" && newValue != "" + if !handleCreate && (oldValue == "" || newValue == "") { + return nil + } + + var newRulesUpdate papi.RulesUpdate + if err := json.Unmarshal([]byte(newValue), &newRulesUpdate); err != nil { + return fmt.Errorf("cannot parse rules JSON from config: %s", err) + } + + if handleCreate { + rules, err := unifyRulesDiff(newRulesUpdate) + if err != nil { + return err + } + if err = diff.SetNew("rules", rules); err != nil { + return fmt.Errorf("cannot set a new diff value for 'rules' %s", err) + } + return nil + } + + var oldRulesUpdate papi.RulesUpdate + if err := json.Unmarshal([]byte(oldValue), &oldRulesUpdate); err != nil { + return fmt.Errorf("cannot parse rules JSON from state: %s", err) + } + + normalizeFields(&oldRulesUpdate, &newRulesUpdate) + if rulesEqual(&oldRulesUpdate.Rules, &newRulesUpdate.Rules) && oldRulesUpdate.Comments == newRulesUpdate.Comments { + return nil + } + + rules, err := json.Marshal(newRulesUpdate) + if err != nil { + return fmt.Errorf("cannot encode rules JSON %s", err) + } + + if err = diff.SetNew("rules", string(rules)); err != nil { + return fmt.Errorf("cannot set a new diff value for 'rules' %s", err) + } + return nil +} + func resourcePropertyIncludeCreate(ctx context.Context, rd *schema.ResourceData, m interface{}) diag.Diagnostics { meta := meta.Must(m) logger := meta.Log("PAPI", "resourcePropertyIncludeCreate") diff --git a/pkg/providers/property/resource_akamai_property_include_activation_test.go b/pkg/providers/property/resource_akamai_property_include_activation_test.go index 85bd9c2c4..f60720dbc 100644 --- a/pkg/providers/property/resource_akamai_property_include_activation_test.go +++ b/pkg/providers/property/resource_akamai_property_include_activation_test.go @@ -27,7 +27,7 @@ var ( ) func TestResourcePropertyIncludeActivation(t *testing.T) { - t.Skip() + // lower down the timeouts for testing purposes activationPollInterval = time.Microsecond getActivationInterval = time.Microsecond @@ -834,7 +834,6 @@ func TestResourcePropertyIncludeActivation(t *testing.T) { } func TestReadTimeoutFromEnvOrDefault(t *testing.T) { - t.Skip() tests := map[string]struct { envName string envValue string diff --git a/pkg/providers/property/resource_akamai_property_include_test.go b/pkg/providers/property/resource_akamai_property_include_test.go index 9875ace5e..1b9d1bce6 100644 --- a/pkg/providers/property/resource_akamai_property_include_test.go +++ b/pkg/providers/property/resource_akamai_property_include_test.go @@ -5,19 +5,21 @@ import ( "fmt" "path" "regexp" + "strings" "testing" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/hapi" "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/papi" "github.com/akamai/terraform-provider-akamai/v5/pkg/common/testutils" "github.com/akamai/terraform-provider-akamai/v5/pkg/test" + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) func TestResourcePropertyInclude(t *testing.T) { - t.Skip() type testData struct { groupID string rulesPath string @@ -297,7 +299,7 @@ func TestResourcePropertyInclude(t *testing.T) { includeID: includeID, ruleFormat: "v2022-06-28", contractID: "ctr_123", - includeName: "test include", + includeName: "test_include", includeType: papi.IncludeTypeMicroServices, }, init: func(m *papi.Mock, testData *testData) { @@ -312,7 +314,7 @@ func TestResourcePropertyInclude(t *testing.T) { resource.TestCheckResourceAttr("akamai_property_include.test", "group_id", "grp_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "contract_id", "ctr_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "product_id", "prd_test"), - resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test include"), + resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test_include"), resource.TestCheckResourceAttr("akamai_property_include.test", "rule_format", "v2022-06-28"), resource.TestCheckResourceAttr("akamai_property_include.test", "type", "MICROSERVICES"), resource.TestCheckResourceAttr("akamai_property_include.test", "latest_version", "1"), @@ -333,7 +335,7 @@ func TestResourcePropertyInclude(t *testing.T) { includeID: includeID, ruleFormat: "v2022-06-28", contractID: "ctr_123", - includeName: "test include", + includeName: "test_include", includeType: papi.IncludeTypeMicroServices, rules: simpleRules, }, @@ -349,7 +351,7 @@ func TestResourcePropertyInclude(t *testing.T) { resource.TestCheckResourceAttr("akamai_property_include.test", "group_id", "grp_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "contract_id", "ctr_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "product_id", "prd_test"), - resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test include"), + resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test_include"), resource.TestCheckResourceAttr("akamai_property_include.test", "rule_format", "v2022-06-28"), resource.TestCheckResourceAttr("akamai_property_include.test", "type", "MICROSERVICES"), resource.TestCheckResourceAttr("akamai_property_include.test", "latest_version", "1"), @@ -370,7 +372,7 @@ func TestResourcePropertyInclude(t *testing.T) { includeID: includeID, ruleFormat: "v2022-06-28", contractID: "ctr_123", - includeName: "test include", + includeName: "test_include", includeType: papi.IncludeTypeMicroServices, rules: simpleRulesWithComment, }, @@ -386,7 +388,7 @@ func TestResourcePropertyInclude(t *testing.T) { resource.TestCheckResourceAttr("akamai_property_include.test", "group_id", "grp_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "contract_id", "ctr_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "product_id", "prd_test"), - resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test include"), + resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test_include"), resource.TestCheckResourceAttr("akamai_property_include.test", "rule_format", "v2022-06-28"), resource.TestCheckResourceAttr("akamai_property_include.test", "type", "MICROSERVICES"), resource.TestCheckResourceAttr("akamai_property_include.test", "latest_version", "1"), @@ -407,7 +409,7 @@ func TestResourcePropertyInclude(t *testing.T) { includeID: includeID, ruleFormat: "v2022-06-28", contractID: "ctr_123", - includeName: "test include", + includeName: "test_include", includeType: papi.IncludeTypeMicroServices, rules: simpleRules, }, @@ -424,7 +426,7 @@ func TestResourcePropertyInclude(t *testing.T) { resource.TestCheckResourceAttr("akamai_property_include.test", "group_id", "grp_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "contract_id", "ctr_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "product_id", "prd_test"), - resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test include"), + resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test_include"), resource.TestCheckResourceAttr("akamai_property_include.test", "rule_format", "v2022-06-28"), resource.TestCheckResourceAttr("akamai_property_include.test", "type", "MICROSERVICES"), resource.TestCheckResourceAttr("akamai_property_include.test", "latest_version", "1"), @@ -444,7 +446,7 @@ func TestResourcePropertyInclude(t *testing.T) { includeID: includeID, ruleFormat: "v2022-06-28", contractID: "ctr_123", - includeName: "test include", + includeName: "test_include", includeType: papi.IncludeTypeMicroServices, rules: simpleRules, }, @@ -461,7 +463,7 @@ func TestResourcePropertyInclude(t *testing.T) { resource.TestCheckResourceAttr("akamai_property_include.test", "group_id", "grp_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "contract_id", "ctr_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "product_id", "prd_test"), - resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test include"), + resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test_include"), resource.TestCheckResourceAttr("akamai_property_include.test", "rule_format", "v2022-06-28"), resource.TestCheckResourceAttr("akamai_property_include.test", "type", "MICROSERVICES"), resource.TestCheckResourceAttr("akamai_property_include.test", "latest_version", "1"), @@ -482,7 +484,7 @@ func TestResourcePropertyInclude(t *testing.T) { includeID: includeID, ruleFormat: "v2022-06-28", contractID: "ctr_123", - includeName: "test include", + includeName: "test_include", includeType: papi.IncludeTypeMicroServices, rules: simpleRules, }, @@ -499,7 +501,7 @@ func TestResourcePropertyInclude(t *testing.T) { resource.TestCheckResourceAttr("akamai_property_include.test", "group_id", "grp_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "contract_id", "ctr_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "product_id", "prd_test"), - resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test include"), + resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test_include"), resource.TestCheckResourceAttr("akamai_property_include.test", "rule_format", "v2022-06-28"), resource.TestCheckResourceAttr("akamai_property_include.test", "type", "MICROSERVICES"), resource.TestCheckResourceAttr("akamai_property_include.test", "latest_version", "1"), @@ -520,7 +522,7 @@ func TestResourcePropertyInclude(t *testing.T) { includeID: includeID, ruleFormat: "v2022-06-28", contractID: "ctr_123", - includeName: "test include", + includeName: "test_include", includeType: papi.IncludeTypeMicroServices, rules: simpleRules, }, @@ -542,7 +544,7 @@ func TestResourcePropertyInclude(t *testing.T) { resource.TestCheckResourceAttr("akamai_property_include.test", "group_id", "grp_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "contract_id", "ctr_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "product_id", "prd_test"), - resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test include"), + resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test_include"), resource.TestCheckResourceAttr("akamai_property_include.test", "rule_format", "v2022-06-28"), resource.TestCheckResourceAttr("akamai_property_include.test", "type", "MICROSERVICES"), resource.TestCheckResourceAttr("akamai_property_include.test", "latest_version", "1"), @@ -559,7 +561,7 @@ func TestResourcePropertyInclude(t *testing.T) { resource.TestCheckResourceAttr("akamai_property_include.test", "group_id", "grp_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "contract_id", "ctr_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "product_id", "prd_test"), - resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test include"), + resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test_include"), resource.TestCheckResourceAttr("akamai_property_include.test", "rule_format", "v2022-06-28"), resource.TestCheckResourceAttr("akamai_property_include.test", "type", "MICROSERVICES"), resource.TestCheckResourceAttr("akamai_property_include.test", "latest_version", "1"), @@ -580,7 +582,7 @@ func TestResourcePropertyInclude(t *testing.T) { includeID: includeID, ruleFormat: "v2022-06-28", contractID: "ctr_123", - includeName: "test include", + includeName: "test_include", includeType: papi.IncludeTypeMicroServices, rules: simpleRules, }, @@ -602,7 +604,7 @@ func TestResourcePropertyInclude(t *testing.T) { resource.TestCheckResourceAttr("akamai_property_include.test", "group_id", "grp_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "contract_id", "ctr_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "product_id", "prd_test"), - resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test include"), + resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test_include"), resource.TestCheckResourceAttr("akamai_property_include.test", "rule_format", "v2022-06-28"), resource.TestCheckResourceAttr("akamai_property_include.test", "type", "MICROSERVICES"), resource.TestCheckResourceAttr("akamai_property_include.test", "latest_version", "1"), @@ -619,7 +621,7 @@ func TestResourcePropertyInclude(t *testing.T) { resource.TestCheckResourceAttr("akamai_property_include.test", "group_id", "grp_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "contract_id", "ctr_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "product_id", "prd_test"), - resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test include"), + resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test_include"), resource.TestCheckResourceAttr("akamai_property_include.test", "rule_format", "v2022-06-28"), resource.TestCheckResourceAttr("akamai_property_include.test", "type", "MICROSERVICES"), resource.TestCheckResourceAttr("akamai_property_include.test", "latest_version", "1"), @@ -639,7 +641,7 @@ func TestResourcePropertyInclude(t *testing.T) { includeID: includeID, ruleFormat: "v2022-06-28", contractID: "ctr_123", - includeName: "test include", + includeName: "test_include", includeType: papi.IncludeTypeMicroServices, stagingStatus: papi.VersionStatusInactive, productionStatus: papi.VersionStatusInactive, @@ -664,7 +666,7 @@ func TestResourcePropertyInclude(t *testing.T) { resource.TestCheckResourceAttr("akamai_property_include.test", "group_id", "grp_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "contract_id", "ctr_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "product_id", "prd_test"), - resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test include"), + resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test_include"), resource.TestCheckResourceAttr("akamai_property_include.test", "rule_format", "v2022-06-28"), resource.TestCheckResourceAttr("akamai_property_include.test", "type", "MICROSERVICES"), resource.TestCheckResourceAttr("akamai_property_include.test", "latest_version", "1"), @@ -681,7 +683,7 @@ func TestResourcePropertyInclude(t *testing.T) { resource.TestCheckResourceAttr("akamai_property_include.test", "group_id", "grp_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "contract_id", "ctr_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "product_id", "prd_test"), - resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test include"), + resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test_include"), resource.TestCheckResourceAttr("akamai_property_include.test", "rule_format", "v2022-06-28"), resource.TestCheckResourceAttr("akamai_property_include.test", "type", "MICROSERVICES"), resource.TestCheckResourceAttr("akamai_property_include.test", "latest_version", "1"), @@ -701,7 +703,7 @@ func TestResourcePropertyInclude(t *testing.T) { includeID: includeID, ruleFormat: "v2022-06-28", contractID: "ctr_123", - includeName: "test include", + includeName: "test_include", includeType: papi.IncludeTypeMicroServices, stagingStatus: papi.VersionStatusInactive, productionStatus: papi.VersionStatusInactive, @@ -731,7 +733,7 @@ func TestResourcePropertyInclude(t *testing.T) { resource.TestCheckResourceAttr("akamai_property_include.test", "group_id", "grp_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "contract_id", "ctr_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "product_id", "prd_test"), - resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test include"), + resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test_include"), resource.TestCheckResourceAttr("akamai_property_include.test", "rule_format", "v2022-06-28"), resource.TestCheckResourceAttr("akamai_property_include.test", "type", "MICROSERVICES"), resource.TestCheckResourceAttr("akamai_property_include.test", "latest_version", "1"), @@ -748,7 +750,7 @@ func TestResourcePropertyInclude(t *testing.T) { resource.TestCheckResourceAttr("akamai_property_include.test", "group_id", "grp_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "contract_id", "ctr_123"), resource.TestCheckResourceAttr("akamai_property_include.test", "product_id", "prd_test"), - resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test include"), + resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test_include"), resource.TestCheckResourceAttr("akamai_property_include.test", "rule_format", "v2022-06-28"), resource.TestCheckResourceAttr("akamai_property_include.test", "type", "MICROSERVICES"), resource.TestCheckResourceAttr("akamai_property_include.test", "latest_version", "2"), @@ -768,7 +770,7 @@ func TestResourcePropertyInclude(t *testing.T) { includeID: includeID, ruleFormat: "v2022-06-28", contractID: "ctr_123", - includeName: "test include", + includeName: "test_include", includeType: papi.IncludeTypeMicroServices, stagingStatus: papi.VersionStatusInactive, productionStatus: papi.VersionStatusInactive, @@ -837,7 +839,7 @@ func TestResourcePropertyInclude(t *testing.T) { includeID: includeID, ruleFormat: "v2023-01-05", contractID: "ctr_123", - includeName: "test include", + includeName: "test_include", includeType: papi.IncludeTypeMicroServices, rules: nullRules, }, @@ -850,7 +852,7 @@ func TestResourcePropertyInclude(t *testing.T) { { Config: testutils.LoadFixtureString(t, "%s/property_include_null_cpcode.tf", workdir), Check: resource.ComposeAggregateTestCheckFunc( - resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test include"), + resource.TestCheckResourceAttr("akamai_property_include.test", "name", "test_include"), resource.TestCheckResourceAttr("akamai_property_include.test", "rules", testutils.LoadFixtureString(t, "%s/expected/rules_cpcode_null.json", workdir)), ), }, @@ -864,7 +866,7 @@ func TestResourcePropertyInclude(t *testing.T) { includeID: includeID, ruleFormat: "v2022-06-28", contractID: "ctr_123", - includeName: "test include", + includeName: "test_include", includeType: papi.IncludeTypeMicroServices, rules: simpleRules, }, @@ -894,7 +896,7 @@ func TestResourcePropertyInclude(t *testing.T) { includeID: includeID, ruleFormat: "v2022-06-28", contractID: "ctr_123", - includeName: "test include", + includeName: "test_include", includeType: papi.IncludeTypeMicroServices, rules: simpleRules, }, @@ -979,3 +981,67 @@ func TestResourcePropertyInclude(t *testing.T) { }) } } + +func TestValidatePropertyIncludeName(t *testing.T) { + invalidNameCharacters := diag.Errorf("a name must only contain letters, numbers, and these characters: . _ -") + invalidNameLength := diag.Errorf("a name must be longer than 2 characters and shorter than 86 characters") + + tests := map[string]struct { + propertyName string + expectedReturn diag.Diagnostics + }{ + "name contains only valid characters": { + propertyName: "Test_Name.With_Valid-Chars.123", + expectedReturn: nil, + }, + "name contains only numbers": { + propertyName: "123", + expectedReturn: nil, + }, + "name contains only letters": { + propertyName: "TestName", + expectedReturn: nil, + }, + "name contains invalid char !": { + propertyName: "Invalid_Char_!", + expectedReturn: invalidNameCharacters, + }, + "name contains invalid char @": { + propertyName: "@_Invalid_Char", + expectedReturn: invalidNameCharacters, + }, + "name contains invalid spaces": { + propertyName: "test name", + expectedReturn: invalidNameCharacters, + }, + "name too long (86 chars)": { + propertyName: strings.Repeat("a", 86), + expectedReturn: invalidNameLength, + }, + "name of max length (85 chars)": { + propertyName: strings.Repeat("a", 85), + expectedReturn: nil, + }, + "name too short (2 chars)": { + propertyName: strings.Repeat("a", 2), + expectedReturn: invalidNameLength, + }, + "name of min length (3 char)": { + propertyName: strings.Repeat("a", 3), + expectedReturn: nil, + }, + "name empty": { + propertyName: "", + expectedReturn: invalidNameLength, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + ret := validateNameWithBound(3)(test.propertyName, cty.Path{}) + + assert.Equal(t, test.expectedReturn, ret) + + }) + } +} diff --git a/pkg/providers/property/resource_akamai_property_test.go b/pkg/providers/property/resource_akamai_property_test.go index f902723cf..6ad837a91 100644 --- a/pkg/providers/property/resource_akamai_property_test.go +++ b/pkg/providers/property/resource_akamai_property_test.go @@ -1,9 +1,11 @@ package property import ( + "encoding/json" "errors" "fmt" "net/http" + "path" "regexp" "strings" "testing" @@ -11,14 +13,15 @@ import ( "github.com/akamai/AkamaiOPEN-edgegrid-golang/v7/pkg/papi" "github.com/akamai/terraform-provider-akamai/v5/pkg/common/testutils" "github.com/akamai/terraform-provider-akamai/v5/pkg/tools" + "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/require" ) func TestResProperty(t *testing.T) { - t.Skip() // These more or less track the state of a Property in PAPI for the lifecycle tests type TestState struct { Client *papi.Mock @@ -159,14 +162,14 @@ func TestResProperty(t *testing.T) { GetVersionResources := func(propertyID, contractID, groupID string, version int) BehaviorFunc { return func(state *TestState) { ExpectGetPropertyVersionHostnames(state.Client, propertyID, groupID, contractID, version, &state.Hostnames) - ExpectGetRuleTree(state.Client, propertyID, groupID, contractID, version, &state.Rules, &state.RuleFormat) + ExpectGetRuleTree(state.Client, propertyID, groupID, contractID, version, &state.Rules, &state.RuleFormat, nil, nil) } } GetVersionResourcesDrift := func(propertyID, contractID, groupID string, version int, rules papi.RulesUpdate) BehaviorFunc { return func(state *TestState) { ExpectGetPropertyVersionHostnames(state.Client, propertyID, groupID, contractID, version, &state.Hostnames) - ExpectGetRuleTree(state.Client, propertyID, groupID, contractID, version, &rules, &state.RuleFormat) + ExpectGetRuleTree(state.Client, propertyID, groupID, contractID, version, &rules, &state.RuleFormat, nil, nil) } } @@ -216,6 +219,22 @@ func TestResProperty(t *testing.T) { } } + // propertyLifecycleWithPropertyID covers lifecycle when property_id is set + propertyLifecycleWithPropertyID := func(propertyName, propertyID, groupID string, rules papi.RulesUpdate) BehaviorFunc { + return func(state *TestState) { + state.Property.PropertyID = "prp_0" + state.Property.LatestVersion = 1 + state.Property.ContractID = "ctr_0" + state.Property.GroupID = "grp_0" + state.Property.PropertyName = "test_property" + state.Rules = rules + state.RuleFormat = "v2020-01-01" + getProperty(propertyID)(state) + GetVersionResources(propertyID, "ctr_0", "grp_0", 1)(state) + // no deletion since it should be covered by property_bootstrap resource + } + } + propertyLifecycleWithDrift := func(propertyName, propertyID, groupID string, rulesToSend, rulesToReceive papi.RulesUpdate) BehaviorFunc { return func(state *TestState) { createProperty(propertyName, propertyID, rulesToSend)(state) @@ -226,8 +245,8 @@ func TestResProperty(t *testing.T) { importProperty := func(propertyID string) BehaviorFunc { return func(state *TestState) { - // Depending on how much of the import ID is given, the initial property lookup may not have group/contract - ExpectGetProperty(state.Client, "prp_0", "grp_0", "", &state.Property).Maybe() + // Depending on how much of the import ID is given, the initial property lookup may not have group and contract + ExpectGetProperty(state.Client, "prp_0", "grp_0", "ctr_0", &state.Property).Maybe() ExpectGetProperty(state.Client, "prp_0", "", "", &state.Property).Maybe() } } @@ -267,6 +286,14 @@ func TestResProperty(t *testing.T) { ) } + // addPropertyIDAttrCheck adds resource.TestCheckFunc that checks if property_id attribute was set correctly + addPropertyIDAttrCheck := func(checks resource.TestCheckFunc, propertyID string) resource.TestCheckFunc { + return resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("akamai_property.test", "property_id", propertyID), + checks, + ) + } + type StepsFunc = func(State *TestState, FixturePath string) []resource.TestStep // Defines standard variations of client behaviors for a Lifecycle test @@ -440,6 +467,35 @@ func TestResProperty(t *testing.T) { }, } + // withPropertyID covers case when property was initially created with property_bootstrap resource + withPropertyID := LifecycleTestCase{ + Name: "Create with propertyID", + ClientSetup: composeBehaviors( + propertyLifecycleWithPropertyID("test_property", "prp_0", "grp_0", + papi.RulesUpdate{Rules: papi.Rules{Name: "default"}}), + getPropertyVersionResources("prp_0", "grp_0", "ctr_0", 1, papi.VersionStatusInactive, papi.VersionStatusInactive), + setHostnames("prp_0", 1, "to.test.domain"), + setHostnames("prp_0", 1, "to2.test.domain"), + ), + Steps: func(State *TestState, FixturePath string) []resource.TestStep { + return []resource.TestStep{ + { + PreConfig: func() { + State.VersionItems = papi.PropertyVersionItems{Items: []papi.PropertyVersionGetItem{{PropertyVersion: 1, ProductionStatus: papi.VersionStatusInactive}}} + }, + Config: testutils.LoadFixtureString(t, "%s/step0.tf", FixturePath), + Check: checkAttrs("prp_0", "to.test.domain", "1", "0", "0", "ehn_123", + "{\"rules\":{\"name\":\"default\",\"options\":{}}}"), + }, + { + Config: testutils.LoadFixtureString(t, "%s/step1.tf", FixturePath), + Check: addPropertyIDAttrCheck(checkAttrs("prp_0", "to2.test.domain", "1", "0", "0", "ehn_123", + "{\"rules\":{\"name\":\"default\",\"options\":{}}}"), "prp_0"), + }, + } + }, + } + // Standard test behavior for cases where the property's latest version is not active latestVersionNotActive := LifecycleTestCase{ Name: "Latest version not active", @@ -757,11 +813,21 @@ func TestResProperty(t *testing.T) { client.Test(T{t}) parameters := strings.Split(importID, ",") + var propertyBootstrap bool + if parameters[len(parameters)-1] == "property-bootstrap" { + propertyBootstrap = true + parameters = parameters[:len(parameters)-1] + } numberParameters := len(parameters) lastParameter := parameters[len(parameters)-1] + if propertyBootstrap { + setup = append(setup, propertyLifecycleWithPropertyID("test_property", "prp_0", "grp_0", + papi.RulesUpdate{Rules: papi.Rules{Name: "default"}})) + } else { + setup = append(setup, propertyLifecycle("test_property", "prp_0", "grp_0", + papi.RulesUpdate{Rules: papi.Rules{Name: "default"}})) + } setup = append(setup, - propertyLifecycle("test_property", "prp_0", "grp_0", - papi.RulesUpdate{Rules: papi.Rules{Name: "default"}}), getPropertyVersionResources("prp_0", "grp_0", "ctr_0", 1, papi.VersionStatusInactive, papi.VersionStatusInactive), setHostnames("prp_0", 1, "to.test.domain"), importProperty("prp_0"), @@ -811,7 +877,7 @@ func TestResProperty(t *testing.T) { ResourceName: "akamai_property.test", Config: testutils.LoadFixtureString(t, fixturePath), ImportStateVerifyIgnore: []string{"product", "read_version"}, - Check: checkAttrs("prp_0", "to.test.domain", "1", "1", "0", "ehn_123", rules), + Check: addPropertyIDAttrCheck(checkAttrs("prp_0", "to.test.domain", "1", "1", "0", "ehn_123", rules), "prp_0"), }, } }, @@ -833,6 +899,11 @@ func TestResProperty(t *testing.T) { return assertImportableWithOptions(t, testName, importID, "importable.tf", "{\"rules\":{\"name\":\"default\",\"options\":{}}}", []BehaviorFunc{}) } + // assertImportableWithBootstrap covers imports when property-bootstrap flag is provided + assertImportableWithBootstrap := func(t *testing.T, testName, importID string) func(t *testing.T) { + return assertImportableWithOptions(t, testName, importID, "importable-with-bootstrap.tf", "{\"rules\":{\"name\":\"default\",\"options\":{}}}", []BehaviorFunc{}) + } + suppressLogging(t, func() { // Test Schema Configuration @@ -843,10 +914,11 @@ func TestResProperty(t *testing.T) { t.Run("Schema Configuration Error: product_id not given", assertConfigError(t, "product_id not given", `Missing required argument`)) t.Run("Schema Configuration Error: invalid json rules", assertConfigError(t, "invalid json rules", `rules are not valid JSON`)) t.Run("Schema Configuration Error: invalid name given", assertConfigError(t, "invalid name given", `a name must only contain letters, numbers, and these characters: . _ -`)) - t.Run("Schema Configuration Error: name given too long", assertConfigError(t, "name given too long", `a name must be shorter than 86 characters`)) + t.Run("Schema Configuration Error: name given too long", assertConfigError(t, "name given too long", `a name must be longer than 0 characters and shorter than 86 characters`)) // Test Lifecycle + t.Run("Lifecycle: create with propertyID", assertLifecycle(t, t.Name(), "with-propertyID", withPropertyID)) t.Run("Lifecycle: latest version is not active (normal)", assertLifecycle(t, t.Name(), "normal", latestVersionNotActive)) t.Run("Lifecycle: latest version is active in staging (normal)", assertLifecycle(t, t.Name(), "normal", latestVersionActiveInStaging)) t.Run("Lifecycle: latest version is active in production (normal)", assertLifecycle(t, t.Name(), "normal", latestVersionActiveInProd)) @@ -884,6 +956,7 @@ func TestResProperty(t *testing.T) { }, }})}, )) + t.Run("Importable: property_id with property-bootstrap", assertImportableWithBootstrap(t, "property_id", "prp_0,property-bootstrap")) t.Run("Importable: property_id", assertImportable(t, "property_id", "prp_0")) t.Run("Importable: property_id and ver_# version", assertImportable(t, "property_id and ver_# version", "prp_0,ver_1")) t.Run("Importable: property_id and # version", assertImportable(t, "property_id and # version", "prp_0,1")) @@ -1039,6 +1112,93 @@ func TestResProperty(t *testing.T) { client.AssertExpectations(t) }) + t.Run("validation warning when creating property with rules tree", func(t *testing.T) { + client := &papi.Mock{} + client.Test(T{t}) + ExpectCreateProperty( + client, "test_property", "grp_0", + "ctr_0", "prd_0", "prp_1", + ) + rules := papi.Rules{ + Behaviors: []papi.RuleBehavior{ + { + Name: "origin", + Options: papi.RuleOptionsMap{ + "hostname": "1.2.3.4", + "httpPort": float64(80), + "httpsPort": float64(443), + }, + }, + }, + } + var req = papi.UpdateRulesRequest{ + PropertyID: "prp_1", + ContractID: "ctr_0", + GroupID: "grp_0", + PropertyVersion: 1, + Rules: papi.RulesUpdate{Rules: rules}, + ValidateRules: true, + } + warning := papi.RuleWarnings{ + Type: "https://problems.luna.akamaiapis.net/papi/v0/validation/validation_message.ip_address_origin", + ErrorLocation: "#/rules/behaviors/1", + Detail: "Using an IP address for the `Origin Server` is not recommended. IP addresses may be changed or reassigned without notice which can severely impact your property or cause a DoS. Please use a properly formatted hostname instead.", + } + client.On("UpdateRuleTree", AnyCTX, req).Return(&papi.UpdateRulesResponse{ + AccountID: "", + ContractID: "ctr_0", + Comments: "", + GroupID: "grp_0", + PropertyID: "prp_1", + PropertyVersion: 1, + Etag: "", + RuleFormat: "", + Rules: rules, + Errors: nil, + Warnings: []papi.RuleWarnings{warning}, + }, nil).Once() + ExpectGetProperty( + client, "prp_1", "grp_0", "ctr_0", + &papi.Property{ + PropertyID: "prp_1", GroupID: "grp_0", ContractID: "ctr_0", LatestVersion: 1, + PropertyName: "test_property", + }, + ) + + ExpectGetPropertyVersionHostnames( + client, "prp_1", "grp_0", "ctr_0", 1, + &[]papi.Hostname{}, + ).Times(2) + ruleFormat := "" + ExpectGetRuleTree( + client, "prp_1", "grp_0", "ctr_0", 1, + &papi.RulesUpdate{ + Rules: rules, + }, &ruleFormat, nil, []*papi.Error{ + { + Type: "https://problems.luna.akamaiapis.net/papi/v0/validation/validation_message.ip_address_origin", + ErrorLocation: "#/rules/behaviors/1", + Detail: "Using an IP address for the `Origin Server` is not recommended. IP addresses may be changed or reassigned without notice which can severely impact your property or cause a DoS. Please use a properly formatted hostname instead.", + }, + }) + ExpectGetPropertyVersion(client, "prp_1", "grp_0", "ctr_0", 1, papi.VersionStatusInactive, papi.VersionStatusInactive) + + ExpectRemoveProperty(client, "prp_1", "ctr_0", "grp_0") + useClient(client, nil, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, "testdata/TestResProperty/property_with_validation_warning_for_rules.tf"), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("akamai_property.test", "rule_warnings.0.detail", "Using an IP address for the `Origin Server` is not recommended. IP addresses may be changed or reassigned without notice which can severely impact your property or cause a DoS. Please use a properly formatted hostname instead.")), + }, + }, + }) + }) + + client.AssertExpectations(t) + }) t.Run("validation - when updating a property hostnames to empty it should return error", func(t *testing.T) { client := &papi.Mock{} @@ -1089,8 +1249,7 @@ func TestResProperty(t *testing.T) { ruleFormat := "" ExpectGetRuleTree( client, "prp_0", "grp_0", "ctr_0", 1, - &papi.RulesUpdate{}, &ruleFormat, - ) + &papi.RulesUpdate{}, &ruleFormat, nil, nil) ExpectRemoveProperty(client, "prp_0", "ctr_0", "grp_0") @@ -1525,10 +1684,227 @@ func TestResProperty(t *testing.T) { }) } +func TestPropertyResource_versionNotesLifecycle(t *testing.T) { + testdataDir := "testdata/TestResProperty/Lifecycle/versionNotes" + resourceName := "akamai_property.test" + + name := "test_property" + ruleFormat := "v2023-01-05" + ctr, grp, prd, id := "ctr_123", "grp_123", "prd_123", "prp_123" + propertyVersion := 1 + + versionNotes1, versionNotes2, versionNotes3 := "lifecycleTest", "updatedNotes", "updatedNotes2" + rulesFile1And2, rulesFile3, rulesFile4And5 := "01_02_rules.json", "03_rules.json", "04_05_rules.json" + + client := &papi.Mock{} + + mockRead := func(notes string, rules papi.Rules) []*mock.Call { + getPropertyCall := client.On("GetProperty", mock.Anything, papi.GetPropertyRequest{ + PropertyID: id, + ContractID: ctr, + GroupID: grp, + }).Return(&papi.GetPropertyResponse{ + Property: &papi.Property{ + ContractID: ctr, + GroupID: grp, + PropertyID: id, + PropertyName: name, + LatestVersion: propertyVersion, + }, + }, nil) + + getHostnamesCall := client.On("GetPropertyVersionHostnames", mock.Anything, papi.GetPropertyVersionHostnamesRequest{ + ContractID: ctr, + GroupID: grp, + PropertyID: id, + PropertyVersion: propertyVersion, + IncludeCertStatus: true, + }).Return(&papi.GetPropertyVersionHostnamesResponse{}, nil) + + getRuleTreeCall := client.On("GetRuleTree", mock.Anything, papi.GetRuleTreeRequest{ + PropertyID: id, + ContractID: ctr, + GroupID: grp, + PropertyVersion: propertyVersion, + ValidateRules: true, + ValidateMode: papi.RuleValidateModeFull, + }).Return(&papi.GetRuleTreeResponse{ + Rules: rules, + Comments: notes, + RuleFormat: ruleFormat, + }, nil) + + getPropertyVersionCall := client.On("GetPropertyVersion", mock.Anything, papi.GetPropertyVersionRequest{ + PropertyID: id, + PropertyVersion: propertyVersion, + ContractID: ctr, + GroupID: grp, + }).Return(&papi.GetPropertyVersionsResponse{ + Version: papi.PropertyVersionGetItem{ + Note: notes, + ProductID: prd, + ProductionStatus: papi.VersionStatusInactive, + StagingStatus: papi.VersionStatusInactive, + }, + }, nil) + + return []*mock.Call{getPropertyCall, getHostnamesCall, getRuleTreeCall, getPropertyVersionCall} + } + + mockUpdate := func(currentNotes, newNotes string, rules papi.Rules) { + client.On("GetPropertyVersion", mock.Anything, papi.GetPropertyVersionRequest{ + PropertyID: id, + PropertyVersion: propertyVersion, + ContractID: ctr, + GroupID: grp, + }).Return(&papi.GetPropertyVersionsResponse{ + Version: papi.PropertyVersionGetItem{ + Note: currentNotes, + ProductID: prd, + ProductionStatus: papi.VersionStatusInactive, + StagingStatus: papi.VersionStatusInactive, + }, + }, nil).Once() + + client.On("UpdateRuleTree", mock.Anything, papi.UpdateRulesRequest{ + PropertyID: id, + GroupID: grp, + ContractID: ctr, + PropertyVersion: propertyVersion, + Rules: papi.RulesUpdate{ + Rules: rules, + Comments: newNotes, + }, + ValidateRules: true, + }).Return(&papi.UpdateRulesResponse{}, nil).Once() + } + + // step 1 - create + read + plan + client.On("CreateProperty", mock.Anything, papi.CreatePropertyRequest{ + ContractID: ctr, + GroupID: grp, + Property: papi.PropertyCreate{ + ProductID: prd, + PropertyName: name, + RuleFormat: ruleFormat, + }, + }).Return(&papi.CreatePropertyResponse{ + PropertyID: id, + }, nil).Once() + + rulesJSON := testutils.LoadFixtureBytes(t, path.Join(testdataDir, rulesFile1And2)) + var rules1And2 papi.RulesUpdate + err := json.Unmarshal(rulesJSON, &rules1And2) + require.NoError(t, err) + + client.On("UpdateRuleTree", mock.Anything, papi.UpdateRulesRequest{ + PropertyID: id, + GroupID: grp, + ContractID: ctr, + PropertyVersion: propertyVersion, + Rules: papi.RulesUpdate{ + Rules: rules1And2.Rules, + Comments: versionNotes1, + }, + ValidateRules: true, + }).Return(&papi.UpdateRulesResponse{}, nil).Once() + + for _, m := range mockRead(versionNotes1, rules1And2.Rules) { + m.Times(2) + } + + // step 2 - refresh + plan + for _, m := range mockRead(versionNotes2, rules1And2.Rules) { + m.Times(2) + } + + // step 3 - refresh + update + read + plan + for _, m := range mockRead(versionNotes2, rules1And2.Rules) { + m.Times(1) + } + + var rules3 papi.RulesUpdate + rulesJSON = testutils.LoadFixtureBytes(t, path.Join(testdataDir, rulesFile3)) + err = json.Unmarshal(rulesJSON, &rules3) + require.NoError(t, err) + + mockUpdate(versionNotes3, "updatedNotes2", rules3.Rules) + + for _, m := range mockRead(versionNotes3, rules3.Rules) { + m.Times(2) + } + + // step 4 - refresh + update + read + plan + for _, m := range mockRead(versionNotes3, rules3.Rules) { + m.Times(1) + } + + var rules4And5 papi.RulesUpdate + rulesJSON = testutils.LoadFixtureBytes(t, path.Join(testdataDir, rulesFile4And5)) + err = json.Unmarshal(rulesJSON, &rules4And5) + require.NoError(t, err) + + mockUpdate(versionNotes3, rules4And5.Comments, rules4And5.Rules) + + for _, m := range mockRead(rules4And5.Comments, rules4And5.Rules) { + m.Times(2) + } + + // step 5 - refresh + plan + for _, m := range mockRead(rules4And5.Comments, rules4And5.Rules) { + m.Times(2) + } + + // cleanup + client.On("RemoveProperty", mock.Anything, papi.RemovePropertyRequest{ + PropertyID: id, + ContractID: ctr, + GroupID: grp, + }).Return(&papi.RemovePropertyResponse{}, nil) + + useClient(client, nil, func() { + resource.UnitTest(t, resource.TestCase{ + ProtoV5ProviderFactories: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testutils.LoadFixtureString(t, path.Join(testdataDir, "01_with_notes_and_comments.tf")), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckResourceAttrJSON(resourceName, "rules", testutils.LoadFixtureString(t, path.Join(testdataDir, "01_expected_rules.json"))), + resource.TestCheckResourceAttr("akamai_property.test", "version_notes", "lifecycleTest"), + ), + }, + { + Config: testutils.LoadFixtureString(t, path.Join(testdataDir, "02_update_notes_no_diff.tf")), + PlanOnly: true, + }, + { + Config: testutils.LoadFixtureString(t, path.Join(testdataDir, "03_update_notes_and_rules.tf")), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckResourceAttrJSON(resourceName, "rules", testutils.LoadFixtureString(t, path.Join(testdataDir, "03_expected_rules.json"))), + resource.TestCheckResourceAttr("akamai_property.test", "version_notes", "updatedNotes2"), + ), + }, + { + Config: testutils.LoadFixtureString(t, path.Join(testdataDir, "04_05_remove_notes_update_comments.tf")), + Check: resource.ComposeAggregateTestCheckFunc( + testCheckResourceAttrJSON(resourceName, "rules", testutils.LoadFixtureString(t, path.Join(testdataDir, "04_expected_rules.json"))), + resource.TestCheckResourceAttr("akamai_property.test", "version_notes", "Rules_04"), + ), + }, + { + Config: testutils.LoadFixtureString(t, path.Join(testdataDir, "04_05_remove_notes_update_comments.tf")), + PlanOnly: true, + }, + }, + }) + }) + + client.AssertExpectations(t) +} + func TestValidatePropertyName(t *testing.T) { - t.Skip() invalidNameCharacters := diag.Errorf("a name must only contain letters, numbers, and these characters: . _ -") - invalidNameLength := diag.Errorf("a name must be shorter than 86 characters") + invalidNameLength := diag.Errorf("a name must be longer than 0 characters and shorter than 86 characters") tests := map[string]struct { propertyName string @@ -1572,13 +1948,13 @@ func TestValidatePropertyName(t *testing.T) { }, "name empty": { propertyName: "", - expectedReturn: invalidNameCharacters, + expectedReturn: invalidNameLength, }, } for name, test := range tests { t.Run(name, func(t *testing.T) { - ret := validatePropertyName(test.propertyName, nil) + ret := validateNameWithBound(1)(test.propertyName, cty.Path{}) assert.Equal(t, test.expectedReturn, ret) diff --git a/pkg/providers/property/ruleformats/rule_format_v2023_01_05.gen.go b/pkg/providers/property/ruleformats/rule_format_v2023_01_05.gen.go index 6659d6d07..4219030bc 100644 --- a/pkg/providers/property/ruleformats/rule_format_v2023_01_05.gen.go +++ b/pkg/providers/property/ruleformats/rule_format_v2023_01_05.gen.go @@ -2335,7 +2335,7 @@ func getBehaviorsSchemaV20230105() map[string]*schema.Schema { "construct_response": { Optional: true, Type: schema.TypeList, - Description: "This behavior constructs an HTTP response, complete with HTTP status code and body, to serve from the edge independently of your origin. It supports all request methods except for `POST`. This behavior can be used in includes.", + Description: "This behavior constructs an HTTP response, complete with HTTP status code and body, to serve from the edge independently of your origin. For example, you might want to send a customized response if the URL doesn't point to an object on the origin server, or if the end user is not authorized to view the requested content. You can use it with all request methods you allow for your property, including POST. For more details, see the `allowOptions`, `allowPatch`, `allowPost`, `allowPut`, and `allowDelete` behaviors. This behavior can be used in includes.", MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -2373,7 +2373,7 @@ func getBehaviorsSchemaV20230105() map[string]*schema.Schema { }, "force_eviction": { Optional: true, - Description: "Removes the underlying object from the cache, since it is not being served.", + Description: "For GET requests from clients, this forces edge servers to evict the underlying object from cache. Defaults to `false`.", Type: schema.TypeBool, }, "ignore_purge": { diff --git a/pkg/providers/property/ruleformats/rule_format_v2023_05_30.gen.go b/pkg/providers/property/ruleformats/rule_format_v2023_05_30.gen.go index 8cb5e3220..e64da9645 100644 --- a/pkg/providers/property/ruleformats/rule_format_v2023_05_30.gen.go +++ b/pkg/providers/property/ruleformats/rule_format_v2023_05_30.gen.go @@ -2376,7 +2376,7 @@ func getBehaviorsSchemaV20230530() map[string]*schema.Schema { "construct_response": { Optional: true, Type: schema.TypeList, - Description: "This behavior constructs an HTTP response, complete with HTTP status code and body, to serve from the edge independently of your origin. It supports all request methods except for `POST`. This behavior can be used in includes.", + Description: "This behavior constructs an HTTP response, complete with HTTP status code and body, to serve from the edge independently of your origin. For example, you might want to send a customized response if the URL doesn't point to an object on the origin server, or if the end user is not authorized to view the requested content. You can use it with all request methods you allow for your property, including POST. For more details, see the `allowOptions`, `allowPatch`, `allowPost`, `allowPut`, and `allowDelete` behaviors. This behavior can be used in includes.", MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -2414,7 +2414,7 @@ func getBehaviorsSchemaV20230530() map[string]*schema.Schema { }, "force_eviction": { Optional: true, - Description: "Removes the underlying object from the cache, since it is not being served.", + Description: "For GET requests from clients, this forces edge servers to evict the underlying object from cache. Defaults to `false`.", Type: schema.TypeBool, }, "ignore_purge": { diff --git a/pkg/providers/property/ruleformats/rule_format_v2023_09_20.gen.go b/pkg/providers/property/ruleformats/rule_format_v2023_09_20.gen.go index d68d1dbab..91d226ad0 100644 --- a/pkg/providers/property/ruleformats/rule_format_v2023_09_20.gen.go +++ b/pkg/providers/property/ruleformats/rule_format_v2023_09_20.gen.go @@ -2425,7 +2425,7 @@ func getBehaviorsSchemaV20230920() map[string]*schema.Schema { "construct_response": { Optional: true, Type: schema.TypeList, - Description: "This behavior constructs an HTTP response, complete with HTTP status code and body, to serve from the edge independently of your origin. It supports all request methods except for `POST`. This behavior can be used in includes.", + Description: "This behavior constructs an HTTP response, complete with HTTP status code and body, to serve from the edge independently of your origin. For example, you might want to send a customized response if the URL doesn't point to an object on the origin server, or if the end user is not authorized to view the requested content. You can use it with all request methods you allow for your property, including POST. For more details, see the `allowOptions`, `allowPatch`, `allowPost`, `allowPut`, and `allowDelete` behaviors. This behavior can be used in includes.", MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -2463,7 +2463,7 @@ func getBehaviorsSchemaV20230920() map[string]*schema.Schema { }, "force_eviction": { Optional: true, - Description: "Removes the underlying object from the cache, since it is not being served.", + Description: "For GET requests from clients, this forces edge servers to evict the underlying object from cache. Defaults to `false`.", Type: schema.TypeBool, }, "ignore_purge": { @@ -9608,7 +9608,7 @@ func getBehaviorsSchemaV20230920() map[string]*schema.Schema { }, "permissions_policy_directive": { Optional: true, - Description: "Each directive represents a browser feature. Specify the ones you want enabled in a client browser that accesses your content. Include values from the pre-set list or add a custom entry.", + Description: "Each directive represents a browser feature. Specify the ones you want enabled in a client browser that accesses your content. You can add custom entries or provide pre-set values from the list. For more details on each value, see the `guide section` for this behavior.", Type: schema.TypeList, Elem: &schema.Schema{ Type: schema.TypeString, @@ -10743,7 +10743,7 @@ func getBehaviorsSchemaV20230920() map[string]*schema.Schema { }, "accept_ch": { Optional: true, - Description: "The client hint data objects you want to receive from the browser. You can provide pre-set values from this list, or add custom entries. If you've configured your origin server to pass along data objects, they merge with the ones you set in this array, before the list is sent to the client.", + Description: "The client hint data objects you want to receive from the browser. You can add custom entries or provide pre-set values from the list. For more details on each value, see the `guide section` for this behavior. If you've configured your origin server to pass along data objects, they merge with the ones you set in this array, before the list is sent to the client.", Type: schema.TypeList, Elem: &schema.Schema{ Type: schema.TypeString, @@ -10751,7 +10751,7 @@ func getBehaviorsSchemaV20230920() map[string]*schema.Schema { }, "accept_critical_ch": { Optional: true, - Description: "The critical client hint data objects you want to receive from the browser. The original request from the browser needs to include these objects. Otherwise, a new response header is sent back to the client, asking for all of these client hint data objects. You can provide pre-set values from the list, or add custom entries.", + Description: "The critical client hint data objects you want to receive from the browser. The original request from the browser needs to include these objects. Otherwise, a new response header is sent back to the client, asking for all of these client hint data objects. You can add custom entries or provide pre-set values from the list. For more details on each value, see the `guide section` for this behavior.", Type: schema.TypeList, Elem: &schema.Schema{ Type: schema.TypeString, diff --git a/pkg/providers/property/ruleformats/rule_format_v2023_10_30.gen.go b/pkg/providers/property/ruleformats/rule_format_v2023_10_30.gen.go index d552a9789..f83f6dc86 100644 --- a/pkg/providers/property/ruleformats/rule_format_v2023_10_30.gen.go +++ b/pkg/providers/property/ruleformats/rule_format_v2023_10_30.gen.go @@ -2425,7 +2425,7 @@ func getBehaviorsSchemaV20231030() map[string]*schema.Schema { "construct_response": { Optional: true, Type: schema.TypeList, - Description: "This behavior constructs an HTTP response, complete with HTTP status code and body, to serve from the edge independently of your origin. It supports all request methods except for `POST`. This behavior can be used in includes.", + Description: "This behavior constructs an HTTP response, complete with HTTP status code and body, to serve from the edge independently of your origin. For example, you might want to send a customized response if the URL doesn't point to an object on the origin server, or if the end user is not authorized to view the requested content. You can use it with all request methods you allow for your property, including POST. For more details, see the `allowOptions`, `allowPatch`, `allowPost`, `allowPut`, and `allowDelete` behaviors. This behavior can be used in includes.", MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -2463,7 +2463,7 @@ func getBehaviorsSchemaV20231030() map[string]*schema.Schema { }, "force_eviction": { Optional: true, - Description: "Removes the underlying object from the cache, since it is not being served.", + Description: "For GET requests from clients, this forces edge servers to evict the underlying object from cache. Defaults to `false`.", Type: schema.TypeBool, }, "ignore_purge": { @@ -9608,7 +9608,7 @@ func getBehaviorsSchemaV20231030() map[string]*schema.Schema { }, "permissions_policy_directive": { Optional: true, - Description: "Each directive represents a browser feature. Specify the ones you want enabled in a client browser that accesses your content. Include values from the pre-set list or add a custom entry.", + Description: "Each directive represents a browser feature. Specify the ones you want enabled in a client browser that accesses your content. You can add custom entries or provide pre-set values from the list. For more details on each value, see the `guide section` for this behavior.", Type: schema.TypeList, Elem: &schema.Schema{ Type: schema.TypeString, @@ -10743,7 +10743,7 @@ func getBehaviorsSchemaV20231030() map[string]*schema.Schema { }, "accept_ch": { Optional: true, - Description: "The client hint data objects you want to receive from the browser. You can provide pre-set values from this list, or add custom entries. If you've configured your origin server to pass along data objects, they merge with the ones you set in this array, before the list is sent to the client.", + Description: "The client hint data objects you want to receive from the browser. You can add custom entries or provide pre-set values from the list. For more details on each value, see the `guide section` for this behavior. If you've configured your origin server to pass along data objects, they merge with the ones you set in this array, before the list is sent to the client.", Type: schema.TypeList, Elem: &schema.Schema{ Type: schema.TypeString, @@ -10751,7 +10751,7 @@ func getBehaviorsSchemaV20231030() map[string]*schema.Schema { }, "accept_critical_ch": { Optional: true, - Description: "The critical client hint data objects you want to receive from the browser. The original request from the browser needs to include these objects. Otherwise, a new response header is sent back to the client, asking for all of these client hint data objects. You can provide pre-set values from the list, or add custom entries.", + Description: "The critical client hint data objects you want to receive from the browser. The original request from the browser needs to include these objects. Otherwise, a new response header is sent back to the client, asking for all of these client hint data objects. You can add custom entries or provide pre-set values from the list. For more details on each value, see the `guide section` for this behavior.", Type: schema.TypeList, Elem: &schema.Schema{ Type: schema.TypeString, diff --git a/pkg/providers/property/ruleformats/rule_format_v2024_01_09.gen.go b/pkg/providers/property/ruleformats/rule_format_v2024_01_09.gen.go new file mode 100644 index 000000000..5a391e60e --- /dev/null +++ b/pkg/providers/property/ruleformats/rule_format_v2024_01_09.gen.go @@ -0,0 +1,16236 @@ +package ruleformats + +import ( + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func init() { + schemasRegistry.register(RuleFormat{ + version: "rules_v2024_01_09", + behaviorsSchemas: getBehaviorsSchemaV20240109(), + criteriaSchemas: getCriteriaSchemaV20240109(), + typeMappings: map[string]interface{}{"adScalerCircuitBreaker.returnErrorResponseCodeBased.408": 408, "adScalerCircuitBreaker.returnErrorResponseCodeBased.500": 500, "adScalerCircuitBreaker.returnErrorResponseCodeBased.502": 502, "adScalerCircuitBreaker.returnErrorResponseCodeBased.504": 504}, + nameMappings: map[string]string{"allowFcmParentOverride": "allowFCMParentOverride", "allowHttpsCacheKeySharing": "allowHTTPSCacheKeySharing", "allowHttpsDowngrade": "allowHTTPSDowngrade", "allowHttpsUpgrade": "allowHTTPSUpgrade", "c": "C", "canBeCa": "canBeCA", "cn": "CN", "conditionalHttpStatus": "conditionalHTTPStatus", "contentCharacteristicsAmd": "contentCharacteristicsAMD", "contentCharacteristicsDd": "contentCharacteristicsDD", "dcpAuthHmacTransformation": "dcpAuthHMACTransformation", "detectSmartDnsProxy": "detectSmartDNSProxy", "detectSmartDnsProxyAction": "detectSmartDNSProxyAction", "detectSmartDnsProxyRedirecturl": "detectSmartDNSProxyRedirecturl", "enableCmcdSegmentPrefetch": "enableCMCDSegmentPrefetch", "enableEs256": "enableES256", "enableIpAvoidance": "enableIPAvoidance", "enableIpProtection": "enableIPProtection", "enableIpRedirectOnDeny": "enableIPRedirectOnDeny", "enableRs256": "enableRS256", "enableTokenInUri": "enableTokenInURI", "g2OToken": "g2oToken", "g2Oheader": "g2oheader", "i18NCharset": "i18nCharset", "i18NStatus": "i18nStatus", "isCertificateSniOnly": "isCertificateSNIOnly", "issuerRdns": "issuerRDNs", "logEdgeIp": "logEdgeIP", "o": "O", "originSettings": "origin_settings", "ou": "OU", "overrideIpAddresses": "overrideIPAddresses", "segmentDurationDash": "segmentDurationDASH", "segmentDurationDashCustom": "segmentDurationDASHCustom", "segmentDurationHds": "segmentDurationHDS", "segmentDurationHdsCustom": "segmentDurationHDSCustom", "segmentDurationHls": "segmentDurationHLS", "segmentDurationHlsCustom": "segmentDurationHLSCustom", "segmentSizeDash": "segmentSizeDASH", "segmentSizeHds": "segmentSizeHDS", "segmentSizeHls": "segmentSizeHLS", "sf3COriginHost": "sf3cOriginHost", "sf3COriginHostHeader": "sf3cOriginHostHeader", "smartDnsProxy": "smartDNSProxy", "standardTlsMigration": "standardTLSMigration", "standardTlsMigrationOverride": "standardTLSMigrationOverride", "subjectCn": "subjectCN", "subjectRdns": "subjectRDNs", "titleAicMobile": "title_aic_mobile", "titleAicNonmobile": "title_aic_nonmobile", "tokenAuthHlsTitle": "tokenAuthHLSTitle"}, + shouldFlatten: []string{"apiPrioritization.cloudletPolicy", "apiPrioritization.throttledCpCode", "apiPrioritization.throttledCpCode.cpCodeLimits", "apiPrioritization.netStorage", "applicationLoadBalancer.cloudletPolicy", "applicationLoadBalancer.allDownNetStorage", "audienceSegmentation.cloudletPolicy", "cpCode.value", "cpCode.value.cpCodeLimits", "edgeRedirector.cloudletPolicy", "failAction.netStorageHostname", "failAction.cpCode", "failAction.cpCode.cpCodeLimits", "firstPartyMarketing.cloudletPolicy", "firstPartyMarketingPlus.cloudletPolicy", "forwardRewrite.cloudletPolicy", "imageAndVideoManager.cpCodeOriginal", "imageAndVideoManager.cpCodeOriginal.cpCodeLimits", "imageAndVideoManager.cpCodeTransformed", "imageAndVideoManager.cpCodeTransformed.cpCodeLimits", "imageManager.cpCodeOriginal", "imageManager.cpCodeOriginal.cpCodeLimits", "imageManager.cpCodeTransformed", "imageManager.cpCodeTransformed.cpCodeLimits", "imageManagerVideo.cpCodeOriginal", "imageManagerVideo.cpCodeOriginal.cpCodeLimits", "imageManagerVideo.cpCodeTransformed", "imageManagerVideo.cpCodeTransformed.cpCodeLimits", "origin.netStorage", "origin.customCertificateAuthorities.subjectRDNs", "origin.customCertificateAuthorities.issuerRDNs", "origin.customCertificates.subjectRDNs", "origin.customCertificates.issuerRDNs", "phasedRelease.cloudletPolicy", "requestControl.cloudletPolicy", "requestControl.netStorage", "siteShield.ssmap", "visitorPrioritization.cloudletPolicy", "visitorPrioritization.waitingRoomCpCode", "visitorPrioritization.waitingRoomCpCode.cpCodeLimits", "visitorPrioritization.waitingRoomNetStorage", "webApplicationFirewall.firewallConfiguration", "matchCpCode.value", "matchCpCode.value.cpCodeLimits"}, + }) +} + +func getBehaviorsSchemaV20240109() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "ad_scaler_circuit_breaker": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior works with `manifestRerouting` to provide the scale and reliability of Akamai network while simultaneously allowing third party partners to modify the requested media content with value-added features. The `adScalerCircuitBreaker` behavior specifies the fallback action in case the technology partner encounters errors and can't modify the requested media object. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "response_delay_based": { + Optional: true, + Description: "Triggers a fallback action based on the delayed response from the technology partner's server.", + Type: schema.TypeBool, + }, + "response_delay_threshold": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"500ms"}, false)), + Optional: true, + Description: "Specifies the maximum response delay that, if exceeded, triggers the fallback action.", + Type: schema.TypeString, + }, + "response_code_based": { + Optional: true, + Description: "Triggers a fallback action based on the response code from the technology partner's server.", + Type: schema.TypeBool, + }, + "response_codes": { + ValidateDiagFunc: validateRegexOrVariable("^(([0-9]{3})(,?))+$"), + Optional: true, + Description: "Specifies the codes in the partner's response that trigger the fallback action, either `408`, `500`, `502`, `504`, `SAME_AS_RECEIEVED`, or `SPECIFY_YOUR_OWN` for a custom code.", + Type: schema.TypeString, + }, + "fallback_action_response_code_based": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"RETURN_AKAMAI_COPY", "RETURN_ERROR"}, false)), + Optional: true, + Description: "Specifies the fallback action.", + Type: schema.TypeString, + }, + "return_error_response_code_based": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SAME_AS_RECEIVED", "408", "500", "502", "504", "SPECIFY_YOUR_OWN"}, false)), + Optional: true, + Description: "Specifies the error to include in the response to the client.", + Type: schema.TypeString, + }, + "specify_your_own_response_code_based": { + ValidateDiagFunc: validateRegexOrVariable("^\\d{3}$"), + Optional: true, + Description: "Defines a custom error response.", + Type: schema.TypeString, + }, + }, + }, + }, + "adaptive_acceleration": { + Optional: true, + Type: schema.TypeList, + Description: "Adaptive Acceleration uses HTTP/2 server push functionality with Ion properties to pre-position content and improve the performance of HTML page loading based on real user monitoring (RUM) timing data. It also helps browsers to preconnect to content that’s likely needed for upcoming requests. To use this behavior, make sure you enable the `http2` behavior. Use the `Adaptive Acceleration API` to report on the set of assets this feature optimizes. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "source": { + Optional: true, + Description: "The source Adaptive Acceleration uses to gather the real user monitoring timing data, either `mPulse` or `realUserMonitoring`. The recommended `mPulse` option supports all optimizations and requires the `mPulse` behavior added by default to new Ion properties. The classic `realUserMonitoring` method has been deprecated. If you set it as the data source, make sure you use it with the `realUserMonitoring` behavior.", + Type: schema.TypeString, + }, + "title_http2_server_push": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "enable_push": { + Optional: true, + Description: "Recognizes resources like JavaScript, CSS, and images based on gathered timing data and sends these resources to a browser as it's waiting for a response to the initial request for your website or app. See `Automatic Server Push` for more information.", + Type: schema.TypeBool, + }, + "title_preconnect": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "enable_preconnect": { + Optional: true, + Description: "Allows browsers to anticipate what connections your site needs, and establishes those connections ahead of time. See `Automatic Preconnect` for more information.", + Type: schema.TypeBool, + }, + "title_preload": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "preload_enable": { + Optional: true, + Description: "Allows browsers to preload necessary fonts before they fetch and process other resources. See `Automatic Font Preload` for more information.", + Type: schema.TypeBool, + }, + "ab_testing": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "ab_logic": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"DISABLED", "CLOUDLETS", "MANUAL"}, false)), + Optional: true, + Description: "Specifies whether to use Adaptive Acceleration in an A/B testing environment. To include Adaptive Acceleration data in your A/B testing, specify the mode you want to apply. Otherwise, `DISABLED` by default. See `Add A/B testing to A2` for details.", + Type: schema.TypeString, + }, + "cookie_name": { + Optional: true, + Description: "This specifies the name of the cookie file used for redirecting the requests in the A/B testing environment.", + Type: schema.TypeString, + }, + "compression": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "title_ro": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "enable_ro": { + Optional: true, + Description: "Enables the Resource Optimizer, which automates the compression and delivery of your `.css`, `.js`, and `.svg` content using a combination of Brotli and Zopfli compressions. The compression is performed offline, during a time to live that the feature automatically sets. See the `resourceOptimizer` and `resourceOptimizerExtendedCompatibility` behaviors for more details.", + Type: schema.TypeBool, + }, + "title_brotli": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "enable_brotli_compression": { + Optional: true, + Description: "Applies Brotli compression, converting your origin content to cache on edge servers.", + Type: schema.TypeBool, + }, + "enable_for_noncacheable": { + Optional: true, + Description: "Applies Brotli compression to non-cacheable content.", + Type: schema.TypeBool, + }, + }, + }, + }, + "adaptive_image_compression": { + Optional: true, + Type: schema.TypeList, + Description: "The Adaptive Image Compression feature compresses JPEG images depending on the requesting network's performance, thus improving response time. The behavior specifies three performance tiers based on round-trip tests: 1 for excellent, 2 for good, and 3 for poor. It assigns separate performance criteria for mobile (cellular) and non-mobile networks, which the `compressMobile` and `compressStandard` options enable independently. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "title_aic_mobile": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "compress_mobile": { + Optional: true, + Description: "Adapts images served over cellular mobile networks.", + Type: schema.TypeBool, + }, + "tier1_mobile_compression_method": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"COMPRESS", "BYPASS", "STRIP"}, false)), + Optional: true, + Description: "Specifies tier-1 behavior.", + Type: schema.TypeString, + }, + "tier1_mobile_compression_value": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 100)), + Optional: true, + Description: "Specifies the compression percentage.", + Type: schema.TypeInt, + }, + "tier2_mobile_compression_method": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"COMPRESS", "BYPASS", "STRIP"}, false)), + Optional: true, + Description: "Specifies tier-2 cellular-network behavior.", + Type: schema.TypeString, + }, + "tier2_mobile_compression_value": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 100)), + Optional: true, + Description: "Specifies the compression percentage.", + Type: schema.TypeInt, + }, + "tier3_mobile_compression_method": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"COMPRESS", "BYPASS", "STRIP"}, false)), + Optional: true, + Description: "Specifies tier-3 cellular-network behavior.", + Type: schema.TypeString, + }, + "tier3_mobile_compression_value": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 100)), + Optional: true, + Description: "Specifies the compression percentage.", + Type: schema.TypeInt, + }, + "title_aic_nonmobile": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "compress_standard": { + Optional: true, + Description: "Adapts images served over non-cellular networks.", + Type: schema.TypeBool, + }, + "tier1_standard_compression_method": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"COMPRESS", "BYPASS", "STRIP"}, false)), + Optional: true, + Description: "Specifies tier-1 non-cellular network behavior.", + Type: schema.TypeString, + }, + "tier1_standard_compression_value": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 100)), + Optional: true, + Description: "Specifies the compression percentage.", + Type: schema.TypeInt, + }, + "tier2_standard_compression_method": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"COMPRESS", "BYPASS", "STRIP"}, false)), + Optional: true, + Description: "Specifies tier-2 non-cellular network behavior.", + Type: schema.TypeString, + }, + "tier2_standard_compression_value": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 100)), + Optional: true, + Description: "Specifies the compression percentage.", + Type: schema.TypeInt, + }, + "tier3_standard_compression_method": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"COMPRESS", "BYPASS", "STRIP"}, false)), + Optional: true, + Description: "Specifies tier-3 non-cellular network behavior.", + Type: schema.TypeString, + }, + "tier3_standard_compression_value": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 100)), + Optional: true, + Description: "Specifies the compression percentage.", + Type: schema.TypeInt, + }, + }, + }, + }, + "advanced": { + Optional: true, + Type: schema.TypeList, + Description: "This specifies Akamai XML metadata. It can only be configured on your behalf by Akamai Professional Services. This behavior is for internal usage only. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "description": { + Optional: true, + Description: "Human-readable description of what the XML block does.", + Type: schema.TypeString, + }, + "xml": { + Optional: true, + Description: "Akamai XML metadata.", + Type: schema.TypeString, + }, + }, + }, + }, + "aggregated_reporting": { + Optional: true, + Type: schema.TypeList, + Description: "Configure a custom report that collects traffic data. The data is based on one to four variables, such as `sum`, `average`, `min`, and `max`. These aggregation attributes help compile traffic data summaries. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables aggregated reporting.", + Type: schema.TypeBool, + }, + "report_name": { + Optional: true, + Description: "The unique name of the aggregated report within the property. If you reconfigure any attributes or variables in the aggregated reporting behavior, update this field to a unique value to enable logging data in a new instance of the report.", + Type: schema.TypeString, + }, + "attributes_count": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(1, 4)), + Optional: true, + Description: "The number of attributes to include in the report, ranging from 1 to 4.", + Type: schema.TypeInt, + }, + "attribute1": { + Optional: true, + Description: "Specify a previously user-defined variable name as a report attribute. The values extracted for all attributes range from 0 to 20 characters.", + Type: schema.TypeString, + }, + "attribute2": { + Optional: true, + Description: "Specify a previously user-defined variable name as a report attribute. The values extracted for all attributes range from 0 to 20 characters.", + Type: schema.TypeString, + }, + "attribute3": { + Optional: true, + Description: "Specify a previously user-defined variable name as a report attribute. The values extracted for all attributes range from 0 to 20 characters.", + Type: schema.TypeString, + }, + "attribute4": { + Optional: true, + Description: "Specify a previously user-defined variable name as a report attribute. The values extracted for all attributes range from 0 to 20 characters.", + Type: schema.TypeString, + }, + }, + }, + }, + "akamaizer": { + Optional: true, + Type: schema.TypeList, + Description: "This allows you to run regular expression substitutions over web pages. To apply this behavior, you need to match on a `contentType`. Contact Akamai Professional Services for help configuring the Akamaizer. See also the `akamaizerTag` behavior. This behavior is for internal usage only. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the Akamaizer behavior.", + Type: schema.TypeBool, + }, + }, + }, + }, + "akamaizer_tag": { + Optional: true, + Type: schema.TypeList, + Description: "This specifies HTML tags and replacement rules for hostnames used in conjunction with the `akamaizer` behavior. Contact Akamai Professional Services for help configuring the Akamaizer. This behavior is for internal usage only. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_hostname": { + Optional: true, + Description: "Specifies the hostname to match on as a Perl-compatible regular expression.", + Type: schema.TypeString, + }, + "replacement_hostname": { + Optional: true, + Description: "Specifies the replacement hostname for the tag to use.", + Type: schema.TypeString, + }, + "scope": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ATTRIBUTE", "URL_ATTRIBUTE", "BLOCK", "PAGE"}, false)), + Optional: true, + Description: "Specifies the part of HTML content the `tagsAttribute` refers to.", + Type: schema.TypeString, + }, + "tags_attribute": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"A", "A_HREF", "IMG", "IMG_SRC", "SCRIPT", "SCRIPT_SRC", "LINK", "LINK_HREF", "TD", "TD_BACKGROUND", "TABLE", "TABLE_BACKGROUND", "IFRAME", "IFRAME_SRC", "AREA", "AREA_HREF", "BASE", "BASE_HREF", "FORM", "FORM_ACTION"}, false)), + Optional: true, + Description: "Specifies the tag or tag/attribute combination to operate on.", + Type: schema.TypeString, + }, + "replace_all": { + Optional: true, + Description: "Replaces all matches when enabled, otherwise replaces only the first match.", + Type: schema.TypeBool, + }, + "include_tags_attribute": { + Optional: true, + Description: "Whether to include the `tagsAttribute` value.", + Type: schema.TypeBool, + }, + }, + }, + }, + "all_http_in_cache_hierarchy": { + Optional: true, + Type: schema.TypeList, + Description: "Allow all HTTP request methods to be used for the edge's parent servers, useful to implement features such as `Site Shield`, `SureRoute`, and Tiered Distribution. (See the `siteShield`, `sureRoute`, and `tieredDistribution` behaviors.) This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables all HTTP requests for parent servers in the cache hierarchy.", + Type: schema.TypeBool, + }, + }, + }, + }, + "allow_cloudlets_origins": { + Optional: true, + Type: schema.TypeList, + Description: "Allows Cloudlets Origins to determine the criteria, separately from the Property Manager, under which alternate `origin` definitions are assigned. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Allows you to assign custom origin definitions referenced in sub-rules by `cloudletsOrigin` labels. If disabled, all sub-rules are ignored.", + Type: schema.TypeBool, + }, + "honor_base_directory": { + Optional: true, + Description: "Prefixes any Cloudlet-generated origin path with a path defined by an Origin Base Path behavior. If no path is defined, it has no effect. If another Cloudlet policy already prepends the same Origin Base Path, the path is not duplicated.", + Type: schema.TypeBool, + }, + "purge_origin_query_parameter": { + ValidateDiagFunc: validateRegexOrVariable("^[^:/?#\\[\\]@&]+$"), + Optional: true, + Description: "When purging content from a Cloudlets Origin, this specifies a query parameter name whose value is the specific named origin to purge. Note that this only applies to content purge requests, for example when using the `Content Control Utility API`.", + Type: schema.TypeString, + }, + }, + }, + }, + "allow_delete": { + Optional: true, + Type: schema.TypeList, + Description: "Allow HTTP requests using the DELETE method. By default, GET, HEAD, and OPTIONS requests are allowed, and all other methods result in a 501 error. Such content does not cache, and any DELETE requests pass to the origin. See also the `allowOptions`, `allowPatch`, `allowPost`, and `allowPut` behaviors. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Allows DELETE requests. Content does `not` cache.", + Type: schema.TypeBool, + }, + "allow_body": { + Optional: true, + Description: "Allows data in the body of the DELETE request.", + Type: schema.TypeBool, + }, + }, + }, + }, + "allow_https_cache_key_sharing": { + Optional: true, + Type: schema.TypeList, + Description: "HTTPS cache key sharing allows HTTP requests to be served from an HTTPS cache. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables HTTPS cache key sharing.", + Type: schema.TypeBool, + }, + }, + }, + }, + "allow_https_downgrade": { + Optional: true, + Type: schema.TypeList, + Description: "Passes HTTPS requests to origin as HTTP. This is useful when incorporating Standard TLS or Akamai's shared certificate delivery security with an origin that serves HTTP traffic. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Downgrades to HTTP protocol for the origin server.", + Type: schema.TypeBool, + }, + }, + }, + }, + "allow_options": { + Optional: true, + Type: schema.TypeList, + Description: "GET, HEAD, and OPTIONS requests are allowed by default. All other HTTP methods result in a 501 error. For full support of Cross-Origin Resource Sharing (CORS), you need to allow requests that use the OPTIONS method. If you're using the `corsSupport` behavior, do not disable OPTIONS requests. The response to an OPTIONS request is not cached, so the request always goes through the Akamai network to your origin, unless you use the `constructResponse` behavior to send responses directly from the Akamai network. See also the `allowDelete`, `allowPatch`, `allowPost`, and `allowPut` behaviors. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Set this to `true` to reflect the default policy where edge servers allow the OPTIONS method, without caching the response. Set this to `false` to deny OPTIONS requests and respond with a 501 error.", + Type: schema.TypeBool, + }, + }, + }, + }, + "allow_patch": { + Optional: true, + Type: schema.TypeList, + Description: "Allow HTTP requests using the PATCH method. By default, GET, HEAD, and OPTIONS requests are allowed, and all other methods result in a 501 error. Such content does not cache, and any PATCH requests pass to the origin. See also the `allowDelete`, `allowOptions`, `allowPost`, and `allowPut` behaviors. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Allows PATCH requests. Content does `not` cache.", + Type: schema.TypeBool, + }, + }, + }, + }, + "allow_post": { + Optional: true, + Type: schema.TypeList, + Description: "Allow HTTP requests using the POST method. By default, GET, HEAD, and OPTIONS requests are allowed, and POST requests are denied with 403 error. All other methods result in a 501 error. See also the `allowDelete`, `allowOptions`, `allowPatch`, and `allowPut` behaviors. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Allows POST requests.", + Type: schema.TypeBool, + }, + "allow_without_content_length": { + Optional: true, + Description: "By default, POST requests also require a `Content-Length` header, or they result in a 411 error. With this option enabled with no specified `Content-Length`, the edge server relies on a `Transfer-Encoding` header to chunk the data. If neither header is present, it assumes the request has no body, and it adds a header with a `0` value to the forward request.", + Type: schema.TypeBool, + }, + }, + }, + }, + "allow_put": { + Optional: true, + Type: schema.TypeList, + Description: "Allow HTTP requests using the PUT method. By default, GET, HEAD, and OPTIONS requests are allowed, and all other methods result in a 501 error. Such content does not cache, and any PUT requests pass to the origin. See also the `allowDelete`, `allowOptions`, `allowPatch`, and `allowPost` behaviors. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Allows PUT requests. Content does `not` cache.", + Type: schema.TypeBool, + }, + }, + }, + }, + "allow_transfer_encoding": { + Optional: true, + Type: schema.TypeList, + Description: "Controls whether to allow or deny Chunked Transfer Encoding (CTE) requests to pass to your origin. If your origin supports CTE, you should enable this behavior. This behavior also protects against a known issue when pairing `http2` and `webdav` behaviors within the same rule tree, in which case it's required. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Allows Chunked Transfer Encoding requests.", + Type: schema.TypeBool, + }, + }, + }, + }, + "alt_svc_header": { + Optional: true, + Type: schema.TypeList, + Description: "Sets the maximum age value for the Alternative Services (`Alt-Svc`) header. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "max_age": { + Optional: true, + Description: "Specifies the `max-age` value in seconds for the `Alt-Svc` header. The default `max-age` for an `Alt-Svc` header is 93600 seconds (26 hours).", + Type: schema.TypeInt, + }, + }, + }, + }, + "api_prioritization": { + Optional: true, + Type: schema.TypeList, + Description: "Enables the API Prioritization Cloudlet, which maintains continuity in user experience by serving an alternate static response when load is too high. You can configure rules using either the Cloudlets Policy Manager application or the `Cloudlets API`. Use this feature serve static API content, such as fallback JSON data. To serve non-API HTML content, use the `visitorPrioritization` behavior. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Activates the API Prioritization feature.", + Type: schema.TypeBool, + }, + "is_shared_policy": { + Optional: true, + Description: "Whether you want to apply the Cloudlet shared policy to an unlimited number of properties within your account. Learn more about shared policies and how to create them in `Cloudlets Policy Manager`.", + Type: schema.TypeBool, + }, + "cloudlet_policy": { + Optional: true, + Description: "Identifies the Cloudlet policy.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "cloudlet_shared_policy": { + Optional: true, + Description: "Identifies the Cloudlet shared policy to use with this behavior. Use the `Cloudlets API` to list available shared policies.", + Type: schema.TypeInt, + }, + "label": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "A label to distinguish this API Prioritization policy from any others in the same property.", + Type: schema.TypeString, + }, + "use_throttled_cp_code": { + Optional: true, + Description: "Specifies whether to apply an alternative CP code for requests served the alternate response.", + Type: schema.TypeBool, + }, + "throttled_cp_code": { + Optional: true, + Description: "Specifies the CP code as an object. You only need to provide the initial `id`, stripping any `cpc_` prefix to pass the integer to the rule tree. Additional CP code details may reflect back in subsequent read-only data.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "created_date": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "description": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "products": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "cp_code_limits": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "current_capacity": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit_type": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + "use_throttled_status_code": { + Optional: true, + Description: "Allows you to assign a specific HTTP response code to a throttled request.", + Type: schema.TypeBool, + }, + "throttled_status_code": { + ValidateDiagFunc: validateRegexOrVariable("^\\d{3}$"), + Optional: true, + Description: "Specifies the HTTP response code for requests that receive the alternate response.", + Type: schema.TypeInt, + }, + "net_storage": { + Optional: true, + Description: "Specify the NetStorage domain that contains the alternate response.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cp_code": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "download_domain_name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "g2o_token": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "net_storage_path": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "Specify the full NetStorage path for the alternate response, including trailing file name.", + Type: schema.TypeString, + }, + "alternate_response_cache_ttl": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(5, 30)), + Optional: true, + Description: "Specifies the alternate response's time to live in the cache, `5` minutes by default.", + Type: schema.TypeInt, + }, + }, + }, + }, + "application_load_balancer": { + Optional: true, + Type: schema.TypeList, + Description: "Enables the Application Load Balancer Cloudlet, which automates load balancing based on configurable criteria. To configure this behavior, use either the Cloudlets Policy Manager or the `Cloudlets API` to set up a policy. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Activates the Application Load Balancer Cloudlet.", + Type: schema.TypeBool, + }, + "cloudlet_policy": { + Optional: true, + Description: "Identifies the Cloudlet policy.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "label": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "A label to distinguish this Application Load Balancer policy from any others within the same property.", + Type: schema.TypeString, + }, + "stickiness_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "stickiness_cookie_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"NONE", "NEVER", "ON_BROWSER_CLOSE", "FIXED_DATE", "DURATION", "ORIGIN_SESSION"}, false)), + Optional: true, + Description: "Determines how a cookie persistently associates the client with a load-balanced origin.", + Type: schema.TypeString, + }, + "stickiness_expiration_date": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "Specifies when the cookie expires.", + Type: schema.TypeString, + }, + "stickiness_duration": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Sets how long it is before the cookie expires.", + Type: schema.TypeString, + }, + "stickiness_refresh": { + Optional: true, + Description: "Extends the duration of the cookie with each new request. When enabled, the `DURATION` thus specifies the latency between requests that would cause the cookie to expire.", + Type: schema.TypeBool, + }, + "origin_cookie_name": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "Specifies the name for your session cookie.", + Type: schema.TypeString, + }, + "specify_stickiness_cookie_domain": { + Optional: true, + Description: "Specifies whether to use a cookie domain with the stickiness cookie, to tell the browser to which domain to send the cookie.", + Type: schema.TypeBool, + }, + "stickiness_cookie_domain": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), + Optional: true, + Description: "Specifies the domain to track the stickiness cookie.", + Type: schema.TypeString, + }, + "stickiness_cookie_automatic_salt": { + Optional: true, + Description: "Sets whether to assign a `salt` value automatically to the cookie to prevent manipulation by the user. You should not enable this if sharing the population cookie across more than one property.", + Type: schema.TypeBool, + }, + "stickiness_cookie_salt": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "Specifies the stickiness cookie's salt value. Use this option to share the cookie across many properties.", + Type: schema.TypeString, + }, + "stickiness_cookie_set_http_only_flag": { + Optional: true, + Description: "Ensures the cookie is transmitted only over HTTP.", + Type: schema.TypeBool, + }, + "all_down_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "all_down_net_storage": { + Optional: true, + Description: "Specifies a NetStorage account for a static maintenance page as a fallback when no origins are available.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cp_code": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "download_domain_name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "g2o_token": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "all_down_net_storage_file": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "Specifies the fallback maintenance page's filename, expressed as a full path from the root of the NetStorage server.", + Type: schema.TypeString, + }, + "all_down_status_code": { + ValidateDiagFunc: validateRegexOrVariable("^\\d{3}$"), + Optional: true, + Description: "Specifies the HTTP response code when all load-balancing origins are unavailable.", + Type: schema.TypeString, + }, + "failover_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "failover_status_codes": { + Optional: true, + Description: "Specifies a set of HTTP status codes that signal a failure on the origin, in which case the cookie that binds the client to that origin is invalidated and the client is rerouted to another available origin.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "failover_mode": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"AUTOMATIC", "MANUAL", "DISABLED"}, false)), + Optional: true, + Description: "Determines what to do if an origin fails.", + Type: schema.TypeString, + }, + "failover_origin_map": { + Optional: true, + Description: "Specifies a fixed set of failover mapping rules.", + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "from_origin_id": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-\\.]+$"), + Optional: true, + Description: "Specifies the origin whose failure triggers the mapping rule.", + Type: schema.TypeString, + }, + "to_origin_ids": { + Optional: true, + Description: "Requests stuck to the `fromOriginId` origin retry for each alternate origin `toOriginIds`, until one succeeds.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "failover_attempts_threshold": { + Optional: true, + Description: "Sets the number of failed requests that would trigger the failover process.", + Type: schema.TypeInt, + }, + "cached_content_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "allow_cache_prefresh": { + Optional: true, + Description: "Allows the cache to prefresh. Only appropriate if all origins serve the same content for the same URL.", + Type: schema.TypeBool, + }, + }, + }, + }, + "audience_segmentation": { + Optional: true, + Type: schema.TypeList, + Description: "Allows you to divide your users into different segments based on a persistent cookie. You can configure rules using either the Cloudlets Policy Manager application or the `Cloudlets API`. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the Audience Segmentation cloudlet feature.", + Type: schema.TypeBool, + }, + "is_shared_policy": { + Optional: true, + Description: "Whether you want to use a shared policy for a Cloudlet. Learn more about shared policies and how to create them in `Cloudlets Policy Manager`.", + Type: schema.TypeBool, + }, + "cloudlet_policy": { + Optional: true, + Description: "Identifies the Cloudlet policy.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "cloudlet_shared_policy": { + Optional: true, + Description: "This identifies the Cloudlet shared policy to use with this behavior. You can list available shared policies with the `Cloudlets API`.", + Type: schema.TypeInt, + }, + "label": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "Specifies a suffix to append to the cookie name. This helps distinguish this audience segmentation policy from any others within the same property.", + Type: schema.TypeString, + }, + "segment_tracking_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "segment_tracking_method": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IN_QUERY_PARAM", "IN_COOKIE_HEADER", "IN_CUSTOM_HEADER", "NONE"}, false)), + Optional: true, + Description: "Specifies the method to pass segment information to the origin. The Cloudlet passes the rule applied to a given request location.", + Type: schema.TypeString, + }, + "segment_tracking_query_param": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "This query parameter specifies the name of the segmentation rule.", + Type: schema.TypeString, + }, + "segment_tracking_cookie_name": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "This cookie name specifies the name of the segmentation rule.", + Type: schema.TypeString, + }, + "segment_tracking_custom_header": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "This custom HTTP header specifies the name of the segmentation rule.", + Type: schema.TypeString, + }, + "population_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "population_cookie_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"NEVER", "ON_BROWSER_CLOSE", "DURATION"}, false)), + Optional: true, + Description: "Specifies when the segmentation cookie expires.", + Type: schema.TypeString, + }, + "population_duration": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Specifies the lifetime of the segmentation cookie.", + Type: schema.TypeString, + }, + "population_refresh": { + Optional: true, + Description: "If disabled, sets the expiration time only if the cookie is not yet present in the request.", + Type: schema.TypeBool, + }, + "specify_population_cookie_domain": { + Optional: true, + Description: "Whether to specify a cookie domain with the population cookie. It tells the browser to which domain to send the cookie.", + Type: schema.TypeBool, + }, + "population_cookie_domain": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), + Optional: true, + Description: "Specifies the domain to track the population cookie.", + Type: schema.TypeString, + }, + "population_cookie_automatic_salt": { + Optional: true, + Description: "Whether to assign a `salt` value automatically to the cookie to prevent manipulation by the user. You should not enable if sharing the population cookie across more than one property.", + Type: schema.TypeBool, + }, + "population_cookie_salt": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "Specifies the cookie's salt value. Use this option to share the cookie across many properties.", + Type: schema.TypeString, + }, + "population_cookie_include_rule_name": { + Optional: true, + Description: "When enabled, includes in the session cookie the name of the rule in which this behavior appears.", + Type: schema.TypeBool, + }, + }, + }, + }, + "auto_domain_validation": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior allows standard TLS domain validated certificates to renew automatically. Apply it after using the `Certificate Provisioning System` to request a certificate for a hostname. To provision certificates programmatically, see the `Certificate Provisioning System API`. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "autodv": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + "base_directory": { + Optional: true, + Type: schema.TypeList, + Description: "Prefix URLs sent to the origin with a base path. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "value": { + ValidateDiagFunc: validateRegexOrVariable("^/([^:#\\[\\]@/?]+/)*$"), + Optional: true, + Description: "Specifies the base path of content on your origin server. The value needs to begin and end with a slash (`/`) character, for example `/parent/child/`.", + Type: schema.TypeString, + }, + }, + }, + }, + "boss_beaconing": { + Optional: true, + Type: schema.TypeList, + Description: "Triggers diagnostic data beacons for use with BOSS, Akamai's monitoring and diagnostics system. This behavior is for internal usage only. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enable diagnostic data beacons.", + Type: schema.TypeBool, + }, + "cpcodes": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9 ]*$"), + Optional: true, + Description: "The space-separated list of CP codes that trigger the beacons. You need to specify the same set of CP codes within BOSS.", + Type: schema.TypeString, + }, + "request_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"EDGE", "EDGE_MIDGRESS"}, false)), + Optional: true, + Description: "Specify when to trigger a beacon.", + Type: schema.TypeString, + }, + "forward_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"MIDGRESS", "ORIGIN", "MIDGRESS_ORIGIN"}, false)), + Optional: true, + Description: "Specify when to trigger a beacon.", + Type: schema.TypeString, + }, + "sampling_frequency": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SAMPLING_FREQ_0_0", "SAMPLING_FREQ_0_1"}, false)), + Optional: true, + Description: "Specifies a sampling frequency or disables beacons.", + Type: schema.TypeString, + }, + "conditional_sampling_frequency": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CONDITIONAL_SAMPLING_FREQ_0_0", "CONDITIONAL_SAMPLING_FREQ_0_1", "CONDITIONAL_SAMPLING_FREQ_0_2", "CONDITIONAL_SAMPLING_FREQ_0_3"}, false)), + Optional: true, + Description: "Specifies a conditional sampling frequency or disables beacons.", + Type: schema.TypeString, + }, + "conditional_http_status": { + Optional: true, + Description: "Specifies the set of response status codes or ranges that trigger the beacon.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "conditional_error_pattern": { + Optional: true, + Description: "A space-separated set of error patterns that trigger beacons to conditional feeds. Each pattern can include wildcards, where `?` matches a single character and `*` matches zero or more characters. For example, `*CONNECT* *DENIED*` matches two different words as substrings.", + Type: schema.TypeString, + }, + }, + }, + }, + "breadcrumbs": { + Optional: true, + Type: schema.TypeList, + Description: "Provides per-HTTP transaction visibility into a request for content, regardless of how deep the request goes into the Akamai platform. The `Akamai-Request-BC` response header includes various data, such as network health and the location in the Akamai network used to serve content, which simplifies log review for troubleshooting. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the Breadcrumbs feature.", + Type: schema.TypeBool, + }, + "opt_mode": { + Optional: true, + Description: "Specifies whether to include Breadcrumbs data in the response header. To bypass the current `optMode`, append the opposite `ak-bc` query string to each request from your player.", + Type: schema.TypeBool, + }, + "logging_enabled": { + Optional: true, + Description: "Whether to collect all Breadcrumbs data in logs, including the response headers sent a requesting client. This can also be helpful if you're using `DataStream 2` to retrieve log data. This way, all Breadcrumbs data is carried in the logs it uses.", + Type: schema.TypeBool, + }, + }, + }, + }, + "break_connection": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior simulates an origin connection problem, typically to test an accompanying `failAction` policy. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the break connection behavior.", + Type: schema.TypeBool, + }, + }, + }, + }, + "brotli": { + Optional: true, + Type: schema.TypeList, + Description: "Accesses Brotli-compressed assets from your origin and caches them on edge servers. This doesn't compress resources within the content delivery network in real time. You need to set up Brotli compression separately on your origin. If a requesting client doesn't support Brotli, edge servers deliver non-Brotli resources. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Fetches Brotli-compressed assets from your origin and caches them on edge servers.", + Type: schema.TypeBool, + }, + }, + }, + }, + "cache_error": { + Optional: true, + Type: schema.TypeList, + Description: "Caches the origin's error responses to decrease server load. Applies for 10 seconds by default to the following HTTP codes: `204`, `305`, `404`, `405`, `501`, `502`, `503`, `504`, and `505`. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Activates the error-caching behavior.", + Type: schema.TypeBool, + }, + "ttl": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Overrides the default caching duration of `10s`. Note that if set to `0`, it is equivalent to `no-cache`, which forces revalidation and may cause a traffic spike. This can be counterproductive when, for example, the origin is producing an error code of `500`.", + Type: schema.TypeString, + }, + "preserve_stale": { + Optional: true, + Description: "When enabled, the edge server preserves stale cached objects when the origin returns `500`, `502`, `503`, and `504` error codes. This avoids re-fetching and re-caching content after transient errors.", + Type: schema.TypeBool, + }, + }, + }, + }, + "cache_id": { + Optional: true, + Type: schema.TypeList, + Description: "Controls which query parameters, headers, and cookies are included in or excluded from the cache key identifier. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "rule": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"INCLUDE_QUERY_PARAMS", "INCLUDE_COOKIES", "INCLUDE_HEADERS", "EXCLUDE_QUERY_PARAMS", "INCLUDE_ALL_QUERY_PARAMS", "INCLUDE_VARIABLE", "INCLUDE_URL"}, false)), + Optional: true, + Description: "Specifies how to modify the cache ID.", + Type: schema.TypeString, + }, + "include_value": { + Optional: true, + Description: "Includes the value of the specified elements in the cache ID. Otherwise only their names are included.", + Type: schema.TypeBool, + }, + "optional": { + Optional: true, + Description: "Requires the behavior's specified elements to be present for content to cache. When disabled, requests that lack the specified elements are still cached.", + Type: schema.TypeBool, + }, + "elements": { + Optional: true, + Description: "Specifies the names of the query parameters, cookies, or headers to include or exclude from the cache ID.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "variable_name": { + Optional: true, + Description: "Specifies the name of the variable you want to include in the cache key.", + Type: schema.TypeString, + }, + }, + }, + }, + "cache_key_ignore_case": { + Optional: true, + Type: schema.TypeList, + Description: "By default, cache keys are generated under the assumption that path and filename components are case-sensitive, so that `File.html` and `file.html` use separate cache keys. Enabling this behavior forces URL components whose case varies to resolve to the same cache key. Enable this behavior if your origin server is already case-insensitive, such as those based on Microsoft IIS. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Ignores case when forming cache keys.", + Type: schema.TypeBool, + }, + }, + }, + }, + "cache_key_query_params": { + Optional: true, + Type: schema.TypeList, + Description: "By default, cache keys are formed as URLs with full query strings. This behavior allows you to consolidate cached objects based on specified sets of query parameters. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "behavior": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"INCLUDE_ALL_PRESERVE_ORDER", "INCLUDE_ALL_ALPHABETIZE_ORDER", "IGNORE_ALL", "INCLUDE", "IGNORE"}, false)), + Optional: true, + Description: "Configures how sets of query string parameters translate to cache keys. Be careful not to ignore any parameters that result in substantially different content, as it is `not` reflected in the cached object.", + Type: schema.TypeString, + }, + "parameters": { + Optional: true, + Description: "Specifies the set of parameter field names to include in or exclude from the cache key. By default, these match the field names as string prefixes.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "exact_match": { + Optional: true, + Description: "When enabled, `parameters` needs to match exactly. Keep disabled to match string prefixes.", + Type: schema.TypeBool, + }, + }, + }, + }, + "cache_key_rewrite": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior rewrites a default cache key's path. Contact Akamai Professional Services for help configuring it. This behavior is for internal usage only. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "purge_key": { + ValidateDiagFunc: validateRegexOrVariable("^[\\w-]+$"), + Optional: true, + Description: "Specifies the new cache key path as an alphanumeric value.", + Type: schema.TypeString, + }, + }, + }, + }, + "cache_post": { + Optional: true, + Type: schema.TypeList, + Description: "By default, POST requests are passed to the origin. This behavior overrides the default, and allows you to cache POST responses. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables caching of POST responses.", + Type: schema.TypeBool, + }, + "use_body": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IGNORE", "MD5", "QUERY"}, false)), + Optional: true, + Description: "Define how and whether to use the POST message body as a cache key.", + Type: schema.TypeString, + }, + }, + }, + }, + "cache_redirect": { + Optional: true, + Type: schema.TypeList, + Description: "Controls the caching of HTTP 302 and 307 temporary redirects. By default, Akamai edge servers don't cache them. Enabling this behavior instructs edge servers to allow these redirects to be cached the same as HTTP 200 responses. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the redirect caching behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + "cache_tag": { + Optional: true, + Type: schema.TypeList, + Description: "This adds a cache tag to the requested object. With cache tags, you can flexibly fast purge tagged segments of your cached content. You can either define these tags with an `Edge-Cache-Tag` header at the origin server level, or use this behavior to directly add a cache tag to the object as the edge server caches it. The `cacheTag` behavior can only take a single value, including a variable. If you want to specify more tags for an object, add a few instances of this behavior to your configuration. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "tag": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9\\&\\'\\^\\-\\$\\!\\`\\#\\%\\.\\+\\~\\_\\|\\/]+$"), + Optional: true, + Description: "Specifies the cache tag you want to add to your cached content. A cache tag is only added when the object is first added to cache. A single cache tag can't exceed 128 characters and can only include alphanumeric characters, plus this class of characters: ```[!#$%'+./^_`|~-]```", + Type: schema.TypeString, + }, + }, + }, + }, + "cache_tag_visible": { + Optional: true, + Type: schema.TypeList, + Description: "Cache tags are comma-separated string values you define within an `Edge-Cache-Tag` header. You can use them to flexibly fast purge tagged segments of your cached content. You can either define these headers at the origin server level, or use the `modifyOutgoingResponseHeader` behavior to configure them at the edge. Apply this behavior to confirm you're deploying the intended set of cache tags to your content. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "behavior": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"NEVER", "PRAGMA_HEADER", "ALWAYS"}, false)), + Optional: true, + Description: "Specifies when to include the `Edge-Cache-Tag` in responses.", + Type: schema.TypeString, + }, + }, + }, + }, + "caching": { + Optional: true, + Type: schema.TypeList, + Description: "Control content caching on edge servers: whether or not to cache, whether to honor the origin's caching headers, and for how long to cache. Note that any `NO_STORE` or `BYPASS_CACHE` HTTP headers set on the origin's content override this behavior. For more details on how caching works in Property Manager, see the `Learn about caching` section in the guide. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "behavior": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"MAX_AGE", "NO_STORE", "BYPASS_CACHE", "CACHE_CONTROL_AND_EXPIRES", "CACHE_CONTROL", "EXPIRES"}, false)), + Optional: true, + Description: "Specify the caching option.", + Type: schema.TypeString, + }, + "must_revalidate": { + Optional: true, + Description: "Determines what to do once the cached content has expired, by which time the Akamai platform should have re-fetched and validated content from the origin. If enabled, only allows the re-fetched content to be served. If disabled, may serve stale content if the origin is unavailable.", + Type: schema.TypeBool, + }, + "ttl": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "The maximum time content may remain cached. Setting the value to `0` is the same as setting a `no-cache` header, which forces content to revalidate.", + Type: schema.TypeString, + }, + "default_ttl": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Set the `MAX_AGE` header for the cached content.", + Type: schema.TypeString, + }, + "cache_control_directives": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "enhanced_rfc_support": { + Optional: true, + Description: "This enables honoring particular `Cache-Control` header directives from the origin. Supports all official `RFC 7234` directives except for `no-transform`.", + Type: schema.TypeBool, + }, + "cacheability_settings": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "honor_no_store": { + Optional: true, + Description: "Instructs edge servers not to cache the response when the origin response includes the `no-store` directive.", + Type: schema.TypeBool, + }, + "honor_private": { + Optional: true, + Description: "Instructs edge servers not to cache the response when the origin response includes the `private` directive.", + Type: schema.TypeBool, + }, + "honor_no_cache": { + Optional: true, + Description: "With the `no-cache` directive present in the response, this instructs edge servers to validate or refetch the response for each request. Effectively, set the time to live `ttl` to zero seconds.", + Type: schema.TypeBool, + }, + "expiration_settings": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "honor_max_age": { + Optional: true, + Description: "This instructs edge servers to cache the object for a length of time set by the `max-age` directive in the response. When present in the origin response, this directive takes precedence over the `max-age` directive and the `defaultTtl` setting.", + Type: schema.TypeBool, + }, + "honor_s_maxage": { + Optional: true, + Description: "Instructs edge servers to cache the object for a length of time set by the `s-maxage` directive in the response. When present in the origin response, this directive takes precedence over the `max-age` directive and the `defaultTtl` setting.", + Type: schema.TypeBool, + }, + "revalidation_settings": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "honor_must_revalidate": { + Optional: true, + Description: "This instructs edge servers to successfully revalidate with the origin server before using stale objects in the cache to satisfy new requests.", + Type: schema.TypeBool, + }, + "honor_proxy_revalidate": { + Optional: true, + Description: "With the `proxy-revalidate` directive present in the response, this instructs edge servers to successfully revalidate with the origin server before using stale objects in the cache to satisfy new requests.", + Type: schema.TypeBool, + }, + }, + }, + }, + "central_authorization": { + Optional: true, + Type: schema.TypeList, + Description: "Forward client requests to the origin server for authorization, along with optional `Set-Cookie` headers, useful when you need to maintain tight access control. The edge server forwards an `If-Modified-Since` header, to which the origin needs to respond with a `304` (Not-Modified) HTTP status when authorization succeeds. If so, the edge server responds to the client with the cached object, since it does not need to be re-acquired from the origin. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the centralized authorization behavior.", + Type: schema.TypeBool, + }, + }, + }, + }, + "chase_redirects": { + Optional: true, + Type: schema.TypeList, + Description: "Controls whether the edge server chases any redirects served from the origin. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Allows edge servers to chase redirects.", + Type: schema.TypeBool, + }, + "limit": { + Optional: true, + Description: "Specifies, as a string, the maximum number of redirects to follow.", + Type: schema.TypeString, + }, + "serve404": { + Optional: true, + Description: "Once the redirect `limit` is reached, enabling this option serves an HTTP `404` (Not Found) error instead of the last redirect.", + Type: schema.TypeBool, + }, + }, + }, + }, + "client_certificate_auth": { + Optional: true, + Type: schema.TypeList, + Description: "Sends a `Client-To-Edge` header to your origin server with details from the mutual TLS certificate sent from the requesting client to the edge network. This establishes transitive trust between the client and your origin server. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enable": { + Optional: true, + Description: "Constructs the `Client-To-Edge` authentication header using information from the client to edge mTLS handshake and forwards it to your origin. You can configure your origin to acknowledge the header to enable transitive trust. Some form of the client x.509 certificate needs to be included in the header. You can include the full certificate or specific attributes.", + Type: schema.TypeBool, + }, + "enable_complete_client_certificate": { + Optional: true, + Description: "Whether to include the complete client certificate in the header, in its binary (DER) format. DER-formatted certificates leave out the `BEGIN CERTIFICATE/END CERTIFICATE` statements and most often use the `.der` extension. Alternatively, you can specify individual `clientCertificateAttributes` you want included in the request.", + Type: schema.TypeBool, + }, + "client_certificate_attributes": { + Optional: true, + Description: "Specify client certificate attributes to include in the `Client-To-Edge` authentication header that's sent to your origin server.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "enable_client_certificate_validation_status": { + Optional: true, + Description: "Whether to include the current validation status of the client certificate in the `Client-To-Edge` authentication header. This verifies the validation status of the certificate, regardless of the certificate attributes you're including in the header.", + Type: schema.TypeBool, + }, + }, + }, + }, + "client_characteristics": { + Optional: true, + Type: schema.TypeList, + Description: "Specifies characteristics of the client ecosystem. Akamai uses this information to optimize your metadata configuration, which may result in better end-user performance. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "country": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"GLOBAL", "GLOBAL_US_CENTRIC", "GLOBAL_EU_CENTRIC", "GLOBAL_ASIA_CENTRIC", "EUROPE", "NORTH_AMERICA", "SOUTH_AMERICA", "NORDICS", "ASIA_PACIFIC", "AUSTRALIA", "GERMANY", "INDIA", "ITALY", "JAPAN", "TAIWAN", "UNITED_KINGDOM", "OTHER", "UNKNOWN"}, false)), + Optional: true, + Description: "Specifies the client request's geographic region.", + Type: schema.TypeString, + }, + }, + }, + }, + "cloud_interconnects": { + Optional: true, + Type: schema.TypeList, + Description: "Cloud Interconnects forwards traffic from edge servers to your cloud origin through Private Network Interconnects (PNIs), helping to reduce the egress costs at the origin. Supports origins hosted by Google Cloud Provider (GCP). This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Channels the traffic to maximize the egress discount at the origin.", + Type: schema.TypeBool, + }, + "cloud_locations": { + Optional: true, + Description: "Specifies the geographical locations of your cloud origin. You should enable Cloud Interconnects only if your origin is in one of these locations, since GCP doesn't provide a discount for egress traffic for any other regions.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "cloud_wrapper": { + Optional: true, + Type: schema.TypeList, + Description: "`Cloud Wrapper` maximizes origin offload for large libraries of video, game, and software downloads by optimizing data caches in regions nearest to your origin. You can't use this behavior in conjunction with `sureRoute` or `tieredDistribution`. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables Cloud Wrapper behavior.", + Type: schema.TypeBool, + }, + "location": { + Optional: true, + Description: "The location you want to distribute your Cloud Wrapper cache space to. This behavior allows all locations configured in your Cloud Wrapper configuration.", + Type: schema.TypeString, + }, + }, + }, + }, + "cloud_wrapper_advanced": { + Optional: true, + Type: schema.TypeList, + Description: "Your account representative uses this behavior to implement a customized failover configuration on your behalf. Use Cloud Wrapper Advanced with an enabled `cloudWrapper` behavior in the same rule. This behavior is for internal usage only. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables failover for Cloud Wrapper.", + Type: schema.TypeBool, + }, + "failover_map": { + Optional: true, + Description: "Specifies the failover map to handle Cloud Wrapper failures. Contact your account representative for more information.", + Type: schema.TypeString, + }, + "custom_failover_map": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z][a-zA-Z0-9-]*$"), + Optional: true, + Description: "Specifies the custom failover map to handle Cloud Wrapper failures. Contact your account representative for more information.", + Type: schema.TypeString, + }, + }, + }, + }, + "common_media_client_data": { + Optional: true, + Type: schema.TypeList, + Description: "Use this behavior to send expanded playback information as CMCD metadata in requests from a media player. Edge servers may use this metadata for segment prefetching to optimize your content's delivery, or for logging. For more details and additional property requirements, see the `Adaptive Media Delivery` documentation. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enable_cmcd_segment_prefetch": { + Optional: true, + Description: "Uses Common Media Client Data (CMCD) metadata to determine the segment URLs your origin server prefetches to speed up content delivery.", + Type: schema.TypeBool, + }, + }, + }, + }, + "conditional_origin": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "origin_id": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-\\.]+$"), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "construct_response": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior constructs an HTTP response, complete with HTTP status code and body, to serve from the edge independently of your origin. For example, you might want to send a customized response if the URL doesn't point to an object on the origin server, or if the end user is not authorized to view the requested content. You can use it with all request methods you allow for your property, including POST. For more details, see the `allowOptions`, `allowPatch`, `allowPost`, `allowPut`, and `allowDelete` behaviors. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Serves the custom response.", + Type: schema.TypeBool, + }, + "body": { + Optional: true, + Description: "HTML response of up to 2000 characters to send to the end-user client.", + Type: schema.TypeString, + }, + "response_code": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntInSlice([]int{200, 404, 401, 403, 405, 417, 500, 501, 502, 503, 504})), + Optional: true, + Description: "The HTTP response code to send to the end-user client.", + Type: schema.TypeInt, + }, + "force_eviction": { + Optional: true, + Description: "For GET requests from clients, this forces edge servers to evict the underlying object from cache. Defaults to `false`.", + Type: schema.TypeBool, + }, + "ignore_purge": { + Optional: true, + Description: "Whether to ignore the custom response when purging.", + Type: schema.TypeBool, + }, + }, + }, + }, + "content_characteristics": { + Optional: true, + Type: schema.TypeList, + Description: "Specifies characteristics of the delivered content. Akamai uses this information to optimize your metadata configuration, which may result in better origin offload and end-user performance. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "object_size": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LESS_THAN_1MB", "ONE_MB_TO_TEN_MB", "TEN_MB_TO_100_MB", "OTHER", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the size of the object retrieved from the origin.", + Type: schema.TypeString, + }, + "popularity_distribution": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LONG_TAIL", "ALL_POPULAR", "OTHER", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the content's expected popularity.", + Type: schema.TypeString, + }, + "catalog_size": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SMALL", "MEDIUM", "LARGE", "EXTRA_LARGE", "OTHER", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the total size of the content library delivered.", + Type: schema.TypeString, + }, + "content_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"USER_GENERATED", "WEB_OBJECTS", "SOFTWARE", "IMAGES", "OTHER_OBJECTS", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the type of content.", + Type: schema.TypeString, + }, + }, + }, + }, + "content_characteristics_amd": { + Optional: true, + Type: schema.TypeList, + Description: "Specifies characteristics of the delivered content. Akamai uses this information to optimize your metadata configuration, which may result in better origin offload and end-user performance. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "catalog_size": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SMALL", "MEDIUM", "LARGE", "EXTRA_LARGE", "OTHER", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the total size of the content library delivered.", + Type: schema.TypeString, + }, + "content_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SD", "HD", "ULTRA_HD", "OTHER", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the quality of media content.", + Type: schema.TypeString, + }, + "popularity_distribution": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LONG_TAIL", "ALL_POPULAR", "OTHER", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the content's expected popularity.", + Type: schema.TypeString, + }, + "hls": { + Optional: true, + Description: "Enable delivery of HLS media.", + Type: schema.TypeBool, + }, + "segment_duration_hls": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SEGMENT_DURATION_2S", "SEGMENT_DURATION_4S", "SEGMENT_DURATION_6S", "SEGMENT_DURATION_8S", "SEGMENT_DURATION_10S", "OTHER"}, false)), + Optional: true, + Description: "Specifies the duration of individual segments.", + Type: schema.TypeString, + }, + "segment_duration_hls_custom": { + Optional: true, + Description: "Customizes the number of seconds for the segment.", + Type: schema.TypeFloat, + }, + "segment_size_hls": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LESS_THAN_1MB", "ONE_MB_TO_TEN_MB", "TEN_MB_TO_100_MB", "GREATER_THAN_100MB", "UNKNOWN", "OTHER"}, false)), + Optional: true, + Description: "Specifies the size of the media object retrieved from the origin.", + Type: schema.TypeString, + }, + "hds": { + Optional: true, + Description: "Enable delivery of HDS media.", + Type: schema.TypeBool, + }, + "segment_duration_hds": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SEGMENT_DURATION_2S", "SEGMENT_DURATION_4S", "SEGMENT_DURATION_6S", "SEGMENT_DURATION_8S", "SEGMENT_DURATION_10S", "OTHER"}, false)), + Optional: true, + Description: "Specifies the duration of individual fragments.", + Type: schema.TypeString, + }, + "segment_duration_hds_custom": { + Optional: true, + Description: "Customizes the number of seconds for the fragment.", + Type: schema.TypeInt, + }, + "segment_size_hds": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LESS_THAN_1MB", "ONE_MB_TO_TEN_MB", "TEN_MB_TO_100_MB", "GREATER_THAN_100MB", "UNKNOWN", "OTHER"}, false)), + Optional: true, + Description: "Specifies the size of the media object retrieved from the origin.", + Type: schema.TypeString, + }, + "dash": { + Optional: true, + Description: "Enable delivery of DASH media.", + Type: schema.TypeBool, + }, + "segment_duration_dash": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SEGMENT_DURATION_2S", "SEGMENT_DURATION_4S", "SEGMENT_DURATION_6S", "SEGMENT_DURATION_8S", "SEGMENT_DURATION_10S", "OTHER"}, false)), + Optional: true, + Description: "Specifies the duration of individual segments.", + Type: schema.TypeString, + }, + "segment_duration_dash_custom": { + Optional: true, + Description: "Customizes the number of seconds for the segment.", + Type: schema.TypeInt, + }, + "segment_size_dash": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LESS_THAN_1MB", "ONE_MB_TO_TEN_MB", "TEN_MB_TO_100_MB", "GREATER_THAN_100MB", "UNKNOWN", "OTHER"}, false)), + Optional: true, + Description: "Specifies the size of the media object retrieved from the origin.", + Type: schema.TypeString, + }, + "smooth": { + Optional: true, + Description: "Enable delivery of Smooth media.", + Type: schema.TypeBool, + }, + "segment_duration_smooth": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SEGMENT_DURATION_2S", "SEGMENT_DURATION_4S", "SEGMENT_DURATION_6S", "SEGMENT_DURATION_8S", "SEGMENT_DURATION_10S", "OTHER"}, false)), + Optional: true, + Description: "Specifies the duration of individual fragments.", + Type: schema.TypeString, + }, + "segment_duration_smooth_custom": { + Optional: true, + Description: "Customizes the number of seconds for the fragment.", + Type: schema.TypeFloat, + }, + "segment_size_smooth": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LESS_THAN_1MB", "ONE_MB_TO_TEN_MB", "TEN_MB_TO_100_MB", "GREATER_THAN_100MB", "UNKNOWN", "OTHER"}, false)), + Optional: true, + Description: "Specifies the size of the media object retrieved from the origin.", + Type: schema.TypeString, + }, + }, + }, + }, + "content_characteristics_dd": { + Optional: true, + Type: schema.TypeList, + Description: "Specifies characteristics of the delivered content. Akamai uses this information to optimize your metadata configuration, which may result in better origin offload and end-user performance. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "object_size": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LESS_THAN_1MB", "ONE_MB_TO_TEN_MB", "TEN_MB_TO_100_MB", "GREATER_THAN_100MB", "OTHER", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the size of the object retrieved from the origin.", + Type: schema.TypeString, + }, + "popularity_distribution": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LONG_TAIL", "ALL_POPULAR", "OTHER", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the content's expected popularity.", + Type: schema.TypeString, + }, + "catalog_size": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SMALL", "MEDIUM", "LARGE", "EXTRA_LARGE", "OTHER", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the total size of the content library delivered.", + Type: schema.TypeString, + }, + "content_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"VIDEO", "SOFTWARE", "SOFTWARE_PATCH", "GAME", "GAME_PATCH", "OTHER_DOWNLOADS", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the type of content.", + Type: schema.TypeString, + }, + "optimize_option": { + Optional: true, + Description: "Optimizes the delivery throughput and download times for large files.", + Type: schema.TypeBool, + }, + }, + }, + }, + "content_characteristics_wsd_large_file": { + Optional: true, + Type: schema.TypeList, + Description: "Specifies characteristics of the delivered content, specifically targeted to delivering large files. Akamai uses this information to optimize your metadata configuration, which may result in better origin offload and end-user performance. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "object_size": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LESS_THAN_1MB", "ONE_MB_TO_TEN_MB", "TEN_MB_TO_100_MB", "GREATER_THAN_100MB", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the size of the object retrieved from the origin.", + Type: schema.TypeString, + }, + "popularity_distribution": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LONG_TAIL", "ALL_POPULAR", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the content's expected popularity.", + Type: schema.TypeString, + }, + "catalog_size": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SMALL", "MEDIUM", "LARGE", "EXTRA_LARGE", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the total size of the content library delivered.", + Type: schema.TypeString, + }, + "content_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"VIDEO", "SOFTWARE", "SOFTWARE_PATCH", "GAME", "GAME_PATCH", "OTHER_DOWNLOADS", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the type of content.", + Type: schema.TypeString, + }, + }, + }, + }, + "content_characteristics_wsd_live": { + Optional: true, + Type: schema.TypeList, + Description: "Specifies characteristics of the delivered content, specifically targeted to delivering live video. Akamai uses this information to optimize your metadata configuration, which may result in better origin offload and end-user performance. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "catalog_size": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SMALL", "MEDIUM", "LARGE", "EXTRA_LARGE", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the total size of the content library delivered.", + Type: schema.TypeString, + }, + "content_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SD", "HD", "ULTRA_HD", "OTHER", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the quality of media content.", + Type: schema.TypeString, + }, + "popularity_distribution": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LONG_TAIL", "ALL_POPULAR", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the content's expected popularity.", + Type: schema.TypeString, + }, + "hls": { + Optional: true, + Description: "Enable delivery of HLS media.", + Type: schema.TypeBool, + }, + "segment_duration_hls": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SEGMENT_DURATION_2S", "SEGMENT_DURATION_4S", "SEGMENT_DURATION_6S", "SEGMENT_DURATION_8S", "SEGMENT_DURATION_10S"}, false)), + Optional: true, + Description: "Specifies the duration of individual segments.", + Type: schema.TypeString, + }, + "segment_size_hls": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LESS_THAN_1MB", "ONE_MB_TO_TEN_MB", "TEN_MB_TO_100_MB", "GREATER_THAN_100MB", "UNKNOWN", "OTHER"}, false)), + Optional: true, + Description: "Specifies the size of the media object retrieved from the origin.", + Type: schema.TypeString, + }, + "hds": { + Optional: true, + Description: "Enable delivery of HDS media.", + Type: schema.TypeBool, + }, + "segment_duration_hds": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SEGMENT_DURATION_2S", "SEGMENT_DURATION_4S", "SEGMENT_DURATION_6S", "SEGMENT_DURATION_8S", "SEGMENT_DURATION_10S"}, false)), + Optional: true, + Description: "Specifies the duration of individual fragments.", + Type: schema.TypeString, + }, + "segment_size_hds": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LESS_THAN_1MB", "ONE_MB_TO_TEN_MB", "TEN_MB_TO_100_MB", "GREATER_THAN_100MB", "UNKNOWN", "OTHER"}, false)), + Optional: true, + Description: "Specifies the size of the media object retrieved from the origin.", + Type: schema.TypeString, + }, + "dash": { + Optional: true, + Description: "Enable delivery of DASH media.", + Type: schema.TypeBool, + }, + "segment_duration_dash": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SEGMENT_DURATION_2S", "SEGMENT_DURATION_4S", "SEGMENT_DURATION_6S", "SEGMENT_DURATION_8S", "SEGMENT_DURATION_10S"}, false)), + Optional: true, + Description: "Specifies the duration of individual segments.", + Type: schema.TypeString, + }, + "segment_size_dash": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LESS_THAN_1MB", "ONE_MB_TO_TEN_MB", "TEN_MB_TO_100_MB", "GREATER_THAN_100MB", "UNKNOWN", "OTHER"}, false)), + Optional: true, + Description: "Specifies the size of the media object retrieved from the origin.", + Type: schema.TypeString, + }, + "smooth": { + Optional: true, + Description: "Enable delivery of Smooth media.", + Type: schema.TypeBool, + }, + "segment_duration_smooth": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SEGMENT_DURATION_2S", "SEGMENT_DURATION_4S", "SEGMENT_DURATION_6S", "SEGMENT_DURATION_8S", "SEGMENT_DURATION_10S"}, false)), + Optional: true, + Description: "Specifies the duration of individual fragments.", + Type: schema.TypeString, + }, + "segment_size_smooth": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LESS_THAN_1MB", "ONE_MB_TO_TEN_MB", "TEN_MB_TO_100_MB", "GREATER_THAN_100MB", "UNKNOWN", "OTHER"}, false)), + Optional: true, + Description: "Specifies the size of the media object retrieved from the origin.", + Type: schema.TypeString, + }, + }, + }, + }, + "content_characteristics_wsd_vod": { + Optional: true, + Type: schema.TypeList, + Description: "Specifies characteristics of the delivered content, specifically targeted to delivering on-demand video. Akamai uses this information to optimize your metadata configuration, which may result in better origin offload and end-user performance. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "catalog_size": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SMALL", "MEDIUM", "LARGE", "EXTRA_LARGE", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the total size of the content library delivered.", + Type: schema.TypeString, + }, + "content_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SD", "HD", "ULTRA_HD", "OTHER", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the quality of media content.", + Type: schema.TypeString, + }, + "popularity_distribution": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LONG_TAIL", "ALL_POPULAR", "UNKNOWN"}, false)), + Optional: true, + Description: "Optimize based on the content's expected popularity.", + Type: schema.TypeString, + }, + "hls": { + Optional: true, + Description: "Enable delivery of HLS media.", + Type: schema.TypeBool, + }, + "segment_duration_hls": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SEGMENT_DURATION_2S", "SEGMENT_DURATION_4S", "SEGMENT_DURATION_6S", "SEGMENT_DURATION_8S", "SEGMENT_DURATION_10S"}, false)), + Optional: true, + Description: "Specifies the duration of individual segments.", + Type: schema.TypeString, + }, + "segment_size_hls": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LESS_THAN_1MB", "ONE_MB_TO_TEN_MB", "TEN_MB_TO_100_MB", "GREATER_THAN_100MB", "UNKNOWN", "OTHER"}, false)), + Optional: true, + Description: "Specifies the size of the media object retrieved from the origin.", + Type: schema.TypeString, + }, + "hds": { + Optional: true, + Description: "Enable delivery of HDS media.", + Type: schema.TypeBool, + }, + "segment_duration_hds": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SEGMENT_DURATION_2S", "SEGMENT_DURATION_4S", "SEGMENT_DURATION_6S", "SEGMENT_DURATION_8S", "SEGMENT_DURATION_10S"}, false)), + Optional: true, + Description: "Specifies the duration of individual fragments.", + Type: schema.TypeString, + }, + "segment_size_hds": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LESS_THAN_1MB", "ONE_MB_TO_TEN_MB", "TEN_MB_TO_100_MB", "GREATER_THAN_100MB", "UNKNOWN", "OTHER"}, false)), + Optional: true, + Description: "Specifies the size of the media object retrieved from the origin.", + Type: schema.TypeString, + }, + "dash": { + Optional: true, + Description: "Enable delivery of DASH media.", + Type: schema.TypeBool, + }, + "segment_duration_dash": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SEGMENT_DURATION_2S", "SEGMENT_DURATION_4S", "SEGMENT_DURATION_6S", "SEGMENT_DURATION_8S", "SEGMENT_DURATION_10S"}, false)), + Optional: true, + Description: "Specifies the duration of individual segments.", + Type: schema.TypeString, + }, + "segment_size_dash": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LESS_THAN_1MB", "ONE_MB_TO_TEN_MB", "TEN_MB_TO_100_MB", "GREATER_THAN_100MB", "UNKNOWN", "OTHER"}, false)), + Optional: true, + Description: "Specifies the size of the media object retrieved from the origin.", + Type: schema.TypeString, + }, + "smooth": { + Optional: true, + Description: "Enable delivery of Smooth media.", + Type: schema.TypeBool, + }, + "segment_duration_smooth": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SEGMENT_DURATION_2S", "SEGMENT_DURATION_4S", "SEGMENT_DURATION_6S", "SEGMENT_DURATION_8S", "SEGMENT_DURATION_10S"}, false)), + Optional: true, + Description: "Specifies the duration of individual fragments.", + Type: schema.TypeString, + }, + "segment_size_smooth": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LESS_THAN_1MB", "ONE_MB_TO_TEN_MB", "TEN_MB_TO_100_MB", "GREATER_THAN_100MB", "UNKNOWN", "OTHER"}, false)), + Optional: true, + Description: "Specifies the size of the media object retrieved from the origin.", + Type: schema.TypeString, + }, + }, + }, + }, + "content_pre_position": { + Optional: true, + Type: schema.TypeList, + Description: "Content Preposition. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the Content PrePosition behavior.", + Type: schema.TypeBool, + }, + "source_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ORIGIN"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "targets": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CLOUDWRAPPER"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "first_location": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "second_location": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "content_targeting_protection": { + Optional: true, + Type: schema.TypeList, + Description: "Content Targeting is based on `EdgeScape`, Akamai's location-based access control system. You can use it to allow or deny access to a set of geographic regions or IP addresses. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the Content Targeting feature.", + Type: schema.TypeBool, + }, + "geo_protection_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "enable_geo_protection": { + Optional: true, + Description: "When enabled, verifies IP addresses are unique to specific geographic regions.", + Type: schema.TypeBool, + }, + "geo_protection_mode": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ALLOW", "DENY"}, false)), + Optional: true, + Description: "Specifies how to handle requests.", + Type: schema.TypeString, + }, + "countries": { + Optional: true, + Description: "Specifies a set of two-character ISO 3166 country codes from which to allow or deny traffic. See `EdgeScape Data Codes` for a list.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "regions": { + Optional: true, + Description: "Specifies a set of ISO 3166-2 regional codes from which to allow or deny traffic. See `EdgeScape Data Codes` for a list.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "dmas": { + Optional: true, + Description: "Specifies the set of Designated Market Area codes from which to allow or deny traffic. See `EdgeScape Data Codes` for a list.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "override_ip_addresses": { + Optional: true, + Description: "Specify a set of IP addresses or CIDR blocks that exceptions to the set of included or excluded areas.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "enable_geo_redirect_on_deny": { + Optional: true, + Description: "When enabled, redirects denied requests rather than responding with an error code.", + Type: schema.TypeBool, + }, + "geo_redirect_url": { + Optional: true, + Description: "This specifies the full URL to the redirect page for denied requests.", + Type: schema.TypeString, + }, + "ip_protection_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "enable_ip_protection": { + Optional: true, + Description: "Allows you to control access to your content from specific sets of IP addresses and CIDR blocks.", + Type: schema.TypeBool, + }, + "ip_protection_mode": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ALLOW", "DENY"}, false)), + Optional: true, + Description: "Specifies how to handle requests.", + Type: schema.TypeString, + }, + "ip_addresses": { + Optional: true, + Description: "Specify a set of IP addresses or CIDR blocks to allow or deny.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "enable_ip_redirect_on_deny": { + Optional: true, + Description: "When enabled, redirects denied requests rather than responding with an error code.", + Type: schema.TypeBool, + }, + "ip_redirect_url": { + Optional: true, + Description: "This specifies the full URL to the redirect page for denied requests.", + Type: schema.TypeString, + }, + "referrer_protection_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "enable_referrer_protection": { + Optional: true, + Description: "Allows you allow traffic from certain referring websites, and disallow traffic from unauthorized sites that hijack those links.", + Type: schema.TypeBool, + }, + "referrer_protection_mode": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ALLOW", "DENY"}, false)), + Optional: true, + Description: "Specify the action to take.", + Type: schema.TypeString, + }, + "referrer_domains": { + Optional: true, + Description: "Specifies the set of domains from which to allow or deny traffic.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "enable_referrer_redirect_on_deny": { + Optional: true, + Description: "When enabled, redirects denied requests rather than responding with an error code.", + Type: schema.TypeBool, + }, + "referrer_redirect_url": { + Optional: true, + Description: "This specifies the full URL to the redirect page for denied requests.", + Type: schema.TypeString, + }, + }, + }, + }, + "cors_support": { + Optional: true, + Type: schema.TypeList, + Description: "Cross-origin resource sharing (CORS) allows web pages in one domain to access restricted resources from your domain. Specify external origin hostnames, methods, and headers that you want to accept via HTTP response headers. Full support of CORS requires allowing requests that use the OPTIONS method. See `allowOptions`. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables CORS feature.", + Type: schema.TypeBool, + }, + "allow_origins": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ANY", "SPECIFIED"}, false)), + Optional: true, + Description: "In responses to preflight requests, sets which origin hostnames to accept requests from.", + Type: schema.TypeString, + }, + "origins": { + Optional: true, + Description: "Defines the origin hostnames to accept requests from. The hostnames that you enter need to start with `http` or `https`. For detailed hostname syntax requirements, refer to RFC-952 and RFC-1123 specifications.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "allow_credentials": { + Optional: true, + Description: "Accepts requests made using credentials, like cookies or TLS client certificates.", + Type: schema.TypeBool, + }, + "allow_headers": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ANY", "SPECIFIED"}, false)), + Optional: true, + Description: "In responses to preflight requests, defines which headers to allow when making the actual request.", + Type: schema.TypeString, + }, + "headers": { + Optional: true, + Description: "Defines the supported request headers.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "methods": { + Optional: true, + Description: "Specifies any combination of the following methods: `DELETE`, `GET`, `PATCH`, `POST`, and `PUT` that are allowed when accessing the resource from an external domain.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "expose_headers": { + Optional: true, + Description: "In responses to preflight requests, lists names of headers that clients can access. By default, clients can access the following simple response headers: `Cache-Control`, `Content-Language`, `Content-Type`, `Expires`, `Last-Modified`, and `Pragma`. You can add other header names to make them accessible to clients.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "preflight_max_age": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Defines the number of seconds that the browser should cache the response to a preflight request.", + Type: schema.TypeString, + }, + }, + }, + }, + "cp_code": { + Optional: true, + Type: schema.TypeList, + Description: "Content Provider Codes (CP codes) allow you to distinguish various reporting and billing traffic segments, and you need them to access properties. You receive an initial CP code when purchasing Akamai, and you can run the `Create a new CP code` operation to generate more. This behavior applies any valid CP code, either as required as a default at the top of the rule tree, or subsequently to override the default. For a CP code to be valid, it needs to be assigned the same contract and product as the property, and the group needs access to it. For available values, run the `List CP codes` operation. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "value": { + Optional: true, + Description: "Specifies the CP code as an object. You only need to provide the initial `id`, stripping any `cpc_` prefix to pass the integer to the rule tree. Additional CP code details may reflect back in subsequent read-only data.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "created_date": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "description": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "products": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "cp_code_limits": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "current_capacity": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit_type": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "custom_behavior": { + Optional: true, + Type: schema.TypeList, + Description: "Allows you to insert a customized XML metadata behavior into any property's rule tree. Talk to your Akamai representative to implement the customized behavior. Once it's ready, run PAPI's `List custom behaviors` operation, then apply the relevant `behaviorId` value from the response within the current `customBehavior`. See `Custom behaviors and overrides` for guidance on custom metadata behaviors. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "behavior_id": { + Optional: true, + Description: "The unique identifier for the predefined custom behavior you want to insert into the current rule.", + Type: schema.TypeString, + }, + }, + }, + }, + "datastream": { + Optional: true, + Type: schema.TypeList, + Description: "The `DataStream` reporting service provides real-time logs on application activity, including aggregated metrics on complete request and response cycles and origin response times. Apply this behavior to report on this set of traffic. Use the `DataStream API` to aggregate the data. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "stream_type": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validation.ToDiagFunc(validation.StringInSlice([]string{"BEACON", "LOG", "BEACON_AND_LOG"}, false))), + Optional: true, + Description: "Specify the DataStream type.", + Type: schema.TypeString, + }, + "beacon_stream_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables DataStream reporting.", + Type: schema.TypeBool, + }, + "datastream_ids": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validateRegexOrVariable("^[0-9]+(-[0-9]+)*$")), + Optional: true, + Description: "A set of dash-separated DataStream ID values to limit the scope of reported data. By default, all active streams report. Use the DataStream application to gather stream ID values that apply to this property configuration. Specifying IDs for any streams that don't apply to this property has no effect, and results in no data reported.", + Type: schema.TypeString, + }, + "log_stream_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "log_enabled": { + Optional: true, + Description: "Enables log collection for the property by associating it with DataStream configurations.", + Type: schema.TypeBool, + }, + "log_stream_name": { + Optional: true, + Description: "Specifies the unique IDs of streams configured for the property. For properties created with the previous version of the rule format, this option contains a string instead of an array of strings. You can use the `List streams` operation to get stream IDs.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "sampling_percentage": { + Optional: true, + Description: "Specifies the percentage of log data you want to collect for this property.", + Type: schema.TypeInt, + }, + "collect_midgress_traffic": { + Optional: true, + Description: "If enabled, gathers midgress traffic data within the Akamai platform, such as between two edge servers, for all streams configured.", + Type: schema.TypeBool, + }, + }, + }, + }, + "dcp": { + Optional: true, + Type: schema.TypeList, + Description: "The `Internet of Things: Edge Connect` product allows connected users and devices to communicate on a publish-subscribe basis within reserved namespaces. (The `IoT Edge Connect API` allows programmatic access.) This behavior allows you to select previously reserved namespaces and set the protocols for users to publish and receive messages within these namespaces. Use the `verifyJsonWebTokenForDcp` behavior to control access. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables IoT Edge Connect.", + Type: schema.TypeBool, + }, + "namespace_id": { + Optional: true, + Description: "Specifies the globally reserved name for a specific configuration. It includes authorization rules over publishing and subscribing to logical categories known as `topics`. This provides a root path for all topics defined within a namespace configuration. You can use the `IoT Edge Connect API` to configure access control lists for your namespace configuration.", + Type: schema.TypeString, + }, + "tlsenabled": { + Optional: true, + Description: "When enabled, you can publish and receive messages over a secured MQTT connection on port 8883.", + Type: schema.TypeBool, + }, + "wsenabled": { + Optional: true, + Description: "When enabled, you can publish and receive messages through a secured MQTT connection over WebSockets on port 443.", + Type: schema.TypeBool, + }, + "gwenabled": { + Optional: true, + Description: "When enabled, you can publish and receive messages over a secured HTTP connection on port 443.", + Type: schema.TypeBool, + }, + "anonymous": { + Optional: true, + Description: "When enabled, you don't need to pass the JWT token with the mqtt request, and JWT validation is skipped.", + Type: schema.TypeBool, + }, + }, + }, + }, + "dcp_auth_hmac_transformation": { + Optional: true, + Type: schema.TypeList, + Description: "The `Internet of Things: Edge Connect` product allows connected users and devices to communicate on a publish-subscribe basis within reserved namespaces. In conjunction with `dcpAuthVariableExtractor`, this behavior affects how clients can authenticate themselves to edge servers, and which groups within namespaces are authorized to access topics. It transforms a source string value extracted from the client certificate and stored as a variable, then generates a hash value based on the selected algorithm, for use in authenticating the client request. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "hash_conversion_algorithm": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SHA256", "MD5", "SHA384"}, false)), + Optional: true, + Description: "Specifies the hash algorithm.", + Type: schema.TypeString, + }, + "hash_conversion_key": { + Optional: true, + Description: "Specifies the key to generate the hash, ideally a long random string to ensure adequate security.", + Type: schema.TypeString, + }, + }, + }, + }, + "dcp_auth_regex_transformation": { + Optional: true, + Type: schema.TypeList, + Description: "The `Internet of Things: Edge Connect` product allows connected users and devices to communicate on a publish-subscribe basis within reserved namespaces. In conjunction with `dcpAuthVariableExtractor`, this behavior affects how clients can authenticate themselves to edge servers, and which groups within namespaces are authorized to access topics. It transforms a source string value extracted from the client certificate and stored as a variable, then transforms the string based on a regular expression search pattern, for use in authenticating the client request. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "regex_pattern": { + ValidateDiagFunc: validateRegexOrVariable("^[^\\(\\)]*\\([^\\(\\)]+\\)[^\\(\\)]*$"), + Optional: true, + Description: "Specifies a Perl-compatible regular expression with a single grouping to capture the text. For example, a value of `^.(.{0,10})` omits the first character, but then captures up to 10 characters after that. If the regular expression does not capture a substring, authentication may fail.", + Type: schema.TypeString, + }, + }, + }, + }, + "dcp_auth_substring_transformation": { + Optional: true, + Type: schema.TypeList, + Description: "The `Internet of Things: Edge Connect` product allows connected users and devices to communicate on a publish-subscribe basis within reserved namespaces. In conjunction with `dcpAuthVariableExtractor`, this behavior affects how clients can authenticate themselves to edge servers, and which groups within namespaces are authorized to access topics. It transforms a source string value extracted from the client certificate and stored as a variable, then extracts a substring, for use in authenticating the client request. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "substring_start": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validateRegexOrVariable("^[0-9]+$")), + Optional: true, + Description: "The zero-based index offset of the first character to extract. If the index is out of bound from the string's length, authentication may fail.", + Type: schema.TypeString, + }, + "substring_end": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validateRegexOrVariable("^[0-9]+$")), + Optional: true, + Description: "The zero-based index offset of the last character to extract, where `-1` selects the remainder of the string. If the index is out of bound from the string's length, authentication may fail.", + Type: schema.TypeString, + }, + }, + }, + }, + "dcp_auth_variable_extractor": { + Optional: true, + Type: schema.TypeList, + Description: "The `Internet of Things: Edge Connect` product allows connected users and devices to communicate on a publish-subscribe basis within reserved namespaces. This behavior affects how clients can authenticate themselves to edge servers, and which groups within namespaces are authorized to access topics. When enabled, this behavior allows end users to authenticate their requests with valid x509 client certificates. Either a client identifier or access authorization groups are required to make the request valid. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "certificate_field": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SUBJECT_DN", "V3_SUBJECT_ALT_NAME", "SERIAL", "FINGERPRINT_DYN", "FINGERPRINT_MD5", "FINGERPRINT_SHA1", "V3_NETSCAPE_COMMENT"}, false)), + Optional: true, + Description: "Specifies the field in the client certificate to extract the variable from.", + Type: schema.TypeString, + }, + "dcp_mutual_auth_processing_variable_id": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"VAR_DCP_CLIENT_ID", "VAR_DCP_AUTH_GROUP"}, false)), + Optional: true, + Description: "Where to store the value.", + Type: schema.TypeString, + }, + }, + }, + }, + "dcp_default_authz_groups": { + Optional: true, + Type: schema.TypeList, + Description: "The `Internet of Things: Edge Connect` product allows connected users and devices to communicate on a publish-subscribe basis within reserved namespaces. This behavior defines a set of default authorization groups to add to each request the property configuration controls. These groups have access regardless of the authentication method you use, either JWT using the `verifyJsonWebTokenForDcp` behavior, or mutual authentication using the `dcpAuthVariableExtractor` behavior to control where authorization groups are extracted from within certificates. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "group_names": { + Optional: true, + Description: "Specifies the set of authorization groups to assign to all connecting devices.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "dcp_dev_relations": { + Optional: true, + Type: schema.TypeList, + Description: "The `Internet of Things: Edge Connect` product allows connected users and devices to communicate on a publish-subscribe basis within reserved namespaces. This behavior allows Akamai-external clients to use developer test accounts in a shared environment. In conjunction with `verifyJsonWebTokenForDcp`, this behavior allows you to use your own JWTs in your requests, or those generated by Akamai. It lets you either enable the default JWT server for your test configuration by setting the authentication endpoint to a default path, or specify custom settings for your JWT server and the authentication endpoint. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the default JWT server and sets the authentication endpoint to a default path.", + Type: schema.TypeBool, + }, + "custom_values": { + Optional: true, + Description: "Allows you to specify custom JWT server connection values.", + Type: schema.TypeBool, + }, + "hostname": { + ValidateDiagFunc: validateRegexOrVariable("^(([a-zA-Z0-9]([a-zA-Z0-9_\\-]*[a-zA-Z0-9])?)\\.)+([a-zA-Z]+|xn--[a-zA-Z0-9]+)$"), + Optional: true, + Description: "Specifies the JWT server's hostname.", + Type: schema.TypeString, + }, + "path": { + Optional: true, + Description: "Specifies the path to your JWT server's authentication endpoint. This lets you generate JWTs to sign your requests.", + Type: schema.TypeString, + }, + }, + }, + }, + "dcp_real_time_auth": { + Optional: true, + Type: schema.TypeList, + Description: "INTERNAL ONLY: The `Internet of Things: Edge Connect` product allows connected users and devices to communicate on a publish-subscribe basis within reserved namespaces. This behavior lets you configure the real time authentication to edge servers. This behavior is for internal usage only. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "extract_namespace": { + Optional: true, + Description: "Extracts a namespace from JSON web tokens (JWT).", + Type: schema.TypeBool, + }, + "namespace_claim": { + Optional: true, + Description: "Specifies the claim in JWT to extract the namespace from.", + Type: schema.TypeString, + }, + "extract_jurisdiction": { + Optional: true, + Description: "Extracts a jurisdiction that defines a geographically distributed set of servers from JWT.", + Type: schema.TypeBool, + }, + "jurisdiction_claim": { + Optional: true, + Description: "Specifies the claim in JWT to extract the jurisdiction from.", + Type: schema.TypeString, + }, + "extract_hostname": { + Optional: true, + Description: "Extracts a hostname from JWT.", + Type: schema.TypeBool, + }, + "hostname_claim": { + Optional: true, + Description: "Specifies the claim in JWT to extract the hostname from.", + Type: schema.TypeString, + }, + }, + }, + }, + "delivery_receipt": { + Optional: true, + Type: schema.TypeList, + Description: "A static behavior that's required when specifying the Cloud Monitor module's (`edgeConnect` behavior. You can only apply this behavior if the property is marked as secure. See `Secure property requirements` for guidance. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + "deny_access": { + Optional: true, + Type: schema.TypeList, + Description: "Assuming a condition in the rule matches, this denies access to the requested content. For example, a `userLocation` match paired with this behavior would deny requests from a specified part of the world. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "reason": { + ValidateDiagFunc: validateRegexOrVariable("^[\\w-]+$"), + Optional: true, + Description: "Text message that keys why access is denied. Any subsequent `denyAccess` behaviors within the rule tree may refer to the same `reason` key to override the current behavior.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Denies access when enabled.", + Type: schema.TypeBool, + }, + }, + }, + }, + "deny_direct_failover_access": { + Optional: true, + Type: schema.TypeList, + Description: "A static behavior required for all properties that implement a failover under the Cloud Security Failover product. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + "device_characteristic_cache_id": { + Optional: true, + Type: schema.TypeList, + Description: "By default, source URLs serve as cache IDs on edge servers. Electronic Data Capture allows you to specify an additional set of device characteristics to generate separate cache keys. Use this in conjunction with the `deviceCharacteristicHeader` behavior. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "elements": { + Optional: true, + Description: "Specifies a set of information about the device with which to generate a separate cache key.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "device_characteristic_header": { + Optional: true, + Type: schema.TypeList, + Description: "Sends selected information about requesting devices to the origin server, in the form of an `X-Akamai-Device-Characteristics` HTTP header. Use in conjunction with the `deviceCharacteristicCacheId` behavior. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "elements": { + Optional: true, + Description: "Specifies the set of information about the requesting device to send to the origin server.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "dns_async_refresh": { + Optional: true, + Type: schema.TypeList, + Description: "Allow an edge server to use an expired DNS record when forwarding a request to your origin. The `type A` DNS record refreshes `after` content is served to the end user, so there is no wait for the DNS resolution. Avoid this behavior if you want to be able to disable a server immediately after its DNS record expires. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Allows edge servers to refresh an expired DNS record after serving content.", + Type: schema.TypeBool, + }, + "timeout": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Set the maximum allowed time an expired DNS record may be active.", + Type: schema.TypeString, + }, + }, + }, + }, + "dns_prefresh": { + Optional: true, + Type: schema.TypeList, + Description: "Allows edge servers to refresh your origin's DNS record independently from end-user requests. The `type A` DNS record refreshes before the origin's DNS record expires. This behavior is for internal usage only. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Allows edge servers to refresh DNS records before they expire.", + Type: schema.TypeBool, + }, + "delay": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Specifies the amount of time following a DNS record's expiration to asynchronously prefresh it.", + Type: schema.TypeString, + }, + "timeout": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Specifies the amount of time to prefresh a DNS entry if there have been no requests to the domain name.", + Type: schema.TypeString, + }, + }, + }, + }, + "downgrade_protocol": { + Optional: true, + Type: schema.TypeList, + Description: "Serve static objects to the end-user client over HTTPS, but fetch them from the origin via HTTP. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the protocol downgrading behavior.", + Type: schema.TypeBool, + }, + }, + }, + }, + "download_complete_marker": { + Optional: true, + Type: schema.TypeList, + Description: "The `Internet of Things: OTA Updates` product allows customers to securely distribute firmware to devices over cellular networks. Based on match criteria that executes a rule, this behavior logs requests to the OTA servers as completed in aggregated and individual reports. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + "download_notification": { + Optional: true, + Type: schema.TypeList, + Description: "The `Internet of Things: OTA Updates` product allows customers to securely distribute firmware to devices over cellular networks. Based on match criteria that executes a rule, this behavior allows requests to the `OTA Updates API` for a list of completed downloads to individual vehicles. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + "downstream_cache": { + Optional: true, + Type: schema.TypeList, + Description: "Specify the caching instructions the edge server sends to the end user's client or client proxies. By default, the cache's duration is whichever is less: the remaining lifetime of the edge cache, or what the origin's header specifies. If the origin is set to `no-store` or `bypass-cache`, edge servers send `cache-busting` headers downstream to prevent downstream caching. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "behavior": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ALLOW", "MUST_REVALIDATE", "BUST", "TUNNEL_ORIGIN", "NONE"}, false)), + Optional: true, + Description: "Specify the caching instructions the edge server sends to the end user's client.", + Type: schema.TypeString, + }, + "allow_behavior": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LESSER", "GREATER", "REMAINING_LIFETIME", "FROM_MAX_AGE", "FROM_VALUE", "PASS_ORIGIN"}, false)), + Optional: true, + Description: "Specify how the edge server calculates the downstream cache by setting the value of the `Expires` header.", + Type: schema.TypeString, + }, + "ttl": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Sets the duration of the cache. Setting the value to `0` equates to a `no-cache` header that forces revalidation.", + Type: schema.TypeString, + }, + "send_headers": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CACHE_CONTROL_AND_EXPIRES", "CACHE_CONTROL", "EXPIRES", "PASS_ORIGIN"}, false)), + Optional: true, + Description: "Specifies the HTTP headers to include in the response to the client.", + Type: schema.TypeString, + }, + "send_private": { + Optional: true, + Description: "Adds a `Cache-Control: private` header to prevent objects from being cached in a shared caching proxy.", + Type: schema.TypeBool, + }, + }, + }, + }, + "dynamic_throughtput_optimization": { + Optional: true, + Type: schema.TypeList, + Description: "Enables `quick retry`, which detects slow forward throughput while fetching an object, and attempts a different forward connection path to avoid congestion. By default, connections under 5 mbps trigger this behavior. When the transfer rate drops below this rate during a connection attempt, quick retry is enabled and a different forward connection path is used. Contact Akamai Professional Services to override this threshold. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the quick retry feature.", + Type: schema.TypeBool, + }, + }, + }, + }, + "dynamic_throughtput_optimization_override": { + Optional: true, + Type: schema.TypeList, + Description: "This overrides the default threshold of 5 Mbps that triggers the `dynamicThroughtputOptimization` behavior, which enables the quick retry feature. Quick retry detects slow forward throughput while fetching an object, and attempts a different forward connection path to avoid congestion. This behavior is for internal usage only. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "throughput": { + Optional: true, + Description: "Specifies the default target forward throughput in Mbps, ranging from 2 to 50 Mbps. If this time is exceeded during a connection attempt, quick retry is enabled and a different forward connection path is used.", + Type: schema.TypeString, + }, + }, + }, + }, + "dynamic_web_content": { + Optional: true, + Type: schema.TypeList, + Description: "In conjunction with the `subCustomer` behavior, this optional behavior allows you to control how dynamic web content behaves for your subcustomers using `Akamai Cloud Embed`. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "sure_route": { + Optional: true, + Description: "Optimizes how subcustomer traffic routes from origin to edge servers. See the `sureRoute` behavior for more information.", + Type: schema.TypeBool, + }, + "prefetch": { + Optional: true, + Description: "Allows subcustomer content to prefetch over HTTP/2.", + Type: schema.TypeBool, + }, + "real_user_monitoring": { + Optional: true, + Description: "Allows Real User Monitoring (RUM) to collect performance data for subcustomer content. See the `realUserMonitoring` behavior for more information.", + Type: schema.TypeBool, + }, + "image_compression": { + Optional: true, + Description: "Enables image compression for subcustomer content.", + Type: schema.TypeBool, + }, + }, + }, + }, + "ecms_bulk_upload": { + Optional: true, + Type: schema.TypeList, + Description: "Uploads a ZIP archive with objects to an existing data set. The target data set stores objects as key-value pairs. The path to an object in the ZIP archive is a key, and the content of an object is a value. For an overview, see `ecmsDatabase`. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables sending a compressed archive file with objects. Sends the archive file to the default path of the target data set: `/bulk//`.", + Type: schema.TypeBool, + }, + }, + }, + }, + "ecms_database": { + Optional: true, + Type: schema.TypeList, + Description: "Edge Connect Message Store is available for `Internet of Things: Edge Connect` users. It lets you create databases and data sets within these databases. You can use this object store to save files smaller than 2 GB. `ecmsDatabase` specifies a default database for requests to this property, unless indicated otherwise in the URL. To access objects in the default database, you can skip its name in the URLs. To access objects in a different database, pass its name in the header, query parameter, or a regular expression matching a URL segment. You can also configure the `ecmsDataset` behavior to specify a default data set for requests. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "database": { + Optional: true, + Description: "Specifies a default database for this property. If you don't configure a default data set in the `ecmsDataset` behavior, requests to objects in this database follow the pattern: `/datastore//`.", + Type: schema.TypeString, + }, + "extract_location": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CLIENT_REQUEST_HEADER", "QUERY_STRING", "REGEX"}, false)), + Optional: true, + Description: "Specifies where to pass a database name in requests. If the specified location doesn't include the database name or the name doesn't match the regular expression, the default database is used.", + Type: schema.TypeString, + }, + "header_name": { + Optional: true, + Description: "Specifies the request header that passed the database name. By default, it points to `X-KV-Database`.", + Type: schema.TypeString, + }, + "query_parameter_name": { + Optional: true, + Description: "Specifies the query string parameter that passed the database name. By default, it points to `database`.", + Type: schema.TypeString, + }, + "regex_pattern": { + ValidateDiagFunc: validateRegexOrVariable("^[^\\(\\)]*\\([^\\(\\)]+\\)[^\\(\\)]*$"), + Optional: true, + Description: "Specifies the regular expression that matches the database name in the URL.", + Type: schema.TypeString, + }, + }, + }, + }, + "ecms_dataset": { + Optional: true, + Type: schema.TypeList, + Description: "Specifies a default data set for requests to this property unless indicated otherwise in the URL. To access objects in this data set, you can skip the data set name in the URLs. To access objects in a different data set within a database, pass the data set name in the header, query parameter, or a regular expression pattern matching a URL segment. You can also configure the `ecmsDatabase` behavior to specify a default database for requests. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "dataset": { + Optional: true, + Description: "Specifies a default data set for this property. If you don't configure a default database in the `ecmsDatabase` behavior, requests to objects in this data set follow the pattern: `/datastore//`.", + Type: schema.TypeString, + }, + "extract_location": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CLIENT_REQUEST_HEADER", "QUERY_STRING", "REGEX"}, false)), + Optional: true, + Description: "Specifies where to pass a data set name in requests. If the specified location doesn't include the data set name or the name doesn't match the regular expression pattern, the default data set is used.", + Type: schema.TypeString, + }, + "header_name": { + Optional: true, + Description: "Specifies the request header that passed the data set name. By default, it points to `X-KV-Dataset`.", + Type: schema.TypeString, + }, + "query_parameter_name": { + Optional: true, + Description: "Specifies the query string parameter that passed the data set name. By default, it points to `dataset`.", + Type: schema.TypeString, + }, + "regex_pattern": { + ValidateDiagFunc: validateRegexOrVariable("^[^\\(\\)]*\\([^\\(\\)]+\\)[^\\(\\)]*$"), + Optional: true, + Description: "Specifies the regular expression that matches the data set name in the URL.", + Type: schema.TypeString, + }, + }, + }, + }, + "ecms_object_key": { + Optional: true, + Type: schema.TypeList, + Description: "Defines a regular expression to match object keys in custom URLs and to access objects in a data set. You can point custom URLs to access proper values in the target data set. For an overview, see `ecmsDatabase`. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "regex": { + ValidateDiagFunc: validateRegexOrVariable("^[^\\(\\)]*\\([^\\(\\)]+\\)[^\\(\\)]*$"), + Optional: true, + Description: "Enables sending a compressed archive file with objects to the default path of the target data set: `/bulk//`.", + Type: schema.TypeString, + }, + }, + }, + }, + "edge_connect": { + Optional: true, + Type: schema.TypeList, + Description: "Configures traffic logs for the Cloud Monitor push API. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables Cloud Monitor's log-publishing behavior.", + Type: schema.TypeBool, + }, + "api_connector": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"DEFAULT", "SIEM_JSON", "BMC_APM"}, false)), + Optional: true, + Description: "Describes the API connector type.", + Type: schema.TypeString, + }, + "api_data_elements": { + Optional: true, + Description: "Specifies the data set to log.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "destination_hostname": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), + Optional: true, + Description: "Specifies the target hostname accepting push API requests.", + Type: schema.TypeString, + }, + "destination_path": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "Specifies the push API's endpoint.", + Type: schema.TypeString, + }, + "override_aggregate_settings": { + Optional: true, + Description: "When enabled, overrides default log settings.", + Type: schema.TypeBool, + }, + "aggregate_time": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Specifies how often logs are generated.", + Type: schema.TypeString, + }, + "aggregate_lines": { + ValidateDiagFunc: validateRegexOrVariable("^[1-9]\\d*$"), + Optional: true, + Description: "Specifies the maximum number of lines to include in each log.", + Type: schema.TypeString, + }, + "aggregate_size": { + ValidateDiagFunc: validateRegexOrVariable("^\\d+[K,M,G,T]B$"), + Optional: true, + Description: "Specifies the log's maximum size.", + Type: schema.TypeString, + }, + }, + }, + }, + "edge_load_balancing_advanced": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior implements customized Edge Load Balancing features. Contact Akamai Professional Services for help configuring it. This behavior is for internal usage only. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "description": { + Optional: true, + Description: "A description of what the `xml` block does.", + Type: schema.TypeString, + }, + "xml": { + Optional: true, + Description: "A block of Akamai XML metadata.", + Type: schema.TypeString, + }, + }, + }, + }, + "edge_load_balancing_data_center": { + Optional: true, + Type: schema.TypeList, + Description: "The Edge Load Balancing module allows you to specify groups of data centers that implement load balancing, session persistence, and real-time dynamic failover. Enabling ELB routes requests contextually based on location, device, or network, along with optional rules you specify. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "origin_id": { + Optional: true, + Description: "Corresponds to the `id` specified by the `edgeLoadBalancingOrigin` behavior associated with this data center.", + Type: schema.TypeString, + }, + "description": { + Optional: true, + Description: "Provides a description for the ELB data center, for your own reference.", + Type: schema.TypeString, + }, + "hostname": { + ValidateDiagFunc: validateAny(validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), validateRegexOrVariable("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$")), + Optional: true, + Description: "Specifies the data center's hostname.", + Type: schema.TypeString, + }, + "cookie_name": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validateRegexOrVariable("^[^\\s;]+$")), + Optional: true, + Description: "If using session persistence, this specifies the value of the cookie named in the corresponding `edgeLoadBalancingOrigin` behavior's `cookie_name` option.", + Type: schema.TypeString, + }, + "failover_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "enable_failover": { + Optional: true, + Description: "Allows you to specify failover rules.", + Type: schema.TypeBool, + }, + "ip": { + ValidateDiagFunc: validateRegexOrVariable("^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"), + Optional: true, + Description: "Specifies this data center's IP address.", + Type: schema.TypeString, + }, + "failover_rules": { + Optional: true, + Description: "Provides up to four failover rules to apply in the specified order.", + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "failover_hostname": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), + Optional: true, + Description: "The hostname of the data center to fail over to.", + Type: schema.TypeString, + }, + "modify_request": { + Optional: true, + Description: "Allows you to modify the request's hostname or path.", + Type: schema.TypeBool, + }, + "override_hostname": { + Optional: true, + Description: "Overrides the request's hostname with the `failover_hostname`.", + Type: schema.TypeBool, + }, + "context_root": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "Specifies the path to use in the forwarding request, typically the root (`/`) when failing over to a different data center, or a full path such as `/static/error.html` when failing over to an error page.", + Type: schema.TypeString, + }, + "absolute_path": { + Optional: true, + Description: "When enabled, interprets the path specified by `context_root` as an absolute server path, for example to reference a site-down page. Otherwise when disabled, the path is appended to the request.", + Type: schema.TypeBool, + }, + }, + }, + }, + }, + }, + }, + "edge_load_balancing_origin": { + Optional: true, + Type: schema.TypeList, + Description: "The Edge Load Balancing module allows you to implement groups of data centers featuring load balancing, session persistence, and real-time dynamic failover. Enabling ELB routes requests contextually based on location, device, or network, along with optional rules you specify. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "id": { + Optional: true, + Description: "Specifies a unique descriptive string for this ELB origin. The value needs to match the `origin_id` specified by the `edgeLoadBalancingDataCenter` behavior associated with this origin.", + Type: schema.TypeString, + }, + "description": { + Optional: true, + Description: "Provides a description for the ELB origin, for your own reference.", + Type: schema.TypeString, + }, + "hostname": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), + Optional: true, + Description: "Specifies the hostname associated with the ELB rule.", + Type: schema.TypeString, + }, + "session_persistence_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "enable_session_persistence": { + Optional: true, + Description: "Allows you to specify a cookie to pin the user's browser session to one data center. When disabled, ELB's default load balancing may send users to various data centers within the same session.", + Type: schema.TypeBool, + }, + "cookie_name": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "This specifies the name of the cookie that marks users' persistent sessions. The accompanying `edgeLoadBalancingDataCenter` behavior's `description` option specifies the cookie's value.", + Type: schema.TypeString, + }, + }, + }, + }, + "edge_origin_authorization": { + Optional: true, + Type: schema.TypeList, + Description: "Allows the origin server to use a cookie to ensure requests from Akamai servers are genuine. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the cookie-authorization behavior.", + Type: schema.TypeBool, + }, + "cookie_name": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "Specifies the name of the cookie to use for authorization.", + Type: schema.TypeString, + }, + "value": { + ValidateDiagFunc: validateRegexOrVariable("^[^\\s;]+$"), + Optional: true, + Description: "Specifies the value of the authorization cookie.", + Type: schema.TypeString, + }, + "domain": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), + Optional: true, + Description: "Specify the cookie's domain, which needs to match the top-level domain of the `Host` header the origin server receives.", + Type: schema.TypeString, + }, + }, + }, + }, + "edge_redirector": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior enables the `Edge Redirector Cloudlet` application, which helps you manage large numbers of redirects. With Cloudlets available on your contract, choose `Your services` > `Edge logic Cloudlets` to control the Edge Redirector within `Control Center`. Otherwise use the `Cloudlets API` to configure it programmatically. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the Edge Redirector Cloudlet.", + Type: schema.TypeBool, + }, + "is_shared_policy": { + Optional: true, + Description: "Whether you want to apply the Cloudlet shared policy to an unlimited number of properties within your account. Learn more about shared policies and how to create them in `Cloudlets Policy Manager`.", + Type: schema.TypeBool, + }, + "cloudlet_policy": { + Optional: true, + Description: "Specifies the Cloudlet policy as an object.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "cloudlet_shared_policy": { + Optional: true, + Description: "Identifies the Cloudlet shared policy to use with this behavior. Use the `Cloudlets API` to list available shared policies.", + Type: schema.TypeInt, + }, + }, + }, + }, + "edge_scape": { + Optional: true, + Type: schema.TypeList, + Description: "`EdgeScape` allows you to customize content based on the end user's geographic location or connection speed. When enabled, the edge server sends a special `X-Akamai-Edgescape` header to the origin server encoding relevant details about the end-user client as key-value pairs. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "When enabled, sends the `X-Akamai-Edgescape` request header to the origin.", + Type: schema.TypeBool, + }, + }, + }, + }, + "edge_side_includes": { + Optional: true, + Type: schema.TypeList, + Description: "Allows edge servers to process edge side include (ESI) code to generate dynamic content. To apply this behavior, you need to match on a `contentType`, `path`, or `filename`. Since this behavior requires more parsing time, you should not apply it to pages that lack ESI code, or to any non-HTML content. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables ESI processing.", + Type: schema.TypeBool, + }, + "enable_via_http": { + Optional: true, + Description: "Enable ESI only for content featuring the `Edge-control: dca=esi` HTTP response header.", + Type: schema.TypeBool, + }, + "pass_set_cookie": { + Optional: true, + Description: "Allows edge servers to pass your origin server's cookies to the ESI processor.", + Type: schema.TypeBool, + }, + "pass_client_ip": { + Optional: true, + Description: "Allows edge servers to pass the client IP header to the ESI processor.", + Type: schema.TypeBool, + }, + "i18n_status": { + Optional: true, + Description: "Provides internationalization support for ESI.", + Type: schema.TypeBool, + }, + "i18n_charset": { + Optional: true, + Description: "Specifies the character sets to use when transcoding the ESI language, `UTF-8` and `ISO-8859-1` for example.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "detect_injection": { + Optional: true, + Description: "Denies attempts to inject ESI code.", + Type: schema.TypeBool, + }, + }, + }, + }, + "edge_worker": { + Optional: true, + Type: schema.TypeList, + Description: "`EdgeWorkers` are JavaScript applications that allow you to manipulate your web traffic on edge servers outside of Property Manager behaviors, and deployed independently from your configuration's logic. This behavior applies an EdgeWorker to a set of edge requests. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "When enabled, applies specified EdgeWorker functionality to this rule's web traffic.", + Type: schema.TypeBool, + }, + "create_edge_worker": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "edge_worker_id": { + Optional: true, + Description: "Identifies the EdgeWorker application to apply to this rule's web traffic. You can use the `EdgeWorkers API` to get this value.", + Type: schema.TypeString, + }, + "resource_tier": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "m_pulse": { + Optional: true, + Description: "Enables mPulse reports that include data about EdgeWorkers errors generated due to JavaScript errors. For more details, see `Integrate mPulse reports with EdgeWorkers`.", + Type: schema.TypeBool, + }, + "m_pulse_information": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + "enforce_mtls_settings": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior repeats mTLS validation checks between a requesting client and the edge network. If the checks fail, you can deny the request or apply custom error handling. To use this behavior, you need to add either the `hostname` or `clientCertificate` criteria to the same rule. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enable_auth_set": { + Optional: true, + Description: "Whether to require a specific mutual transport layer security (mTLS) certificate authority (CA) set in a request from a client to the edge network.", + Type: schema.TypeBool, + }, + "certificate_authority_set": { + Optional: true, + Description: "Specify the client certificate authority (CA) sets you want to support in client requests. Run the `List CA Sets` operation in the mTLS Edge TrustStore API to get the `setId` value and pass it in this option as a string. If a request includes a set not defined here, it will be denied. The preset list items you can select are contingent on the CA sets you've created using the mTLS Edge Truststore, and then associated with a certificate in the `Certificate Provisioning System`.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "enable_ocsp_status": { + Optional: true, + Description: "Whether the mutual transport layer security requests from a client should use the online certificate support protocol (OCSP). OCSP can determine the x.509 certificate revocation status during the TLS handshake.", + Type: schema.TypeBool, + }, + "enable_deny_request": { + Optional: true, + Description: "This denies a request from a client that doesn't match what you've set for the options in this behavior. When disabled, non-matching requests are allowed, but you can incorporate a custom handling operation, such as reviewing generated log entries to see the discrepancies, enable the `Client-To-Edge` authentication header, or issue a custom message.", + Type: schema.TypeBool, + }, + }, + }, + }, + "enhanced_akamai_protocol": { + Optional: true, + Type: schema.TypeList, + Description: "Enables the Enhanced Akamai Protocol, a suite of advanced routing and transport optimizations that increase your website's performance and reliability. It is only available to specific applications, and requires a special routing from edge to origin. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "display": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + "enhanced_proxy_detection": { + Optional: true, + Type: schema.TypeList, + Description: "Enhanced Proxy Detection (EPD) leverages the GeoGuard service provided by GeoComply to add proxy detection and location spoofing protection. It identifies requests for your content that have been redirected from an unwanted source through a proxy. You can then allow, deny, or redirect these requests. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Applies GeoGuard proxy detection.", + Type: schema.TypeBool, + }, + "forward_header_enrichment": { + Optional: true, + Description: "Whether the Enhanced Proxy Detection (Akamai-EPD) header is included in the forward request to mark a connecting IP address as an anonymous proxy, with a two-letter designation. See the `epdForwardHeaderEnrichment` behavior for details.", + Type: schema.TypeBool, + }, + "enable_configuration_mode": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"BEST_PRACTICE", "ADVANCED"}, false)), + Optional: true, + Description: "Specifies how to field the proxy request.", + Type: schema.TypeString, + }, + "best_practice_action": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ALLOW", "DENY", "REDIRECT"}, false)), + Optional: true, + Description: "Specifies how to field the proxy request.", + Type: schema.TypeString, + }, + "best_practice_redirecturl": { + ValidateDiagFunc: validateRegexOrVariable("(http|https)://(\\w+:{0,1}\\w*@)?(\\S+)(:[0-9]+)?(/|/([\\w#!:.?+=&%@!\\-/]))?"), + Optional: true, + Description: "This specifies the URL to which to redirect requests.", + Type: schema.TypeString, + }, + "anonymous_vpn": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "detect_anonymous_vpn": { + Optional: true, + Description: "This enables detection of requests from anonymous VPNs.", + Type: schema.TypeBool, + }, + "detect_anonymous_vpn_action": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ALLOW", "DENY", "REDIRECT"}, false)), + Optional: true, + Description: "Specifies how to field anonymous VPN requests.", + Type: schema.TypeString, + }, + "detect_anonymous_vpn_redirecturl": { + ValidateDiagFunc: validateRegexOrVariable("(http|https)://(\\w+:{0,1}\\w*@)?(\\S+)(:[0-9]+)?(/|/([\\w#!:.?+=&%@!\\-/]))?"), + Optional: true, + Description: "This specifies the URL to which to redirect anonymous VPN requests.", + Type: schema.TypeString, + }, + "public_proxy": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "detect_public_proxy": { + Optional: true, + Description: "This enables detection of requests from public proxies.", + Type: schema.TypeBool, + }, + "detect_public_proxy_action": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ALLOW", "DENY", "REDIRECT"}, false)), + Optional: true, + Description: "Specifies how to field public proxy requests.", + Type: schema.TypeString, + }, + "detect_public_proxy_redirecturl": { + ValidateDiagFunc: validateRegexOrVariable("(http|https)://(\\w+:{0,1}\\w*@)?(\\S+)(:[0-9]+)?(/|/([\\w#!:.?+=&%@!\\-/]))?"), + Optional: true, + Description: "This specifies the URL to which to redirect public proxy requests.", + Type: schema.TypeString, + }, + "tor_exit_node": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "detect_tor_exit_node": { + Optional: true, + Description: "This enables detection of requests from Tor exit nodes.", + Type: schema.TypeBool, + }, + "detect_tor_exit_node_action": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ALLOW", "DENY", "REDIRECT"}, false)), + Optional: true, + Description: "This specifies whether to `DENY`, `ALLOW`, or `REDIRECT` requests from Tor exit nodes.", + Type: schema.TypeString, + }, + "detect_tor_exit_node_redirecturl": { + ValidateDiagFunc: validateRegexOrVariable("(http|https)://(\\w+:{0,1}\\w*@)?(\\S+)(:[0-9]+)?(/|/([\\w#!:.?+=&%@!\\-/]))?"), + Optional: true, + Description: "This specifies the URL to which to redirect requests from Tor exit nodes.", + Type: schema.TypeString, + }, + "smart_dns_proxy": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "detect_smart_dns_proxy": { + Optional: true, + Description: "This enables detection of requests from smart DNS proxies.", + Type: schema.TypeBool, + }, + "detect_smart_dns_proxy_action": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ALLOW", "DENY", "REDIRECT"}, false)), + Optional: true, + Description: "Specifies whether to `DENY`, `ALLOW`, or `REDIRECT` smart DNS proxy requests.", + Type: schema.TypeString, + }, + "detect_smart_dns_proxy_redirecturl": { + ValidateDiagFunc: validateRegexOrVariable("(http|https)://(\\w+:{0,1}\\w*@)?(\\S+)(:[0-9]+)?(/|/([\\w#!:.?+=&%@!\\-/]))?"), + Optional: true, + Description: "This specifies the URL to which to redirect DNS proxy requests.", + Type: schema.TypeString, + }, + "hosting_provider": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "detect_hosting_provider": { + Optional: true, + Description: "This detects requests from a hosting provider.", + Type: schema.TypeBool, + }, + "detect_hosting_provider_action": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ALLOW", "DENY", "REDIRECT"}, false)), + Optional: true, + Description: "This specifies whether to `DENY`, `ALLOW`, or `REDIRECT` requests from hosting providers.", + Type: schema.TypeString, + }, + "detect_hosting_provider_redirecturl": { + ValidateDiagFunc: validateRegexOrVariable("(http|https)://(\\w+:{0,1}\\w*@)?(\\S+)(:[0-9]+)?(/|/([\\w#!:.?+=&%@!\\-/]))?"), + Optional: true, + Description: "This specifies the absolute URL to which to redirect requests from hosting providers.", + Type: schema.TypeString, + }, + "vpn_data_center": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "detect_vpn_data_center": { + Optional: true, + Description: "This enables detection of requests from VPN data centers.", + Type: schema.TypeBool, + }, + "detect_vpn_data_center_action": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ALLOW", "DENY", "REDIRECT"}, false)), + Optional: true, + Description: "This specifies whether to `DENY`, `ALLOW`, or `REDIRECT` requests from VPN data centers.", + Type: schema.TypeString, + }, + "detect_vpn_data_center_redirecturl": { + ValidateDiagFunc: validateRegexOrVariable("(http|https)://(\\w+:{0,1}\\w*@)?(\\S+)(:[0-9]+)?(/|/([\\w#!:.?+=&%@!\\-/]))?"), + Optional: true, + Description: "This specifies the URL to which to redirect requests from VPN data centers.", + Type: schema.TypeString, + }, + "residential_proxy": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "detect_residential_proxy": { + Optional: true, + Description: "This enables detection of requests from a residential proxy. See `Enhanced Proxy Detection with GeoGuard` and learn more about this GeoGuard category before enabling it.", + Type: schema.TypeBool, + }, + "detect_residential_proxy_action": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ALLOW", "DENY", "REDIRECT"}, false)), + Optional: true, + Description: "This specifies whether to `DENY`, `ALLOW`, or `REDIRECT` requests from residential proxies.", + Type: schema.TypeString, + }, + "detect_residential_proxy_redirecturl": { + ValidateDiagFunc: validateRegexOrVariable("(http|https)://(\\w+:{0,1}\\w*@)?(\\S+)(:[0-9]+)?(/|/([\\w#!:.?+=&%@!\\-/]))?"), + Optional: true, + Description: "This specifies the URL to which to redirect requests.", + Type: schema.TypeString, + }, + }, + }, + }, + "epd_forward_header_enrichment": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior identifies unwanted requests from an anonymous proxy. This and the `enhancedProxyDetection` behavior work together and need to be included either in the same rule, or in the default one. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Sends the Enhanced Proxy Detection (`Akamai-EPD`) header in the forward request to determine whether the connecting IP address is an anonymous proxy. The header can contain one or more two-letter codes that indicate the IP address type detected by edge servers:", + Type: schema.TypeBool, + }, + }, + }, + }, + "fail_action": { + Optional: true, + Type: schema.TypeList, + Description: "Specifies how to respond when the origin is not available: by serving stale content, by serving an error page, or by redirecting. To apply this behavior, you should match on an `originTimeout` or `matchResponseCode`. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "When enabled in case of a failure to contact the origin, the current behavior applies.", + Type: schema.TypeBool, + }, + "action_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SERVE_STALE", "REDIRECT", "RECREATED_CO", "RECREATED_CEX", "RECREATED_NS", "DYNAMIC"}, false)), + Optional: true, + Description: "Specifies the basic action to take when there is a failure to contact the origin.", + Type: schema.TypeString, + }, + "saas_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"HOSTNAME", "PATH", "QUERY_STRING", "COOKIE"}, false)), + Optional: true, + Description: "Identifies the component of the request that identifies the SaaS dynamic fail action.", + Type: schema.TypeString, + }, + "saas_cname_enabled": { + Optional: true, + Description: "Specifies whether to use a CNAME chain to determine the hostname for the SaaS dynamic failaction.", + Type: schema.TypeBool, + }, + "saas_cname_level": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "Specifies the number of elements in the CNAME chain backwards from the edge hostname that determines the hostname for the SaaS dynamic failaction.", + Type: schema.TypeInt, + }, + "saas_cookie": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "Specifies the name of the cookie that identifies this SaaS dynamic failaction.", + Type: schema.TypeString, + }, + "saas_query_string": { + ValidateDiagFunc: validateRegexOrVariable("^[^:/?#\\[\\]@&]+$"), + Optional: true, + Description: "Specifies the name of the query parameter that identifies this SaaS dynamic failaction.", + Type: schema.TypeString, + }, + "saas_regex": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9\\:\\[\\]\\{\\}\\(\\)\\.\\?_\\-\\*\\+\\^\\$\\\\\\/\\|&=!]{1,250})$"), + Optional: true, + Description: "Specifies the substitution pattern (a Perl-compatible regular expression) that defines the SaaS dynamic failaction.", + Type: schema.TypeString, + }, + "saas_replace": { + ValidateDiagFunc: validateRegexOrVariable("^(([a-zA-Z0-9]|\\$[1-9])(([a-zA-Z0-9\\._\\-]|\\$[1-9]){0,250}([a-zA-Z0-9]|\\$[1-9]))?){1,10}$"), + Optional: true, + Description: "Specifies the replacement pattern that defines the SaaS dynamic failaction.", + Type: schema.TypeString, + }, + "saas_suffix": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})\\.(com|net|org|info|biz|us|co\\.uk|ac\\.uk|org\\.uk|me\\.uk|ca|eu|com\\.au|co|co\\.za|ru|es|me|tv|pro|in|ie|de|it|nl|fr|co\\.il|ch|se|co\\.nz|pl|jp|name|mobi|cc|ws|be|com\\.mx|at|nu|asia|co\\.nz|net\\.nz|org\\.nz|com\\.au|net\\.au|org\\.au|tools)$"), + Optional: true, + Description: "Specifies the static portion of the SaaS dynamic failaction.", + Type: schema.TypeString, + }, + "dynamic_method": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SERVE_301", "SERVE_302", "SERVE_ALTERNATE"}, false)), + Optional: true, + Description: "Specifies the redirect method.", + Type: schema.TypeString, + }, + "dynamic_custom_path": { + Optional: true, + Description: "Allows you to modify the original requested path.", + Type: schema.TypeBool, + }, + "dynamic_path": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "Specifies the new path.", + Type: schema.TypeString, + }, + "redirect_hostname_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ORIGINAL", "ALTERNATE"}, false)), + Optional: true, + Description: "Whether to preserve or customize the hostname.", + Type: schema.TypeString, + }, + "redirect_hostname": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), + Optional: true, + Description: "When the `actionType` is `REDIRECT` and the `redirectHostnameType` is `ALTERNATE`, this specifies the hostname for the redirect.", + Type: schema.TypeString, + }, + "redirect_custom_path": { + Optional: true, + Description: "Uses the `redirectPath` to customize a new path.", + Type: schema.TypeBool, + }, + "redirect_path": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "Specifies a new path.", + Type: schema.TypeString, + }, + "redirect_method": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntInSlice([]int{302, 301})), + Optional: true, + Description: "Specifies the HTTP response code.", + Type: schema.TypeInt, + }, + "content_hostname": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), + Optional: true, + Description: "Specifies the static hostname for the alternate redirect.", + Type: schema.TypeString, + }, + "content_custom_path": { + Optional: true, + Description: "Specifies a custom redirect path.", + Type: schema.TypeBool, + }, + "content_path": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "Specifies a custom redirect path.", + Type: schema.TypeString, + }, + "net_storage_hostname": { + Optional: true, + Description: "When the `actionType` is `RECREATED_NS`, specifies the `NetStorage` origin to serve the alternate content. Contact Akamai Professional Services for your NetStorage origin's `id`.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cp_code": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "download_domain_name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "g2o_token": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "net_storage_path": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "When the `actionType` is `RECREATED_NS`, specifies the path for the `NetStorage` request.", + Type: schema.TypeString, + }, + "cex_hostname": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), + Optional: true, + Description: "Specifies a hostname.", + Type: schema.TypeString, + }, + "cex_custom_path": { + Optional: true, + Description: "Specifies a custom path.", + Type: schema.TypeBool, + }, + "cex_path": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "Specifies a custom path.", + Type: schema.TypeString, + }, + "cp_code": { + Optional: true, + Description: "Specifies a CP code for which to log errors for the NetStorage location. You only need to provide the initial `id`, stripping any `cpc_` prefix to pass the integer to the rule tree. Additional CP code details may reflect back in subsequent read-only data.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "created_date": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "description": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "products": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "cp_code_limits": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "current_capacity": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit_type": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + "status_code": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntInSlice([]int{200, 404, 500, 100, 101, 102, 103, 122, 201, 202, 203, 204, 205, 206, 207, 226, 400, 401, 402, 403, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 422, 423, 424, 425, 426, 428, 429, 431, 444, 449, 450, 499, 501, 502, 503, 504, 505, 506, 507, 509, 510, 511, 598, 599})), + Optional: true, + Description: "Assigns a new HTTP status code to the failure response.", + Type: schema.TypeInt, + }, + "preserve_query_string": { + Optional: true, + Description: "When using either `contentCustomPath`, `cexCustomPath`, `dynamicCustomPath`, or `redirectCustomPath` to specify a custom path, enabling this passes in the original request's query string as part of the path.", + Type: schema.TypeBool, + }, + "modify_protocol": { + Optional: true, + Description: "Modifies the redirect's protocol using the value of the `protocol` field.", + Type: schema.TypeBool, + }, + "protocol": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"HTTP", "HTTPS"}, false)), + Optional: true, + Description: "When the `actionType` is `REDIRECT` and `modifyProtocol` is enabled, this specifies the redirect's protocol.", + Type: schema.TypeString, + }, + "allow_fcm_parent_override": { + Optional: true, + Description: "", + Type: schema.TypeBool, + }, + }, + }, + }, + "failover_bot_manager_feature_compatibility": { + Optional: true, + Type: schema.TypeList, + Description: "Ensures that functionality such as challenge authentication and reset protocol work with a failover product property you use to create an alternate hostname. Apply it to any properties that implement a failover under the Cloud Security Failover product. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "compatibility": { + Optional: true, + Description: "This behavior does not include any options. Specifying the behavior itself enables it.", + Type: schema.TypeBool, + }, + }, + }, + }, + "fast_invalidate": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior is deprecated, but you should not disable or remove it if present. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "When enabled, forces a validation test for all edge content to which the behavior applies.", + Type: schema.TypeBool, + }, + }, + }, + }, + "fips": { + Optional: true, + Type: schema.TypeList, + Description: "Ensures `Federal Information Process Standards (FIPS) 140-2` compliance for a connection to an origin server. For this behavior to work properly, verify that your origin's secure certificate supports Enhanced TLS and is FIPS-compliant. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enable": { + Optional: true, + Description: "When enabled, supports the use of FIPS-validated ciphers in the connection between this delivery configuration and your origin server.", + Type: schema.TypeBool, + }, + }, + }, + }, + "first_party_marketing": { + Optional: true, + Type: schema.TypeList, + Description: "Enables the Cloud Marketing Cloudlet, which helps MediaMath customers collect usage data and place corresponding tags for use in online advertising. You can configure tags using either the Cloudlets Policy Manager application or the `Cloudlets API`. See also the `firstPartyMarketingPlus` behavior, which integrates better with both MediaMath and its partners. Both behaviors support the same set of options. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the Cloud Marketing Cloudlet.", + Type: schema.TypeBool, + }, + "java_script_insertion_rule": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"NEVER", "POLICY", "ALWAYS"}, false)), + Optional: true, + Description: "Select how to insert the MediaMath JavaScript reference script.", + Type: schema.TypeString, + }, + "cloudlet_policy": { + Optional: true, + Description: "Identifies the Cloudlet policy.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "media_math_prefix": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "Specify the URL path prefix that distinguishes Cloud Marketing requests from your other web traffic. Include the leading slash character, but no trailing slash. For example, if the path prefix is `/mmath`, and the request is for `www.example.com/dir`, the new URL is `www.example.com/mmath/dir`.", + Type: schema.TypeString, + }, + }, + }, + }, + "first_party_marketing_plus": { + Optional: true, + Type: schema.TypeList, + Description: "Enables the Cloud Marketing Plus Cloudlet, which helps MediaMath customers collect usage data and place corresponding tags for use in online advertising. You can configure tags using either the Cloudlets Policy Manager application or the `Cloudlets API`. See also the `firstPartyMarketing` behavior, which integrates with MediaMath but not its partners. Both behaviors support the same set of options. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the Cloud Marketing Plus Cloudlet.", + Type: schema.TypeBool, + }, + "java_script_insertion_rule": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"NEVER", "POLICY", "ALWAYS"}, false)), + Optional: true, + Description: "Select how to insert the MediaMath JavaScript reference script.", + Type: schema.TypeString, + }, + "cloudlet_policy": { + Optional: true, + Description: "Identifies the Cloudlet policy.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "media_math_prefix": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "Specify the URL path prefix that distinguishes Cloud Marketing requests from your other web traffic. Include the leading slash character, but no trailing slash. For example, if the path prefix is `/mmath`, and the request is for `www.example.com/dir`, the new URL is `www.example.com/mmath/dir`.", + Type: schema.TypeString, + }, + }, + }, + }, + "forward_rewrite": { + Optional: true, + Type: schema.TypeList, + Description: "The Forward Rewrite Cloudlet allows you to conditionally modify the forward path in edge content without affecting the URL that displays in the user's address bar. If Cloudlets are available on your contract, choose `Your services` > `Edge logic Cloudlets` to control how this feature works within `Control Center`, or use the `Cloudlets API` to configure it programmatically. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the Forward Rewrite Cloudlet behavior.", + Type: schema.TypeBool, + }, + "is_shared_policy": { + Optional: true, + Description: "Whether you want to use a shared policy for a Cloudlet. Learn more about shared policies and how to create them in `Cloudlets Policy Manager`.", + Type: schema.TypeBool, + }, + "cloudlet_policy": { + Optional: true, + Description: "Identifies the Cloudlet policy.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "cloudlet_shared_policy": { + Optional: true, + Description: "This identifies the Cloudlet shared policy to use with this behavior. You can list available shared policies with the `Cloudlets API`.", + Type: schema.TypeInt, + }, + }, + }, + }, + "g2oheader": { + Optional: true, + Type: schema.TypeList, + Description: "The `signature header authentication` (g2o) security feature provides header-based verification of outgoing origin requests. Edge servers encrypt request data in a pre-defined header, which the origin uses to verify that the edge server processed the request. This behavior configures the request data, header names, encryption algorithm, and shared secret to use for verification. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the g2o verification behavior.", + Type: schema.TypeBool, + }, + "data_header": { + ValidateDiagFunc: validateRegexOrVariable("^[^()<>@,;:\\\"/\\[\\]?{}\\s]+$"), + Optional: true, + Description: "Specifies the name of the header that contains the request data that needs to be encrypted.", + Type: schema.TypeString, + }, + "signed_header": { + ValidateDiagFunc: validateRegexOrVariable("^[^()<>@,;:\\\"/\\[\\]?{}\\s]+$"), + Optional: true, + Description: "Specifies the name of the header containing encrypted request data.", + Type: schema.TypeString, + }, + "encoding_version": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntInSlice([]int{1, 2, 3, 4, 5})), + Optional: true, + Description: "Specifies the version of the encryption algorithm as an integer from `1` through `5`.", + Type: schema.TypeInt, + }, + "use_custom_sign_string": { + Optional: true, + Description: "When disabled, the encrypted string is based on the forwarded URL. If enabled, you can use `customSignString` to customize the set of data to encrypt.", + Type: schema.TypeBool, + }, + "custom_sign_string": { + Optional: true, + Description: "Specifies the set of data to be encrypted as a combination of concatenated strings.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "secret_key": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validateRegexOrVariable("^[0-9a-zA-Z]{24}$")), + Optional: true, + Description: "Specifies the shared secret key.", + Type: schema.TypeString, + }, + "nonce": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9a-zA-Z]{1,8}$"), + Optional: true, + Description: "Specifies the cryptographic `nonce` string.", + Type: schema.TypeString, + }, + }, + }, + }, + "global_request_number": { + Optional: true, + Type: schema.TypeList, + Description: "Generates a unique identifier for each request on the Akamai edge network, for use in logging and debugging. GRN identifiers follow the same format as Akamai's error reference strings, for example: `0.05313217.1567801841.1457a3`. You can use the Edge Diagnostics API's `Translate error string` operation to get low-level details about any request. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "output_option": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"RESPONSE_HEADER", "REQUEST_HEADER", "BOTH_HEADERS", "ASSIGN_VARIABLE"}, false)), + Optional: true, + Description: "Specifies how to report the GRN value.", + Type: schema.TypeString, + }, + "header_name": { + ValidateDiagFunc: validateRegexOrVariable("^[^()<>@,;:\\\"/\\[\\]?{}\\s]+$"), + Optional: true, + Description: "With `outputOption` set to specify any set of headers, this specifies the name of the header to report the GRN value.", + Type: schema.TypeString, + }, + "variable_name": { + Optional: true, + Description: "This specifies the name of the variable to assign the GRN value to. You need to pre-declare any `variable` you specify within the rule tree.", + Type: schema.TypeString, + }, + }, + }, + }, + "graphql_caching": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior configures how to cache GraphQL-based API traffic. Enable `caching` for your GraphQL API traffic, along with `allowPost` to cache POST responses. To configure REST API traffic, use the `rapid` behavior. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables GraphQL caching.", + Type: schema.TypeBool, + }, + "cache_responses_with_errors": { + Optional: true, + Description: "When enabled, caches responses that include an `error` field at the top of the response body object. Disable this if your GraphQL server yields temporary errors with success codes in the 2xx range.", + Type: schema.TypeBool, + }, + "advanced": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "post_request_processing_error_handling": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"APPLY_CACHING_BEHAVIOR", "NO_STORE"}, false)), + Optional: true, + Description: "Specify what happens if GraphQL query processing fails on POST requests.", + Type: schema.TypeString, + }, + "operations_url_query_parameter_name": { + Optional: true, + Description: "Specifies the name of a query parameter that identifies requests as GraphQL queries.", + Type: schema.TypeString, + }, + "operations_json_body_parameter_name": { + Optional: true, + Description: "The name of the JSON body parameter that identifies GraphQL POST requests.", + Type: schema.TypeString, + }, + }, + }, + }, + "gzip_response": { + Optional: true, + Type: schema.TypeList, + Description: "Apply `gzip` compression to speed transfer time. This behavior applies best to text-based content such as HTML, CSS, and JavaScript, especially once files exceed about 10KB. Do not apply it to already compressed image formats, or to small files that would add more time to uncompress. To apply this behavior, you should match on `contentType` or the content's `cacheability`. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "behavior": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ORIGIN_RESPONSE", "ALWAYS", "NEVER"}, false)), + Optional: true, + Description: "Specify when to compress responses.", + Type: schema.TypeString, + }, + }, + }, + }, + "hd_data_advanced": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior specifies Akamai XML metadata that can only be configured on your behalf by Akamai Professional Services. Unlike the `advanced` behavior, this may apply a different set of overriding metadata that executes in a post-processing phase. This behavior is for internal usage only. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "description": { + Optional: true, + Description: "Human-readable description of what the XML block does.", + Type: schema.TypeString, + }, + "xml": { + Optional: true, + Description: "A block of Akamai XML metadata.", + Type: schema.TypeString, + }, + }, + }, + }, + "health_detection": { + Optional: true, + Type: schema.TypeList, + Description: "Monitors the health of your origin server by tracking unsuccessful attempts to contact it. Use this behavior to keep end users from having to wait several seconds before a forwarded request times out, or to reduce requests on the origin server when it is unavailable. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "retry_count": { + Optional: true, + Description: "The number of consecutive connection failures that mark an IP address as faulty.", + Type: schema.TypeInt, + }, + "retry_interval": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Specifies the amount of time the edge server will wait before trying to reconnect to an IP address it has already identified as faulty.", + Type: schema.TypeString, + }, + "maximum_reconnects": { + Optional: true, + Description: "Specifies the maximum number of times the edge server will contact your origin server. If your origin is associated with several IP addresses, `maximumReconnects` effectively overrides the value of `retryCount`.", + Type: schema.TypeInt, + }, + }, + }, + }, + "hsaf_eip_binding": { + Optional: true, + Type: schema.TypeList, + Description: "Edge IP Binding works with a limited set of static IP addresses to distribute your content, which can be limiting in large footprint environments. This behavior sets Hash Serial and Forward (HSAF) for Edge IP Binding to deal with larger footprints. It can only be configured on your behalf by Akamai Professional Services. This behavior is for internal usage only. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables HSAF for Edge IP Binding customers with a large footprint.", + Type: schema.TypeBool, + }, + "custom_extracted_serial": { + Optional: true, + Description: "Whether to pull the serial number from the variable value set in the `advanced` behavior. Work with your Akamai Services team to add the `advanced` behavior earlier in your property to extract and apply the `AKA_PM_EIP_HSAF_SERIAL` variable.", + Type: schema.TypeBool, + }, + "hash_min_value": { + Optional: true, + Description: "Specifies the minimum value for the HSAF hash range, from 2 through 2045. This needs to be lower than `hashMaxValue`.", + Type: schema.TypeInt, + }, + "hash_max_value": { + Optional: true, + Description: "Specifies the maximum value for the hash range, from 3 through 2046. This needs to be higher than `hashMinValue`.", + Type: schema.TypeInt, + }, + "tier": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"EDGE", "PARENT", "BOTH"}, false)), + Optional: true, + Description: "Specifies where the behavior is applied.", + Type: schema.TypeString, + }, + }, + }, + }, + "http2": { + Optional: true, + Type: schema.TypeList, + Description: "Enables the HTTP/2 protocol, which reduces latency and improves efficiency. You can only apply this behavior if the property is marked as secure. See `Secure property requirements` for guidance. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + "http3": { + Optional: true, + Type: schema.TypeList, + Description: "This enables the HTTP/3 protocol that uses QUIC. The behavior allows for improved performance and faster connection setup. You can only apply this behavior if the property is marked as secure. See `Secure property requirements` and the `Property Manager documentation` for guidance. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enable": { + Optional: true, + Description: "This enables HTTP/3 connections between requesting clients and Akamai edge servers. You also need to enable QUIC and TLS 1.3 in your certificate deployment settings. See the `Property Manager documentation` for more details.", + Type: schema.TypeBool, + }, + }, + }, + }, + "http_strict_transport_security": { + Optional: true, + Type: schema.TypeList, + Description: "Applies HTTP Strict Transport Security (HSTS), disallowing insecure HTTP traffic. Apply this to hostnames managed with Standard TLS or Enhanced TLS certificates. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enable": { + Optional: true, + Description: "Applies HSTS to this set of requests.", + Type: schema.TypeBool, + }, + "max_age": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ZERO_MINS", "TEN_MINS", "ONE_DAY", "ONE_MONTH", "THREE_MONTHS", "SIX_MONTHS", "ONE_YEAR"}, false)), + Optional: true, + Description: "Specifies the duration for which to apply HSTS for new browser connections.", + Type: schema.TypeString, + }, + "include_sub_domains": { + Optional: true, + Description: "When enabled, applies HSTS to all subdomains.", + Type: schema.TypeBool, + }, + "preload": { + Optional: true, + Description: "When enabled, adds this domain to the browser's preload list. You still need to declare the domain at `hstspreload.org`.", + Type: schema.TypeBool, + }, + "redirect": { + Optional: true, + Description: "When enabled, redirects all HTTP requests to HTTPS.", + Type: schema.TypeBool, + }, + "redirect_status_code": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntInSlice([]int{301, 302})), + Optional: true, + Description: "Specifies a response code.", + Type: schema.TypeInt, + }, + }, + }, + }, + "http_to_https_upgrade": { + Optional: true, + Type: schema.TypeList, + Description: "Upgrades an HTTP edge request to HTTPS for the remainder of the request flow. Enable this behavior only if your origin supports HTTPS, and if your `origin` behavior is configured with `originCertsToHonor` to verify SSL certificates. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "upgrade": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + "im_override": { + Optional: true, + Type: schema.TypeList, + Description: "This specifies common query parameters that affect how `imageManager` transforms images, potentially overriding policy, width, format, or density request parameters. This also allows you to assign the value of one of the property's `rule tree variables` to one of Image and Video Manager's own policy variables. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "override": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"POLICY", "POLICY_VARIABLE", "WIDTH", "FORMAT", "DPR", "EXCLUDE_QUERY"}, false)), + Optional: true, + Description: "Selects the type of query parameter you want to set.", + Type: schema.TypeString, + }, + "typesel": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"VALUE", "VARIABLE"}, false)), + Optional: true, + Description: "Specifies how to set a query parameter.", + Type: schema.TypeString, + }, + "formatvar": { + Optional: true, + Description: "This selects the variable with the name of the browser you want to optimize images for. The variable specifies the same type of data as the `format` option below.", + Type: schema.TypeString, + }, + "format": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CHROME", "IE", "SAFARI", "GENERIC", "AVIF_WEBP_JPEG_PNG_GIF", "JP2_WEBP_JPEG_PNG_GIF", "WEBP_JPEG_PNG_GIF", "JPEG_PNG_GIF"}, false)), + Optional: true, + Description: "Specifies the type of the browser, or the encodings passed in the `Accept` header, that you want to optimize images for.", + Type: schema.TypeString, + }, + "dprvar": { + Optional: true, + Description: "This selects the variable with the desired pixel density. The variable specifies the same type of data as the `dpr` option below.", + Type: schema.TypeString, + }, + "dpr": { + Optional: true, + Description: "Directly specifies the pixel density. The numeric value is a scaling factor of 1, representing normal density.", + Type: schema.TypeFloat, + }, + "widthvar": { + Optional: true, + Description: "Selects the variable with the desired width. If the Image and Video Manager policy doesn't define that width, it serves the next largest width.", + Type: schema.TypeString, + }, + "width": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "Sets the image's desired pixel width directly. If the Image Manager policy doesn't define that width, it serves the next largest width.", + Type: schema.TypeFloat, + }, + "policyvar": { + Optional: true, + Description: "This selects the variable with the desired Image and Video Manager policy name to apply to image requests. If there is no policy by that name, Image and Video Manager serves the image unmodified.", + Type: schema.TypeString, + }, + "policy": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_-]{1,32}$"), + Optional: true, + Description: "This selects the desired Image and Video Manager policy name directly. If there is no policy by that name, Image and Video Manager serves the image unmodified.", + Type: schema.TypeString, + }, + "policyvar_name": { + Optional: true, + Description: "This selects the name of one of the variables defined in an Image and Video Manager policy that you want to replace with the property's rule tree variable.", + Type: schema.TypeString, + }, + "policyvar_i_mvar": { + Optional: true, + Description: "This selects one of the property's rule tree variables to assign to the `policyvarName` variable within Image and Video Manager.", + Type: schema.TypeString, + }, + "exclude_all_query_parameters": { + Optional: true, + Description: "Whether to exclude all query parameters from the Image and Video Manager cache key.", + Type: schema.TypeBool, + }, + "excluded_query_parameters": { + Optional: true, + Description: "Specifies individual query parameters to exclude from the Image and Video Manager cache key.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "image_and_video_manager": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "policy_set_type": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "", + Type: schema.TypeBool, + }, + "resize": { + Optional: true, + Description: "", + Type: schema.TypeBool, + }, + "apply_best_file_type": { + Optional: true, + Description: "", + Type: schema.TypeBool, + }, + "cp_code_original": { + Optional: true, + Description: "", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "created_date": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "description": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "products": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "cp_code_limits": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "current_capacity": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit_type": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + "cp_code_transformed": { + Optional: true, + Description: "", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "created_date": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "description": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "products": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "cp_code_limits": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "current_capacity": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit_type": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + "image_set": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_-]+([^-].|[^v])$"), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "video_set": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_-]+-v$"), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "image_manager": { + Optional: true, + Type: schema.TypeList, + Description: "Optimizes images' size or file type for the requesting device. You can also use this behavior to generate API tokens to apply your own policies to matching images using the `Image and Video Manager API`. To apply this behavior, you need to match on a `fileExtension`. Once you apply Image and Video Manager to traffic, you can add the `advancedImMatch` to ensure the behavior applies to the requests from the Image and Video Manager backend. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "settings_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enable image management capabilities and generate a corresponding API token.", + Type: schema.TypeBool, + }, + "resize": { + Optional: true, + Description: "Specify whether to scale down images to the maximum screen resolution, as determined by the rendering device's user agent. Note that enabling this may affect screen layout in unexpected ways.", + Type: schema.TypeBool, + }, + "apply_best_file_type": { + Optional: true, + Description: "Specify whether to convert images to the best file type for the requesting device, based on its user agent and the initial image file. This produces the smallest file size possible that retains image quality.", + Type: schema.TypeBool, + }, + "super_cache_region": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"US", "ASIA", "AUSTRALIA", "EMEA", "JAPAN", "CHINA"}, false)), + Optional: true, + Description: "Specifies a location for your site's heaviest traffic, for use in caching derivatives on edge servers.", + Type: schema.TypeString, + }, + "traffic_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "cp_code_original": { + Optional: true, + Description: "Assigns a CP code to track traffic and billing for original images that the Image and Video Manager has not modified. You only need to provide the initial `id`, stripping any `cpc_` prefix to pass the integer to the rule tree. Additional CP code details may reflect back in subsequent read-only data.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "created_date": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "description": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "products": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "cp_code_limits": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "current_capacity": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit_type": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + "cp_code_transformed": { + Optional: true, + Description: "Assigns a separate CP code to track traffic and billing for derived images. You only need to provide the initial `id`, stripping any `cpc_` prefix to pass the integer to the rule tree. Additional CP code details may reflect back in subsequent read-only data.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "created_date": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "description": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "products": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "cp_code_limits": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "current_capacity": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit_type": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + "api_reference_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "use_existing_policy_set": { + Optional: true, + Description: "Whether to use a previously created policy set that may be referenced in other properties, or create a new policy set to use with this property. A policy set can be shared across multiple properties belonging to the same contract. The behavior populates any changes to the policy set across all properties that reference that set.", + Type: schema.TypeBool, + }, + "policy_set": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_-]+([^-].|[^v])$"), + Optional: true, + Description: "Identifies the existing policy set configured with `Image and Video Manager API`.", + Type: schema.TypeString, + }, + "advanced": { + Optional: true, + Description: "Generates a custom `Image and Video Manager API` token to apply a corresponding policy to this set of images. The token consists of a descriptive label (the `policyToken`) concatenated with a property-specific identifier that's generated when you save the property. The API registers the token when you activate the property.", + Type: schema.TypeBool, + }, + "policy_token": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_-]{1,64}$"), + Optional: true, + Description: "Assign a prefix label to help match the policy token to this set of images, limited to 32 alphanumeric or underscore characters. If you don't specify a label, `default` becomes the prefix.", + Type: schema.TypeString, + }, + "policy_token_default": { + Optional: true, + Description: "Specify the default policy identifier, which is registered with the `Image and Video Manager API` once you activate this property. The `advanced` option needs to be inactive.", + Type: schema.TypeString, + }, + }, + }, + }, + "image_manager_video": { + Optional: true, + Type: schema.TypeList, + Description: "Optimizes videos managed by Image and Video Manager for the requesting device. You can also use this behavior to generate API tokens to apply your own policies to matching videos using the `Image and Video Manager API`. To apply this behavior, you need to match on a `fileExtension`. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "settings_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Applies Image and Video Manager's video optimization to the current content.", + Type: schema.TypeBool, + }, + "resize": { + Optional: true, + Description: "When enabled, scales down video for smaller mobile screens, based on the device's `User-Agent` header.", + Type: schema.TypeBool, + }, + "apply_best_file_type": { + Optional: true, + Description: "When enabled, automatically converts videos to the best file type for the requesting device. This produces the smallest file size that retains image quality, based on the user agent and the initial image file.", + Type: schema.TypeBool, + }, + "super_cache_region": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"US", "ASIA", "AUSTRALIA", "EMEA", "JAPAN", "CHINA"}, false)), + Optional: true, + Description: "To optimize caching, assign a region close to your site's heaviest traffic.", + Type: schema.TypeString, + }, + "traffic_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "cp_code_original": { + Optional: true, + Description: "Specifies the CP code for which to track Image and Video Manager video traffic. Use this along with `cpCodeTransformed` to track traffic to derivative video content. You only need to provide the initial `id`, stripping any `cpc_` prefix to pass the integer to the rule tree. Additional CP code details may reflect back in subsequent read-only data.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "created_date": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "description": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "products": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "cp_code_limits": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "current_capacity": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit_type": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + "cp_code_transformed": { + Optional: true, + Description: "Specifies the CP code to identify derivative transformed video content. You only need to provide the initial `id`, stripping any `cpc_` prefix to pass the integer to the rule tree. Additional CP code details may reflect back in subsequent read-only data.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "created_date": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "description": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "products": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "cp_code_limits": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "current_capacity": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit_type": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + "api_reference_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "use_existing_policy_set": { + Optional: true, + Description: "Whether to use a previously created policy set that may be referenced in other properties, or create a new policy set to use with this property. A policy set can be shared across multiple properties belonging to the same contract. The behavior populates any changes to the policy set across all properties that reference that set.", + Type: schema.TypeBool, + }, + "policy_set": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_-]+-v$"), + Optional: true, + Description: "Identifies the existing policy set configured with `Image and Video Manager API`.", + Type: schema.TypeString, + }, + "advanced": { + Optional: true, + Description: "When disabled, applies a single standard policy based on your property name. Allows you to reference a rule-specific `policyToken` for videos with different match criteria.", + Type: schema.TypeBool, + }, + "policy_token": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_-]{1,64}$"), + Optional: true, + Description: "Specifies a custom policy defined in the Image and Video Manager Policy Manager or the `Image and Video Manager API`. The policy name can include up to 64 alphanumeric, dash, or underscore characters.", + Type: schema.TypeString, + }, + "policy_token_default": { + Optional: true, + Description: "Specifies the default policy identifier, which is registered with the `Image and Video Manager API` once you activate this property.", + Type: schema.TypeString, + }, + }, + }, + }, + "include": { + Optional: true, + Type: schema.TypeList, + Description: "Includes let you reuse chunks of a property configuration that you can manage separately from the rest of the property rule tree. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "id": { + Optional: true, + Description: "Identifies the include you want to add to your rule tree. You can get the include ID using `PAPI`. This option only accepts digits, without the `inc_` ID prefix.", + Type: schema.TypeString, + }, + }, + }, + }, + "instant": { + Optional: true, + Type: schema.TypeList, + Description: "The Instant feature allows you to prefetch content to the edge cache by adding link relation attributes to markup. For example: This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "prefetch_cacheable": { + Optional: true, + Description: "When enabled, applies prefetching only to objects already set to be cacheable, for example using the `caching` behavior. Only applies to content with the `tieredDistribution` behavior enabled.", + Type: schema.TypeBool, + }, + "prefetch_no_store": { + Optional: true, + Description: "Allows otherwise non-cacheable `no-store` content to prefetch if the URL path ends with `/` to indicate a request for a default file, or if the extension matches the value of the `prefetchNoStoreExtensions` option. Only applies to content with the `sureRoute` behavior enabled.", + Type: schema.TypeBool, + }, + "prefetch_no_store_extensions": { + Optional: true, + Description: "Specifies a set of file extensions for which the `prefetchNoStore` option is allowed.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "prefetch_html": { + Optional: true, + Description: "Allows edge servers to prefetch additional HTML pages while pages that link to them are being delivered. This only applies to links from `` or `` tags with the appropriate link relation attribute.", + Type: schema.TypeBool, + }, + "custom_link_relations": { + Optional: true, + Description: "Specify link relation values that activate the prefetching behavior. For example, specifying `fetch` allows you to use shorter `rel=\"fetch\"` markup.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "instant_config": { + Optional: true, + Type: schema.TypeList, + Description: "Multi-Domain Configuration, also known as `InstantConfig`, allows you to apply property settings to all incoming hostnames based on a DNS lookup, without explicitly listing them among the property's hostnames. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the InstantConfig behavior.", + Type: schema.TypeBool, + }, + }, + }, + }, + "large_file_optimization": { + Optional: true, + Type: schema.TypeList, + Description: "The `Large File Optimization` (LFO) feature improves performance and reliability when delivering large files. You need this behavior for objects larger than 1.8GB, and you should apply it to anything over 100MB. You should apply it only to the specific content to be optimized, such as a download directory's `.gz` files, and enable the `useVersioning` option while enforcing your own filename versioning policy. Make sure you meet all the `requirements and best practices` for the LFO delivery. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the file optimization behavior.", + Type: schema.TypeBool, + }, + "enable_partial_object_caching": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"PARTIAL_OBJECT_CACHING", "NON_PARTIAL_OBJECT_CACHING"}, false)), + Optional: true, + Description: "Specifies whether to cache partial objects.", + Type: schema.TypeString, + }, + "minimum_size": { + ValidateDiagFunc: validateRegexOrVariable("^\\d+[K,M,G,T]B$"), + Optional: true, + Description: "Optimization only applies to files larger than this, expressed as a number suffixed with a unit string such as `MB` or `GB`.", + Type: schema.TypeString, + }, + "maximum_size": { + ValidateDiagFunc: validateRegexOrVariable("^\\d+[K,M,G,T]B$"), + Optional: true, + Description: "Optimization does not apply to files larger than this, expressed as a number suffixed with a unit string such as `MB` or `GB`. The size of a file can't be greater than 323 GB. If you need to optimize a larger file, contact Akamai Professional Services for help. This option is for internal usage only.", + Type: schema.TypeString, + }, + "use_versioning": { + Optional: true, + Description: "When `enablePartialObjectCaching` is set to `PARTIAL_OBJECT_CACHING`, enabling this option signals your intention to vary filenames by version, strongly recommended to avoid serving corrupt content when chunks come from different versions of the same file.", + Type: schema.TypeBool, + }, + }, + }, + }, + "large_file_optimization_advanced": { + Optional: true, + Type: schema.TypeList, + Description: "The `Large File Optimization` feature improves performance and reliability when delivering large files. You need this behavior for objects larger than 1.8GB, and it's recommended for anything over 100MB. You should apply it only to the specific content to be optimized, such as a download directory's `.gz` files. Note that it is best to use `NetStorage` for objects larger than 1.8GB. This behavior is for internal usage only. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the file optimization behavior.", + Type: schema.TypeBool, + }, + "object_size": { + ValidateDiagFunc: validateRegexOrVariable("^\\d+[K,M,G,T]B$"), + Optional: true, + Description: "Specifies the size of the file at which point to apply partial object (POC) caching. Append a numeric value with a `MB` or `GB` suffix.", + Type: schema.TypeString, + }, + "fragment_size": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"HALF_MB", "ONE_MB", "TWO_MB", "FOUR_MB"}, false)), + Optional: true, + Description: "Specifies the size of each fragment used for partial object caching.", + Type: schema.TypeString, + }, + "prefetch_during_request": { + Optional: true, + Description: "The number of POC fragments to prefetch during the request.", + Type: schema.TypeInt, + }, + "prefetch_after_request": { + Optional: true, + Description: "The number of POC fragments to prefetch after the request.", + Type: schema.TypeInt, + }, + }, + }, + }, + "limit_bit_rate": { + Optional: true, + Type: schema.TypeList, + Description: "Control the rate at which content serves out to end users, optionally varying the speed depending on the file size or elapsed download time. Each bit rate specified in the `bitrateTable` array corresponds to a `thresholdTable` entry that activates it. You can use this behavior to prevent media downloads from progressing faster than they are viewed, for example, or to differentiate various tiers of end-user experience. To apply this behavior, you should match on a `contentType`, `path`, or `filename`. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "When enabled, activates the bit rate limiting behavior.", + Type: schema.TypeBool, + }, + "bitrate_table": { + Optional: true, + Description: "Specifies a download rate that corresponds to a `thresholdTable` entry. The bit rate appears as a two-member object consisting of a numeric `bitrateValue` and a `bitrateUnit` string, with allowed values of `Kbps`, `Mbps`, and `Gbps`.", + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bitrate_value": { + Optional: true, + Description: "The numeric indicator of the download rate.", + Type: schema.TypeFloat, + }, + "bitrate_unit": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"KBPS", "MBPS", "GBPS"}, false)), + Optional: true, + Description: "The unit of measurement, either `KBPS`, `MBPS`, or `GBPS`.", + Type: schema.TypeString, + }, + }, + }, + }, + "threshold_table": { + Optional: true, + Description: "Specifies the minimum size of the file or the amount of elapsed download time before applying the bit rate limit from the corresponding `bitrateTable` entry. The threshold appears as a two-member object consisting of a numeric `thresholdValue` and `thresholdUnit` string, with allowed values of `SECONDS` or `BYTES`.", + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "threshold_value": { + Optional: true, + Description: "The numeric indicator of the minimum file size or elapsed download time.", + Type: schema.TypeInt, + }, + "threshold_unit": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"BYTES", "SECONDS"}, false)), + Optional: true, + Description: "The unit of measurement, either `SECONDS` of the elapsed download time, or `BYTES` of the file size.", + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + "log_custom": { + Optional: true, + Type: schema.TypeList, + Description: "Logs custom details from the origin response in the `Log Delivery Service` report. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "log_custom_log_field": { + Optional: true, + Description: "Whether to append additional custom data to each log line.", + Type: schema.TypeBool, + }, + "custom_log_field": { + Optional: true, + Description: "Specifies an additional data field to append to each log line, maximum 1000 bytes, typically based on a dynamically generated built-in system variable. For example, `round-trip: {{builtin.AK_CLIENT_TURNAROUND_TIME}}ms` logs the total time to complete the response. See `Support for variables` for more information. Since this option can specify both a request and response, it overrides any `customLogField` settings in the `report` behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + "m_pulse": { + Optional: true, + Type: schema.TypeList, + Description: "`mPulse` provides high-level performance analytics and predictive recommendations based on real end user data. See the `mPulse Quick Start` to set up mPulse on your website. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Applies performance monitoring to this behavior's set of content.", + Type: schema.TypeBool, + }, + "require_pci": { + Optional: true, + Description: "Suppresses gathering metrics for potentially sensitive end-user interactions. Enabling this omits data from some older browsers.", + Type: schema.TypeBool, + }, + "loader_version": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"V10", "V12", "LATEST", "BETA"}, false)), + Optional: true, + Description: "Specifies the version of the Boomerang JavaScript loader snippet. See `mPulse Loader Snippets` for more information.", + Type: schema.TypeString, + }, + "title_optional": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "api_key": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validateRegexOrVariable("^$|^[a-zA-Z2-9]{5}-[a-zA-Z2-9]{5}-[a-zA-Z2-9]{5}-[a-zA-Z2-9]{5}-[a-zA-Z2-9]{5}$")), + Optional: true, + Description: "This generated value uniquely identifies sections of your website for you to analyze independently. To access this value, see `Enable mPulse in Property Manager`.", + Type: schema.TypeString, + }, + "buffer_size": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validateRegexOrVariable("^(1[5-9][0-9]|1[0-9]{3}|[2-9][0-9]{2,3})$")), + Optional: true, + Description: "Allows you to override the browser's default (150) maximum number of reported performance timeline entries.", + Type: schema.TypeString, + }, + "config_override": { + Optional: true, + Description: "A JSON string representing a configuration object passed to the JavaScript library under which mPulse runs. It corresponds at run-time to the `window.BOOMR_config` object. For example, this turns on monitoring of Single Page App frameworks: `\"{\\\"history\\\": {\\\"enabled\\\": true, \\\"auto\\\": true}}\"`. See `Configuration Overrides` for more information.", + Type: schema.TypeString, + }, + }, + }, + }, + "manifest_personalization": { + Optional: true, + Type: schema.TypeList, + Description: "Allows customers who use the Adaptive Media Delivery product to enhance content based on the capabilities of each end user's device. This behavior configures a `manifest` for both HLS Live and on-demand streaming. For more information, see `Adaptive Media Delivery`. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the Manifest Personalization feature.", + Type: schema.TypeBool, + }, + "hls_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "hls_enabled": { + Optional: true, + Description: "Allows you to customize the HLS master manifest that's sent to the requesting client.", + Type: schema.TypeBool, + }, + "hls_mode": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"BEST_PRACTICE", "CUSTOM"}, false)), + Optional: true, + Description: "Applies with `hlsEnabled` on.", + Type: schema.TypeString, + }, + "hls_preferred_bitrate": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validateRegexOrVariable("^\\d+$")), + Optional: true, + Description: "Sets the preferred bit rate in Kbps. This causes the media playlist specified in the `#EXT-X-STREAM-INF` tag that most closely matches the value to list first. All other playlists maintain their current position in the manifest.", + Type: schema.TypeString, + }, + "hls_filter_in_bitrates": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validateRegexOrVariable("^\\d+(,\\d+)*$")), + Optional: true, + Description: "Specifies a comma-delimited set of preferred bit rates, such as `100,200,400`. Playlists specified in the `#EXT-X-STREAM-INF` tag with bit rates outside of any of those values by up to 100 Kbps are excluded from the manifest.", + Type: schema.TypeString, + }, + "hls_filter_in_bitrate_ranges": { + Optional: true, + Description: "Specifies a comma-delimited set of bit rate ranges, such as `100-400,1000-4000`. Playlists specified in the `#EXT-X-STREAM-INF` tag with bit rates outside of any of those ranges are excluded from the manifest.", + Type: schema.TypeString, + }, + "hls_query_param_enabled": { + Optional: true, + Description: "Specifies query parameters for the HLS master manifest to customize the manifest's content. Any settings specified in the query string override those already configured in Property Manager.", + Type: schema.TypeBool, + }, + "hls_query_param_secret_key": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validateRegexOrVariable("^(0x)?[0-9a-fA-F]{32}$")), + Optional: true, + Description: "Specifies a primary key as a token to accompany the request.", + Type: schema.TypeString, + }, + "hls_query_param_transition_key": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validateRegexOrVariable("^(0x)?[0-9a-fA-F]{32}$")), + Optional: true, + Description: "Specifies a transition key as a token to accompany the request.", + Type: schema.TypeString, + }, + "hls_show_advanced": { + Optional: true, + Description: "Allows you to configure advanced settings.", + Type: schema.TypeBool, + }, + "hls_enable_debug_headers": { + Optional: true, + Description: "Includes additional `Akamai-Manifest-Personalization` and `Akamai-Manifest-Personalization-Config-Source` debugging headers.", + Type: schema.TypeBool, + }, + }, + }, + }, + "manifest_rerouting": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior works with `adScalerCircuitBreaker`. It delegates parts of the media delivery workflow, like ad insertion, to other technology partners. Akamai reroutes manifest file requests to partner platforms for processing prior to being delivered. Rerouting simplifies the workflow and improves the media streaming experience. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "partner": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"adobe_primetime"}, false)), + Optional: true, + Description: "Set this value to `adobe_primetime`, which is an external technology partner that provides value added offerings, like advertisement integration, to the requested media objects.", + Type: schema.TypeString, + }, + "username": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9A-Za-z!@.,;:'\"?-]{1,50}$"), + Optional: true, + Description: "The user name for your Adobe Primetime account.", + Type: schema.TypeString, + }, + }, + }, + }, + "manual_server_push": { + Optional: true, + Type: schema.TypeList, + Description: "With the `http2` behavior enabled, this loads a specified set of objects into the client browser's cache. To apply this behavior, you should match on a `path` or `filename`. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "serverpushlist": { + Optional: true, + Description: "Specifies the set of objects to load into the client browser's cache over HTTP2. Each value in the array represents a hostname and full path to the object, such as `www.example.com/js/site.js`.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "media_acceleration": { + Optional: true, + Type: schema.TypeList, + Description: "Enables Accelerated Media Delivery for this set of requests. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables Media Acceleration.", + Type: schema.TypeBool, + }, + }, + }, + }, + "media_acceleration_quic_optout": { + Optional: true, + Type: schema.TypeList, + Description: "When enabled, disables use of QUIC protocol for this set of accelerated media content. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "optout": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + "media_client": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior is deprecated, but you should not disable or remove it if present. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables client-side download analytics.", + Type: schema.TypeBool, + }, + "beacon_id": { + Optional: true, + Description: "Specifies the ID of data source's beacon.", + Type: schema.TypeString, + }, + "use_hybrid_http_udp": { + Optional: true, + Description: "Enables the hybrid HTTP/UDP protocol.", + Type: schema.TypeBool, + }, + }, + }, + }, + "media_file_retrieval_optimization": { + Optional: true, + Type: schema.TypeList, + Description: "Media File Retrieval Optimization (MFRO) speeds the delivery of large media files by relying on caches of partial objects. You should use it for files larger than 100 MB. It's required for files larger than 1.8 GB, and works best with `NetStorage`. To apply this behavior, you should match on a `fileExtension`. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the partial-object caching behavior.", + Type: schema.TypeBool, + }, + }, + }, + }, + "media_origin_failover": { + Optional: true, + Type: schema.TypeList, + Description: "Specifies how edge servers respond when the origin is unresponsive, or suffers from server or content errors. You can specify how many times to retry, switch to a backup origin hostname, or configure a redirect. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "detect_origin_unresponsive_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "detect_origin_unresponsive": { + Optional: true, + Description: "Allows you to configure what happens when the origin is unresponsive.", + Type: schema.TypeBool, + }, + "origin_unresponsive_detection_level": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"AGGRESSIVE", "CONSERVATIVE", "MODERATE"}, false)), + Optional: true, + Description: "Specify the level of response to slow origin connections.", + Type: schema.TypeString, + }, + "origin_unresponsive_blacklist_origin_ip": { + Optional: true, + Description: "Enabling this blacklists the origin's IP address.", + Type: schema.TypeBool, + }, + "origin_unresponsive_blacklist_window": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"TEN_S", "THIRTY_S"}, false)), + Optional: true, + Description: "This sets the delay before blacklisting an IP address.", + Type: schema.TypeString, + }, + "origin_unresponsive_recovery": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"RETRY_X_TIMES", "SWITCH_TO_BACKUP_ORIGIN", "REDIRECT_TO_DIFFERENT_ORIGIN_LOCATION"}, false)), + Optional: true, + Description: "This sets the recovery option.", + Type: schema.TypeString, + }, + "origin_unresponsive_retry_limit": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ONE", "TWO", "THREE"}, false)), + Optional: true, + Description: "Sets how many times to retry.", + Type: schema.TypeString, + }, + "origin_unresponsive_backup_host": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), + Optional: true, + Description: "This specifies the origin hostname.", + Type: schema.TypeString, + }, + "origin_unresponsive_alternate_host": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), + Optional: true, + Description: "This specifies the redirect's destination hostname.", + Type: schema.TypeString, + }, + "origin_unresponsive_modify_request_path": { + Optional: true, + Description: "Modifies the request path.", + Type: schema.TypeBool, + }, + "origin_unresponsive_modified_path": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "This specifies the path to form the new URL.", + Type: schema.TypeString, + }, + "origin_unresponsive_include_query_string": { + Optional: true, + Description: "Enabling this includes the original set of query parameters.", + Type: schema.TypeBool, + }, + "origin_unresponsive_redirect_method": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntInSlice([]int{301, 302})), + Optional: true, + Description: "Specifies the redirect response code.", + Type: schema.TypeInt, + }, + "origin_unresponsive_change_protocol": { + Optional: true, + Description: "This allows you to change the request protocol.", + Type: schema.TypeBool, + }, + "origin_unresponsive_protocol": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"HTTP", "HTTPS"}, false)), + Optional: true, + Description: "Specifies which protocol to use.", + Type: schema.TypeString, + }, + "detect_origin_unavailable_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "detect_origin_unavailable": { + Optional: true, + Description: "Allows you to configure failover settings when the origin server responds with errors.", + Type: schema.TypeBool, + }, + "origin_unavailable_detection_level": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"RESPONSE_CODES"}, false)), + Optional: true, + Description: "Specify `RESPONSE_CODES`, the only available option.", + Type: schema.TypeString, + }, + "origin_unavailable_response_codes": { + Optional: true, + Description: "Specifies the set of response codes identifying when the origin responds with errors.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "origin_unavailable_blacklist_origin_ip": { + Optional: true, + Description: "Enabling this blacklists the origin's IP address.", + Type: schema.TypeBool, + }, + "origin_unavailable_blacklist_window": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"TEN_S", "THIRTY_S"}, false)), + Optional: true, + Description: "This sets the delay before blacklisting an IP address.", + Type: schema.TypeString, + }, + "origin_unavailable_recovery": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"RETRY_X_TIMES", "SWITCH_TO_BACKUP_ORIGIN", "REDIRECT_TO_DIFFERENT_ORIGIN_LOCATION"}, false)), + Optional: true, + Description: "This sets the recovery option.", + Type: schema.TypeString, + }, + "origin_unavailable_retry_limit": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ONE", "TWO", "THREE"}, false)), + Optional: true, + Description: "Sets how many times to retry.", + Type: schema.TypeString, + }, + "origin_unavailable_backup_host": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), + Optional: true, + Description: "This specifies the origin hostname.", + Type: schema.TypeString, + }, + "origin_unavailable_alternate_host": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), + Optional: true, + Description: "This specifies the redirect's destination hostname.", + Type: schema.TypeString, + }, + "origin_unavailable_modify_request_path": { + Optional: true, + Description: "Modifies the request path.", + Type: schema.TypeBool, + }, + "origin_unavailable_modified_path": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "This specifies the path to form the new URL.", + Type: schema.TypeString, + }, + "origin_unavailable_include_query_string": { + Optional: true, + Description: "Enabling this includes the original set of query parameters.", + Type: schema.TypeBool, + }, + "origin_unavailable_redirect_method": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntInSlice([]int{301, 302})), + Optional: true, + Description: "Specifies either a redirect response code.", + Type: schema.TypeInt, + }, + "origin_unavailable_change_protocol": { + Optional: true, + Description: "Modifies the request protocol.", + Type: schema.TypeBool, + }, + "origin_unavailable_protocol": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"HTTP", "HTTPS"}, false)), + Optional: true, + Description: "Specifies either the `HTTP` or `HTTPS` protocol.", + Type: schema.TypeString, + }, + "detect_object_unavailable_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "detect_object_unavailable": { + Optional: true, + Description: "Allows you to configure failover settings when the origin has content errors.", + Type: schema.TypeBool, + }, + "object_unavailable_detection_level": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"RESPONSE_CODES"}, false)), + Optional: true, + Description: "Specify `RESPONSE_CODES`, the only available option.", + Type: schema.TypeString, + }, + "object_unavailable_response_codes": { + Optional: true, + Description: "Specifies the set of response codes identifying when there are content errors.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "object_unavailable_blacklist_origin_ip": { + Optional: true, + Description: "Enabling this blacklists the origin's IP address.", + Type: schema.TypeBool, + }, + "object_unavailable_blacklist_window": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"TEN_S", "THIRTY_S"}, false)), + Optional: true, + Description: "This sets the delay before blacklisting an IP address.", + Type: schema.TypeString, + }, + "object_unavailable_recovery": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"RETRY_X_TIMES", "SWITCH_TO_BACKUP_ORIGIN", "REDIRECT_TO_DIFFERENT_ORIGIN_LOCATION"}, false)), + Optional: true, + Description: "This sets the recovery option.", + Type: schema.TypeString, + }, + "object_unavailable_retry_limit": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ONE", "TWO", "THREE"}, false)), + Optional: true, + Description: "Sets how many times to retry.", + Type: schema.TypeString, + }, + "object_unavailable_backup_host": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), + Optional: true, + Description: "This specifies the origin hostname.", + Type: schema.TypeString, + }, + "object_unavailable_alternate_host": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), + Optional: true, + Description: "This specifies the redirect's destination hostname.", + Type: schema.TypeString, + }, + "object_unavailable_modify_request_path": { + Optional: true, + Description: "Enabling this allows you to modify the request path.", + Type: schema.TypeBool, + }, + "object_unavailable_modified_path": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "This specifies the path to form the new URL.", + Type: schema.TypeString, + }, + "object_unavailable_include_query_string": { + Optional: true, + Description: "Enabling this includes the original set of query parameters.", + Type: schema.TypeBool, + }, + "object_unavailable_redirect_method": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntInSlice([]int{301, 302})), + Optional: true, + Description: "Specifies a redirect response code.", + Type: schema.TypeInt, + }, + "object_unavailable_change_protocol": { + Optional: true, + Description: "Changes the request protocol.", + Type: schema.TypeBool, + }, + "object_unavailable_protocol": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"HTTP", "HTTPS"}, false)), + Optional: true, + Description: "Specifies either the `HTTP` or `HTTPS` protocol.", + Type: schema.TypeString, + }, + "other_options": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "client_response_code": { + Optional: true, + Description: "Specifies the response code served to the client.", + Type: schema.TypeString, + }, + "cache_error_response": { + Optional: true, + Description: "When enabled, caches the error response.", + Type: schema.TypeBool, + }, + "cache_window": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ONE_S", "TEN_S", "THIRTY_S"}, false)), + Optional: true, + Description: "This sets error response's TTL.", + Type: schema.TypeString, + }, + }, + }, + }, + "metadata_caching": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior reduces time spent waiting for the initial response, also known as time to first byte, during peak traffic events. Contact Akamai Professional Services for help configuring it. This behavior is for internal usage only. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables metadata caching.", + Type: schema.TypeBool, + }, + }, + }, + }, + "mobile_sdk_performance": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior is deprecated, but you should not disable or remove it if present. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the Mobile App Performance SDK.", + Type: schema.TypeBool, + }, + "secondary_multipath_to_origin": { + Optional: true, + Description: "When enabled, sends secondary multi-path requests to the origin server.", + Type: schema.TypeBool, + }, + }, + }, + }, + "modify_incoming_request_header": { + Optional: true, + Type: schema.TypeList, + Description: "Modify, add, remove, or pass along specific request headers coming upstream from the client. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "action": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ADD", "DELETE", "MODIFY", "PASS"}, false)), + Optional: true, + Description: "Either `ADD`, `DELETE`, `MODIFY`, or `PASS` incoming HTTP request headers.", + Type: schema.TypeString, + }, + "standard_add_header_name": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ACCEPT_ENCODING", "ACCEPT_LANGUAGE", "OTHER"}, false)), + Optional: true, + Description: "If the value of `action` is `ADD`, this specifies the name of the field to add.", + Type: schema.TypeString, + }, + "standard_delete_header_name": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IF_MODIFIED_SINCE", "VIA", "OTHER"}, false)), + Optional: true, + Description: "If the value of `action` is `DELETE`, this specifies the name of the field to remove.", + Type: schema.TypeString, + }, + "standard_modify_header_name": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ACCEPT_ENCODING", "ACCEPT_LANGUAGE", "OTHER"}, false)), + Optional: true, + Description: "If the value of `action` is `MODIFY`, this specifies the name of the field to modify.", + Type: schema.TypeString, + }, + "standard_pass_header_name": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ACCEPT_ENCODING", "ACCEPT_LANGUAGE", "OTHER"}, false)), + Optional: true, + Description: "If the value of `action` is `PASS`, this specifies the name of the field to pass through.", + Type: schema.TypeString, + }, + "custom_header_name": { + ValidateDiagFunc: validateRegexOrVariable("^[^()<>@,;:\\\"/\\[\\]?{}\\s]+$"), + Optional: true, + Description: "Specifies a custom field name that applies when the relevant `standard` header name is set to `OTHER`.", + Type: schema.TypeString, + }, + "header_value": { + Optional: true, + Description: "Specifies the new header value.", + Type: schema.TypeString, + }, + "new_header_value": { + Optional: true, + Description: "Supplies an HTTP header replacement value.", + Type: schema.TypeString, + }, + "avoid_duplicate_headers": { + Optional: true, + Description: "When enabled with the `action` set to `MODIFY`, prevents creation of more than one instance of a header.", + Type: schema.TypeBool, + }, + }, + }, + }, + "modify_incoming_response_header": { + Optional: true, + Type: schema.TypeList, + Description: "Modify, add, remove, or pass along specific response headers coming downstream from the origin. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "action": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ADD", "DELETE", "MODIFY", "PASS"}, false)), + Optional: true, + Description: "Either `ADD`, `DELETE`, `MODIFY`, or `PASS` incoming HTTP response headers.", + Type: schema.TypeString, + }, + "standard_add_header_name": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CACHE_CONTROL", "CONTENT_TYPE", "EDGE_CONTROL", "EXPIRES", "LAST_MODIFIED", "OTHER"}, false)), + Optional: true, + Description: "If the value of `action` is `ADD`, this specifies the name of the field to add.", + Type: schema.TypeString, + }, + "standard_delete_header_name": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CACHE_CONTROL", "CONTENT_TYPE", "VARY", "OTHER"}, false)), + Optional: true, + Description: "If the value of `action` is `DELETE`, this specifies the name of the field to remove.", + Type: schema.TypeString, + }, + "standard_modify_header_name": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CACHE_CONTROL", "CONTENT_TYPE", "EDGE_CONTROL", "OTHER"}, false)), + Optional: true, + Description: "If the value of `action` is `MODIFY`, this specifies the name of the field to modify.", + Type: schema.TypeString, + }, + "standard_pass_header_name": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CACHE_CONTROL", "EXPIRES", "PRAGMA", "OTHER"}, false)), + Optional: true, + Description: "If the value of `action` is `PASS`, this specifies the name of the field to pass through.", + Type: schema.TypeString, + }, + "custom_header_name": { + ValidateDiagFunc: validateRegexOrVariable("^[^()<>@,;:\\\"/\\[\\]?{}\\s]+$"), + Optional: true, + Description: "Specifies a custom field name that applies when the relevant `standard` header name is set to `OTHER`.", + Type: schema.TypeString, + }, + "header_value": { + Optional: true, + Description: "Specifies the header's new value.", + Type: schema.TypeString, + }, + "new_header_value": { + Optional: true, + Description: "Specifies an HTTP header replacement value.", + Type: schema.TypeString, + }, + "avoid_duplicate_headers": { + Optional: true, + Description: "When enabled with the `action` set to `MODIFY`, prevents creation of more than one instance of a header.", + Type: schema.TypeBool, + }, + }, + }, + }, + "modify_outgoing_request_header": { + Optional: true, + Type: schema.TypeList, + Description: "Modify, add, remove, or pass along specific request headers going upstream towards the origin. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "action": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ADD", "DELETE", "MODIFY", "REGEX"}, false)), + Optional: true, + Description: "Either `ADD` or `DELETE` outgoing HTTP request headers, `MODIFY` their fixed values, or specify a `REGEX` pattern to transform them.", + Type: schema.TypeString, + }, + "standard_add_header_name": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"USER_AGENT", "OTHER"}, false)), + Optional: true, + Description: "If the value of `action` is `ADD`, this specifies the name of the field to add.", + Type: schema.TypeString, + }, + "standard_delete_header_name": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"PRAGMA", "USER_AGENT", "VIA", "OTHER"}, false)), + Optional: true, + Description: "If the value of `action` is `DELETE`, this specifies the name of the field to remove.", + Type: schema.TypeString, + }, + "standard_modify_header_name": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"USER_AGENT", "OTHER"}, false)), + Optional: true, + Description: "If the value of `action` is `MODIFY` or `REGEX`, this specifies the name of the field to modify.", + Type: schema.TypeString, + }, + "custom_header_name": { + ValidateDiagFunc: validateRegexOrVariable("^[^()<>@,;:\\\"/\\[\\]?{}\\s]+$"), + Optional: true, + Description: "Specifies a custom field name that applies when the relevant `standard` header name is set to `OTHER`.", + Type: schema.TypeString, + }, + "header_value": { + Optional: true, + Description: "Specifies the new header value.", + Type: schema.TypeString, + }, + "new_header_value": { + Optional: true, + Description: "Specifies an HTTP header replacement value.", + Type: schema.TypeString, + }, + "regex_header_match": { + Optional: true, + Description: "Specifies a Perl-compatible regular expression to match within the header value.", + Type: schema.TypeString, + }, + "regex_header_replace": { + Optional: true, + Description: "Specifies text that replaces the `regexHeaderMatch` pattern within the header value.", + Type: schema.TypeString, + }, + "match_multiple": { + Optional: true, + Description: "When enabled with the `action` set to `REGEX`, replaces all occurrences of the matched regular expression, otherwise only the first match if disabled.", + Type: schema.TypeBool, + }, + "avoid_duplicate_headers": { + Optional: true, + Description: "When enabled with the `action` set to `MODIFY`, prevents creation of more than one instance of a header.", + Type: schema.TypeBool, + }, + }, + }, + }, + "modify_outgoing_response_header": { + Optional: true, + Type: schema.TypeList, + Description: "Modify, add, remove, or pass along specific response headers going downstream towards the client. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "action": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ADD", "DELETE", "MODIFY", "REGEX"}, false)), + Optional: true, + Description: "Either `ADD` or `DELETE` outgoing HTTP response headers, `MODIFY` their fixed values, or specify a `REGEX` pattern to transform them.", + Type: schema.TypeString, + }, + "standard_add_header_name": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CACHE_CONTROL", "CONTENT_DISPOSITION", "CONTENT_TYPE", "EDGE_CONTROL", "P3P", "PRAGMA", "ACCESS_CONTROL_ALLOW_ORIGIN", "ACCESS_CONTROL_ALLOW_METHODS", "ACCESS_CONTROL_ALLOW_HEADERS", "ACCESS_CONTROL_EXPOSE_HEADERS", "ACCESS_CONTROL_ALLOW_CREDENTIALS", "ACCESS_CONTROL_MAX_AGE", "OTHER"}, false)), + Optional: true, + Description: "If the value of `action` is `ADD`, this specifies the name of the field to add.", + Type: schema.TypeString, + }, + "standard_delete_header_name": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CACHE_CONTROL", "CONTENT_DISPOSITION", "CONTENT_TYPE", "EXPIRES", "P3P", "PRAGMA", "ACCESS_CONTROL_ALLOW_ORIGIN", "ACCESS_CONTROL_ALLOW_METHODS", "ACCESS_CONTROL_ALLOW_HEADERS", "ACCESS_CONTROL_EXPOSE_HEADERS", "ACCESS_CONTROL_ALLOW_CREDENTIALS", "ACCESS_CONTROL_MAX_AGE", "OTHER"}, false)), + Optional: true, + Description: "If the value of `action` is `DELETE`, this specifies the name of the field to remove.", + Type: schema.TypeString, + }, + "standard_modify_header_name": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CACHE_CONTROL", "CONTENT_DISPOSITION", "CONTENT_TYPE", "P3P", "PRAGMA", "ACCESS_CONTROL_ALLOW_ORIGIN", "ACCESS_CONTROL_ALLOW_METHODS", "ACCESS_CONTROL_ALLOW_HEADERS", "ACCESS_CONTROL_EXPOSE_HEADERS", "ACCESS_CONTROL_ALLOW_CREDENTIALS", "ACCESS_CONTROL_MAX_AGE", "OTHER"}, false)), + Optional: true, + Description: "If the value of `action` is `MODIFY` or `REGEX`, this specifies the name of the field to modify.", + Type: schema.TypeString, + }, + "custom_header_name": { + ValidateDiagFunc: validateRegexOrVariable("^[^()<>@,;:\\\"/\\[\\]?{}\\s]+$"), + Optional: true, + Description: "Specifies a custom field name that applies when the relevant `standard` header name is set to `OTHER`.", + Type: schema.TypeString, + }, + "header_value": { + Optional: true, + Description: "Specifies the existing value of the header to match.", + Type: schema.TypeString, + }, + "new_header_value": { + Optional: true, + Description: "Specifies the new HTTP header replacement value.", + Type: schema.TypeString, + }, + "regex_header_match": { + Optional: true, + Description: "Specifies a Perl-compatible regular expression to match within the header value.", + Type: schema.TypeString, + }, + "regex_header_replace": { + Optional: true, + Description: "Specifies text that replaces the `regexHeaderMatch` pattern within the header value.", + Type: schema.TypeString, + }, + "match_multiple": { + Optional: true, + Description: "When enabled with the `action` set to `REGEX`, replaces all occurrences of the matched regular expression, otherwise only the first match if disabled.", + Type: schema.TypeBool, + }, + "avoid_duplicate_headers": { + Optional: true, + Description: "When enabled with the `action` set to `MODIFY`, prevents creation of more than one instance of a header. The last header clobbers others with the same name. This option affects the entire set of outgoing headers, and is not confined to the subset of regular expression matches.", + Type: schema.TypeBool, + }, + }, + }, + }, + "modify_via_header": { + Optional: true, + Type: schema.TypeList, + Description: "Removes or renames the HTTP `Via` headers used to inform the server of proxies through which the request was sent to the origin. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables `Via` header modifications.", + Type: schema.TypeBool, + }, + "modification_option": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"REMOVE_HEADER", "RENAME_HEADER"}, false)), + Optional: true, + Description: "Specify how you want to handle the header.", + Type: schema.TypeString, + }, + "rename_header_to": { + ValidateDiagFunc: validateRegexOrVariable("^[^()<>@,;:\\\"\\[\\]?{}\\s]+$"), + Optional: true, + Description: "Specifies a new name to replace the existing `Via` header.", + Type: schema.TypeString, + }, + }, + }, + }, + "origin": { + Optional: true, + Type: schema.TypeList, + Description: "Specify the hostname and settings used to contact the origin once service begins. You can use your own origin, `NetStorage`, an Edge Load Balancing origin, or a SaaS dynamic origin. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "origin_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CUSTOMER", "NET_STORAGE", "MEDIA_SERVICE_LIVE", "EDGE_LOAD_BALANCING_ORIGIN_GROUP", "SAAS_DYNAMIC_ORIGIN"}, false)), + Optional: true, + Description: "Choose where your content is retrieved from.", + Type: schema.TypeString, + }, + "net_storage": { + Optional: true, + Description: "Specifies the details of the NetStorage server.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cp_code": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "download_domain_name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "g2o_token": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "origin_id": { + Optional: true, + Description: "Identifies the Edge Load Balancing origin. This needs to correspond to an `edgeLoadBalancingOrigin` behavior's `id` attribute within the same property.", + Type: schema.TypeString, + }, + "hostname": { + Optional: true, + Description: "Specifies the hostname or IPv4 address of your origin server, from which edge servers can retrieve your content.", + Type: schema.TypeString, + }, + "second_hostname_enabled": { + Optional: true, + Description: "Available only for certain products. This specifies whether you want to use an additional origin server address.", + Type: schema.TypeBool, + }, + "second_hostname": { + Optional: true, + Description: "Specifies the origin server's hostname, IPv4 address, or IPv6 address. Edge servers retrieve your content from this origin server.", + Type: schema.TypeString, + }, + "mslorigin": { + Optional: true, + Description: "This specifies the media's origin server.", + Type: schema.TypeString, + }, + "saas_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"HOSTNAME", "PATH", "QUERY_STRING", "COOKIE"}, false)), + Optional: true, + Description: "Specifies the part of the request that identifies this SaaS dynamic origin.", + Type: schema.TypeString, + }, + "saas_cname_enabled": { + Optional: true, + Description: "Enabling this allows you to use a `CNAME chain` to determine the hostname for this SaaS dynamic origin.", + Type: schema.TypeBool, + }, + "saas_cname_level": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "Specifies the desired number of hostnames to use in the `CNAME chain`, starting backwards from the edge server.", + Type: schema.TypeInt, + }, + "saas_cookie": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "Specifies the name of the cookie that identifies this SaaS dynamic origin.", + Type: schema.TypeString, + }, + "saas_query_string": { + ValidateDiagFunc: validateRegexOrVariable("^[^:/?#\\[\\]@&]+$"), + Optional: true, + Description: "Specifies the name of the query parameter that identifies this SaaS dynamic origin.", + Type: schema.TypeString, + }, + "saas_regex": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9\\:\\[\\]\\{\\}\\(\\)\\.\\?_\\-\\*\\+\\^\\$\\\\\\/\\|&=!]{1,250})$"), + Optional: true, + Description: "Specifies the Perl-compatible regular expression match that identifies this SaaS dynamic origin.", + Type: schema.TypeString, + }, + "saas_replace": { + ValidateDiagFunc: validateRegexOrVariable("^(([a-zA-Z0-9]|\\$[1-9])(([a-zA-Z0-9\\._\\-]|\\$[1-9]){0,250}([a-zA-Z0-9]|\\$[1-9]))?){1,10}$"), + Optional: true, + Description: "Specifies replacement text for what `saasRegex` matches.", + Type: schema.TypeString, + }, + "saas_suffix": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})\\.(com|net|org|info|biz|us|co\\.uk|ac\\.uk|org\\.uk|me\\.uk|ca|eu|com\\.au|co|co\\.za|ru|es|me|tv|pro|in|ie|de|it|nl|fr|co\\.il|ch|se|co\\.nz|pl|jp|name|mobi|cc|ws|be|com\\.mx|at|nu|asia|co\\.nz|net\\.nz|org\\.nz|com\\.au|net\\.au|org\\.au|tools)$"), + Optional: true, + Description: "Specifies the static part of the SaaS dynamic origin.", + Type: schema.TypeString, + }, + "forward_host_header": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"REQUEST_HOST_HEADER", "ORIGIN_HOSTNAME", "CUSTOM"}, false)), + Optional: true, + Description: "When the `originType` is set to either `CUSTOMER` or `SAAS_DYNAMIC_ORIGIN`, this specifies which `Host` header to pass to the origin.", + Type: schema.TypeString, + }, + "custom_forward_host_header": { + Optional: true, + Description: "This specifies the name of the custom host header the edge server should pass to the origin.", + Type: schema.TypeString, + }, + "cache_key_hostname": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"REQUEST_HOST_HEADER", "ORIGIN_HOSTNAME"}, false)), + Optional: true, + Description: "Specifies the hostname to use when forming a cache key.", + Type: schema.TypeString, + }, + "ip_version": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IPV4", "DUALSTACK", "IPV6"}, false)), + Optional: true, + Description: "Specifies which IP version to use when getting content from the origin.", + Type: schema.TypeString, + }, + "use_unique_cache_key": { + Optional: true, + Description: "With a shared `hostname` such as provided by Amazon AWS, sets a unique cache key for your content.", + Type: schema.TypeBool, + }, + "compress": { + Optional: true, + Description: "Enables `gzip` compression for non-NetStorage origins.", + Type: schema.TypeBool, + }, + "enable_true_client_ip": { + Optional: true, + Description: "When enabled on non-NetStorage origins, allows you to send a custom header (the `trueClientIpHeader`) identifying the IP address of the immediate client connecting to the edge server. This may provide more useful information than the standard `X-Forward-For` header, which proxies may modify.", + Type: schema.TypeBool, + }, + "true_client_ip_header": { + ValidateDiagFunc: validateRegexOrVariable("^[^()<>@,;:\\\"/\\[\\]?{}\\s]+$"), + Optional: true, + Description: "This specifies the name of the field that identifies the end client's IP address, for example `True-Client-IP`.", + Type: schema.TypeString, + }, + "true_client_ip_client_setting": { + Optional: true, + Description: "If a client sets the `True-Client-IP` header, the edge server allows it and passes the value to the origin. Otherwise the edge server removes it and sets the value itself.", + Type: schema.TypeBool, + }, + "origin_certificate": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "verification_mode": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"PLATFORM_SETTINGS", "CUSTOM", "THIRD_PARTY"}, false)), + Optional: true, + Description: "For non-NetStorage origins, maximize security by controlling which certificates edge servers should trust.", + Type: schema.TypeString, + }, + "origin_sni": { + Optional: true, + Description: "For non-NetStorage origins, enabling this adds a Server Name Indication (SNI) header in the SSL request sent to the origin, with the origin hostname as the value. See the `verification settings in the Origin Server behavior` or contact your Akamai representative for more information.", + Type: schema.TypeBool, + }, + "custom_valid_cn_values": { + Optional: true, + Description: "Specifies values to look for in the origin certificate's `Subject Alternate Name` or `Common Name` fields. Specify `{{Origin Hostname}}` and `{{Forward Host Header}}` within the text in the order you want them to be evaluated. (Note that these two template items are not the same as in-line `variables`, which use the same curly-brace syntax.)", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "origin_certs_to_honor": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"COMBO", "STANDARD_CERTIFICATE_AUTHORITIES", "CUSTOM_CERTIFICATE_AUTHORITIES", "CUSTOM_CERTIFICATES"}, false)), + Optional: true, + Description: "Specifies which certificate to trust.", + Type: schema.TypeString, + }, + "standard_certificate_authorities": { + Optional: true, + Description: "", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "custom_certificate_authorities": { + Optional: true, + Description: "Specifies an array of certification objects. See the `verification settings in the Origin Server behavior` or contact your Akamai representative for details on this object's requirements.", + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "subject_cn": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "subject_alternative_names": { + Optional: true, + Description: "", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "subject_rdns": { + Optional: true, + Description: "", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "c": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "ou": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "o": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "cn": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "issuer_rdns": { + Optional: true, + Description: "", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "c": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "ou": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "o": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "cn": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "not_before": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "not_after": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "sig_alg_name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "public_key": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "public_key_algorithm": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "public_key_format": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "serial_number": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "version": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "sha1_fingerprint": { + ValidateDiagFunc: validateRegexOrVariable("^[a-f0-9]{40}$"), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "pem_encoded_cert": { + ValidateDiagFunc: validateRegexOrVariable("^-----BEGIN CERTIFICATE-----(.|\\s)*-----END CERTIFICATE-----\\s*$"), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "can_be_leaf": { + Optional: true, + Description: "", + Type: schema.TypeBool, + }, + "can_be_ca": { + Optional: true, + Description: "", + Type: schema.TypeBool, + }, + "self_signed": { + Optional: true, + Description: "", + Type: schema.TypeBool, + }, + }, + }, + }, + "custom_certificates": { + Optional: true, + Description: "Specifies an array of certification objects. See the `verification settings in the Origin Server behavior` or contact your Akamai representative for details on this object's requirements.", + Type: schema.TypeList, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "subject_cn": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "subject_alternative_names": { + Optional: true, + Description: "", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "subject_rdns": { + Optional: true, + Description: "", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "c": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "ou": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "o": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "cn": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "issuer_rdns": { + Optional: true, + Description: "", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "c": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "ou": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "o": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "cn": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "not_before": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "not_after": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "sig_alg_name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "public_key": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "public_key_algorithm": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "public_key_format": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "serial_number": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "version": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "sha1_fingerprint": { + ValidateDiagFunc: validateRegexOrVariable("^[a-f0-9]{40}$"), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "pem_encoded_cert": { + ValidateDiagFunc: validateRegexOrVariable("^-----BEGIN CERTIFICATE-----(.|\\s)*-----END CERTIFICATE-----\\s*$"), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "can_be_leaf": { + Optional: true, + Description: "", + Type: schema.TypeBool, + }, + "can_be_ca": { + Optional: true, + Description: "", + Type: schema.TypeBool, + }, + "self_signed": { + Optional: true, + Description: "", + Type: schema.TypeBool, + }, + }, + }, + }, + "ports": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "http_port": { + Optional: true, + Description: "Specifies the port on your origin server to which edge servers should connect for HTTP requests, customarily `80`.", + Type: schema.TypeInt, + }, + "https_port": { + Optional: true, + Description: "Specifies the port on your origin server to which edge servers should connect for secure HTTPS requests, customarily `443`. This option only applies if the property is marked as secure. See `Secure property requirements` for guidance.", + Type: schema.TypeInt, + }, + "tls_version_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "tls13_support": { + Optional: true, + Description: "Enables transport layer security (TLS) version 1.3 for connections to your origin server.", + Type: schema.TypeBool, + }, + "min_tls_version": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"DYNAMIC", "TLSV1_1", "TLSV1_2", "TLSV1_3"}, false)), + Optional: true, + Description: "Specifies the minimum TLS version to use for connections to your origin server.", + Type: schema.TypeString, + }, + "max_tls_version": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"DYNAMIC", "TLSV1_1", "TLSV1_2", "TLSV1_3"}, false)), + Optional: true, + Description: "Specifies the maximum TLS version to use for connections to your origin server. As best practice, use `DYNAMIC` to automatically apply the latest supported version.", + Type: schema.TypeString, + }, + }, + }, + }, + "origin_characteristics": { + Optional: true, + Type: schema.TypeList, + Description: "Specifies characteristics of the origin. Akamai uses this information to optimize your metadata configuration, which may result in better origin offload and end-user performance. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "authentication_method_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "authentication_method": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"AUTOMATIC", "SIGNATURE_HEADER_AUTHENTICATION", "MSL_AUTHENTICATION", "AWS", "GCS_HMAC_AUTHENTICATION", "AWS_STS"}, false)), + Optional: true, + Description: "Specifies the authentication method.", + Type: schema.TypeString, + }, + "encoding_version": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntInSlice([]int{1, 2, 3, 4, 5})), + Optional: true, + Description: "Specifies the version of the encryption algorithm, an integer from `1` to `5`.", + Type: schema.TypeInt, + }, + "use_custom_sign_string": { + Optional: true, + Description: "Specifies whether to customize your signed string.", + Type: schema.TypeBool, + }, + "custom_sign_string": { + Optional: true, + Description: "Specifies the data to be encrypted as a series of enumerated variable names. See `Built-in system variables` for guidance on each.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "secret_key": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validateRegexOrVariable("^[0-9a-zA-Z]{24}$")), + Optional: true, + Description: "Specifies the shared secret key.", + Type: schema.TypeString, + }, + "nonce": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9a-zA-Z]{1,8}$"), + Optional: true, + Description: "Specifies the nonce.", + Type: schema.TypeString, + }, + "mslkey": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9a-zA-Z]{10,}$"), + Optional: true, + Description: "Specifies the access key provided by the hosting service.", + Type: schema.TypeString, + }, + "mslname": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9a-zA-Z]{1,8}$"), + Optional: true, + Description: "Specifies the origin name provided by the hosting service.", + Type: schema.TypeString, + }, + "access_key_encrypted_storage": { + Optional: true, + Description: "Enables secure use of access keys defined in Cloud Access Manager. Access keys store encrypted authentication details required to sign requests to cloud origins. If you disable this option, you'll need to store the authentication details unencrypted.", + Type: schema.TypeBool, + }, + "gcs_access_key_version_guid": { + Optional: true, + Description: "Identifies the unique `gcsAccessKeyVersionGuid` access key `created` in Cloud Access Manager to sign your requests to Google Cloud Storage in interoperability mode.", + Type: schema.TypeString, + }, + "gcs_hmac_key_access_id": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9]{1,128}$"), + Optional: true, + Description: "Specifies the active access ID linked to your Google account.", + Type: schema.TypeString, + }, + "gcs_hmac_key_secret": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9+/=_-]{1,40}$"), + Optional: true, + Description: "Specifies the secret linked to the access ID that you want to use to sign requests to Google Cloud Storage.", + Type: schema.TypeString, + }, + "aws_access_key_version_guid": { + Optional: true, + Description: "Identifies the unique `awsAccessKeyVersionGuid` access key `created` in Cloud Access Manager to sign your requests to AWS S3.", + Type: schema.TypeString, + }, + "aws_access_key_id": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9]{1,128}$"), + Optional: true, + Description: "Specifies active access key ID linked to your AWS account.", + Type: schema.TypeString, + }, + "aws_secret_access_key": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9+/=_-]{1,1024}$"), + Optional: true, + Description: "Specifies the secret linked to the access key identifier that you want to use to sign requests to AWS.", + Type: schema.TypeString, + }, + "aws_region": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9-]+$"), + Optional: true, + Description: "This specifies the AWS region code of the location where your bucket resides.", + Type: schema.TypeString, + }, + "aws_host": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validateRegexOrVariable("^(([a-zA-Z0-9]([a-zA-Z0-9_\\-]*[a-zA-Z0-9])?)\\.)+([a-zA-Z]+|xn--[a-zA-Z0-9]+)$")), + Optional: true, + Description: "This specifies the AWS hostname, without `http://` or `https://` prefixes. If you leave this option empty, it inherits the hostname from the `origin` behavior.", + Type: schema.TypeString, + }, + "aws_service": { + Optional: true, + Description: "This specifies the subdomain of your AWS service. It precedes `amazonaws.com` or the region code in the AWS hostname. For example, `s3.amazonaws.com`.", + Type: schema.TypeString, + }, + "property_id_tag": { + Optional: true, + Description: "", + Type: schema.TypeBool, + }, + "hostname_tag": { + Optional: true, + Description: "", + Type: schema.TypeBool, + }, + "role_arn": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9][a-zA-Z0-9_\\+=,.@\\-:/]{0,2047}$"), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "aws_ar_region": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9][a-zA-Z0-9\\-]{0,63}$"), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "end_point_service": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validateRegexOrVariable("^[a-zA-Z0-9][a-zA-Z0-9\\-]{0,63}$")), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "origin_location_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "country": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"EUROPE", "NORTH_AMERICA", "LATIN_AMERICA", "SOUTH_AMERICA", "NORDICS", "ASIA_PACIFIC", "OTHER_AMERICAS", "OTHER_APJ", "OTHER_EMEA", "AUSTRALIA", "GERMANY", "INDIA", "ITALY", "JAPAN", "MEXICO", "TAIWAN", "UNITED_KINGDOM", "US_EAST", "US_CENTRAL", "US_WEST", "GLOBAL_MULTI_GEO", "OTHER", "UNKNOWN", "ADC"}, false)), + Optional: true, + Description: "Specifies the origin's geographic region.", + Type: schema.TypeString, + }, + "adc_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "direct_connect_geo": { + Optional: true, + Description: "Provides a region used by Akamai Direct Connection.", + Type: schema.TypeString, + }, + }, + }, + }, + "origin_characteristics_wsd": { + Optional: true, + Type: schema.TypeList, + Description: "Specifies characteristics of the origin, for use in Akamai's Wholesale Delivery product. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "origintype": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"AZURE", "UNKNOWN"}, false)), + Optional: true, + Description: "Specifies an origin type.", + Type: schema.TypeString, + }, + }, + }, + }, + "origin_failure_recovery_method": { + Optional: true, + Type: schema.TypeList, + Description: "Origin Failover requires that you set up a separate rule containing origin failure recovery methods. You also need to set up the Origin Failure Recovery Policy behavior in a separate rule with a desired match criteria, and select the desired failover method. You can do this using Property Manager. Learn more about this process in `Adaptive Media Delivery Implementation Guide`. You can use the `originFailureRecoveryPolicy` member to edit existing instances of the Origin Failure Recover Policy behavior. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "recovery_method": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"RETRY_ALTERNATE_ORIGIN", "RESPOND_CUSTOM_STATUS"}, false)), + Optional: true, + Description: "Specifies the recovery method.", + Type: schema.TypeString, + }, + "custom_status_code": { + Optional: true, + Description: "Specifies the custom status code to be sent to the client.", + Type: schema.TypeString, + }, + }, + }, + }, + "origin_failure_recovery_policy": { + Optional: true, + Type: schema.TypeList, + Description: "Configures how to detect an origin failure, in which case the `originFailureRecoveryMethod` behavior applies. You can also define up to three sets of criteria to detect origin failure based on specific response codes. Use it to apply specific retry or recovery actions. You can do this using Property Manager. Learn more about this process in `Adaptive Media Delivery Implementation Guide`. You can use the `originFailureRecoveryMethod` member to edit existing instances of the Origin Failure Recover Method behavior. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Activates and configures a recovery policy.", + Type: schema.TypeBool, + }, + "tuning_parameters": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "enable_ip_avoidance": { + Optional: true, + Description: "Temporarily blocks an origin IP address that experienced a certain number of failures. When an IP address is blocked, the `configName` established for `originResponsivenessRecoveryConfigName` is applied.", + Type: schema.TypeBool, + }, + "ip_avoidance_error_threshold": { + Optional: true, + Description: "Defines the number of failures that need to occur to an origin address before it's blocked.", + Type: schema.TypeInt, + }, + "ip_avoidance_retry_interval": { + Optional: true, + Description: "Defines the number of seconds after which the IP address is removed from the blocklist.", + Type: schema.TypeInt, + }, + "binary_equivalent_content": { + Optional: true, + Description: "Synchronizes content between the primary and backup origins, byte for byte.", + Type: schema.TypeBool, + }, + "origin_responsiveness_monitoring": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "monitor_origin_responsiveness": { + Optional: true, + Description: "Enables continuous monitoring of connectivity to the origin. If necessary, applies retry or recovery actions.", + Type: schema.TypeBool, + }, + "origin_responsiveness_timeout": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"AGGRESSIVE", "MODERATE", "CONSERVATIVE", "USER_SPECIFIED"}, false)), + Optional: true, + Description: "The timeout threshold that triggers a retry or recovery action.", + Type: schema.TypeString, + }, + "origin_responsiveness_custom_timeout": { + Optional: true, + Description: "Specify a custom timeout, from 1 to 10 seconds.", + Type: schema.TypeInt, + }, + "origin_responsiveness_enable_retry": { + Optional: true, + Description: "If a specific failure condition applies, attempts a retry on the same origin before executing the recovery method.", + Type: schema.TypeBool, + }, + "origin_responsiveness_enable_recovery": { + Optional: true, + Description: "Enables a recovery action for a specific failure condition.", + Type: schema.TypeBool, + }, + "origin_responsiveness_recovery_config_name": { + Optional: true, + Description: "Specifies a recovery configuration using the `configName` you defined in the `recoveryConfig` match criteria. Specify 3 to 20 alphanumeric characters or dashes. Ensure that you use the `recoveryConfig` match criteria to apply this option.", + Type: schema.TypeString, + }, + "status_code_monitoring1": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "monitor_status_codes1": { + Optional: true, + Description: "Enables continuous monitoring for the specific origin status codes that trigger retry or recovery actions.", + Type: schema.TypeBool, + }, + "monitor_response_codes1": { + Optional: true, + Description: "Defines the origin response codes that trigger a subsequent retry or recovery action. Specify a single code entry (`501`) or a range (`501:504`). If you configure other `monitorStatusCodes*` and `monitorResponseCodes*` options, you can't use the same codes here.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "monitor_status_codes1_enable_retry": { + Optional: true, + Description: "When the defined response codes apply, attempts a retry on the same origin before executing the recovery method.", + Type: schema.TypeBool, + }, + "monitor_status_codes1_enable_recovery": { + Optional: true, + Description: "Enables the recovery action for the response codes you define.", + Type: schema.TypeBool, + }, + "monitor_status_codes1_recovery_config_name": { + Optional: true, + Description: "Specifies a recovery configuration using the `configName` you defined in the `recoveryConfig` match criteria. Specify 3 to 20 alphanumeric characters or dashes. Ensure that you use the `recoveryConfig` match criteria to apply this option.", + Type: schema.TypeString, + }, + "status_code_monitoring2": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "monitor_status_codes2": { + Optional: true, + Description: "Enables continuous monitoring for the specific origin status codes that trigger retry or recovery actions.", + Type: schema.TypeBool, + }, + "monitor_response_codes2": { + Optional: true, + Description: "Defines the origin response codes that trigger a subsequent retry or recovery action. Specify a single code entry (`501`) or a range (`501:504`). If you configure other `monitorStatusCodes*` and `monitorResponseCodes*` options, you can't use the same codes here.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "monitor_status_codes2_enable_retry": { + Optional: true, + Description: "When the defined response codes apply, attempts a retry on the same origin before executing the recovery method.", + Type: schema.TypeBool, + }, + "monitor_status_codes2_enable_recovery": { + Optional: true, + Description: "Enables the recovery action for the response codes you define.", + Type: schema.TypeBool, + }, + "monitor_status_codes2_recovery_config_name": { + Optional: true, + Description: "Specifies a recovery configuration using the `configName` you defined in the `recoveryConfig` match criteria. Specify 3 to 20 alphanumeric characters or dashes. Ensure that you use the `recoveryConfig` match criteria to apply this option.", + Type: schema.TypeString, + }, + "status_code_monitoring3": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "monitor_status_codes3": { + Optional: true, + Description: "Enables continuous monitoring for the specific origin status codes that trigger retry or recovery actions.", + Type: schema.TypeBool, + }, + "monitor_response_codes3": { + Optional: true, + Description: "Defines the origin response codes that trigger a subsequent retry or recovery action. Specify a single code entry (`501`) or a range (`501:504`). If you configure other `monitorStatusCodes*` and `monitorResponseCodes*` options, you can't use the same codes here..", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "monitor_status_codes3_enable_retry": { + Optional: true, + Description: "When the defined response codes apply, attempts a retry on the same origin before executing the recovery method.", + Type: schema.TypeBool, + }, + "monitor_status_codes3_enable_recovery": { + Optional: true, + Description: "Enables the recovery action for the response codes you define.", + Type: schema.TypeBool, + }, + "monitor_status_codes3_recovery_config_name": { + Optional: true, + Description: "Specifies a recovery configuration using the `configName` you defined in the `recoveryConfig` match criteria. Specify 3 to 20 alphanumeric characters or dashes. Ensure that you use the `recoveryConfig` match criteria to apply this option.", + Type: schema.TypeString, + }, + }, + }, + }, + "origin_ip_acl": { + Optional: true, + Type: schema.TypeList, + Description: "Origin IP Access Control List limits the traffic to your origin. It only allows requests from specific edge servers that are configured as part of a supernet defined by CIDR blocks. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enable": { + Optional: true, + Description: "Enables the Origin IP Access Control List behavior.", + Type: schema.TypeBool, + }, + }, + }, + }, + "permissions_policy": { + Optional: true, + Type: schema.TypeList, + Description: "Manages whether your page and its embedded iframes can access various browser features that affect end-user privacy, security, and performance. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "permissions_policy_directive": { + Optional: true, + Description: "Each directive represents a browser feature. Specify the ones you want enabled in a client browser that accesses your content. You can add custom entries or provide pre-set values from the list. For more details on each value, see the `guide section` for this behavior.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "allow_list": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*:%\\[\\]@.\\s]+$"), + Optional: true, + Description: "The features you've set in `permissionsPolicyDirective` are enabled for domains you specify here. They'll remain disabled for all other domains. Separate multiple domains with a single space. To block the specified directives from all domains, set this to `none`. This generates an empty value in the `Permissions-Policy` header.", + Type: schema.TypeString, + }, + }, + }, + }, + "persistent_client_connection": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior activates `persistent connections` between edge servers and clients, which allow for better performance and more efficient use of resources. Compare with the `persistentConnection` behavior, which configures persistent connections for the entire journey from origin to edge to client. Contact Akamai Professional Services for help configuring either. This behavior is for internal usage only. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the persistent connections behavior.", + Type: schema.TypeBool, + }, + "timeout": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Specifies the timeout period after which edge server closes the persistent connection with the client, 500 seconds by default.", + Type: schema.TypeString, + }, + }, + }, + }, + "persistent_connection": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior enables more efficient `persistent connections` from origin to edge server to client. Compare with the `persistentClientConnection` behavior, which customizes persistent connections from edge to client. Contact Akamai Professional Services for help configuring either. This behavior is for internal usage only. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables persistent connections.", + Type: schema.TypeBool, + }, + "timeout": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Specifies the timeout period after which edge server closes a persistent connection.", + Type: schema.TypeString, + }, + }, + }, + }, + "personally_identifiable_information": { + Optional: true, + Type: schema.TypeList, + Description: "Marks content covered by the current rule as sensitive `personally identifiable information` that needs to be treated as secure and private. That includes anything involving personal information: name, social security number, date and place of birth, mother's maiden name, biometric data, or any other data linked to an individual. If you attempt to save a property with such a rule that also caches or logs sensitive content, the added behavior results in a validation error. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "When enabled, marks content as personally identifiable information (PII).", + Type: schema.TypeBool, + }, + }, + }, + }, + "phased_release": { + Optional: true, + Type: schema.TypeList, + Description: "The Phased Release Cloudlet provides gradual and granular traffic management to an alternate origin in near real time. Use the `Cloudlets API` or the Cloudlets Policy Manager application within `Control Center` to set up your Cloudlets policies. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the Phased Release Cloudlet.", + Type: schema.TypeBool, + }, + "is_shared_policy": { + Optional: true, + Description: "Whether you want to apply the Cloudlet shared policy to an unlimited number of properties within your account. Learn more about shared policies and how to create them in `Cloudlets Policy Manager`.", + Type: schema.TypeBool, + }, + "cloudlet_policy": { + Optional: true, + Description: "Specifies the Cloudlet policy as an object.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "cloudlet_shared_policy": { + Optional: true, + Description: "Identifies the Cloudlet shared policy to use with this behavior. Use the `Cloudlets API` to list available shared policies.", + Type: schema.TypeInt, + }, + "label": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "A label to distinguish this Phased Release policy from any others within the same property.", + Type: schema.TypeString, + }, + "population_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "population_cookie_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"NONE", "NEVER", "ON_BROWSER_CLOSE", "FIXED_DATE", "DURATION"}, false)), + Optional: true, + Description: "Select when to assign a cookie to the population of users the Cloudlet defines. If you select the Cloudlet's `random` membership option, it overrides this option's value so that it is effectively `NONE`.", + Type: schema.TypeString, + }, + "population_expiration_date": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "Specifies the date and time when membership expires, and the browser no longer sends the cookie. Subsequent requests re-evaluate based on current membership settings.", + Type: schema.TypeString, + }, + "population_duration": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Sets the lifetime of the cookie from the initial request. Subsequent requests re-evaluate based on current membership settings.", + Type: schema.TypeString, + }, + "population_refresh": { + Optional: true, + Description: "Enabling this option resets the original duration of the cookie if the browser refreshes before the cookie expires.", + Type: schema.TypeBool, + }, + "failover_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "failover_enabled": { + Optional: true, + Description: "Allows failure responses at the origin defined by the Cloudlet to fail over to the prevailing origin defined by the property.", + Type: schema.TypeBool, + }, + "failover_response_code": { + Optional: true, + Description: "Defines the set of failure codes that initiate the failover response.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "failover_duration": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 300)), + Optional: true, + Description: "Specifies the number of seconds to wait until the client tries to access the failover origin after the initial failure is detected. Set the value to `0` to immediately request the alternate origin upon failure.", + Type: schema.TypeInt, + }, + }, + }, + }, + "preconnect": { + Optional: true, + Type: schema.TypeList, + Description: "With the `http2` behavior enabled, this requests a specified set of domains that relate to your property hostname, and keeps the connection open for faster loading of content from those domains. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "preconnectlist": { + Optional: true, + Description: "Specifies the set of hostnames to which to preconnect over HTTP2.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "predictive_content_delivery": { + Optional: true, + Type: schema.TypeList, + Description: "Improves user experience and reduces the cost of downloads by enabling mobile devices to predictively fetch and cache content from catalogs managed by Akamai servers. You can't use this feature if in the `segmentedMediaOptimization` behavior, the value for `behavior` is set to `LIVE`. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the predictive content delivery behavior.", + Type: schema.TypeBool, + }, + }, + }, + }, + "predictive_prefetching": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior potentially reduces the client's page load time by pre-caching objects based on historical data for the page, not just its current set of referenced objects. It also detects second-level dependencies, such as objects retrieved by JavaScript. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the predictive prefetching behavior.", + Type: schema.TypeBool, + }, + "accuracy_target": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LOW", "MEDIUM", "HIGH"}, false)), + Optional: true, + Description: "The level of prefetching. A higher level results in better client performance, but potentially greater load on the origin.", + Type: schema.TypeString, + }, + }, + }, + }, + "prefetch": { + Optional: true, + Type: schema.TypeList, + Description: "Instructs edge servers to retrieve content linked from requested pages as they load, rather than waiting for separate requests for the linked content. This behavior applies depending on the rule's set of matching conditions. Use in conjunction with the `prefetchable` behavior, which specifies the set of objects to prefetch. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Applies prefetching behavior when enabled.", + Type: schema.TypeBool, + }, + }, + }, + }, + "prefetchable": { + Optional: true, + Type: schema.TypeList, + Description: "Allow matching objects to prefetch into the edge cache as the parent page that links to them loads, rather than waiting for a direct request. This behavior applies depending on the rule's set of matching conditions. Use `prefetch` to enable the overall behavior for parent pages that contain links to the object. To apply this behavior, you need to match on a `filename` or `fileExtension`. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Allows matching content to prefetch when referenced on a requested parent page.", + Type: schema.TypeBool, + }, + }, + }, + }, + "prefresh_cache": { + Optional: true, + Type: schema.TypeList, + Description: "Refresh cached content before its time-to-live (TTL) expires, to keep end users from having to wait for the origin to provide fresh content. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the cache prefreshing behavior.", + Type: schema.TypeBool, + }, + "prefreshval": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 99)), + Optional: true, + Description: "Specifies when the prefresh occurs as a percentage of the TTL. For example, for an object whose cache has 10 minutes left to live, and an origin response that is routinely less than 30 seconds, a percentage of `95` prefreshes the content without unnecessarily increasing load on the origin.", + Type: schema.TypeInt, + }, + }, + }, + }, + "quality": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "origin_settings": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "country": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"EUROPE", "NORTH_AMERICA", "LATIN_AMERICA", "SOUTH_AMERICA", "NORDICS", "ASIA_PACIFIC", "OTHER_AMERICAS", "OTHER_APJ", "OTHER_EMEA", "AUSTRALIA", "GERMANY", "INDIA", "ITALY", "JAPAN", "MEXICO", "TAIWAN", "UNITED_KINGDOM", "US_EAST", "US_CENTRAL", "US_WEST"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "audience_settings": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "end_user_location": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"GLOBAL", "GLOBAL_US_CENTRIC", "GLOBAL_EU_CENTRIC", "GLOBAL_ASIA_CENTRIC", "EUROPE", "NORTH_AMERICA", "SOUTH_AMERICA", "NORDICS", "ASIA_PACIFIC", "AUSTRALIA", "GERMANY", "INDIA", "ITALY", "JAPAN", "TAIWAN", "UNITED_KINGDOM"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "maximum_concurrent_users": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"NONE", "LESS_THAN_10K", "10K_TO_50K", "50K_TO_100K", "GREATER_THAN_100K"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "content_settings": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "content_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"NONE", "SITE", "IMAGES", "CONFIG", "OTHERS", "AUDIO", "SD_VIDEO", "HD_VIDEO", "SUPER_HD_VIDEO", "LARGE_OBJECTS"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "object_size": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"LESS_THAN_1MB", "1_TO_10MB", "10_TO_100MB", "GREATER_THAN_100MB"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "download_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"FOREGROUND", "BACKGROUND"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "popularity_distribution": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"TYPICAL", "LONG_TAIL", "ALL_POPULAR", "ALL_UNPOPULAR"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "delivery_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ON_DEMAND", "LIVE"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "delivery_format": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"DASH", "HDS", "HLS", "SILVER_LIGHT", "OTHER"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "segment_duration": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntInSlice([]int{2, 4, 6, 8, 10})), + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "catalog_size": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SMALL", "MEDIUM", "LARGE", "EXTRA_LARGE"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "refresh_rate": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"NONE", "HOURLY", "DAILY", "MONTHLY", "YEARLY"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "optimize_for": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"NONE", "ORIGIN", "STARTUP"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "quic_beta": { + Optional: true, + Type: schema.TypeList, + Description: "For a share of responses, includes an `Alt-Svc` header for compatible clients to initiate subsequent sessions using the QUIC protocol. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables QUIC support.", + Type: schema.TypeBool, + }, + "quic_offer_percentage": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(1, 50)), + Optional: true, + Description: "The percentage of responses for which to allow QUIC sessions.", + Type: schema.TypeInt, + }, + }, + }, + }, + "random_seek": { + Optional: true, + Type: schema.TypeList, + Description: "Optimizes `.flv` and `.mp4` files to allow random jump-point navigation. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "flv": { + Optional: true, + Description: "Enables random seek optimization in FLV files.", + Type: schema.TypeBool, + }, + "mp4": { + Optional: true, + Description: "Enables random seek optimization in MP4 files.", + Type: schema.TypeBool, + }, + "maximum_size": { + ValidateDiagFunc: validateRegexOrVariable("^\\d+[K,M,G,T]B$"), + Optional: true, + Description: "Sets the maximum size of the MP4 file to optimize, expressed as a number suffixed with a unit string such as `MB` or `GB`.", + Type: schema.TypeString, + }, + }, + }, + }, + "rapid": { + Optional: true, + Type: schema.TypeList, + Description: "The `Akamai API Gateway` allows you to configure API traffic delivered over the Akamai network. Apply this behavior to a set of API assets, then use Akamai's `API Endpoints API` to configure how the traffic responds. Use the `API Keys and Traffic Management API` to control access to your APIs. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables API Gateway for the current set of content.", + Type: schema.TypeBool, + }, + }, + }, + }, + "read_timeout": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior specifies how long the edge server should wait for a response from the requesting forward server after a connection has already been established. Any failure to read aborts the request and sends a `504` Gateway Timeout error to the client. Contact Akamai Professional Services for help configuring this behavior. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "value": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Specifies the read timeout necessary before failing with a `504` error. This value should never be zero.", + Type: schema.TypeString, + }, + }, + }, + }, + "real_time_reporting": { + Optional: true, + Type: schema.TypeList, + Description: "This enables `Real-Time Reporting` for Akamai Cloud Embed customers. The behavior can only be configured on your behalf by Akamai Professional Services. This behavior is for internal usage only. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables reports on delivery of cloud hosted content at near real-time latencies.", + Type: schema.TypeBool, + }, + "advanced": { + Optional: true, + Description: "Enables advanced options.", + Type: schema.TypeBool, + }, + "beacon_sampling_percentage": { + Optional: true, + Description: "Specifies the percentage for sampling.", + Type: schema.TypeFloat, + }, + }, + }, + }, + "real_user_monitoring": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior is deprecated, but you should not disable or remove it if present. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "When enabled, activates real-use monitoring.", + Type: schema.TypeBool, + }, + }, + }, + }, + "redirect": { + Optional: true, + Type: schema.TypeList, + Description: "Respond to the client request with a redirect without contacting the origin. Specify the redirect as a path expression starting with a `/` character relative to the current root, or as a fully qualified URL. This behavior relies primarily on `destinationHostname` and `destinationPath` to manipulate the hostname and path independently. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "mobile_default_choice": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"DEFAULT", "MOBILE"}, false)), + Optional: true, + Description: "Either specify a default response for mobile browsers, or customize your own.", + Type: schema.TypeString, + }, + "destination_protocol": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SAME_AS_REQUEST", "HTTP", "HTTPS"}, false)), + Optional: true, + Description: "Choose the protocol for the redirect URL.", + Type: schema.TypeString, + }, + "destination_hostname": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SAME_AS_REQUEST", "SUBDOMAIN", "SIBLING", "OTHER"}, false)), + Optional: true, + Description: "Specify how to change the requested hostname, independently from the pathname.", + Type: schema.TypeString, + }, + "destination_hostname_subdomain": { + Optional: true, + Description: "Specifies a subdomain to prepend to the current hostname. For example, a value of `m` changes `www.example.com` to `m.www.example.com`.", + Type: schema.TypeString, + }, + "destination_hostname_sibling": { + Optional: true, + Description: "Specifies the subdomain with which to replace to the current hostname's leftmost subdomain. For example, a value of `m` changes `www.example.com` to `m.example.com`.", + Type: schema.TypeString, + }, + "destination_hostname_other": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), + Optional: true, + Description: "Specifies the full hostname with which to replace the current hostname.", + Type: schema.TypeString, + }, + "destination_path": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SAME_AS_REQUEST", "PREFIX_REQUEST", "OTHER"}, false)), + Optional: true, + Description: "Specify how to change the requested pathname, independently from the hostname.", + Type: schema.TypeString, + }, + "destination_path_prefix": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "When `destinationPath` is set to `PREFIX_REQUEST`, this prepends the current path. For example, a value of `/prefix/path` changes `/example/index.html` to `/prefix/path/example/index.html`.", + Type: schema.TypeString, + }, + "destination_path_suffix_status": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"NO_SUFFIX", "SUFFIX"}, false)), + Optional: true, + Description: "When `destinationPath` is set to `PREFIX_REQUEST`, this gives you the option of adding a suffix.", + Type: schema.TypeString, + }, + "destination_path_suffix": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9\\[\\]/?#!=&_\\-\\.]+$"), + Optional: true, + Description: "When `destinationPath` is set to `PREFIX_REQUEST` and `destinationPathSuffixStatus` is set to `SUFFIX`, this specifies the suffix to append to the path.", + Type: schema.TypeString, + }, + "destination_path_other": { + ValidateDiagFunc: validateRegexOrVariable("^/"), + Optional: true, + Description: "When `destinationPath` is set to `PREFIX_REQUEST`, this replaces the current path.", + Type: schema.TypeString, + }, + "query_string": { + Optional: true, + Description: "When set to `APPEND`, passes incoming query string parameters as part of the redirect URL. Otherwise set this to `IGNORE`.", + Type: schema.TypeString, + }, + "response_code": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntInSlice([]int{301, 302, 303, 307})), + Optional: true, + Description: "Specify the redirect's response code.", + Type: schema.TypeInt, + }, + }, + }, + }, + "redirectplus": { + Optional: true, + Type: schema.TypeList, + Description: "Respond to the client request with a redirect without contacting the origin. This behavior fills the same need as `redirect`, but allows you to use `variables` to express the redirect `destination`'s component values more concisely. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the redirect feature.", + Type: schema.TypeBool, + }, + "destination": { + Optional: true, + Description: "Specifies the redirect as a path expression starting with a `/` character relative to the current root, or as a fully qualified URL. Optionally inject variables, as in this example that refers to the original request's filename: `/path/to/{{builtin.AK_FILENAME}}`.", + Type: schema.TypeString, + }, + "response_code": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntInSlice([]int{301, 302, 303, 307})), + Optional: true, + Description: "Assigns the status code for the redirect response.", + Type: schema.TypeInt, + }, + }, + }, + }, + "referer_checking": { + Optional: true, + Type: schema.TypeList, + Description: "Limits allowed requests to a set of domains you specify. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the referer-checking behavior.", + Type: schema.TypeBool, + }, + "strict": { + Optional: true, + Description: "When enabled, excludes requests whose `Referer` header include a relative path, or that are missing a `Referer`. When disabled, only excludes requests whose `Referer` hostname is not part of the `domains` set.", + Type: schema.TypeBool, + }, + "domains": { + Optional: true, + Description: "Specifies the set of allowed domains. With `allowChildren` disabled, prefixing values with `*.` specifies domains for which subdomains are allowed.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "allow_children": { + Optional: true, + Description: "Allows all subdomains for the `domains` set, just like adding a `*.` prefix to each.", + Type: schema.TypeBool, + }, + }, + }, + }, + "remove_query_parameter": { + Optional: true, + Type: schema.TypeList, + Description: "Remove named query parameters before forwarding the request to the origin. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "parameters": { + Optional: true, + Description: "Specifies parameters to remove from the request.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "remove_vary": { + Optional: true, + Type: schema.TypeList, + Description: "By default, responses that feature a `Vary` header value of anything other than `Accept-Encoding` and a corresponding `Content-Encoding: gzip` header aren't cached on edge servers. `Vary` headers indicate when a URL's content varies depending on some variable, such as which `User-Agent` requests it. This behavior simply removes the `Vary` header to make responses cacheable. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "When enabled, removes the `Vary` header to ensure objects can be cached.", + Type: schema.TypeBool, + }, + }, + }, + }, + "report": { + Optional: true, + Type: schema.TypeList, + Description: "Specify the HTTP request headers or cookie names to log in your Log Delivery Service reports. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "log_host": { + Optional: true, + Description: "Log the `Host` header.", + Type: schema.TypeBool, + }, + "log_referer": { + Optional: true, + Description: "Log the `Referer` header.", + Type: schema.TypeBool, + }, + "log_user_agent": { + Optional: true, + Description: "Log the `User-Agent` header.", + Type: schema.TypeBool, + }, + "log_accept_language": { + Optional: true, + Description: "Log the `Accept-Language` header.", + Type: schema.TypeBool, + }, + "log_cookies": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"OFF", "ALL", "SOME"}, false)), + Optional: true, + Description: "Specifies the set of cookies to log.", + Type: schema.TypeString, + }, + "cookies": { + Optional: true, + Description: "This specifies the set of cookies names whose values you want to log.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "log_custom_log_field": { + Optional: true, + Description: "Whether to append additional custom data to each log line.", + Type: schema.TypeBool, + }, + "custom_log_field": { + Optional: true, + Description: "Specifies an additional data field to append to each log line, maximum 1000 bytes, typically based on a dynamically generated built-in system variable. For example, `round-trip: {{builtin.AK_CLIENT_TURNAROUND_TIME}}ms` logs the total time to complete the response. See `Support for variables` for more information. If you enable the `logCustom` behavior, it overrides the `customLogField` option.", + Type: schema.TypeString, + }, + "log_edge_ip": { + Optional: true, + Description: "Whether to log the IP address of the Akamai edge server that served the response to the client.", + Type: schema.TypeBool, + }, + "log_x_forwarded_for": { + Optional: true, + Description: "Log any `X-Forwarded-For` request header.", + Type: schema.TypeBool, + }, + }, + }, + }, + "request_client_hints": { + Optional: true, + Type: schema.TypeList, + Description: "Client hints are HTTP request header fields that determine which resources the browser should include in the response. This behavior configures and prioritizes the client hints you want to send to request specific client and device information. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "accept_ch": { + Optional: true, + Description: "The client hint data objects you want to receive from the browser. You can add custom entries or provide pre-set values from the list. For more details on each value, see the `guide section` for this behavior. If you've configured your origin server to pass along data objects, they merge with the ones you set in this array, before the list is sent to the client.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "accept_critical_ch": { + Optional: true, + Description: "The critical client hint data objects you want to receive from the browser. The original request from the browser needs to include these objects. Otherwise, a new response header is sent back to the client, asking for all of these client hint data objects. You can add custom entries or provide pre-set values from the list. For more details on each value, see the `guide section` for this behavior.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "reset": { + Optional: true, + Description: "This sends an empty instance of the `Accept-CH` response header to clear other `Accept-CH` values currently stored in the client browser. This empty header doesn't get merged with other objects sent from your origin server.", + Type: schema.TypeBool, + }, + }, + }, + }, + "request_control": { + Optional: true, + Type: schema.TypeList, + Description: "The Request Control Cloudlet allows you to control access to your web content based on the incoming request's IP or geographic location. With Cloudlets available on your contract, choose `Your services` > `Edge logic Cloudlets` to control how the feature works within `Control Center`, or use the `Cloudlets API` to configure it programmatically. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the Request Control Cloudlet.", + Type: schema.TypeBool, + }, + "is_shared_policy": { + Optional: true, + Description: "Whether you want to apply the Cloudlet shared policy to an unlimited number of properties within your account. Learn more about shared policies and how to create them in `Cloudlets Policy Manager`.", + Type: schema.TypeBool, + }, + "cloudlet_policy": { + Optional: true, + Description: "Identifies the Cloudlet policy.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "cloudlet_shared_policy": { + Optional: true, + Description: "Identifies the Cloudlet shared policy to use with this behavior. Use the `Cloudlets API` to list available shared policies.", + Type: schema.TypeInt, + }, + "enable_branded403": { + Optional: true, + Description: "If enabled, serves a branded 403 page for this Cloudlet instance.", + Type: schema.TypeBool, + }, + "branded403_status_code": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntInSlice([]int{200, 302, 403, 503})), + Optional: true, + Description: "Specifies the response status code for the branded deny action.", + Type: schema.TypeInt, + }, + "net_storage": { + Optional: true, + Description: "Specifies the NetStorage domain that contains the branded 403 page.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cp_code": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "download_domain_name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "g2o_token": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "branded403_file": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "Specifies the full path of the branded 403 page, including the filename, but excluding the NetStorage CP code path component.", + Type: schema.TypeString, + }, + "branded403_url": { + ValidateDiagFunc: validateRegexOrVariable("^[^\\s]+$"), + Optional: true, + Description: "Specifies the redirect URL for the branded deny action.", + Type: schema.TypeString, + }, + "branded_deny_cache_ttl": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(5, 30)), + Optional: true, + Description: "Specifies the branded response page's time to live in the cache, `5` minutes by default.", + Type: schema.TypeInt, + }, + }, + }, + }, + "request_type_marker": { + Optional: true, + Type: schema.TypeList, + Description: "The `Internet of Things: OTA Updates` product allows customers to securely distribute firmware to devices over cellular networks. When using the `downloadCompleteMarker` behavior to log successful downloads, this related behavior identifies download or campaign server types in aggregated and individual reports. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "request_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"DOWNLOAD", "CAMPAIGN_SERVER"}, false)), + Optional: true, + Description: "Specifies the type of request.", + Type: schema.TypeString, + }, + }, + }, + }, + "resource_optimizer": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior is deprecated, but you should not disable or remove it if present. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the Resource Optimizer feature.", + Type: schema.TypeBool, + }, + }, + }, + }, + "resource_optimizer_extended_compatibility": { + Optional: true, + Type: schema.TypeList, + Description: "This enhances the standard version of the `resourceOptimizer` behavior to support the compression of additional file formats and address some compatibility issues. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the Resource Optimizer feature.", + Type: schema.TypeBool, + }, + "enable_all_features": { + Optional: true, + Description: "Enables `additional support` and error handling.", + Type: schema.TypeBool, + }, + }, + }, + }, + "response_code": { + Optional: true, + Type: schema.TypeList, + Description: "Change the existing response code. For example, if your origin sends a `301` permanent redirect, this behavior can change it on the edge to a temporary `302` redirect. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "status_code": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntInSlice([]int{200, 301, 302, 303, 404, 500, 100, 101, 102, 103, 122, 201, 202, 203, 204, 205, 206, 207, 226, 300, 304, 305, 306, 307, 308, 400, 401, 402, 403, 405, 406, 407, 408, 409, 410, 411, 412, 413, 414, 415, 416, 417, 422, 423, 424, 425, 426, 428, 429, 431, 444, 449, 450, 499, 501, 502, 503, 504, 505, 506, 507, 509, 510, 511, 598, 599})), + Optional: true, + Description: "The HTTP status code to replace the existing one.", + Type: schema.TypeInt, + }, + "override206": { + Optional: true, + Description: "Allows any specified `200` success code to override a `206` partial-content code, in which case the response's content length matches the requested range length.", + Type: schema.TypeBool, + }, + }, + }, + }, + "response_cookie": { + Optional: true, + Type: schema.TypeList, + Description: "Set a cookie to send downstream to the client with either a fixed value or a unique stamp. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "cookie_name": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "Specifies the name of the cookie, which serves as a key to determine if the cookie is set.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Allows you to set a response cookie.", + Type: schema.TypeBool, + }, + "type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"FIXED", "UNIQUE"}, false)), + Optional: true, + Description: "What type of value to assign.", + Type: schema.TypeString, + }, + "value": { + ValidateDiagFunc: validateRegexOrVariable("^[^\\s;]+$"), + Optional: true, + Description: "If the cookie `type` is `FIXED`, this specifies the cookie value.", + Type: schema.TypeString, + }, + "format": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"AKAMAI", "APACHE"}, false)), + Optional: true, + Description: "When the `type` of cookie is set to `UNIQUE`, this sets the date format.", + Type: schema.TypeString, + }, + "default_domain": { + Optional: true, + Description: "When enabled, uses the default domain value, otherwise the set specified in the `domain` field.", + Type: schema.TypeBool, + }, + "default_path": { + Optional: true, + Description: "When enabled, uses the default path value, otherwise the set specified in the `path` field.", + Type: schema.TypeBool, + }, + "domain": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), + Optional: true, + Description: "If the `defaultDomain` is disabled, this sets the domain for which the cookie is valid. For example, `example.com` makes the cookie valid for that hostname and all subdomains.", + Type: schema.TypeString, + }, + "path": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "If the `defaultPath` is disabled, sets the path component for which the cookie is valid.", + Type: schema.TypeString, + }, + "expires": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ON_BROWSER_CLOSE", "FIXED_DATE", "DURATION", "NEVER"}, false)), + Optional: true, + Description: "Sets various ways to specify when the cookie expires.", + Type: schema.TypeString, + }, + "expiration_date": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "If `expires` is set to `FIXED_DATE`, this sets when the cookie expires as a UTC date and time.", + Type: schema.TypeString, + }, + "duration": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "If `expires` is set to `DURATION`, this sets the cookie's lifetime.", + Type: schema.TypeString, + }, + "same_site": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"DEFAULT", "NONE", "LAX", "STRICT"}, false)), + Optional: true, + Description: "This option controls the `SameSite` cookie attribute that reduces the risk of cross-site request forgery attacks.", + Type: schema.TypeString, + }, + "secure": { + Optional: true, + Description: "When enabled, sets the cookie's `Secure` flag to transmit it with `HTTPS`.", + Type: schema.TypeBool, + }, + "http_only": { + Optional: true, + Description: "When enabled, includes the `HttpOnly` attribute in the `Set-Cookie` response header to mitigate the risk of client-side scripts accessing the protected cookie, if the browser supports it.", + Type: schema.TypeBool, + }, + }, + }, + }, + "restrict_object_caching": { + Optional: true, + Type: schema.TypeList, + Description: "You need this behavior to deploy the Object Caching product. It disables serving HTML content and limits the maximum object size to 100MB. Contact Akamai Professional Services for help configuring it. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "maximum_size": { + Optional: true, + Description: "Specifies a fixed maximum size of non-HTML content to cache.", + Type: schema.TypeString, + }, + }, + }, + }, + "return_cache_status": { + Optional: true, + Type: schema.TypeList, + Description: "Generates a response header with information about cache status. Among other things, this can tell you whether the response came from the Akamai cache, or from the origin. Status values report with either of these forms of syntax, depending for example on whether you're deploying traffic using `sureRoute` or `tieredDistribution`: This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "response_header_name": { + ValidateDiagFunc: validateRegexOrVariable("^[^()<>@,;:\\\"/\\[\\]?{}\\s]+$"), + Optional: true, + Description: "Specifies the name of the HTTP header in which to report the cache status value.", + Type: schema.TypeString, + }, + }, + }, + }, + "rewrite_url": { + Optional: true, + Type: schema.TypeList, + Description: "Modifies the path of incoming requests to forward to the origin. This helps you offload URL-rewriting tasks to the edge to increase the origin server's performance, allows you to redirect links to different targets without changing markup, and hides your original directory structure. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "behavior": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"REPLACE", "REMOVE", "REWRITE", "PREPEND", "REGEX_REPLACE"}, false)), + Optional: true, + Description: "The action to perform on the path.", + Type: schema.TypeString, + }, + "match": { + ValidateDiagFunc: validateRegexOrVariable("^/([^:#\\[\\]@/?]+/)*$"), + Optional: true, + Description: "When `behavior` is `REMOVE` or `REPLACE`, specifies the part of the incoming path you'd like to remove or modify.", + Type: schema.TypeString, + }, + "match_regex": { + Optional: true, + Description: "When `behavior` is set to `REGEX_REPLACE`, specifies the Perl-compatible regular expression to replace with `targetRegex`.", + Type: schema.TypeString, + }, + "target_regex": { + Optional: true, + Description: "When `behavior` is set to `REGEX_REPLACE`, this replaces whatever the `matchRegex` field matches, along with any captured sequences from `\\$1` through `\\$9`.", + Type: schema.TypeString, + }, + "target_path": { + ValidateDiagFunc: validateRegexOrVariable("^/([^:#\\[\\]@/?]+/)*$"), + Optional: true, + Description: "When `behavior` is set to `REPLACE`, this path replaces whatever the `match` field matches in the incoming request's path.", + Type: schema.TypeString, + }, + "target_path_prepend": { + ValidateDiagFunc: validateRegexOrVariable("^/([^:#\\[\\]@/?]+/)*$"), + Optional: true, + Description: "When `behavior` is set to `PREPEND`, specifies a path to prepend to the incoming request's URL.", + Type: schema.TypeString, + }, + "target_url": { + ValidateDiagFunc: validateRegexOrVariable("(/\\S*)?$"), + Optional: true, + Description: "When `behavior` is set to `REWRITE`, specifies the full path to request from the origin.", + Type: schema.TypeString, + }, + "match_multiple": { + Optional: true, + Description: "When enabled, replaces all potential matches rather than only the first.", + Type: schema.TypeBool, + }, + "keep_query_string": { + Optional: true, + Description: "When enabled, retains the original path's query parameters.", + Type: schema.TypeBool, + }, + }, + }, + }, + "rum_custom": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior is deprecated, but you should not disable or remove it if present. This behavior is for internal usage only. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "rum_sample_rate": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 100)), + Optional: true, + Description: "Specifies the percentage of web traffic to include in your RUM report.", + Type: schema.TypeInt, + }, + "rum_group_name": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validateRegexOrVariable("^[0-9a-zA-Z]*$")), + Optional: true, + Description: "A deprecated option to specify an alternate name under which to batch this set of web traffic in your report. Do not use it.", + Type: schema.TypeString, + }, + }, + }, + }, + "saas_definitions": { + Optional: true, + Type: schema.TypeList, + Description: "Configures how the Software as a Service feature identifies `customers`, `applications`, and `users`. A different set of options is available for each type of targeted request, each enabled with the `action`-suffixed option. In each case, you can use `PATH`, `COOKIE`, `QUERY_STRING`, or `HOSTNAME` components as identifiers, or `disable` the SaaS behavior for certain targets. If you rely on a `HOSTNAME`, you also have the option of specifying a `CNAME chain` rather than an individual hostname. The various options suffixed `regex` and `replace` subsequently remove the identifier from the request. This behavior requires a sibling `origin` behavior whose `originType` option is set to `SAAS_DYNAMIC_ORIGIN`. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "customer_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "customer_action": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"DISABLED", "HOSTNAME", "PATH", "QUERY_STRING", "COOKIE"}, false)), + Optional: true, + Description: "Specifies the request component that identifies a SaaS customer.", + Type: schema.TypeString, + }, + "customer_cname_enabled": { + Optional: true, + Description: "Enabling this allows you to identify customers using a `CNAME chain` rather than a single hostname.", + Type: schema.TypeBool, + }, + "customer_cname_level": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "Specifies the number of CNAMEs to use in the chain.", + Type: schema.TypeInt, + }, + "customer_cookie": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "This specifies the name of the cookie that identifies the customer.", + Type: schema.TypeString, + }, + "customer_query_string": { + ValidateDiagFunc: validateRegexOrVariable("^[^:/?#\\[\\]@&]+$"), + Optional: true, + Description: "This names the query parameter that identifies the customer.", + Type: schema.TypeString, + }, + "customer_regex": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9\\:\\[\\]\\{\\}\\(\\)\\.\\?_\\-\\*\\+\\^\\$\\\\\\/\\|&=!]{1,250})$"), + Optional: true, + Description: "Specifies a Perl-compatible regular expression with which to substitute the request's customer ID.", + Type: schema.TypeString, + }, + "customer_replace": { + ValidateDiagFunc: validateRegexOrVariable("^(([a-zA-Z0-9_\\-]|\\$[1-9]){1,250})$"), + Optional: true, + Description: "Specifies a string to replace the request's customer ID matched by `customerRegex`.", + Type: schema.TypeString, + }, + "application_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "application_action": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"DISABLED", "HOSTNAME", "PATH", "QUERY_STRING", "COOKIE"}, false)), + Optional: true, + Description: "Specifies the request component that identifies a SaaS application.", + Type: schema.TypeString, + }, + "application_cname_enabled": { + Optional: true, + Description: "Enabling this allows you to identify applications using a `CNAME chain` rather than a single hostname.", + Type: schema.TypeBool, + }, + "application_cname_level": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "Specifies the number of CNAMEs to use in the chain.", + Type: schema.TypeInt, + }, + "application_cookie": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "This specifies the name of the cookie that identifies the application.", + Type: schema.TypeString, + }, + "application_query_string": { + ValidateDiagFunc: validateRegexOrVariable("^[^:/?#\\[\\]@&]+$"), + Optional: true, + Description: "This names the query parameter that identifies the application.", + Type: schema.TypeString, + }, + "application_regex": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9\\:\\[\\]\\{\\}\\(\\)\\.\\?_\\-\\*\\+\\^\\$\\\\\\/\\|&=!]{1,250})$"), + Optional: true, + Description: "Specifies a Perl-compatible regular expression with which to substitute the request's application ID.", + Type: schema.TypeString, + }, + "application_replace": { + ValidateDiagFunc: validateRegexOrVariable("^(([a-zA-Z0-9_\\-]|\\$[1-9]){1,250})$"), + Optional: true, + Description: "Specifies a string to replace the request's application ID matched by `applicationRegex`.", + Type: schema.TypeString, + }, + "users_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "users_action": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"DISABLED", "HOSTNAME", "PATH", "QUERY_STRING", "COOKIE"}, false)), + Optional: true, + Description: "Specifies the request component that identifies a SaaS user.", + Type: schema.TypeString, + }, + "users_cname_enabled": { + Optional: true, + Description: "Enabling this allows you to identify users using a `CNAME chain` rather than a single hostname.", + Type: schema.TypeBool, + }, + "users_cname_level": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "Specifies the number of CNAMEs to use in the chain.", + Type: schema.TypeInt, + }, + "users_cookie": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "This specifies the name of the cookie that identifies the user.", + Type: schema.TypeString, + }, + "users_query_string": { + ValidateDiagFunc: validateRegexOrVariable("^[^:/?#\\[\\]@&]+$"), + Optional: true, + Description: "This names the query parameter that identifies the user.", + Type: schema.TypeString, + }, + "users_regex": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9\\:\\[\\]\\{\\}\\(\\)\\.\\?_\\-\\*\\+\\^\\$\\\\\\/\\|&=!]{1,250})$"), + Optional: true, + Description: "Specifies a Perl-compatible regular expression with which to substitute the request's user ID.", + Type: schema.TypeString, + }, + "users_replace": { + ValidateDiagFunc: validateRegexOrVariable("^(([a-zA-Z0-9_\\-]|\\$[1-9]){1,250})$"), + Optional: true, + Description: "Specifies a string to replace the request's user ID matched by `usersRegex`.", + Type: schema.TypeString, + }, + }, + }, + }, + "sales_force_commerce_cloud_client": { + Optional: true, + Type: schema.TypeList, + Description: "If you use the Salesforce Commerce Cloud platform for your origin content, this behavior allows your edge content managed by Akamai to contact directly to origin. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the Akamai Connector for Salesforce Commerce Cloud.", + Type: schema.TypeBool, + }, + "connector_id": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\.]+\\-[a-zA-Z0-9_\\.]+\\-[a-zA-Z0-9\\-_\\.]+$|^door2.dw.com$"), + Optional: true, + Description: "An ID value that helps distinguish different types of traffic sent from Akamai to the Salesforce Commerce Cloud. Form the value as `instance-realm-customer`, where `instance` is either `production` or `development`, `realm` is your Salesforce Commerce Cloud service `$REALM` value, and `customer` is the name for your organization in Salesforce Commerce Cloud. You can use alphanumeric characters, underscores, or dot characters within dash-delimited segment values.", + Type: schema.TypeString, + }, + "origin_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"DEFAULT", "CUSTOMER"}, false)), + Optional: true, + Description: "Specifies where the origin is.", + Type: schema.TypeString, + }, + "sf3c_origin_host": { + Optional: true, + Description: "This specifies the hostname or IP address of the custom Salesforce origin.", + Type: schema.TypeString, + }, + "origin_host_header": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"DEFAULT", "CUSTOMER"}, false)), + Optional: true, + Description: "Specifies where the `Host` header is defined.", + Type: schema.TypeString, + }, + "sf3c_origin_host_header": { + Optional: true, + Description: "This specifies the hostname or IP address of the custom Salesforce host header.", + Type: schema.TypeString, + }, + "allow_override_origin_cache_key": { + Optional: true, + Description: "When enabled, overrides the forwarding origin's cache key.", + Type: schema.TypeBool, + }, + }, + }, + }, + "sales_force_commerce_cloud_provider": { + Optional: true, + Type: schema.TypeList, + Description: "This manages traffic between mutual customers and the Salesforce Commerce Cloud platform. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables Akamai Provider for Salesforce Commerce Cloud.", + Type: schema.TypeBool, + }, + }, + }, + }, + "sales_force_commerce_cloud_provider_host_header": { + Optional: true, + Type: schema.TypeList, + Description: "Manages host header values sent to the Salesforce Commerce Cloud platform. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "host_header_source": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"PROPERTY", "CUSTOMER"}, false)), + Optional: true, + Description: "Specify where the host header derives from.", + Type: schema.TypeString, + }, + }, + }, + }, + "save_post_dca_processing": { + Optional: true, + Type: schema.TypeList, + Description: "Used in conjunction with the `cachePost` behavior, this behavior allows the body of POST requests to be processed through Dynamic Content Assembly. Contact Akamai Professional Services for help configuring it. This behavior is for internal usage only. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables processing of POST requests.", + Type: schema.TypeBool, + }, + }, + }, + }, + "schedule_invalidation": { + Optional: true, + Type: schema.TypeList, + Description: "Specifies when cached content that satisfies a rule's criteria expires, optionally at repeating intervals. In addition to periodic cache flushes, you can use this behavior to minimize potential conflicts when related objects expire at different times. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "start": { + Optional: true, + Description: "The UTC date and time when matching cached content is to expire.", + Type: schema.TypeString, + }, + "repeat": { + Optional: true, + Description: "When enabled, invalidation recurs periodically from the `start` time based on the `repeatInterval` time.", + Type: schema.TypeBool, + }, + "repeat_interval": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Specifies how often to invalidate content from the `start` time, expressed in seconds. For example, an expiration set to midnight and an interval of `86400` seconds invalidates content once a day. Repeating intervals of less than 5 minutes are not allowed for `NetStorage` origins.", + Type: schema.TypeString, + }, + "refresh_method": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"INVALIDATE", "PURGE"}, false)), + Optional: true, + Description: "Specifies how to invalidate the content.", + Type: schema.TypeString, + }, + }, + }, + }, + "script_management": { + Optional: true, + Type: schema.TypeList, + Description: "Ensures unresponsive linked JavaScript files do not prevent HTML pages from loading. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the Script Management feature.", + Type: schema.TypeBool, + }, + "serviceworker": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"YES_SERVICE_WORKER", "NO_SERVICE_WORKER"}, false)), + Optional: true, + Description: "Script Management uses a JavaScript service worker called `akam-sw.js`. It applies a policy that helps you manage scripts.", + Type: schema.TypeString, + }, + "timestamp": { + Optional: true, + Description: "A read-only epoch timestamp that represents the last time a Script Management policy was synchronized with its Ion property.", + Type: schema.TypeInt, + }, + }, + }, + }, + "segmented_content_protection": { + Optional: true, + Type: schema.TypeList, + Description: "Validates authorization tokens at the edge server to prevent unauthorized link sharing. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "token_authentication_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the segmented content protection behavior.", + Type: schema.TypeBool, + }, + "key": { + ValidateDiagFunc: validateRegexOrVariable("^(0x)?[0-9a-fA-F]{32}$"), + Optional: true, + Description: "Specifies the encryption key to use as a shared secret to validate tokens.", + Type: schema.TypeString, + }, + "use_advanced": { + Optional: true, + Description: "Allows you to specify advanced `transitionKey` and `salt` options.", + Type: schema.TypeBool, + }, + "transition_key": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validateRegexOrVariable("^(0x)?[0-9a-fA-F]{32}$")), + Optional: true, + Description: "An alternate encryption key to match along with the `key` field, allowing you to rotate keys with no down time.", + Type: schema.TypeString, + }, + "salt": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validation.ToDiagFunc(validation.StringLenBetween(16, 16))), + Optional: true, + Description: "Specifies a salt as input into the token for added security. This value needs to match the salt used in the token generation code.", + Type: schema.TypeString, + }, + "header_for_salt": { + Optional: true, + Description: "This allows you to include additional salt properties specific to each end user to strengthen the relationship between the session token and playback session. This specifies the set of request headers whose values generate the salt value, typically `User-Agent`, `X-Playback-Session-Id`, and `Origin`. Any specified header needs to appear in the player's request.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "field_carry_over": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "session_id": { + Optional: true, + Description: "Enabling this option carries the `session_id` value from the access token over to the session token, for use in tracking and counting unique playback sessions.", + Type: schema.TypeBool, + }, + "data_payload": { + Optional: true, + Description: "Enabling this option carries the `data/payload` field from the access token over to the session token, allowing access to opaque data for log analysis for a URL protected by a session token.", + Type: schema.TypeBool, + }, + "ip": { + Optional: true, + Description: "Enabling this restricts content access to a specific IP address, only appropriate if it does not change during the playback session.", + Type: schema.TypeBool, + }, + "acl": { + Optional: true, + Description: "Enabling this option carries the `ACL` field from the access token over to the session token, to limit the requesting client's access to the specific URL or path set in the `ACL` field. Playback may fail if the base path of the master playlist (and variant playlist, plus segments) varies from that of the `ACL` field.", + Type: schema.TypeBool, + }, + "token_auth_hls_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "enable_token_in_uri": { + Optional: true, + Description: "When enabled, passes tokens in HLS variant manifest URLs and HLS segment URLs, as an alternative to cookies.", + Type: schema.TypeBool, + }, + "hls_master_manifest_files": { + Optional: true, + Description: "Specifies the set of filenames that form HLS master manifest URLs. You can use `*` wildcard character that matches zero or more characters. Make sure to specify master manifest filenames uniquely, to distinguish them from variant manifest files.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "token_revocation_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "token_revocation_enabled": { + Optional: true, + Description: "Enable this to deny requests from playback URLs that contain a `TokenAuth` token that uses specific token identifiers.", + Type: schema.TypeBool, + }, + "revoked_list_id": { + Optional: true, + Description: "Identifies the `TokenAuth` tokens to block from accessing your content.", + Type: schema.TypeInt, + }, + "media_encryption_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "hls_media_encryption": { + Optional: true, + Description: "Enables HLS Segment Encryption.", + Type: schema.TypeBool, + }, + "dash_media_encryption": { + Optional: true, + Description: "Whether to enable DASH Media Encryption.", + Type: schema.TypeBool, + }, + }, + }, + }, + "segmented_media_optimization": { + Optional: true, + Type: schema.TypeList, + Description: "Optimizes segmented media for live or streaming delivery contexts. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "behavior": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ON_DEMAND", "LIVE"}, false)), + Optional: true, + Description: "Sets the type of media content to optimize.", + Type: schema.TypeString, + }, + "enable_ull_streaming": { + Optional: true, + Description: "Enables ultra low latency (ULL) streaming. ULL reduces latency and decreases overall transfer time of live streams.", + Type: schema.TypeBool, + }, + "show_advanced": { + Optional: true, + Description: "Allows you to configure advanced media options.", + Type: schema.TypeBool, + }, + "live_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CONTINUOUS", "EVENT"}, false)), + Optional: true, + Description: "The type of live media.", + Type: schema.TypeString, + }, + "start_time": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "This specifies when the live media event begins.", + Type: schema.TypeString, + }, + "end_time": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "This specifies when the live media event ends.", + Type: schema.TypeString, + }, + "dvr_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CONFIGURABLE", "UNKNOWN"}, false)), + Optional: true, + Description: "The type of DVR.", + Type: schema.TypeString, + }, + "dvr_window": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Set the duration for your media, or `0m` if a DVR is not required.", + Type: schema.TypeString, + }, + }, + }, + }, + "segmented_media_streaming_prefetch": { + Optional: true, + Type: schema.TypeList, + Description: "Prefetches HLS and DASH media stream manifest and segment files, accelerating delivery to end users. For prefetching to work, your origin media's response needs to specify `CDN-Origin-Assist-Prefetch-Path` headers with each URL to prefetch, expressed as either a relative or absolute path. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables media stream prefetching.", + Type: schema.TypeBool, + }, + }, + }, + }, + "set_variable": { + Optional: true, + Type: schema.TypeList, + Description: "Modify a variable to insert into subsequent fields within the rule tree. Use this behavior to specify the predeclared `variableName` and determine from where to derive its new value. Based on this `valueSource`, you can either generate the value, extract it from some part of the incoming request, assign it from another variable (including a set of built-in system variables), or directly specify its text. Optionally choose a `transform` function to modify the value once. See `Support for variables` for more information. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "variable_name": { + Optional: true, + Description: "Specifies the predeclared root name of the variable to modify. When you declare a variable name such as `VAR`, its name is preprended with `PMUSER_` and accessible in a `user` namespace, so that you invoke it in subsequent text fields within the rule tree as `{{user.PMUSER_VAR}}`. In deployed `XML metadata`, it appears as `%(PMUSER_VAR)`.", + Type: schema.TypeString, + }, + "value_source": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"EXPRESSION", "EXTRACT", "GENERATE"}, false)), + Optional: true, + Description: "Determines how you want to set the value.", + Type: schema.TypeString, + }, + "variable_value": { + Optional: true, + Description: "This directly specifies the value to assign to the variable. The expression may include a mix of static text and other variables, such as `new_filename.{{builtin.AK_EXTENSION}}` to embed a system variable.", + Type: schema.TypeString, + }, + "extract_location": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CLIENT_CERTIFICATE", "CLIENT_REQUEST_HEADER", "COOKIE", "EDGESCAPE", "PATH_COMPONENT_OFFSET", "QUERY_STRING", "DEVICE_PROFILE", "RESPONSE_HEADER", "SET_COOKIE"}, false)), + Optional: true, + Description: "This specifies from where to get the value.", + Type: schema.TypeString, + }, + "certificate_field_name": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"VERSION", "SERIAL", "FINGERPRINT_MD5", "FINGERPRINT_SHA1", "FINGERPRINT_DYN", "ISSUER_DN", "SUBJECT_DN", "NOT_BEFORE", "NOT_AFTER", "SIGNATURE_ALGORITHM", "SIGNATURE", "CONTENTS_DER", "CONTENTS_PEM", "CONTENTS_PEM_NO_LABELS", "COUNT", "STATUS_MSG", "KEY_LENGTH"}, false)), + Optional: true, + Description: "Specifies the certificate's content.", + Type: schema.TypeString, + }, + "header_name": { + Optional: true, + Description: "Specifies the case-insensitive name of the HTTP header to extract.", + Type: schema.TypeString, + }, + "response_header_name": { + Optional: true, + Description: "Specifies the case-insensitive name of the HTTP header to extract.", + Type: schema.TypeString, + }, + "set_cookie_name": { + Optional: true, + Description: "Specifies the name of the origin's `Set-Cookie` response header.", + Type: schema.TypeString, + }, + "cookie_name": { + Optional: true, + Description: "Specifies the name of the cookie to extract.", + Type: schema.TypeString, + }, + "location_id": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"GEOREGION", "COUNTRY_CODE", "REGION_CODE", "CITY", "DMA", "PMSA", "MSA", "AREACODE", "COUNTY", "FIPS", "LAT", "LONG", "TIMEZONE", "ZIP", "CONTINENT", "NETWORK", "NETWORK_TYPE", "ASNUM", "THROUGHPUT", "BW"}, false)), + Optional: true, + Description: "Specifies the `X-Akamai-Edgescape` header's field name. Possible values specify basic geolocation, various geographic standards, and information about the client's network. For details on EdgeScape header fields, see the `EdgeScape User Guide`.", + Type: schema.TypeString, + }, + "path_component_offset": { + Optional: true, + Description: "This specifies a portion of the path. The indexing starts from `1`, so a value of `/path/to/nested/filename.html` and an offset of `1` yields `path`, and `3` yields `nested`. Negative indexes offset from the right, so `-2` also yields `nested`.", + Type: schema.TypeString, + }, + "query_parameter_name": { + Optional: true, + Description: "Specifies the name of the query parameter from which to extract the value.", + Type: schema.TypeString, + }, + "generator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"HEXRAND", "RAND"}, false)), + Optional: true, + Description: "This specifies the type of value to generate.", + Type: schema.TypeString, + }, + "number_of_bytes": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(1, 16)), + Optional: true, + Description: "Specifies the number of random hex bytes to generate.", + Type: schema.TypeInt, + }, + "min_random_number": { + Optional: true, + Description: "Specifies the lower bound of the random number.", + Type: schema.TypeInt, + }, + "max_random_number": { + Optional: true, + Description: "Specifies the upper bound of the random number.", + Type: schema.TypeInt, + }, + "transform": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"NONE", "ADD", "BASE_64_DECODE", "BASE_64_ENCODE", "BASE_32_DECODE", "BASE_32_ENCODE", "BITWISE_AND", "BITWISE_NOT", "BITWISE_OR", "BITWISE_XOR", "DECIMAL_TO_HEX", "DECRYPT", "DIVIDE", "ENCRYPT", "EPOCH_TO_STRING", "EXTRACT_PARAM", "HASH", "JSON_EXTRACT", "HEX_TO_DECIMAL", "HEX_DECODE", "HEX_ENCODE", "HMAC", "LOWER", "MD5", "MINUS", "MODULO", "MULTIPLY", "NORMALIZE_PATH_WIN", "REMOVE_WHITESPACE", "COMPRESS_WHITESPACE", "SHA_1", "SHA_256", "STRING_INDEX", "STRING_LENGTH", "STRING_TO_EPOCH", "SUBSTITUTE", "SUBSTRING", "SUBTRACT", "TRIM", "UPPER", "BASE_64_URL_DECODE", "BASE_64_URL_ENCODE", "URL_DECODE", "URL_ENCODE", "URL_DECODE_UNI", "UTC_SECONDS", "XML_DECODE", "XML_ENCODE"}, false)), + Optional: true, + Description: "Specifies a function to transform the value. For more details on each transform function, see `Set Variable: Operations`.", + Type: schema.TypeString, + }, + "operand_one": { + Optional: true, + Description: "Specifies an additional operand when the `transform` function is set to various arithmetic functions (`ADD`, `SUBTRACT`, `MULTIPLY`, `DIVIDE`, or `MODULO`) or bitwise functions (`BITWISE_AND`, `BITWISE_OR`, or `BITWISE_XOR`).", + Type: schema.TypeString, + }, + "algorithm": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ALG_3DES", "ALG_AES128", "ALG_AES256"}, false)), + Optional: true, + Description: "Specifies the algorithm to apply.", + Type: schema.TypeString, + }, + "encryption_key": { + ValidateDiagFunc: validateRegexOrVariable("^(0x)?[0-9a-fA-F]+$"), + Optional: true, + Description: "Specifies the encryption hex key. For `ALG_3DES` it needs to be 48 characters long, 32 characters for `ALG_AES128`, and 64 characters for `ALG_AES256`.", + Type: schema.TypeString, + }, + "initialization_vector": { + ValidateDiagFunc: validateRegexOrVariable("^(0x)?[0-9a-fA-F]+$"), + Optional: true, + Description: "Specifies a one-time number as an initialization vector. It needs to be 15 characters long for `ALG_3DES`, and 32 characters for both `ALG_AES128` and `ALG_AES256`.", + Type: schema.TypeString, + }, + "encryption_mode": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CBC", "ECB"}, false)), + Optional: true, + Description: "Specifies the encryption mode.", + Type: schema.TypeString, + }, + "nonce": { + Optional: true, + Description: "Specifies the one-time number used for encryption.", + Type: schema.TypeString, + }, + "prepend_bytes": { + Optional: true, + Description: "Specifies a number of random bytes to prepend to the key.", + Type: schema.TypeBool, + }, + "format_string": { + Optional: true, + Description: "Specifies an optional format string for the conversion, using format codes such as `%m/%d/%y` as specified by `strftime`. A blank value defaults to RFC-2616 format.", + Type: schema.TypeString, + }, + "param_name": { + Optional: true, + Description: "Extracts the value for the specified parameter name from a string that contains key/value pairs. (Use `separator` below to parse them.)", + Type: schema.TypeString, + }, + "separator": { + Optional: true, + Description: "Specifies the character that separates pairs of values within the string.", + Type: schema.TypeString, + }, + "min": { + Optional: true, + Description: "Specifies a minimum value for the generated integer.", + Type: schema.TypeInt, + }, + "max": { + Optional: true, + Description: "Specifies a maximum value for the generated integer.", + Type: schema.TypeInt, + }, + "hmac_key": { + Optional: true, + Description: "Specifies the secret to use in generating the base64-encoded digest.", + Type: schema.TypeString, + }, + "hmac_algorithm": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SHA1", "SHA256", "MD5"}, false)), + Optional: true, + Description: "Specifies the algorithm to use to generate the base64-encoded digest.", + Type: schema.TypeString, + }, + "ip_version": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IPV4", "IPV6"}, false)), + Optional: true, + Description: "Specifies the IP version under which a subnet mask generates.", + Type: schema.TypeString, + }, + "ipv6_prefix": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 128)), + Optional: true, + Description: "Specifies the prefix of the IPV6 address, a value between 0 and 128.", + Type: schema.TypeInt, + }, + "ipv4_prefix": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 32)), + Optional: true, + Description: "Specifies the prefix of the IPV4 address, a value between 0 and 32.", + Type: schema.TypeInt, + }, + "sub_string": { + Optional: true, + Description: "Specifies a substring for which the returned value represents a zero-based offset of where it appears in the original string, or `-1` if there's no match.", + Type: schema.TypeString, + }, + "regex": { + Optional: true, + Description: "Specifies the regular expression pattern (PCRE) to match the value.", + Type: schema.TypeString, + }, + "replacement": { + Optional: true, + Description: "Specifies the replacement string. Reinsert grouped items from the match into the replacement using `$1`, `$2` ... `$n`.", + Type: schema.TypeString, + }, + "case_sensitive": { + Optional: true, + Description: "Enabling this makes all matches case sensitive.", + Type: schema.TypeBool, + }, + "global_substitution": { + Optional: true, + Description: "Replaces all matches in the string, not just the first.", + Type: schema.TypeBool, + }, + "start_index": { + Optional: true, + Description: "Specifies the zero-based character offset at the start of the substring. Negative indexes specify the offset from the end of the string.", + Type: schema.TypeInt, + }, + "end_index": { + Optional: true, + Description: "Specifies the zero-based character offset at the end of the substring, without including the character at that index position. Negative indexes specify the offset from the end of the string.", + Type: schema.TypeInt, + }, + "except_chars": { + Optional: true, + Description: "Specifies characters `not` to encode, possibly overriding the default set.", + Type: schema.TypeString, + }, + "force_chars": { + Optional: true, + Description: "Specifies characters to encode, possibly overriding the default set.", + Type: schema.TypeString, + }, + "device_profile": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS_MOBILE", "IS_TABLET", "IS_WIRELESS_DEVICE", "PHYSICAL_SCREEN_HEIGHT", "PHYSICAL_SCREEN_WIDTH", "RESOLUTION_HEIGHT", "RESOLUTION_WIDTH", "VIEWPORT_WIDTH", "BRAND_NAME", "DEVICE_OS", "DEVICE_OS_VERSION", "DUAL_ORIENTATION", "MAX_IMAGE_HEIGHT", "MAX_IMAGE_WIDTH", "MOBILE_BROWSER", "MOBILE_BROWSER_VERSION", "PDF_SUPPORT", "COOKIE_SUPPORT"}, false)), + Optional: true, + Description: "Specifies the client device attribute. Possible values specify information about the client device, including device type, size and browser. For details on fields, see `Device Characterization`.", + Type: schema.TypeString, + }, + }, + }, + }, + "simulate_error_code": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior simulates various error response codes. Contact Akamai Professional Services for help configuring it. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "error_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ERR_DNS_TIMEOUT", "ERR_SUREROUTE_DNS_FAIL", "ERR_DNS_FAIL", "ERR_CONNECT_TIMEOUT", "ERR_NO_GOOD_FWD_IP", "ERR_DNS_IN_REGION", "ERR_CONNECT_FAIL", "ERR_READ_TIMEOUT", "ERR_READ_ERROR", "ERR_WRITE_ERROR"}, false)), + Optional: true, + Description: "Specifies the type of error.", + Type: schema.TypeString, + }, + "timeout": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "When the `errorType` is `ERR_CONNECT_TIMEOUT`, `ERR_DNS_TIMEOUT`, `ERR_SUREROUTE_DNS_FAIL`, or `ERR_READ_TIMEOUT`, generates an error after the specified amount of time from the initial request.", + Type: schema.TypeString, + }, + }, + }, + }, + "site_shield": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior implements the `Site Shield` feature, which helps prevent non-Akamai machines from contacting your origin. You get an email with a list of Akamai servers allowed to contact your origin, with which you establish an Access Control List on your firewall to prevent any other requests. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "ssmap": { + Optional: true, + Description: "Identifies the hostname for the Site Shield map. See `Create a Site Shield map` for more details. Form an object with a `value` key that references the hostname, for example: `\"ssmap\":{\"value\":\"ss.akamai.net\"}`.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "value": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "srmap": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "china_cdn_map": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "has_mixed_hosts": { + Optional: true, + Description: "", + Type: schema.TypeBool, + }, + "src": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"FALLBACK", "PROTECTED_HOST_MATCH", "ORIGIN_MATCH", "PREVIOUS_MAP", "PROPERTY_MATCH"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "nossmap": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + "standard_tls_migration": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior is deprecated, but you should not disable or remove it if present. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Allows migration to Standard TLS.", + Type: schema.TypeBool, + }, + "migration_from": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SHARED_CERT", "NON_SECURE", "ENHANCED_SECURE"}, false)), + Optional: true, + Description: "What kind of traffic you're migrating from.", + Type: schema.TypeString, + }, + "allow_https_upgrade": { + Optional: true, + Description: "Allows temporary upgrade of HTTP traffic to HTTPS.", + Type: schema.TypeBool, + }, + "allow_https_downgrade": { + Optional: true, + Description: "Allow temporary downgrade of HTTPS traffic to HTTP. This removes various `Origin`, `Referer`, `Cookie`, `Cookie2`, `sec-*` and `proxy-*` headers from the request to origin.", + Type: schema.TypeBool, + }, + "migration_start_time": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "Specifies when to start migrating the cache.", + Type: schema.TypeString, + }, + "migration_duration": { + ValidateDiagFunc: validateRegexOrVariable("^[1-9]$|^[1-2]\\d$|^30$"), + Optional: true, + Description: "Specifies the number of days to migrate the cache.", + Type: schema.TypeInt, + }, + "cache_sharing_start_time": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "Specifies when to start cache sharing.", + Type: schema.TypeString, + }, + "cache_sharing_duration": { + ValidateDiagFunc: validateRegexOrVariable("^[1-9]$|^[1-2]\\d$|^30$"), + Optional: true, + Description: "Specifies the number cache sharing days.", + Type: schema.TypeInt, + }, + "is_certificate_sni_only": { + Optional: true, + Description: "Sets whether your new certificate is SNI-only.", + Type: schema.TypeBool, + }, + "is_tiered_distribution_used": { + Optional: true, + Description: "Allows you to align traffic to various `tieredDistribution` areas.", + Type: schema.TypeBool, + }, + "td_location": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"GLOBAL", "APAC", "EUROPE", "US_EAST", "US_CENTRAL", "US_WEST", "AUSTRALIA", "GLOBAL_LEGACY"}, false)), + Optional: true, + Description: "Specifies the `tieredDistribution` location.", + Type: schema.TypeString, + }, + }, + }, + }, + "standard_tls_migration_override": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior is deprecated, but you should not disable or remove it if present. This behavior is for internal usage only. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "info": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + "strict_header_parsing": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior specifies how the edge servers should handle requests containing improperly formatted or invalid headers that don’t comply with `RFC 9110`. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "valid_mode": { + Optional: true, + Description: "Rejects requests made with non-RFC-compliant headers that contain invalid characters in the header name or value or which contain invalidly-folded header lines. When disabled, the edge servers allow such requests, passing the invalid headers to the origin server unchanged.", + Type: schema.TypeBool, + }, + "strict_mode": { + Optional: true, + Description: "Rejects requests made with non-RFC-compliant, improperly formatted headers, where the header line starts with a colon, misses a colon or doesn’t end with CR LF. When disabled, the edge servers allow such requests, but correct the violation by removing or rewriting the header line before passing the headers to the origin server.", + Type: schema.TypeBool, + }, + }, + }, + }, + "sub_customer": { + Optional: true, + Type: schema.TypeList, + Description: "When positioned in a property's top-level default rule, enables various `Cloud Embed` features that allow you to leverage Akamai's CDN architecture for your own subcustomers. This behavior's options allow you to use Cloud Embed to configure your subcustomers' content. Once enabled, you can use the `Akamai Cloud Embed API` (ACE) to assign subcustomers to this base configuration, and to customize policies for them. See also the `dynamicWebContent` behavior to configure subcustomers' dynamic web content. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Allows Cloud Embed to dynamically modify your subcustomers' content.", + Type: schema.TypeBool, + }, + "origin": { + Optional: true, + Description: "Allows you to assign origin hostnames for customers.", + Type: schema.TypeBool, + }, + "partner_domain_suffix": { + Optional: true, + Description: "This specifies the appropriate domain suffix, which you should typically match with your property hostname. It identifies the domain as trustworthy on the Akamai network, despite being defined within Cloud Embed, outside of your base property configuration. Include this domain suffix if you want to purge subcustomer URLs. For example, if you provide a value of `suffix.example.com`, then to purge `subcustomer.com/some/path`, specify `subcustomer.com.suffix.example.com/some/path` as the purge request's URL.", + Type: schema.TypeString, + }, + "caching": { + Optional: true, + Description: "Modifies content caching rules.", + Type: schema.TypeBool, + }, + "referrer": { + Optional: true, + Description: "Sets subcustomers' referrer whitelists or blacklist.", + Type: schema.TypeBool, + }, + "ip": { + Optional: true, + Description: "Sets subcustomers' IP whitelists or blacklists.", + Type: schema.TypeBool, + }, + "geo_location": { + Optional: true, + Description: "Sets subcustomers' location-based whitelists or blacklists.", + Type: schema.TypeBool, + }, + "refresh_content": { + Optional: true, + Description: "Allows you to reschedule when content validates for subcustomers.", + Type: schema.TypeBool, + }, + "modify_path": { + Optional: true, + Description: "Modifies a subcustomer's request path.", + Type: schema.TypeBool, + }, + "cache_key": { + Optional: true, + Description: "Allows you to set which query parameters are included in the cache key.", + Type: schema.TypeBool, + }, + "token_authorization": { + Optional: true, + Description: "When enabled, this allows you to configure edge servers to use tokens to control access to subcustomer content. Use Cloud Embed to configure the token to appear in a cookie, header, or query parameter.", + Type: schema.TypeBool, + }, + "site_failover": { + Optional: true, + Description: "Allows you to configure unique failover sites for each subcustomer's policy.", + Type: schema.TypeBool, + }, + "content_compressor": { + Optional: true, + Description: "Allows compression of subcustomer content.", + Type: schema.TypeBool, + }, + "access_control": { + Optional: true, + Description: "When enabled, this allows you to deny requests to a subcustomer's content based on specific match conditions, which you use Cloud Embed to configure in each subcustomer's policy.", + Type: schema.TypeBool, + }, + "dynamic_web_content": { + Optional: true, + Description: "Allows you to apply the `dynamicWebContent` behavior to further modify how dynamic content behaves for subcustomers.", + Type: schema.TypeBool, + }, + "on_demand_video_delivery": { + Optional: true, + Description: "Enables delivery of media assets to subcustomers.", + Type: schema.TypeBool, + }, + "large_file_delivery": { + Optional: true, + Description: "Enables large file delivery for subcustomers.", + Type: schema.TypeBool, + }, + "live_video_delivery": { + Optional: true, + Description: "", + Type: schema.TypeBool, + }, + "web_application_firewall": { + Optional: true, + Description: "Web application firewall (WAF) filters, monitors, and blocks certain HTTP traffic. Use `Akamai Cloud Embed` to add a specific behavior to a subcustomer policy and configure how WAF protection is applied.", + Type: schema.TypeBool, + }, + }, + }, + }, + "sure_route": { + Optional: true, + Type: schema.TypeList, + Description: "The `SureRoute` feature continually tests different routes between origin and edge servers to identify the optimal path. By default, it conducts `races` to identify alternative paths to use in case of a transmission failure. These races increase origin traffic slightly. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the SureRoute behavior, to optimize delivery of non-cached content.", + Type: schema.TypeBool, + }, + "type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"PERFORMANCE", "CUSTOM_MAP"}, false)), + Optional: true, + Description: "Specifies the set of edge servers used to test routes.", + Type: schema.TypeString, + }, + "custom_map": { + Optional: true, + Description: "If `type` is `CUSTOM_MAP`, this specifies the map string provided to you by Akamai Professional Services, or included as part of the `Site Shield` product.", + Type: schema.TypeString, + }, + "test_object_url": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "Specifies the path and filename for your origin's test object to use in races to test routes.", + Type: schema.TypeString, + }, + "sr_download_link_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "to_host_status": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"INCOMING_HH", "OTHER"}, false)), + Optional: true, + Description: "Specifies which hostname to use.", + Type: schema.TypeString, + }, + "to_host": { + Optional: true, + Description: "If `toHostStatus` is `OTHER`, this specifies the custom `Host` header to use when requesting the SureRoute test object.", + Type: schema.TypeString, + }, + "race_stat_ttl": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Specifies the time-to-live to preserve SureRoute race results, typically `30m`. If traffic exceeds a certain threshold after TTL expires, the overflow is routed directly to the origin, not necessarily optimally. If traffic remains under the threshold, the route is determined by the winner of the most recent race.", + Type: schema.TypeString, + }, + "force_ssl_forward": { + Optional: true, + Description: "Forces SureRoute to use SSL when requesting the origin's test object, appropriate if your origin does not respond to HTTP requests, or responds with a redirect to HTTPS.", + Type: schema.TypeBool, + }, + "allow_fcm_parent_override": { + Optional: true, + Description: "", + Type: schema.TypeBool, + }, + "enable_custom_key": { + Optional: true, + Description: "When disabled, caches race results under the race destination's hostname. If enabled, use `customStatKey` to specify a custom hostname.", + Type: schema.TypeBool, + }, + "custom_stat_key": { + ValidateDiagFunc: validateRegexOrVariable("^([a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})(\\.[a-zA-Z0-9][a-zA-Z0-9\\-]{0,62})+$"), + Optional: true, + Description: "This specifies a hostname under which to cache race results. This may be useful when a property corresponds to many origin hostnames. By default, SureRoute would launch races for each origin, but consolidating under a single hostname runs only one race.", + Type: schema.TypeString, + }, + }, + }, + }, + "tcp_optimization": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior is deprecated, but you should not disable or remove it if present. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "display": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + "tea_leaf": { + Optional: true, + Type: schema.TypeList, + Description: "Allows IBM Tealeaf Customer Experience on Cloud to record HTTPS requests and responses for Akamai-enabled properties. Recorded data becomes available in your IBM Tealeaf account. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "When enabled, capture HTTPS requests and responses, and send the data to your IBM Tealeaf account.", + Type: schema.TypeBool, + }, + "limit_to_dynamic": { + Optional: true, + Description: "Limit traffic to dynamic, uncached (`No-Store`) content.", + Type: schema.TypeBool, + }, + "ibm_customer_id": { + Optional: true, + Description: "The integer identifier for the IBM Tealeaf Connector account.", + Type: schema.TypeInt, + }, + }, + }, + }, + "tiered_distribution": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior allows Akamai edge servers to retrieve cached content from other Akamai servers, rather than directly from the origin. These interim `parent` servers in the `cache hierarchy` (`CH`) are positioned close to the origin, and fall along the path from the origin to the edge server. Tiered Distribution typically reduces the origin server's load, and reduces the time it takes for edge servers to refresh content. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "When enabled, activates tiered distribution.", + Type: schema.TypeBool, + }, + "tiered_distribution_map": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CH2", "CHAPAC", "CHEU2", "CHEUS2", "CHCUS2", "CHWUS2", "CHAUS", "CH"}, false)), + Optional: true, + Description: "Optionally map the tiered parent server's location close to your origin. A narrower local map minimizes the origin server's load, and increases the likelihood the requested object is cached. A wider global map reduces end-user latency, but decreases the likelihood the requested object is in any given parent server's cache. This option cannot apply if the property is marked as secure. See `Secure property requirements` for guidance.", + Type: schema.TypeString, + }, + }, + }, + }, + "tiered_distribution_advanced": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior allows Akamai edge servers to retrieve cached content from other Akamai servers, rather than directly from the origin. These interim `parent` servers in the `cache hierarchy` (`CH`) are positioned close to the origin, and fall along the path from the origin to the edge server. Tiered Distribution typically reduces the origin server's load, and reduces the time it takes for edge servers to refresh content. This advanced behavior provides a wider set of options than `tieredDistribution`. This behavior is for internal usage only. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "When enabled, activates tiered distribution.", + Type: schema.TypeBool, + }, + "method": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SERIAL_PREPEND", "DOMAIN_LOOKUP"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "policy": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"PERFORMANCE", "TIERED_DISTRIBUTION", "FAIL_OVER", "SITE_SHIELD", "SITE_SHIELD_PERFORMANCE"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "tiered_distribution_map": { + Optional: true, + Description: "Optionally map the tiered parent server's location close to your origin: `CHEU2` for Europe; `CHAUS` for Australia; `CHAPAC` for China and the Asian Pacific area; `CHWUS2`, `CHCUS2`, and `CHEUS2` for different parts of the United States. Choose `CH` or `CH2` for a more global map. A narrower local map minimizes the origin server's load, and increases the likelihood the requested object is cached. A wider global map reduces end-user latency, but decreases the likelihood the requested object is in any given parent server's cache. This option cannot apply if the property is marked as secure. See `Secure property requirements` for guidance.", + Type: schema.TypeString, + }, + "allowall": { + Optional: true, + Description: "", + Type: schema.TypeBool, + }, + }, + }, + }, + "tiered_distribution_customization": { + Optional: true, + Type: schema.TypeList, + Description: "With Tiered Distribution, Akamai edge servers retrieve cached content from other Akamai servers, rather than directly from the origin. This behavior sets custom Tiered Distribution maps (TD0) and migrates TD1 maps configured with `advanced features` to Cloud Wrapper. You need to enable `cloudWrapper` within the same rule. This behavior is for internal usage only. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "tier1_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "custom_map_enabled": { + Optional: true, + Description: "Enables custom maps.", + Type: schema.TypeBool, + }, + "custom_map_name": { + ValidateDiagFunc: validateRegexOrVariable("^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)+(akamai|akamaiedge)\\.net$"), + Optional: true, + Description: "Specifies the custom map name.", + Type: schema.TypeString, + }, + "serial_start": { + Optional: true, + Description: "Specifies a numeric serial start value.", + Type: schema.TypeString, + }, + "serial_end": { + Optional: true, + Description: "Specifies a numeric serial end value. Akamai uses serial numbers to group machines and share objects in their cache with other machines in the same region.", + Type: schema.TypeString, + }, + "hash_algorithm": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"GCC", "JENKINS"}, false)), + Optional: true, + Description: "Specifies the hash algorithm.", + Type: schema.TypeString, + }, + "cloudwrapper_map_migration_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "map_migration_enabled": { + Optional: true, + Description: "Enables migration of the custom map to Cloud Wrapper.", + Type: schema.TypeBool, + }, + "migration_within_cw_maps_enabled": { + Optional: true, + Description: "Enables migration within Cloud Wrapper maps.", + Type: schema.TypeBool, + }, + "location": { + Optional: true, + Description: "Location from which Cloud Wrapper migration is performed. User should choose the existing Cloud Wrapper location. The new Cloud Wrapper location (to which migration has to happen) is expected to be updated as part of the main \"Cloud Wrapper\" behavior.", + Type: schema.TypeString, + }, + "migration_start_date": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "Specifies when to start migrating the map.", + Type: schema.TypeString, + }, + "migration_end_date": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "Specifies when the map migration should end.", + Type: schema.TypeString, + }, + }, + }, + }, + "timeout": { + Optional: true, + Type: schema.TypeList, + Description: "Sets the HTTP connect timeout. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "value": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Specifies the timeout, for example `10s`.", + Type: schema.TypeString, + }, + }, + }, + }, + "uid_configuration": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior allows you to extract unique identifier (UID) values from live traffic, for use in OTA applications. Note that you are responsible for maintaining the security of any data that may identify individual users. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "legal_text": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Allows you to extract UIDs from client requests.", + Type: schema.TypeBool, + }, + "extract_location": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CLIENT_REQUEST_HEADER", "QUERY_STRING", "VARIABLE"}, false)), + Optional: true, + Description: "Where to extract the UID value from.", + Type: schema.TypeString, + }, + "header_name": { + Optional: true, + Description: "This specifies the name of the HTTP header from which to extract the UID value.", + Type: schema.TypeString, + }, + "query_parameter_name": { + Optional: true, + Description: "This specifies the name of the query parameter from which to extract the UID value.", + Type: schema.TypeString, + }, + "variable_name": { + Optional: true, + Description: "This specifies the name of the rule tree variable from which to extract the UID value.", + Type: schema.TypeString, + }, + }, + }, + }, + "validate_entity_tag": { + Optional: true, + Type: schema.TypeList, + Description: "Instructs edge servers to compare the request's `ETag` header with that of the cached object. If they differ, the edge server sends a new copy of the object. This validation occurs in addition to the default validation of `Last-Modified` and `If-Modified-Since` headers. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the ETag validation behavior.", + Type: schema.TypeBool, + }, + }, + }, + }, + "verify_json_web_token": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior allows you to use JSON Web Tokens (JWT) to verify requests. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "extract_location": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CLIENT_REQUEST_HEADER", "QUERY_STRING"}, false)), + Optional: true, + Description: "Specify from where to extract the JWT value.", + Type: schema.TypeString, + }, + "header_name": { + Optional: true, + Description: "This specifies the name of the header from which to extract the JWT value.", + Type: schema.TypeString, + }, + "query_parameter_name": { + Optional: true, + Description: "This specifies the name of the query parameter from which to extract the JWT value.", + Type: schema.TypeString, + }, + "jwt": { + Optional: true, + Description: "An identifier for the JWT keys collection.", + Type: schema.TypeString, + }, + "enable_rs256": { + Optional: true, + Description: "Verifies JWTs signed with the RS256 algorithm. This signature helps ensure that the token hasn't been tampered with.", + Type: schema.TypeBool, + }, + "enable_es256": { + Optional: true, + Description: "Verifies JWTs signed with the ES256 algorithm. This signature helps ensure that the token hasn't been tampered with.", + Type: schema.TypeBool, + }, + }, + }, + }, + "verify_json_web_token_for_dcp": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior allows you to use JSON web tokens (JWT) to verify requests for use in implementing `IoT Edge Connect`, which you use the `dcp` behavior to configure. You can specify the location in a request to pass a JSON web token (JWT), collections of public keys to verify the integrity of this token, and specific claims to extract from it. Use the `verifyJsonWebToken` behavior for other JWT validation. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "extract_location": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CLIENT_REQUEST_HEADER", "QUERY_STRING", "CLIENT_REQUEST_HEADER_AND_QUERY_STRING"}, false)), + Optional: true, + Description: "Specifies where to get the JWT value from.", + Type: schema.TypeString, + }, + "primary_location": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CLIENT_REQUEST_HEADER", "QUERY_STRING"}, false)), + Optional: true, + Description: "Specifies the primary location to extract the JWT value from. If the specified option doesn't include the JWTs, the system checks the secondary one.", + Type: schema.TypeString, + }, + "custom_header": { + Optional: true, + Description: "The JWT value comes from the `X-Akamai-DCP-Token` header by default. Enabling this option allows you to extract it from another header name that you specify.", + Type: schema.TypeBool, + }, + "header_name": { + Optional: true, + Description: "This specifies the name of the header to extract the JWT value from.", + Type: schema.TypeString, + }, + "query_parameter_name": { + Optional: true, + Description: "Specifies the name of the query parameter from which to extract the JWT value.", + Type: schema.TypeString, + }, + "jwt": { + Optional: true, + Description: "An identifier for the JWT keys collection.", + Type: schema.TypeString, + }, + "extract_client_id": { + Optional: true, + Description: "Allows you to extract the client ID claim name stored in JWT.", + Type: schema.TypeBool, + }, + "client_id": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_-]{1,20}$"), + Optional: true, + Description: "This specifies the claim name.", + Type: schema.TypeString, + }, + "extract_authorizations": { + Optional: true, + Description: "Allows you to extract the authorization groups stored in the JWT.", + Type: schema.TypeBool, + }, + "authorizations": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_-]{1,20}$"), + Optional: true, + Description: "This specifies the authorization group name.", + Type: schema.TypeString, + }, + "extract_user_name": { + Optional: true, + Description: "Allows you to extract the user name stored in the JWT.", + Type: schema.TypeBool, + }, + "user_name": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_-]{1,20}$"), + Optional: true, + Description: "This specifies the user name.", + Type: schema.TypeString, + }, + "enable_rs256": { + Optional: true, + Description: "Verifies JWTs signed with the RS256 algorithm. This signature helps to ensure that the token hasn't been tampered with.", + Type: schema.TypeBool, + }, + "enable_es256": { + Optional: true, + Description: "Verifies JWTs signed with the ES256 algorithm. This signature helps to ensure that the token hasn't been tampered with.", + Type: schema.TypeBool, + }, + }, + }, + }, + "verify_token_authorization": { + Optional: true, + Type: schema.TypeList, + Description: "Verifies Auth 2.0 tokens. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "use_advanced": { + Optional: true, + Description: "If enabled, allows you to specify advanced options such as `algorithm`, `escapeHmacInputs`, `ignoreQueryString`, `transitionKey`, and `salt`.", + Type: schema.TypeBool, + }, + "location": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"COOKIE", "QUERY_STRING", "CLIENT_REQUEST_HEADER"}, false)), + Optional: true, + Description: "Specifies where to find the token in the incoming request.", + Type: schema.TypeString, + }, + "location_id": { + Optional: true, + Description: "When `location` is `CLIENT_REQUEST_HEADER`, specifies the name of the incoming request's header where to find the token.", + Type: schema.TypeString, + }, + "algorithm": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"SHA256", "SHA1", "MD5"}, false)), + Optional: true, + Description: "Specifies the algorithm that generates the token. It needs to match the method chosen in the token generation code.", + Type: schema.TypeString, + }, + "escape_hmac_inputs": { + Optional: true, + Description: "URL-escapes HMAC inputs passed in as query parameters.", + Type: schema.TypeBool, + }, + "ignore_query_string": { + Optional: true, + Description: "Enabling this removes the query string from the URL used to form an encryption key.", + Type: schema.TypeBool, + }, + "key": { + ValidateDiagFunc: validateRegexOrVariable("^(0x)?[0-9a-fA-F]{64}$"), + Optional: true, + Description: "The shared secret used to validate tokens, which needs to match the key used in the token generation code.", + Type: schema.TypeString, + }, + "transition_key": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validateRegexOrVariable("^(0x)?[0-9a-fA-F]{64}$")), + Optional: true, + Description: "Specifies a transition key as a hex value.", + Type: schema.TypeString, + }, + "salt": { + ValidateDiagFunc: validateAny(validation.ToDiagFunc(validation.StringIsEmpty), validation.ToDiagFunc(validation.StringLenBetween(16, 16))), + Optional: true, + Description: "Specifies a salt string for input when generating the token, which needs to match the salt value used in the token generation code.", + Type: schema.TypeString, + }, + "failure_response": { + Optional: true, + Description: "When enabled, sends an HTTP error when an authentication test fails.", + Type: schema.TypeBool, + }, + }, + }, + }, + "virtual_waiting_room": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior helps you maintain business continuity for dynamic applications in high-demand situations such as flash sales. It decreases abandonment by providing a user-friendly waiting room experience. FIFO (First-in First-out) is a request processing mechanism that prioritizes the first requests that enter the waiting room to send them first to the origin. Users can see both their estimated arrival time and position in the line. With Cloudlets available on your contract, choose `Your services` > `Edge logic Cloudlets` to control Virtual Waitig Room within `Control Center`. Otherwise use the `Cloudlets API` to configure it programmatically. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "cloudlet_shared_policy": { + Optional: true, + Description: "This identifies the Visitor Waiting Room Cloudlet shared policy to use with this behavior. You can list available shared policies with the `Cloudlets API`.", + Type: schema.TypeInt, + }, + "domain_config": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"HOST_HEADER", "CUSTOM"}, false)), + Optional: true, + Description: "This specifies the domain used to establish a session with the visitor.", + Type: schema.TypeString, + }, + "custom_cookie_domain": { + ValidateDiagFunc: validateRegexOrVariable("^(\\.)?(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)+([A-Za-z]|[A-Za-z][A-Za-z0-9\\-]*[A-Za-z0-9])$"), + Optional: true, + Description: "This specifies a domain for all session cookies. In case you configure many property hostnames, this may be their common domain. Make sure the user agent accepts the custom domain for any request matching the `virtualWaitingRoom` behavior. Don't use top level domains (TLDs).", + Type: schema.TypeString, + }, + "waiting_room_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "waiting_room_path": { + Optional: true, + Description: "This specifies the path to the waiting room main page on the origin server, for example `/vp/waiting-room.html`. When the request is marked as Waiting Room Main Page and blocked, the visitor enters the waiting room. The behavior sets the outgoing request path to the `waitingRoomPath` and modifies the cache key accordingly. See the `virtualWaitingRoomRequest` match criteria to further customize these requests.", + Type: schema.TypeString, + }, + "waiting_room_assets_paths": { + Optional: true, + Description: "This specifies the base paths to static resources such as JavaScript, CSS, or image files for the Waiting Room Main Page requests. The option supports the `*` wildcard that matches zero or more characters. Requests matching any of these paths aren't blocked, but marked as Waiting Room Assets and passed through to the origin. See the `virtualWaitingRoomRequest` match criteria to further customize these requests.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "access_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "session_duration": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 86400)), + Optional: true, + Description: "Specifies the number of seconds users remain in the waiting room queue.", + Type: schema.TypeInt, + }, + "session_auto_prolong": { + Optional: true, + Description: "Whether the queue session should prolong automatically when the `sessionDuration` expires and the visitor remains active.", + Type: schema.TypeBool, + }, + }, + }, + }, + "virtual_waiting_room_with_edge_workers": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior allows you to configure the `virtualWaitingRoom` behavior with EdgeWorkers for extended scalability and customization. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + }, + }, + }, + "visitor_prioritization": { + Optional: true, + Type: schema.TypeList, + Description: "The `Visitor Prioritization Cloudlet` decreases abandonment by providing a user-friendly waiting room experience. With Cloudlets available on your contract, choose `Your services` > `Edge logic Cloudlets` to control Visitor Prioritization within `Control Center`. Otherwise use the `Cloudlets API` to configure it programmatically. To serve non-HTML API content such as JSON blocks, see the `apiPrioritization` behavior. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the Visitor Prioritization behavior.", + Type: schema.TypeBool, + }, + "cloudlet_policy": { + Optional: true, + Description: "Identifies the Cloudlet policy.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "user_identification_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "user_identification_by_cookie": { + Optional: true, + Description: "When enabled, identifies users by the value of a cookie.", + Type: schema.TypeBool, + }, + "user_identification_key_cookie": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "Specifies the name of the cookie whose value identifies users. To match a user, the value of the cookie needs to remain constant across all requests.", + Type: schema.TypeString, + }, + "user_identification_by_headers": { + Optional: true, + Description: "When enabled, identifies users by the values of GET or POST request headers.", + Type: schema.TypeBool, + }, + "user_identification_key_headers": { + Optional: true, + Description: "Specifies names of request headers whose values identify users. To match a user, values for all the specified headers need to remain constant across all requests.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "user_identification_by_ip": { + Optional: true, + Description: "Allows IP addresses to identify users.", + Type: schema.TypeBool, + }, + "user_identification_by_params": { + Optional: true, + Description: "When enabled, identifies users by the values of GET or POST request parameters.", + Type: schema.TypeBool, + }, + "user_identification_key_params": { + Optional: true, + Description: "Specifies names of request parameters whose values identify users. To match a user, values for all the specified parameters need to remain constant across all requests. Parameters that are absent or blank may also identify users.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "allowed_user_cookie_management_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "allowed_user_cookie_enabled": { + Optional: true, + Description: "Sets a cookie for users who have been allowed through to the site.", + Type: schema.TypeBool, + }, + "allowed_user_cookie_label": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "Specifies a label to distinguish this cookie for an allowed user from others. The value appends to the cookie's name, and helps you to maintain the same user assignment across behaviors within a property, and across properties.", + Type: schema.TypeString, + }, + "allowed_user_cookie_duration": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 600)), + Optional: true, + Description: "Sets the number of seconds for the allowed user's session once allowed through to the site.", + Type: schema.TypeInt, + }, + "allowed_user_cookie_refresh": { + Optional: true, + Description: "Resets the duration of an allowed cookie with each request, so that it only expires if the user doesn't make any requests for the specified duration. Do not enable this option if you want to set a fixed time for all users.", + Type: schema.TypeBool, + }, + "allowed_user_cookie_advanced": { + Optional: true, + Description: "Sets advanced configuration options for the allowed user's cookie.", + Type: schema.TypeBool, + }, + "allowed_user_cookie_automatic_salt": { + Optional: true, + Description: "Sets an automatic `salt` value to verify the integrity of the cookie for an allowed user. Disable this if you want to share the cookie across properties.", + Type: schema.TypeBool, + }, + "allowed_user_cookie_salt": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "Specifies a fixed `salt` value, which is incorporated into the cookie's value to prevent users from manipulating it. You can use the same salt string across different behaviors or properties to apply a single cookie to all allowed users.", + Type: schema.TypeString, + }, + "allowed_user_cookie_domain_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"DYNAMIC", "CUSTOMER"}, false)), + Optional: true, + Description: "Specify with `allowedUserCookieAdvanced` enabled.", + Type: schema.TypeString, + }, + "allowed_user_cookie_domain": { + ValidateDiagFunc: validateRegexOrVariable("^(\\.)?(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)+([A-Za-z]|[A-Za-z][A-Za-z0-9\\-]*[A-Za-z0-9])$"), + Optional: true, + Description: "Specifies a domain for an allowed user cookie.", + Type: schema.TypeString, + }, + "allowed_user_cookie_http_only": { + Optional: true, + Description: "Applies the `HttpOnly` flag to the allowed user's cookie to ensure it's accessed over HTTP and not manipulated by the client.", + Type: schema.TypeBool, + }, + "waiting_room_cookie_management_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "waiting_room_cookie_enabled": { + Optional: true, + Description: "Enables a cookie to track a waiting room assignment.", + Type: schema.TypeBool, + }, + "waiting_room_cookie_share_label": { + Optional: true, + Description: "Enabling this option shares the same `allowedUserCookieLabel` string. If disabled, specify a different `waitingRoomCookieLabel`.", + Type: schema.TypeBool, + }, + "waiting_room_cookie_label": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "Specifies a label to distinguish this waiting room cookie from others. The value appends to the cookie's name, and helps you to maintain the same waiting room assignment across behaviors within a property, and across properties.", + Type: schema.TypeString, + }, + "waiting_room_cookie_duration": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 120)), + Optional: true, + Description: "Sets the number of seconds for which users remain in the waiting room. During this time, users who refresh the waiting room page remain there.", + Type: schema.TypeInt, + }, + "waiting_room_cookie_advanced": { + Optional: true, + Description: "When enabled along with `waitingRoomCookieEnabled`, sets advanced configuration options for the waiting room cookie.", + Type: schema.TypeBool, + }, + "waiting_room_cookie_automatic_salt": { + Optional: true, + Description: "Sets an automatic `salt` value to verify the integrity of the waiting room cookie. Disable this if you want to share the cookie across properties.", + Type: schema.TypeBool, + }, + "waiting_room_cookie_salt": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "Specifies a fixed `salt` value, which is incorporated into the cookie's value to prevent users from manipulating it. You can use the same salt string across different behaviors or properties to apply a single cookie for the waiting room session.", + Type: schema.TypeString, + }, + "waiting_room_cookie_domain_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"DYNAMIC", "CUSTOMER"}, false)), + Optional: true, + Description: "Specify with `waitingRoomCookieAdvanced` enabled, selects whether to use the `DYNAMIC` incoming host header, or a `CUSTOMER`-defined cookie domain.", + Type: schema.TypeString, + }, + "waiting_room_cookie_domain": { + ValidateDiagFunc: validateRegexOrVariable("^(\\.)?(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)+([A-Za-z]|[A-Za-z][A-Za-z0-9\\-]*[A-Za-z0-9])$"), + Optional: true, + Description: "Specifies a domain for the waiting room cookie.", + Type: schema.TypeString, + }, + "waiting_room_cookie_http_only": { + Optional: true, + Description: "Applies the `HttpOnly` flag to the waiting room cookie to ensure it's accessed over HTTP and not manipulated by the client.", + Type: schema.TypeBool, + }, + "waiting_room_management_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "waiting_room_status_code": { + ValidateDiagFunc: validateRegexOrVariable("[2|4|5][0-9][0-9]"), + Optional: true, + Description: "Specifies the response code for requests sent to the waiting room.", + Type: schema.TypeInt, + }, + "waiting_room_use_cp_code": { + Optional: true, + Description: "Allows you to assign a different CP code that tracks any requests that are sent to the waiting room.", + Type: schema.TypeBool, + }, + "waiting_room_cp_code": { + Optional: true, + Description: "Specifies a CP code for requests sent to the waiting room. You only need to provide the initial `id`, stripping any `cpc_` prefix to pass the integer to the rule tree. Additional CP code details may reflect back in subsequent read-only data.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "created_date": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "description": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "products": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "cp_code_limits": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "current_capacity": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit_type": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + "waiting_room_net_storage": { + Optional: true, + Description: "Specifies the NetStorage domain for the waiting room page.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "cp_code": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "download_domain_name": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "g2o_token": { + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "waiting_room_directory": { + ValidateDiagFunc: validateRegexOrVariable("^[^#\\[\\]@]+$"), + Optional: true, + Description: "Specifies the NetStorage directory that contains the static waiting room page, with no trailing slash character.", + Type: schema.TypeString, + }, + "waiting_room_cache_ttl": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(5, 30)), + Optional: true, + Description: "Specifies the waiting room page's time to live in the cache, `5` minutes by default.", + Type: schema.TypeInt, + }, + }, + }, + }, + "visitor_prioritization_fifo": { + Optional: true, + Type: schema.TypeList, + Description: "(**BETA**) The `Visitor Prioritization Cloudlet (FIFO)` decreases abandonment by providing a user-friendly waiting room experience. FIFO (First-in First-out) is a fair request processing mechanism, which prioritizes the first requests that enter the waiting room to send them first to the origin. Users can see both their estimated arrival time and position in the line. With Cloudlets available on your contract, choose `Your services` > `Edge logic Cloudlets` to control Visitor Prioritization (FIFO) within `Control Center`. Otherwise use the `Cloudlets API` to configure it programmatically. To serve non-HTML API content such as JSON blocks, see the `apiPrioritization` behavior. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "cloudlet_shared_policy": { + Optional: true, + Description: "This identifies the Visitor Prioritization FIFO shared policy to use with this behavior. You can list available shared policies with the `Cloudlets API`.", + Type: schema.TypeInt, + }, + "domain_config": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"HOST_HEADER", "CUSTOM"}, false)), + Optional: true, + Description: "This specifies how to set the domain used to establish a session with the visitor.", + Type: schema.TypeString, + }, + "custom_cookie_domain": { + ValidateDiagFunc: validateRegexOrVariable("^(\\.)?(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\\-]*[a-zA-Z0-9])\\.)+([A-Za-z]|[A-Za-z][A-Za-z0-9\\-]*[A-Za-z0-9])$"), + Optional: true, + Description: "This specifies a domain for all session cookies. In case you configure many property hostnames, this may be their common domain. Make sure the user agent accepts the custom domain for any request matching the `visitorPrioritizationFifo` behavior. Don't use top level domains (TLDs).", + Type: schema.TypeString, + }, + "waiting_room_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "waiting_room_path": { + Optional: true, + Description: "This specifies the path to the waiting room main page on the origin server, for example `/vp/waiting-room.html`. When the request is marked as `Waiting Room Main Page` and blocked, the visitor enters the waiting room. The behavior sets the outgoing request path to the `waitingRoomPath` and modifies the cache key accordingly. See the `visitorPrioritizationRequest` match criteria to further customize these requests.", + Type: schema.TypeString, + }, + "waiting_room_assets_paths": { + Optional: true, + Description: "This specifies the base paths to static resources such as `JavaScript`, `CSS`, or image files for the `Waiting Room Main Page` requests. The option supports the `*` wildcard wildcard that matches zero or more characters. Requests matching any of these paths aren't blocked, but marked as Waiting Room Assets and passed through to the origin. See the `visitorPrioritizationRequest` match criteria to further customize these requests.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "access_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "session_duration": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 86400)), + Optional: true, + Description: "Specifies the number of seconds users remain in the waiting room queue.", + Type: schema.TypeInt, + }, + "session_auto_prolong": { + Optional: true, + Description: "Whether the queue session should prolong automatically when the `sessionDuration` expires and the visitor remains active.", + Type: schema.TypeBool, + }, + }, + }, + }, + "visitor_prioritization_fifo_standalone": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + }, + }, + }, + "watermarking": { + Optional: true, + Type: schema.TypeList, + Description: "Adds watermarking for each valid user's content. Content segments are delivered from different sources using a pattern unique to each user, based on a watermarking token included in each request. If your content is pirated or redistributed, you can forensically analyze the segments to extract the pattern, and identify the user who leaked the content. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enable": { + Optional: true, + Description: "Enables the watermarking behavior.", + Type: schema.TypeBool, + }, + "token_signing_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "signature_verification_enable": { + Optional: true, + Description: "When enabled, you can verify the signature in your watermarking token.", + Type: schema.TypeBool, + }, + "verification_key_id1": { + Optional: true, + Description: "Specifies a unique identifier for the first public key.", + Type: schema.TypeString, + }, + "verification_public_key1": { + Optional: true, + Description: "Specifies the first public key in its entirety.", + Type: schema.TypeString, + }, + "verification_key_id2": { + Optional: true, + Description: "Specifies a unique identifier for the optional second public key.", + Type: schema.TypeString, + }, + "verification_public_key2": { + Optional: true, + Description: "Specifies the optional second public key in its entirety. Specify a second key to enable rotation.", + Type: schema.TypeString, + }, + "pattern_encryption_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "pattern_decryption_enable": { + Optional: true, + Description: "If patterns in your watermarking tokens have been encrypted, enabling this allows you to provide values to decrypt them.", + Type: schema.TypeBool, + }, + "decryption_password_id1": { + Optional: true, + Description: "Specifies a label that corresponds to the primary password.", + Type: schema.TypeString, + }, + "decryption_password1": { + Optional: true, + Description: "Provides the primary password used to encrypt patterns in your watermarking tokens.", + Type: schema.TypeString, + }, + "decryption_password_id2": { + Optional: true, + Description: "Specifies a label for the secondary password, used in rotation scenarios to identify which password to use for decryption.", + Type: schema.TypeString, + }, + "decryption_password2": { + Optional: true, + Description: "Provides the secondary password you can use to rotate passwords.", + Type: schema.TypeString, + }, + "miscellaneous_settings_title": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "use_original_as_a": { + Optional: true, + Description: "When you work with your watermarking vendor, you can apply several preprocessing methods to your content. See the `AMD help` for more information. With the standard `filename-prefix AB naming` preprocessing method, the watermarking vendor creates two variants of the original segment content and labels them as an `A` and `B` segment in the filename. If you selected the `unlabeled A variant` preprocessing method, enabling this option tells your configuration to use the original filename segment content as your `A` variant.", + Type: schema.TypeBool, + }, + "ab_variant_location": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"FILENAME_PREFIX", "PARENT_DIRECTORY_PREFIX"}, false)), + Optional: true, + Description: "When you work with your watermarking vendor, you can apply several preprocessing methods to your content. See the `AMD help` for more information. Use this option to specify the location of the `A` and `B` variant segments.", + Type: schema.TypeString, + }, + }, + }, + }, + "web_application_firewall": { + Optional: true, + Type: schema.TypeList, + Description: "This behavior implements a suite of security features that blocks threatening HTTP and HTTPS requests. Use it as your primary firewall, or in addition to existing security measures. Only one referenced configuration is allowed per property, so this behavior typically belongs as part of its default rule. This behavior cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "firewall_configuration": { + Optional: true, + Description: "An object featuring details about your firewall configuration.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "config_id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "production_status": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior", + Type: schema.TypeString, + }, + "staging_status": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior", + Type: schema.TypeString, + }, + "production_version": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior", + Type: schema.TypeInt, + }, + "staging_version": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior", + Type: schema.TypeInt, + }, + "file_name": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior", + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + "web_sockets": { + Optional: true, + Type: schema.TypeList, + Description: "The WebSocket protocol allows web applications real-time bidirectional communication between clients and servers. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables WebSocket traffic.", + Type: schema.TypeBool, + }, + }, + }, + }, + "webdav": { + Optional: true, + Type: schema.TypeList, + Description: "Web-based Distributed Authoring and Versioning (WebDAV) is a set of extensions to the HTTP protocol that allows users to collaboratively edit and manage files on remote web servers. This behavior enables WebDAV, and provides support for the following additional request methods: PROPFIND, PROPPATCH, MKCOL, COPY, MOVE, LOCK, and UNLOCK. To apply this behavior, you need to match on a `requestMethod`. This behavior can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "enabled": { + Optional: true, + Description: "Enables the WebDAV behavior.", + Type: schema.TypeBool, + }, + }, + }, + }, + } +} + +func getCriteriaSchemaV20240109() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "advanced_im_match": { + Optional: true, + Type: schema.TypeList, + Description: "Matches whether the `imageManager` behavior already applies to the current set of requests. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS", "IS_NOT"}, false)), + Optional: true, + Description: "Specifies the match's logic.", + Type: schema.TypeString, + }, + "match_on": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ANY_IM", "PRISTINE"}, false)), + Optional: true, + Description: "Specifies the match's scope.", + Type: schema.TypeString, + }, + }, + }, + }, + "bucket": { + Optional: true, + Type: schema.TypeList, + Description: "This matches a specified percentage of requests when used with the accompanying behavior. Contact Akamai Professional Services for help configuring it. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "percentage": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 100)), + Optional: true, + Description: "Specifies the percentage of requests to match.", + Type: schema.TypeInt, + }, + }, + }, + }, + "cacheability": { + Optional: true, + Type: schema.TypeList, + Description: "Matches the current cache state. Note that any `NO_STORE` or `BYPASS_CACHE` HTTP headers set on the origin's content overrides properties' `caching` instructions, in which case this criteria does not apply. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS", "IS_NOT"}, false)), + Optional: true, + Description: "Specifies the match's logic.", + Type: schema.TypeString, + }, + "value": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"NO_STORE", "BYPASS_CACHE", "CACHEABLE"}, false)), + Optional: true, + Description: "Content's cache is enabled (`CACHEABLE`) or not (`NO_STORE`), or else is ignored (`BYPASS_CACHE`).", + Type: schema.TypeString, + }, + }, + }, + }, + "china_cdn_region": { + Optional: true, + Type: schema.TypeList, + Description: "Identifies traffic deployed over Akamai's regional ChinaCDN infrastructure. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS", "IS_NOT"}, false)), + Optional: true, + Description: "Specify whether the request `IS` or `IS_NOT` deployed over ChinaCDN.", + Type: schema.TypeString, + }, + }, + }, + }, + "client_certificate": { + Optional: true, + Type: schema.TypeList, + Description: "Matches whether you have configured a client certificate to authenticate requests to edge servers. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "is_certificate_present": { + Optional: true, + Description: "Executes rule behaviors only if a client certificate authenticates requests.", + Type: schema.TypeBool, + }, + "is_certificate_valid": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"VALID", "INVALID", "IGNORE"}, false)), + Optional: true, + Description: "Matches whether the certificate is `VALID` or `INVALID`. You can also `IGNORE` the certificate's validity.", + Type: schema.TypeString, + }, + "enforce_mtls": { + Optional: true, + Description: "Specifies custom handling of requests if any of the checks in the `enforceMtlsSettings` behavior fail. Enable this and use with behaviors such as `logCustom` so that they execute if the check fails. You need to add the `enforceMtlsSettings` behavior to a parent rule, with its own unique match condition and `enableDenyRequest` option disabled.", + Type: schema.TypeBool, + }, + }, + }, + }, + "client_ip": { + Optional: true, + Type: schema.TypeList, + Description: "Matches the IP number of the requesting client. To use this condition to match end-user IP addresses, apply it together with the `requestType` matching on the `CLIENT_REQ` value. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS_ONE_OF", "IS_NOT_ONE_OF"}, false)), + Optional: true, + Description: "Matches the contents of `values` if set to `IS_ONE_OF`, otherwise `IS_NOT_ONE_OF` reverses the match.", + Type: schema.TypeString, + }, + "values": { + Optional: true, + Description: "IP or CIDR block, for example: `71.92.0.0/14`.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "use_headers": { + Optional: true, + Description: "When connecting via a proxy server as determined by the `X-Forwarded-For` header, enabling this option matches the connecting client's IP address rather than the original end client specified in the header.", + Type: schema.TypeBool, + }, + }, + }, + }, + "client_ip_version": { + Optional: true, + Type: schema.TypeList, + Description: "Matches the version of the IP protocol used by the requesting client. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "value": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IPV4", "IPV6"}, false)), + Optional: true, + Description: "The IP version of the client request, either `IPV4` or `IPV6`.", + Type: schema.TypeString, + }, + "use_x_forwarded_for": { + Optional: true, + Description: "When connecting via a proxy server as determined by the `X-Forwarded-For` header, enabling this option matches the connecting client's IP address rather than the original end client specified in the header.", + Type: schema.TypeBool, + }, + }, + }, + }, + "cloudlets_origin": { + Optional: true, + Type: schema.TypeList, + Description: "Allows Cloudlets Origins, referenced by label, to define their own criteria to assign custom origin definitions. The criteria may match, for example, for a specified percentage of requests defined by the cloudlet to use an alternative version of a website. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "origin_id": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-\\.]+$"), + Optional: true, + Description: "The Cloudlets Origins identifier, limited to alphanumeric and underscore characters.", + Type: schema.TypeString, + }, + }, + }, + }, + "content_delivery_network": { + Optional: true, + Type: schema.TypeList, + Description: "Specifies the type of Akamai network handling the request. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS", "IS_NOT"}, false)), + Optional: true, + Description: "Matches the specified `network` if set to `IS`, otherwise `IS_NOT` reverses the match.", + Type: schema.TypeString, + }, + "network": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"STAGING", "PRODUCTION"}, false)), + Optional: true, + Description: "Match the network.", + Type: schema.TypeString, + }, + }, + }, + }, + "content_type": { + Optional: true, + Type: schema.TypeList, + Description: "Matches the HTTP response header's `Content-Type`. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS_ONE_OF", "IS_NOT_ONE_OF"}, false)), + Optional: true, + Description: "Matches any `Content-Type` among specified `values` when set to `IS_ONE_OF`, otherwise `IS_NOT_ONE_OF` reverses the match.", + Type: schema.TypeString, + }, + "values": { + Optional: true, + Description: "`Content-Type` response header value, for example `text/html`.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "match_wildcard": { + Optional: true, + Description: "Allows wildcards in the `value` field, where `?` matches a single character and `*` matches zero or more characters. Specifying `text/*` matches both `text/html` and `text/css`.", + Type: schema.TypeBool, + }, + "match_case_sensitive": { + Optional: true, + Description: "Sets a case-sensitive match for all `values`.", + Type: schema.TypeBool, + }, + }, + }, + }, + "device_characteristic": { + Optional: true, + Type: schema.TypeList, + Description: "Match various aspects of the device or browser making the request. Based on the value of the `characteristic` option, the expected value is either a boolean, a number, or a string, possibly representing a version number. Each type of value requires a different field. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "characteristic": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"BRAND_NAME", "MODEL_NAME", "MARKETING_NAME", "IS_WIRELESS_DEVICE", "IS_TABLET", "DEVICE_OS", "DEVICE_OS_VERSION", "MOBILE_BROWSER", "MOBILE_BROWSER_VERSION", "RESOLUTION_WIDTH", "RESOLUTION_HEIGHT", "PHYSICAL_SCREEN_HEIGHT", "PHYSICAL_SCREEN_WIDTH", "COOKIE_SUPPORT", "AJAX_SUPPORT_JAVASCRIPT", "FULL_FLASH_SUPPORT", "ACCEPT_THIRD_PARTY_COOKIE", "XHTML_SUPPORT_LEVEL", "IS_MOBILE"}, false)), + Optional: true, + Description: "Aspect of the device or browser to match.", + Type: schema.TypeString, + }, + "string_match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"MATCHES_ONE_OF", "DOES_NOT_MATCH_ONE_OF"}, false)), + Optional: true, + Description: "When the `characteristic` expects a string value, set this to `MATCHES_ONE_OF` to match against the `stringValue` set, otherwise set to `DOES_NOT_MATCH_ONE_OF` to exclude that set of values.", + Type: schema.TypeString, + }, + "numeric_match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS", "IS_NOT", "IS_LESS_THAN", "IS_LESS_THAN_OR_EQUAL", "IS_MORE_THAN", "IS_MORE_THAN_OR_EQUAL"}, false)), + Optional: true, + Description: "When the `characteristic` expects a numeric value, compares the specified `numericValue` against the matched client.", + Type: schema.TypeString, + }, + "version_match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS", "IS_NOT", "IS_LESS_THAN", "IS_LESS_THAN_OR_EQUAL", "IS_MORE_THAN", "IS_MORE_THAN_OR_EQUAL"}, false)), + Optional: true, + Description: "When the `characteristic` expects a version string value, compares the specified `versionValue` against the matched client, using the following operators: `IS`, `IS_MORE_THAN_OR_EQUAL`, `IS_MORE_THAN`, `IS_LESS_THAN_OR_EQUAL`, `IS_LESS_THAN`, `IS_NOT`.", + Type: schema.TypeString, + }, + "boolean_value": { + Optional: true, + Description: "When the `characteristic` expects a boolean value, this specifies the value.", + Type: schema.TypeBool, + }, + "string_value": { + Optional: true, + Description: "When the `characteristic` expects a string, this specifies the set of values.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "numeric_value": { + Optional: true, + Description: "When the `characteristic` expects a numeric value, this specifies the number.", + Type: schema.TypeInt, + }, + "version_value": { + Optional: true, + Description: "When the `characteristic` expects a version number, this specifies it as a string.", + Type: schema.TypeString, + }, + "match_case_sensitive": { + Optional: true, + Description: "Sets a case-sensitive match for the `stringValue` field.", + Type: schema.TypeBool, + }, + "match_wildcard": { + Optional: true, + Description: "Allows wildcards in the `stringValue` field, where `?` matches a single character and `*` matches zero or more characters.", + Type: schema.TypeBool, + }, + }, + }, + }, + "ecmd_auth_groups": { + Optional: true, + Type: schema.TypeList, + Description: "This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CONTAINS", "DOES_NOT_CONTAIN"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "value": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_-]{1,255}$"), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "ecmd_auth_scheme": { + Optional: true, + Type: schema.TypeList, + Description: "This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "auth_scheme": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ANONYMOUS", "JWT", "MUTUAL"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "ecmd_is_authenticated": { + Optional: true, + Type: schema.TypeList, + Description: "This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS_AUTHENTICATED", "IS_NOT_AUTHENTICATED"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "ecmd_username": { + Optional: true, + Type: schema.TypeList, + Description: "This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CONTAINS", "DOES_NOT_CONTAIN", "STARTS_WITH", "DOES_NOT_START_WITH", "ENDS_WITH", "DOES_NOT_END_WITH", "LENGTH_EQUALS", "LENGTH_GREATER_THAN", "LENGTH_SMALLER_THAN"}, false)), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "value": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_-]{1,255}$"), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + "length": { + ValidateDiagFunc: validateRegexOrVariable("^[1-9]\\d*$"), + Optional: true, + Description: "", + Type: schema.TypeString, + }, + }, + }, + }, + "edge_workers_failure": { + Optional: true, + Type: schema.TypeList, + Description: "Checks the EdgeWorkers execution status and detects whether a customer's JavaScript failed on edge servers. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "exec_status": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"FAILURE", "SUCCESS"}, false)), + Optional: true, + Description: "Specify execution status.", + Type: schema.TypeString, + }, + }, + }, + }, + "file_extension": { + Optional: true, + Type: schema.TypeList, + Description: "Matches the requested filename's extension, if present. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS_ONE_OF", "IS_NOT_ONE_OF"}, false)), + Optional: true, + Description: "Matches the contents of `values` if set to `IS_ONE_OF`, otherwise `IS_NOT_ONE_OF` reverses the match.", + Type: schema.TypeString, + }, + "values": { + Optional: true, + Description: "An array of file extension strings, with no leading dot characters, for example `png`, `jpg`, `jpeg`, and `gif`.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "match_case_sensitive": { + Optional: true, + Description: "Sets a case-sensitive match.", + Type: schema.TypeBool, + }, + }, + }, + }, + "filename": { + Optional: true, + Type: schema.TypeList, + Description: "Matches the requested filename, or test whether it is present. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS_ONE_OF", "IS_NOT_ONE_OF", "IS_EMPTY", "IS_NOT_EMPTY"}, false)), + Optional: true, + Description: "If set to `IS_ONE_OF` or `IS_NOT_ONE_OF`, matches whether the filename matches one of the `values`. If set to `IS_EMPTY` or `IS_NOT_EMPTY`, matches whether the specified filename is part of the path.", + Type: schema.TypeString, + }, + "values": { + Optional: true, + Description: "Matches the filename component of the request URL. Allows wildcards, where `?` matches a single character and `*` matches zero or more characters. For example, specify `filename.*` to accept any extension.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "match_case_sensitive": { + Optional: true, + Description: "Sets a case-sensitive match for the `values` field.", + Type: schema.TypeBool, + }, + }, + }, + }, + "hostname": { + Optional: true, + Type: schema.TypeList, + Description: "Matches the requested hostname. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS_ONE_OF", "IS_NOT_ONE_OF"}, false)), + Optional: true, + Description: "Matches the contents of `values` when set to `IS_ONE_OF`, otherwise `IS_NOT_ONE_OF` reverses the match.", + Type: schema.TypeString, + }, + "values": { + Optional: true, + Description: "A list of hostnames. Allows wildcards, where `?` matches a single character and `*` matches zero or more characters. Specifying `*.example.com` matches both `m.example.com` and `www.example.com`.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "match_advanced": { + Optional: true, + Type: schema.TypeList, + Description: "This specifies match criteria using Akamai XML metadata. It can only be configured on your behalf by Akamai Professional Services. This criterion is for internal usage only. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "description": { + Optional: true, + Description: "A human-readable description of what the XML block does.", + Type: schema.TypeString, + }, + "open_xml": { + Optional: true, + Description: "An XML string that opens the relevant block.", + Type: schema.TypeString, + }, + "close_xml": { + Optional: true, + Description: "An XML string that closes the relevant block.", + Type: schema.TypeString, + }, + }, + }, + }, + "match_cp_code": { + Optional: true, + Type: schema.TypeList, + Description: "Match the assigned content provider code. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "value": { + Optional: true, + Description: "Specifies the CP code as an object. You only need to provide the initial `id` to match the CP code, stripping any `cpc_` prefix to pass the integer to the rule tree. Additional CP code details may reflect back in subsequent read-only data.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "id": { + Optional: true, + Description: "", + Type: schema.TypeInt, + }, + "name": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "created_date": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "description": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + "products": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "cp_code_limits": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeList, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "current_capacity": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeInt, + }, + "limit_type": { + Optional: true, + Description: "This field is only intended for export compatibility purposes, and modifying it will not impact your use of the behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + "match_response_code": { + Optional: true, + Type: schema.TypeList, + Description: "Match a set or range of HTTP response codes. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS_ONE_OF", "IS_NOT_ONE_OF", "IS_BETWEEN", "IS_NOT_BETWEEN"}, false)), + Optional: true, + Description: "Matches numeric range or a specified set of `values`.", + Type: schema.TypeString, + }, + "values": { + Optional: true, + Description: "A set of response codes to match, for example `[\"404\",\"500\"]`.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "lower_bound": { + ValidateDiagFunc: validateRegexOrVariable("^\\d{3}$"), + Optional: true, + Description: "Specifies the start of a range of responses. For example, `400` to match anything from `400` to `500`.", + Type: schema.TypeInt, + }, + "upper_bound": { + ValidateDiagFunc: validateRegexOrVariable("^\\d{3}$"), + Optional: true, + Description: "Specifies the end of a range of responses. For example, `500` to match anything from `400` to `500`.", + Type: schema.TypeInt, + }, + }, + }, + }, + "match_variable": { + Optional: true, + Type: schema.TypeList, + Description: "Matches a built-in variable, or a custom variable pre-declared within the rule tree by the `setVariable` behavior. See `Support for variables` for more information on this feature. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "variable_name": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z_][a-zA-Z0-9_]{0,31}$"), + Optional: true, + Description: "The name of the variable to match.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS", "IS_NOT", "IS_ONE_OF", "IS_NOT_ONE_OF", "IS_EMPTY", "IS_NOT_EMPTY", "IS_BETWEEN", "IS_NOT_BETWEEN", "IS_GREATER_THAN", "IS_GREATER_THAN_OR_EQUAL_TO", "IS_LESS_THAN", "IS_LESS_THAN_OR_EQUAL_TO"}, false)), + Optional: true, + Description: "The type of match, based on which you use different options to specify the match criteria.", + Type: schema.TypeString, + }, + "variable_values": { + Optional: true, + Description: "Specifies an array of matching strings.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "variable_expression": { + Optional: true, + Description: "Specifies a single matching string.", + Type: schema.TypeString, + }, + "lower_bound": { + ValidateDiagFunc: validateRegexOrVariable("^[1-9]\\d*$"), + Optional: true, + Description: "Specifies the range's numeric minimum value.", + Type: schema.TypeString, + }, + "upper_bound": { + ValidateDiagFunc: validateRegexOrVariable("^[1-9]\\d*$"), + Optional: true, + Description: "Specifies the range's numeric maximum value.", + Type: schema.TypeString, + }, + "match_wildcard": { + Optional: true, + Description: "When matching string expressions, enabling this allows wildcards, where `?` matches a single character and `*` matches zero or more characters.", + Type: schema.TypeBool, + }, + "match_case_sensitive": { + Optional: true, + Description: "When matching string expressions, enabling this performs a case-sensitive match.", + Type: schema.TypeBool, + }, + }, + }, + }, + "metadata_stage": { + Optional: true, + Type: schema.TypeList, + Description: "Matches how the current rule corresponds to low-level syntax elements in translated XML metadata, indicating progressive stages as each edge server handles the request and response. To use this match, you need to be thoroughly familiar with how Akamai edge servers process requests. Contact your Akamai Technical representative if you need help, and test thoroughly on staging before activating on production. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS", "IS_NOT"}, false)), + Optional: true, + Description: "Compares the current rule with the specified metadata stage.", + Type: schema.TypeString, + }, + "value": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"cache-hit", "client-done", "client-request", "client-request-body", "client-response", "content-policy", "forward-request", "forward-response", "forward-start", "ipa-response"}, false)), + Optional: true, + Description: "Specifies the metadata stage.", + Type: schema.TypeString, + }, + }, + }, + }, + "origin_timeout": { + Optional: true, + Type: schema.TypeList, + Description: "Matches when the origin responds with a timeout error. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"ORIGIN_TIMED_OUT"}, false)), + Optional: true, + Description: "Specifies a single required `ORIGIN_TIMED_OUT` value.", + Type: schema.TypeString, + }, + }, + }, + }, + "path": { + Optional: true, + Type: schema.TypeList, + Description: "Matches the URL's non-hostname path component. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"MATCHES_ONE_OF", "DOES_NOT_MATCH_ONE_OF"}, false)), + Optional: true, + Description: "Matches the contents of the `values` array.", + Type: schema.TypeString, + }, + "values": { + Optional: true, + Description: "Matches the URL path, excluding leading hostname and trailing query parameters. The path is relative to the server root, for example `/blog`. This field allows wildcards, where `?` matches a single character and `*` matches zero or more characters. For example, `/blog/*/2014` matches paths with two fixed segments and other varying segments between them.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "match_case_sensitive": { + Optional: true, + Description: "Sets a case-sensitive match.", + Type: schema.TypeBool, + }, + "normalize": { + Optional: true, + Description: "Transforms URLs before comparing them with the provided value. URLs are decoded, and any directory syntax such as `../..` or `//` is stripped as a security measure. This protects URL paths from being accessed by unauthorized users.", + Type: schema.TypeBool, + }, + }, + }, + }, + "query_string_parameter": { + Optional: true, + Type: schema.TypeList, + Description: "Matches query string field names or values. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "parameter_name": { + ValidateDiagFunc: validateRegexOrVariable("^[^:/?#\\[\\]@&]+$"), + Optional: true, + Description: "The name of the query field, for example, `q` in `?q=string`.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS_ONE_OF", "IS_NOT_ONE_OF", "EXISTS", "DOES_NOT_EXIST", "IS_LESS_THAN", "IS_MORE_THAN", "IS_BETWEEN"}, false)), + Optional: true, + Description: "Narrows the match criteria.", + Type: schema.TypeString, + }, + "values": { + Optional: true, + Description: "The value of the query field, for example, `string` in `?q=string`.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "lower_bound": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "Specifies the match's minimum value.", + Type: schema.TypeInt, + }, + "upper_bound": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "When the `value` is numeric, this field specifies the match's maximum value.", + Type: schema.TypeInt, + }, + "match_wildcard_name": { + Optional: true, + Description: "Allows wildcards in the `parameterName` field, where `?` matches a single character and `*` matches zero or more characters.", + Type: schema.TypeBool, + }, + "match_case_sensitive_name": { + Optional: true, + Description: "Sets a case-sensitive match for the `parameterName` field.", + Type: schema.TypeBool, + }, + "match_wildcard_value": { + Optional: true, + Description: "Allows wildcards in the `value` field, where `?` matches a single character and `*` matches zero or more characters.", + Type: schema.TypeBool, + }, + "match_case_sensitive_value": { + Optional: true, + Description: "Sets a case-sensitive match for the `value` field.", + Type: schema.TypeBool, + }, + "escape_value": { + Optional: true, + Description: "Matches when the `value` is URL-escaped.", + Type: schema.TypeBool, + }, + }, + }, + }, + "random": { + Optional: true, + Type: schema.TypeList, + Description: "Matches a specified percentage of requests. Use this match to apply behaviors to a percentage of your incoming requests that differ from the remainder, useful for A/b testing, or to offload traffic onto different servers. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "bucket": { + ValidateDiagFunc: validation.ToDiagFunc(validation.IntBetween(0, 100)), + Optional: true, + Description: "Specify a percentage of random requests to which to apply a behavior. Any remainders do not match.", + Type: schema.TypeInt, + }, + }, + }, + }, + "recovery_config": { + Optional: true, + Type: schema.TypeList, + Description: "Matches on specified origin recovery scenarios. The `originFailureRecoveryPolicy` behavior defines the scenarios that trigger the recovery or retry methods you set in the `originFailureRecoveryMethod` rule. If the origin fails, the system checks the name of the recovery method applied to your policy. It then either redirects the requesting client to a backup origin or returns predefined HTTP response codes. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "config_name": { + ValidateDiagFunc: validateRegexOrVariable("^[A-Z0-9-]+$"), + Optional: true, + Description: "A unique identifier used for origin failure recovery configurations. This is the recovery method configuration name you apply when setting origin failure recovery methods and scenarios in `originFailureRecoveryMethod` and `originFailureRecoveryPolicy` behaviors. The value can contain alphanumeric characters and dashes.", + Type: schema.TypeString, + }, + }, + }, + }, + "regular_expression": { + Optional: true, + Type: schema.TypeList, + Description: "Matches a regular expression against a string, especially to apply behaviors flexibly based on the contents of dynamic `variables`. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_string": { + Optional: true, + Description: "The string to match, typically the contents of a dynamic variable.", + Type: schema.TypeString, + }, + "regex": { + Optional: true, + Description: "The regular expression (PCRE) to match against the string.", + Type: schema.TypeString, + }, + "case_sensitive": { + Optional: true, + Description: "Sets a case-sensitive regular expression match.", + Type: schema.TypeBool, + }, + }, + }, + }, + "request_cookie": { + Optional: true, + Type: schema.TypeList, + Description: "Match the cookie name or value passed with the request. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "cookie_name": { + ValidateDiagFunc: validateRegexOrVariable("^[a-zA-Z0-9_\\-*\\.]+$"), + Optional: true, + Description: "The name of the cookie, for example, `visitor` in `visitor:anon`.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS", "IS_NOT", "EXISTS", "DOES_NOT_EXIST", "IS_BETWEEN"}, false)), + Optional: true, + Description: "Narrows the match criteria.", + Type: schema.TypeString, + }, + "value": { + ValidateDiagFunc: validateRegexOrVariable("^[^\\s;]+$"), + Optional: true, + Description: "The cookie's value, for example, `anon` in `visitor:anon`.", + Type: schema.TypeString, + }, + "lower_bound": { + ValidateDiagFunc: validateRegexOrVariable("^[1-9]\\d*$"), + Optional: true, + Description: "When the `value` is numeric, this field specifies the match's minimum value.", + Type: schema.TypeInt, + }, + "upper_bound": { + ValidateDiagFunc: validateRegexOrVariable("^[1-9]\\d*$"), + Optional: true, + Description: "When the `value` is numeric, this field specifies the match's maximum value.", + Type: schema.TypeInt, + }, + "match_wildcard_name": { + Optional: true, + Description: "Allows wildcards in the `cookieName` field, where `?` matches a single character and `*` matches zero or more characters.", + Type: schema.TypeBool, + }, + "match_case_sensitive_name": { + Optional: true, + Description: "Sets a case-sensitive match for the `cookieName` field.", + Type: schema.TypeBool, + }, + "match_wildcard_value": { + Optional: true, + Description: "Allows wildcards in the `value` field, where `?` matches a single character and `*` matches zero or more characters.", + Type: schema.TypeBool, + }, + "match_case_sensitive_value": { + Optional: true, + Description: "Sets a case-sensitive match for the `value` field.", + Type: schema.TypeBool, + }, + }, + }, + }, + "request_header": { + Optional: true, + Type: schema.TypeList, + Description: "Match HTTP header names or values. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "header_name": { + ValidateDiagFunc: validateRegexOrVariable("^[^()<>@,;:\\\"/\\[\\]?{}\\s]+$"), + Optional: true, + Description: "The name of the request header, for example `Accept-Language`.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS_ONE_OF", "IS_NOT_ONE_OF", "EXISTS", "DOES_NOT_EXIST"}, false)), + Optional: true, + Description: "Narrows the match criteria.", + Type: schema.TypeString, + }, + "values": { + Optional: true, + Description: "The request header's value, for example `en-US` when the header `headerName` is `Accept-Language`.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "match_wildcard_name": { + Optional: true, + Description: "Allows wildcards in the `headerName` field, where `?` matches a single character and `*` matches zero or more characters.", + Type: schema.TypeBool, + }, + "match_wildcard_value": { + Optional: true, + Description: "Allows wildcards in the `value` field, where `?` matches a single character and `*` matches zero or more characters.", + Type: schema.TypeBool, + }, + "match_case_sensitive_value": { + Optional: true, + Description: "Sets a case-sensitive match for the `value` field.", + Type: schema.TypeBool, + }, + }, + }, + }, + "request_method": { + Optional: true, + Type: schema.TypeList, + Description: "Specify the request's HTTP verb. Also supports WebDAV methods and common Akamai operations. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS", "IS_NOT"}, false)), + Optional: true, + Description: "Matches the `value` when set to `IS`, otherwise `IS_NOT` reverses the match.", + Type: schema.TypeString, + }, + "value": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"GET", "POST", "HEAD", "PUT", "PATCH", "HTTP_DELETE", "AKAMAI_TRANSLATE", "AKAMAI_PURGE", "OPTIONS", "DAV_ACL", "DAV_CHECKOUT", "DAV_COPY", "DAV_DMCREATE", "DAV_DMINDEX", "DAV_DMMKPATH", "DAV_DMMKPATHS", "DAV_DMOVERLAY", "DAV_DMPATCHPATHS", "DAV_LOCK", "DAV_MKCALENDAR", "DAV_MKCOL", "DAV_MOVE", "DAV_PROPFIND", "DAV_PROPPATCH", "DAV_REPORT", "DAV_SETPROCESS", "DAV_SETREDIRECT", "DAV_TRUTHGET", "DAV_UNLOCK"}, false)), + Optional: true, + Description: "Any of these HTTP methods, WebDAV methods, or Akamai operations.", + Type: schema.TypeString, + }, + }, + }, + }, + "request_protocol": { + Optional: true, + Type: schema.TypeList, + Description: "Matches whether the request uses the HTTP or HTTPS protocol. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "value": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"HTTP", "HTTPS"}, false)), + Optional: true, + Description: "Specifies the protocol.", + Type: schema.TypeString, + }, + }, + }, + }, + "request_type": { + Optional: true, + Type: schema.TypeList, + Description: "Matches the basic type of request. To use this match, you need to be thoroughly familiar with how Akamai edge servers process requests. Contact your Akamai Technical representative if you need help, and test thoroughly on staging before activating on production. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS", "IS_NOT"}, false)), + Optional: true, + Description: "Specifies whether the request `IS` or `IS_NOT` the type of specified `value`.", + Type: schema.TypeString, + }, + "value": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"CLIENT_REQ", "ESI_FRAGMENT", "EW_SUBREQUEST"}, false)), + Optional: true, + Description: "Specifies the type of request, either a standard `CLIENT_REQ`, an `ESI_FRAGMENT`, or an `EW_SUBREQUEST`.", + Type: schema.TypeString, + }, + }, + }, + }, + "response_header": { + Optional: true, + Type: schema.TypeList, + Description: "Match HTTP header names or values. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "header_name": { + ValidateDiagFunc: validateRegexOrVariable("^[^()<>@,;:\\\"/\\[\\]?{}\\s]+$"), + Optional: true, + Description: "The name of the response header, for example `Content-Type`.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS_ONE_OF", "IS_NOT_ONE_OF", "EXISTS", "DOES_NOT_EXIST", "IS_LESS_THAN", "IS_MORE_THAN", "IS_BETWEEN"}, false)), + Optional: true, + Description: "Narrows the match according to various criteria.", + Type: schema.TypeString, + }, + "values": { + Optional: true, + Description: "The response header's value, for example `application/x-www-form-urlencoded` when the header `headerName` is `Content-Type`.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "lower_bound": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "When the `value` is numeric, this field specifies the match's minimum value.", + Type: schema.TypeInt, + }, + "upper_bound": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+$"), + Optional: true, + Description: "When the `value` is numeric, this field specifies the match's maximum value.", + Type: schema.TypeInt, + }, + "match_wildcard_name": { + Optional: true, + Description: "Allows wildcards in the `headerName` field, where `?` matches a single character and `*` matches zero or more characters.", + Type: schema.TypeBool, + }, + "match_wildcard_value": { + Optional: true, + Description: "Allows wildcards in the `value` field, where `?` matches a single character and `*` matches zero or more characters.", + Type: schema.TypeBool, + }, + "match_case_sensitive_value": { + Optional: true, + Description: "When enabled, the match is case-sensitive for the `value` field.", + Type: schema.TypeBool, + }, + }, + }, + }, + "server_location": { + Optional: true, + Type: schema.TypeList, + Description: "The location of the Akamai server handling the request. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "location_type": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"COUNTRY", "CONTINENT", "REGION"}, false)), + Optional: true, + Description: "Indicates the geographic scope.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS_ONE_OF", "IS_NOT_ONE_OF"}, false)), + Optional: true, + Description: "Matches the specified set of values when set to `IS_ONE_OF`, otherwise `IS_NOT_ONE_OF` reverses the match.", + Type: schema.TypeString, + }, + "countries": { + Optional: true, + Description: "ISO 3166-1 country codes, such as `US` or `CN`.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "continents": { + Optional: true, + Description: "Continent codes.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "regions": { + Optional: true, + Description: "ISO 3166 country and region codes, for example `US:MA` for Massachusetts or `JP:13` for Tokyo.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "time": { + Optional: true, + Type: schema.TypeList, + Description: "Specifies ranges of times during which the request occurred. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"BEGINNING", "BETWEEN", "LASTING", "REPEATING"}, false)), + Optional: true, + Description: "Specifies how to define the range of time.", + Type: schema.TypeString, + }, + "repeat_interval": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Sets the time between each repeating time period's starting points.", + Type: schema.TypeString, + }, + "repeat_duration": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Sets the duration of each repeating time period.", + Type: schema.TypeString, + }, + "lasting_duration": { + ValidateDiagFunc: validateRegexOrVariable("^[0-9]+[DdHhMmSs]$"), + Optional: true, + Description: "Specifies the end of a time period as a duration relative to the `lastingDate`.", + Type: schema.TypeString, + }, + "lasting_date": { + Optional: true, + Description: "Sets the start of a fixed time period.", + Type: schema.TypeString, + }, + "repeat_begin_date": { + Optional: true, + Description: "Sets the start of the initial time period.", + Type: schema.TypeString, + }, + "apply_daylight_savings_time": { + Optional: true, + Description: "Adjusts the start time plus repeat interval to account for daylight saving time. Applies when the current time and the start time use different systems, daylight and standard, and the two values are in conflict.", + Type: schema.TypeBool, + }, + "begin_date": { + Optional: true, + Description: "Sets the start of a time period.", + Type: schema.TypeString, + }, + "end_date": { + Optional: true, + Description: "Sets the end of a fixed time period.", + Type: schema.TypeString, + }, + }, + }, + }, + "token_authorization": { + Optional: true, + Type: schema.TypeList, + Description: "Match on Auth Token 2.0 verification results. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS_SUCCESS", "IS_CUSTOM_FAILURE", "IS_ANY_FAILURE"}, false)), + Optional: true, + Description: "Error match scope.", + Type: schema.TypeString, + }, + "status_list": { + Optional: true, + Description: "", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "user_agent": { + Optional: true, + Type: schema.TypeList, + Description: "Matches the user agent string that helps identify the client browser and device. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS_ONE_OF", "IS_NOT_ONE_OF"}, false)), + Optional: true, + Description: "Matches the specified set of `values` when set to `IS_ONE_OF`, otherwise `IS_NOT_ONE_OF` reverses the match.", + Type: schema.TypeString, + }, + "values": { + Optional: true, + Description: "The `User-Agent` header's value. For example, `Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1)`.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "match_wildcard": { + Optional: true, + Description: "Allows wildcards in the `value` field, where `?` matches a single character and `*` matches zero or more characters. For example, `*Android*`, `*iPhone5*`, `*Firefox*`, or `*Chrome*` allow substring matches.", + Type: schema.TypeBool, + }, + "match_case_sensitive": { + Optional: true, + Description: "Sets a case-sensitive match for the `value` field.", + Type: schema.TypeBool, + }, + }, + }, + }, + "user_location": { + Optional: true, + Type: schema.TypeList, + Description: "The client browser's approximate geographic location, determined by looking up the IP address in a database. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "field": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"COUNTRY", "CONTINENT", "REGION"}, false)), + Optional: true, + Description: "Indicates the geographic scope.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS_ONE_OF", "IS_NOT_ONE_OF"}, false)), + Optional: true, + Description: "Matches the specified set of values when set to `IS_ONE_OF`, otherwise `IS_NOT_ONE_OF` reverses the match.", + Type: schema.TypeString, + }, + "country_values": { + Optional: true, + Description: "ISO 3166-1 country codes, such as `US` or `CN`.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "continent_values": { + Optional: true, + Description: "Continent codes.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "region_values": { + Optional: true, + Description: "ISO 3166 country and region codes, for example `US:MA` for Massachusetts or `JP:13` for Tokyo.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "check_ips": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"BOTH", "CONNECTING", "HEADERS"}, false)), + Optional: true, + Description: "Specifies which IP addresses determine the user's location.", + Type: schema.TypeString, + }, + "use_only_first_x_forwarded_for_ip": { + Optional: true, + Description: "When connecting via a proxy server as determined by the `X-Forwarded-For` header, enabling this option matches the end client specified in the header. Disabling it matches the connecting client's IP address.", + Type: schema.TypeBool, + }, + }, + }, + }, + "user_network": { + Optional: true, + Type: schema.TypeList, + Description: "Matches details of the network over which the request was made, determined by looking up the IP address in a database. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "field": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"NETWORK", "NETWORK_TYPE", "BANDWIDTH"}, false)), + Optional: true, + Description: "The type of information to match.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS_ONE_OF", "IS_NOT_ONE_OF"}, false)), + Optional: true, + Description: "Matches the specified set of values when set to `IS_ONE_OF`, otherwise `IS_NOT_ONE_OF` reverses the match.", + Type: schema.TypeString, + }, + "network_type_values": { + Optional: true, + Description: "", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "network_values": { + Optional: true, + Description: "Any set of specific networks.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "bandwidth_values": { + Optional: true, + Description: "Bandwidth range in bits per second, either `1`, `57`, `257`, `1000`, `2000`, or `5000`.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "check_ips": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"BOTH", "CONNECTING", "HEADERS"}, false)), + Optional: true, + Description: "Specifies which IP addresses determine the user's network.", + Type: schema.TypeString, + }, + "use_only_first_x_forwarded_for_ip": { + Optional: true, + Description: "When connecting via a proxy server as determined by the `X-Forwarded-For` header, enabling this option matches the end client specified in the header. Disabling it matches the connecting client's IP address.", + Type: schema.TypeBool, + }, + }, + }, + }, + "variable_error": { + Optional: true, + Type: schema.TypeList, + Description: "Matches any runtime errors that occur on edge servers based on the configuration of a `setVariable` behavior. See `Support for variables` section for more information on this feature. This criterion can be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "result": { + Optional: true, + Description: "Matches errors for the specified set of `variableNames`, otherwise matches errors from variables outside that set.", + Type: schema.TypeBool, + }, + "variable_names": { + Optional: true, + Description: "The name of the variable whose error triggers the match, or a space- or comma-delimited list of more than one variable name. Note that if you define a variable named `VAR`, the name in this field needs to appear with its added prefix as `PMUSER_VAR`. When such a variable is inserted into other fields, it appears with an additional namespace as `{{user.PMUSER_VAR}}`. See the `setVariable` behavior for details on variable names.", + Type: schema.TypeList, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + }, + }, + }, + "virtual_waiting_room_request": { + Optional: true, + Type: schema.TypeList, + Description: "Helps to customize the requests identified by the `virtualWaitingRoom` behavior. Use this match criteria to define the `originServer` behavior for the waiting room. This criterion cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS", "IS_NOT"}, false)), + Optional: true, + Description: "Specifies the match's logic.", + Type: schema.TypeString, + }, + "match_on": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"WR_ANY", "WR_MAIN_PAGE", "WR_ASSETS"}, false)), + Optional: true, + Description: "Specifies the type of request identified by the `virtualWaitingRoom` behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + "visitor_prioritization_request": { + Optional: true, + Type: schema.TypeList, + Description: "Helps to customize the requests identified by the `visitorPrioritizationFifo` behavior. The basic use case for this match criteria is to define the `originServer` behavior for the waiting room. This criterion cannot be used in includes.", + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "locked": { + Optional: true, + Description: "Indicates that your Akamai representative has locked this behavior or criteria so that you can't modify it. This option is for internal usage only.", + Type: schema.TypeBool, + }, + "uuid": { + ValidateDiagFunc: validateRegex("^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"), + Optional: true, + Description: "A uuid member indicates that at least one of its component behaviors or criteria is advanced and read-only. You need to preserve this uuid as well when modifying the rule tree. This option is for internal usage only.", + Type: schema.TypeString, + }, + "template_uuid": { + Optional: true, + Description: "This option is for internal usage only.", + Type: schema.TypeString, + }, + "match_operator": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"IS", "IS_NOT"}, false)), + Optional: true, + Description: "Specifies the match's logic.", + Type: schema.TypeString, + }, + "match_on": { + ValidateDiagFunc: validation.ToDiagFunc(validation.StringInSlice([]string{"WR_ANY", "WR_MAIN_PAGE", "WR_ASSETS"}, false)), + Optional: true, + Description: "Specifies the type of request identified by the `visitorPrioritizationFifo` behavior.", + Type: schema.TypeString, + }, + }, + }, + }, + } +} diff --git a/pkg/providers/property/testdata/TestDSPropertyRulesBuilder/content_compression_v2024_01_09.json b/pkg/providers/property/testdata/TestDSPropertyRulesBuilder/content_compression_v2024_01_09.json new file mode 100755 index 000000000..a923a06b5 --- /dev/null +++ b/pkg/providers/property/testdata/TestDSPropertyRulesBuilder/content_compression_v2024_01_09.json @@ -0,0 +1,68 @@ +{ + "_ruleFormat_": "rules_v2024_01_09", + "rules": { + "behaviors": [ + { + "name": "cpCode", + "options": { + "value": { + "cpCodeLimits": { + "currentCapacity": -143, + "limit": 100, + "limitType": "global" + }, + "createdDate": 1678276597000, + "description": "papi.declarativ.test.ipqa", + "id": 1048126, + "name": "papi.declarativ.test.ipqa", + "products": [ + "Fresca" + ] + } + } + }, + { + "name": "gzipResponse", + "options": { + "behavior": "ALWAYS" + } + } + ], + "criteria": [ + { + "name": "contentType", + "options": { + "matchCaseSensitive": false, + "matchOperator": "IS_ONE_OF", + "matchWildcard": true, + "values": [ + "text/*", + "application/javascript", + "application/x-javascript", + "application/x-javascript*", + "application/json", + "application/x-json", + "application/*+json", + "application/*+xml", + "application/text", + "application/vnd.microsoft.icon", + "application/vnd-ms-fontobject", + "application/x-font-ttf", + "application/x-font-opentype", + "application/x-font-truetype", + "application/xmlfont/eot", + "application/xml", + "font/opentype", + "font/otf", + "font/eot", + "image/svg+xml", + "image/vnd.microsoft.icon" + ] + } + } + ], + "name": "Content Compression", + "options": {}, + "criteriaMustSatisfy": "all" + } +} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestDSPropertyRulesBuilder/default_v2024_01_09.json b/pkg/providers/property/testdata/TestDSPropertyRulesBuilder/default_v2024_01_09.json new file mode 100755 index 000000000..c13fc21b3 --- /dev/null +++ b/pkg/providers/property/testdata/TestDSPropertyRulesBuilder/default_v2024_01_09.json @@ -0,0 +1,373 @@ +{ + "_ruleFormat_": "rules_v2024_01_09", + "rules": { + "advancedOverride": "test", + "behaviors": [ + { + "name": "contentCharacteristicsAMD", + "options": { + "catalogSize": "SMALL", + "contentType": "ULTRA_HD", + "dash": true, + "hds": true, + "hls": true, + "popularityDistribution": "UNKNOWN", + "segmentDurationDASH": "SEGMENT_DURATION_10S", + "segmentDurationDASHCustom": 100, + "segmentDurationHDS": "SEGMENT_DURATION_2S", + "segmentDurationHDSCustom": 100, + "segmentDurationHLS": "SEGMENT_DURATION_4S", + "segmentDurationHLSCustom": 3.14, + "segmentDurationSmooth": "SEGMENT_DURATION_8S", + "segmentDurationSmoothCustom": 3.14, + "segmentSizeDASH": "GREATER_THAN_100MB", + "segmentSizeHDS": "TEN_MB_TO_100_MB", + "segmentSizeHLS": "GREATER_THAN_100MB", + "segmentSizeSmooth": "UNKNOWN", + "smooth": true + } + }, + { + "name": "origin", + "options": { + "cacheKeyHostname": "ORIGIN_HOSTNAME", + "compress": true, + "customCertificates": [ + { + "canBeCA": false, + "canBeLeaf": true, + "issuerRDNs": { + "C": "US", + "CN": "DigiCert TLS RSA SHA256 2020 CA1", + "O": "DigiCert Inc" + } + } + ], + "enableTrueClientIp": true, + "forwardHostHeader": "REQUEST_HOST_HEADER", + "httpPort": 80, + "httpsPort": 443, + "originSni": true, + "originType": "CUSTOMER", + "trueClientIpClientSetting": false, + "trueClientIpHeader": "True-Client-IP", + "useUniqueCacheKey": false, + "verificationMode": "PLATFORM_SETTINGS" + } + }, + { + "name": "adScalerCircuitBreaker", + "options": { + "returnErrorResponseCodeBased": 502 + } + }, + { + "name": "applicationLoadBalancer", + "options": { + "allDownNetStorage": { + "cpCode": 123, + "downloadDomainName": "test" + }, + "failoverOriginMap": [ + { + "fromOriginId": "123" + } + ] + } + }, + { + "name": "apiPrioritization", + "options": { + "cloudletPolicy": { + "id": 1337, + "name": "test" + } + } + }, + { + "name": "caching", + "options": { + "behavior": "NO_STORE" + } + }, + { + "name": "sureRoute", + "options": { + "enabled": true, + "forceSslForward": false, + "raceStatTtl": "30m", + "toHostStatus": "INCOMING_HH", + "type": "PERFORMANCE" + } + }, + { + "name": "tieredDistribution", + "options": { + "enabled": true, + "tieredDistributionMap": "CH2" + } + }, + { + "name": "prefetch", + "options": { + "enabled": true + } + }, + { + "name": "allowPost", + "options": { + "allowWithoutContentLength": false, + "enabled": true + } + }, + { + "name": "cpCode", + "options": { + "value": { + "createdDate": 1678276597000, + "description": "papi.declarativ.test.ipqa", + "id": 1048126, + "name": "papi.declarativ.test.ipqa", + "products": [ + "Fresca" + ] + } + } + }, + { + "name": "report", + "options": { + "logAcceptLanguage": false, + "logCookies": "OFF", + "logCustomLogField": false, + "logEdgeIP": false, + "logHost": false, + "logReferer": false, + "logUserAgent": true, + "logXForwardedFor": false + } + }, + { + "name": "mPulse", + "options": { + "apiKey": "", + "bufferSize": "", + "configOverride": "\n", + "enabled": true, + "loaderVersion": "V12", + "requirePci": false + } + } + ], + "children": [ + { + "behaviors": [ + { + "name": "cpCode", + "options": { + "value": { + "cpCodeLimits": { + "currentCapacity": -143, + "limit": 100, + "limitType": "global" + }, + "createdDate": 1678276597000, + "description": "papi.declarativ.test.ipqa", + "id": 1048126, + "name": "papi.declarativ.test.ipqa", + "products": [ + "Fresca" + ] + } + } + }, + { + "name": "gzipResponse", + "options": { + "behavior": "ALWAYS" + } + } + ], + "criteria": [ + { + "name": "contentType", + "options": { + "matchCaseSensitive": false, + "matchOperator": "IS_ONE_OF", + "matchWildcard": true, + "values": [ + "text/*", + "application/javascript", + "application/x-javascript", + "application/x-javascript*", + "application/json", + "application/x-json", + "application/*+json", + "application/*+xml", + "application/text", + "application/vnd.microsoft.icon", + "application/vnd-ms-fontobject", + "application/x-font-ttf", + "application/x-font-opentype", + "application/x-font-truetype", + "application/xmlfont/eot", + "application/xml", + "font/opentype", + "font/otf", + "font/eot", + "image/svg+xml", + "image/vnd.microsoft.icon" + ] + } + } + ], + "name": "Content Compression", + "options": {}, + "criteriaMustSatisfy": "all" + }, + { + "behaviors": [ + { + "name": "caching", + "options": { + "behavior": "MAX_AGE", + "mustRevalidate": false, + "ttl": "1d" + } + }, + { + "name": "prefetch", + "options": { + "enabled": false + } + }, + { + "name": "prefetchable", + "options": { + "enabled": true + } + } + ], + "criteria": [ + { + "name": "fileExtension", + "options": { + "matchCaseSensitive": false, + "matchOperator": "IS_ONE_OF", + "values": [ + "aif", + "aiff", + "au", + "avi", + "bin", + "bmp", + "cab", + "carb", + "cct", + "cdf", + "class", + "css", + "doc", + "dcr", + "dtd", + "exe", + "flv", + "gcf", + "gff", + "gif", + "grv", + "hdml", + "hqx", + "ico", + "ini", + "jpeg", + "jpg", + "js", + "mov", + "mp3", + "nc", + "pct", + "pdf", + "png", + "ppc", + "pws", + "swa", + "swf", + "txt", + "vbs", + "w32", + "wav", + "wbmp", + "wml", + "wmlc", + "wmls", + "wmlsc", + "xsd", + "zip", + "webp", + "jxr", + "hdp", + "wdp", + "pict", + "tif", + "tiff", + "mid", + "midi", + "ttf", + "eot", + "woff", + "woff2", + "otf", + "svg", + "svgz", + "webp", + "jxr", + "jar", + "jp2" + ] + } + } + ], + "name": "Static Content", + "options": {}, + "criteriaMustSatisfy": "all" + }, + { + "behaviors": [ + { + "name": "downstreamCache", + "options": { + "behavior": "TUNNEL_ORIGIN" + } + }, + { + "name": "restrictObjectCaching", + "options": {} + } + ], + "criteria": [ + { + "name": "cacheability", + "options": { + "matchOperator": "IS_NOT", + "value": "CACHEABLE" + } + } + ], + "name": "Dynamic Content", + "options": {}, + "criteriaMustSatisfy": "all" + } + ], + "comments": "test", + "customOverride": { + "name": "test", + "overrideId": "test" + }, + "name": "default", + "options": {}, + "uuid": "test", + "templateUuid": "test", + "templateLink": "test" + } +} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestDSPropertyRulesBuilder/dynamic_content_v2024_01_09.json b/pkg/providers/property/testdata/TestDSPropertyRulesBuilder/dynamic_content_v2024_01_09.json new file mode 100755 index 000000000..84cbc6c97 --- /dev/null +++ b/pkg/providers/property/testdata/TestDSPropertyRulesBuilder/dynamic_content_v2024_01_09.json @@ -0,0 +1,29 @@ +{ + "_ruleFormat_": "rules_v2024_01_09", + "rules": { + "behaviors": [ + { + "name": "downstreamCache", + "options": { + "behavior": "TUNNEL_ORIGIN" + } + }, + { + "name": "restrictObjectCaching", + "options": {} + } + ], + "criteria": [ + { + "name": "cacheability", + "options": { + "matchOperator": "IS_NOT", + "value": "CACHEABLE" + } + } + ], + "name": "Dynamic Content", + "options": {}, + "criteriaMustSatisfy": "all" + } +} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestDSPropertyRulesBuilder/rules_v2024_01_09.tf b/pkg/providers/property/testdata/TestDSPropertyRulesBuilder/rules_v2024_01_09.tf new file mode 100644 index 000000000..3a8ad94b0 --- /dev/null +++ b/pkg/providers/property/testdata/TestDSPropertyRulesBuilder/rules_v2024_01_09.tf @@ -0,0 +1,268 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +data "akamai_property_rules_builder" "default" { + rules_v2024_01_09 { + name = "default" + is_secure = false + custom_override { + name = "test" + override_id = "test" + } + advanced_override = "test" + comments = "test" + uuid = "test" + template_uuid = "test" + template_link = "test" + + behavior { + content_characteristics_amd { + catalog_size = "SMALL" + content_type = "ULTRA_HD" + dash = true + hds = true + hls = true + popularity_distribution = "UNKNOWN" + segment_duration_dash = "SEGMENT_DURATION_10S" + segment_duration_dash_custom = 100 + segment_duration_hds = "SEGMENT_DURATION_2S" + segment_duration_hds_custom = 100 + segment_duration_hls = "SEGMENT_DURATION_4S" + segment_duration_hls_custom = 3.14 + segment_duration_smooth = "SEGMENT_DURATION_8S" + segment_duration_smooth_custom = 3.14 + segment_size_dash = "GREATER_THAN_100MB" + segment_size_hds = "TEN_MB_TO_100_MB" + segment_size_hls = "GREATER_THAN_100MB" + segment_size_smooth = "UNKNOWN" + smooth = true + } + } + behavior { + origin { + cache_key_hostname = "ORIGIN_HOSTNAME" + compress = true + enable_true_client_ip = true + forward_host_header = "REQUEST_HOST_HEADER" + http_port = 80 + https_port = 443 + origin_sni = true + origin_type = "CUSTOMER" + true_client_ip_client_setting = false + true_client_ip_header = "True-Client-IP" + use_unique_cache_key = false + verification_mode = "PLATFORM_SETTINGS" + custom_certificates { + can_be_ca = false + can_be_leaf = true + issuer_rdns { + c = "US" + cn = "DigiCert TLS RSA SHA256 2020 CA1" + o = "DigiCert Inc" + } + } + } + } + behavior { + ad_scaler_circuit_breaker { + return_error_response_code_based = "502" + } + } + behavior { + application_load_balancer { + all_down_net_storage { + cp_code = 123 + download_domain_name = "test" + } + failover_origin_map { + from_origin_id = "123" + + } + } + } + behavior { + api_prioritization { + cloudlet_policy { + id = 1337 + name = "test" + } + } + } + + behavior { + caching { + behavior = "NO_STORE" + } + } + + behavior { + sure_route { + enabled = true + force_ssl_forward = false + race_stat_ttl = "30m" + to_host_status = "INCOMING_HH" + type = "PERFORMANCE" + } + } + + behavior { + tiered_distribution { + enabled = true + tiered_distribution_map = "CH2" + } + } + + behavior { + prefetch { + enabled = true + } + } + + behavior { + allow_post { + allow_without_content_length = false + enabled = true + } + } + behavior { + cp_code { + value { + created_date = 1678276597000 + description = "papi.declarativ.test.ipqa" + id = 1048126 + name = "papi.declarativ.test.ipqa" + products = ["Fresca", ] + } + } + } + behavior { + report { + log_accept_language = false + log_cookies = "OFF" + log_custom_log_field = false + log_edge_ip = false + log_host = false + log_referer = false + log_user_agent = true + log_x_forwarded_for = false + } + } + + behavior { + m_pulse { + api_key = "" + buffer_size = "" + config_override = <<-EOT + +EOT + enabled = true + loader_version = "V12" + require_pci = false + + } + } + children = [ + data.akamai_property_rules_builder.content_compression.json, + data.akamai_property_rules_builder.static_content.json, + data.akamai_property_rules_builder.dynamic_content.json, + ] + } +} + +data "akamai_property_rules_builder" "content_compression" { + rules_v2024_01_09 { + name = "Content Compression" + criteria_must_satisfy = "all" + criterion { + content_type { + match_case_sensitive = false + match_operator = "IS_ONE_OF" + match_wildcard = true + values = ["text/*", "application/javascript", "application/x-javascript", "application/x-javascript*", "application/json", "application/x-json", "application/*+json", "application/*+xml", "application/text", "application/vnd.microsoft.icon", "application/vnd-ms-fontobject", "application/x-font-ttf", "application/x-font-opentype", "application/x-font-truetype", "application/xmlfont/eot", "application/xml", "font/opentype", "font/otf", "font/eot", "image/svg+xml", "image/vnd.microsoft.icon", ] + } + } + behavior { + cp_code { + value { + created_date = 1678276597000 + description = "papi.declarativ.test.ipqa" + id = 1048126 + name = "papi.declarativ.test.ipqa" + products = ["Fresca", ] + cp_code_limits { + current_capacity = -143 + limit = 100 + limit_type = "global" + } + } + } + } + behavior { + gzip_response { + behavior = "ALWAYS" + } + } + children = [ + ] + } +} + +data "akamai_property_rules_builder" "static_content" { + rules_v2024_01_09 { + name = "Static Content" + criteria_must_satisfy = "all" + criterion { + file_extension { + match_case_sensitive = false + match_operator = "IS_ONE_OF" + values = ["aif", "aiff", "au", "avi", "bin", "bmp", "cab", "carb", "cct", "cdf", "class", "css", "doc", "dcr", "dtd", "exe", "flv", "gcf", "gff", "gif", "grv", "hdml", "hqx", "ico", "ini", "jpeg", "jpg", "js", "mov", "mp3", "nc", "pct", "pdf", "png", "ppc", "pws", "swa", "swf", "txt", "vbs", "w32", "wav", "wbmp", "wml", "wmlc", "wmls", "wmlsc", "xsd", "zip", "webp", "jxr", "hdp", "wdp", "pict", "tif", "tiff", "mid", "midi", "ttf", "eot", "woff", "woff2", "otf", "svg", "svgz", "webp", "jxr", "jar", "jp2", ] + } + } + behavior { + caching { + behavior = "MAX_AGE" + must_revalidate = false + ttl = "1d" + } + } + behavior { + prefetch { + enabled = false + } + } + behavior { + prefetchable { + enabled = true + } + } + children = [ + ] + } +} + +data "akamai_property_rules_builder" "dynamic_content" { + rules_v2024_01_09 { + name = "Dynamic Content" + criteria_must_satisfy = "all" + criterion { + cacheability { + match_operator = "IS_NOT" + value = "CACHEABLE" + } + } + behavior { + downstream_cache { + behavior = "TUNNEL_ORIGIN" + } + } + + behavior { + restrict_object_caching {} + } + + children = [ + ] + } +} + diff --git a/pkg/providers/property/testdata/TestDSPropertyRulesBuilder/static_content_v2024_01_09.json b/pkg/providers/property/testdata/TestDSPropertyRulesBuilder/static_content_v2024_01_09.json new file mode 100755 index 000000000..5b9e980f6 --- /dev/null +++ b/pkg/providers/property/testdata/TestDSPropertyRulesBuilder/static_content_v2024_01_09.json @@ -0,0 +1,110 @@ +{ + "_ruleFormat_": "rules_v2024_01_09", + "rules": { + "behaviors": [ + { + "name": "caching", + "options": { + "behavior": "MAX_AGE", + "mustRevalidate": false, + "ttl": "1d" + } + }, + { + "name": "prefetch", + "options": { + "enabled": false + } + }, + { + "name": "prefetchable", + "options": { + "enabled": true + } + } + ], + "criteria": [ + { + "name": "fileExtension", + "options": { + "matchCaseSensitive": false, + "matchOperator": "IS_ONE_OF", + "values": [ + "aif", + "aiff", + "au", + "avi", + "bin", + "bmp", + "cab", + "carb", + "cct", + "cdf", + "class", + "css", + "doc", + "dcr", + "dtd", + "exe", + "flv", + "gcf", + "gff", + "gif", + "grv", + "hdml", + "hqx", + "ico", + "ini", + "jpeg", + "jpg", + "js", + "mov", + "mp3", + "nc", + "pct", + "pdf", + "png", + "ppc", + "pws", + "swa", + "swf", + "txt", + "vbs", + "w32", + "wav", + "wbmp", + "wml", + "wmlc", + "wmls", + "wmlsc", + "xsd", + "zip", + "webp", + "jxr", + "hdp", + "wdp", + "pict", + "tif", + "tiff", + "mid", + "midi", + "ttf", + "eot", + "woff", + "woff2", + "otf", + "svg", + "svgz", + "webp", + "jxr", + "jar", + "jp2" + ] + } + } + ], + "name": "Static Content", + "options": {}, + "criteriaMustSatisfy": "all" + } +} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestResProperty/Importable/importable-with-bootstrap.tf b/pkg/providers/property/testdata/TestResProperty/Importable/importable-with-bootstrap.tf new file mode 100644 index 000000000..ee398fb64 --- /dev/null +++ b/pkg/providers/property/testdata/TestResProperty/Importable/importable-with-bootstrap.tf @@ -0,0 +1,17 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_property" "test" { + name = "test_property" + group_id = "grp_0" + contract_id = "ctr_0" + product_id = "prd_0" + property_id = "prp_0" + + hostnames { + cname_to = "to.test.domain" + cname_from = "from.test.domain" + cert_provisioning_type = "DEFAULT" + } +} diff --git a/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/01_02_rules.json b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/01_02_rules.json new file mode 100644 index 000000000..dd206a3be --- /dev/null +++ b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/01_02_rules.json @@ -0,0 +1,16 @@ +{ + "rules": { + "name": "default", + "behaviors": [ + { + "name": "caching", + "options": { + "behavior": "MAX_AGE", + "mustRevalidate": false, + "ttl": "12d" + } + } + ] + }, + "comments": "Rules_01_02" +} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/01_expected_rules.json b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/01_expected_rules.json new file mode 100644 index 000000000..1365913b8 --- /dev/null +++ b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/01_expected_rules.json @@ -0,0 +1 @@ +{"comments":"lifecycleTest","rules":{"behaviors":[{"name":"caching","options":{"behavior":"MAX_AGE","mustRevalidate":false,"ttl":"12d"}}],"name":"default","options":{}}} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/01_with_notes_and_comments.tf b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/01_with_notes_and_comments.tf new file mode 100644 index 000000000..1a17c58cb --- /dev/null +++ b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/01_with_notes_and_comments.tf @@ -0,0 +1,14 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_property" "test" { + name = "test_property" + contract_id = "ctr_123" + group_id = "grp_123" + product_id = "prd_123" + + version_notes = "lifecycleTest" + rule_format = "v2023-01-05" + rules = file("testdata/TestResProperty/Lifecycle/versionNotes/01_02_rules.json") +} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/02_update_notes_no_diff.tf b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/02_update_notes_no_diff.tf new file mode 100644 index 000000000..867264674 --- /dev/null +++ b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/02_update_notes_no_diff.tf @@ -0,0 +1,14 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_property" "test" { + name = "test_property" + contract_id = "ctr_123" + group_id = "grp_123" + product_id = "prd_123" + + version_notes = "updatedNotes" + rule_format = "v2023-01-05" + rules = file("testdata/TestResProperty/Lifecycle/versionNotes/01_02_rules.json") +} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/03_expected_rules.json b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/03_expected_rules.json new file mode 100644 index 000000000..b5ce60f65 --- /dev/null +++ b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/03_expected_rules.json @@ -0,0 +1 @@ +{"comments":"updatedNotes2","rules":{"behaviors":[{"name":"caching","options":{"behavior":"MAX_AGE","mustRevalidate":false,"ttl":"10d"}}],"name":"default","options":{}}} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/03_rules.json b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/03_rules.json new file mode 100644 index 000000000..7903182d7 --- /dev/null +++ b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/03_rules.json @@ -0,0 +1,16 @@ +{ + "rules": { + "name": "default", + "behaviors": [ + { + "name": "caching", + "options": { + "behavior": "MAX_AGE", + "mustRevalidate": false, + "ttl": "10d" + } + } + ] + }, + "comments": "Rules_03" +} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/03_update_notes_and_rules.tf b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/03_update_notes_and_rules.tf new file mode 100644 index 000000000..77e97c3a8 --- /dev/null +++ b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/03_update_notes_and_rules.tf @@ -0,0 +1,14 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_property" "test" { + name = "test_property" + contract_id = "ctr_123" + group_id = "grp_123" + product_id = "prd_123" + + version_notes = "updatedNotes2" + rule_format = "v2023-01-05" + rules = file("testdata/TestResProperty/Lifecycle/versionNotes/03_rules.json") +} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/04_05_remove_notes_update_comments.tf b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/04_05_remove_notes_update_comments.tf new file mode 100644 index 000000000..e1ec2c71e --- /dev/null +++ b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/04_05_remove_notes_update_comments.tf @@ -0,0 +1,13 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_property" "test" { + name = "test_property" + contract_id = "ctr_123" + group_id = "grp_123" + product_id = "prd_123" + + rule_format = "v2023-01-05" + rules = file("testdata/TestResProperty/Lifecycle/versionNotes/04_05_rules.json") +} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/04_05_rules.json b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/04_05_rules.json new file mode 100644 index 000000000..6e5a590b1 --- /dev/null +++ b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/04_05_rules.json @@ -0,0 +1,16 @@ +{ + "rules": { + "name": "default", + "behaviors": [ + { + "name": "caching", + "options": { + "behavior": "MAX_AGE", + "mustRevalidate": false, + "ttl": "10d" + } + } + ] + }, + "comments": "Rules_04" +} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/04_expected_rules.json b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/04_expected_rules.json new file mode 100644 index 000000000..b70cb56bb --- /dev/null +++ b/pkg/providers/property/testdata/TestResProperty/Lifecycle/versionNotes/04_expected_rules.json @@ -0,0 +1 @@ +{"comments":"Rules_04","rules":{"behaviors":[{"name":"caching","options":{"behavior":"MAX_AGE","mustRevalidate":false,"ttl":"10d"}}],"name":"default","options":{}}} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestResProperty/Lifecycle/with-propertyID/step0.tf b/pkg/providers/property/testdata/TestResProperty/Lifecycle/with-propertyID/step0.tf new file mode 100644 index 000000000..e3b1007dc --- /dev/null +++ b/pkg/providers/property/testdata/TestResProperty/Lifecycle/with-propertyID/step0.tf @@ -0,0 +1,18 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_property" "test" { + name = "test_property" + contract_id = "ctr_0" + group_id = "grp_0" + product_id = "prd_0" + property_id = "prp_0" + + hostnames { + cname_to = "to.test.domain" + cname_from = "from.test.domain" + cert_provisioning_type = "DEFAULT" + } + +} diff --git a/pkg/providers/property/testdata/TestResProperty/Lifecycle/with-propertyID/step1.tf b/pkg/providers/property/testdata/TestResProperty/Lifecycle/with-propertyID/step1.tf new file mode 100644 index 000000000..a9462920e --- /dev/null +++ b/pkg/providers/property/testdata/TestResProperty/Lifecycle/with-propertyID/step1.tf @@ -0,0 +1,19 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_property" "test" { + name = "test_property" + contract_id = "ctr_0" + group_id = "grp_0" + product_id = "prd_0" + property_id = "prp_0" + + hostnames { + cname_to = "to2.test.domain" + cname_from = "from.test.domain" + cert_provisioning_type = "DEFAULT" + } + + rules = "{\"rules\":{\"name\":\"default\",\"options\":{}}}" +} diff --git a/pkg/providers/property/testdata/TestResProperty/property_with_validation_warning_for_rules.tf b/pkg/providers/property/testdata/TestResProperty/property_with_validation_warning_for_rules.tf new file mode 100644 index 000000000..f27e25ee3 --- /dev/null +++ b/pkg/providers/property/testdata/TestResProperty/property_with_validation_warning_for_rules.tf @@ -0,0 +1,29 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + +resource "akamai_property" "test" { + name = "test_property" + group_id = "grp_0" + contract_id = "ctr_0" + product_id = "prd_0" + # Fetch the newly created property + depends_on = [ + akamai_property.test + ] + rules = jsonencode( + { + "rules" : { + "behaviors" : [ + { + "name" : "origin", + "options" : { + "hostname" : "1.2.3.4", + "httpPort" : 80, + "httpsPort" : 443 + } + } + ] + } + }) +} diff --git a/pkg/providers/property/testdata/TestResPropertyBootstrap/create.tf b/pkg/providers/property/testdata/TestResPropertyBootstrap/create.tf new file mode 100644 index 000000000..c80e43c92 --- /dev/null +++ b/pkg/providers/property/testdata/TestResPropertyBootstrap/create.tf @@ -0,0 +1,11 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + + +resource "akamai_property_bootstrap" "test" { + name = "property_name" + group_id = "grp_1" + contract_id = "ctr_2" + product_id = "prd_3" +} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestResPropertyBootstrap/create_without_prefixes.tf b/pkg/providers/property/testdata/TestResPropertyBootstrap/create_without_prefixes.tf new file mode 100644 index 000000000..1de8697e1 --- /dev/null +++ b/pkg/providers/property/testdata/TestResPropertyBootstrap/create_without_prefixes.tf @@ -0,0 +1,11 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + + +resource "akamai_property_bootstrap" "test" { + name = "property_name" + group_id = "1" + contract_id = "2" + product_id = "3" +} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestResPropertyBootstrap/update_contract.tf b/pkg/providers/property/testdata/TestResPropertyBootstrap/update_contract.tf new file mode 100644 index 000000000..138598d46 --- /dev/null +++ b/pkg/providers/property/testdata/TestResPropertyBootstrap/update_contract.tf @@ -0,0 +1,11 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + + +resource "akamai_property_bootstrap" "test" { + name = "property_name" + group_id = "grp_1" + contract_id = "ctr_222" + product_id = "prd_3" +} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestResPropertyBootstrap/update_group.tf b/pkg/providers/property/testdata/TestResPropertyBootstrap/update_group.tf new file mode 100644 index 000000000..ecd73e94e --- /dev/null +++ b/pkg/providers/property/testdata/TestResPropertyBootstrap/update_group.tf @@ -0,0 +1,11 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + + +resource "akamai_property_bootstrap" "test" { + name = "property_name" + group_id = "grp_111" + contract_id = "ctr_2" + product_id = "prd_3" +} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestResPropertyBootstrap/update_product.tf b/pkg/providers/property/testdata/TestResPropertyBootstrap/update_product.tf new file mode 100644 index 000000000..0af7693cf --- /dev/null +++ b/pkg/providers/property/testdata/TestResPropertyBootstrap/update_product.tf @@ -0,0 +1,11 @@ +provider "akamai" { + edgerc = "../../test/edgerc" +} + + +resource "akamai_property_bootstrap" "test" { + name = "property_name" + group_id = "grp_1" + contract_id = "ctr_2" + product_id = "prd_333" +} \ No newline at end of file diff --git a/pkg/providers/property/testdata/TestResPropertyInclude/custom_validation_errors.tf b/pkg/providers/property/testdata/TestResPropertyInclude/custom_validation_errors.tf index c904439b1..911acfabc 100644 --- a/pkg/providers/property/testdata/TestResPropertyInclude/custom_validation_errors.tf +++ b/pkg/providers/property/testdata/TestResPropertyInclude/custom_validation_errors.tf @@ -6,7 +6,7 @@ resource "akamai_property_include" "test" { contract_id = "ctr_123" group_id = "grp_123" product_id = "prd_test" - name = "test include" + name = "test_include" type = "INVALID_TYPE" rule_format = "INVALID_RULE_FORMAT" rules = "INVALID_JSON" diff --git a/pkg/providers/property/testdata/TestResPropertyInclude/product_id_error.tf b/pkg/providers/property/testdata/TestResPropertyInclude/product_id_error.tf index 51e237a5e..be4e7b6d8 100644 --- a/pkg/providers/property/testdata/TestResPropertyInclude/product_id_error.tf +++ b/pkg/providers/property/testdata/TestResPropertyInclude/product_id_error.tf @@ -5,7 +5,7 @@ provider "akamai" { resource "akamai_property_include" "test" { contract_id = "ctr_123" group_id = "grp_123" - name = "test include" + name = "test_include" type = "MICROSERVICES" rule_format = "v2022-06-28" rules = "{}" diff --git a/pkg/providers/property/testdata/TestResPropertyInclude/property_include.tf b/pkg/providers/property/testdata/TestResPropertyInclude/property_include.tf index 22c894afa..50f4d74e7 100644 --- a/pkg/providers/property/testdata/TestResPropertyInclude/property_include.tf +++ b/pkg/providers/property/testdata/TestResPropertyInclude/property_include.tf @@ -6,7 +6,7 @@ resource "akamai_property_include" "test" { contract_id = "ctr_123" group_id = "grp_123" product_id = "prd_test" - name = "test include" + name = "test_include" type = "MICROSERVICES" rule_format = "v2022-06-28" rules = data.akamai_property_rules_template.rules.json diff --git a/pkg/providers/property/testdata/TestResPropertyInclude/property_include_force_new.tf b/pkg/providers/property/testdata/TestResPropertyInclude/property_include_force_new.tf index 0fb650957..2d19761cc 100644 --- a/pkg/providers/property/testdata/TestResPropertyInclude/property_include_force_new.tf +++ b/pkg/providers/property/testdata/TestResPropertyInclude/property_include_force_new.tf @@ -6,7 +6,7 @@ resource "akamai_property_include" "test" { contract_id = "ctr_1234" group_id = "grp_1234" product_id = "prd_test2" - name = "test include" + name = "test_include" type = "MICROSERVICES" rule_format = "v2022-06-28" rules = data.akamai_property_rules_template.rules.json diff --git a/pkg/providers/property/testdata/TestResPropertyInclude/property_include_import.tf b/pkg/providers/property/testdata/TestResPropertyInclude/property_include_import.tf index 073cd358f..81c44e80f 100644 --- a/pkg/providers/property/testdata/TestResPropertyInclude/property_include_import.tf +++ b/pkg/providers/property/testdata/TestResPropertyInclude/property_include_import.tf @@ -6,7 +6,7 @@ resource "akamai_property_include" "test" { contract_id = "ctr_123" group_id = "grp_123" product_id = "prd_test" - name = "test include" + name = "test_include" type = "MICROSERVICES" rule_format = "v2022-06-28" rules = file("testdata/TestResPropertyInclude/property-snippets/simple_rules.json") diff --git a/pkg/providers/property/testdata/TestResPropertyInclude/property_include_no_rules.tf b/pkg/providers/property/testdata/TestResPropertyInclude/property_include_no_rules.tf index f47039cd6..2ff4814b9 100644 --- a/pkg/providers/property/testdata/TestResPropertyInclude/property_include_no_rules.tf +++ b/pkg/providers/property/testdata/TestResPropertyInclude/property_include_no_rules.tf @@ -6,7 +6,7 @@ resource "akamai_property_include" "test" { contract_id = "ctr_123" group_id = "grp_123" product_id = "prd_test" - name = "test include" + name = "test_include" type = "MICROSERVICES" rule_format = "v2022-06-28" } diff --git a/pkg/providers/property/testdata/TestResPropertyInclude/property_include_null_cpcode.tf b/pkg/providers/property/testdata/TestResPropertyInclude/property_include_null_cpcode.tf index 72c6d52f6..04d9d0536 100644 --- a/pkg/providers/property/testdata/TestResPropertyInclude/property_include_null_cpcode.tf +++ b/pkg/providers/property/testdata/TestResPropertyInclude/property_include_null_cpcode.tf @@ -6,7 +6,7 @@ resource "akamai_property_include" "test" { contract_id = "ctr_123" group_id = "grp_123" product_id = "prd_test" - name = "test include" + name = "test_include" type = "MICROSERVICES" rule_format = data.akamai_property_rules_builder.rules_with_null.rule_format rules = data.akamai_property_rules_builder.rules_with_null.json diff --git a/pkg/providers/property/testdata/TestResPropertyInclude/property_include_with_comment.tf b/pkg/providers/property/testdata/TestResPropertyInclude/property_include_with_comment.tf index 8abe1e36f..32db0c933 100644 --- a/pkg/providers/property/testdata/TestResPropertyInclude/property_include_with_comment.tf +++ b/pkg/providers/property/testdata/TestResPropertyInclude/property_include_with_comment.tf @@ -6,7 +6,7 @@ resource "akamai_property_include" "test" { contract_id = "ctr_123" group_id = "grp_123" product_id = "prd_test" - name = "test include" + name = "test_include" type = "MICROSERVICES" rule_format = "v2022-06-28" rules = data.akamai_property_rules_template.rules.json diff --git a/pkg/providers/property/testdata/TestResPropertyInclude/property_include_with_ds_create.tf b/pkg/providers/property/testdata/TestResPropertyInclude/property_include_with_ds_create.tf index df3aff869..cbf99a593 100644 --- a/pkg/providers/property/testdata/TestResPropertyInclude/property_include_with_ds_create.tf +++ b/pkg/providers/property/testdata/TestResPropertyInclude/property_include_with_ds_create.tf @@ -6,7 +6,7 @@ resource "akamai_property_include" "test" { contract_id = "ctr_123" group_id = "grp_123" product_id = "prd_test" - name = "test include" + name = "test_include" type = "MICROSERVICES" rule_format = "v2022-06-28" } diff --git a/pkg/providers/property/testdata/TestResPropertyInclude/property_include_with_ds_update.tf b/pkg/providers/property/testdata/TestResPropertyInclude/property_include_with_ds_update.tf index 399d3ab9f..9c537ab2b 100644 --- a/pkg/providers/property/testdata/TestResPropertyInclude/property_include_with_ds_update.tf +++ b/pkg/providers/property/testdata/TestResPropertyInclude/property_include_with_ds_update.tf @@ -6,7 +6,7 @@ resource "akamai_property_include" "test" { contract_id = "ctr_123" group_id = "grp_123" product_id = "prd_test" - name = "test include" + name = "test_include" type = "MICROSERVICES" rule_format = "v2022-06-28" rules = data.akamai_property_rules_template.rules.json diff --git a/pkg/providers/property/testdata/TestResPropertyInclude/rule_format_blank.tf b/pkg/providers/property/testdata/TestResPropertyInclude/rule_format_blank.tf index 2a1539843..b3f0b3e3b 100644 --- a/pkg/providers/property/testdata/TestResPropertyInclude/rule_format_blank.tf +++ b/pkg/providers/property/testdata/TestResPropertyInclude/rule_format_blank.tf @@ -5,7 +5,7 @@ provider "akamai" { resource "akamai_property_include" "test" { contract_id = "ctr_123" group_id = "grp_123" - name = "test include" + name = "test_include" product_id = "prd_test" type = "MICROSERVICES" rule_format = "" diff --git a/pkg/providers/property/testdata/TestResPropertyInclude/rule_format_latest.tf b/pkg/providers/property/testdata/TestResPropertyInclude/rule_format_latest.tf index b29cbb533..f14b3821d 100644 --- a/pkg/providers/property/testdata/TestResPropertyInclude/rule_format_latest.tf +++ b/pkg/providers/property/testdata/TestResPropertyInclude/rule_format_latest.tf @@ -5,7 +5,7 @@ provider "akamai" { resource "akamai_property_include" "test" { contract_id = "ctr_123" group_id = "grp_123" - name = "test include" + name = "test_include" product_id = "prd_test" type = "MICROSERVICES" rule_format = "latest" diff --git a/tools/go.mod b/tools/go.mod index c239f43bc..9efaca36b 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -2,234 +2,9 @@ module github.com/akamai/terraform-provider-akamai/tools go 1.18 -require ( - github.com/golangci/golangci-lint v1.50.1 - github.com/terraform-linters/tflint v0.45.0 - golang.org/x/tools v0.12.0 -) +require golang.org/x/tools v0.13.0 require ( - 4d63.com/gochecknoglobals v0.1.0 // indirect - cloud.google.com/go v0.110.0 // indirect - cloud.google.com/go/compute v1.19.1 // indirect - cloud.google.com/go/compute/metadata v0.2.3 // indirect - cloud.google.com/go/iam v0.13.0 // indirect - cloud.google.com/go/storage v1.28.1 // indirect - github.com/Abirdcfly/dupword v0.0.7 // indirect - github.com/Antonboom/errname v0.1.7 // indirect - github.com/Antonboom/nilnil v0.1.1 // indirect - github.com/BurntSushi/toml v1.2.1 // indirect - github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 // indirect - github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 // indirect - github.com/Masterminds/semver v1.5.0 // indirect - github.com/Masterminds/semver/v3 v3.2.0 // indirect - github.com/OpenPeeDeeP/depguard v1.1.1 // indirect - github.com/agext/levenshtein v1.2.3 // indirect - github.com/alexkohler/prealloc v1.0.0 // indirect - github.com/alingse/asasalint v0.0.11 // indirect - github.com/apparentlymart/go-cidr v1.1.0 // indirect - github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect - github.com/ashanbrown/forbidigo v1.3.0 // indirect - github.com/ashanbrown/makezero v1.1.1 // indirect - github.com/aws/aws-sdk-go v1.44.122 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect - github.com/bkielbasa/cyclop v1.2.0 // indirect - github.com/blizzy78/varnamelen v0.8.0 // indirect - github.com/bmatcuk/doublestar v1.1.5 // indirect - github.com/bombsimon/wsl/v3 v3.3.0 // indirect - github.com/breml/bidichk v0.2.3 // indirect - github.com/breml/errchkjson v0.3.0 // indirect - github.com/butuzov/ireturn v0.1.1 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect - github.com/charithe/durationcheck v0.0.9 // indirect - github.com/chavacava/garif v0.0.0-20220630083739-93517212f375 // indirect - github.com/curioswitch/go-reassign v0.2.0 // indirect - github.com/daixiang0/gci v0.8.1 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect - github.com/denis-tingaikin/go-header v0.4.3 // indirect - github.com/esimonov/ifshort v1.0.4 // indirect - github.com/ettle/strcase v0.1.1 // indirect - github.com/fatih/color v1.14.1 // indirect - github.com/fatih/structtag v1.2.0 // indirect - github.com/firefart/nonamedreturns v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect - github.com/fzipp/gocyclo v0.6.0 // indirect - github.com/go-critic/go-critic v0.6.5 // indirect - github.com/go-toolsmith/astcast v1.0.0 // indirect - github.com/go-toolsmith/astcopy v1.0.2 // indirect - github.com/go-toolsmith/astequal v1.0.3 // indirect - github.com/go-toolsmith/astfmt v1.0.0 // indirect - github.com/go-toolsmith/astp v1.0.0 // indirect - github.com/go-toolsmith/strparse v1.0.0 // indirect - github.com/go-toolsmith/typep v1.0.2 // indirect - github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b // indirect - github.com/gobwas/glob v0.2.3 // indirect - github.com/gofrs/flock v0.8.1 // indirect - github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/golang/protobuf v1.5.3 // indirect - github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 // indirect - github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a // indirect - github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe // indirect - github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 // indirect - github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 // indirect - github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca // indirect - github.com/golangci/misspell v0.3.5 // indirect - github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 // indirect - github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 // indirect - github.com/google/go-cmp v0.5.9 // indirect - github.com/google/go-github/v35 v35.3.0 // indirect - github.com/google/go-querystring v1.0.0 // indirect - github.com/google/uuid v1.3.0 // indirect - github.com/googleapis/enterprise-certificate-proxy v0.2.3 // indirect - github.com/googleapis/gax-go/v2 v2.7.1 // indirect - github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8 // indirect - github.com/gostaticanalysis/analysisutil v0.7.1 // indirect - github.com/gostaticanalysis/comment v1.4.2 // indirect - github.com/gostaticanalysis/forcetypeassert v0.1.0 // indirect - github.com/gostaticanalysis/nilerr v0.1.1 // indirect - github.com/hashicorp/errwrap v1.0.0 // indirect - github.com/hashicorp/go-cleanhttp v0.5.2 // indirect - github.com/hashicorp/go-getter v1.7.1 // indirect - github.com/hashicorp/go-hclog v1.4.0 // indirect - github.com/hashicorp/go-multierror v1.1.1 // indirect - github.com/hashicorp/go-plugin v1.4.8 // indirect - github.com/hashicorp/go-safetemp v1.0.0 // indirect - github.com/hashicorp/go-uuid v1.0.3 // indirect - github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect - github.com/hashicorp/hcl/v2 v2.16.0 // indirect - github.com/hashicorp/logutils v1.0.0 // indirect - github.com/hashicorp/terraform-registry-address v0.1.0 // indirect - github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 // indirect - github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d // indirect - github.com/hexops/gotextdiff v1.0.3 // indirect - github.com/inconshreveable/mousetrap v1.0.1 // indirect - github.com/jessevdk/go-flags v1.5.0 // indirect - github.com/jgautheron/goconst v1.5.1 // indirect - github.com/jingyugao/rowserrcheck v1.1.1 // indirect - github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af // indirect - github.com/jmespath/go-jmespath v0.4.0 // indirect - github.com/jstemmer/go-junit-report v1.0.0 // indirect - github.com/julz/importas v0.1.0 // indirect - github.com/kisielk/errcheck v1.6.2 // indirect - github.com/kisielk/gotool v1.0.0 // indirect - github.com/kkHAIKE/contextcheck v1.1.3 // indirect - github.com/klauspost/compress v1.15.11 // indirect - github.com/kulti/thelper v0.6.3 // indirect - github.com/kunwardeep/paralleltest v1.0.6 // indirect - github.com/kyoh86/exportloopref v0.1.8 // indirect - github.com/ldez/gomoddirectives v0.2.3 // indirect - github.com/ldez/tagliatelle v0.3.1 // indirect - github.com/leonklingele/grouper v1.1.0 // indirect - github.com/lufeee/execinquery v1.2.1 // indirect - github.com/magiconair/properties v1.8.6 // indirect - github.com/maratori/testableexamples v1.0.0 // indirect - github.com/maratori/testpackage v1.1.0 // indirect - github.com/matoous/godox v0.0.0-20210227103229-6504466cf951 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.17 // indirect - github.com/mattn/go-runewidth v0.0.9 // indirect - github.com/matttproud/golang_protobuf_extensions v1.0.1 // indirect - github.com/mbilski/exhaustivestruct v1.2.0 // indirect - github.com/mgechev/revive v1.2.4 // indirect - github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/go-testing-interface v1.14.1 // indirect - github.com/mitchellh/go-wordwrap v1.0.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect - github.com/moricho/tparallel v0.2.1 // indirect - github.com/nakabonne/nestif v0.3.1 // indirect - github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 // indirect - github.com/nishanths/exhaustive v0.8.3 // indirect - github.com/nishanths/predeclared v0.2.2 // indirect - github.com/oklog/run v1.0.0 // indirect - github.com/olekukonko/tablewriter v0.0.5 // indirect - github.com/owenrumney/go-sarif v1.1.1 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.5 // indirect - github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d // indirect - github.com/pkg/errors v0.9.1 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/polyfloyd/go-errorlint v1.0.5 // indirect - github.com/prometheus/client_golang v1.12.1 // indirect - github.com/prometheus/client_model v0.2.0 // indirect - github.com/prometheus/common v0.32.1 // indirect - github.com/prometheus/procfs v0.7.3 // indirect - github.com/quasilyte/go-ruleguard v0.3.18 // indirect - github.com/quasilyte/gogrep v0.0.0-20220828223005-86e4605de09f // indirect - github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95 // indirect - github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 // indirect - github.com/ryancurrah/gomodguard v1.2.4 // indirect - github.com/ryanrolds/sqlclosecheck v0.3.0 // indirect - github.com/sanposhiho/wastedassign/v2 v2.0.6 // indirect - github.com/sashamelentyev/interfacebloat v1.1.0 // indirect - github.com/sashamelentyev/usestdlibvars v1.20.0 // indirect - github.com/securego/gosec/v2 v2.13.1 // indirect - github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c // indirect - github.com/sirupsen/logrus v1.9.0 // indirect - github.com/sivchari/containedctx v1.0.2 // indirect - github.com/sivchari/nosnakecase v1.7.0 // indirect - github.com/sivchari/tenv v1.7.0 // indirect - github.com/sonatard/noctx v0.0.1 // indirect - github.com/sourcegraph/go-diff v0.6.1 // indirect - github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d // indirect - github.com/sourcegraph/jsonrpc2 v0.1.0 // indirect - github.com/spf13/afero v1.9.3 // indirect - github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/cobra v1.6.0 // indirect - github.com/spf13/jwalterweatherman v1.1.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.12.0 // indirect - github.com/ssgreg/nlreturn/v2 v2.2.1 // indirect - github.com/stbenjam/no-sprintf-host-port v0.1.1 // indirect - github.com/stretchr/objx v0.5.0 // indirect - github.com/stretchr/testify v1.8.1 // indirect - github.com/subosito/gotenv v1.4.1 // indirect - github.com/tdakkota/asciicheck v0.1.1 // indirect - github.com/terraform-linters/tflint-plugin-sdk v0.15.0 // indirect - github.com/terraform-linters/tflint-ruleset-terraform v0.2.2 // indirect - github.com/tetafro/godot v1.4.11 // indirect - github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 // indirect - github.com/timonwong/loggercheck v0.9.3 // indirect - github.com/tomarrell/wrapcheck/v2 v2.7.0 // indirect - github.com/tommy-muehle/go-mnd/v2 v2.5.1 // indirect - github.com/ulikunitz/xz v0.5.10 // indirect - github.com/ultraware/funlen v0.0.3 // indirect - github.com/ultraware/whitespace v0.0.5 // indirect - github.com/uudashr/gocognit v1.0.6 // indirect - github.com/vmihailenco/msgpack/v4 v4.3.12 // indirect - github.com/vmihailenco/tagparser v0.1.1 // indirect - github.com/yagipy/maintidx v1.0.0 // indirect - github.com/yeya24/promlinter v0.2.0 // indirect - github.com/zclconf/go-cty v1.12.1 // indirect - github.com/zclconf/go-cty-yaml v1.0.3 // indirect - gitlab.com/bosi/decorder v0.2.3 // indirect - go.opencensus.io v0.24.0 // indirect - go.uber.org/atomic v1.7.0 // indirect - go.uber.org/multierr v1.6.0 // indirect - go.uber.org/zap v1.17.0 // indirect - golang.org/x/crypto v0.17.0 // indirect - golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e // indirect - golang.org/x/exp/typeparams v0.0.0-20220827204233-334a2380cb91 // indirect - golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect - golang.org/x/mod v0.12.0 // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/oauth2 v0.7.0 // indirect - golang.org/x/sync v0.3.0 // indirect - golang.org/x/sys v0.15.0 // indirect - golang.org/x/text v0.14.0 // indirect - golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect - google.golang.org/api v0.114.0 // indirect - google.golang.org/appengine v1.6.7 // indirect - google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect - google.golang.org/grpc v1.56.3 // indirect - google.golang.org/protobuf v1.30.0 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - honnef.co/go/tools v0.3.3 // indirect - mvdan.cc/gofumpt v0.4.0 // indirect - mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed // indirect - mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b // indirect - mvdan.cc/unparam v0.0.0-20220706161116-678bad134442 // indirect + golang.org/x/mod v0.13.0 // indirect + golang.org/x/sys v0.13.0 // indirect ) diff --git a/tools/go.sum b/tools/go.sum index cd74a34e9..d90944cfa 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -1,1488 +1,6 @@ -4d63.com/gochecknoglobals v0.1.0 h1:zeZSRqj5yCg28tCkIV/z/lWbwvNm5qnKVS15PI8nhD0= -4d63.com/gochecknoglobals v0.1.0/go.mod h1:wfdC5ZjKSPr7CybKEcgJhUOgeAQW1+7WcyK8OvUilfo= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= -cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= -cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= -cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= -cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= -cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= -cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= -cloud.google.com/go v0.78.0/go.mod h1:QjdrLG0uq+YwhjoVOLsS1t7TW8fs36kLs4XO5R5ECHg= -cloud.google.com/go v0.79.0/go.mod h1:3bzgcEeQlzbuEAYu4mrWhKqWjmpprinYgKJLgKHnbb8= -cloud.google.com/go v0.81.0/go.mod h1:mk/AM35KwGk/Nm2YSeZbxXdrNK3KZOYHmLkOqC2V6E0= -cloud.google.com/go v0.83.0/go.mod h1:Z7MJUsANfY0pYPdw0lbnivPx4/vhy/e2FEkSkF7vAVY= -cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSUM= -cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY= -cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ= -cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI= -cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4= -cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc= -cloud.google.com/go v0.99.0/go.mod h1:w0Xx2nLzqWJPuozYQX+hFfCSI8WioryfRDzkoI/Y2ZA= -cloud.google.com/go v0.100.2/go.mod h1:4Xra9TjzAeYHrl5+oeLlzbM2k3mjVhZh4UqTZ//w99A= -cloud.google.com/go v0.102.0/go.mod h1:oWcCzKlqJ5zgHQt9YsaeTY9KzIvjyy0ArmiBUgpQ+nc= -cloud.google.com/go v0.102.1/go.mod h1:XZ77E9qnTEnrgEOvr4xzfdX5TRo7fB4T2F4O6+34hIU= -cloud.google.com/go v0.104.0/go.mod h1:OO6xxXdJyvuJPcEPBLN9BJPD+jep5G1+2U5B5gkRYtA= -cloud.google.com/go v0.110.0 h1:Zc8gqp3+a9/Eyph2KDmcGaPtbKRIoqq4YTlL4NMD0Ys= -cloud.google.com/go v0.110.0/go.mod h1:SJnCLqQ0FCFGSZMUNUf84MV3Aia54kn7pi8st7tMzaY= -cloud.google.com/go/aiplatform v1.22.0/go.mod h1:ig5Nct50bZlzV6NvKaTwmplLLddFx0YReh9WfTO5jKw= -cloud.google.com/go/aiplatform v1.24.0/go.mod h1:67UUvRBKG6GTayHKV8DBv2RtR1t93YRu5B1P3x99mYY= -cloud.google.com/go/analytics v0.11.0/go.mod h1:DjEWCu41bVbYcKyvlws9Er60YE4a//bK6mnhWvQeFNI= -cloud.google.com/go/analytics v0.12.0/go.mod h1:gkfj9h6XRf9+TS4bmuhPEShsh3hH8PAZzm/41OOhQd4= -cloud.google.com/go/area120 v0.5.0/go.mod h1:DE/n4mp+iqVyvxHN41Vf1CR602GiHQjFPusMFW6bGR4= -cloud.google.com/go/area120 v0.6.0/go.mod h1:39yFJqWVgm0UZqWTOdqkLhjoC7uFfgXRC8g/ZegeAh0= -cloud.google.com/go/artifactregistry v1.6.0/go.mod h1:IYt0oBPSAGYj/kprzsBjZ/4LnG/zOcHyFHjWPCi6SAQ= -cloud.google.com/go/artifactregistry v1.7.0/go.mod h1:mqTOFOnGZx8EtSqK/ZWcsm/4U8B77rbcLP6ruDU2Ixk= -cloud.google.com/go/asset v1.5.0/go.mod h1:5mfs8UvcM5wHhqtSv8J1CtxxaQq3AdBxxQi2jGW/K4o= -cloud.google.com/go/asset v1.7.0/go.mod h1:YbENsRK4+xTiL+Ofoj5Ckf+O17kJtgp3Y3nn4uzZz5s= -cloud.google.com/go/asset v1.8.0/go.mod h1:mUNGKhiqIdbr8X7KNayoYvyc4HbbFO9URsjbytpUaW0= -cloud.google.com/go/assuredworkloads v1.5.0/go.mod h1:n8HOZ6pff6re5KYfBXcFvSViQjDwxFkAkmUFffJRbbY= -cloud.google.com/go/assuredworkloads v1.6.0/go.mod h1:yo2YOk37Yc89Rsd5QMVECvjaMKymF9OP+QXWlKXUkXw= -cloud.google.com/go/assuredworkloads v1.7.0/go.mod h1:z/736/oNmtGAyU47reJgGN+KVoYoxeLBoj4XkKYscNI= -cloud.google.com/go/automl v1.5.0/go.mod h1:34EjfoFGMZ5sgJ9EoLsRtdPSNZLcfflJR39VbVNS2M0= -cloud.google.com/go/automl v1.6.0/go.mod h1:ugf8a6Fx+zP0D59WLhqgTDsQI9w07o64uf/Is3Nh5p8= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= -cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= -cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= -cloud.google.com/go/bigquery v1.42.0/go.mod h1:8dRTJxhtG+vwBKzE5OseQn/hiydoQN3EedCaOdYmxRA= -cloud.google.com/go/billing v1.4.0/go.mod h1:g9IdKBEFlItS8bTtlrZdVLWSSdSyFUZKXNS02zKMOZY= -cloud.google.com/go/billing v1.5.0/go.mod h1:mztb1tBc3QekhjSgmpf/CV4LzWXLzCArwpLmP2Gm88s= -cloud.google.com/go/binaryauthorization v1.1.0/go.mod h1:xwnoWu3Y84jbuHa0zd526MJYmtnVXn0syOjaJgy4+dM= -cloud.google.com/go/binaryauthorization v1.2.0/go.mod h1:86WKkJHtRcv5ViNABtYMhhNWRrD1Vpi//uKEy7aYEfI= -cloud.google.com/go/cloudtasks v1.5.0/go.mod h1:fD92REy1x5woxkKEkLdvavGnPJGEn8Uic9nWuLzqCpY= -cloud.google.com/go/cloudtasks v1.6.0/go.mod h1:C6Io+sxuke9/KNRkbQpihnW93SWDU3uXt92nu85HkYI= -cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow= -cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM= -cloud.google.com/go/compute v1.5.0/go.mod h1:9SMHyhJlzhlkJqrPAc839t2BZFTSk6Jdj6mkzQJeu0M= -cloud.google.com/go/compute v1.6.0/go.mod h1:T29tfhtVbq1wvAPo0E3+7vhgmkOYeXjhFvz/FMzPu0s= -cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= -cloud.google.com/go/compute v1.7.0/go.mod h1:435lt8av5oL9P3fv1OEzSbSUe+ybHXGMPQHHZWZxy9U= -cloud.google.com/go/compute v1.10.0/go.mod h1:ER5CLbMxl90o2jtNbGSbtfOpQKR0t15FOtRsugnLrlU= -cloud.google.com/go/compute v1.19.1 h1:am86mquDUgjGNWxiGn+5PGLbmgiWXlE/yNWpIpNvuXY= -cloud.google.com/go/compute v1.19.1/go.mod h1:6ylj3a05WF8leseCdIf77NK0g1ey+nj5IKd5/kvShxE= -cloud.google.com/go/compute/metadata v0.2.3 h1:mg4jlk7mCAj6xXp9UJ4fjI9VUI5rubuGBW5aJ7UnBMY= -cloud.google.com/go/compute/metadata v0.2.3/go.mod h1:VAV5nSsACxMJvgaAuX6Pk2AawlZn8kiOGuCv6gTkwuA= -cloud.google.com/go/containeranalysis v0.5.1/go.mod h1:1D92jd8gRR/c0fGMlymRgxWD3Qw9C1ff6/T7mLgVL8I= -cloud.google.com/go/containeranalysis v0.6.0/go.mod h1:HEJoiEIu+lEXM+k7+qLCci0h33lX3ZqoYFdmPcoO7s4= -cloud.google.com/go/datacatalog v1.3.0/go.mod h1:g9svFY6tuR+j+hrTw3J2dNcmI0dzmSiyOzm8kpLq0a0= -cloud.google.com/go/datacatalog v1.5.0/go.mod h1:M7GPLNQeLfWqeIm3iuiruhPzkt65+Bx8dAKvScX8jvs= -cloud.google.com/go/datacatalog v1.6.0/go.mod h1:+aEyF8JKg+uXcIdAmmaMUmZ3q1b/lKLtXCmXdnc0lbc= -cloud.google.com/go/dataflow v0.6.0/go.mod h1:9QwV89cGoxjjSR9/r7eFDqqjtvbKxAK2BaYU6PVk9UM= -cloud.google.com/go/dataflow v0.7.0/go.mod h1:PX526vb4ijFMesO1o202EaUmouZKBpjHsTlCtB4parQ= -cloud.google.com/go/dataform v0.3.0/go.mod h1:cj8uNliRlHpa6L3yVhDOBrUXH+BPAO1+KFMQQNSThKo= -cloud.google.com/go/dataform v0.4.0/go.mod h1:fwV6Y4Ty2yIFL89huYlEkwUPtS7YZinZbzzj5S9FzCE= -cloud.google.com/go/datalabeling v0.5.0/go.mod h1:TGcJ0G2NzcsXSE/97yWjIZO0bXj0KbVlINXMG9ud42I= -cloud.google.com/go/datalabeling v0.6.0/go.mod h1:WqdISuk/+WIGeMkpw/1q7bK/tFEZxsrFJOJdY2bXvTQ= -cloud.google.com/go/dataqna v0.5.0/go.mod h1:90Hyk596ft3zUQ8NkFfvICSIfHFh1Bc7C4cK3vbhkeo= -cloud.google.com/go/dataqna v0.6.0/go.mod h1:1lqNpM7rqNLVgWBJyk5NF6Uen2PHym0jtVJonplVsDA= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/datastream v1.2.0/go.mod h1:i/uTP8/fZwgATHS/XFu0TcNUhuA0twZxxQ3EyCUQMwo= -cloud.google.com/go/datastream v1.3.0/go.mod h1:cqlOX8xlyYF/uxhiKn6Hbv6WjwPPuI9W2M9SAXwaLLQ= -cloud.google.com/go/dialogflow v1.15.0/go.mod h1:HbHDWs33WOGJgn6rfzBW1Kv807BE3O1+xGbn59zZWI4= -cloud.google.com/go/dialogflow v1.16.1/go.mod h1:po6LlzGfK+smoSmTBnbkIZY2w8ffjz/RcGSS+sh1el0= -cloud.google.com/go/dialogflow v1.17.0/go.mod h1:YNP09C/kXA1aZdBgC/VtXX74G/TKn7XVCcVumTflA+8= -cloud.google.com/go/documentai v1.7.0/go.mod h1:lJvftZB5NRiFSX4moiye1SMxHx0Bc3x1+p9e/RfXYiU= -cloud.google.com/go/documentai v1.8.0/go.mod h1:xGHNEB7CtsnySCNrCFdCyyMz44RhFEEX2Q7UD0c5IhU= -cloud.google.com/go/domains v0.6.0/go.mod h1:T9Rz3GasrpYk6mEGHh4rymIhjlnIuB4ofT1wTxDeT4Y= -cloud.google.com/go/domains v0.7.0/go.mod h1:PtZeqS1xjnXuRPKE/88Iru/LdfoRyEHYA9nFQf4UKpg= -cloud.google.com/go/edgecontainer v0.1.0/go.mod h1:WgkZ9tp10bFxqO8BLPqv2LlfmQF1X8lZqwW4r1BTajk= -cloud.google.com/go/edgecontainer v0.2.0/go.mod h1:RTmLijy+lGpQ7BXuTDa4C4ssxyXT34NIuHIgKuP4s5w= -cloud.google.com/go/functions v1.6.0/go.mod h1:3H1UA3qiIPRWD7PeZKLvHZ9SaQhR26XIJcC0A5GbvAk= -cloud.google.com/go/functions v1.7.0/go.mod h1:+d+QBcWM+RsrgZfV9xo6KfA1GlzJfxcfZcRPEhDDfzg= -cloud.google.com/go/gaming v1.5.0/go.mod h1:ol7rGcxP/qHTRQE/RO4bxkXq+Fix0j6D4LFPzYTIrDM= -cloud.google.com/go/gaming v1.6.0/go.mod h1:YMU1GEvA39Qt3zWGyAVA9bpYz/yAhTvaQ1t2sK4KPUA= -cloud.google.com/go/gkeconnect v0.5.0/go.mod h1:c5lsNAg5EwAy7fkqX/+goqFsU1Da/jQFqArp+wGNr/o= -cloud.google.com/go/gkeconnect v0.6.0/go.mod h1:Mln67KyU/sHJEBY8kFZ0xTeyPtzbq9StAVvEULYK16A= -cloud.google.com/go/gkehub v0.9.0/go.mod h1:WYHN6WG8w9bXU0hqNxt8rm5uxnk8IH+lPY9J2TV7BK0= -cloud.google.com/go/gkehub v0.10.0/go.mod h1:UIPwxI0DsrpsVoWpLB0stwKCP+WFVG9+y977wO+hBH0= -cloud.google.com/go/grafeas v0.2.0/go.mod h1:KhxgtF2hb0P191HlY5besjYm6MqTSTj3LSI+M+ByZHc= -cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY= -cloud.google.com/go/iam v0.5.0/go.mod h1:wPU9Vt0P4UmCux7mqtRu6jcpPAb74cP1fh50J3QpkUc= -cloud.google.com/go/iam v0.13.0 h1:+CmB+K0J/33d0zSQ9SlFWUeCCEn5XJA0ZMZ3pHE9u8k= -cloud.google.com/go/iam v0.13.0/go.mod h1:ljOg+rcNfzZ5d6f1nAUJ8ZIxOaZUVoS14bKCtaLZ/D0= -cloud.google.com/go/language v1.4.0/go.mod h1:F9dRpNFQmJbkaop6g0JhSBXCNlO90e1KWx5iDdxbWic= -cloud.google.com/go/language v1.6.0/go.mod h1:6dJ8t3B+lUYfStgls25GusK04NLh3eDLQnWM3mdEbhI= -cloud.google.com/go/lifesciences v0.5.0/go.mod h1:3oIKy8ycWGPUyZDR/8RNnTOYevhaMLqh5vLUXs9zvT8= -cloud.google.com/go/lifesciences v0.6.0/go.mod h1:ddj6tSX/7BOnhxCSd3ZcETvtNr8NZ6t/iPhY2Tyfu08= -cloud.google.com/go/longrunning v0.4.1 h1:v+yFJOfKC3yZdY6ZUI933pIYdhyhV8S3NpWrXWmg7jM= -cloud.google.com/go/mediatranslation v0.5.0/go.mod h1:jGPUhGTybqsPQn91pNXw0xVHfuJ3leR1wj37oU3y1f4= -cloud.google.com/go/mediatranslation v0.6.0/go.mod h1:hHdBCTYNigsBxshbznuIMFNe5QXEowAuNmmC7h8pu5w= -cloud.google.com/go/memcache v1.4.0/go.mod h1:rTOfiGZtJX1AaFUrOgsMHX5kAzaTQ8azHiuDoTPzNsE= -cloud.google.com/go/memcache v1.5.0/go.mod h1:dk3fCK7dVo0cUU2c36jKb4VqKPS22BTkf81Xq617aWM= -cloud.google.com/go/metastore v1.5.0/go.mod h1:2ZNrDcQwghfdtCwJ33nM0+GrBGlVuh8rakL3vdPY3XY= -cloud.google.com/go/metastore v1.6.0/go.mod h1:6cyQTls8CWXzk45G55x57DVQ9gWg7RiH65+YgPsNh9s= -cloud.google.com/go/networkconnectivity v1.4.0/go.mod h1:nOl7YL8odKyAOtzNX73/M5/mGZgqqMeryi6UPZTk/rA= -cloud.google.com/go/networkconnectivity v1.5.0/go.mod h1:3GzqJx7uhtlM3kln0+x5wyFvuVH1pIBJjhCpjzSt75o= -cloud.google.com/go/networksecurity v0.5.0/go.mod h1:xS6fOCoqpVC5zx15Z/MqkfDwH4+m/61A3ODiDV1xmiQ= -cloud.google.com/go/networksecurity v0.6.0/go.mod h1:Q5fjhTr9WMI5mbpRYEbiexTzROf7ZbDzvzCrNl14nyU= -cloud.google.com/go/notebooks v1.2.0/go.mod h1:9+wtppMfVPUeJ8fIWPOq1UnATHISkGXGqTkxeieQ6UY= -cloud.google.com/go/notebooks v1.3.0/go.mod h1:bFR5lj07DtCPC7YAAJ//vHskFBxA5JzYlH68kXVdk34= -cloud.google.com/go/osconfig v1.7.0/go.mod h1:oVHeCeZELfJP7XLxcBGTMBvRO+1nQ5tFG9VQTmYS2Fs= -cloud.google.com/go/osconfig v1.8.0/go.mod h1:EQqZLu5w5XA7eKizepumcvWx+m8mJUhEwiPqWiZeEdg= -cloud.google.com/go/oslogin v1.4.0/go.mod h1:YdgMXWRaElXz/lDk1Na6Fh5orF7gvmJ0FGLIs9LId4E= -cloud.google.com/go/oslogin v1.5.0/go.mod h1:D260Qj11W2qx/HVF29zBg+0fd6YCSjSqLUkY/qEenQU= -cloud.google.com/go/phishingprotection v0.5.0/go.mod h1:Y3HZknsK9bc9dMi+oE8Bim0lczMU6hrX0UpADuMefr0= -cloud.google.com/go/phishingprotection v0.6.0/go.mod h1:9Y3LBLgy0kDTcYET8ZH3bq/7qni15yVUoAxiFxnlSUA= -cloud.google.com/go/privatecatalog v0.5.0/go.mod h1:XgosMUvvPyxDjAVNDYxJ7wBW8//hLDDYmnsNcMGq1K0= -cloud.google.com/go/privatecatalog v0.6.0/go.mod h1:i/fbkZR0hLN29eEWiiwue8Pb+GforiEIBnV9yrRUOKI= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= -cloud.google.com/go/recaptchaenterprise v1.3.1/go.mod h1:OdD+q+y4XGeAlxRaMn1Y7/GveP6zmq76byL6tjPE7d4= -cloud.google.com/go/recaptchaenterprise/v2 v2.1.0/go.mod h1:w9yVqajwroDNTfGuhmOjPDN//rZGySaf6PtFVcSCa7o= -cloud.google.com/go/recaptchaenterprise/v2 v2.2.0/go.mod h1:/Zu5jisWGeERrd5HnlS3EUGb/D335f9k51B/FVil0jk= -cloud.google.com/go/recaptchaenterprise/v2 v2.3.0/go.mod h1:O9LwGCjrhGHBQET5CA7dd5NwwNQUErSgEDit1DLNTdo= -cloud.google.com/go/recommendationengine v0.5.0/go.mod h1:E5756pJcVFeVgaQv3WNpImkFP8a+RptV6dDLGPILjvg= -cloud.google.com/go/recommendationengine v0.6.0/go.mod h1:08mq2umu9oIqc7tDy8sx+MNJdLG0fUi3vaSVbztHgJ4= -cloud.google.com/go/recommender v1.5.0/go.mod h1:jdoeiBIVrJe9gQjwd759ecLJbxCDED4A6p+mqoqDvTg= -cloud.google.com/go/recommender v1.6.0/go.mod h1:+yETpm25mcoiECKh9DEScGzIRyDKpZ0cEhWGo+8bo+c= -cloud.google.com/go/redis v1.7.0/go.mod h1:V3x5Jq1jzUcg+UNsRvdmsfuFnit1cfe3Z/PGyq/lm4Y= -cloud.google.com/go/redis v1.8.0/go.mod h1:Fm2szCDavWzBk2cDKxrkmWBqoCiL1+Ctwq7EyqBCA/A= -cloud.google.com/go/retail v1.8.0/go.mod h1:QblKS8waDmNUhghY2TI9O3JLlFk8jybHeV4BF19FrE4= -cloud.google.com/go/retail v1.9.0/go.mod h1:g6jb6mKuCS1QKnH/dpu7isX253absFl6iE92nHwlBUY= -cloud.google.com/go/scheduler v1.4.0/go.mod h1:drcJBmxF3aqZJRhmkHQ9b3uSSpQoltBPGPxGAWROx6s= -cloud.google.com/go/scheduler v1.5.0/go.mod h1:ri073ym49NW3AfT6DZi21vLZrG07GXr5p3H1KxN5QlI= -cloud.google.com/go/secretmanager v1.6.0/go.mod h1:awVa/OXF6IiyaU1wQ34inzQNc4ISIDIrId8qE5QGgKA= -cloud.google.com/go/security v1.5.0/go.mod h1:lgxGdyOKKjHL4YG3/YwIL2zLqMFCKs0UbQwgyZmfJl4= -cloud.google.com/go/security v1.7.0/go.mod h1:mZklORHl6Bg7CNnnjLH//0UlAlaXqiG7Lb9PsPXLfD0= -cloud.google.com/go/security v1.8.0/go.mod h1:hAQOwgmaHhztFhiQ41CjDODdWP0+AE1B3sX4OFlq+GU= -cloud.google.com/go/securitycenter v1.13.0/go.mod h1:cv5qNAqjY84FCN6Y9z28WlkKXyWsgLO832YiWwkCWcU= -cloud.google.com/go/securitycenter v1.14.0/go.mod h1:gZLAhtyKv85n52XYWt6RmeBdydyxfPeTrpToDPw4Auc= -cloud.google.com/go/servicedirectory v1.4.0/go.mod h1:gH1MUaZCgtP7qQiI+F+A+OpeKF/HQWgtAddhTbhL2bs= -cloud.google.com/go/servicedirectory v1.5.0/go.mod h1:QMKFL0NUySbpZJ1UZs3oFAmdvVxhhxB6eJ/Vlp73dfg= -cloud.google.com/go/speech v1.6.0/go.mod h1:79tcr4FHCimOp56lwC01xnt/WPJZc4v3gzyT7FoBkCM= -cloud.google.com/go/speech v1.7.0/go.mod h1:KptqL+BAQIhMsj1kOP2la5DSEEerPDuOP/2mmkhHhZQ= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= -cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= -cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= -cloud.google.com/go/storage v1.22.1/go.mod h1:S8N1cAStu7BOeFfE8KAQzmyyLkK8p/vmRq6kuBTW58Y= -cloud.google.com/go/storage v1.23.0/go.mod h1:vOEEDNFnciUMhBeT6hsJIn3ieU5cFRmzeLgDvXzfIXc= -cloud.google.com/go/storage v1.27.0/go.mod h1:x9DOL8TK/ygDUMieqwfhdpQryTeEkhGKMi80i/iqR2s= -cloud.google.com/go/storage v1.28.1 h1:F5QDG5ChchaAVQhINh24U99OWHURqrW8OmQcGKXcbgI= -cloud.google.com/go/storage v1.28.1/go.mod h1:Qnisd4CqDdo6BGs2AD5LLnEsmSQ80wQ5ogcBBKhU86Y= -cloud.google.com/go/talent v1.1.0/go.mod h1:Vl4pt9jiHKvOgF9KoZo6Kob9oV4lwd/ZD5Cto54zDRw= -cloud.google.com/go/talent v1.2.0/go.mod h1:MoNF9bhFQbiJ6eFD3uSsg0uBALw4n4gaCaEjBw9zo8g= -cloud.google.com/go/videointelligence v1.6.0/go.mod h1:w0DIDlVRKtwPCn/C4iwZIJdvC69yInhW0cfi+p546uU= -cloud.google.com/go/videointelligence v1.7.0/go.mod h1:k8pI/1wAhjznARtVT9U1llUaFNPh7muw8QyOUpavru4= -cloud.google.com/go/vision v1.2.0/go.mod h1:SmNwgObm5DpFBme2xpyOyasvBc1aPdjvMk2bBk0tKD0= -cloud.google.com/go/vision/v2 v2.2.0/go.mod h1:uCdV4PpN1S0jyCyq8sIM42v2Y6zOLkZs+4R9LrGYwFo= -cloud.google.com/go/vision/v2 v2.3.0/go.mod h1:UO61abBx9QRMFkNBbf1D8B1LXdS2cGiiCRx0vSpZoUo= -cloud.google.com/go/webrisk v1.4.0/go.mod h1:Hn8X6Zr+ziE2aNd8SliSDWpEnSS1u4R9+xXZmFiHmGE= -cloud.google.com/go/webrisk v1.5.0/go.mod h1:iPG6fr52Tv7sGk0H6qUFzmL3HHZev1htXuWDEEsqMTg= -cloud.google.com/go/workflows v1.6.0/go.mod h1:6t9F5h/unJz41YqfBmqSASJSXccBLtD1Vwf+KmJENM0= -cloud.google.com/go/workflows v1.7.0/go.mod h1:JhSrZuVZWuiDfKEFxU0/F1PQjmpnpcoISEXH2bcHC3M= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Abirdcfly/dupword v0.0.7 h1:z14n0yytA3wNO2gpCD/jVtp/acEXPGmYu0esewpBt6Q= -github.com/Abirdcfly/dupword v0.0.7/go.mod h1:K/4M1kj+Zh39d2aotRwypvasonOyAMH1c/IZJzE0dmk= -github.com/Antonboom/errname v0.1.7 h1:mBBDKvEYwPl4WFFNwec1CZO096G6vzK9vvDQzAwkako= -github.com/Antonboom/errname v0.1.7/go.mod h1:g0ONh16msHIPgJSGsecu1G/dcF2hlYR/0SddnIAGavU= -github.com/Antonboom/nilnil v0.1.1 h1:PHhrh5ANKFWRBh7TdYmyyq2gyT2lotnvFvvFbylF81Q= -github.com/Antonboom/nilnil v0.1.1/go.mod h1:L1jBqoWM7AOeTD+tSquifKSesRHs4ZdaxvZR+xdJEaI= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24 h1:sHglBQTwgx+rWPdisA5ynNEsoARbiCBOyGcJM4/OzsM= -github.com/Djarvur/go-err113 v0.0.0-20210108212216-aea10b59be24/go.mod h1:4UJr5HIiMZrwgkSPdsjy2uOQExX/WEILpIrO9UPGuXs= -github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0 h1:+r1rSv4gvYn0wmRjC8X7IAzX8QezqtFV9m0MUHFJgts= -github.com/GaijinEntertainment/go-exhaustruct/v2 v2.3.0/go.mod h1:b3g59n2Y+T5xmcxJL+UEG2f8cQploZm1mR/v6BW0mU0= -github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= -github.com/Masterminds/semver v1.5.0/go.mod h1:MB6lktGJrhw8PrUyiEoblNEGEQ+RzHPF078ddwwvV3Y= -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/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/OpenPeeDeeP/depguard v1.1.1 h1:TSUznLjvp/4IUP+OQ0t/4jF4QUyxIcVX8YnghZdunyA= -github.com/OpenPeeDeeP/depguard v1.1.1/go.mod h1:JtAMzWkmFEzDPyAd+W0NHl1lvpQKTvT9jnRVsohBKpc= -github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= -github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= -github.com/alexkohler/prealloc v1.0.0 h1:Hbq0/3fJPQhNkN0dR95AVrr6R7tou91y0uHG5pOcUuw= -github.com/alexkohler/prealloc v1.0.0/go.mod h1:VetnK3dIgFBBKmg0YnD9F9x6Icjd+9cvfHR56wJVlKE= -github.com/alingse/asasalint v0.0.11 h1:SFwnQXJ49Kx/1GghOFz1XGqHYKp21Kq1nHad/0WQRnw= -github.com/alingse/asasalint v0.0.11/go.mod h1:nCaoMhw7a9kSJObvQyVzNTPBDbNpdocqrSP7t/cW5+I= -github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= -github.com/apparentlymart/go-cidr v1.1.0 h1:2mAhrMoF+nhXqxTzSZMUzDHkLjmIHC+Zzn4tdgBZjnU= -github.com/apparentlymart/go-cidr v1.1.0/go.mod h1:EBcsNrHc3zQeuaeCeCtQruQm+n9/YjEn/vI25Lg7Gwc= -github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3 h1:ZSTrOEhiM5J5RFxEaFvMZVEAM1KvT1YzbEOwB2EAGjA= -github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk= -github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw= -github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo= -github.com/ashanbrown/forbidigo v1.3.0 h1:VkYIwb/xxdireGAdJNZoo24O4lmnEWkactplBlWTShc= -github.com/ashanbrown/forbidigo v1.3.0/go.mod h1:vVW7PEdqEFqapJe95xHkTfB1+XvZXBFg8t0sG2FIxmI= -github.com/ashanbrown/makezero v1.1.1 h1:iCQ87C0V0vSyO+M9E/FZYbu65auqH0lnsOkf5FcB28s= -github.com/ashanbrown/makezero v1.1.1/go.mod h1:i1bJLCRSCHOcOa9Y6MyF2FTfMZMFdHvxKHxgO5Z1axI= -github.com/aws/aws-sdk-go v1.44.122 h1:p6mw01WBaNpbdP2xrisz5tIkcNwzj/HysobNoaAHjgo= -github.com/aws/aws-sdk-go v1.44.122/go.mod h1:y4AeaBuwd2Lk+GepC1E9v0qOiTws0MIWAX4oIKwKHZo= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d h1:xDfNPAt8lFiC1UJrqV3uuy861HCTo708pDMbjHHdCas= -github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d/go.mod h1:6QX/PXZ00z/TKoufEY6K/a0k6AhaJrQKdFe6OfVXsa4= -github.com/bkielbasa/cyclop v1.2.0 h1:7Jmnh0yL2DjKfw28p86YTd/B4lRGcNuu12sKE35sM7A= -github.com/bkielbasa/cyclop v1.2.0/go.mod h1:qOI0yy6A7dYC4Zgsa72Ppm9kONl0RoIlPbzot9mhmeI= -github.com/blizzy78/varnamelen v0.8.0 h1:oqSblyuQvFsW1hbBHh1zfwrKe3kcSj0rnXkKzsQ089M= -github.com/blizzy78/varnamelen v0.8.0/go.mod h1:V9TzQZ4fLJ1DSrjVDfl89H7aMnTvKkApdHeyESmyR7k= -github.com/bmatcuk/doublestar v1.1.5 h1:2bNwBOmhyFEFcoB3tGvTD5xanq+4kyOZlB8wFYbMjkk= -github.com/bmatcuk/doublestar v1.1.5/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= -github.com/bombsimon/wsl/v3 v3.3.0 h1:Mka/+kRLoQJq7g2rggtgQsjuI/K5Efd87WX96EWFxjM= -github.com/bombsimon/wsl/v3 v3.3.0/go.mod h1:st10JtZYLE4D5sC7b8xV4zTKZwAQjCH/Hy2Pm1FNZIc= -github.com/breml/bidichk v0.2.3 h1:qe6ggxpTfA8E75hdjWPZ581sY3a2lnl0IRxLQFelECI= -github.com/breml/bidichk v0.2.3/go.mod h1:8u2C6DnAy0g2cEq+k/A2+tr9O1s+vHGxWn0LTc70T2A= -github.com/breml/errchkjson v0.3.0 h1:YdDqhfqMT+I1vIxPSas44P+9Z9HzJwCeAzjB8PxP1xw= -github.com/breml/errchkjson v0.3.0/go.mod h1:9Cogkyv9gcT8HREpzi3TiqBxCqDzo8awa92zSDFcofU= -github.com/butuzov/ireturn v0.1.1 h1:QvrO2QF2+/Cx1WA/vETCIYBKtRjc30vesdoPUNo1EbY= -github.com/butuzov/ireturn v0.1.1/go.mod h1:Wh6Zl3IMtTpaIKbmwzqi6olnM9ptYQxxVacMsOEFPoc= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/charithe/durationcheck v0.0.9 h1:mPP4ucLrf/rKZiIG/a9IPXHGlh8p4CzgpyTy6EEutYk= -github.com/charithe/durationcheck v0.0.9/go.mod h1:SSbRIBVfMjCi/kEB6K65XEA83D6prSM8ap1UCpNKtgg= -github.com/chavacava/garif v0.0.0-20220630083739-93517212f375 h1:E7LT642ysztPWE0dfz43cWOvMiF42DyTRC+eZIaO4yI= -github.com/chavacava/garif v0.0.0-20220630083739-93517212f375/go.mod h1:4m1Rv7xfuwWPNKXlThldNuJvutYM6J95wNuuVmn55To= -github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI= -github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cristalhq/acmd v0.8.1/go.mod h1:LG5oa43pE/BbxtfMoImHCQN++0Su7dzipdgBjMCBVDQ= -github.com/curioswitch/go-reassign v0.2.0 h1:G9UZyOcpk/d7Gd6mqYgd8XYWFMw/znxwGDUstnC9DIo= -github.com/curioswitch/go-reassign v0.2.0/go.mod h1:x6OpXuWvgfQaMGks2BZybTngWjT84hqJfKoO8Tt/Roc= -github.com/daixiang0/gci v0.8.1 h1:T4xpSC+hmsi4CSyuYfIJdMZAr9o7xZmHpQVygMghGZ4= -github.com/daixiang0/gci v0.8.1/go.mod h1:EpVfrztufwVgQRXjnX4zuNinEpLj5OmMjtu/+MB0V0c= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denis-tingaikin/go-header v0.4.3 h1:tEaZKAlqql6SKCY++utLmkPLd6K8IBM20Ha7UVm+mtU= -github.com/denis-tingaikin/go-header v0.4.3/go.mod h1:0wOCWuN71D5qIgE2nz9KrKmuYBAC2Mra5RassOIQ2/c= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= -github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= -github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= -github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= -github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/esimonov/ifshort v1.0.4 h1:6SID4yGWfRae/M7hkVDVVyppy8q/v9OuxNdmjLQStBA= -github.com/esimonov/ifshort v1.0.4/go.mod h1:Pe8zjlRrJ80+q2CxHLfEOfTwxCZ4O+MuhcHcfgNWTk0= -github.com/ettle/strcase v0.1.1 h1:htFueZyVeE1XNnMEfbqp5r67qAN/4r6ya1ysq8Q+Zcw= -github.com/ettle/strcase v0.1.1/go.mod h1:hzDLsPC7/lwKyBOywSHEP89nt2pDgdy+No1NBA9o9VY= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= -github.com/fatih/color v1.14.1 h1:qfhVLaG5s+nCROl1zJsZRxFeYrHLqWroPOQ8BWiNb4w= -github.com/fatih/color v1.14.1/go.mod h1:2oHN61fhTpgcxD3TSWCgKDiH1+x4OiDVVGH8WlgGZGg= -github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= -github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= -github.com/firefart/nonamedreturns v1.0.4 h1:abzI1p7mAEPYuR4A+VLKn4eNDOycjYo2phmY9sfv40Y= -github.com/firefart/nonamedreturns v1.0.4/go.mod h1:TDhe/tjI1BXo48CmYbUduTV7BdIga8MAO/xbKdcVsGI= -github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= -github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= -github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= -github.com/fzipp/gocyclo v0.6.0 h1:lsblElZG7d3ALtGMx9fmxeTKZaLLpU8mET09yN4BBLo= -github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlyaLkLoA= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-critic/go-critic v0.6.5 h1:fDaR/5GWURljXwF8Eh31T2GZNz9X4jeboS912mWF8Uo= -github.com/go-critic/go-critic v0.6.5/go.mod h1:ezfP/Lh7MA6dBNn4c6ab5ALv3sKnZVLx37tr00uuaOY= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/go-test/deep v1.1.0 h1:WOcxcdHcvdgThNXjw0t76K42FXTU7HpNQWHpA2HHNlg= -github.com/go-toolsmith/astcast v1.0.0 h1:JojxlmI6STnFVG9yOImLeGREv8W2ocNUM+iOhR6jE7g= -github.com/go-toolsmith/astcast v1.0.0/go.mod h1:mt2OdQTeAQcY4DQgPSArJjHCcOwlX+Wl/kwN+LbLGQ4= -github.com/go-toolsmith/astcopy v1.0.2 h1:YnWf5Rnh1hUudj11kei53kI57quN/VH6Hp1n+erozn0= -github.com/go-toolsmith/astcopy v1.0.2/go.mod h1:4TcEdbElGc9twQEYpVo/aieIXfHhiuLh4aLAck6dO7Y= -github.com/go-toolsmith/astequal v1.0.0/go.mod h1:H+xSiq0+LtiDC11+h1G32h7Of5O3CYFJ99GVbS5lDKY= -github.com/go-toolsmith/astequal v1.0.2/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= -github.com/go-toolsmith/astequal v1.0.3 h1:+LVdyRatFS+XO78SGV4I3TCEA0AC7fKEGma+fH+674o= -github.com/go-toolsmith/astequal v1.0.3/go.mod h1:9Ai4UglvtR+4up+bAD4+hCj7iTo4m/OXVTSLnCyTAx4= -github.com/go-toolsmith/astfmt v1.0.0 h1:A0vDDXt+vsvLEdbMFJAUBI/uTbRw1ffOPnxsILnFL6k= -github.com/go-toolsmith/astfmt v1.0.0/go.mod h1:cnWmsOAuq4jJY6Ct5YWlVLmcmLMn1JUPuQIHCY7CJDw= -github.com/go-toolsmith/astp v1.0.0 h1:alXE75TXgcmupDsMK1fRAy0YUzLzqPVvBKoyWV+KPXg= -github.com/go-toolsmith/astp v1.0.0/go.mod h1:RSyrtpVlfTFGDYRbrjyWP1pYu//tSFcvdYrA8meBmLI= -github.com/go-toolsmith/pkgload v1.0.2-0.20220101231613-e814995d17c5 h1:eD9POs68PHkwrx7hAB78z1cb6PfGq/jyWn3wJywsH1o= -github.com/go-toolsmith/pkgload v1.0.2-0.20220101231613-e814995d17c5/go.mod h1:3NAwwmD4uY/yggRxoEjk/S00MIV3A+H7rrE3i87eYxM= -github.com/go-toolsmith/strparse v1.0.0 h1:Vcw78DnpCAKlM20kSbAyO4mPfJn/lyYA4BJUDxe2Jb4= -github.com/go-toolsmith/strparse v1.0.0/go.mod h1:YI2nUKP9YGZnL/L1/DLFBfixrcjslWct4wyljWhSRy8= -github.com/go-toolsmith/typep v1.0.2 h1:8xdsa1+FSIH/RhEkgnD1j2CJOy5mNllW1Q9tRiYwvlk= -github.com/go-toolsmith/typep v1.0.2/go.mod h1:JSQCQMUPdRlMZFswiq3TGpNp1GMktqkR2Ns5AIQkATU= -github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b h1:khEcpUM4yFcxg4/FHQWkvVRmgijNXRfzkIDHh23ggEo= -github.com/go-xmlfmt/xmlfmt v0.0.0-20191208150333-d5b6f63a941b/go.mod h1:aUCEOzzezBEjDBbFBoSiya/gduyIiWYRP6CnSFIV8AM= -github.com/gobwas/glob v0.2.3 h1:A4xDbljILXROh+kObIiy5kIaPYD8e96x1tgBhUI5J+Y= -github.com/gobwas/glob v0.2.3/go.mod h1:d3Ez4x06l9bZtSvzIay5+Yzi0fmZzPgnTbPcKjJAkT8= -github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw= -github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= -github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= -github.com/golang/mock v1.5.0/go.mod h1:CWnOUgYIOo4TcNZ0wHX3YZCqsaM1I1Jvs6v3mP3KVu8= -github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= -github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.1/go.mod h1:DopwsBzvsk0Fs44TXzsVbJyPhcCPeIwnvohx4u74HPM= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= -github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2 h1:23T5iq8rbUYlhpt5DB4XJkc6BU31uODLD1o1gKvZmD0= -github.com/golangci/check v0.0.0-20180506172741-cfe4005ccda2/go.mod h1:k9Qvh+8juN+UKMCS/3jFtGICgW8O96FVaZsaxdzDkR4= -github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a h1:w8hkcTqaFpzKqonE9uMCefW1WDie15eSP/4MssdenaM= -github.com/golangci/dupl v0.0.0-20180902072040-3e9179ac440a/go.mod h1:ryS0uhF+x9jgbj/N71xsEqODy9BN81/GonCZiOzirOk= -github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe h1:6RGUuS7EGotKx6J5HIP8ZtyMdiDscjMLfRBSPuzVVeo= -github.com/golangci/go-misc v0.0.0-20220329215616-d24fe342adfe/go.mod h1:gjqyPShc/m8pEMpk0a3SeagVb0kaqvhscv+i9jI5ZhQ= -github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2 h1:amWTbTGqOZ71ruzrdA+Nx5WA3tV1N0goTspwmKCQvBY= -github.com/golangci/gofmt v0.0.0-20220901101216-f2edd75033f2/go.mod h1:9wOXstvyDRshQ9LggQuzBCGysxs3b6Uo/1MvYCR2NMs= -github.com/golangci/golangci-lint v1.50.1 h1:C829clMcZXEORakZlwpk7M4iDw2XiwxxKaG504SZ9zY= -github.com/golangci/golangci-lint v1.50.1/go.mod h1:AQjHBopYS//oB8xs0y0M/dtxdKHkdhl0RvmjUct0/4w= -github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0 h1:MfyDlzVjl1hoaPzPD4Gpb/QgoRfSBR0jdhwGyAWwMSA= -github.com/golangci/lint-1 v0.0.0-20191013205115-297bf364a8e0/go.mod h1:66R6K6P6VWk9I95jvqGxkqJxVWGFy9XlDwLwVz1RCFg= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca h1:kNY3/svz5T29MYHubXix4aDDuE3RWHkPvopM/EDv/MA= -github.com/golangci/maligned v0.0.0-20180506175553-b1d89398deca/go.mod h1:tvlJhZqDe4LMs4ZHD0oMUlt9G2LWuDGoisJTBzLMV9o= -github.com/golangci/misspell v0.3.5 h1:pLzmVdl3VxTOncgzHcvLOKirdvcx/TydsClUQXTehjo= -github.com/golangci/misspell v0.3.5/go.mod h1:dEbvlSfYbMQDtrpRMQU675gSDLDNa8sCPPChZ7PhiVA= -github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6 h1:DIPQnGy2Gv2FSA4B/hh8Q7xx3B7AIDk3DAMeHclH1vQ= -github.com/golangci/revgrep v0.0.0-20220804021717-745bb2f7c2e6/go.mod h1:0AKcRCkMoKvUvlf89F6O7H2LYdhr1zBh736mBItOdRs= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4 h1:zwtduBRr5SSWhqsYNgcuWO2kFlpdOZbP0+yRjmvPGys= -github.com/golangci/unconvert v0.0.0-20180507085042-28b1c447d1f4/go.mod h1:Izgrg8RkN3rCIMLGE9CyYmU9pY2Jer6DgANEnZ/L/cQ= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= -github.com/google/go-github/v35 v35.3.0 h1:fU+WBzuukn0VssbayTT+Zo3/ESKX9JYWjbZTLOTEyho= -github.com/google/go-github/v35 v35.3.0/go.mod h1:yWB7uCcVWaUbUP74Aq3whuMySRMatyRmq5U9FTNlbio= -github.com/google/go-querystring v1.0.0 h1:Xkwi/a1rcvNg1PPYe5vI8GbeBY/jrVuDX5ASuANWTrk= -github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= -github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk= -github.com/google/martian/v3 v3.3.2 h1:IqNFLAmvJOgVlpdEBiQbDc2EwKW77amAycfTuWKdfvw= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210122040257-d980be63207e/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210226084205-cbba55b83ad5/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210601050228-01bbb1931b22/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210609004039-a478d1d731e9/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/pprof v0.0.0-20210720184732-4bb14d4b1be1/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= -github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/enterprise-certificate-proxy v0.0.0-20220520183353-fd19c99a87aa/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.1.0/go.mod h1:17drOmN3MwGY7t0e+Ei9b45FFGA3fBs3x36SsCg1hq8= -github.com/googleapis/enterprise-certificate-proxy v0.2.0/go.mod h1:8C0jb7/mgJe/9KK8Lm7X9ctZC2t60YyIpYEI16jx0Qg= -github.com/googleapis/enterprise-certificate-proxy v0.2.3 h1:yk9/cqRKtT9wXZSsRH9aurXEpJX+U6FLtpYTdC3R06k= -github.com/googleapis/enterprise-certificate-proxy v0.2.3/go.mod h1:AwSRAtLfXpU5Nm3pW+v7rGDHp09LsPtGY9MduiEsR9k= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0= -github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM= -github.com/googleapis/gax-go/v2 v2.2.0/go.mod h1:as02EH8zWkzwUoLbBaFeQ+arQaj/OthfcblKl4IGNaM= -github.com/googleapis/gax-go/v2 v2.3.0/go.mod h1:b8LNqSzNabLiUpXKkY7HAR5jr6bIT99EXz9pXxye9YM= -github.com/googleapis/gax-go/v2 v2.4.0/go.mod h1:XOTVJ59hdnfJLIP/dh8n5CGryZR2LxK9wbMD5+iXC6c= -github.com/googleapis/gax-go/v2 v2.5.1/go.mod h1:h6B0KMMFNtI2ddbGJn3T3ZbwkeT6yqEF02fYlzkUCyo= -github.com/googleapis/gax-go/v2 v2.6.0/go.mod h1:1mjbznJAPHFpesgE5ucqfYEscaz5kMdcIDwU/6+DDoY= -github.com/googleapis/gax-go/v2 v2.7.1 h1:gF4c0zjUP2H/s/hEGyLA3I0fA2ZWjzYiONAD6cvPr8A= -github.com/googleapis/gax-go/v2 v2.7.1/go.mod h1:4orTrqY6hXxxaUL4LHIPl6lGo8vAE38/qKbhSAKP6QI= -github.com/googleapis/go-type-adapters v1.0.0/go.mod h1:zHW75FOG2aur7gAO2B+MLby+cLsWGBF62rFAi7WjWO4= -github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= -github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8 h1:PVRE9d4AQKmbelZ7emNig1+NT27DUmKZn5qXxfio54U= -github.com/gordonklaus/ineffassign v0.0.0-20210914165742-4cc7213b9bc8/go.mod h1:Qcp2HIAYhR7mNUVSIxZww3Guk4it82ghYcEXIAk+QT0= -github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gostaticanalysis/analysisutil v0.0.0-20190318220348-4088753ea4d3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= -github.com/gostaticanalysis/analysisutil v0.0.3/go.mod h1:eEOZF4jCKGi+aprrirO9e7WKB3beBRtWgqGunKl6pKE= -github.com/gostaticanalysis/analysisutil v0.1.0/go.mod h1:dMhHRU9KTiDcuLGdy87/2gTR8WruwYZrKdRq9m1O6uw= -github.com/gostaticanalysis/analysisutil v0.7.1 h1:ZMCjoue3DtDWQ5WyU16YbjbQEQ3VuzwxALrpYd+HeKk= -github.com/gostaticanalysis/analysisutil v0.7.1/go.mod h1:v21E3hY37WKMGSnbsw2S/ojApNWb6C1//mXO48CXbVc= -github.com/gostaticanalysis/comment v1.3.0/go.mod h1:xMicKDx7XRXYdVwY9f9wQpDJVnqWxw9wCauCMKp+IBI= -github.com/gostaticanalysis/comment v1.4.1/go.mod h1:ih6ZxzTHLdadaiSnF5WY3dxUoXfXAlTaRzuaNDlSado= -github.com/gostaticanalysis/comment v1.4.2 h1:hlnx5+S2fY9Zo9ePo4AhgYsYHbM2+eAv8m/s1JiCd6Q= -github.com/gostaticanalysis/comment v1.4.2/go.mod h1:KLUTGDv6HOCotCH8h2erHKmpci2ZoR8VPu34YA2uzdM= -github.com/gostaticanalysis/forcetypeassert v0.1.0 h1:6eUflI3DiGusXGK6X7cCcIgVCpZ2CiZ1Q7jl6ZxNV70= -github.com/gostaticanalysis/forcetypeassert v0.1.0/go.mod h1:qZEedyP/sY1lTGV1uJ3VhWZ2mqag3IkWsDHVbplHXak= -github.com/gostaticanalysis/nilerr v0.1.1 h1:ThE+hJP0fEp4zWLkWHWcRyI2Od0p7DlgYG3Uqrmrcpk= -github.com/gostaticanalysis/nilerr v0.1.1/go.mod h1:wZYb6YI5YAxxq0i1+VJbY0s2YONW0HU0GPE3+5PWN4A= -github.com/gostaticanalysis/testutil v0.3.1-0.20210208050101-bfb5c8eec0e4/go.mod h1:D+FIZ+7OahH3ePw/izIEeH5I06eKs1IKI4Xr64/Am3M= -github.com/gostaticanalysis/testutil v0.4.0 h1:nhdCmubdmDF6VEatUNjgUZBJKWRqugoISdUv3PPQgHY= -github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= -github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/UYA= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/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-getter v1.7.1 h1:SWiSWN/42qdpR0MdhaOc/bLR48PLuP1ZQtYLRlM69uY= -github.com/hashicorp/go-getter v1.7.1/go.mod h1:W7TalhMmbPmsSMdNjD0ZskARur/9GJ17cfHTRtXV744= -github.com/hashicorp/go-hclog v1.4.0 h1:ctuWFGrhFha8BnnzxqeRGidlEcQkDyL5u8J8t5eA11I= -github.com/hashicorp/go-hclog v1.4.0/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M= -github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= -github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= -github.com/hashicorp/go-plugin v1.4.8 h1:CHGwpxYDOttQOY7HOWgETU9dyVjOXzniXDqJcYJE1zM= -github.com/hashicorp/go-plugin v1.4.8/go.mod h1:viDMjcLJuDui6pXb8U4HVfb8AamCWhHGUjr2IrTF67s= -github.com/hashicorp/go-safetemp v1.0.0 h1:2HR189eFNrjHQyENnQMMpCiBAsRxzbTMIgBhEyExpmo= -github.com/hashicorp/go-safetemp v1.0.0/go.mod h1:oaerMy3BhqiTbVye6QuFhFtIceqFoDHxNAB65b+Rj1I= -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.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.2.1/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hashicorp/hcl/v2 v2.16.0 h1:MPq1q615H+9wBAdE3EbwEd6imSohElrIguuasbQruB0= -github.com/hashicorp/hcl/v2 v2.16.0/go.mod h1:JRmR89jycNkrrqnMmvPDMd56n1rQJ2Q6KocSLCMCXng= -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-registry-address v0.1.0 h1:W6JkV9wbum+m516rCl5/NjKxCyTVaaUBbzYcMzBDO3U= -github.com/hashicorp/terraform-registry-address v0.1.0/go.mod h1:EnyO2jYO6j29DTHbJcm00E5nQTFeTtyZH3H5ycydQ5A= -github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734 h1:HKLsbzeOsfXmKNpr3GiT18XAblV0BjCbzL8KQAMZGa0= -github.com/hashicorp/terraform-svchost v0.0.0-20200729002733-f050f53b9734/go.mod h1:kNDNcF7sN4DocDLBkQYz73HGKwN1ANB1blq4lIYLYvg= -github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d h1:kJCB4vdITiW1eC1vq2e6IsrXKrZit1bv/TDYFGMp4BQ= -github.com/hashicorp/yamux v0.0.0-20181012175058-2f1d1f20f75d/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= -github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUqJM= -github.com/hexops/gotextdiff v1.0.3/go.mod h1:pSWU5MAI3yDq+fZBTazCSJysOMbxWL1BSow5/V2vxeg= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/inconshreveable/mousetrap v1.0.1 h1:U3uMjPSQEBMNp1lFxmllqCPM6P5u/Xq7Pgzkat/bFNc= -github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc= -github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/jgautheron/goconst v1.5.1 h1:HxVbL1MhydKs8R8n/HE5NPvzfaYmQJA3o879lE4+WcM= -github.com/jgautheron/goconst v1.5.1/go.mod h1:aAosetZ5zaeC/2EfMeRswtxUFBpe2Hr7HzkgX4fanO4= -github.com/jhump/protoreflect v1.6.0 h1:h5jfMVslIg6l29nsMs0D8Wj17RDVdNYti0vDN/PZZoE= -github.com/jingyugao/rowserrcheck v1.1.1 h1:zibz55j/MJtLsjP1OF4bSdgXxwL1b+Vn7Tjzq7gFzUs= -github.com/jingyugao/rowserrcheck v1.1.1/go.mod h1:4yvlZSDb3IyDTUZJUmpZfm2Hwok+Dtp+nu2qOq+er9c= -github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af h1:KA9BjwUk7KlCh6S9EAGWBt1oExIUv9WyNCiRz5amv48= -github.com/jirfag/go-printf-func-name v0.0.0-20200119135958-7558a9eaa5af/go.mod h1:HEWGJkRDzjJY2sqdDwxccsGicWEf9BQOZsq2tV+xzM0= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= -github.com/jmoiron/sqlx v1.2.0/go.mod h1:1FEQNm3xlJgrMD+FBdI9+xvCksHtbpVBBw5dYhBSsks= -github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jstemmer/go-junit-report v1.0.0 h1:8X1gzZpR+nVQLAht+L/foqOeX2l9DTZoaIPbEQHxsds= -github.com/jstemmer/go-junit-report v1.0.0/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= -github.com/julz/importas v0.1.0 h1:F78HnrsjY3cR7j0etXy5+TU1Zuy7Xt08X/1aJnH5xXY= -github.com/julz/importas v0.1.0/go.mod h1:oSFU2R4XK/P7kNBrnL/FEQlDGN1/6WoxXEjSSXO0DV0= -github.com/kisielk/errcheck v1.6.2 h1:uGQ9xI8/pgc9iOoCe7kWQgRE6SBTrCGmTSf0LrEtY7c= -github.com/kisielk/errcheck v1.6.2/go.mod h1:nXw/i/MfnvRHqXa7XXmQMUB0oNFGuBrNI8d8NLy0LPw= -github.com/kisielk/gotool v1.0.0 h1:AV2c/EiW3KqPNT9ZKl07ehoAGi4C5/01Cfbblndcapg= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/kkHAIKE/contextcheck v1.1.3 h1:l4pNvrb8JSwRd51ojtcOxOeHJzHek+MtOyXbaR0uvmw= -github.com/kkHAIKE/contextcheck v1.1.3/go.mod h1:PG/cwd6c0705/LM0KTr1acO2gORUxkSVWyLJOFW5qoo= -github.com/klauspost/compress v1.15.11 h1:Lcadnb3RKGin4FYM/orgq0qde+nc15E5Cbqg4B9Sx9c= -github.com/klauspost/compress v1.15.11/go.mod h1:QPwzmACJjUTFsnSHH934V6woptycfrDDJnH7hvFVbGM= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kulti/thelper v0.6.3 h1:ElhKf+AlItIu+xGnI990no4cE2+XaSu1ULymV2Yulxs= -github.com/kulti/thelper v0.6.3/go.mod h1:DsqKShOvP40epevkFrvIwkCMNYxMeTNjdWL4dqWHZ6I= -github.com/kunwardeep/paralleltest v1.0.6 h1:FCKYMF1OF2+RveWlABsdnmsvJrei5aoyZoaGS+Ugg8g= -github.com/kunwardeep/paralleltest v1.0.6/go.mod h1:Y0Y0XISdZM5IKm3TREQMZ6iteqn1YuwCsJO/0kL9Zes= -github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348/go.mod h1:B69LEHPfb2qLo0BaaOLcbitczOKLWTsrBG9LczfCD4k= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kyoh86/exportloopref v0.1.8 h1:5Ry/at+eFdkX9Vsdw3qU4YkvGtzuVfzT4X7S77LoN/M= -github.com/kyoh86/exportloopref v0.1.8/go.mod h1:1tUcJeiioIs7VWe5gcOObrux3lb66+sBqGZrRkMwPgg= -github.com/ldez/gomoddirectives v0.2.3 h1:y7MBaisZVDYmKvt9/l1mjNCiSA1BVn34U0ObUcJwlhA= -github.com/ldez/gomoddirectives v0.2.3/go.mod h1:cpgBogWITnCfRq2qGoDkKMEVSaarhdBr6g8G04uz6d0= -github.com/ldez/tagliatelle v0.3.1 h1:3BqVVlReVUZwafJUwQ+oxbx2BEX2vUG4Yu/NOfMiKiM= -github.com/ldez/tagliatelle v0.3.1/go.mod h1:8s6WJQwEYHbKZDsp/LjArytKOG8qaMrKQQ3mFukHs88= -github.com/leonklingele/grouper v1.1.0 h1:tC2y/ygPbMFSBOs3DcyaEMKnnwH7eYKzohOtRrf0SAg= -github.com/leonklingele/grouper v1.1.0/go.mod h1:uk3I3uDfi9B6PeUjsCKi6ndcf63Uy7snXgR4yDYQVDY= -github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= -github.com/lufeee/execinquery v1.2.1 h1:hf0Ems4SHcUGBxpGN7Jz78z1ppVkP/837ZlETPCEtOM= -github.com/lufeee/execinquery v1.2.1/go.mod h1:EC7DrEKView09ocscGHC+apXMIaorh4xqSxS/dy8SbM= -github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= -github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= -github.com/maratori/testableexamples v1.0.0 h1:dU5alXRrD8WKSjOUnmJZuzdxWOEQ57+7s93SLMxb2vI= -github.com/maratori/testableexamples v1.0.0/go.mod h1:4rhjL1n20TUTT4vdh3RDqSizKLyXp7K2u6HgraZCGzE= -github.com/maratori/testpackage v1.1.0 h1:GJY4wlzQhuBusMF1oahQCBtUV/AQ/k69IZ68vxaac2Q= -github.com/maratori/testpackage v1.1.0/go.mod h1:PeAhzU8qkCwdGEMTEupsHJNlQu2gZopMC6RjbhmHeDc= -github.com/matoous/godox v0.0.0-20210227103229-6504466cf951 h1:pWxk9e//NbPwfxat7RXkts09K+dEBJWakUWwICVqYbA= -github.com/matoous/godox v0.0.0-20210227103229-6504466cf951/go.mod h1:1BELzlh859Sh1c6+90blK8lbYy0kwQf1bYlBhBysy1s= -github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= -github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= -github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= -github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-isatty v0.0.17/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -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/mattn/go-sqlite3 v1.9.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/mbilski/exhaustivestruct v1.2.0 h1:wCBmUnSYufAHO6J4AVWY6ff+oxWxsVFrwgOdMUQePUo= -github.com/mbilski/exhaustivestruct v1.2.0/go.mod h1:OeTBVxQWoEmB2J2JCHmXWPJ0aksxSUOUy+nvtVEfzXc= -github.com/mgechev/revive v1.2.4 h1:+2Hd/S8oO2H0Ikq2+egtNwQsVhAeELHjxjIUFX5ajLI= -github.com/mgechev/revive v1.2.4/go.mod h1:iAWlQishqCuj4yhV24FTnKSXGpbAA+0SckXB8GQMX/Q= -github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU= -github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= -github.com/mitchellh/go-wordwrap v1.0.0 h1:6GlHJ/LTGMrIJbwgdqdl2eEH8o+Exx/0m8ir9Gns0u4= -github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= -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/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/moricho/tparallel v0.2.1 h1:95FytivzT6rYzdJLdtfn6m1bfFJylOJK41+lgv/EHf4= -github.com/moricho/tparallel v0.2.1/go.mod h1:fXEIZxG2vdfl0ZF8b42f5a78EhjjD5mX8qUplsoSU4k= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nakabonne/nestif v0.3.1 h1:wm28nZjhQY5HyYPx+weN3Q65k6ilSBxDb8v5S81B81U= -github.com/nakabonne/nestif v0.3.1/go.mod h1:9EtoZochLn5iUprVDmDjqGKPofoUEBL8U4Ngq6aY7OE= -github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354 h1:4kuARK6Y6FxaNu/BnU2OAaLF86eTVhP2hjTB6iMvItA= -github.com/nbutton23/zxcvbn-go v0.0.0-20210217022336-fa2cb2858354/go.mod h1:KSVJerMDfblTH7p5MZaTt+8zaT2iEk3AkVb9PQdZuE8= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= -github.com/nishanths/exhaustive v0.8.3 h1:pw5O09vwg8ZaditDp/nQRqVnrMczSJDxRDJMowvhsrM= -github.com/nishanths/exhaustive v0.8.3/go.mod h1:qj+zJJUgJ76tR92+25+03oYUhzF4R7/2Wk7fGTfCHmg= -github.com/nishanths/predeclared v0.2.2 h1:V2EPdZPliZymNAn79T8RkNApBjMmVKh5XRpLm/w98Vk= -github.com/nishanths/predeclared v0.2.2/go.mod h1:RROzoN6TnGQupbC+lqggsOlcgysk3LMK/HI84Mp280c= -github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/olekukonko/tablewriter v0.0.5 h1:P2Ga83D34wi1o9J6Wh1mRuqd4mF/x/lgBS7N7AbDhec= -github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY= -github.com/onsi/ginkgo/v2 v2.1.4 h1:GNapqRSid3zijZ9H77KrgVG4/8KqiyRsxcSxe+7ApXY= -github.com/onsi/gomega v1.20.0 h1:8W0cWlwFkflGPLltQvLRB7ZVD5HuP6ng320w2IS245Q= -github.com/otiai10/copy v1.2.0 h1:HvG945u96iNadPoG2/Ja2+AUJeW5YuFQMixq9yirC+k= -github.com/otiai10/copy v1.2.0/go.mod h1:rrF5dJ5F0t/EWSYODDu4j9/vEeYHMkc8jt0zJChqQWw= -github.com/otiai10/curr v0.0.0-20150429015615-9b4961190c95/go.mod h1:9qAhocn7zKJG+0mI8eUu6xqkFDYS2kb2saOteoSB3cE= -github.com/otiai10/curr v1.0.0/go.mod h1:LskTG5wDwr8Rs+nNQ+1LlxRjAtTZZjtJW4rMXl6j4vs= -github.com/otiai10/mint v1.3.0/go.mod h1:F5AjcsTsWUqX+Na9fpHb52P8pcRX2CI6A3ctIT91xUo= -github.com/otiai10/mint v1.3.1/go.mod h1:/yxELlJQ0ufhjUwhshSj+wFjZ78CnZ48/1wtmBH1OTc= -github.com/owenrumney/go-sarif v1.1.1 h1:QNObu6YX1igyFKhdzd7vgzmw7XsWN3/6NMGuDzBgXmE= -github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U= -github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= -github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= -github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= -github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= -github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d h1:CdDQnGF8Nq9ocOS/xlSptM1N3BbrA6/kmaep5ggwaIA= -github.com/phayes/checkstyle v0.0.0-20170904204023-bfd46e6a821d/go.mod h1:3OzsM7FXDQlpCiw2j81fOmAwQLnZnLGXVKUzeKQXIAw= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/polyfloyd/go-errorlint v1.0.5 h1:AHB5JRCjlmelh9RrLxT9sgzpalIwwq4hqE8EkwIwKdY= -github.com/polyfloyd/go-errorlint v1.0.5/go.mod h1:APVvOesVSAnne5SClsPxPdfvZTVDojXh1/G3qb5wjGI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= -github.com/prometheus/client_golang v1.12.1 h1:ZiaPsmm9uiBeaSMRznKsCDNtPCS0T3JVDGF+06gjBzk= -github.com/prometheus/client_golang v1.12.1/go.mod h1:3Z9XVyYiZYEO+YQWt3RD2R3jrbd179Rt297l4aS6nDY= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0 h1:uq5h0d+GuxiXLJLNABMgp2qUWDPiLvgCzz2dUR+/W/M= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= -github.com/prometheus/common v0.32.1 h1:hWIdL3N2HoUx3B8j3YN9mWor0qhY/NlEKZEaXxuIRh4= -github.com/prometheus/common v0.32.1/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/procfs v0.7.3 h1:4jVXhlkAyzOScmCkXBTOLRLTz8EeU+eyjrwB/EPq0VU= -github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/quasilyte/go-ruleguard v0.3.1-0.20210203134552-1b5a410e1cc8/go.mod h1:KsAh3x0e7Fkpgs+Q9pNLS5XpFSvYCEVl5gP9Pp1xp30= -github.com/quasilyte/go-ruleguard v0.3.18 h1:sd+abO1PEI9fkYennwzHn9kl3nqP6M5vE7FiOzZ+5CE= -github.com/quasilyte/go-ruleguard v0.3.18/go.mod h1:lOIzcYlgxrQ2sGJ735EHXmf/e9MJ516j16K/Ifcttvs= -github.com/quasilyte/go-ruleguard/dsl v0.3.0/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= -github.com/quasilyte/go-ruleguard/dsl v0.3.21/go.mod h1:KeCP03KrjuSO0H1kTuZQCWlQPulDV6YMIXmpQss17rU= -github.com/quasilyte/go-ruleguard/rules v0.0.0-20201231183845-9e62ed36efe1/go.mod h1:7JTjp89EGyU1d6XfBiXihJNG37wB2VRkd125Q1u7Plc= -github.com/quasilyte/go-ruleguard/rules v0.0.0-20211022131956-028d6511ab71/go.mod h1:4cgAphtvu7Ftv7vOT2ZOYhC6CvBxZixcasr8qIOTA50= -github.com/quasilyte/gogrep v0.0.0-20220828223005-86e4605de09f h1:6Gtn2i04RD0gVyYf2/IUMTIs+qYleBt4zxDqkLTcu4U= -github.com/quasilyte/gogrep v0.0.0-20220828223005-86e4605de09f/go.mod h1:Cm9lpz9NZjEoL1tgZ2OgeUKPIxL1meE7eo60Z6Sk+Ng= -github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95 h1:L8QM9bvf68pVdQ3bCFZMDmnt9yqcMBro1pC7F+IPYMY= -github.com/quasilyte/regex/syntax v0.0.0-20200407221936-30656e2c4a95/go.mod h1:rlzQ04UMyJXu/aOvhd8qT+hvDrFpiwqp8MRXDY9szc0= -github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567 h1:M8mH9eK4OUR4lu7Gd+PU1fV2/qnDNfzT635KRSObncs= -github.com/quasilyte/stdinfo v0.0.0-20220114132959-f7386bf02567/go.mod h1:DWNGW8A4Y+GyBgPuaQJuWiy0XYftx4Xm/y5Jqk9I6VQ= -github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryancurrah/gomodguard v1.2.4 h1:CpMSDKan0LtNGGhPrvupAoLeObRFjND8/tU1rEOtBp4= -github.com/ryancurrah/gomodguard v1.2.4/go.mod h1:+Kem4VjWwvFpUJRJSwa16s1tBJe+vbv02+naTow2f6M= -github.com/ryanrolds/sqlclosecheck v0.3.0 h1:AZx+Bixh8zdUBxUA1NxbxVAS78vTPq4rCb8OUZI9xFw= -github.com/ryanrolds/sqlclosecheck v0.3.0/go.mod h1:1gREqxyTGR3lVtpngyFo3hZAgk0KCtEdgEkHwDbigdA= -github.com/sanposhiho/wastedassign/v2 v2.0.6 h1:+6/hQIHKNJAUixEj6EmOngGIisyeI+T3335lYTyxRoA= -github.com/sanposhiho/wastedassign/v2 v2.0.6/go.mod h1:KyZ0MWTwxxBmfwn33zh3k1dmsbF2ud9pAAGfoLfjhtI= -github.com/sashamelentyev/interfacebloat v1.1.0 h1:xdRdJp0irL086OyW1H/RTZTr1h/tMEOsumirXcOJqAw= -github.com/sashamelentyev/interfacebloat v1.1.0/go.mod h1:+Y9yU5YdTkrNvoX0xHc84dxiN1iBi9+G8zZIhPVoNjQ= -github.com/sashamelentyev/usestdlibvars v1.20.0 h1:K6CXjqqtSYSsuyRDDC7Sjn6vTMLiSJa4ZmDkiokoqtw= -github.com/sashamelentyev/usestdlibvars v1.20.0/go.mod h1:0GaP+ecfZMXShS0A94CJn6aEuPRILv8h/VuWI9n1ygg= -github.com/securego/gosec/v2 v2.13.1 h1:7mU32qn2dyC81MH9L2kefnQyRMUarfDER3iQyMHcjYM= -github.com/securego/gosec/v2 v2.13.1/go.mod h1:EO1sImBMBWFjOTFzMWfTRrZW6M15gm60ljzrmy/wtHo= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c h1:W65qqJCIOVP4jpqPQ0YvHYKwcMEMVWIzWC5iNQQfBTU= -github.com/shazow/go-diff v0.0.0-20160112020656-b6b7b6733b8c/go.mod h1:/PevMnwAxekIXwN8qQyfc5gl2NlkB3CQlkizAbOkeBs= -github.com/shurcooL/go v0.0.0-20180423040247-9e1955d9fb6e/go.mod h1:TDJrrUr11Vxrven61rcy3hJMUqaf/CLWYhHNPmT14Lk= -github.com/shurcooL/go-goon v0.0.0-20170922171312-37c2f522c041/go.mod h1:N5mDOmsrJOB+vfqUK+7DmDyjhSLIIBnXo9lvZJj3MWQ= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/sivchari/containedctx v1.0.2 h1:0hLQKpgC53OVF1VT7CeoFHk9YKstur1XOgfYIc1yrHI= -github.com/sivchari/containedctx v1.0.2/go.mod h1:PwZOeqm4/DLoJOqMSIJs3aKqXRX4YO+uXww087KZ7Bw= -github.com/sivchari/nosnakecase v1.7.0 h1:7QkpWIRMe8x25gckkFd2A5Pi6Ymo0qgr4JrhGt95do8= -github.com/sivchari/nosnakecase v1.7.0/go.mod h1:CwDzrzPea40/GB6uynrNLiorAlgFRvRbFSgJx2Gs+QY= -github.com/sivchari/tenv v1.7.0 h1:d4laZMBK6jpe5PWepxlV9S+LC0yXqvYHiq8E6ceoVVE= -github.com/sivchari/tenv v1.7.0/go.mod h1:64yStXKSOxDfX47NlhVwND4dHwfZDdbp2Lyl018Icvg= -github.com/sonatard/noctx v0.0.1 h1:VC1Qhl6Oxx9vvWo3UDgrGXYCeKCe3Wbw7qAWL6FrmTY= -github.com/sonatard/noctx v0.0.1/go.mod h1:9D2D/EoULe8Yy2joDHJj7bv3sZoq9AaSb8B4lqBjiZI= -github.com/sourcegraph/go-diff v0.6.1 h1:hmA1LzxW0n1c3Q4YbrFgg4P99GSnebYa3x8gr0HZqLQ= -github.com/sourcegraph/go-diff v0.6.1/go.mod h1:iBszgVvyxdc8SFZ7gm69go2KDdt3ag071iBaWPF6cjs= -github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d h1:afLbh+ltiygTOB37ymZVwKlJwWZn+86syPTbrrOAydY= -github.com/sourcegraph/go-lsp v0.0.0-20200429204803-219e11d77f5d/go.mod h1:SULmZY7YNBsvNiQbrb/BEDdEJ84TGnfyUQxaHt8t8rY= -github.com/sourcegraph/jsonrpc2 v0.1.0 h1:ohJHjZ+PcaLxDUjqk2NC3tIGsVa5bXThe1ZheSXOjuk= -github.com/sourcegraph/jsonrpc2 v0.1.0/go.mod h1:ZafdZgk/axhT1cvZAPOhw+95nz2I/Ra5qMlU4gTRwIo= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= -github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= -github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= -github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= -github.com/spf13/cobra v1.6.0 h1:42a0n6jwCot1pUmomAp4T7DeMD+20LFv4Q54pxLf2LI= -github.com/spf13/cobra v1.6.0/go.mod h1:IOw/AERYS7UzyrGinqmz6HLUo219MORXGxhbaJUqzrY= -github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= -github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= -github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= -github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= -github.com/ssgreg/nlreturn/v2 v2.2.1 h1:X4XDI7jstt3ySqGU86YGAURbxw3oTDPK9sPEi6YEwQ0= -github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= -github.com/stbenjam/no-sprintf-host-port v0.1.1 h1:tYugd/yrm1O0dV+ThCbaKZh195Dfm07ysF0U6JQXczc= -github.com/stbenjam/no-sprintf-host-port v0.1.1/go.mod h1:TLhvtIvONRzdmkFiio4O8LHsN9N74I+PhRquPsxpL0I= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/testify v1.1.4/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= -github.com/tdakkota/asciicheck v0.1.1 h1:PKzG7JUTUmVspQTDqtkX9eSiLGossXTybutHwTXuO0A= -github.com/tdakkota/asciicheck v0.1.1/go.mod h1:yHp0ai0Z9gUljN3o0xMhYJnH/IcvkdTBOX2fmJ93JEM= -github.com/tenntenn/modver v1.0.1 h1:2klLppGhDgzJrScMpkj9Ujy3rXPUspSjAcev9tSEBgA= -github.com/tenntenn/modver v1.0.1/go.mod h1:bePIyQPb7UeioSRkw3Q0XeMhYZSMx9B8ePqg6SAMGH0= -github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3 h1:f+jULpRQGxTSkNYKJ51yaw6ChIqO+Je8UqsTKN/cDag= -github.com/tenntenn/text/transform v0.0.0-20200319021203-7eef512accb3/go.mod h1:ON8b8w4BN/kE1EOhwT0o+d62W65a6aPw1nouo9LMgyY= -github.com/terraform-linters/tflint v0.45.0 h1:HBzE6DtiaGBkvMijI1H93V6ka/NeEtEoR5o3BoicrMY= -github.com/terraform-linters/tflint v0.45.0/go.mod h1:DnGJmi/HTs1TNw5shadQXJ5DpbYqQpIiGylH5psjGUA= -github.com/terraform-linters/tflint-plugin-sdk v0.15.0 h1:bUJ9OskzT/I98XaJ5+rs7ymVPHiGT8oI4bG86LkopVY= -github.com/terraform-linters/tflint-plugin-sdk v0.15.0/go.mod h1:enH5i7SHelcvC2AGZavEJzcrRF7nhAaOwTdaBjr/Zjo= -github.com/terraform-linters/tflint-ruleset-terraform v0.2.2 h1:iTE09KkaZ0DE29xvp6IIM1/gmas9V0h8CER28SyBmQ8= -github.com/terraform-linters/tflint-ruleset-terraform v0.2.2/go.mod h1:bCkvH8Vqzr16bWEE3e6Q3hvdZlmSAOR8i6G3M5y+M+k= -github.com/tetafro/godot v1.4.11 h1:BVoBIqAf/2QdbFmSwAWnaIqDivZdOV0ZRwEm6jivLKw= -github.com/tetafro/godot v1.4.11/go.mod h1:LR3CJpxDVGlYOWn3ZZg1PgNZdTUvzsZWu8xaEohUpn8= -github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144 h1:kl4KhGNsJIbDHS9/4U9yQo1UcPQM0kOMJHn29EoH/Ro= -github.com/timakin/bodyclose v0.0.0-20210704033933-f49887972144/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= -github.com/timonwong/loggercheck v0.9.3 h1:ecACo9fNiHxX4/Bc02rW2+kaJIAMAes7qJ7JKxt0EZI= -github.com/timonwong/loggercheck v0.9.3/go.mod h1:wUqnk9yAOIKtGA39l1KLE9Iz0QiTocu/YZoOf+OzFdw= -github.com/tomarrell/wrapcheck/v2 v2.7.0 h1:J/F8DbSKJC83bAvC6FoZaRjZiZ/iKoueSdrEkmGeacA= -github.com/tomarrell/wrapcheck/v2 v2.7.0/go.mod h1:ao7l5p0aOlUNJKI0qVwB4Yjlqutd0IvAB9Rdwyilxvg= -github.com/tommy-muehle/go-mnd/v2 v2.5.1 h1:NowYhSdyE/1zwK9QCLeRb6USWdoif80Ie+v+yU8u1Zw= -github.com/tommy-muehle/go-mnd/v2 v2.5.1/go.mod h1:WsUAkMJMYww6l/ufffCD3m+P7LEvr8TnZn9lwVDlgzw= -github.com/ulikunitz/xz v0.5.10 h1:t92gobL9l3HE202wg3rlk19F6X+JOxl9BBrCCMYEYd8= -github.com/ulikunitz/xz v0.5.10/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= -github.com/ultraware/funlen v0.0.3 h1:5ylVWm8wsNwH5aWo9438pwvsK0QiqVuUrt9bn7S/iLA= -github.com/ultraware/funlen v0.0.3/go.mod h1:Dp4UiAus7Wdb9KUZsYWZEWiRzGuM2kXM1lPbfaF6xhA= -github.com/ultraware/whitespace v0.0.5 h1:hh+/cpIcopyMYbZNVov9iSxvJU3OYQg78Sfaqzi/CzI= -github.com/ultraware/whitespace v0.0.5/go.mod h1:aVMh/gQve5Maj9hQ/hg+F75lr/X5A89uZnzAmWSineA= -github.com/uudashr/gocognit v1.0.6 h1:2Cgi6MweCsdB6kpcVQp7EW4U23iBFQWfTXiWlyp842Y= -github.com/uudashr/gocognit v1.0.6/go.mod h1:nAIUuVBnYU7pcninia3BHOvQkpQCeO76Uscky5BOwcY= -github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= -github.com/vmihailenco/msgpack/v4 v4.3.12 h1:07s4sz9IReOgdikxLTKNbBdqDMLsjPKXwvCazn8G65U= -github.com/vmihailenco/msgpack/v4 v4.3.12/go.mod h1:gborTTJjAo/GWTqqRjrLCn9pgNN+NXzzngzBKDPIqw4= -github.com/vmihailenco/tagparser v0.1.1 h1:quXMXlA39OCbd2wAdTsGDlK9RkOk6Wuw+x37wVyIuWY= -github.com/vmihailenco/tagparser v0.1.1/go.mod h1:OeAg3pn3UbLjkWt+rN9oFYB6u/cQgqMEUPoW2WPyhdI= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= -github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= -github.com/yagipy/maintidx v1.0.0 h1:h5NvIsCz+nRDapQ0exNv4aJ0yXSI0420omVANTv3GJM= -github.com/yagipy/maintidx v1.0.0/go.mod h1:0qNf/I/CCZXSMhsRsrEPDZ+DkekpKLXAJfsTACwgXLk= -github.com/yeya24/promlinter v0.2.0 h1:xFKDQ82orCU5jQujdaD8stOHiv8UN68BSdn2a8u8Y3o= -github.com/yeya24/promlinter v0.2.0/go.mod h1:u54lkmBOZrpEbQQ6gox2zWKKLKu2SGe+2KOiextY+IA= -github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -github.com/zclconf/go-cty v1.1.0/go.mod h1:xnAOWiHeOqg2nWS62VtQ7pbOu17FtxJNW8RLEih+O3s= -github.com/zclconf/go-cty v1.10.0/go.mod h1:vVKLxnk3puL4qRAv72AO+W99LUD4da90g3uUAzyuvAk= -github.com/zclconf/go-cty v1.12.1 h1:PcupnljUm9EIvbgSHQnHhUr3fO6oFmkOrvs2BAFNXXY= -github.com/zclconf/go-cty v1.12.1/go.mod h1:s9IfD1LK5ccNMSWCVFCE2rJfHiZgi7JijgeWIMfhLvA= -github.com/zclconf/go-cty-yaml v1.0.3 h1:og/eOQ7lvA/WWhHGFETVWNduJM7Rjsv2RRpx1sdFMLc= -github.com/zclconf/go-cty-yaml v1.0.3/go.mod h1:9YLUH4g7lOhVWqUbctnVlZ5KLpg7JAprQNgxSZ1Gyxs= -gitlab.com/bosi/decorder v0.2.3 h1:gX4/RgK16ijY8V+BRQHAySfQAb354T7/xQpDB2n10P0= -gitlab.com/bosi/decorder v0.2.3/go.mod h1:9K1RB5+VPNQYtXtTDAzd2OEftsZb1oV0IrJrzChSdGE= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= -go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= -go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= -go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= -go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= -go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= -go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4= -go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= -go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U= -go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= -golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e h1:+WEEuIdZHnUeJJmEUjyYC2gfUMj69yZXw17EnHg/otA= -golang.org/x/exp v0.0.0-20220722155223-a9213eeb770e/go.mod h1:Kr81I6Kryrl9sr8s2FK3vxD90NdsKWRuOIl2O4CvYbA= -golang.org/x/exp/typeparams v0.0.0-20220428152302-39d4317da171/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/exp/typeparams v0.0.0-20220827204233-334a2380cb91 h1:Ic/qN6TEifvObMGQy72k0n1LlJr7DjWWEi+MOsDOiSk= -golang.org/x/exp/typeparams v0.0.0-20220827204233-334a2380cb91/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= -golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3/go.mod h1:3p9vT2HGsQu2K1YbXdKPJLVgG5VJdoTa1poYQBtP1AY= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191009170851-d66e71096ffb/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc= -golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= -golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220412020605-290c469a71a5/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= -golang.org/x/net v0.0.0-20220607020251-c690dde0001d/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220617184016-355a448f1bc9/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220624214902-1bab6f366d9e/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.0.0-20220909164309-bea034e7d591/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.0.0-20221014081412-f15817d10f9b/go.mod h1:YDH+HFinaLZZlnHAfSS6ZXJJ9M9t4Dl22yv3iI2vPwk= -golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210220000619-9bb904979d93/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= -golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220411215720-9780585627b5/go.mod h1:DAh4E804XQdzx2j+YRIaUnCqCV2RuMz24cGBJ5QYIrc= -golang.org/x/oauth2 v0.0.0-20220608161450-d0670ef3b1eb/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220622183110-fd043fe589d2/go.mod h1:jaDAt6Dkxork7LmZnYtzbRWj0W47D86a3TGe0YHBvmE= -golang.org/x/oauth2 v0.0.0-20220822191816-0ebed06d0094/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20220909003341-f21342109be1/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.0.0-20221014153046-6fdb5e3db783/go.mod h1:h4gKUeWbJ4rQPri7E0u6Gs4e9Ri2zaLxzw5DI5XGrYg= -golang.org/x/oauth2 v0.1.0/go.mod h1:G9FE4dLTsbXUu90h/Pf85g4w1D+SSAgR+q46nJZ8M4A= -golang.org/x/oauth2 v0.7.0 h1:qe6s0zUXlPX80/dITx3440hWZ7GwMwgDDyrSGTPJG/g= -golang.org/x/oauth2 v0.7.0/go.mod h1:hPLQkd9LyjfXTiRohC/41GhcFqxisoUQ99sCUOHO9x4= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/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-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20220601150217-0de741cfad7f/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.0.0-20220929204114-8fcdb60fdcc0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210603125802-9665404d3644/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/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-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/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-20211019181941-9d821ace8654/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211105183446-c75c47738b0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211210111614-af8b64212486/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220328115105-d36c6a25d886/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220502124256-b6088ccd6cba/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-20220610221304-9f5ed59c137d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220615213510-4f61da869c0c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220624220833-87e55d714810/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220702020025-31831981b65f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= -golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -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.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.15.0 h1:y/Oo/a/q3IXu26lQgl04j/gjuBDOBlx7X6Om1j2CPW4= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= -golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190307163923-6a08e3108db3/go.mod h1:25r3+/G6/xytQM8iWZKq3Hn0kr0rgFKPUNVEL/dr3z4= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190311215038-5c2858a9cfe5/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190321232350-e250d351ecad/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190322203728-c1a832b0ad89/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190910044552-dd2b5c81c578/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190916130336-e45ffcd953cc/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191108193012-7d206e10da11/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117220505-0cba7a3a9ee9/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= -golang.org/x/tools v0.0.0-20200324003944-a576cf524670/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200329025819-fd4102a86c65/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= -golang.org/x/tools v0.0.0-20200414032229-332987a829c3/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200622203043-20e05c1c8ffa/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200624225443-88f3c62a19ff/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200625211823-6506e20df31f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20200724022722-7017fd6b1305/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200812195022-5ae4c3c160a0/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200820010801-b793a1359eac/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= -golang.org/x/tools v0.0.0-20200831203904-5a2aa26beb65/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= -golang.org/x/tools v0.0.0-20201001104356-43ebab892c4c/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= -golang.org/x/tools v0.0.0-20201002184944-ecd9fd270d5d/go.mod h1:z6u4i615ZeAfBE4XtMziQW1fSVJXACjjbWkB/mvPzlU= -golang.org/x/tools v0.0.0-20201023174141-c8cfbd0f21e6/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20201230224404-63754364767c/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= -golang.org/x/tools v0.1.1-0.20210205202024-ef80cdb6ec6d/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1-0.20210302220138-2ac05c832e1a/go.mod h1:9bzcO0MWcOuT0tm1iBGzDVPshzfwoVvREIui8C+MHqU= -golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.3/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= -golang.org/x/tools v0.1.9-0.20211228192929-ee1ca4ffc4da/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/tools v0.1.10/go.mod h1:Uh6Zz+xoGYZom868N8YTex3t7RhtHDBrE8Gzo9bV56E= -golang.org/x/tools v0.1.11/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= -golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.12.0 h1:YW6HUoUmYBpwSgyaGaZq1fHjrBjX1rlpZ54T6mu2kss= -golang.org/x/tools v0.12.0/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220411194840-2f41105eb62f/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20220517211312-f3a8303e98df/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 h1:H2TDz8ibqkAF6YGhCdN3jS9O0/s90v0rJh3X/OLHEUk= -golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= -google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= -google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= -google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= -google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= -google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= -google.golang.org/api v0.41.0/go.mod h1:RkxM5lITDfTzmyKFPt+wGrCJbVfniCr2ool8kTBzRTU= -google.golang.org/api v0.43.0/go.mod h1:nQsDGjRXMo4lvh5hP0TKqF244gqhGcr/YSIykhUk/94= -google.golang.org/api v0.47.0/go.mod h1:Wbvgpq1HddcWVtzsVLyfLp8lDg6AA241LmgIL59tHXo= -google.golang.org/api v0.48.0/go.mod h1:71Pr1vy+TAZRPkPs/xlCf5SsU8WjuAWv1Pfjbtukyy4= -google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNefaw= -google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU= -google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k= -google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE= -google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI= -google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I= -google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo= -google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g= -google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA= -google.golang.org/api v0.71.0/go.mod h1:4PyU6e6JogV1f9eA4voyrTY2batOLdgZ5qZ5HOCc4j8= -google.golang.org/api v0.74.0/go.mod h1:ZpfMZOVRMywNyvJFeqL9HRWBgAuRfSjJFpe9QtRRyDs= -google.golang.org/api v0.75.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.77.0/go.mod h1:pU9QmyHLnzlpar1Mjt4IbapUCy8J+6HD6GeELN69ljA= -google.golang.org/api v0.78.0/go.mod h1:1Sg78yoMLOhlQTeF+ARBoytAcH1NNyyl390YMy6rKmw= -google.golang.org/api v0.80.0/go.mod h1:xY3nI94gbvBrE0J6NHXhxOmW97HG7Khjkku6AFB3Hyg= -google.golang.org/api v0.84.0/go.mod h1:NTsGnUFJMYROtiquksZHBWtHfeMC7iYthki7Eq3pa8o= -google.golang.org/api v0.85.0/go.mod h1:AqZf8Ep9uZ2pyTvgL+x0D3Zt0eoT9b5E8fmzfu6FO2g= -google.golang.org/api v0.90.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.93.0/go.mod h1:+Sem1dnrKlrXMR/X0bPnMWyluQe4RsNoYfmNLhOIkzw= -google.golang.org/api v0.95.0/go.mod h1:eADj+UBuxkh5zlrSntJghuNeg8HwQ1w5lTKkuqaETEI= -google.golang.org/api v0.96.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.97.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.98.0/go.mod h1:w7wJQLTM+wvQpNf5JyEcBoxK0RH7EDrh/L4qfsuJ13s= -google.golang.org/api v0.100.0/go.mod h1:ZE3Z2+ZOr87Rx7dqFsdRQkRBk36kDtp/h+QpHbB7a70= -google.golang.org/api v0.114.0 h1:1xQPji6cO2E2vLiI+C/XiFAnsn1WV3mjaEwGLhi3grE= -google.golang.org/api v0.114.0/go.mod h1:ifYI2ZsFK6/uGddGfAD5BMxlnkBqCmqHSDUVi45N5Yg= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/appengine v1.6.7 h1:FZR1q0exgwxzPzp/aF+VccGrSfxfPpkBqjIIEq3ru6c= -google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= -google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210222152913-aa3ee6e6a81c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210303154014-9728d6b83eeb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210310155132-4ce2db91004e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210319143718-93e7006c17a6/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/genproto v0.0.0-20210329143202-679c6ae281ee/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210402141018-6c239bbf2bb1/go.mod h1:9lPAdzaEmUacj36I+k7YKbEc5CXzPIeORRgDAUOu28A= -google.golang.org/genproto v0.0.0-20210513213006-bf773b8c8384/go.mod h1:P3QM42oQyzQSnHPnZ/vqoCdDmzH28fzWByN9asMeM8A= -google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210604141403-392c879c8b08/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210608205507-b6d2f5bf0d7d/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= -google.golang.org/genproto v0.0.0-20210624195500-8bfb893ecb84/go.mod h1:SzzZ/N+nwJDaO1kznhnlzqS8ocJICar6hYhVyhi++24= -google.golang.org/genproto v0.0.0-20210713002101-d411969a0d9a/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210716133855-ce7ef5c701ea/go.mod h1:AxrInvYm1dci+enl5hChSFPOmmUF1+uAa/UsgNRWd7k= -google.golang.org/genproto v0.0.0-20210728212813-7823e685a01f/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210805201207-89edb61ffb67/go.mod h1:ob2IJxKrgPT52GcgX759i1sleT07tiKowYBGbczaW48= -google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwySK631ADgubI+/XFU/xp8FD5KIVV4rj8UC5w= -google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= -google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211206160659-862468c7d6e0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc= -google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220304144024-325a89244dc8/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220310185008-1973136f34c6/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI= -google.golang.org/genproto v0.0.0-20220324131243-acbaeb5b85eb/go.mod h1:hAL49I2IFola2sVEjAn7MEwsja0xp51I0tlGAf9hz4E= -google.golang.org/genproto v0.0.0-20220407144326-9054f6ed7bac/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220413183235-5e96e2839df9/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220414192740-2d67ff6cf2b4/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220421151946-72621c1f0bd3/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220429170224-98d788798c3e/go.mod h1:8w6bsBMX6yCPbAVTeqQHvzxW0EIFigd5lZyahWgyfDo= -google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220505152158-f39f71e6c8f3/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220518221133-4f43b3371335/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220523171625-347a074981d8/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4= -google.golang.org/genproto v0.0.0-20220608133413-ed9918b62aac/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220616135557-88e70c0c3a90/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220617124728-180714bec0ad/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220624142145-8cd45d7dbd1f/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220628213854-d9e0b6570c03/go.mod h1:KEWEmljWE5zPzLBa/oHl6DaEt9LmfH6WtH1OHIvleBA= -google.golang.org/genproto v0.0.0-20220722212130-b98a9ff5e252/go.mod h1:GkXuJDJ6aQ7lnJcRF+SJVgFdQhypqgl3LB1C9vabdRE= -google.golang.org/genproto v0.0.0-20220801145646-83ce21fca29f/go.mod h1:iHe1svFLAZg9VWz891+QbRMwUv9O/1Ww+/mngYeThbc= -google.golang.org/genproto v0.0.0-20220815135757-37a418bb8959/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220817144833-d7fd3f11b9b1/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829144015-23454907ede3/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220829175752-36a9c930ecbf/go.mod h1:dbqgFATTzChvnt+ujMdZwITVAJHFtfyN1qUhDqEiIlk= -google.golang.org/genproto v0.0.0-20220913154956-18f8339a66a5/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220914142337-ca0e39ece12f/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220915135415-7fd63a7952de/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220916172020-2692e8806bfa/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220919141832-68c03719ef51/go.mod h1:0Nb8Qy+Sk5eDzHnzlStwW3itdNaWoZA5XeSG+R3JHSo= -google.golang.org/genproto v0.0.0-20220920201722-2b89144ce006/go.mod h1:ht8XFiar2npT/g4vkk7O0WYS1sHOHbdujxbEp7CJWbw= -google.golang.org/genproto v0.0.0-20220926165614-551eb538f295/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20220926220553-6981cbe3cfce/go.mod h1:woMGP53BroOrRY3xTxlbr8Y3eB/nzAvvFM83q7kG2OI= -google.golang.org/genproto v0.0.0-20221010155953-15ba04fc1c0e/go.mod h1:3526vdqwhZAwq4wsRUaVG555sVgsNmIjRtO7t/JH29U= -google.golang.org/genproto v0.0.0-20221014173430-6e2ab493f96b/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221014213838-99cd37c6964a/go.mod h1:1vXfmgAz9N9Jx0QA82PqRVauvCz1SGSz739p0f183jM= -google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71/go.mod h1:9qHF0xnpdSfF6knlcsnpzUu5y+rpwgbvsyGAZPBMg4s= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 h1:KpwkzHKEF7B9Zxg18WzOa7djJ+Ha5DzthMyZYQfEn2A= -google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1/go.mod h1:nKE/iIaLqn2bQwXBg8f1g2Ylh6r5MN5CmZvuzZCgsCU= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= -google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= -google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.36.1/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= -google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= -google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE= -google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.40.1/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= -google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU= -google.golang.org/grpc v1.45.0/go.mod h1:lN7owxKUQEqMfSyQikvvk5tf/6zMPsrK+ONuO11+0rQ= -google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.46.2/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.48.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk= -google.golang.org/grpc v1.49.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.0/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.50.1/go.mod h1:ZgQEeidpAuNRZ8iRrlBKXZQP1ghovWIVhdJRyCDK+GI= -google.golang.org/grpc v1.56.3 h1:8I4C0Yq1EjstUzUJzpcRVbuYA2mODtEmpWiQoN/b2nc= -google.golang.org/grpc v1.56.3/go.mod h1:I9bI3vqKfayGqPUAwGdOSu7kt6oIJLixfffKrpXqQ9s= -google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= -google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= -google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= -google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= -google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= -google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -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.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= -google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/cheggaaa/pb.v1 v1.0.27/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= -gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -honnef.co/go/tools v0.3.3 h1:oDx7VAwstgpYpb3wv0oxiZlxY+foCpRAwY7Vk6XpAgA= -honnef.co/go/tools v0.3.3/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw= -mvdan.cc/gofumpt v0.4.0 h1:JVf4NN1mIpHogBj7ABpgOyZc65/UUOkKQFkoURsz4MM= -mvdan.cc/gofumpt v0.4.0/go.mod h1:PljLOHDeZqgS8opHRKLzp2It2VBuSdteAgqUfzMTxlQ= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed h1:WX1yoOaKQfddO/mLzdV4wptyWgoH/6hwLs7QHTixo0I= -mvdan.cc/interfacer v0.0.0-20180901003855-c20040233aed/go.mod h1:Xkxe497xwlCKkIaQYRfC7CSLworTXY9RMqwhhCm+8Nc= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b h1:DxJ5nJdkhDlLok9K6qO+5290kphDJbHOQO1DFFFTeBo= -mvdan.cc/lint v0.0.0-20170908181259-adc824a0674b/go.mod h1:2odslEg/xrtNQqCYg2/jCoyKnw3vv5biOc3JnIcYfL4= -mvdan.cc/unparam v0.0.0-20220706161116-678bad134442 h1:seuXWbRB1qPrS3NQnHmFKLJLtskWyueeIzmLXghMGgk= -mvdan.cc/unparam v0.0.0-20220706161116-678bad134442/go.mod h1:F/Cxw/6mVrNKqrR2YjFf5CaW0Bw4RL8RfbEf4GRggJk= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +golang.org/x/mod v0.13.0 h1:I/DsJXRlw/8l/0c24sM9yb0T4z9liZTduXvdAWYiysY= +golang.org/x/mod v0.13.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= diff --git a/tools/tools.go b/tools/tools.go index 2bdc3be24..34596d183 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -3,12 +3,8 @@ // Package tools is used for managing developer tools for this project package tools -//go:generate go install github.com/golangci/golangci-lint/cmd/golangci-lint -//go:generate go install github.com/terraform-linters/tflint //go:generate go install golang.org/x/tools/cmd/goimports import ( - _ "github.com/golangci/golangci-lint/cmd/golangci-lint" - _ "github.com/terraform-linters/tflint" _ "golang.org/x/tools/cmd/goimports" )