From 748a103b9c966d946120d10f9d6f6a3897b126a5 Mon Sep 17 00:00:00 2001 From: Ben Gesoff Date: Wed, 17 Feb 2021 11:04:57 +0000 Subject: [PATCH] Resources for Custom TLS and Platform TLS products (#364) * TLS Custom Private Key Resource * TLS Configuration Data Source * TLS Custom Certificate Resource * TLS Custom Private Key Data Source * TLS Custom Certificate Data Source Also add sweepers for TLS certificates and private keys to easily clean up resources leaked during any failed tests. * TLS Activation Resource * TLS Activation Data Source * TLS Private Key IDs Data Source * TLS Custom Certificate IDs Data Source * TLS Configuration IDs Data Source * TLS Activation IDs Data Source * Change test names to use underscore Terraform testing style guide seems to suggest camel case is used for the main test name then an underscore separates different variations of it. * Add dns_records attribute to TLS Configuration Data Source * TLS Platform Certificate Resource * Update go-fastly reference based on merged PR * TLS Platform Certificate Data Source * TLS Platform Certificate IDs Data Source * Platform TLS Data Sources documentation * Remove redundant `id` filter function * Bring in changes from upstream and modify new code to match Main changes were moving docs generation to tfplugindocs, and updating the go-fastly SDK to v3. I added some changes to the upstream docs generation to avoid having to globally install tfplugindocs. This was also done upstream so I had to do some large merge conflict resolution in this commit to combine the similar but different updates. One commit message related to vendoring tfplugindocs was: > Don't cache dependencies in github PR workflow, instead rely on /vendor > > Including the tfplugindocs module in vendor means it's updating with `go > mod vendor` along with the other libraries used. When running `go > install`, this vendored copy is used, and installed to a project-local > /bin directory. This enables the version of tfplugindocs used to be > independent of other go projects installed on one's system. > > This change means `make dependencies` is no longer used, and isn't > needed in the github PR workflow. Furthermore, the source code for the > tool is included in the /vendor already so the caching of ~/go/* isn't > required either. * Stylistic tweaks to make resources more aligned A couple naming/structure things resulting from different people writing the code. Have just tidied them up before PRing. * Add TLSCLientCert and TLSClientKey options for splunk logging (#353) * Add TLSCLientCert and TLSClientKey options for splunk logging * Add some comments to clarify the usage splunk test tls cert values * Update fastly/block_fastly_service_v1_splunk_test.go * Update fastly/block_fastly_service_v1_splunk_test.go * Update fastly/block_fastly_service_v1_splunk_test.go * Update fastly/block_fastly_service_v1_splunk_test.go * Update fastly/block_fastly_service_v1_splunk_test.go * Update fastly/block_fastly_service_v1_splunk_test.go * Update fastly/block_fastly_service_v1_splunk_test.go Co-authored-by: Mark McDonnell * Update go-fastly reference * Support multiple certificates in platform certificate intermediates_blob The intermediates_blob field of the fastly_tls_platform_certificate resource can contain PEM blocks representing an arbitrary length chain of certificates. The validation function for this field has been updated to reflect this. It now loops through the provided string and checks that each block it finds matches the expected block type until it reaches the end of the string. Similarly the validation function for one single block has been updated to fail if the string contains more than one PEM block. * Use allow_untrusted_root in platform certificate update function Was only used in creation function but should have also been used in update too. * Update go-fastly reference to v3.3.0 Also removed the `replace` directive in the go.mod to remove dependency on opencredo fork. * First updates from PR feedback - removal of unneeded .gitignore entry - removal of superfluous whitespace in docs example block - conversion of TypeList to TypeSet in plural data sources' `ids` field - a couple typo fixes here and there - removal of Set function for controlling set hashing, unneeded - consolidation of function naming to include "Fastly" before resource name - fix some acctest.RandomWithPrefix with duplicate prefix - clarify some comments - add some checks in testAcc.*Exists functions when accessing the map of resources in state to avoid a panic if resource not found * Make tfplugindocs location configurable with flag instead of PATH Add a -tfplugindocsPath command line argument to the parsing script to make it a bit more robust than dynamically setting the PATH variable in the Makefile. Defaults to local bin, as the Makefile expects, but I still set the argument in the Makefile in case someone modifies the BIN variable. Co-authored-by: Will May Co-authored-by: Trent Rosenbaum Co-authored-by: Kelly McLaughlin Co-authored-by: Mark McDonnell --- .github/workflows/pr.yml | 19 - GNUmakefile | 23 +- docs/data-sources/tls_activation.md | 38 + docs/data-sources/tls_activation_ids.md | 39 + docs/data-sources/tls_certificate.md | 43 + docs/data-sources/tls_certificate_ids.md | 32 + docs/data-sources/tls_configuration.md | 56 + docs/data-sources/tls_configuration_ids.md | 32 + docs/data-sources/tls_platform_certificate.md | 41 + .../tls_platform_certificate_ids.md | 31 + docs/data-sources/tls_private_key.md | 46 + docs/data-sources/tls_private_key_ids.md | 31 + docs/resources/tls_activation.md | 75 + docs/resources/tls_certificate.md | 84 + docs/resources/tls_platform_certificate.md | 119 ++ docs/resources/tls_private_key.md | 55 + fastly/config.go | 2 +- fastly/data_source_fastly_tls_activation.go | 163 ++ .../data_source_fastly_tls_activation_ids.go | 72 + ...a_source_fastly_tls_activation_ids_test.go | 113 ++ .../data_source_fastly_tls_activation_test.go | 158 ++ fastly/data_source_fastly_tls_certificate.go | 223 +++ .../data_source_fastly_tls_certificate_ids.go | 46 + ..._source_fastly_tls_certificate_ids_test.go | 61 + ...data_source_fastly_tls_certificate_test.go | 76 + .../data_source_fastly_tls_configuration.go | 271 +++ ...ata_source_fastly_tls_configuration_ids.go | 44 + ...ource_fastly_tls_configuration_ids_test.go | 21 + ...ta_source_fastly_tls_configuration_test.go | 63 + ..._source_fastly_tls_platform_certificate.go | 186 ++ ...rce_fastly_tls_platform_certificate_ids.go | 46 + ...astly_tls_platform_certificate_ids_test.go | 72 + ...ce_fastly_tls_platform_certificate_test.go | 76 + fastly/data_source_fastly_tls_private_key.go | 189 ++ .../data_source_fastly_tls_private_key_ids.go | 47 + ..._source_fastly_tls_private_key_ids_test.go | 85 + ...data_source_fastly_tls_private_key_test.go | 82 + fastly/provider.go | 20 +- fastly/provider_test.go | 86 +- fastly/resource_fastly_tls_activation.go | 123 ++ fastly/resource_fastly_tls_activation_test.go | 163 ++ fastly/resource_fastly_tls_certificate.go | 177 ++ .../resource_fastly_tls_certificate_test.go | 200 ++ ...esource_fastly_tls_platform_certificate.go | 167 ++ ...ce_fastly_tls_platform_certificate_test.go | 151 ++ fastly/resource_fastly_tls_private_key.go | 122 ++ .../resource_fastly_tls_private_key_test.go | 135 ++ fastly/sweeper_test.go | 48 + fastly/tls_test.go | 205 +++ fastly/validators.go | 45 + fastly/validators_test.go | 63 + go.mod | 3 +- go.sum | 28 +- scripts/generate-docs.go | 84 +- templates/data-sources/tls_activation.md.tmpl | 26 + .../data-sources/tls_activation_ids.md.tmpl | 29 + .../data-sources/tls_certificate.md.tmpl | 26 + .../data-sources/tls_certificate_ids.md.tmpl | 23 + .../data-sources/tls_configuration.md.tmpl | 31 + .../tls_configuration_ids.md.tmpl | 23 + .../tls_platform_certificate.md.tmpl | 26 + .../tls_platform_certificate_ids.md.tmpl | 22 + .../data-sources/tls_private_key.md.tmpl | 32 + .../data-sources/tls_private_key_ids.md.tmpl | 22 + templates/resources/tls_activation.md.tmpl | 60 + templates/resources/tls_certificate.md.tmpl | 63 + .../tls_platform_certificate.md.tmpl | 98 + templates/resources/tls_private_key.md.tmpl | 37 + .../fastly/go-fastly/v3/fastly/blobstorage.go | 3 + .../fastly/go-fastly/v3/fastly/client.go | 2 +- .../fastly/go-fastly/v3/fastly/cloudfiles.go | 3 + .../v3/fastly/custom_tls_activation.go | 5 +- .../v3/fastly/custom_tls_certificate.go | 15 +- .../go-fastly/v3/fastly/custom_tls_domain.go | 92 + .../go-fastly/v3/fastly/digitalocean.go | 3 + .../fastly/go-fastly/v3/fastly/errors.go | 4 + .../go-fastly/v3/fastly/platform_tls.go | 31 +- .../fastly/go-fastly/v3/fastly/tls.go | 19 +- .../go-fastly/v3/fastly/tls_subscription.go | 231 +++ vendor/github.com/google/jsonapi/.travis.yml | 10 +- vendor/github.com/google/jsonapi/README.md | 22 +- vendor/github.com/google/jsonapi/doc.go | 2 +- vendor/github.com/google/jsonapi/errors.go | 5 +- vendor/github.com/google/jsonapi/request.go | 561 +++--- vendor/github.com/google/jsonapi/response.go | 27 +- vendor/github.com/google/jsonapi/runtime.go | 26 + .../stretchr/testify/require/doc.go | 28 + .../testify/require/forward_requirements.go | 16 + .../stretchr/testify/require/require.go | 1631 +++++++++++++++++ .../stretchr/testify/require/require.go.tmpl | 6 + .../testify/require/require_forward.go | 1277 +++++++++++++ .../testify/require/require_forward.go.tmpl | 5 + .../stretchr/testify/require/requirements.go | 29 + vendor/modules.txt | 6 +- 94 files changed, 8816 insertions(+), 441 deletions(-) create mode 100644 docs/data-sources/tls_activation.md create mode 100644 docs/data-sources/tls_activation_ids.md create mode 100644 docs/data-sources/tls_certificate.md create mode 100644 docs/data-sources/tls_certificate_ids.md create mode 100644 docs/data-sources/tls_configuration.md create mode 100644 docs/data-sources/tls_configuration_ids.md create mode 100644 docs/data-sources/tls_platform_certificate.md create mode 100644 docs/data-sources/tls_platform_certificate_ids.md create mode 100644 docs/data-sources/tls_private_key.md create mode 100644 docs/data-sources/tls_private_key_ids.md create mode 100644 docs/resources/tls_activation.md create mode 100644 docs/resources/tls_certificate.md create mode 100644 docs/resources/tls_platform_certificate.md create mode 100644 docs/resources/tls_private_key.md create mode 100644 fastly/data_source_fastly_tls_activation.go create mode 100644 fastly/data_source_fastly_tls_activation_ids.go create mode 100644 fastly/data_source_fastly_tls_activation_ids_test.go create mode 100644 fastly/data_source_fastly_tls_activation_test.go create mode 100644 fastly/data_source_fastly_tls_certificate.go create mode 100644 fastly/data_source_fastly_tls_certificate_ids.go create mode 100644 fastly/data_source_fastly_tls_certificate_ids_test.go create mode 100644 fastly/data_source_fastly_tls_certificate_test.go create mode 100644 fastly/data_source_fastly_tls_configuration.go create mode 100644 fastly/data_source_fastly_tls_configuration_ids.go create mode 100644 fastly/data_source_fastly_tls_configuration_ids_test.go create mode 100644 fastly/data_source_fastly_tls_configuration_test.go create mode 100644 fastly/data_source_fastly_tls_platform_certificate.go create mode 100644 fastly/data_source_fastly_tls_platform_certificate_ids.go create mode 100644 fastly/data_source_fastly_tls_platform_certificate_ids_test.go create mode 100644 fastly/data_source_fastly_tls_platform_certificate_test.go create mode 100644 fastly/data_source_fastly_tls_private_key.go create mode 100644 fastly/data_source_fastly_tls_private_key_ids.go create mode 100644 fastly/data_source_fastly_tls_private_key_ids_test.go create mode 100644 fastly/data_source_fastly_tls_private_key_test.go create mode 100644 fastly/resource_fastly_tls_activation.go create mode 100644 fastly/resource_fastly_tls_activation_test.go create mode 100644 fastly/resource_fastly_tls_certificate.go create mode 100644 fastly/resource_fastly_tls_certificate_test.go create mode 100644 fastly/resource_fastly_tls_platform_certificate.go create mode 100644 fastly/resource_fastly_tls_platform_certificate_test.go create mode 100644 fastly/resource_fastly_tls_private_key.go create mode 100644 fastly/resource_fastly_tls_private_key_test.go create mode 100644 fastly/sweeper_test.go create mode 100644 fastly/tls_test.go create mode 100644 templates/data-sources/tls_activation.md.tmpl create mode 100644 templates/data-sources/tls_activation_ids.md.tmpl create mode 100644 templates/data-sources/tls_certificate.md.tmpl create mode 100644 templates/data-sources/tls_certificate_ids.md.tmpl create mode 100644 templates/data-sources/tls_configuration.md.tmpl create mode 100644 templates/data-sources/tls_configuration_ids.md.tmpl create mode 100644 templates/data-sources/tls_platform_certificate.md.tmpl create mode 100644 templates/data-sources/tls_platform_certificate_ids.md.tmpl create mode 100644 templates/data-sources/tls_private_key.md.tmpl create mode 100644 templates/data-sources/tls_private_key_ids.md.tmpl create mode 100644 templates/resources/tls_activation.md.tmpl create mode 100644 templates/resources/tls_certificate.md.tmpl create mode 100644 templates/resources/tls_platform_certificate.md.tmpl create mode 100644 templates/resources/tls_private_key.md.tmpl create mode 100644 vendor/github.com/fastly/go-fastly/v3/fastly/custom_tls_domain.go create mode 100644 vendor/github.com/fastly/go-fastly/v3/fastly/tls_subscription.go create mode 100644 vendor/github.com/stretchr/testify/require/doc.go create mode 100644 vendor/github.com/stretchr/testify/require/forward_requirements.go create mode 100644 vendor/github.com/stretchr/testify/require/require.go create mode 100644 vendor/github.com/stretchr/testify/require/require.go.tmpl create mode 100644 vendor/github.com/stretchr/testify/require/require_forward.go create mode 100644 vendor/github.com/stretchr/testify/require/require_forward.go.tmpl create mode 100644 vendor/github.com/stretchr/testify/require/requirements.go diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index 5b1ef1eaa..210b2e5ea 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -52,25 +52,6 @@ jobs: uses: actions/setup-go@v2 with: go-version: 1.14.x - - name: Restore cached binaries - id: cache - uses: actions/cache@v2 - with: - path: ~/go/bin - key: ${{ runner.os }}-go-bin-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go-bin- - - name: Restore cached modules dependencies - uses: actions/cache@v2 - with: - path: ~/go/pkg/mod - key: ${{ runner.os }}-go-mod-${{ hashFiles('**/go.sum') }} - restore-keys: | - ${{ runner.os }}-go-mod- - - name: Install dependencies - if: steps.cache.outputs.cache-hit != 'true' - run: make dependencies - shell: bash - name: Generate Docs run: | make generate-docs diff --git a/GNUmakefile b/GNUmakefile index c0e8f603b..5e3492607 100644 --- a/GNUmakefile +++ b/GNUmakefile @@ -52,18 +52,19 @@ test-compile: fi go test -c $(TEST) $(TESTARGS) -dependencies: - @echo "Download go.mod dependencies" - @go mod download - -install-tools: dependencies +BIN=$(CURDIR)/bin +$(BIN)/%: @echo "Installing tools from tools/tools.go" - @cat tools/tools.go | grep _ | awk -F '"' '{print $$2}' | xargs -tI {} go install {} + @cat tools/tools.go | grep _ | awk -F '"' '{print $$2}' | GOBIN=$(BIN) xargs -tI {} go install {} + +generate-docs: $(BIN)/tfplugindocs + go run scripts/generate-docs.go -tfplugindocsPath=$(BIN)/tfplugindocs -generate-docs: install-tools - go run scripts/generate-docs.go +validate-docs: $(BIN)/tfplugindocs + $(BIN)/tfplugindocs validate -validate-docs: install-tools - tfplugindocs validate +sweep: + @echo "WARNING: This will destroy infrastructure. Use only in development accounts." + go test ./fastly -v -sweep=ALL $(SWEEPARGS) -timeout 30m -.PHONY: build test testacc vet fmt fmtcheck errcheck test-compile validate-docs generate-docs install-tools dependencies +.PHONY: build test testacc vet fmt fmtcheck errcheck test-compile sweep validate-docs generate-docs diff --git a/docs/data-sources/tls_activation.md b/docs/data-sources/tls_activation.md new file mode 100644 index 000000000..c92923be1 --- /dev/null +++ b/docs/data-sources/tls_activation.md @@ -0,0 +1,38 @@ +--- +layout: "fastly" +page_title: "Fastly: fastly_tls_activation" +sidebar_current: "docs-fastly-datasource-tls_activation" +description: |- +Get information on Fastly TLS Activation. +--- + +# fastly_tls_activation + +Use this data source to get information on a TLS activation, including the certificate used, and the domain on which TLS was enabled. + +~> **Warning:** The data source's filters are applied using an **AND** boolean operator, so depending on the combination +of filters, they may become mutually exclusive. The exception to this is `id` which must not be specified in combination +with any of the others. + +~> **Note:** If more or less than a single match is returned by the search, Terraform will fail. Ensure that your search is specific enough to return a single key. + +## Example Usage + +```hcl +data "fastly_tls_activation" "example" { + domain = "example.com" +} +``` + +## Schema + +### Optional + +- **certificate_id** (String) ID of the TLS Certificate used. +- **configuration_id** (String) ID of the TLS Configuration used. +- **domain** (String) Domain that TLS was enabled on. +- **id** (String) Fastly Activation ID. Conflicts with all other filters. + +### Read-Only + +- **created_at** (String) Timestamp (GMT) when TLS was enabled. diff --git a/docs/data-sources/tls_activation_ids.md b/docs/data-sources/tls_activation_ids.md new file mode 100644 index 000000000..33ea99d03 --- /dev/null +++ b/docs/data-sources/tls_activation_ids.md @@ -0,0 +1,39 @@ +--- +layout: "fastly" +page_title: "Fastly: fastly_tls_activation_ids" +sidebar_current: "docs-fastly-datasource-tls_activation_ids" +description: |- +Get the list of TLS Activation identifiers in Fastly. +--- + +# fastly_tls_activation_ids + +Use this data source to get the list of TLS Activation identifiers in Fastly. + +## Example Usage + +```hcl +data "fastly_tls_activation_ids" "example" { + certificate_id = fastly_tls_certificate.example.id +} + +data "fastly_tls_activation" "example" { + for_each = data.fastly_tls_activation_ids.example.ids + id = each.value +} + +output "activation_domains" { + value = [for a in data.fastly_tls_activation.example : a.domain] +} +``` + +## Schema + +### Optional + +- **certificate_id** (String) ID of TLS certificate used to filter activations +- **id** (String) The ID of this resource. + +### Read-Only + +- **ids** (Set of String) List of IDs of the TLS Activations. diff --git a/docs/data-sources/tls_certificate.md b/docs/data-sources/tls_certificate.md new file mode 100644 index 000000000..2e6ba8799 --- /dev/null +++ b/docs/data-sources/tls_certificate.md @@ -0,0 +1,43 @@ +--- +layout: "fastly" +page_title: "Fastly: fastly_tls_certificate" +sidebar_current: "docs-fastly-datasource-tls_certificate" +description: |- +Get information on Fastly TLS certificate. +--- + +# fastly_tls_certificate + +Use this data source to get information of a TLS certificate for use with other resources. + +~> **Warning:** The data source's filters are applied using an **AND** boolean operator, so depending on the combination +of filters, they may become mutually exclusive. The exception to this is `id` which must not be specified in combination +with any of the others. + +~> **Note:** If more or less than a single match is returned by the search, Terraform will fail. Ensure that your search is specific enough to return a single key. + +## Example Usage + +```hcl +data "fastly_tls_certificate" "example" { + name = "example.com" +} +``` + +## Schema + +### Optional + +- **domains** (Set of String) Domains that are listed in any certificates' Subject Alternative Names (SAN) list. +- **id** (String) Unique ID assigned to certificate by Fastly +- **issued_to** (String) The hostname for which a certificate was issued. +- **issuer** (String) The certificate authority that issued the certificate. +- **name** (String) Human-readable name used to identify the certificate. Defaults to the certificate's Common Name or first Subject Alternative Name entry. + +### Read-Only + +- **created_at** (String) Timestamp (GMT) when the certificate was created +- **replace** (Boolean) A recommendation from Fastly indicating the key associated with this certificate is in need of rotation +- **serial_number** (String) A value assigned by the issuer that is unique to a certificate +- **signature_algorithm** (String) The algorithm used to sign the certificate +- **updated_at** (String) Timestamp (GMT) when the certificate was last updated diff --git a/docs/data-sources/tls_certificate_ids.md b/docs/data-sources/tls_certificate_ids.md new file mode 100644 index 000000000..8e46077cd --- /dev/null +++ b/docs/data-sources/tls_certificate_ids.md @@ -0,0 +1,32 @@ +--- +layout: "fastly" +page_title: "Fastly: fastly_tls_certificate_ids" +sidebar_current: "docs-fastly-datasource-tls_certificate_ids" +description: |- +Get IDs of available TLS certificates. +--- + +# fastly_tls_certificate_ids + +Use this data source to get the IDs of available TLS certificates for use with other resources. + +## Example Usage + +```hcl +data "fastly_tls_certificate_ids" "example" {} + +resource "fastly_tls_activation" "example" { + certificate_id = data.fastly_tls_certificate_ids.example.ids[0] + // ... +} +``` + +## Schema + +### Optional + +- **id** (String) The ID of this resource. + +### Read-Only + +- **ids** (Set of String) List of IDs corresponding to Custom TLS certificates. diff --git a/docs/data-sources/tls_configuration.md b/docs/data-sources/tls_configuration.md new file mode 100644 index 000000000..7d354f807 --- /dev/null +++ b/docs/data-sources/tls_configuration.md @@ -0,0 +1,56 @@ +--- +layout: "fastly" +page_title: "Fastly: fastly_tls_configuration" +sidebar_current: "docs-fastly-datasource-tls_configuration" +description: |- +Get information on Fastly TLS configuration. +--- + +# fastly_tls_configuration + +Use this data source to get the ID of a TLS configuration for use with other resources. + +~> **Warning:** The data source's filters are applied using an **AND** boolean operator, so depending on the combination +of filters, they may become mutually exclusive. The exception to this is `id` which must not be specified in combination +with any of the others. + +~> **Note:** If more or less than a single match is returned by the search, Terraform will fail. Ensure that your search is specific enough to return a single key. + +## Example Usage + +```hcl +data "fastly_tls_configuration" "example" { + default = true +} + +resource "fastly_tls_activation" "example" { + configuration_id = data.fastly_tls_configuration.example.id + // ... +} +``` + +## Schema + +### Optional + +- **default** (Boolean) Signifies whether Fastly will use this configuration as a default when creating a new TLS activation. +- **http_protocols** (Set of String) HTTP protocols available on the TLS configuration. +- **id** (String) ID of the TLS configuration obtained from the Fastly API or another data source. Conflicts with all the other filters. +- **name** (String) Custom name of the TLS configuration. +- **tls_protocols** (Set of String) TLS protocols available on the TLS configuration. +- **tls_service** (String) Whether the configuration should support the `PLATFORM` or `CUSTOM` TLS service. + +### Read-Only + +- **created_at** (String) Timestamp (GMT) when the configuration was created. +- **dns_records** (Set of Object) The available DNS addresses that can be used to enable TLS for a domain. DNS must be configured for a domain for TLS handshakes to succeed. If enabling TLS on an apex domain (e.g. `example.com`) you must create four A records (or four AAAA records for IPv6 support) using the displayed global A record's IP addresses with your DNS provider. For subdomains and wildcard domains (e.g. `www.example.com` or `*.example.com`) you will need to create a relevant CNAME record. (see [below for nested schema](#nestedatt--dns_records)) +- **updated_at** (String) Timestamp (GMT) when the configuration was last updated. + + +### Nested Schema for `dns_records` + +Read-Only: + +- **record_type** (String) +- **record_value** (String) +- **region** (String) diff --git a/docs/data-sources/tls_configuration_ids.md b/docs/data-sources/tls_configuration_ids.md new file mode 100644 index 000000000..7096d162f --- /dev/null +++ b/docs/data-sources/tls_configuration_ids.md @@ -0,0 +1,32 @@ +--- +layout: "fastly" +page_title: "Fastly: fastly_tls_configuration_ids" +sidebar_current: "docs-fastly-datasource-tls_configuration_ids" +description: |- +Get IDs of available TLS Configurations. +--- + +# fastly_tls_configuration_ids + +Use this data source to get the IDs of available TLS configurations for use with other resources. + +## Example Usage + +```hcl +data "fastly_tls_configuration_ids" "example" {} + +resource "fastly_tls_activation" "example" { + configuration_id = data.fastly_tls_configuration.example.ids[0] + // ... +} +``` + +## Schema + +### Optional + +- **id** (String) The ID of this resource. + +### Read-Only + +- **ids** (Set of String) List of IDs corresponding to available TLS configurations. diff --git a/docs/data-sources/tls_platform_certificate.md b/docs/data-sources/tls_platform_certificate.md new file mode 100644 index 000000000..4e6b88a58 --- /dev/null +++ b/docs/data-sources/tls_platform_certificate.md @@ -0,0 +1,41 @@ +--- +layout: "fastly" +page_title: "Fastly: fastly_tls_platform_certificate" +sidebar_current: "docs-fastly-datasource-tls_platform_certificate" +description: |- +Get information on Fastly Platform TLS certificate. +--- + +# fastly_tls_platform_certificate + +Use this data source to get information of a Platform TLS certificate for use with other resources. + +~> **Warning:** The data source's filters are applied using an **AND** boolean operator, so depending on the combination +of filters, they may become mutually exclusive. The exception to this is `id` which must not be specified in combination +with any of the others. + +~> **Note:** If more or less than a single match is returned by the search, Terraform will fail. Ensure that your search is specific enough to return a single key. + +## Example Usage + +```hcl +data "fastly_tls_platform_certificate" "example" { + domains = ["example.com"] +} +``` + +## Schema + +### Optional + +- **domains** (Set of String) Domains that are listed in any certificate's Subject Alternative Names (SAN) list. +- **id** (String) Unique ID assigned to certificate by Fastly. Conflicts with all the other filters. + +### Read-Only + +- **configuration_id** (String) ID of TLS configuration used to terminate TLS traffic. +- **created_at** (String) Timestamp (GMT) when the certificate was created. +- **not_after** (String) Timestamp (GMT) when the certificate will expire. +- **not_before** (String) Timestamp (GMT) when the certificate will become valid. +- **replace** (Boolean) A recommendation from Fastly indicating the key associated with this certificate is in need of rotation. +- **updated_at** (String) Timestamp (GMT) when the certificate was last updated. diff --git a/docs/data-sources/tls_platform_certificate_ids.md b/docs/data-sources/tls_platform_certificate_ids.md new file mode 100644 index 000000000..27a5e1bf1 --- /dev/null +++ b/docs/data-sources/tls_platform_certificate_ids.md @@ -0,0 +1,31 @@ +--- +layout: "fastly" +page_title: "Fastly: fastly_tls_platform_certificate_ids" +sidebar_current: "docs-fastly-datasource-tls_platform_certificate_ids" +description: |- +Get IDs of available Platform TLS certificates. +--- + +# fastly_tls_platform_certificate_ids + +Use this data source to get the IDs of available Platform TLS Certificates for use with other resources. + +## Example Usage + +```hcl +data "fastly_tls_platform_certificate_ids" "example" {} + +data "fastly_tls_platform_certificate" "example" { + id = data.fastly_tls_platform_certificate_ids.example.ids[0] +} +``` + +## Schema + +### Optional + +- **id** (String) The ID of this resource. + +### Read-Only + +- **ids** (Set of String) List of IDs corresponding to Platform TLS certificates. diff --git a/docs/data-sources/tls_private_key.md b/docs/data-sources/tls_private_key.md new file mode 100644 index 000000000..844276f99 --- /dev/null +++ b/docs/data-sources/tls_private_key.md @@ -0,0 +1,46 @@ +--- + +layout: "fastly" +page_title: "Fastly: fastly_tls_private_key" +sidebar_current: "docs-fastly-datasource-tls_private_key" +description: |- + Get information on a TLS Private Key. +--- + +# fastly_tls_private_key + +Use this data source to get information on a TLS Private Key uploaded to Fastly. + +~> **Warning:** The data source's filters are applied using an **AND** boolean operator, so depending on the combination + of filters, they may become mutually exclusive. The exception to this is `id` which must not be specified in combination + with any of the others. + +~> **Note:** If more or less than a single match is returned by the search, Terraform will fail. Ensure that your search + is specific enough to return a single key. + +## Example Usage + +```hcl +data "fastly_tls_private_key" "demo" { + name = "demo-private-key" +} + +output "private_key_needs_replacing" { + value = data.fastly_tls_private_key.demo.replace +} +``` + +## Schema + +### Optional + +- **created_at** (String) Timestamp (GMT) when the private key was created. +- **id** (String) Fastly private key ID. Conflicts with all the other filters +- **key_length** (Number) The key length used to generate the private key. +- **key_type** (String) The algorithm used to generate the private key. Must be RSA. +- **name** (String) The human-readable name assigned to the private key when uploaded. +- **public_key_sha1** (String) A hash of the associated public key, useful for safely identifying it. + +### Read-Only + +- **replace** (Boolean) Whether Fastly recommends replacing this private key. diff --git a/docs/data-sources/tls_private_key_ids.md b/docs/data-sources/tls_private_key_ids.md new file mode 100644 index 000000000..955559b05 --- /dev/null +++ b/docs/data-sources/tls_private_key_ids.md @@ -0,0 +1,31 @@ +--- +layout: "fastly" +page_title: "Fastly: fastly_tls_private_key_ids" +sidebar_current: "docs-fastly-datasource-tls_private_key_ids" +description: |- + Get the list of TLS private key identifiers in Fastly. +--- + +# fastly_tls_private_key_ids + +Use this data source to get the list of TLS private key identifiers in Fastly. + +## Example Usage + +```hcl +data "fastly_tls_private_key_ids" "demo" {} + +data "fastly_tls_private_key" "example" { + id = fastly_tls_private_key_ids.demo.ids[0] +} +``` + +## Schema + +### Optional + +- **id** (String) The ID of this resource. + +### Read-Only + +- **ids** (Set of String) List of IDs of the TLS private keys. diff --git a/docs/resources/tls_activation.md b/docs/resources/tls_activation.md new file mode 100644 index 000000000..c1e6276e6 --- /dev/null +++ b/docs/resources/tls_activation.md @@ -0,0 +1,75 @@ +--- +layout: "fastly" +page_title: "Fastly: tls_activation" +sidebar_current: "docs-fastly-resource-tls_activation" +description: |- +Enables TLS on a domain +--- + +# fastly_tls_activation + +Enables TLS on a domain using a specified custom TLS certificate. + +~> **Note:** The Fastly service must be provisioned _prior_ to enabling TLS on it. This can be achieved in Terraform using [`depends_on`](https://www.terraform.io/docs/configuration/meta-arguments/depends_on.html). + +## Example Usage + +Basic usage: + +```hcl +resource "fastly_service_v1" "demo" { + name = "my-service" + + domain { + name = "example.com" + } + + backend { + address = "127.0.0.1" + name = "localhost" + } + + force_destroy = true +} + +resource "fastly_tls_private_key" "demo" { + key_pem = "..." + name = "demo-key" +} + +resource "fastly_tls_certificate" "demo" { + certificate_body = "..." + name = "demo-cert" + depends_on = [fastly_tls_private_key.demo] +} + +resource "fastly_tls_activation" "test" { + certificate_id = fastly_tls_certificate.demo.id + domain = "example.com" + depends_on = [fastly_service_v1.demo] +} +``` + +## Import + +A TLS activation can be imported using its ID, e.g. + +``` +$ terraform import fastly_tls_activation.demo xxxxxxxx +``` + +## Schema + +### Required + +- **certificate_id** (String) ID of certificate to use. Must have the `domain` specified in the certificate's Subject Alternative Names. +- **domain** (String) Domain to enable TLS on. Must be assigned to an existing Fastly Service. + +### Optional + +- **configuration_id** (String) ID of TLS configuration to be used to terminate TLS traffic, or use the default one if missing. +- **id** (String) The ID of this resource. + +### Read-Only + +- **created_at** (String) Time-stamp (GMT) when TLS was enabled. diff --git a/docs/resources/tls_certificate.md b/docs/resources/tls_certificate.md new file mode 100644 index 000000000..9addbfc0b --- /dev/null +++ b/docs/resources/tls_certificate.md @@ -0,0 +1,84 @@ +--- +layout: "fastly" +page_title: "Fastly: tls_certificate" +sidebar_current: "docs-fastly-resource-tls_certificate" +description: |- +Uploads a custom TLS certificate +--- + +# fastly_tls_certificate + +Uploads a custom TLS certificate to Fastly to be used to terminate TLS traffic. + +-> Each TLS certificate **must** have its corresponding private key uploaded _prior_ to uploading the certificate. This +can be achieved in Terraform using [`depends_on`](https://www.terraform.io/docs/configuration/meta-arguments/depends_on.html) + +## Example Usage + +Basic usage: + +```hcl +resource "tls_private_key" "key" { + algorithm = "RSA" +} + +resource "tls_self_signed_cert" "cert" { + key_algorithm = tls_private_key.key.algorithm + private_key_pem = tls_private_key.key.private_key_pem + + subject { + common_name = "example.com" + } + + is_ca_certificate = true + validity_period_hours = 360 + + allowed_uses = [ + "cert_signing", + "server_auth", + ] + + dns_names = ["example.com"] +} + +resource "fastly_tls_private_key" "key" { + key_pem = tls_private_key.key.private_key_pem + name = "tf-demo" +} + +resource "fastly_tls_certificate" "example" { + name = "tf-demo" + certificate_body = tls_self_signed_cert.cert.cert_pem + depends_on = [fastly_tls_private_key.key] // The private key has to be present before the certificate can be uploaded +} +``` + +## Import + +A certificate can be imported using its Fastly certificate ID, e.g. + +``` +$ terraform import fastly_tls_certificate.demo xxxxxxxxxxx +``` + +## Schema + +### Required + +- **certificate_body** (String) PEM-formatted certificate. + +### Optional + +- **id** (String) The ID of this resource. +- **name** (String) Human-readable name used to identify the certificate. Defaults to the certificate's Common Name or first Subject Alternative Name entry. + +### Read-Only + +- **created_at** (String) Timestamp (GMT) when the certificate was created. +- **domains** (Set of String) All the domains (including wildcard domains) that are listed in the certificate's Subject Alternative Names (SAN) list. +- **issued_to** (String) The hostname for which a certificate was issued. +- **issuer** (String) The certificate authority that issued the certificate. +- **replace** (Boolean) A recommendation from Fastly indicating the key associated with this certificate is in need of rotation. +- **serial_number** (String) A value assigned by the issuer that is unique to a certificate. +- **signature_algorithm** (String) The algorithm used to sign the certificate. +- **updated_at** (String) Timestamp (GMT) when the certificate was last updated. diff --git a/docs/resources/tls_platform_certificate.md b/docs/resources/tls_platform_certificate.md new file mode 100644 index 000000000..43e3cbd4a --- /dev/null +++ b/docs/resources/tls_platform_certificate.md @@ -0,0 +1,119 @@ +--- +layout: "fastly" +page_title: "Fastly: tls_platform_certificate" +sidebar_current: "docs-fastly-resource-tls_platform_certificate" +description: |- +Uploads a TLS certificate to the Platform TLS service +--- + +# fastly_tls_platform_certificate + +Uploads a TLS certificate to the Fastly Platform TLS service. + +-> Each TLS certificate **must** have its corresponding private key uploaded _prior_ to uploading the certificate. This +can be achieved in Terraform using [`depends_on`](https://www.terraform.io/docs/configuration/meta-arguments/depends_on.html) + +## Example Usage + +Basic usage with self-signed CA: + +```hcl +resource "tls_private_key" "ca_key" { + algorithm = "RSA" +} + +resource "tls_private_key" "key" { + algorithm = "RSA" +} + +resource "tls_self_signed_cert" "ca" { + key_algorithm = tls_private_key.ca_key.algorithm + private_key_pem = tls_private_key.ca_key.private_key_pem + + subject { + common_name = "Example CA" + } + + is_ca_certificate = true + validity_period_hours = 360 + + allowed_uses = [ + "cert_signing", + "server_auth", + ] +} + +resource "tls_cert_request" "example" { + key_algorithm = tls_private_key.key.algorithm + private_key_pem = tls_private_key.key.private_key_pem + + subject { + common_name = "example.com" + } + + dns_names = ["example.com", "www.example.com"] +} + +resource "tls_locally_signed_cert" "cert" { + cert_request_pem = tls_cert_request.example.cert_request_pem + ca_key_algorithm = tls_private_key.ca_key.algorithm + ca_private_key_pem = tls_private_key.ca_key.private_key_pem + ca_cert_pem = tls_self_signed_cert.ca.cert_pem + + validity_period_hours = 360 + + allowed_uses = [ + "cert_signing", + "server_auth", + ] +} + +data "fastly_tls_configuration" "config" { + tls_service = "PLATFORM" +} + +resource "fastly_tls_private_key" "key" { + key_pem = tls_private_key.key.private_key_pem + name = "tf-demo" +} + +resource "fastly_tls_platform_certificate" "cert" { + certificate_body = tls_locally_signed_cert.cert.cert_pem + intermediates_blob = tls_self_signed_cert.ca.cert_pem + + configuration_id = data.fastly_tls_configuration.config.id + allow_untrusted_root = true + + depends_on = [fastly_tls_private_key.key] +} +``` + +## Import + +A certificate can be imported using its Fastly certificate ID, e.g. + +``` +$ terraform import fastly_tls_platform_certificate.demo xxxxxxxxxxx +``` + +## Schema + +### Required + +- **certificate_body** (String) PEM-formatted certificate. +- **configuration_id** (String) ID of TLS configuration to be used to terminate TLS traffic. +- **intermediates_blob** (String) PEM-formatted certificate chain from the `certificate_body` to its root. + +### Optional + +- **allow_untrusted_root** (Boolean) Disable checking whether the root of the certificate chain is trusted. Useful for development purposes to allow use of self-signed CAs. Defaults to false. Write-only on create. +- **id** (String) The ID of this resource. + +### Read-Only + +- **created_at** (String) Timestamp (GMT) when the certificate was created. +- **domains** (Set of String) All the domains (including wildcard domains) that are listed in any certificate's Subject Alternative Names (SAN) list. +- **not_after** (String) Timestamp (GMT) when the certificate will expire. +- **not_before** (String) Timestamp (GMT) when the certificate will become valid. +- **replace** (Boolean) A recommendation from Fastly indicating the key associated with this certificate is in need of rotation. +- **updated_at** (String) Timestamp (GMT) when the certificate was last updated. diff --git a/docs/resources/tls_private_key.md b/docs/resources/tls_private_key.md new file mode 100644 index 000000000..7258c9a23 --- /dev/null +++ b/docs/resources/tls_private_key.md @@ -0,0 +1,55 @@ +--- +layout: "fastly" +page_title: "Fastly: tls_private_key" +sidebar_current: "docs-fastly-resource-tls_private_key" +description: |- +Uploads a Custom TLS Private Key +--- + +# fastly_tls_private_key + +Uploads a Custom TLS Private Key to Fastly. This can be combined with a `fastly_tls_custom_certificate` resource to provide a TLS Certificate able to be applied to a Fastly Service. + +The Private Key resource requires a key in PEM format, and a name to identify it. + +## Example Usage + +Basic usage: + +```hcl +resource "tls_private_key" "demo" { + algorithm = "RSA" +} + +resource "fastly_tls_private_key" "demo" { + key_pem = tls_private_key.demo.private_key_pem + name = "tf-demo" +} +``` + +## Import + +A Private Key can be imported using its ID, e.g. + +``` +$ terraform import fastly_tls_private_key.demo xxxxxxxxxxx +``` + +## Schema + +### Required + +- **key_pem** (String, Sensitive) Private key in PEM format. +- **name** (String) Customisable name of the private key. + +### Optional + +- **id** (String) The ID of this resource. + +### Read-Only + +- **created_at** (String) Time-stamp (GMT) when the private key was created. +- **key_length** (Number) The key length used to generate the private key. +- **key_type** (String) The algorithm used to generate the private key. Must be RSA. +- **public_key_sha1** (String) Useful for safely identifying the key. +- **replace** (Boolean) Whether Fastly recommends replacing this private key. diff --git a/fastly/config.go b/fastly/config.go index 201ab6e9b..8b2f803f8 100644 --- a/fastly/config.go +++ b/fastly/config.go @@ -22,7 +22,7 @@ type FastlyClient struct { conn *gofastly.Client } -func (c *Config) Client() (interface{}, error) { +func (c *Config) Client() (*FastlyClient, error) { var client FastlyClient if c.ApiKey == "" { diff --git a/fastly/data_source_fastly_tls_activation.go b/fastly/data_source_fastly_tls_activation.go new file mode 100644 index 000000000..5ec3e2967 --- /dev/null +++ b/fastly/data_source_fastly_tls_activation.go @@ -0,0 +1,163 @@ +package fastly + +import ( + "fmt" + "github.com/fastly/go-fastly/v3/fastly" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "time" +) + +func dataSourceFastlyTLSActivation() *schema.Resource { + return &schema.Resource{ + Read: dataSourceFastlyTLSActivationRead, + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + Description: "Fastly Activation ID. Conflicts with all other filters.", + ConflictsWith: []string{"certificate_id", "configuration_id", "domain"}, + }, + "certificate_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"id"}, + Description: "ID of the TLS Certificate used.", + }, + "configuration_id": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"id"}, + Description: "ID of the TLS Configuration used.", + }, + "domain": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ConflictsWith: []string{"id"}, + Description: "Domain that TLS was enabled on.", + }, + "created_at": { + Type: schema.TypeString, + Computed: true, + Description: "Timestamp (GMT) when TLS was enabled.", + }, + }, + } +} + +func dataSourceFastlyTLSActivationRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*FastlyClient).conn + + var activation *fastly.TLSActivation + + if v, ok := d.GetOk("id"); ok { + foundActivation, err := conn.GetTLSActivation(&fastly.GetTLSActivationInput{ + ID: v.(string), + }) + if err != nil { + return err + } + activation = foundActivation + } else { + filters := getTLSActivationFilters(d) + + activations, err := listTLSActivations(conn, filters...) + if err != nil { + return err + } + + if len(activations) == 0 { + return fmt.Errorf("Your query returned no results. Please change your search criteria and try again") + } + + if len(activations) > 1 { + return fmt.Errorf("Your query returned more than one result. Please change to a more specific search criteria") + } + + activation = activations[0] + } + + return dataSourceFastlyTLSActivationSetAttributes(activation, d) +} + +type TLSActivationPredicate func(activation *fastly.TLSActivation) bool + +func getTLSActivationFilters(d *schema.ResourceData) []TLSActivationPredicate { + var filters []TLSActivationPredicate + + if v, ok := d.GetOk("certificate_id"); ok { + filters = append(filters, func(c *fastly.TLSActivation) bool { + return c.Certificate.ID == v.(string) + }) + } + if v, ok := d.GetOk("configuration_id"); ok { + filters = append(filters, func(c *fastly.TLSActivation) bool { + return c.Configuration.ID == v.(string) + }) + } + if v, ok := d.GetOk("domain"); ok { + filters = append(filters, func(c *fastly.TLSActivation) bool { + return c.Domain.ID == v.(string) + }) + } + + return filters +} + +func listTLSActivations(conn *fastly.Client, filters ...TLSActivationPredicate) ([]*fastly.TLSActivation, error) { + var activations []*fastly.TLSActivation + pageNumber := 1 + for { + list, err := conn.ListTLSActivations(&fastly.ListTLSActivationsInput{ + PageNumber: pageNumber, + PageSize: 10, + }) + if err != nil { + return nil, err + } + if len(list) == 0 { + break + } + pageNumber++ + + for _, activation := range list { + if filterTLSActivations(activation, filters) { + activations = append(activations, activation) + } + } + } + + return activations, nil +} + +func dataSourceFastlyTLSActivationSetAttributes(activation *fastly.TLSActivation, d *schema.ResourceData) error { + + d.SetId(activation.ID) + + if err := d.Set("certificate_id", activation.Certificate.ID); err != nil { + return err + } + if err := d.Set("configuration_id", activation.Configuration.ID); err != nil { + return err + } + if err := d.Set("domain", activation.Domain.ID); err != nil { + return err + } + if err := d.Set("created_at", activation.CreatedAt.Format(time.RFC3339)); err != nil { + return err + } + + return nil +} + +func filterTLSActivations(config *fastly.TLSActivation, filters []TLSActivationPredicate) bool { + for _, f := range filters { + if !f(config) { + return false + } + } + return true +} diff --git a/fastly/data_source_fastly_tls_activation_ids.go b/fastly/data_source_fastly_tls_activation_ids.go new file mode 100644 index 000000000..be01dd23e --- /dev/null +++ b/fastly/data_source_fastly_tls_activation_ids.go @@ -0,0 +1,72 @@ +package fastly + +import ( + "fmt" + "github.com/fastly/go-fastly/v3/fastly" + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func dataSourceFastlyTLSActivationIds() *schema.Resource { + return &schema.Resource{ + Read: dataSourceFastlyTLSActivationIDsRead, + Schema: map[string]*schema.Schema{ + "certificate_id": { + Type: schema.TypeString, + Optional: true, + Description: "ID of TLS certificate used to filter activations", + }, + "ids": { + Type: schema.TypeSet, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Description: "List of IDs of the TLS Activations.", + }, + }, + } +} + +func dataSourceFastlyTLSActivationIDsRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*FastlyClient).conn + + var certificateID string + + if v, ok := d.GetOk("certificate_id"); ok { + certificateID = v.(string) + } + + var activations []*fastly.TLSActivation + pageNumber := 1 + for { + list, err := conn.ListTLSActivations(&fastly.ListTLSActivationsInput{ + FilterTLSCertificateID: certificateID, + PageNumber: pageNumber, + PageSize: 10, + }) + if err != nil { + return err + } + if len(list) == 0 { + break + } + pageNumber++ + + activations = append(activations, list...) + } + + var ids []string + for _, activation := range activations { + ids = append(ids, activation.ID) + } + + // 2.x upgrade note - `hashcode.String` was removed from the SDK + // Code will need to be copied into this repository + // https://www.terraform.io/docs/extend/guides/v2-upgrade-guide.html#removal-of-helper-hashcode-package + d.SetId(fmt.Sprintf("%d", hashcode.String(certificateID))) + err := d.Set("ids", ids) + if err != nil { + return err + } + + return nil +} diff --git a/fastly/data_source_fastly_tls_activation_ids_test.go b/fastly/data_source_fastly_tls_activation_ids_test.go new file mode 100644 index 000000000..ec1f04710 --- /dev/null +++ b/fastly/data_source_fastly_tls_activation_ids_test.go @@ -0,0 +1,113 @@ +package fastly + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/stretchr/testify/require" + "strings" + "testing" +) + +func TestAccDataSourceFastlyTLSActivationIds_basic(t *testing.T) { + domain := fmt.Sprintf("%s.com", acctest.RandomWithPrefix(testResourcePrefix)) + key, cert, err := generateKeyAndCert(domain) + require.NoError(t, err) + key = strings.ReplaceAll(key, "\n", `\n`) + cert = strings.ReplaceAll(cert, "\n", `\n`) + + datasourceName := "data.fastly_tls_activation_ids.subject" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceFastlyTLSActivationIdsConfig(key, cert, domain), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(datasourceName, "ids.#", "1"), + testAccTLSActivationIDIncluded(datasourceName, "fastly_tls_activation.test"), + ), + }, + }, + }) +} + +func testAccTLSActivationIDIncluded(dataSourceName string, resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + r, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("resource not found: %s", resourceName) + } + d, ok := s.RootModule().Resources[dataSourceName] + if !ok { + return fmt.Errorf("data source not found: %s", dataSourceName) + } + + for k, v := range d.Primary.Attributes { + if k == "ids.#" { + continue + } + if !strings.HasPrefix(k, "ids.") { + continue + } + if v == r.Primary.ID { + return nil + } + } + + return fmt.Errorf("unable to find private key %s in list of private key ids", r.Primary.ID) + } +} + +func testAccDataSourceFastlyTLSActivationIdsConfig(key, cert, domain string) string { + name := acctest.RandomWithPrefix(testResourcePrefix) + + return fmt.Sprintf( + ` +resource "fastly_service_v1" "test" { + name = "%s" + + domain { + name = "%s" + } + + backend { + address = "127.0.0.1" + name = "localhost" + } + + force_destroy = true +} + +resource "fastly_tls_private_key" "test" { + key_pem = "%s" + name = "%s" +} + +resource "fastly_tls_certificate" "test" { + certificate_body = "%s" + name = "%s" + depends_on = [fastly_tls_private_key.test] +} + +resource "fastly_tls_activation" "test" { + certificate_id = fastly_tls_certificate.test.id + domain = "%s" + depends_on = [fastly_service_v1.test] +} + +data "fastly_tls_activation_ids" "subject" { + certificate_id = fastly_tls_activation.test.certificate_id +} + +`, + name, + domain, + key, + name, + cert, + name, + domain, + ) +} diff --git a/fastly/data_source_fastly_tls_activation_test.go b/fastly/data_source_fastly_tls_activation_test.go new file mode 100644 index 000000000..4364117ef --- /dev/null +++ b/fastly/data_source_fastly_tls_activation_test.go @@ -0,0 +1,158 @@ +package fastly + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/stretchr/testify/require" + "strings" + "testing" +) + +func TestAccDataSourceFastlyTLSActivation_basic(t *testing.T) { + domain := fmt.Sprintf("%s.com", acctest.RandomWithPrefix(testResourcePrefix)) + key, cert, err := generateKeyAndCert(domain) + require.NoError(t, err) + key = strings.ReplaceAll(key, "\n", `\n`) + cert = strings.ReplaceAll(cert, "\n", `\n`) + + resourceName := "data.fastly_tls_activation.test" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceFastlyTLSActivationConfig(key, cert, domain), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "certificate_id"), + resource.TestCheckResourceAttrSet(resourceName, "configuration_id"), + resource.TestCheckResourceAttr(resourceName, "domain", domain), + resource.TestCheckResourceAttrSet(resourceName, "created_at"), + ), + }, + }, + }) +} + +func TestAccFastlyDataSourceFastlyTLSActivation_byID(t *testing.T) { + domain := fmt.Sprintf("%s.com", acctest.RandomWithPrefix(testResourcePrefix)) + key, cert, err := generateKeyAndCert(domain) + require.NoError(t, err) + key = strings.ReplaceAll(key, "\n", `\n`) + cert = strings.ReplaceAll(cert, "\n", `\n`) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccFastlyDataSourceTLSActivationConfigByID(key, cert, domain), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.fastly_tls_activation.subject", "domain", domain), + ), + }, + }, + }) +} + +func testAccDataSourceFastlyTLSActivationConfig(key, cert, domain string) string { + name := acctest.RandomWithPrefix(testResourcePrefix) + + return fmt.Sprintf( + ` +resource "fastly_service_v1" "test" { + name = "%s" + + domain { + name = "%s" + } + + backend { + address = "127.0.0.1" + name = "localhost" + } + + force_destroy = true +} + +resource "fastly_tls_private_key" "test" { + key_pem = "%s" + name = "%s" +} + +resource "fastly_tls_certificate" "test" { + certificate_body = "%s" + name = "%s" + depends_on = [fastly_tls_private_key.test] +} + +resource "fastly_tls_activation" "test" { + certificate_id = fastly_tls_certificate.test.id + domain = "%s" + depends_on = [fastly_service_v1.test] +} + +data "fastly_tls_activation" "test" { + domain = fastly_tls_activation.test.domain +} +`, + name, + domain, + key, + name, + cert, + name, + domain, + ) +} + +func testAccFastlyDataSourceTLSActivationConfigByID(key, cert, domain string) string { + name := acctest.RandomWithPrefix(testResourcePrefix) + + return fmt.Sprintf( + ` +resource "fastly_service_v1" "test" { + name = "%s" + + domain { + name = "%s" + } + + backend { + address = "127.0.0.1" + name = "localhost" + } + + force_destroy = true +} + +resource "fastly_tls_private_key" "test" { + key_pem = "%s" + name = "%s" +} + +resource "fastly_tls_certificate" "test" { + certificate_body = "%s" + name = "%s" + depends_on = [fastly_tls_private_key.test] +} + +resource "fastly_tls_activation" "test" { + certificate_id = fastly_tls_certificate.test.id + domain = "%s" + depends_on = [fastly_service_v1.test] +} + +data "fastly_tls_activation" "subject" { + id = fastly_tls_activation.test.id +} +`, + name, + domain, + key, + name, + cert, + name, + domain, + ) +} diff --git a/fastly/data_source_fastly_tls_certificate.go b/fastly/data_source_fastly_tls_certificate.go new file mode 100644 index 000000000..084c50531 --- /dev/null +++ b/fastly/data_source_fastly_tls_certificate.go @@ -0,0 +1,223 @@ +package fastly + +import ( + "fmt" + "time" + + "github.com/fastly/go-fastly/v3/fastly" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func dataSourceFastlyTLSCertificate() *schema.Resource { + return &schema.Resource{ + Read: dataSourceFastlyTLSCertificateRead, + + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Description: "Unique ID assigned to certificate by Fastly", + Optional: true, + Computed: true, + ConflictsWith: []string{"name", "issued_to", "domains", "issuer"}, + }, + "name": { + Type: schema.TypeString, + Description: "Human-readable name used to identify the certificate. Defaults to the certificate's Common Name or first Subject Alternative Name entry.", + Optional: true, + Computed: true, + ConflictsWith: []string{"id"}, + }, + "issued_to": { + Type: schema.TypeString, + Description: "The hostname for which a certificate was issued.", + Optional: true, + Computed: true, + ConflictsWith: []string{"id"}, + }, + "domains": { + Type: schema.TypeSet, + Description: "Domains that are listed in any certificates' Subject Alternative Names (SAN) list.", + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + ConflictsWith: []string{"id"}, + }, + "issuer": { + Type: schema.TypeString, + Description: "The certificate authority that issued the certificate.", + Optional: true, + Computed: true, + ConflictsWith: []string{"id"}, + }, + "created_at": { + Type: schema.TypeString, + Description: "Timestamp (GMT) when the certificate was created", + Computed: true, + }, + "updated_at": { + Type: schema.TypeString, + Description: "Timestamp (GMT) when the certificate was last updated", + Computed: true, + }, + "replace": { + Type: schema.TypeBool, + Description: "A recommendation from Fastly indicating the key associated with this certificate is in need of rotation", + Computed: true, + }, + "serial_number": { + Type: schema.TypeString, + Description: "A value assigned by the issuer that is unique to a certificate", + Computed: true, + }, + "signature_algorithm": { + Type: schema.TypeString, + Description: "The algorithm used to sign the certificate", + Computed: true, + }, + }, + } +} + +func dataSourceFastlyTLSCertificateRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*FastlyClient).conn + + var certificate *fastly.CustomTLSCertificate + + if v, ok := d.GetOk("id"); ok { + cert, err := conn.GetCustomTLSCertificate(&fastly.GetCustomTLSCertificateInput{ + ID: v.(string), + }) + if err != nil { + return err + } + + certificate = cert + } else { + filters := getTLSCertificateFilters(d) + + certificates, err := listTLSCertificates(conn, filters...) + if err != nil { + return err + } + + if len(certificates) == 0 { + return fmt.Errorf("Your query returned no results. Please change your search criteria and try again.") + } + + if len(certificates) > 1 { + return fmt.Errorf("Your query returned more than one result. Please change try a more specific search criteria and try again.") + } + + certificate = certificates[0] + } + + return dataSourceFastlyTLSCertificateSetAttributes(certificate, d) +} + +type TLSCertificatePredicate func(*fastly.CustomTLSCertificate) bool + +func getTLSCertificateFilters(d *schema.ResourceData) []TLSCertificatePredicate { + var filters []TLSCertificatePredicate + + if v, ok := d.GetOk("name"); ok { + filters = append(filters, func(c *fastly.CustomTLSCertificate) bool { + return c.Name == v.(string) + }) + } + if v, ok := d.GetOk("issued_to"); ok { + filters = append(filters, func(c *fastly.CustomTLSCertificate) bool { + return c.IssuedTo == v.(string) + }) + } + if v, ok := d.GetOk("domains"); ok { + filters = append(filters, func(c *fastly.CustomTLSCertificate) bool { + s := v.(*schema.Set) + for _, domain := range c.Domains { + if s.Contains(domain.ID) { + return true + } + } + return false + }) + } + if v, ok := d.GetOk("issuer"); ok { + filters = append(filters, func(c *fastly.CustomTLSCertificate) bool { + return c.Issuer == v.(string) + }) + } + + return filters +} + +func listTLSCertificates(conn *fastly.Client, filters ...TLSCertificatePredicate) ([]*fastly.CustomTLSCertificate, error) { + var certificates []*fastly.CustomTLSCertificate + pageNumber := 1 + for { + list, err := conn.ListCustomTLSCertificates(&fastly.ListCustomTLSCertificatesInput{ + PageNumber: pageNumber, + PageSize: 10, + }) + if err != nil { + return nil, err + } + if len(list) == 0 { + break + } + pageNumber++ + + for _, certificate := range list { + if filterTLSCertificate(certificate, filters) { + certificates = append(certificates, certificate) + } + } + } + + return certificates, nil +} + +func dataSourceFastlyTLSCertificateSetAttributes(certificate *fastly.CustomTLSCertificate, d *schema.ResourceData) error { + var domains []string + for _, domain := range certificate.Domains { + domains = append(domains, domain.ID) + } + + d.SetId(certificate.ID) + if err := d.Set("name", certificate.Name); err != nil { + return err + } + if err := d.Set("created_at", certificate.CreatedAt.Format(time.RFC3339)); err != nil { + return err + } + if err := d.Set("updated_at", certificate.UpdatedAt.Format(time.RFC3339)); err != nil { + return err + } + if err := d.Set("issued_to", certificate.IssuedTo); err != nil { + return err + } + if err := d.Set("issuer", certificate.Issuer); err != nil { + return err + } + if err := d.Set("replace", certificate.Replace); err != nil { + return err + } + if err := d.Set("serial_number", certificate.SerialNumber); err != nil { + return err + } + if err := d.Set("signature_algorithm", certificate.SignatureAlgorithm); err != nil { + return err + } + if err := d.Set("domains", domains); err != nil { + return err + } + + return nil +} + +func filterTLSCertificate(config *fastly.CustomTLSCertificate, filters []TLSCertificatePredicate) bool { + for _, f := range filters { + if !f(config) { + return false + } + } + return true +} diff --git a/fastly/data_source_fastly_tls_certificate_ids.go b/fastly/data_source_fastly_tls_certificate_ids.go new file mode 100644 index 000000000..b896a1afc --- /dev/null +++ b/fastly/data_source_fastly_tls_certificate_ids.go @@ -0,0 +1,46 @@ +package fastly + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func dataSourceFastlyTLSCertificateIDs() *schema.Resource { + return &schema.Resource{ + Read: dataSourceFastlyTLSCertificateIDsRead, + Schema: map[string]*schema.Schema{ + "ids": { + Type: schema.TypeSet, + Description: "List of IDs corresponding to Custom TLS certificates.", + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceFastlyTLSCertificateIDsRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*FastlyClient).conn + + certificates, err := listTLSCertificates(conn) + if err != nil { + return err + } + + var ids []string + for _, certificate := range certificates { + ids = append(ids, certificate.ID) + } + + // 2.x upgrade note - `hashcode.String` was removed from the SDK + // Code will need to be copied into this repository + // https://www.terraform.io/docs/extend/guides/v2-upgrade-guide.html#removal-of-helper-hashcode-package + d.SetId(fmt.Sprintf("%d", hashcode.String(""))) // if other filters are added to this data source, they should be included in this hashcode instead of the empty string + err = d.Set("ids", ids) + if err != nil { + return err + } + + return nil +} diff --git a/fastly/data_source_fastly_tls_certificate_ids_test.go b/fastly/data_source_fastly_tls_certificate_ids_test.go new file mode 100644 index 000000000..6732cb4e6 --- /dev/null +++ b/fastly/data_source_fastly_tls_certificate_ids_test.go @@ -0,0 +1,61 @@ +package fastly + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/stretchr/testify/require" + "strings" + "testing" +) + +func TestAccFastlyDataSourceTlSCertificateIDs(t *testing.T) { + name := acctest.RandomWithPrefix(testResourcePrefix) + domain := fmt.Sprintf("%s.com", acctest.RandomWithPrefix(testResourcePrefix)) + + key, cert, err := generateKeyAndCert(domain) + require.NoError(t, err) + key = strings.ReplaceAll(key, "\n", `\n`) + cert = strings.ReplaceAll(cert, "\n", `\n`) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccFastlyDataSourceTLSCertificateIDSConfig_resources(name, key, cert), + }, + { + Config: testAccFastlyDataSourceTLSCertificateIDSConfig_resourcesAndData(name, key, cert), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckOutput("result", "true"), + ), + }, + }, + }) +} + +func testAccFastlyDataSourceTLSCertificateIDSConfig_resources(name, key, cert string) string { + return fmt.Sprintf(` +resource "fastly_tls_private_key" "key" { + key_pem = "%s" + name = "%s" +} +resource "fastly_tls_certificate" "cert" { + certificate_body = "%s" + depends_on = [fastly_tls_private_key.key] +} +`, key, name, cert) +} + +func testAccFastlyDataSourceTLSCertificateIDSConfig_resourcesAndData(name, key, cert string) string { + return fmt.Sprintf(` +%s +data "fastly_tls_certificate_ids" "subject" {} +output "result" { + value = tostring(contains( + data.fastly_tls_certificate_ids.subject.ids, fastly_tls_certificate.cert.id + )) +} +`, testAccFastlyDataSourceTLSCertificateIDSConfig_resources(name, key, cert)) +} diff --git a/fastly/data_source_fastly_tls_certificate_test.go b/fastly/data_source_fastly_tls_certificate_test.go new file mode 100644 index 000000000..d8306f82f --- /dev/null +++ b/fastly/data_source_fastly_tls_certificate_test.go @@ -0,0 +1,76 @@ +package fastly + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/stretchr/testify/require" +) + +func TestAccFastlyDataSourceTLSCertificate_withName(t *testing.T) { + name := acctest.RandomWithPrefix(testResourcePrefix) + domain := fmt.Sprintf("%s.example.com", name) + + key, cert, err := generateKeyAndCert(domain) + require.NoError(t, err) + + dataSourceName := "data.fastly_tls_certificate.test" + resourceName := "fastly_tls_certificate.cert" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccDataSourceTlsCertificate(name, key, cert, domain), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair( + dataSourceName, "id", resourceName, "id"), + resource.TestCheckResourceAttrPair( + dataSourceName, "name", resourceName, "name"), + resource.TestCheckResourceAttrPair( + dataSourceName, "created_at", resourceName, "created_at"), + resource.TestCheckResourceAttrPair( + dataSourceName, "updated_at", resourceName, "updated_at"), + resource.TestCheckResourceAttrPair( + dataSourceName, "issued_to", resourceName, "issued_to"), + resource.TestCheckResourceAttrPair( + dataSourceName, "issuer", resourceName, "issuer"), + resource.TestCheckResourceAttrPair( + dataSourceName, "replace", resourceName, "replace"), + resource.TestCheckResourceAttrPair( + dataSourceName, "serial_number", resourceName, "serial_number"), + resource.TestCheckResourceAttrPair( + dataSourceName, "signature_algorithm", resourceName, "signature_algorithm"), + resource.TestCheckResourceAttrPair( + dataSourceName, "domains", resourceName, "domains"), + ), + }, + }, + }) +} + +func testAccDataSourceTlsCertificate(keyName string, key string, cert string, domain string) string { + return fmt.Sprintf(` +resource "fastly_tls_private_key" "key" { + name = "%[1]s" + key_pem = < 1 { + return fmt.Errorf("Your query returned more than one result. Please change try a more specific search criteria and try again.") + } + + configuration = configurations[0] + } + + return dataSourceFastlyTLSConfigurationSetAttributes(configuration, d) +} + +func getTLSConfigurationFilters(d *schema.ResourceData) []func(*fastly.CustomTLSConfiguration) bool { + var filters []func(*fastly.CustomTLSConfiguration) bool + + if v, ok := d.GetOk("name"); ok { + filters = append(filters, func(c *fastly.CustomTLSConfiguration) bool { + return c.Name == v.(string) + }) + } + if v, ok := d.GetOk("tls_protocols"); ok { + filters = append(filters, func(c *fastly.CustomTLSConfiguration) bool { + return containsSubSet(c.TLSProtocols, v.(*schema.Set).List()) + }) + } + if v, ok := d.GetOk("http_protocols"); ok { + filters = append(filters, func(c *fastly.CustomTLSConfiguration) bool { + return containsSubSet(c.HTTPProtocols, v.(*schema.Set).List()) + }) + } + if v, ok := d.GetOk("tls_service"); ok { + service := v.(string) + filters = append(filters, func(c *fastly.CustomTLSConfiguration) bool { + if service == tlsPlatformService { + return c.Bulk == true + } + return c.Bulk == false + }) + } + if v, ok := d.GetOk("default"); ok { + filters = append(filters, func(c *fastly.CustomTLSConfiguration) bool { + return c.Default == v.(bool) + }) + } + + return filters +} + +func listTLSConfigurations(conn *fastly.Client, filters ...func(*fastly.CustomTLSConfiguration) bool) ([]*fastly.CustomTLSConfiguration, error) { + var configurations []*fastly.CustomTLSConfiguration + cursor := 0 + for { + list, err := conn.ListCustomTLSConfigurations(&fastly.ListCustomTLSConfigurationsInput{ + PageNumber: cursor, + Include: "dns_records", + }) + if err != nil { + return nil, err + } + if len(list) == 0 { + break + } + cursor += len(list) + + for _, configuration := range list { + if filterTLSConfiguration(configuration, filters) { + configurations = append(configurations, configuration) + } + } + } + return configurations, nil +} + +func dataSourceFastlyTLSConfigurationSetAttributes(configuration *fastly.CustomTLSConfiguration, d *schema.ResourceData) error { + tlsService := tlsCustomService + if configuration.Bulk { + tlsService = tlsPlatformService + } + + var DNSRecords []map[string]string + for _, record := range configuration.DNSRecords { + DNSRecords = append(DNSRecords, map[string]string{ + "record_type": record.RecordType, + "record_value": record.ID, + "region": record.Region, + }) + } + + d.SetId(configuration.ID) + if err := d.Set("name", configuration.Name); err != nil { + return err + } + if err := d.Set("tls_protocols", configuration.TLSProtocols); err != nil { + return err + } + if err := d.Set("http_protocols", configuration.HTTPProtocols); err != nil { + return err + } + if err := d.Set("tls_service", tlsService); err != nil { + return err + } + if err := d.Set("default", configuration.Default); err != nil { + return err + } + if err := d.Set("created_at", configuration.CreatedAt.Format(time.RFC3339)); err != nil { + return err + } + if err := d.Set("updated_at", configuration.UpdatedAt.Format(time.RFC3339)); err != nil { + return err + } + if err := d.Set("dns_records", DNSRecords); err != nil { + return err + } + return nil +} + +func filterTLSConfiguration(config *fastly.CustomTLSConfiguration, filters []func(*fastly.CustomTLSConfiguration) bool) bool { + for _, f := range filters { + if !f(config) { + return false + } + } + return true +} + +func containsSubSet(set []string, subSet []interface{}) bool { + for _, s := range subSet { + if !contains(set, s) { + return false + } + } + return true +} + +func contains(haystack []string, needle interface{}) bool { + for _, s := range haystack { + if s == needle { + return true + } + } + + return false +} diff --git a/fastly/data_source_fastly_tls_configuration_ids.go b/fastly/data_source_fastly_tls_configuration_ids.go new file mode 100644 index 000000000..a3773f8c2 --- /dev/null +++ b/fastly/data_source_fastly_tls_configuration_ids.go @@ -0,0 +1,44 @@ +package fastly + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func dataSourceFastlyTLSConfigurationIDs() *schema.Resource { + return &schema.Resource{ + Read: dataSourceFastlyTLSConfigurationIDsRead, + Schema: map[string]*schema.Schema{ + "ids": { + Type: schema.TypeSet, + Description: "List of IDs corresponding to available TLS configurations.", + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceFastlyTLSConfigurationIDsRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*FastlyClient).conn + + configurations, err := listTLSConfigurations(conn) + if err != nil { + return err + } + + var ids []string + for _, configuration := range configurations { + ids = append(ids, configuration.ID) + } + + // 2.x upgrade note - `hashcode.String` was removed from the SDK + // Code will need to be copied into this repository + // https://www.terraform.io/docs/extend/guides/v2-upgrade-guide.html#removal-of-helper-hashcode-package + d.SetId(fmt.Sprintf("%d", hashcode.String(""))) // if other filters are added to this data source, they should be included in this hashcode instead of the empty string + if err := d.Set("ids", ids); err != nil { + return err + } + return nil +} diff --git a/fastly/data_source_fastly_tls_configuration_ids_test.go b/fastly/data_source_fastly_tls_configuration_ids_test.go new file mode 100644 index 000000000..bd48672a6 --- /dev/null +++ b/fastly/data_source_fastly_tls_configuration_ids_test.go @@ -0,0 +1,21 @@ +package fastly + +import ( + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "testing" +) + +func TestAccFastlyDataSourceTLSConfigurationIDs(t *testing.T) { + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccFastlyAccDataSourceTLSConfigurationIDs, + Check: resource.TestCheckResourceAttrSet("data.fastly_tls_configuration_ids.subject", "ids.#"), + }, + }, + }) +} + +const testAccFastlyAccDataSourceTLSConfigurationIDs = `data "fastly_tls_configuration_ids" "subject" {}` diff --git a/fastly/data_source_fastly_tls_configuration_test.go b/fastly/data_source_fastly_tls_configuration_test.go new file mode 100644 index 000000000..e10b03e70 --- /dev/null +++ b/fastly/data_source_fastly_tls_configuration_test.go @@ -0,0 +1,63 @@ +package fastly + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" +) + +func TestAccFastlyDataSourceTLSConfiguration_basic(t *testing.T) { + resourceName := "data.fastly_tls_configuration.subject" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccFastlyDataSourceTLSConfiguration_basic, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet(resourceName, "id"), + resource.TestCheckResourceAttrSet(resourceName, "name"), + resource.TestCheckResourceAttrSet(resourceName, "tls_protocols.#"), + resource.TestCheckResourceAttrSet(resourceName, "http_protocols.#"), + resource.TestCheckResourceAttr(resourceName, "tls_service", "CUSTOM"), + resource.TestCheckResourceAttrSet(resourceName, "default"), + resource.TestCheckResourceAttrSet(resourceName, "created_at"), + resource.TestCheckResourceAttrSet(resourceName, "updated_at"), + resource.TestCheckResourceAttrSet(resourceName, "dns_records.#"), + ), + }, + }, + }) +} + +const testAccFastlyDataSourceTLSConfiguration_basic = ` +data "fastly_tls_configuration" "subject" { + default = true + tls_service = "CUSTOM" +} +` + +func TestAccFastlyDataSourceTLSConfiguration_withIDLookup(t *testing.T) { + resourceName := "data.fastly_tls_configuration.subject" + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccFastlyDataSourceTLSConfiguration_withIDLookup, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrPair(resourceName, "name", "data.fastly_tls_configuration.default", "name"), + ), + }, + }, + }) +} + +const testAccFastlyDataSourceTLSConfiguration_withIDLookup = ` +data "fastly_tls_configuration" "default" { + default = true +} +data "fastly_tls_configuration" "subject" { + id = data.fastly_tls_configuration.default.id +} +` diff --git a/fastly/data_source_fastly_tls_platform_certificate.go b/fastly/data_source_fastly_tls_platform_certificate.go new file mode 100644 index 000000000..81f3b2709 --- /dev/null +++ b/fastly/data_source_fastly_tls_platform_certificate.go @@ -0,0 +1,186 @@ +package fastly + +import ( + "fmt" + "time" + + "github.com/fastly/go-fastly/v3/fastly" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func dataSourceFastlyTLSPlatformCertificate() *schema.Resource { + return &schema.Resource{ + Read: dataSourceFastlyTLSPlatformCertificateRead, + + Schema: map[string]*schema.Schema{ + "id": { + Type: schema.TypeString, + Description: "Unique ID assigned to certificate by Fastly. Conflicts with all the other filters.", + Optional: true, + Computed: true, + ConflictsWith: []string{"domains"}, + }, + "domains": { + Type: schema.TypeSet, + Description: "Domains that are listed in any certificate's Subject Alternative Names (SAN) list.", + Optional: true, + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + ConflictsWith: []string{"id"}, + }, + "created_at": { + Type: schema.TypeString, + Description: "Timestamp (GMT) when the certificate was created.", + Computed: true, + }, + "updated_at": { + Type: schema.TypeString, + Description: "Timestamp (GMT) when the certificate was last updated.", + Computed: true, + }, + "not_before": { + Type: schema.TypeString, + Description: "Timestamp (GMT) when the certificate will become valid.", + Computed: true, + }, + "not_after": { + Type: schema.TypeString, + Description: "Timestamp (GMT) when the certificate will expire.", + Computed: true, + }, + "replace": { + Type: schema.TypeBool, + Description: "A recommendation from Fastly indicating the key associated with this certificate is in need of rotation.", + Computed: true, + }, + "configuration_id": { + Type: schema.TypeString, + Description: "ID of TLS configuration used to terminate TLS traffic.", + Computed: true, + }, + }, + } +} + +func dataSourceFastlyTLSPlatformCertificateRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*FastlyClient).conn + + var certificate *fastly.BulkCertificate + + if v, ok := d.GetOk("id"); ok { + cert, err := conn.GetBulkCertificate(&fastly.GetBulkCertificateInput{ + ID: v.(string), + }) + if err != nil { + return err + } + + certificate = cert + } else { + filters := getPlatformTLSCertificateFilters(d) + + certificates, err := listPlatformTLSCertificates(conn, filters...) + if err != nil { + return err + } + + if len(certificates) == 0 { + return fmt.Errorf("Your query returned no results. Please change your search criteria and try again.") + } + + if len(certificates) > 1 { + return fmt.Errorf("Your query returned more than one result. Please change try a more specific search criteria and try again.") + } + + certificate = certificates[0] + } + + return dataSourceFastlyTLSPlatformCertificateSetAttributes(certificate, d) +} + +type PlatformTLSCertificatePredicate func(certificate *fastly.BulkCertificate) bool + +func getPlatformTLSCertificateFilters(d *schema.ResourceData) []PlatformTLSCertificatePredicate { + var filters []PlatformTLSCertificatePredicate + + if v, ok := d.GetOk("domains"); ok { + filters = append(filters, func(c *fastly.BulkCertificate) bool { + s := v.(*schema.Set) + for _, domain := range c.Domains { + if s.Contains(domain.ID) { + return true + } + } + return false + }) + } + + return filters +} + +func listPlatformTLSCertificates(conn *fastly.Client, filters ...PlatformTLSCertificatePredicate) ([]*fastly.BulkCertificate, error) { + var certificates []*fastly.BulkCertificate + pageNumber := 1 + for { + list, err := conn.ListBulkCertificates(&fastly.ListBulkCertificatesInput{ + PageNumber: pageNumber, + PageSize: 10, + }) + if err != nil { + return nil, err + } + if len(list) == 0 { + break + } + pageNumber++ + + for _, certificate := range list { + if filterPlatformTLSCertificate(certificate, filters) { + certificates = append(certificates, certificate) + } + } + } + + return certificates, nil +} + +func dataSourceFastlyTLSPlatformCertificateSetAttributes(certificate *fastly.BulkCertificate, d *schema.ResourceData) error { + var domains []string + for _, domain := range certificate.Domains { + domains = append(domains, domain.ID) + } + + d.SetId(certificate.ID) + if err := d.Set("created_at", certificate.CreatedAt.Format(time.RFC3339)); err != nil { + return err + } + if err := d.Set("updated_at", certificate.UpdatedAt.Format(time.RFC3339)); err != nil { + return err + } + if err := d.Set("not_before", certificate.NotBefore.Format(time.RFC3339)); err != nil { + return err + } + if err := d.Set("not_after", certificate.NotAfter.Format(time.RFC3339)); err != nil { + return err + } + if err := d.Set("replace", certificate.Replace); err != nil { + return err + } + if err := d.Set("domains", domains); err != nil { + return err + } + if err := d.Set("configuration_id", certificate.Configurations[0].ID); err != nil { + return err + } + + return nil +} + +func filterPlatformTLSCertificate(config *fastly.BulkCertificate, filters []PlatformTLSCertificatePredicate) bool { + for _, f := range filters { + if !f(config) { + return false + } + } + return true +} diff --git a/fastly/data_source_fastly_tls_platform_certificate_ids.go b/fastly/data_source_fastly_tls_platform_certificate_ids.go new file mode 100644 index 000000000..d8e2c43e0 --- /dev/null +++ b/fastly/data_source_fastly_tls_platform_certificate_ids.go @@ -0,0 +1,46 @@ +package fastly + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func dataSourceFastlyTLSPlatformCertificateIDs() *schema.Resource { + return &schema.Resource{ + Read: dataSourceFastlyTLSPlatformCertificateIDsRead, + Schema: map[string]*schema.Schema{ + "ids": { + Type: schema.TypeSet, + Description: "List of IDs corresponding to Platform TLS certificates.", + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceFastlyTLSPlatformCertificateIDsRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*FastlyClient).conn + + certificates, err := listPlatformTLSCertificates(conn) + if err != nil { + return err + } + + var ids []string + for _, certificate := range certificates { + ids = append(ids, certificate.ID) + } + + // 2.x upgrade note - `hashcode.String` was removed from the SDK + // Code will need to be copied into this repository + // https://www.terraform.io/docs/extend/guides/v2-upgrade-guide.html#removal-of-helper-hashcode-package + d.SetId(fmt.Sprintf("%d", hashcode.String(""))) // if other filters are added to this data source, they should be included in this hashcode instead of the empty string + err = d.Set("ids", ids) + if err != nil { + return err + } + + return nil +} diff --git a/fastly/data_source_fastly_tls_platform_certificate_ids_test.go b/fastly/data_source_fastly_tls_platform_certificate_ids_test.go new file mode 100644 index 000000000..54b8fa485 --- /dev/null +++ b/fastly/data_source_fastly_tls_platform_certificate_ids_test.go @@ -0,0 +1,72 @@ +package fastly + +import ( + "fmt" + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/stretchr/testify/require" + "testing" +) + +func TestAccFastlyDataSourceTLSPlatformCertificateIDs(t *testing.T) { + name := acctest.RandomWithPrefix(testResourcePrefix) + domain := fmt.Sprintf("%s.test", name) + + key, cert, ca, err := generateKeyAndCertWithCA(domain) + require.NoError(t, err) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccFastlyDataSourceTLSPlatformCertificateIDSConfig_resources(name, key, cert, ca), + }, + { + Config: testAccFastlyDataSourceTLSPlatformCertificateIDSConfig_resourcesAndData(name, key, cert, ca), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckOutput("result", "true"), + ), + }, + }, + }) +} + +func testAccFastlyDataSourceTLSPlatformCertificateIDSConfig_resources(name, key, cert, ca string) string { + return fmt.Sprintf(` +resource "fastly_tls_private_key" "key" { + name = "%[1]s" + key_pem = < 1 { + return fmt.Errorf("Your query returned more than one result. Please change to a more specific search criteria and try again.") + } + + privateKey = privateKeys[0] + } + + return dataSourceFastlyTLSPrivateKeySetAttributes(privateKey, d) +} + +type TLSPrivateKeyPredicate func(key *fastly.PrivateKey) bool + +func getTLSPrivateKeyFilters(d *schema.ResourceData) []TLSPrivateKeyPredicate { + var filters []TLSPrivateKeyPredicate + + if v, ok := d.GetOk("id"); ok { + filters = append(filters, func(key *fastly.PrivateKey) bool { + return key.ID == v.(string) + }) + } + if v, ok := d.GetOk("name"); ok { + filters = append(filters, func(key *fastly.PrivateKey) bool { + return key.Name == v.(string) + }) + } + if v, ok := d.GetOk("key_length"); ok { + filters = append(filters, func(key *fastly.PrivateKey) bool { + return key.KeyLength == v.(int) + }) + } + if v, ok := d.GetOk("key_type"); ok { + filters = append(filters, func(key *fastly.PrivateKey) bool { + return key.KeyType == v.(string) + }) + } + if v, ok := d.GetOk("public_key_sha1"); ok { + filters = append(filters, func(key *fastly.PrivateKey) bool { + return key.PublicKeySHA1 == v.(string) + }) + } + + return filters +} + +func listTLSPrivateKeys(conn *fastly.Client, filters ...TLSPrivateKeyPredicate) ([]*fastly.PrivateKey, error) { + var privateKeys []*fastly.PrivateKey + pageNumber := 1 + for { + list, err := conn.ListPrivateKeys(&fastly.ListPrivateKeysInput{ + PageNumber: pageNumber, + }) + if err != nil { + return nil, err + } + if len(list) == 0 { + break + } + pageNumber++ + + for _, privateKey := range list { + if filterPrivateKey(privateKey, filters) { + privateKeys = append(privateKeys, privateKey) + } + } + } + + return privateKeys, nil +} + +func dataSourceFastlyTLSPrivateKeySetAttributes(privateKey *fastly.PrivateKey, d *schema.ResourceData) error { + d.SetId(privateKey.ID) + + if err := d.Set("name", privateKey.Name); err != nil { + return err + } + if err := d.Set("created_at", privateKey.CreatedAt.Format(time.RFC3339)); err != nil { + return err + } + if err := d.Set("key_length", privateKey.KeyLength); err != nil { + return err + } + if err := d.Set("key_type", privateKey.KeyType); err != nil { + return err + } + if err := d.Set("replace", privateKey.Replace); err != nil { + return err + } + if err := d.Set("public_key_sha1", privateKey.PublicKeySHA1); err != nil { + return err + } + + return nil +} + +func filterPrivateKey(privateKey *fastly.PrivateKey, filters []TLSPrivateKeyPredicate) bool { + for _, f := range filters { + if !f(privateKey) { + return false + } + } + return true +} diff --git a/fastly/data_source_fastly_tls_private_key_ids.go b/fastly/data_source_fastly_tls_private_key_ids.go new file mode 100644 index 000000000..1469e41d0 --- /dev/null +++ b/fastly/data_source_fastly_tls_private_key_ids.go @@ -0,0 +1,47 @@ +package fastly + +import ( + "fmt" + + "github.com/hashicorp/terraform-plugin-sdk/helper/hashcode" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" +) + +func dataSourceFastlyTLSPrivateKeyIDs() *schema.Resource { + return &schema.Resource{ + Read: dataSourceFastlyTLSPrivateKeyIDsRead, + + Schema: map[string]*schema.Schema{ + "ids": { + Type: schema.TypeSet, + Description: "List of IDs of the TLS private keys.", + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + }, + }, + } +} + +func dataSourceFastlyTLSPrivateKeyIDsRead(d *schema.ResourceData, meta interface{}) error { + conn := meta.(*FastlyClient).conn + + keys, err := listTLSPrivateKeys(conn) + if err != nil { + return err + } + + var ids []string + for _, key := range keys { + ids = append(ids, key.ID) + } + + // 2.x upgrade note - `hashcode.String` was removed from the SDK + // Code will need to be copied into this repository + // https://www.terraform.io/docs/extend/guides/v2-upgrade-guide.html#removal-of-helper-hashcode-package + d.SetId(fmt.Sprintf("%d", hashcode.String(""))) // if other filters are added to this data source, they should be included in this hashcode instead of the empty string + if err := d.Set("ids", ids); err != nil { + return err + } + + return nil +} diff --git a/fastly/data_source_fastly_tls_private_key_ids_test.go b/fastly/data_source_fastly_tls_private_key_ids_test.go new file mode 100644 index 000000000..9b701af1d --- /dev/null +++ b/fastly/data_source_fastly_tls_private_key_ids_test.go @@ -0,0 +1,85 @@ +package fastly + +import ( + "fmt" + "strings" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" + "github.com/stretchr/testify/require" +) + +func TestAccFastlyDataSourceTLSPrivateKeyIds_basic(t *testing.T) { + key, _, err := generateKeyAndCert() + require.NoError(t, err) + + name := acctest.RandomWithPrefix(testResourcePrefix) + + resource.ParallelTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccFastlyDataSourceTLSPrivateKeyIdsConfigOnlyTestKey(key, name), + }, + { + Config: testAccFastlyDataSourceTLSPrivateKeyIdsConfigTestKeyWithData(key, name), + Check: testAccTLSPrivateKeyIDIncluded("data.fastly_tls_private_key_ids.subject", "fastly_tls_private_key.test"), + }, + }, + }) +} + +// This can be replaced with `TestCheckTypeSetElemNestedAttrs` when using SDK 2.x +func testAccTLSPrivateKeyIDIncluded(dataSourceName string, resourceName string) resource.TestCheckFunc { + return func(s *terraform.State) error { + r, ok := s.RootModule().Resources[resourceName] + if !ok { + return fmt.Errorf("resource not found: %s", resourceName) + } + d, ok := s.RootModule().Resources[dataSourceName] + if !ok { + return fmt.Errorf("data source not found: %s", dataSourceName) + } + + for k, v := range d.Primary.Attributes { + if k == "ids.#" { + continue + } + if !strings.HasPrefix(k, "ids.") { + continue + } + if v == r.Primary.ID { + return nil + } + } + + return fmt.Errorf("unable to find private key %s in list of private key ids", r.Primary.ID) + } +} + +func testAccFastlyDataSourceTLSPrivateKeyIdsConfigOnlyTestKey(key, name string) string { + return fmt.Sprintf(` +resource "fastly_tls_private_key" "test" { + key_pem = < **Warning:** The data source's filters are applied using an **AND** boolean operator, so depending on the combination +of filters, they may become mutually exclusive. The exception to this is `id` which must not be specified in combination +with any of the others. + +~> **Note:** If more or less than a single match is returned by the search, Terraform will fail. Ensure that your search is specific enough to return a single key. + +## Example Usage + +```hcl +data "fastly_tls_activation" "example" { + domain = "example.com" +} +``` +{{end}} \ No newline at end of file diff --git a/templates/data-sources/tls_activation_ids.md.tmpl b/templates/data-sources/tls_activation_ids.md.tmpl new file mode 100644 index 000000000..8e246b908 --- /dev/null +++ b/templates/data-sources/tls_activation_ids.md.tmpl @@ -0,0 +1,29 @@ +{{define "data_source_tls_activation_ids"}}--- +layout: "fastly" +page_title: "Fastly: fastly_tls_activation_ids" +sidebar_current: "docs-fastly-datasource-tls_activation_ids" +description: |- +Get the list of TLS Activation identifiers in Fastly. +--- + +# fastly_tls_activation_ids + +Use this data source to get the list of TLS Activation identifiers in Fastly. + +## Example Usage + +```hcl +data "fastly_tls_activation_ids" "example" { + certificate_id = fastly_tls_certificate.example.id +} + +data "fastly_tls_activation" "example" { + for_each = data.fastly_tls_activation_ids.example.ids + id = each.value +} + +output "activation_domains" { + value = [for a in data.fastly_tls_activation.example : a.domain] +} +``` +{{end}} \ No newline at end of file diff --git a/templates/data-sources/tls_certificate.md.tmpl b/templates/data-sources/tls_certificate.md.tmpl new file mode 100644 index 000000000..daf0824df --- /dev/null +++ b/templates/data-sources/tls_certificate.md.tmpl @@ -0,0 +1,26 @@ +{{define "data_source_tls_certificate"}}--- +layout: "fastly" +page_title: "Fastly: fastly_tls_certificate" +sidebar_current: "docs-fastly-datasource-tls_certificate" +description: |- +Get information on Fastly TLS certificate. +--- + +# fastly_tls_certificate + +Use this data source to get information of a TLS certificate for use with other resources. + +~> **Warning:** The data source's filters are applied using an **AND** boolean operator, so depending on the combination +of filters, they may become mutually exclusive. The exception to this is `id` which must not be specified in combination +with any of the others. + +~> **Note:** If more or less than a single match is returned by the search, Terraform will fail. Ensure that your search is specific enough to return a single key. + +## Example Usage + +```hcl +data "fastly_tls_certificate" "example" { + name = "example.com" +} +``` +{{end}} \ No newline at end of file diff --git a/templates/data-sources/tls_certificate_ids.md.tmpl b/templates/data-sources/tls_certificate_ids.md.tmpl new file mode 100644 index 000000000..87976398b --- /dev/null +++ b/templates/data-sources/tls_certificate_ids.md.tmpl @@ -0,0 +1,23 @@ +{{define "data_source_tls_certificate_ids"}}--- +layout: "fastly" +page_title: "Fastly: fastly_tls_certificate_ids" +sidebar_current: "docs-fastly-datasource-tls_certificate_ids" +description: |- +Get IDs of available TLS certificates. +--- + +# fastly_tls_certificate_ids + +Use this data source to get the IDs of available TLS certificates for use with other resources. + +## Example Usage + +```hcl +data "fastly_tls_certificate_ids" "example" {} + +resource "fastly_tls_activation" "example" { + certificate_id = data.fastly_tls_certificate_ids.example.ids[0] + // ... +} +``` +{{end}} \ No newline at end of file diff --git a/templates/data-sources/tls_configuration.md.tmpl b/templates/data-sources/tls_configuration.md.tmpl new file mode 100644 index 000000000..2efa99a44 --- /dev/null +++ b/templates/data-sources/tls_configuration.md.tmpl @@ -0,0 +1,31 @@ +{{define "data_source_tls_configuration"}}--- +layout: "fastly" +page_title: "Fastly: fastly_tls_configuration" +sidebar_current: "docs-fastly-datasource-tls_configuration" +description: |- +Get information on Fastly TLS configuration. +--- + +# fastly_tls_configuration + +Use this data source to get the ID of a TLS configuration for use with other resources. + +~> **Warning:** The data source's filters are applied using an **AND** boolean operator, so depending on the combination +of filters, they may become mutually exclusive. The exception to this is `id` which must not be specified in combination +with any of the others. + +~> **Note:** If more or less than a single match is returned by the search, Terraform will fail. Ensure that your search is specific enough to return a single key. + +## Example Usage + +```hcl +data "fastly_tls_configuration" "example" { + default = true +} + +resource "fastly_tls_activation" "example" { + configuration_id = data.fastly_tls_configuration.example.id + // ... +} +``` +{{end}} diff --git a/templates/data-sources/tls_configuration_ids.md.tmpl b/templates/data-sources/tls_configuration_ids.md.tmpl new file mode 100644 index 000000000..325295abc --- /dev/null +++ b/templates/data-sources/tls_configuration_ids.md.tmpl @@ -0,0 +1,23 @@ +{{define "data_source_tls_configuration_ids"}}--- +layout: "fastly" +page_title: "Fastly: fastly_tls_configuration_ids" +sidebar_current: "docs-fastly-datasource-tls_configuration_ids" +description: |- +Get IDs of available TLS Configurations. +--- + +# fastly_tls_configuration_ids + +Use this data source to get the IDs of available TLS configurations for use with other resources. + +## Example Usage + +```hcl +data "fastly_tls_configuration_ids" "example" {} + +resource "fastly_tls_activation" "example" { + configuration_id = data.fastly_tls_configuration.example.ids[0] + // ... +} +``` +{{end}} \ No newline at end of file diff --git a/templates/data-sources/tls_platform_certificate.md.tmpl b/templates/data-sources/tls_platform_certificate.md.tmpl new file mode 100644 index 000000000..98d1bf621 --- /dev/null +++ b/templates/data-sources/tls_platform_certificate.md.tmpl @@ -0,0 +1,26 @@ +{{define "data_source_tls_platform_certificate"}}--- +layout: "fastly" +page_title: "Fastly: fastly_tls_platform_certificate" +sidebar_current: "docs-fastly-datasource-tls_platform_certificate" +description: |- +Get information on Fastly Platform TLS certificate. +--- + +# fastly_tls_platform_certificate + +Use this data source to get information of a Platform TLS certificate for use with other resources. + +~> **Warning:** The data source's filters are applied using an **AND** boolean operator, so depending on the combination +of filters, they may become mutually exclusive. The exception to this is `id` which must not be specified in combination +with any of the others. + +~> **Note:** If more or less than a single match is returned by the search, Terraform will fail. Ensure that your search is specific enough to return a single key. + +## Example Usage + +```hcl +data "fastly_tls_platform_certificate" "example" { + domains = ["example.com"] +} +``` +{{end}} \ No newline at end of file diff --git a/templates/data-sources/tls_platform_certificate_ids.md.tmpl b/templates/data-sources/tls_platform_certificate_ids.md.tmpl new file mode 100644 index 000000000..652e17b46 --- /dev/null +++ b/templates/data-sources/tls_platform_certificate_ids.md.tmpl @@ -0,0 +1,22 @@ +{{define "data_source_tls_platform_certificate_ids"}}--- +layout: "fastly" +page_title: "Fastly: fastly_tls_platform_certificate_ids" +sidebar_current: "docs-fastly-datasource-tls_platform_certificate_ids" +description: |- +Get IDs of available Platform TLS certificates. +--- + +# fastly_tls_platform_certificate_ids + +Use this data source to get the IDs of available Platform TLS Certificates for use with other resources. + +## Example Usage + +```hcl +data "fastly_tls_platform_certificate_ids" "example" {} + +data "fastly_tls_platform_certificate" "example" { + id = data.fastly_tls_platform_certificate_ids.example.ids[0] +} +``` +{{end}} \ No newline at end of file diff --git a/templates/data-sources/tls_private_key.md.tmpl b/templates/data-sources/tls_private_key.md.tmpl new file mode 100644 index 000000000..9c0db54f0 --- /dev/null +++ b/templates/data-sources/tls_private_key.md.tmpl @@ -0,0 +1,32 @@ +{{define "data_source_tls_private_key"}}--- + +layout: "fastly" +page_title: "Fastly: fastly_tls_private_key" +sidebar_current: "docs-fastly-datasource-tls_private_key" +description: |- + Get information on a TLS Private Key. +--- + +# fastly_tls_private_key + +Use this data source to get information on a TLS Private Key uploaded to Fastly. + +~> **Warning:** The data source's filters are applied using an **AND** boolean operator, so depending on the combination + of filters, they may become mutually exclusive. The exception to this is `id` which must not be specified in combination + with any of the others. + +~> **Note:** If more or less than a single match is returned by the search, Terraform will fail. Ensure that your search + is specific enough to return a single key. + +## Example Usage + +```hcl +data "fastly_tls_private_key" "demo" { + name = "demo-private-key" +} + +output "private_key_needs_replacing" { + value = data.fastly_tls_private_key.demo.replace +} +``` +{{end}} diff --git a/templates/data-sources/tls_private_key_ids.md.tmpl b/templates/data-sources/tls_private_key_ids.md.tmpl new file mode 100644 index 000000000..7bf9818e2 --- /dev/null +++ b/templates/data-sources/tls_private_key_ids.md.tmpl @@ -0,0 +1,22 @@ +{{define "data_source_tls_private_key_ids"}}--- +layout: "fastly" +page_title: "Fastly: fastly_tls_private_key_ids" +sidebar_current: "docs-fastly-datasource-tls_private_key_ids" +description: |- + Get the list of TLS private key identifiers in Fastly. +--- + +# fastly_tls_private_key_ids + +Use this data source to get the list of TLS private key identifiers in Fastly. + +## Example Usage + +```hcl +data "fastly_tls_private_key_ids" "demo" {} + +data "fastly_tls_private_key" "example" { + id = fastly_tls_private_key_ids.demo.ids[0] +} +``` +{{end}} diff --git a/templates/resources/tls_activation.md.tmpl b/templates/resources/tls_activation.md.tmpl new file mode 100644 index 000000000..d6d683951 --- /dev/null +++ b/templates/resources/tls_activation.md.tmpl @@ -0,0 +1,60 @@ +{{define "tls_activation"}}--- +layout: "fastly" +page_title: "Fastly: tls_activation" +sidebar_current: "docs-fastly-resource-tls_activation" +description: |- +Enables TLS on a domain +--- + +# fastly_tls_activation + +Enables TLS on a domain using a specified custom TLS certificate. + +~> **Note:** The Fastly service must be provisioned _prior_ to enabling TLS on it. This can be achieved in Terraform using [`depends_on`](https://www.terraform.io/docs/configuration/meta-arguments/depends_on.html). + +## Example Usage + +Basic usage: + +```hcl +resource "fastly_service_v1" "demo" { + name = "my-service" + + domain { + name = "example.com" + } + + backend { + address = "127.0.0.1" + name = "localhost" + } + + force_destroy = true +} + +resource "fastly_tls_private_key" "demo" { + key_pem = "..." + name = "demo-key" +} + +resource "fastly_tls_certificate" "demo" { + certificate_body = "..." + name = "demo-cert" + depends_on = [fastly_tls_private_key.demo] +} + +resource "fastly_tls_activation" "test" { + certificate_id = fastly_tls_certificate.demo.id + domain = "example.com" + depends_on = [fastly_service_v1.demo] +} +``` + +## Import + +A TLS activation can be imported using its ID, e.g. + +``` +$ terraform import fastly_tls_activation.demo xxxxxxxx +``` +{{end}} diff --git a/templates/resources/tls_certificate.md.tmpl b/templates/resources/tls_certificate.md.tmpl new file mode 100644 index 000000000..e324cf86e --- /dev/null +++ b/templates/resources/tls_certificate.md.tmpl @@ -0,0 +1,63 @@ +{{define "tls_certificate"}}--- +layout: "fastly" +page_title: "Fastly: tls_certificate" +sidebar_current: "docs-fastly-resource-tls_certificate" +description: |- +Uploads a custom TLS certificate +--- + +# fastly_tls_certificate + +Uploads a custom TLS certificate to Fastly to be used to terminate TLS traffic. + +-> Each TLS certificate **must** have its corresponding private key uploaded _prior_ to uploading the certificate. This +can be achieved in Terraform using [`depends_on`](https://www.terraform.io/docs/configuration/meta-arguments/depends_on.html) + +## Example Usage + +Basic usage: + +```hcl +resource "tls_private_key" "key" { + algorithm = "RSA" +} + +resource "tls_self_signed_cert" "cert" { + key_algorithm = tls_private_key.key.algorithm + private_key_pem = tls_private_key.key.private_key_pem + + subject { + common_name = "example.com" + } + + is_ca_certificate = true + validity_period_hours = 360 + + allowed_uses = [ + "cert_signing", + "server_auth", + ] + + dns_names = ["example.com"] +} + +resource "fastly_tls_private_key" "key" { + key_pem = tls_private_key.key.private_key_pem + name = "tf-demo" +} + +resource "fastly_tls_certificate" "example" { + name = "tf-demo" + certificate_body = tls_self_signed_cert.cert.cert_pem + depends_on = [fastly_tls_private_key.key] // The private key has to be present before the certificate can be uploaded +} +``` + +## Import + +A certificate can be imported using its Fastly certificate ID, e.g. + +``` +$ terraform import fastly_tls_certificate.demo xxxxxxxxxxx +``` +{{end}} \ No newline at end of file diff --git a/templates/resources/tls_platform_certificate.md.tmpl b/templates/resources/tls_platform_certificate.md.tmpl new file mode 100644 index 000000000..e4dcc7b8e --- /dev/null +++ b/templates/resources/tls_platform_certificate.md.tmpl @@ -0,0 +1,98 @@ +{{define "tls_platform_certificate"}}--- +layout: "fastly" +page_title: "Fastly: tls_platform_certificate" +sidebar_current: "docs-fastly-resource-tls_platform_certificate" +description: |- +Uploads a TLS certificate to the Platform TLS service +--- + +# fastly_tls_platform_certificate + +Uploads a TLS certificate to the Fastly Platform TLS service. + +-> Each TLS certificate **must** have its corresponding private key uploaded _prior_ to uploading the certificate. This +can be achieved in Terraform using [`depends_on`](https://www.terraform.io/docs/configuration/meta-arguments/depends_on.html) + +## Example Usage + +Basic usage with self-signed CA: + +```hcl +resource "tls_private_key" "ca_key" { + algorithm = "RSA" +} + +resource "tls_private_key" "key" { + algorithm = "RSA" +} + +resource "tls_self_signed_cert" "ca" { + key_algorithm = tls_private_key.ca_key.algorithm + private_key_pem = tls_private_key.ca_key.private_key_pem + + subject { + common_name = "Example CA" + } + + is_ca_certificate = true + validity_period_hours = 360 + + allowed_uses = [ + "cert_signing", + "server_auth", + ] +} + +resource "tls_cert_request" "example" { + key_algorithm = tls_private_key.key.algorithm + private_key_pem = tls_private_key.key.private_key_pem + + subject { + common_name = "example.com" + } + + dns_names = ["example.com", "www.example.com"] +} + +resource "tls_locally_signed_cert" "cert" { + cert_request_pem = tls_cert_request.example.cert_request_pem + ca_key_algorithm = tls_private_key.ca_key.algorithm + ca_private_key_pem = tls_private_key.ca_key.private_key_pem + ca_cert_pem = tls_self_signed_cert.ca.cert_pem + + validity_period_hours = 360 + + allowed_uses = [ + "cert_signing", + "server_auth", + ] +} + +data "fastly_tls_configuration" "config" { + tls_service = "PLATFORM" +} + +resource "fastly_tls_private_key" "key" { + key_pem = tls_private_key.key.private_key_pem + name = "tf-demo" +} + +resource "fastly_tls_platform_certificate" "cert" { + certificate_body = tls_locally_signed_cert.cert.cert_pem + intermediates_blob = tls_self_signed_cert.ca.cert_pem + + configuration_id = data.fastly_tls_configuration.config.id + allow_untrusted_root = true + + depends_on = [fastly_tls_private_key.key] +} +``` + +## Import + +A certificate can be imported using its Fastly certificate ID, e.g. + +``` +$ terraform import fastly_tls_platform_certificate.demo xxxxxxxxxxx +``` +{{end}} \ No newline at end of file diff --git a/templates/resources/tls_private_key.md.tmpl b/templates/resources/tls_private_key.md.tmpl new file mode 100644 index 000000000..9bcfc9132 --- /dev/null +++ b/templates/resources/tls_private_key.md.tmpl @@ -0,0 +1,37 @@ +{{define "tls_private_key"}}--- +layout: "fastly" +page_title: "Fastly: tls_private_key" +sidebar_current: "docs-fastly-resource-tls_private_key" +description: |- +Uploads a Custom TLS Private Key +--- + +# fastly_tls_private_key + +Uploads a Custom TLS Private Key to Fastly. This can be combined with a `fastly_tls_custom_certificate` resource to provide a TLS Certificate able to be applied to a Fastly Service. + +The Private Key resource requires a key in PEM format, and a name to identify it. + +## Example Usage + +Basic usage: + +```hcl +resource "tls_private_key" "demo" { + algorithm = "RSA" +} + +resource "fastly_tls_private_key" "demo" { + key_pem = tls_private_key.demo.private_key_pem + name = "tf-demo" +} +``` + +## Import + +A Private Key can be imported using its ID, e.g. + +``` +$ terraform import fastly_tls_private_key.demo xxxxxxxxxxx +``` +{{end}} \ No newline at end of file diff --git a/vendor/github.com/fastly/go-fastly/v3/fastly/blobstorage.go b/vendor/github.com/fastly/go-fastly/v3/fastly/blobstorage.go index 846a3478e..0d1a45e64 100644 --- a/vendor/github.com/fastly/go-fastly/v3/fastly/blobstorage.go +++ b/vendor/github.com/fastly/go-fastly/v3/fastly/blobstorage.go @@ -27,6 +27,7 @@ type BlobStorage struct { MessageType string `mapstructure:"message_type"` Placement string `mapstructure:"placement"` ResponseCondition string `mapstructure:"response_condition"` + FileMaxBytes uint `mapstructure:"file_max_bytes"` CreatedAt *time.Time `mapstructure:"created_at"` UpdatedAt *time.Time `mapstructure:"updated_at"` DeletedAt *time.Time `mapstructure:"deleted_at"` @@ -98,6 +99,7 @@ type CreateBlobStorageInput struct { MessageType string `form:"message_type,omitempty"` Placement string `form:"placement,omitempty"` ResponseCondition string `form:"response_condition,omitempty"` + FileMaxBytes uint `form:"file_max_bytes,omitempty"` } // CreateBlobStorage creates a new Fastly blob storage. @@ -188,6 +190,7 @@ type UpdateBlobStorageInput struct { MessageType *string `form:"message_type,omitempty"` Placement *string `form:"placement,omitempty"` ResponseCondition *string `form:"response_condition,omitempty"` + FileMaxBytes *uint `form:"file_max_bytes,omitempty"` } // UpdateBlobStorage updates a specific blob storage. diff --git a/vendor/github.com/fastly/go-fastly/v3/fastly/client.go b/vendor/github.com/fastly/go-fastly/v3/fastly/client.go index 03f8b3019..46d5f6d7b 100644 --- a/vendor/github.com/fastly/go-fastly/v3/fastly/client.go +++ b/vendor/github.com/fastly/go-fastly/v3/fastly/client.go @@ -46,7 +46,7 @@ const DefaultRealtimeStatsEndpoint = "https://rt.fastly.com" var ProjectURL = "github.com/fastly/go-fastly" // ProjectVersion is the version of this library. -var ProjectVersion = "3.0.0" +var ProjectVersion = "3.3.0" // UserAgent is the user agent for this particular client. var UserAgent = fmt.Sprintf("FastlyGo/%s (+%s; %s)", diff --git a/vendor/github.com/fastly/go-fastly/v3/fastly/cloudfiles.go b/vendor/github.com/fastly/go-fastly/v3/fastly/cloudfiles.go index 325dd47e0..b0e6c22d3 100644 --- a/vendor/github.com/fastly/go-fastly/v3/fastly/cloudfiles.go +++ b/vendor/github.com/fastly/go-fastly/v3/fastly/cloudfiles.go @@ -27,6 +27,7 @@ type Cloudfiles struct { MessageType string `mapstructure:"message_type"` TimestampFormat string `mapstructure:"timestamp_format"` PublicKey string `mapstructure:"public_key"` + CompressionCodec string `mapstructure:"compression_codec"` CreatedAt *time.Time `mapstructure:"created_at"` UpdatedAt *time.Time `mapstructure:"updated_at"` DeletedAt *time.Time `mapstructure:"deleted_at"` @@ -98,6 +99,7 @@ type CreateCloudfilesInput struct { MessageType string `form:"message_type,omitempty"` TimestampFormat string `form:"timestamp_format,omitempty"` PublicKey string `form:"public_key,omitempty"` + CompressionCodec string `form:"compression_codec,omitempty"` } // CreateCloudfiles creates a new Fastly Cloudfiles. @@ -188,6 +190,7 @@ type UpdateCloudfilesInput struct { MessageType *string `form:"message_type,omitempty"` TimestampFormat *string `form:"timestamp_format,omitempty"` PublicKey *string `form:"public_key,omitempty"` + CompressionCodec *string `form:"compression_codec,omitempty"` } // UpdateCloudfiles updates a specific Cloudfiles. diff --git a/vendor/github.com/fastly/go-fastly/v3/fastly/custom_tls_activation.go b/vendor/github.com/fastly/go-fastly/v3/fastly/custom_tls_activation.go index 19b3dad2d..7aaf62e1c 100644 --- a/vendor/github.com/fastly/go-fastly/v3/fastly/custom_tls_activation.go +++ b/vendor/github.com/fastly/go-fastly/v3/fastly/custom_tls_activation.go @@ -129,7 +129,7 @@ func (c *Client) GetTLSActivation(i *GetTLSActivationInput) (*TLSActivation, err type CreateTLSActivationInput struct { ID string `jsonapi:"primary,tls_activation"` // ID value does not need to be set. Certificate *CustomTLSCertificate `jsonapi:"relation,tls_certificate"` // Only ID of CustomTLSCertificate needs to be set. - Configuration *TLSConfiguration `jsonapi:"relation,tls_configuration"` + Configuration *TLSConfiguration `jsonapi:"relation,tls_configuration,omitempty"` Domain *TLSDomain `jsonapi:"relation,tls_domain"` } @@ -138,9 +138,6 @@ func (c *Client) CreateTLSActivation(i *CreateTLSActivationInput) (*TLSActivatio if i.Certificate == nil { return nil, ErrMissingTLSCertificate } - if i.Configuration == nil { - return nil, ErrMissingTLSConfiguration - } if i.Domain == nil { return nil, ErrMissingTLSDomain } diff --git a/vendor/github.com/fastly/go-fastly/v3/fastly/custom_tls_certificate.go b/vendor/github.com/fastly/go-fastly/v3/fastly/custom_tls_certificate.go index 10d259e88..e48d67f94 100644 --- a/vendor/github.com/fastly/go-fastly/v3/fastly/custom_tls_certificate.go +++ b/vendor/github.com/fastly/go-fastly/v3/fastly/custom_tls_certificate.go @@ -20,12 +20,12 @@ type CustomTLSCertificate struct { Replace bool `jsonapi:"attr,replace"` SerialNumber string `jsonapi:"attr,serial_number"` SignatureAlgorithm string `jsonapi:"attr,signature_algorithm"` - TLSDomains []*TLSDomain `jsonapi:"relation,tls_domains"` + Domains []*TLSDomain `jsonapi:"relation,tls_domains"` CreatedAt *time.Time `jsonapi:"attr,created_at,iso8601"` UpdatedAt *time.Time `jsonapi:"attr,updated_at,iso8601"` } -// ListCustomTLSCertificatesInput is used as input to the ListCustomTLSCertificatesInput function. +// ListCustomTLSCertificatesInput is used as input to the Client.ListCustomTLSCertificates function. type ListCustomTLSCertificatesInput struct { FilterNotAfter string // Limit the returned certificates to those that expire prior to the specified date in UTC. Accepts parameters: lte (e.g., filter[not_after][lte]=2020-05-05). FilterTLSDomainsID string // Limit the returned certificates to those that include the specific domain. @@ -124,7 +124,7 @@ func (c *Client) GetCustomTLSCertificate(i *GetCustomTLSCertificateInput) (*Cust type CreateCustomTLSCertificateInput struct { ID string `jsonapi:"primary,tls_certificate"` // ID value does not need to be set. CertBlob string `jsonapi:"attr,cert_blob"` - Name string `jsonapi:"attr,name"` + Name string `jsonapi:"attr,name,omitempty"` } // CreateCustomTLSCertificate creates a custom TLS certificate. @@ -132,9 +132,6 @@ func (c *Client) CreateCustomTLSCertificate(i *CreateCustomTLSCertificateInput) if i.CertBlob == "" { return nil, ErrMissingCertBlob } - if i.Name == "" { - return nil, ErrMissingName - } p := "/tls/certificates" @@ -155,7 +152,7 @@ func (c *Client) CreateCustomTLSCertificate(i *CreateCustomTLSCertificateInput) type UpdateCustomTLSCertificateInput struct { ID string `jsonapi:"primary,tls_certificate"` CertBlob string `jsonapi:"attr,cert_blob"` - Name string `jsonapi:"attr,name"` + Name string `jsonapi:"attr,name,omitempty"` } // UpdateCustomTLSCertificate replace a certificate with a newly reissued certificate. @@ -171,10 +168,6 @@ func (c *Client) UpdateCustomTLSCertificate(i *UpdateCustomTLSCertificateInput) return nil, ErrMissingCertBlob } - if i.Name == "" { - return nil, ErrMissingName - } - path := fmt.Sprintf("/tls/certificates/%s", i.ID) resp, err := c.PatchJSONAPI(path, i, nil) if err != nil { diff --git a/vendor/github.com/fastly/go-fastly/v3/fastly/custom_tls_domain.go b/vendor/github.com/fastly/go-fastly/v3/fastly/custom_tls_domain.go new file mode 100644 index 000000000..bf9155f24 --- /dev/null +++ b/vendor/github.com/fastly/go-fastly/v3/fastly/custom_tls_domain.go @@ -0,0 +1,92 @@ +package fastly + +import ( + "fmt" + "reflect" + "strconv" + + "github.com/google/jsonapi" +) + +// ListTLSDomainsInput is used as input to Client.ListTLSDomains. +type ListTLSDomainsInput struct { + // Limit the returned domains to those currently using Fastly to terminate TLS with SNI (that is, domains considered "in use") + FilterInUse *bool + // Limit the returned domains to those listed in the given TLS certificate's SAN list + FilterTLSCertificateID string + // Limit the returned domains to those for a given TLS subscription + FilterTLSSubscriptionID string + // Include related objects + Include string + // Current page + PageNumber int + // Number of records per page + PageSize int + // The order in which to list the results by creation date + Sort string +} + +// formatFilters converts user input into query parameters for filtering. +func (l *ListTLSDomainsInput) formatFilters() map[string]string { + result := map[string]string{} + pairings := map[string]interface{}{ + "filter[in_use]": l.FilterInUse, + "filter[tls_certificate.id]": l.FilterTLSCertificateID, + "filter[tls_subscriptions.id]": l.FilterTLSSubscriptionID, + "include": l.Include, + "page[number]": l.PageNumber, + "page[size]": l.PageSize, + "sort": l.Sort, + } + + for key, value := range pairings { + switch t := value.(type) { + case string: + if t != "" { + result[key] = t + } + case int: + if t != 0 { + result[key] = strconv.Itoa(t) + } + case *bool: + if t != nil { + result[key] = strconv.FormatBool(*t) + } + } + } + + return result +} + +// ListTLSDomains retrieves a page of TLS domains. +func (c *Client) ListTLSDomains(i *ListTLSDomainsInput) ([]*TLSDomain, error) { + p := "/tls/domains" + filters := &RequestOptions{ + Params: i.formatFilters(), + Headers: map[string]string{ + "Accept": "application/vnd.api+json", // this is required otherwise the filters don't work + }, + } + + r, err := c.Get(p, filters) + if err != nil { + return nil, err + } + + data, err := jsonapi.UnmarshalManyPayload(r.Body, reflect.TypeOf(new(TLSDomain))) + if err != nil { + return nil, err + } + + a := make([]*TLSDomain, len(data)) + for i := range data { + typed, ok := data[i].(*TLSDomain) + if !ok { + return nil, fmt.Errorf("unexpected response type: %T", data[i]) + } + a[i] = typed + } + + return a, nil +} diff --git a/vendor/github.com/fastly/go-fastly/v3/fastly/digitalocean.go b/vendor/github.com/fastly/go-fastly/v3/fastly/digitalocean.go index 608d7a5ad..ff56fe46f 100644 --- a/vendor/github.com/fastly/go-fastly/v3/fastly/digitalocean.go +++ b/vendor/github.com/fastly/go-fastly/v3/fastly/digitalocean.go @@ -27,6 +27,7 @@ type DigitalOcean struct { TimestampFormat string `mapstructure:"timestamp_format"` Placement string `mapstructure:"placement"` PublicKey string `mapstructure:"public_key"` + CompressionCodec string `mapstructure:"compression_codec"` CreatedAt *time.Time `mapstructure:"created_at"` UpdatedAt *time.Time `mapstructure:"updated_at"` DeletedAt *time.Time `mapstructure:"deleted_at"` @@ -98,6 +99,7 @@ type CreateDigitalOceanInput struct { TimestampFormat string `form:"timestamp_format,omitempty"` Placement string `form:"placement,omitempty"` PublicKey string `form:"public_key,omitempty"` + CompressionCodec string `form:"compression_codec,omitempty"` } // CreateDigitalOcean creates a new Fastly DigitalOcean. @@ -188,6 +190,7 @@ type UpdateDigitalOceanInput struct { TimestampFormat *string `form:"timestamp_format,omitempty"` Placement *string `form:"placement,omitempty"` PublicKey *string `form:"public_key,omitempty"` + CompressionCodec *string `form:"compression_codec,omitempty"` } // UpdateDigitalOcean updates a specific DigitalOcean. diff --git a/vendor/github.com/fastly/go-fastly/v3/fastly/errors.go b/vendor/github.com/fastly/go-fastly/v3/fastly/errors.go index e7d3eb985..d4678f925 100644 --- a/vendor/github.com/fastly/go-fastly/v3/fastly/errors.go +++ b/vendor/github.com/fastly/go-fastly/v3/fastly/errors.go @@ -179,6 +179,10 @@ var ErrMissingTLSConfiguration = NewFieldError("TLSConfiguration") // requires a "TLSDomain" key, but one was not set. var ErrMissingTLSDomain = NewFieldError("TLSDomain") +// ErrCommonNameNotInDomains is an error that is returned when an input struct +// requires that the domain in "CommonName" is also in "Domains" +var ErrCommonNameNotInDomains = NewFieldError("CommonName").Message("CommonName must be in Domains") + // ErrMissingTo is an error that is returned when an input struct // requires a "To" key, but one was not set. var ErrMissingTo = NewFieldError("To") diff --git a/vendor/github.com/fastly/go-fastly/v3/fastly/platform_tls.go b/vendor/github.com/fastly/go-fastly/v3/fastly/platform_tls.go index 7cfb000bb..29875c993 100644 --- a/vendor/github.com/fastly/go-fastly/v3/fastly/platform_tls.go +++ b/vendor/github.com/fastly/go-fastly/v3/fastly/platform_tls.go @@ -3,6 +3,7 @@ package fastly import ( "fmt" "reflect" + "strconv" "time" "github.com/google/jsonapi" @@ -28,16 +29,19 @@ type TLSConfiguration struct { // TLSDomain represents a domain (including wildcard domains) that is listed on a certificate's Subject Alternative Names (SAN) list. type TLSDomain struct { - ID string `jsonapi:"primary,tls_domain"` - Type string `jsonapi:"attr,type"` + ID string `jsonapi:"primary,tls_domain"` + Type string `jsonapi:"attr,type"` + Activations []*TLSActivation `jsonapi:"relation,tls_activations,omitempty"` + Certificates []*CustomTLSCertificate `jsonapi:"relation,tls_certificates,omitempty"` + Subscriptions []*TLSSubscription `jsonapi:"relation,tls_subscriptions,omitempty"` } // ListBulkCertificatesInput is used as input to the ListBulkCertificates function. type ListBulkCertificatesInput struct { - PageNumber *uint // The page index for pagination. - PageSize *uint // The number of keys per page. - FilterTLSDomainsIDMatch *string // Filter certificates by their matching, fully-qualified domain name. Returns all partial matches. Must provide a value longer than 3 characters. - Sort *string // The order in which to list certificates. Valid values are created_at, not_before, not_after. May precede any value with a - for descending. + PageNumber int // The page index for pagination. + PageSize int // The number of keys per page. + FilterTLSDomainsIDMatch string // Filter certificates by their matching, fully-qualified domain name. Returns all partial matches. Must provide a value longer than 3 characters. + Sort string // The order in which to list certificates. Valid values are created_at, not_before, not_after. May precede any value with a - for descending. } // formatFilters converts user input into query parameters for filtering. @@ -50,8 +54,15 @@ func (i *ListBulkCertificatesInput) formatFilters() map[string]string { "sort": i.Sort, } for key, value := range pairings { - if !reflect.ValueOf(value).IsNil() { - result[key] = fmt.Sprintf("%v", reflect.ValueOf(value).Elem()) + switch v := value.(type) { + case int: + if v != 0 { + result[key] = strconv.Itoa(v) + } + case string: + if v != "" { + result[key] = v + } } } return result @@ -121,6 +132,7 @@ func (c *Client) GetBulkCertificate(i *GetBulkCertificateInput) (*BulkCertificat type CreateBulkCertificateInput struct { CertBlob string `jsonapi:"attr,cert_blob"` IntermediatesBlob string `jsonapi:"attr,intermediates_blob"` + AllowUntrusted bool `jsonapi:"attr,allow_untrusted_root,omitempty"` Configurations []*TLSConfiguration `jsonapi:"relation,tls_configurations,tls_configuration"` } @@ -153,7 +165,8 @@ func (c *Client) CreateBulkCertificate(i *CreateBulkCertificateInput) (*BulkCert type UpdateBulkCertificateInput struct { ID string `jsonapi:"attr,id"` CertBlob string `jsonapi:"attr,cert_blob"` - IntermediatesBlob string `jsonapi:"attr,intermediates_blob"` + IntermediatesBlob string `jsonapi:"attr,intermediates_blob,omitempty"` + AllowUntrusted bool `jsonapi:"attr,allow_untrusted_root"` } // UpdateBulkCertificate replace a certificate with a newly reissued certificate. diff --git a/vendor/github.com/fastly/go-fastly/v3/fastly/tls.go b/vendor/github.com/fastly/go-fastly/v3/fastly/tls.go index 285ee7515..a8bc83265 100644 --- a/vendor/github.com/fastly/go-fastly/v3/fastly/tls.go +++ b/vendor/github.com/fastly/go-fastly/v3/fastly/tls.go @@ -3,6 +3,7 @@ package fastly import ( "fmt" "reflect" + "strconv" "time" "github.com/google/jsonapi" @@ -27,9 +28,9 @@ type PrivateKey struct { // ListPrivateKeysInput is used as input to the ListPrivateKeys function. type ListPrivateKeysInput struct { - PageNumber *uint // The page index for pagination. - PageSize *uint // The number of keys per page. - FilterInUse *string // Limit the returned keys to those without any matching TLS certificates. + PageNumber int // The page index for pagination. + PageSize int // The number of keys per page. + FilterInUse string // Limit the returned keys to those without any matching TLS certificates. } // formatFilters converts user input into query parameters for filtering. @@ -40,9 +41,17 @@ func (i *ListPrivateKeysInput) formatFilters() map[string]string { "page[size]": i.PageSize, "page[number]": i.PageNumber, } + for key, value := range pairings { - if !reflect.ValueOf(value).IsNil() { - result[key] = fmt.Sprintf("%v", reflect.ValueOf(value).Elem()) + switch t := reflect.TypeOf(value).String(); t { + case "string": + if value != "" { + result[key] = value.(string) + } + case "int": + if value != 0 { + result[key] = strconv.Itoa(value.(int)) + } } } return result diff --git a/vendor/github.com/fastly/go-fastly/v3/fastly/tls_subscription.go b/vendor/github.com/fastly/go-fastly/v3/fastly/tls_subscription.go new file mode 100644 index 000000000..cf4d5ecec --- /dev/null +++ b/vendor/github.com/fastly/go-fastly/v3/fastly/tls_subscription.go @@ -0,0 +1,231 @@ +package fastly + +import ( + "fmt" + "reflect" + "strconv" + "time" + + "github.com/google/jsonapi" +) + +// TLSSubscription represents a managed TLS certificate +type TLSSubscription struct { + ID string `jsonapi:"primary,tls_subscription"` + CertificateAuthority string `jsonapi:"attr,certificate_authority"` + State string `jsonapi:"attr,state"` + CreatedAt *time.Time `jsonapi:"attr,created_at,iso8601"` + UpdatedAt *time.Time `jsonapi:"attr,updated_at,iso8601"` + Configuration *TLSConfiguration `jsonapi:"relation,tls_configuration"` + CommonName *TLSDomain `jsonapi:"relation,common_name"` + Domains []*TLSDomain `jsonapi:"relation,tls_domains"` + Certificates []*TLSSubscriptionCertificate `jsonapi:"relation,tls_certificates"` + Authorizations []*TLSAuthorizations `jsonapi:"relation,tls_authorizations"` +} + +type TLSSubscriptionCertificate struct { + ID string `jsonapi:"primary,tls_certificate"` +} + +// TLSAuthorizations gives information needed to verify domain ownership in order to enable a TLSSubscription +type TLSAuthorizations struct { + ID string `jsonapi:"primary,tls_authorization"` + // Nested structs only work with values, not pointers. See https://github.com/google/jsonapi/pull/99 + Challenges []TLSChallenge `jsonapi:"attr,challenges"` + CreatedAt *time.Time `jsonapi:"attr,created_at,iso8601,omitempty"` + UpdatedAt *time.Time `jsonapi:"attr,updated_at,iso8601,omitempty"` + State string `jsonapi:"attr,state,omitempty"` +} + +// TLSChallenge represents a DNS record to be added for a specific type of domain ownership challenge +type TLSChallenge struct { + Type string `jsonapi:"attr,type"` + RecordType string `jsonapi:"attr,record_type"` + RecordName string `jsonapi:"attr,record_name"` + Values []string `jsonapi:"attr,values"` +} + +// ListTLSSubscriptionsInput is used as input to the ListTLSSubscriptions function +type ListTLSSubscriptionsInput struct { + // Limit the returned subscriptions by state. Valid values are pending, processing, issued, and renewing. Accepts parameters: not (e.g., filter[state][not]=renewing). + FilterState string + // Limit the returned subscriptions to those that include the specific domain. + FilterTLSDomainsID string + // Include related objects. Optional, comma-separated values. Permitted values: tls_authorizations. + Include string + // Current page. + PageNumber int + // Number of records per page. + PageSize int + // The order in which to list the results by creation date. Accepts created_at (ascending sort order) or -created_at (descending). + Sort string +} + +// formatFilters converts user input into query parameters for filtering +func (s *ListTLSSubscriptionsInput) formatFilters() map[string]string { + result := map[string]string{} + pairings := map[string]interface{}{ + "filter[state]": s.FilterState, + "filter[tls_domains.id]": s.FilterTLSDomainsID, + "include": s.Include, + "page[number]": s.PageNumber, + "page[size]": s.PageSize, + "sort": s.Sort, + } + + for key, v := range pairings { + switch value := v.(type) { + case string: + if value != "" { + result[key] = value + } + case int: + if value != 0 { + result[key] = strconv.Itoa(value) + } + } + } + return result +} + +// ListTLSSubscriptions lists all managed TLS subscriptions +func (c *Client) ListTLSSubscriptions(i *ListTLSSubscriptionsInput) ([]*TLSSubscription, error) { + response, err := c.Get("/tls/subscriptions", &RequestOptions{ + Params: i.formatFilters(), + Headers: map[string]string{ + "Accept": "application/vnd.api+json", // Needed for "include" but seemingly not the other fields + }, + }) + if err != nil { + return nil, err + } + + data, err := jsonapi.UnmarshalManyPayload(response.Body, reflect.TypeOf(new(TLSSubscription))) + if err != nil { + return nil, err + } + + // Convert slice of interface{}s to a slice of TLSSubscription structs + subscriptions := make([]*TLSSubscription, len(data)) + for i := range data { + typed, ok := data[i].(*TLSSubscription) + if !ok { + return nil, fmt.Errorf("unexpected response type: %T", data[i]) + } + subscriptions[i] = typed + } + + return subscriptions, nil +} + +// CreateTLSSubscriptionInput is used as input to the CreateTLSSubscription function +type CreateTLSSubscriptionInput struct { + // ID value is ignored and should not be set, needed to make JSONAPI work correctly. + ID string `jsonapi:"primary,tls_subscription"` + // CertificateAuthority is the entity that issues and certifies the TLS certificates for your subscription. Valid values are lets-encrypt or globalsign. + CertificateAuthority string `jsonapi:"attr,certificate_authority,omitempty"` + // Configuration options that apply to the enabled domains on this subscription. Only ID needs to be populated + Configuration *TLSConfiguration `jsonapi:"relation,tls_configuration,omitempty"` + // CommonName is the common name associated with the subscription generated by Fastly TLS. Must be included in Domains. Only the ID fields of each one need to be set. + CommonName *TLSDomain `jsonapi:"relation,common_name,omitempty"` + // Domains list to enable TLS for. Only the ID fields of each one need to be set. + Domains []*TLSDomain `jsonapi:"relation,tls_domain"` +} + +func (c *Client) CreateTLSSubscription(i *CreateTLSSubscriptionInput) (*TLSSubscription, error) { + if len(i.Domains) == 0 { + return nil, ErrMissingTLSDomain + } + if i.CommonName != nil && !domainInSlice(i.Domains, i.CommonName) { + return nil, ErrCommonNameNotInDomains + } + + response, err := c.PostJSONAPI("/tls/subscriptions", i, nil) + if err != nil { + return nil, err + } + + var subscription TLSSubscription + err = jsonapi.UnmarshalPayload(response.Body, &subscription) + if err != nil { + return nil, err + } + + return &subscription, nil +} + +// domainInSlice takes a slice of TLSDomain structs, and another TLSDomain struct to search for, returning true if any +// of the ID fields in the slice match +func domainInSlice(haystack []*TLSDomain, needle *TLSDomain) bool { + for _, s := range haystack { + if s.ID == needle.ID { + return true + } + } + + return false +} + +// GetTLSSubscriptionInput is used as input to the GetTLSSubscription function +type GetTLSSubscriptionInput struct { + // ID of the TLS subscription to fetch. + ID string + // Include related objects. Optional, comma-separated values. Permitted values: tls_authorizations. + Include *string +} + +func (c *Client) GetTLSSubscription(i *GetTLSSubscriptionInput) (*TLSSubscription, error) { + if i.ID == "" { + return nil, ErrMissingID + } + + path := fmt.Sprintf("/tls/subscriptions/%s", i.ID) + + requestOptions := &RequestOptions{ + Headers: map[string]string{ + "Accept": "application/vnd.api+json", // this is required otherwise the params don't work + }, + } + + if i.Include != nil { + requestOptions.Params = map[string]string{"include": *i.Include} + } + + response, err := c.Get(path, requestOptions) + if err != nil { + return nil, err + } + + var subscription TLSSubscription + err = jsonapi.UnmarshalPayload(response.Body, &subscription) + if err != nil { + return nil, err + } + + return &subscription, err +} + +// DeleteTLSSubscriptionInput is used as input to the DeleteTLSSubscription function +type DeleteTLSSubscriptionInput struct { + // ID of the TLS subscription to delete. + ID string + // Force the subscription to be deleted, even if domains are active. Warning: can disable production traffic. + Force bool +} + +func (c *Client) DeleteTLSSubscription(i *DeleteTLSSubscriptionInput) error { + if i.ID == "" { + return ErrMissingID + } + + var ro RequestOptions + if i.Force { + ro.Params = map[string]string{ + "force": "true", + } + } + + path := fmt.Sprintf("/tls/subscriptions/%s", i.ID) + _, err := c.Delete(path, &ro) + return err +} diff --git a/vendor/github.com/google/jsonapi/.travis.yml b/vendor/github.com/google/jsonapi/.travis.yml index d07c5f5f5..6e73a067c 100644 --- a/vendor/github.com/google/jsonapi/.travis.yml +++ b/vendor/github.com/google/jsonapi/.travis.yml @@ -1,7 +1,13 @@ language: go +arch: + - amd64 + - ppc64le go: - - 1.8.x - - 1.9.x - 1.10.x + - 1.11.x + - 1.12.x + - 1.13.x + - 1.14.x + - 1.15.x - tip script: go test ./... -v diff --git a/vendor/github.com/google/jsonapi/README.md b/vendor/github.com/google/jsonapi/README.md index 44b054181..8dfb9438b 100644 --- a/vendor/github.com/google/jsonapi/README.md +++ b/vendor/github.com/google/jsonapi/README.md @@ -3,10 +3,13 @@ [![Build Status](https://travis-ci.org/google/jsonapi.svg?branch=master)](https://travis-ci.org/google/jsonapi) [![Go Report Card](https://goreportcard.com/badge/github.com/google/jsonapi)](https://goreportcard.com/report/github.com/google/jsonapi) [![GoDoc](https://godoc.org/github.com/google/jsonapi?status.svg)](http://godoc.org/github.com/google/jsonapi) +[![No Maintenance Intended](http://unmaintained.tech/badge.svg)](http://unmaintained.tech/) A serializer/deserializer for JSON payloads that comply to the [JSON API - jsonapi.org](http://jsonapi.org) spec in go. + + ## Installation ``` @@ -181,7 +184,7 @@ to-many from being serialized. **All `Marshal` and `Unmarshal` methods expect pointers to struct instance or slices of the same contained with the `interface{}`s** -Now you have your structs prepared to be seralized or materialized, What +Now you have your structs prepared to be serialized or materialized, What about the rest? ### Create Record Example @@ -343,6 +346,23 @@ func (post Post) JSONAPIRelationshipMeta(relation string) *Meta { } ``` +### Custom types + +Custom types are supported for primitive types, only, as attributes. Examples, + +```go +type CustomIntType int +type CustomFloatType float64 +type CustomStringType string +``` + +Types like following are not supported, but may be in the future: + +```go +type CustomMapType map[string]interface{} +type CustomSliceMapType []map[string]interface{} +``` + ### Errors This package also implements support for JSON API compatible `errors` payloads using the following types. diff --git a/vendor/github.com/google/jsonapi/doc.go b/vendor/github.com/google/jsonapi/doc.go index 29d7a14ba..2e91606d1 100644 --- a/vendor/github.com/google/jsonapi/doc.go +++ b/vendor/github.com/google/jsonapi/doc.go @@ -49,7 +49,7 @@ Value, attr: "attr,[,]" These fields' values should end up in the "attribute" hash for a record. The first argument must be, "attr', and the second should be the name for the key to display in -the the "attributes" hash for that record. +the "attributes" hash for that record. The following extra arguments are also supported: diff --git a/vendor/github.com/google/jsonapi/errors.go b/vendor/github.com/google/jsonapi/errors.go index ed7fa9f75..798fed0a2 100644 --- a/vendor/github.com/google/jsonapi/errors.go +++ b/vendor/github.com/google/jsonapi/errors.go @@ -12,10 +12,7 @@ import ( // http://jsonapi.org/format/#document-top-level // and here: http://jsonapi.org/format/#error-objects. func MarshalErrors(w io.Writer, errorObjects []*ErrorObject) error { - if err := json.NewEncoder(w).Encode(&ErrorsPayload{Errors: errorObjects}); err != nil { - return err - } - return nil + return json.NewEncoder(w).Encode(&ErrorsPayload{Errors: errorObjects}) } // ErrorsPayload is a serializer struct for representing a valid JSON API errors payload. diff --git a/vendor/github.com/google/jsonapi/request.go b/vendor/github.com/google/jsonapi/request.go index fe29706a5..a7bb0b1aa 100644 --- a/vendor/github.com/google/jsonapi/request.go +++ b/vendor/github.com/google/jsonapi/request.go @@ -13,7 +13,7 @@ import ( ) const ( - unsuportedStructTagMsg = "Unsupported jsonapi tag annotation, %s" + unsupportedStructTagMsg = "Unsupported jsonapi tag annotation, %s" ) var ( @@ -27,13 +27,35 @@ var ( // (numeric) but the Struct field was a non numeric type (i.e. not int, uint, // float, etc) ErrUnknownFieldNumberType = errors.New("The struct field was not of a known number type") - // ErrUnsupportedPtrType is returned when the Struct field was a pointer but - // the JSON value was of a different type - ErrUnsupportedPtrType = errors.New("Pointer type in struct is not supported") // ErrInvalidType is returned when the given type is incompatible with the expected type. ErrInvalidType = errors.New("Invalid type provided") // I wish we used punctuation. + ) +// ErrUnsupportedPtrType is returned when the Struct field was a pointer but +// the JSON value was of a different type +type ErrUnsupportedPtrType struct { + rf reflect.Value + t reflect.Type + structField reflect.StructField +} + +func (eupt ErrUnsupportedPtrType) Error() string { + typeName := eupt.t.Elem().Name() + kind := eupt.t.Elem().Kind() + if kind.String() != "" && kind.String() != typeName { + typeName = fmt.Sprintf("%s (%s)", typeName, kind.String()) + } + return fmt.Sprintf( + "jsonapi: Can't unmarshal %+v (%s) to struct field `%s`, which is a pointer to `%s`", + eupt.rf, eupt.rf.Type().Kind(), eupt.structField.Name, typeName, + ) +} + +func newErrUnsupportedPtrType(rf reflect.Value, t reflect.Type, structField reflect.StructField) error { + return ErrUnsupportedPtrType{rf, t, structField} +} + // UnmarshalPayload converts an io into a struct instance using jsonapi tags on // struct fields. This method supports single request payloads only, at the // moment. Bulk creates and updates are not supported yet. @@ -125,7 +147,7 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) }() modelValue := model.Elem() - modelType := model.Type().Elem() + modelType := modelValue.Type() var er error @@ -139,7 +161,6 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) fieldValue := modelValue.Field(i) args := strings.Split(tag, ",") - if len(args) < 1 { er = ErrBadJSONAPIStructTag break @@ -196,39 +217,8 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) // Convert the numeric float to one of the supported ID numeric types // (int[8,16,32,64] or uint[8,16,32,64]) - var idValue reflect.Value - switch kind { - case reflect.Int: - n := int(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Int8: - n := int8(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Int16: - n := int16(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Int32: - n := int32(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Int64: - n := int64(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Uint: - n := uint(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Uint8: - n := uint8(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Uint16: - n := uint16(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Uint32: - n := uint32(floatValue) - idValue = reflect.ValueOf(&n) - case reflect.Uint64: - n := uint64(floatValue) - idValue = reflect.ValueOf(&n) - default: + idValue, err := handleNumeric(floatValue, fieldType.Type, fieldValue) + if err != nil { // We had a JSON float (numeric), but our field was not one of the // allowed numeric types er = ErrBadJSONAPIID @@ -244,213 +234,26 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) fieldValue.Set(reflect.ValueOf(data.ClientID)) } else if annotation == annotationAttribute { attributes := data.Attributes + if attributes == nil || len(data.Attributes) == 0 { continue } - var iso8601 bool - - if len(args) > 2 { - for _, arg := range args[2:] { - if arg == annotationISO8601 { - iso8601 = true - } - } - } - - val := attributes[args[1]] + attribute := attributes[args[1]] // continue if the attribute was not included in the request - if val == nil { - continue - } - - v := reflect.ValueOf(val) - - // Handle field of type time.Time - if fieldValue.Type() == reflect.TypeOf(time.Time{}) { - if iso8601 { - var tm string - if v.Kind() == reflect.String { - tm = v.Interface().(string) - } else { - er = ErrInvalidISO8601 - break - } - - t, err := time.Parse(iso8601TimeFormat, tm) - if err != nil { - er = ErrInvalidISO8601 - break - } - - fieldValue.Set(reflect.ValueOf(t)) - - continue - } - - var at int64 - - if v.Kind() == reflect.Float64 { - at = int64(v.Interface().(float64)) - } else if v.Kind() == reflect.Int { - at = v.Int() - } else { - return ErrInvalidTime - } - - t := time.Unix(at, 0) - - fieldValue.Set(reflect.ValueOf(t)) - - continue - } - - if fieldValue.Type() == reflect.TypeOf([]string{}) { - values := make([]string, v.Len()) - for i := 0; i < v.Len(); i++ { - values[i] = v.Index(i).Interface().(string) - } - - fieldValue.Set(reflect.ValueOf(values)) - + if attribute == nil { continue } - if fieldValue.Type() == reflect.TypeOf(new(time.Time)) { - if iso8601 { - var tm string - if v.Kind() == reflect.String { - tm = v.Interface().(string) - } else { - er = ErrInvalidISO8601 - break - } - - v, err := time.Parse(iso8601TimeFormat, tm) - if err != nil { - er = ErrInvalidISO8601 - break - } - - t := &v - - fieldValue.Set(reflect.ValueOf(t)) - - continue - } - - var at int64 - - if v.Kind() == reflect.Float64 { - at = int64(v.Interface().(float64)) - } else if v.Kind() == reflect.Int { - at = v.Int() - } else { - return ErrInvalidTime - } - - v := time.Unix(at, 0) - t := &v - - fieldValue.Set(reflect.ValueOf(t)) - - continue - } - - // JSON value was a float (numeric) - if v.Kind() == reflect.Float64 { - floatValue := v.Interface().(float64) - - // The field may or may not be a pointer to a numeric; the kind var - // will not contain a pointer type - var kind reflect.Kind - if fieldValue.Kind() == reflect.Ptr { - kind = fieldType.Type.Elem().Kind() - } else { - kind = fieldType.Type.Kind() - } - - var numericValue reflect.Value - - switch kind { - case reflect.Int: - n := int(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Int8: - n := int8(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Int16: - n := int16(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Int32: - n := int32(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Int64: - n := int64(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Uint: - n := uint(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Uint8: - n := uint8(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Uint16: - n := uint16(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Uint32: - n := uint32(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Uint64: - n := uint64(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Float32: - n := float32(floatValue) - numericValue = reflect.ValueOf(&n) - case reflect.Float64: - n := floatValue - numericValue = reflect.ValueOf(&n) - default: - return ErrUnknownFieldNumberType - } - - assign(fieldValue, numericValue) - continue - } - - // Field was a Pointer type - if fieldValue.Kind() == reflect.Ptr { - var concreteVal reflect.Value - - switch cVal := val.(type) { - case string: - concreteVal = reflect.ValueOf(&cVal) - case bool: - concreteVal = reflect.ValueOf(&cVal) - case complex64: - concreteVal = reflect.ValueOf(&cVal) - case complex128: - concreteVal = reflect.ValueOf(&cVal) - case uintptr: - concreteVal = reflect.ValueOf(&cVal) - default: - return ErrUnsupportedPtrType - } - - if fieldValue.Type() != concreteVal.Type() { - return ErrUnsupportedPtrType - } - - fieldValue.Set(concreteVal) - continue - } - - // As a final catch-all, ensure types line up to avoid a runtime panic. - if fieldValue.Kind() != v.Kind() { - return ErrInvalidType + structField := fieldType + value, err := unmarshalAttribute(attribute, args, structField, fieldValue) + if err != nil { + er = err + break } - fieldValue.Set(reflect.ValueOf(val)) + assign(fieldValue, value) } else if annotation == annotationRelation { isSlice := fieldValue.Type().Kind() == reflect.Slice @@ -522,7 +325,7 @@ func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) } } else { - er = fmt.Errorf(unsuportedStructTagMsg, annotation) + er = fmt.Errorf(unsupportedStructTagMsg, annotation) } } @@ -542,9 +345,293 @@ func fullNode(n *Node, included *map[string]*Node) *Node { // assign will take the value specified and assign it to the field; if // field is expecting a ptr assign will assign a ptr. func assign(field, value reflect.Value) { + value = reflect.Indirect(value) + if field.Kind() == reflect.Ptr { + // initialize pointer so it's value + // can be set by assignValue + field.Set(reflect.New(field.Type().Elem())) + field = field.Elem() + + } + + assignValue(field, value) +} + +// assign assigns the specified value to the field, +// expecting both values not to be pointer types. +func assignValue(field, value reflect.Value) { + switch field.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, + reflect.Int32, reflect.Int64: + field.SetInt(value.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, + reflect.Uint32, reflect.Uint64, reflect.Uintptr: + field.SetUint(value.Uint()) + case reflect.Float32, reflect.Float64: + field.SetFloat(value.Float()) + case reflect.String: + field.SetString(value.String()) + case reflect.Bool: + field.SetBool(value.Bool()) + default: field.Set(value) + } +} + +func unmarshalAttribute( + attribute interface{}, + args []string, + structField reflect.StructField, + fieldValue reflect.Value) (value reflect.Value, err error) { + value = reflect.ValueOf(attribute) + fieldType := structField.Type + + // Handle field of type []string + if fieldValue.Type() == reflect.TypeOf([]string{}) { + value, err = handleStringSlice(attribute) + return + } + + // Handle field of type time.Time + if fieldValue.Type() == reflect.TypeOf(time.Time{}) || + fieldValue.Type() == reflect.TypeOf(new(time.Time)) { + value, err = handleTime(attribute, args, fieldValue) + return + } + + // Handle field of type struct + if fieldValue.Type().Kind() == reflect.Struct { + value, err = handleStruct(attribute, fieldValue) + return + } + + // Handle field containing slice of structs + if fieldValue.Type().Kind() == reflect.Slice && + reflect.TypeOf(fieldValue.Interface()).Elem().Kind() == reflect.Struct { + value, err = handleStructSlice(attribute, fieldValue) + return + } + + // JSON value was a float (numeric) + if value.Kind() == reflect.Float64 { + value, err = handleNumeric(attribute, fieldType, fieldValue) + return + } + + // Field was a Pointer type + if fieldValue.Kind() == reflect.Ptr { + value, err = handlePointer(attribute, args, fieldType, fieldValue, structField) + return + } + + // As a final catch-all, ensure types line up to avoid a runtime panic. + if fieldValue.Kind() != value.Kind() { + err = ErrInvalidType + return + } + + return +} + +func handleStringSlice(attribute interface{}) (reflect.Value, error) { + v := reflect.ValueOf(attribute) + values := make([]string, v.Len()) + for i := 0; i < v.Len(); i++ { + values[i] = v.Index(i).Interface().(string) + } + + return reflect.ValueOf(values), nil +} + +func handleTime(attribute interface{}, args []string, fieldValue reflect.Value) (reflect.Value, error) { + var isIso8601 bool + v := reflect.ValueOf(attribute) + + if len(args) > 2 { + for _, arg := range args[2:] { + if arg == annotationISO8601 { + isIso8601 = true + } + } + } + + if isIso8601 { + var tm string + if v.Kind() == reflect.String { + tm = v.Interface().(string) + } else { + return reflect.ValueOf(time.Now()), ErrInvalidISO8601 + } + + t, err := time.Parse(iso8601TimeFormat, tm) + if err != nil { + return reflect.ValueOf(time.Now()), ErrInvalidISO8601 + } + + if fieldValue.Kind() == reflect.Ptr { + return reflect.ValueOf(&t), nil + } + + return reflect.ValueOf(t), nil + } + + var at int64 + + if v.Kind() == reflect.Float64 { + at = int64(v.Interface().(float64)) + } else if v.Kind() == reflect.Int { + at = v.Int() } else { - field.Set(reflect.Indirect(value)) + return reflect.ValueOf(time.Now()), ErrInvalidTime } + + t := time.Unix(at, 0) + + return reflect.ValueOf(t), nil +} + +func handleNumeric( + attribute interface{}, + fieldType reflect.Type, + fieldValue reflect.Value) (reflect.Value, error) { + v := reflect.ValueOf(attribute) + floatValue := v.Interface().(float64) + + var kind reflect.Kind + if fieldValue.Kind() == reflect.Ptr { + kind = fieldType.Elem().Kind() + } else { + kind = fieldType.Kind() + } + + var numericValue reflect.Value + + switch kind { + case reflect.Int: + n := int(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Int8: + n := int8(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Int16: + n := int16(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Int32: + n := int32(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Int64: + n := int64(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Uint: + n := uint(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Uint8: + n := uint8(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Uint16: + n := uint16(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Uint32: + n := uint32(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Uint64: + n := uint64(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Float32: + n := float32(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Float64: + n := floatValue + numericValue = reflect.ValueOf(&n) + default: + return reflect.Value{}, ErrUnknownFieldNumberType + } + + return numericValue, nil +} + +func handlePointer( + attribute interface{}, + args []string, + fieldType reflect.Type, + fieldValue reflect.Value, + structField reflect.StructField) (reflect.Value, error) { + t := fieldValue.Type() + var concreteVal reflect.Value + + switch cVal := attribute.(type) { + case string: + concreteVal = reflect.ValueOf(&cVal) + case bool: + concreteVal = reflect.ValueOf(&cVal) + case complex64, complex128, uintptr: + concreteVal = reflect.ValueOf(&cVal) + case map[string]interface{}: + var err error + concreteVal, err = handleStruct(attribute, fieldValue) + if err != nil { + return reflect.Value{}, newErrUnsupportedPtrType( + reflect.ValueOf(attribute), fieldType, structField) + } + return concreteVal, err + default: + return reflect.Value{}, newErrUnsupportedPtrType( + reflect.ValueOf(attribute), fieldType, structField) + } + + if t != concreteVal.Type() { + return reflect.Value{}, newErrUnsupportedPtrType( + reflect.ValueOf(attribute), fieldType, structField) + } + + return concreteVal, nil +} + +func handleStruct( + attribute interface{}, + fieldValue reflect.Value) (reflect.Value, error) { + + data, err := json.Marshal(attribute) + if err != nil { + return reflect.Value{}, err + } + + node := new(Node) + if err := json.Unmarshal(data, &node.Attributes); err != nil { + return reflect.Value{}, err + } + + var model reflect.Value + if fieldValue.Kind() == reflect.Ptr { + model = reflect.New(fieldValue.Type().Elem()) + } else { + model = reflect.New(fieldValue.Type()) + } + + if err := unmarshalNode(node, model, nil); err != nil { + return reflect.Value{}, err + } + + return model, nil +} + +func handleStructSlice( + attribute interface{}, + fieldValue reflect.Value) (reflect.Value, error) { + models := reflect.New(fieldValue.Type()).Elem() + dataMap := reflect.ValueOf(attribute).Interface().([]interface{}) + for _, data := range dataMap { + model := reflect.New(fieldValue.Type().Elem()).Elem() + + value, err := handleStruct(data, model) + + if err != nil { + continue + } + + models = reflect.Append(models, reflect.Indirect(value)) + } + + return models, nil } diff --git a/vendor/github.com/google/jsonapi/response.go b/vendor/github.com/google/jsonapi/response.go index f6a1b866b..3f8ab73d8 100644 --- a/vendor/github.com/google/jsonapi/response.go +++ b/vendor/github.com/google/jsonapi/response.go @@ -68,10 +68,7 @@ func MarshalPayload(w io.Writer, models interface{}) error { return err } - if err := json.NewEncoder(w).Encode(payload); err != nil { - return err - } - return nil + return json.NewEncoder(w).Encode(payload) } // Marshal does the same as MarshalPayload except it just returns the payload @@ -128,10 +125,7 @@ func MarshalPayloadWithoutIncluded(w io.Writer, model interface{}) error { } payload.clearIncluded() - if err := json.NewEncoder(w).Encode(payload); err != nil { - return err - } - return nil + return json.NewEncoder(w).Encode(payload) } // marshalOne does the same as MarshalOnePayload except it just returns the @@ -195,11 +189,7 @@ func MarshalOnePayloadEmbedded(w io.Writer, model interface{}) error { payload := &OnePayload{Data: rootNode} - if err := json.NewEncoder(w).Encode(payload); err != nil { - return err - } - - return nil + return json.NewEncoder(w).Encode(payload) } func visitModelNode(model interface{}, included *map[string]*Node, @@ -207,9 +197,13 @@ func visitModelNode(model interface{}, included *map[string]*Node, node := new(Node) var er error + value := reflect.ValueOf(model) + if value.IsNil() { + return nil, nil + } - modelValue := reflect.ValueOf(model).Elem() - modelType := reflect.ValueOf(model).Type().Elem() + modelValue := value.Elem() + modelType := value.Type().Elem() for i := 0; i < modelValue.NumField(); i++ { structField := modelValue.Type().Field(i) @@ -276,6 +270,9 @@ func visitModelNode(model interface{}, included *map[string]*Node, // We had a JSON float (numeric), but our field was not one of the // allowed numeric types er = ErrBadJSONAPIID + } + + if er != nil { break } diff --git a/vendor/github.com/google/jsonapi/runtime.go b/vendor/github.com/google/jsonapi/runtime.go index 7dc658155..db2d9f2f0 100644 --- a/vendor/github.com/google/jsonapi/runtime.go +++ b/vendor/github.com/google/jsonapi/runtime.go @@ -8,35 +8,58 @@ import ( "time" ) +// Event represents a lifecycle event in the marshaling or unmarshalling +// process. type Event int const ( + // UnmarshalStart is the Event that is sent when deserialization of a payload + // begins. UnmarshalStart Event = iota + + // UnmarshalStop is the Event that is sent when deserialization of a payload + // ends. UnmarshalStop + + // MarshalStart is the Event that is sent sent when serialization of a payload + // begins. MarshalStart + + // MarshalStop is the Event that is sent sent when serialization of a payload + // ends. MarshalStop ) +// Runtime has the same methods as jsonapi package for serialization and +// deserialization but also has a ctx, a map[string]interface{} for storing +// state, designed for instrumenting serialization timings. type Runtime struct { ctx map[string]interface{} } +// Events is the func type that provides the callback for handling event timings. type Events func(*Runtime, Event, string, time.Duration) +// Instrumentation is a a global Events variable. This is the handler for all +// timing events. var Instrumentation Events +// NewRuntime creates a Runtime for use in an application. func NewRuntime() *Runtime { return &Runtime{make(map[string]interface{})} } +// WithValue adds custom state variables to the runtime context. func (r *Runtime) WithValue(key string, value interface{}) *Runtime { r.ctx[key] = value return r } +// Value returns a state variable in the runtime context. func (r *Runtime) Value(key string) interface{} { return r.ctx[key] } +// Instrument is deprecated. func (r *Runtime) Instrument(key string) *Runtime { return r.WithValue("instrument", key) } @@ -45,12 +68,14 @@ func (r *Runtime) shouldInstrument() bool { return Instrumentation != nil } +// UnmarshalPayload has docs in request.go for UnmarshalPayload. func (r *Runtime) UnmarshalPayload(reader io.Reader, model interface{}) error { return r.instrumentCall(UnmarshalStart, UnmarshalStop, func() error { return UnmarshalPayload(reader, model) }) } +// UnmarshalManyPayload has docs in request.go for UnmarshalManyPayload. func (r *Runtime) UnmarshalManyPayload(reader io.Reader, kind reflect.Type) (elems []interface{}, err error) { r.instrumentCall(UnmarshalStart, UnmarshalStop, func() error { elems, err = UnmarshalManyPayload(reader, kind) @@ -60,6 +85,7 @@ func (r *Runtime) UnmarshalManyPayload(reader io.Reader, kind reflect.Type) (ele return } +// MarshalPayload has docs in response.go for MarshalPayload. func (r *Runtime) MarshalPayload(w io.Writer, model interface{}) error { return r.instrumentCall(MarshalStart, MarshalStop, func() error { return MarshalPayload(w, model) diff --git a/vendor/github.com/stretchr/testify/require/doc.go b/vendor/github.com/stretchr/testify/require/doc.go new file mode 100644 index 000000000..169de3922 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/doc.go @@ -0,0 +1,28 @@ +// Package require implements the same assertions as the `assert` package but +// stops test execution when a test fails. +// +// Example Usage +// +// The following is a complete example using require in a standard test function: +// import ( +// "testing" +// "github.com/stretchr/testify/require" +// ) +// +// func TestSomething(t *testing.T) { +// +// var a string = "Hello" +// var b string = "Hello" +// +// require.Equal(t, a, b, "The two words should be the same.") +// +// } +// +// Assertions +// +// The `require` package have same global functions as in the `assert` package, +// but instead of returning a boolean result they call `t.FailNow()`. +// +// Every assertion function also takes an optional string message as the final argument, +// allowing custom error messages to be appended to the message the assertion method outputs. +package require diff --git a/vendor/github.com/stretchr/testify/require/forward_requirements.go b/vendor/github.com/stretchr/testify/require/forward_requirements.go new file mode 100644 index 000000000..1dcb2338c --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/forward_requirements.go @@ -0,0 +1,16 @@ +package require + +// Assertions provides assertion methods around the +// TestingT interface. +type Assertions struct { + t TestingT +} + +// New makes a new Assertions object for the specified TestingT. +func New(t TestingT) *Assertions { + return &Assertions{ + t: t, + } +} + +//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require_forward.go.tmpl -include-format-funcs" diff --git a/vendor/github.com/stretchr/testify/require/require.go b/vendor/github.com/stretchr/testify/require/require.go new file mode 100644 index 000000000..ec4624b28 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require.go @@ -0,0 +1,1631 @@ +/* +* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen +* THIS FILE MUST NOT BE EDITED BY HAND + */ + +package require + +import ( + assert "github.com/stretchr/testify/assert" + http "net/http" + url "net/url" + time "time" +) + +// Condition uses a Comparison to assert a complex condition. +func Condition(t TestingT, comp assert.Comparison, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Condition(t, comp, msgAndArgs...) { + return + } + t.FailNow() +} + +// Conditionf uses a Comparison to assert a complex condition. +func Conditionf(t TestingT, comp assert.Comparison, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Conditionf(t, comp, msg, args...) { + return + } + t.FailNow() +} + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// assert.Contains(t, "Hello World", "World") +// assert.Contains(t, ["Hello", "World"], "World") +// assert.Contains(t, {"Hello": "World"}, "Hello") +func Contains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Contains(t, s, contains, msgAndArgs...) { + return + } + t.FailNow() +} + +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted") +// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") +// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") +func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Containsf(t, s, contains, msg, args...) { + return + } + t.FailNow() +} + +// DirExists checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func DirExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.DirExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// DirExistsf checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func DirExistsf(t TestingT, path string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.DirExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2]) +func ElementsMatch(t TestingT, listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ElementsMatch(t, listA, listB, msgAndArgs...) { + return + } + t.FailNow() +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ElementsMatchf(t, listA, listB, msg, args...) { + return + } + t.FailNow() +} + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Empty(t, obj) +func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Empty(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Emptyf(t, obj, "error message %s", "formatted") +func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Emptyf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// Equal asserts that two objects are equal. +// +// assert.Equal(t, 123, 123) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func Equal(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Equal(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// assert.EqualError(t, err, expectedErrorString) +func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualError(t, theError, errString, msgAndArgs...) { + return + } + t.FailNow() +} + +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") +func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualErrorf(t, theError, errString, msg, args...) { + return + } + t.FailNow() +} + +// EqualValues asserts that two objects are equal or convertable to the same types +// and equal. +// +// assert.EqualValues(t, uint32(123), int32(123)) +func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualValues(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// EqualValuesf asserts that two objects are equal or convertable to the same types +// and equal. +// +// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted") +func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualValuesf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Equalf asserts that two objects are equal. +// +// assert.Equalf(t, 123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Equalf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Error(t, err) { +// assert.Equal(t, expectedError, err) +// } +func Error(t TestingT, err error, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Error(t, err, msgAndArgs...) { + return + } + t.FailNow() +} + +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Errorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func Errorf(t TestingT, err error, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Errorf(t, err, msg, args...) { + return + } + t.FailNow() +} + +// Eventually asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// assert.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond) +func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Eventually(t, condition, waitFor, tick, msgAndArgs...) { + return + } + t.FailNow() +} + +// Eventuallyf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Eventuallyf(t, condition, waitFor, tick, msg, args...) { + return + } + t.FailNow() +} + +// Exactly asserts that two objects are equal in value and type. +// +// assert.Exactly(t, int32(123), int64(123)) +func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Exactly(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// Exactlyf asserts that two objects are equal in value and type. +// +// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted") +func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Exactlyf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Fail reports a failure through +func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Fail(t, failureMessage, msgAndArgs...) { + return + } + t.FailNow() +} + +// FailNow fails test +func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FailNow(t, failureMessage, msgAndArgs...) { + return + } + t.FailNow() +} + +// FailNowf fails test +func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FailNowf(t, failureMessage, msg, args...) { + return + } + t.FailNow() +} + +// Failf reports a failure through +func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Failf(t, failureMessage, msg, args...) { + return + } + t.FailNow() +} + +// False asserts that the specified value is false. +// +// assert.False(t, myBool) +func False(t TestingT, value bool, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.False(t, value, msgAndArgs...) { + return + } + t.FailNow() +} + +// Falsef asserts that the specified value is false. +// +// assert.Falsef(t, myBool, "error message %s", "formatted") +func Falsef(t TestingT, value bool, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Falsef(t, value, msg, args...) { + return + } + t.FailNow() +} + +// FileExists checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func FileExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FileExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// FileExistsf checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func FileExistsf(t TestingT, path string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FileExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// Greater asserts that the first element is greater than the second +// +// assert.Greater(t, 2, 1) +// assert.Greater(t, float64(2), float64(1)) +// assert.Greater(t, "b", "a") +func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Greater(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// GreaterOrEqual asserts that the first element is greater than or equal to the second +// +// assert.GreaterOrEqual(t, 2, 1) +// assert.GreaterOrEqual(t, 2, 2) +// assert.GreaterOrEqual(t, "b", "a") +// assert.GreaterOrEqual(t, "b", "b") +func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.GreaterOrEqual(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// GreaterOrEqualf asserts that the first element is greater than or equal to the second +// +// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted") +// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted") +// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted") +// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted") +func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.GreaterOrEqualf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// Greaterf asserts that the first element is greater than the second +// +// assert.Greaterf(t, 2, 1, "error message %s", "formatted") +// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted") +// assert.Greaterf(t, "b", "a", "error message %s", "formatted") +func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Greaterf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyContains(t, handler, method, url, values, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyContainsf(t, handler, method, url, values, str, msg, args...) { + return + } + t.FailNow() +} + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyNotContains(t, handler, method, url, values, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyNotContainsf(t, handler, method, url, values, str, msg, args...) { + return + } + t.FailNow() +} + +// HTTPError asserts that a specified handler returns an error status code. +// +// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPError(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPError(t, handler, method, url, values, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPErrorf(t, handler, method, url, values, msg, args...) { + return + } + t.FailNow() +} + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPRedirect(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPRedirect(t, handler, method, url, values, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPRedirectf(t, handler, method, url, values, msg, args...) { + return + } + t.FailNow() +} + +// HTTPStatusCode asserts that a specified handler returns a specified status code. +// +// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501) +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPStatusCode(t, handler, method, url, values, statuscode, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPStatusCodef asserts that a specified handler returns a specified status code. +// +// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPStatusCodef(t, handler, method, url, values, statuscode, msg, args...) { + return + } + t.FailNow() +} + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPSuccess(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPSuccess(t, handler, method, url, values, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPSuccessf(t, handler, method, url, values, msg, args...) { + return + } + t.FailNow() +} + +// Implements asserts that an object is implemented by the specified interface. +// +// assert.Implements(t, (*MyInterface)(nil), new(MyObject)) +func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Implements(t, interfaceObject, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// Implementsf asserts that an object is implemented by the specified interface. +// +// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Implementsf(t, interfaceObject, object, msg, args...) { + return + } + t.FailNow() +} + +// InDelta asserts that the two numerals are within delta of each other. +// +// assert.InDelta(t, math.Pi, 22/7.0, 0.01) +func InDelta(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDelta(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValues(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaMapValues(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaMapValuesf(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func InDeltaSlice(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaSlice(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaSlicef(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// InDeltaf asserts that the two numerals are within delta of each other. +// +// assert.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted") +func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaf(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +func InEpsilon(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilon(t, expected, actual, epsilon, msgAndArgs...) { + return + } + t.FailNow() +} + +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlice(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilonSlice(t, expected, actual, epsilon, msgAndArgs...) { + return + } + t.FailNow() +} + +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilonSlicef(t, expected, actual, epsilon, msg, args...) { + return + } + t.FailNow() +} + +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilonf(t, expected, actual, epsilon, msg, args...) { + return + } + t.FailNow() +} + +// IsType asserts that the specified objects are of the same type. +func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsType(t, expectedType, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// IsTypef asserts that the specified objects are of the same type. +func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsTypef(t, expectedType, object, msg, args...) { + return + } + t.FailNow() +} + +// JSONEq asserts that two JSON strings are equivalent. +// +// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.JSONEq(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// JSONEqf asserts that two JSON strings are equivalent. +// +// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.JSONEqf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// assert.Len(t, mySlice, 3) +func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Len(t, object, length, msgAndArgs...) { + return + } + t.FailNow() +} + +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// assert.Lenf(t, mySlice, 3, "error message %s", "formatted") +func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Lenf(t, object, length, msg, args...) { + return + } + t.FailNow() +} + +// Less asserts that the first element is less than the second +// +// assert.Less(t, 1, 2) +// assert.Less(t, float64(1), float64(2)) +// assert.Less(t, "a", "b") +func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Less(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// LessOrEqual asserts that the first element is less than or equal to the second +// +// assert.LessOrEqual(t, 1, 2) +// assert.LessOrEqual(t, 2, 2) +// assert.LessOrEqual(t, "a", "b") +// assert.LessOrEqual(t, "b", "b") +func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.LessOrEqual(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// LessOrEqualf asserts that the first element is less than or equal to the second +// +// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted") +// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted") +// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted") +// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted") +func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.LessOrEqualf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// Lessf asserts that the first element is less than the second +// +// assert.Lessf(t, 1, 2, "error message %s", "formatted") +// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted") +// assert.Lessf(t, "a", "b", "error message %s", "formatted") +func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Lessf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// Never asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// assert.Never(t, func() bool { return false; }, time.Second, 10*time.Millisecond) +func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Never(t, condition, waitFor, tick, msgAndArgs...) { + return + } + t.FailNow() +} + +// Neverf asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// assert.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Neverf(t, condition, waitFor, tick, msg, args...) { + return + } + t.FailNow() +} + +// Nil asserts that the specified object is nil. +// +// assert.Nil(t, err) +func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Nil(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// Nilf asserts that the specified object is nil. +// +// assert.Nilf(t, err, "error message %s", "formatted") +func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Nilf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// NoDirExists checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func NoDirExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoDirExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// NoDirExistsf checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func NoDirExistsf(t TestingT, path string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoDirExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if assert.NoError(t, err) { +// assert.Equal(t, expectedObj, actualObj) +// } +func NoError(t TestingT, err error, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoError(t, err, msgAndArgs...) { + return + } + t.FailNow() +} + +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if assert.NoErrorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } +func NoErrorf(t TestingT, err error, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoErrorf(t, err, msg, args...) { + return + } + t.FailNow() +} + +// NoFileExists checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func NoFileExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoFileExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// NoFileExistsf checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func NoFileExistsf(t TestingT, path string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoFileExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// assert.NotContains(t, "Hello World", "Earth") +// assert.NotContains(t, ["Hello", "World"], "Earth") +// assert.NotContains(t, {"Hello": "World"}, "Earth") +func NotContains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotContains(t, s, contains, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") +func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotContainsf(t, s, contains, msg, args...) { + return + } + t.FailNow() +} + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmpty(t, obj) { +// assert.Equal(t, "two", obj[1]) +// } +func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEmpty(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmptyf(t, obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEmptyf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// NotEqual asserts that the specified values are NOT equal. +// +// assert.NotEqual(t, obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqual(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqual(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotEqualValues asserts that two objects are not equal even when converted to the same type +// +// assert.NotEqualValues(t, obj1, obj2) +func NotEqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqualValues(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotEqualValuesf asserts that two objects are not equal even when converted to the same type +// +// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted") +func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqualValuesf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// NotEqualf asserts that the specified values are NOT equal. +// +// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqualf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// NotNil asserts that the specified object is not nil. +// +// assert.NotNil(t, err) +func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotNil(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotNilf asserts that the specified object is not nil. +// +// assert.NotNilf(t, err, "error message %s", "formatted") +func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotNilf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// assert.NotPanics(t, func(){ RemainCalm() }) +func NotPanics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotPanics(t, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") +func NotPanicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotPanicsf(t, f, msg, args...) { + return + } + t.FailNow() +} + +// NotRegexp asserts that a specified regexp does not match a string. +// +// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") +// assert.NotRegexp(t, "^start", "it's not starting") +func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotRegexp(t, rx, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotRegexpf asserts that a specified regexp does not match a string. +// +// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") +// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") +func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotRegexpf(t, rx, str, msg, args...) { + return + } + t.FailNow() +} + +// NotSame asserts that two pointers do not reference the same object. +// +// assert.NotSame(t, ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func NotSame(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSame(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotSamef asserts that two pointers do not reference the same object. +// +// assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSamef(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// NotSubset asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// assert.NotSubset(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") +func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSubset(t, list, subset, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotSubsetf asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") +func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSubsetf(t, list, subset, msg, args...) { + return + } + t.FailNow() +} + +// NotZero asserts that i is not the zero value for its type. +func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotZero(t, i, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotZerof asserts that i is not the zero value for its type. +func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotZerof(t, i, msg, args...) { + return + } + t.FailNow() +} + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panics(t, func(){ GoCrazy() }) +func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Panics(t, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// PanicsWithError asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// assert.PanicsWithError(t, "crazy error", func(){ GoCrazy() }) +func PanicsWithError(t TestingT, errString string, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithError(t, errString, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func PanicsWithErrorf(t TestingT, errString string, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithErrorf(t, errString, f, msg, args...) { + return + } + t.FailNow() +} + +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) +func PanicsWithValue(t TestingT, expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithValue(t, expected, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func PanicsWithValuef(t TestingT, expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithValuef(t, expected, f, msg, args...) { + return + } + t.FailNow() +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") +func Panicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Panicsf(t, f, msg, args...) { + return + } + t.FailNow() +} + +// Regexp asserts that a specified regexp matches a string. +// +// assert.Regexp(t, regexp.MustCompile("start"), "it's starting") +// assert.Regexp(t, "start...$", "it's not starting") +func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Regexp(t, rx, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// Regexpf asserts that a specified regexp matches a string. +// +// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") +// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") +func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Regexpf(t, rx, str, msg, args...) { + return + } + t.FailNow() +} + +// Same asserts that two pointers reference the same object. +// +// assert.Same(t, ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func Same(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Same(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// Samef asserts that two pointers reference the same object. +// +// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func Samef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Samef(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Subset asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// assert.Subset(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") +func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Subset(t, list, subset, msgAndArgs...) { + return + } + t.FailNow() +} + +// Subsetf asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// assert.Subsetf(t, [1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") +func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Subsetf(t, list, subset, msg, args...) { + return + } + t.FailNow() +} + +// True asserts that the specified value is true. +// +// assert.True(t, myBool) +func True(t TestingT, value bool, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.True(t, value, msgAndArgs...) { + return + } + t.FailNow() +} + +// Truef asserts that the specified value is true. +// +// assert.Truef(t, myBool, "error message %s", "formatted") +func Truef(t TestingT, value bool, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Truef(t, value, msg, args...) { + return + } + t.FailNow() +} + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) +func WithinDuration(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.WithinDuration(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.WithinDurationf(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// YAMLEq asserts that two YAML strings are equivalent. +func YAMLEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.YAMLEq(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// YAMLEqf asserts that two YAML strings are equivalent. +func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.YAMLEqf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Zero asserts that i is the zero value for its type. +func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Zero(t, i, msgAndArgs...) { + return + } + t.FailNow() +} + +// Zerof asserts that i is the zero value for its type. +func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Zerof(t, i, msg, args...) { + return + } + t.FailNow() +} diff --git a/vendor/github.com/stretchr/testify/require/require.go.tmpl b/vendor/github.com/stretchr/testify/require/require.go.tmpl new file mode 100644 index 000000000..55e42ddeb --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require.go.tmpl @@ -0,0 +1,6 @@ +{{.Comment}} +func {{.DocInfo.Name}}(t TestingT, {{.Params}}) { + if h, ok := t.(tHelper); ok { h.Helper() } + if assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { return } + t.FailNow() +} diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go b/vendor/github.com/stretchr/testify/require/require_forward.go new file mode 100644 index 000000000..103d7dcb6 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require_forward.go @@ -0,0 +1,1277 @@ +/* +* CODE GENERATED AUTOMATICALLY WITH github.com/stretchr/testify/_codegen +* THIS FILE MUST NOT BE EDITED BY HAND + */ + +package require + +import ( + assert "github.com/stretchr/testify/assert" + http "net/http" + url "net/url" + time "time" +) + +// Condition uses a Comparison to assert a complex condition. +func (a *Assertions) Condition(comp assert.Comparison, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Condition(a.t, comp, msgAndArgs...) +} + +// Conditionf uses a Comparison to assert a complex condition. +func (a *Assertions) Conditionf(comp assert.Comparison, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Conditionf(a.t, comp, msg, args...) +} + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Contains("Hello World", "World") +// a.Contains(["Hello", "World"], "World") +// a.Contains({"Hello": "World"}, "Hello") +func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Contains(a.t, s, contains, msgAndArgs...) +} + +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Containsf("Hello World", "World", "error message %s", "formatted") +// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted") +// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted") +func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Containsf(a.t, s, contains, msg, args...) +} + +// DirExists checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + DirExists(a.t, path, msgAndArgs...) +} + +// DirExistsf checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + DirExistsf(a.t, path, msg, args...) +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatch([1, 3, 2, 3], [1, 3, 3, 2]) +func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatchf([1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ElementsMatchf(a.t, listA, listB, msg, args...) +} + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Empty(obj) +func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Empty(a.t, object, msgAndArgs...) +} + +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Emptyf(obj, "error message %s", "formatted") +func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Emptyf(a.t, object, msg, args...) +} + +// Equal asserts that two objects are equal. +// +// a.Equal(123, 123) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Equal(a.t, expected, actual, msgAndArgs...) +} + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// a.EqualError(err, expectedErrorString) +func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualError(a.t, theError, errString, msgAndArgs...) +} + +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted") +func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualErrorf(a.t, theError, errString, msg, args...) +} + +// EqualValues asserts that two objects are equal or convertable to the same types +// and equal. +// +// a.EqualValues(uint32(123), int32(123)) +func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualValues(a.t, expected, actual, msgAndArgs...) +} + +// EqualValuesf asserts that two objects are equal or convertable to the same types +// and equal. +// +// a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted") +func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualValuesf(a.t, expected, actual, msg, args...) +} + +// Equalf asserts that two objects are equal. +// +// a.Equalf(123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Equalf(a.t, expected, actual, msg, args...) +} + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Error(err) { +// assert.Equal(t, expectedError, err) +// } +func (a *Assertions) Error(err error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Error(a.t, err, msgAndArgs...) +} + +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Errorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func (a *Assertions) Errorf(err error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Errorf(a.t, err, msg, args...) +} + +// Eventually asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// a.Eventually(func() bool { return true; }, time.Second, 10*time.Millisecond) +func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Eventually(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// Eventuallyf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// a.Eventuallyf(func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func (a *Assertions) Eventuallyf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Eventuallyf(a.t, condition, waitFor, tick, msg, args...) +} + +// Exactly asserts that two objects are equal in value and type. +// +// a.Exactly(int32(123), int64(123)) +func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Exactly(a.t, expected, actual, msgAndArgs...) +} + +// Exactlyf asserts that two objects are equal in value and type. +// +// a.Exactlyf(int32(123), int64(123), "error message %s", "formatted") +func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Exactlyf(a.t, expected, actual, msg, args...) +} + +// Fail reports a failure through +func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Fail(a.t, failureMessage, msgAndArgs...) +} + +// FailNow fails test +func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FailNow(a.t, failureMessage, msgAndArgs...) +} + +// FailNowf fails test +func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FailNowf(a.t, failureMessage, msg, args...) +} + +// Failf reports a failure through +func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Failf(a.t, failureMessage, msg, args...) +} + +// False asserts that the specified value is false. +// +// a.False(myBool) +func (a *Assertions) False(value bool, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + False(a.t, value, msgAndArgs...) +} + +// Falsef asserts that the specified value is false. +// +// a.Falsef(myBool, "error message %s", "formatted") +func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Falsef(a.t, value, msg, args...) +} + +// FileExists checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FileExists(a.t, path, msgAndArgs...) +} + +// FileExistsf checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FileExistsf(a.t, path, msg, args...) +} + +// Greater asserts that the first element is greater than the second +// +// a.Greater(2, 1) +// a.Greater(float64(2), float64(1)) +// a.Greater("b", "a") +func (a *Assertions) Greater(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Greater(a.t, e1, e2, msgAndArgs...) +} + +// GreaterOrEqual asserts that the first element is greater than or equal to the second +// +// a.GreaterOrEqual(2, 1) +// a.GreaterOrEqual(2, 2) +// a.GreaterOrEqual("b", "a") +// a.GreaterOrEqual("b", "b") +func (a *Assertions) GreaterOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + GreaterOrEqual(a.t, e1, e2, msgAndArgs...) +} + +// GreaterOrEqualf asserts that the first element is greater than or equal to the second +// +// a.GreaterOrEqualf(2, 1, "error message %s", "formatted") +// a.GreaterOrEqualf(2, 2, "error message %s", "formatted") +// a.GreaterOrEqualf("b", "a", "error message %s", "formatted") +// a.GreaterOrEqualf("b", "b", "error message %s", "formatted") +func (a *Assertions) GreaterOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + GreaterOrEqualf(a.t, e1, e2, msg, args...) +} + +// Greaterf asserts that the first element is greater than the second +// +// a.Greaterf(2, 1, "error message %s", "formatted") +// a.Greaterf(float64(2), float64(1), "error message %s", "formatted") +// a.Greaterf("b", "a", "error message %s", "formatted") +func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Greaterf(a.t, e1, e2, msg, args...) +} + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...) +} + +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...) +} + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...) +} + +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...) +} + +// HTTPError asserts that a specified handler returns an error status code. +// +// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPError(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPErrorf(a.t, handler, method, url, values, msg, args...) +} + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPRedirect(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPRedirectf(a.t, handler, method, url, values, msg, args...) +} + +// HTTPStatusCode asserts that a specified handler returns a specified status code. +// +// a.HTTPStatusCode(myHandler, "GET", "/notImplemented", nil, 501) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPStatusCode(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPStatusCode(a.t, handler, method, url, values, statuscode, msgAndArgs...) +} + +// HTTPStatusCodef asserts that a specified handler returns a specified status code. +// +// a.HTTPStatusCodef(myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPStatusCodef(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPStatusCodef(a.t, handler, method, url, values, statuscode, msg, args...) +} + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPSuccess(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPSuccessf(a.t, handler, method, url, values, msg, args...) +} + +// Implements asserts that an object is implemented by the specified interface. +// +// a.Implements((*MyInterface)(nil), new(MyObject)) +func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Implements(a.t, interfaceObject, object, msgAndArgs...) +} + +// Implementsf asserts that an object is implemented by the specified interface. +// +// a.Implementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Implementsf(a.t, interfaceObject, object, msg, args...) +} + +// InDelta asserts that the two numerals are within delta of each other. +// +// a.InDelta(math.Pi, 22/7.0, 0.01) +func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDelta(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaMapValues(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaMapValuesf(a.t, expected, actual, delta, msg, args...) +} + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaSlicef(a.t, expected, actual, delta, msg, args...) +} + +// InDeltaf asserts that the two numerals are within delta of each other. +// +// a.InDeltaf(math.Pi, 22/7.0, 0.01, "error message %s", "formatted") +func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaf(a.t, expected, actual, delta, msg, args...) +} + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) +} + +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...) +} + +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...) +} + +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonf(a.t, expected, actual, epsilon, msg, args...) +} + +// IsType asserts that the specified objects are of the same type. +func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsType(a.t, expectedType, object, msgAndArgs...) +} + +// IsTypef asserts that the specified objects are of the same type. +func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsTypef(a.t, expectedType, object, msg, args...) +} + +// JSONEq asserts that two JSON strings are equivalent. +// +// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + JSONEq(a.t, expected, actual, msgAndArgs...) +} + +// JSONEqf asserts that two JSON strings are equivalent. +// +// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + JSONEqf(a.t, expected, actual, msg, args...) +} + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// a.Len(mySlice, 3) +func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Len(a.t, object, length, msgAndArgs...) +} + +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// a.Lenf(mySlice, 3, "error message %s", "formatted") +func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Lenf(a.t, object, length, msg, args...) +} + +// Less asserts that the first element is less than the second +// +// a.Less(1, 2) +// a.Less(float64(1), float64(2)) +// a.Less("a", "b") +func (a *Assertions) Less(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Less(a.t, e1, e2, msgAndArgs...) +} + +// LessOrEqual asserts that the first element is less than or equal to the second +// +// a.LessOrEqual(1, 2) +// a.LessOrEqual(2, 2) +// a.LessOrEqual("a", "b") +// a.LessOrEqual("b", "b") +func (a *Assertions) LessOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + LessOrEqual(a.t, e1, e2, msgAndArgs...) +} + +// LessOrEqualf asserts that the first element is less than or equal to the second +// +// a.LessOrEqualf(1, 2, "error message %s", "formatted") +// a.LessOrEqualf(2, 2, "error message %s", "formatted") +// a.LessOrEqualf("a", "b", "error message %s", "formatted") +// a.LessOrEqualf("b", "b", "error message %s", "formatted") +func (a *Assertions) LessOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + LessOrEqualf(a.t, e1, e2, msg, args...) +} + +// Lessf asserts that the first element is less than the second +// +// a.Lessf(1, 2, "error message %s", "formatted") +// a.Lessf(float64(1), float64(2), "error message %s", "formatted") +// a.Lessf("a", "b", "error message %s", "formatted") +func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Lessf(a.t, e1, e2, msg, args...) +} + +// Never asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// a.Never(func() bool { return false; }, time.Second, 10*time.Millisecond) +func (a *Assertions) Never(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Never(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// Neverf asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// a.Neverf(func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func (a *Assertions) Neverf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Neverf(a.t, condition, waitFor, tick, msg, args...) +} + +// Nil asserts that the specified object is nil. +// +// a.Nil(err) +func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Nil(a.t, object, msgAndArgs...) +} + +// Nilf asserts that the specified object is nil. +// +// a.Nilf(err, "error message %s", "formatted") +func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Nilf(a.t, object, msg, args...) +} + +// NoDirExists checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func (a *Assertions) NoDirExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoDirExists(a.t, path, msgAndArgs...) +} + +// NoDirExistsf checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func (a *Assertions) NoDirExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoDirExistsf(a.t, path, msg, args...) +} + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoError(err) { +// assert.Equal(t, expectedObj, actualObj) +// } +func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoError(a.t, err, msgAndArgs...) +} + +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoErrorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } +func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoErrorf(a.t, err, msg, args...) +} + +// NoFileExists checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func (a *Assertions) NoFileExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoFileExists(a.t, path, msgAndArgs...) +} + +// NoFileExistsf checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func (a *Assertions) NoFileExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoFileExistsf(a.t, path, msg, args...) +} + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContains("Hello World", "Earth") +// a.NotContains(["Hello", "World"], "Earth") +// a.NotContains({"Hello": "World"}, "Earth") +func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotContains(a.t, s, contains, msgAndArgs...) +} + +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted") +// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted") +// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted") +func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotContainsf(a.t, s, contains, msg, args...) +} + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmpty(obj) { +// assert.Equal(t, "two", obj[1]) +// } +func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEmpty(a.t, object, msgAndArgs...) +} + +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmptyf(obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEmptyf(a.t, object, msg, args...) +} + +// NotEqual asserts that the specified values are NOT equal. +// +// a.NotEqual(obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqual(a.t, expected, actual, msgAndArgs...) +} + +// NotEqualValues asserts that two objects are not equal even when converted to the same type +// +// a.NotEqualValues(obj1, obj2) +func (a *Assertions) NotEqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqualValues(a.t, expected, actual, msgAndArgs...) +} + +// NotEqualValuesf asserts that two objects are not equal even when converted to the same type +// +// a.NotEqualValuesf(obj1, obj2, "error message %s", "formatted") +func (a *Assertions) NotEqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqualValuesf(a.t, expected, actual, msg, args...) +} + +// NotEqualf asserts that the specified values are NOT equal. +// +// a.NotEqualf(obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqualf(a.t, expected, actual, msg, args...) +} + +// NotNil asserts that the specified object is not nil. +// +// a.NotNil(err) +func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotNil(a.t, object, msgAndArgs...) +} + +// NotNilf asserts that the specified object is not nil. +// +// a.NotNilf(err, "error message %s", "formatted") +func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotNilf(a.t, object, msg, args...) +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanics(func(){ RemainCalm() }) +func (a *Assertions) NotPanics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotPanics(a.t, f, msgAndArgs...) +} + +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted") +func (a *Assertions) NotPanicsf(f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotPanicsf(a.t, f, msg, args...) +} + +// NotRegexp asserts that a specified regexp does not match a string. +// +// a.NotRegexp(regexp.MustCompile("starts"), "it's starting") +// a.NotRegexp("^start", "it's not starting") +func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotRegexp(a.t, rx, str, msgAndArgs...) +} + +// NotRegexpf asserts that a specified regexp does not match a string. +// +// a.NotRegexpf(regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") +// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") +func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotRegexpf(a.t, rx, str, msg, args...) +} + +// NotSame asserts that two pointers do not reference the same object. +// +// a.NotSame(ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) NotSame(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSame(a.t, expected, actual, msgAndArgs...) +} + +// NotSamef asserts that two pointers do not reference the same object. +// +// a.NotSamef(ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) NotSamef(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSamef(a.t, expected, actual, msg, args...) +} + +// NotSubset asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// a.NotSubset([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]") +func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSubset(a.t, list, subset, msgAndArgs...) +} + +// NotSubsetf asserts that the specified list(array, slice...) contains not all +// elements given in the specified subset(array, slice...). +// +// a.NotSubsetf([1, 3, 4], [1, 2], "But [1, 3, 4] does not contain [1, 2]", "error message %s", "formatted") +func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSubsetf(a.t, list, subset, msg, args...) +} + +// NotZero asserts that i is not the zero value for its type. +func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotZero(a.t, i, msgAndArgs...) +} + +// NotZerof asserts that i is not the zero value for its type. +func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotZerof(a.t, i, msg, args...) +} + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panics(func(){ GoCrazy() }) +func (a *Assertions) Panics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Panics(a.t, f, msgAndArgs...) +} + +// PanicsWithError asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// a.PanicsWithError("crazy error", func(){ GoCrazy() }) +func (a *Assertions) PanicsWithError(errString string, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithError(a.t, errString, f, msgAndArgs...) +} + +// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// a.PanicsWithErrorf("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) PanicsWithErrorf(errString string, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithErrorf(a.t, errString, f, msg, args...) +} + +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValue("crazy error", func(){ GoCrazy() }) +func (a *Assertions) PanicsWithValue(expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithValue(a.t, expected, f, msgAndArgs...) +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) PanicsWithValuef(expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithValuef(a.t, expected, f, msg, args...) +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) Panicsf(f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Panicsf(a.t, f, msg, args...) +} + +// Regexp asserts that a specified regexp matches a string. +// +// a.Regexp(regexp.MustCompile("start"), "it's starting") +// a.Regexp("start...$", "it's not starting") +func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Regexp(a.t, rx, str, msgAndArgs...) +} + +// Regexpf asserts that a specified regexp matches a string. +// +// a.Regexpf(regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") +// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") +func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Regexpf(a.t, rx, str, msg, args...) +} + +// Same asserts that two pointers reference the same object. +// +// a.Same(ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) Same(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Same(a.t, expected, actual, msgAndArgs...) +} + +// Samef asserts that two pointers reference the same object. +// +// a.Samef(ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) Samef(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Samef(a.t, expected, actual, msg, args...) +} + +// Subset asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// a.Subset([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]") +func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Subset(a.t, list, subset, msgAndArgs...) +} + +// Subsetf asserts that the specified list(array, slice...) contains all +// elements given in the specified subset(array, slice...). +// +// a.Subsetf([1, 2, 3], [1, 2], "But [1, 2, 3] does contain [1, 2]", "error message %s", "formatted") +func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Subsetf(a.t, list, subset, msg, args...) +} + +// True asserts that the specified value is true. +// +// a.True(myBool) +func (a *Assertions) True(value bool, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + True(a.t, value, msgAndArgs...) +} + +// Truef asserts that the specified value is true. +// +// a.Truef(myBool, "error message %s", "formatted") +func (a *Assertions) Truef(value bool, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Truef(a.t, value, msg, args...) +} + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// a.WithinDuration(time.Now(), time.Now(), 10*time.Second) +func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinDuration(a.t, expected, actual, delta, msgAndArgs...) +} + +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinDurationf(a.t, expected, actual, delta, msg, args...) +} + +// YAMLEq asserts that two YAML strings are equivalent. +func (a *Assertions) YAMLEq(expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + YAMLEq(a.t, expected, actual, msgAndArgs...) +} + +// YAMLEqf asserts that two YAML strings are equivalent. +func (a *Assertions) YAMLEqf(expected string, actual string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + YAMLEqf(a.t, expected, actual, msg, args...) +} + +// Zero asserts that i is the zero value for its type. +func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Zero(a.t, i, msgAndArgs...) +} + +// Zerof asserts that i is the zero value for its type. +func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Zerof(a.t, i, msg, args...) +} diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl b/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl new file mode 100644 index 000000000..54124df1d --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl @@ -0,0 +1,5 @@ +{{.CommentWithoutT "a"}} +func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) { + if h, ok := a.t.(tHelper); ok { h.Helper() } + {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) +} diff --git a/vendor/github.com/stretchr/testify/require/requirements.go b/vendor/github.com/stretchr/testify/require/requirements.go new file mode 100644 index 000000000..91772dfeb --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/requirements.go @@ -0,0 +1,29 @@ +package require + +// TestingT is an interface wrapper around *testing.T +type TestingT interface { + Errorf(format string, args ...interface{}) + FailNow() +} + +type tHelper interface { + Helper() +} + +// ComparisonAssertionFunc is a common function prototype when comparing two values. Can be useful +// for table driven tests. +type ComparisonAssertionFunc func(TestingT, interface{}, interface{}, ...interface{}) + +// ValueAssertionFunc is a common function prototype when validating a single value. Can be useful +// for table driven tests. +type ValueAssertionFunc func(TestingT, interface{}, ...interface{}) + +// BoolAssertionFunc is a common function prototype when validating a bool value. Can be useful +// for table driven tests. +type BoolAssertionFunc func(TestingT, bool, ...interface{}) + +// ErrorAssertionFunc is a common function prototype when validating an error value. Can be useful +// for table driven tests. +type ErrorAssertionFunc func(TestingT, error, ...interface{}) + +//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require.go.tmpl -include-format-funcs" diff --git a/vendor/modules.txt b/vendor/modules.txt index 10b1d025f..992732b6b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -67,7 +67,7 @@ github.com/bgentry/go-netrc/netrc github.com/bgentry/speakeasy # github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew/spew -# github.com/fastly/go-fastly/v3 v3.0.0 +# github.com/fastly/go-fastly/v3 v3.3.0 ## explicit github.com/fastly/go-fastly/v3/fastly # github.com/fatih/color v1.7.0 @@ -86,8 +86,7 @@ github.com/google/go-cmp/cmp/internal/diff github.com/google/go-cmp/cmp/internal/flags github.com/google/go-cmp/cmp/internal/function github.com/google/go-cmp/cmp/internal/value -# github.com/google/jsonapi v0.0.0-20180313013858-2dcc18f43696 -## explicit +# github.com/google/jsonapi v0.0.0-20201022225600-f822737867f6 github.com/google/jsonapi # github.com/google/uuid v1.1.2 github.com/google/uuid @@ -252,6 +251,7 @@ github.com/spf13/afero/mem # github.com/stretchr/testify v1.6.1 ## explicit github.com/stretchr/testify/assert +github.com/stretchr/testify/require # github.com/ulikunitz/xz v0.5.5 github.com/ulikunitz/xz github.com/ulikunitz/xz/internal/hash