diff --git a/.travis.yml b/.travis.yml index 151ea213..a891a554 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,33 +1,21 @@ -# Adapted from https://gist.github.com/y0ssar1an/df2dab474520c4086926f672c52db139 -language: go +services: + - docker -# Only the last two Go releases are supported by the Go team with security -# updates. Any versions older than that should be considered deprecated. -# Don't bother testing with them. tip builds your code with the latest -# development version of Go. This can warn you that your code will break -# in the next version of Go. Don't worry! Later we declare that test runs -# are allowed to fail on Go tip. +language: go go: - - 1.10.x - - 1.11.x - - master - -matrix: - # It's ok if our code fails on unstable development versions of Go. - allow_failures: - - go: master - # Don't wait for tip tests to finish. Mark the test run green if the - # tests pass on the stable versions of Go. - fast_finish: true - -# Don't email me the results of the test runs. -notifications: - email: false - + - "1.11.x" env: - - GO111MODULE="on" + GOFLAGS=-mod=vendor + +install: + # Decrypt Artifactory License Key + - openssl aes-256-cbc -K $encrypted_1d073d5eb2c7_key -iv $encrypted_1d073d5eb2c7_iv + -in scripts/artifactory.lic.enc -out scripts/artifactory.lic -d script: - - go build -v - - go test -v -race ./... # Run all the tests with the race detector enabled - - go vet ./... # go vet is the official Go static analyzer \ No newline at end of file + - make testacc + +matrix: + fast_finish: true + allow_failures: + - go: tip \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index b5ef67b4..3b5db701 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,12 @@ +## 1.4.3 (January 22, 2019) +BUG FIXES: +* Fixed setting of passwords in replications and remote repository + +NOTES: +* Added integration tests +* Bumped to terraform v0.11.11 +* Cleaned up lint checks and build + ## 1.4.2 (December 4, 2018) FEATURES: * Added docs website [#14] diff --git a/GNUmakefile b/GNUmakefile new file mode 100644 index 00000000..8de484bb --- /dev/null +++ b/GNUmakefile @@ -0,0 +1,29 @@ +TEST?=./... +GOFMT_FILES?=$$(find . -name '*.go' |grep -v vendor) +PKG_NAME=pkg/artifactory + +default: build + +build: fmtcheck + go install + +test: fmtcheck + go test $(TEST) -timeout=30s -parallel=4 + +docker: + @echo "==> Launching Artifactory in Docker..." + @scripts/run-artifactory.sh + +testacc: fmtcheck docker + TF_ACC=1 ARTIFACTORY_USERNAME=admin ARTIFACTORY_PASSWORD=password ARTIFACTORY_URL=http://localhost:8080/artifactory \ + go test $(TEST) -v -parallel 20 $(TESTARGS) -timeout 120m + @docker stop artifactory + +fmt: + @echo "==> Fixing source code with gofmt..." + gofmt -s -w ./$(PKG_NAME) + +fmtcheck: + @sh -c "'$(CURDIR)/scripts/gofmtcheck.sh'" + +.PHONY: build test testacc fmt \ No newline at end of file diff --git a/README.md b/README.md index c8e7858d..799e1e24 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ # Terraform Provider Artifactory # +[![Build Status](https://travis-ci.org/atlassian/terraform-provider-artifactory.svg?branch=master)](https://travis-ci.org/atlassian/terraform-provider-artifactory) +[![Go Report Card](https://goreportcard.com/badge/github.com/atlassian/terraform-provider-artifactory)](https://goreportcard.com/report/github.com/atlassian/terraform-provider-artifactory) ## Using the provider ## If you're building the provider, follow the instructions to [install it as a plugin.](https://www.terraform.io/docs/plugins/basics.html#installing-a-plugin) After placing it into your plugins directory, run `terraform init` to initialize it. ## Requirements ## -- [Go](https://golang.org/doc/install) 1.10+ (to build the provider plugin) +- [Go](https://golang.org/doc/install) 1.11+ (to build the provider plugin) - [Terraform](https://www.terraform.io/downloads.html) 0.11 ## Building The Provider ## @@ -24,14 +26,6 @@ cd $GOPATH/src/github.com/atlassian/terraform-provider-artifactory go install ``` -## Roadmap ## - -This library is being initially developed for an internal application at -Atlassian, so resources will likely be implemented in the order that they are -needed. Eventually, it would be ideal to cover the entire Artifactory API, so -contributions are of course always welcome. The calling pattern is pretty well -established, so adding new methods is relatively straightforward. - ## Versioning ## In general, this project follows [semver](https://semver.org/) as closely as we diff --git a/docs/r/remote_repository.md b/docs/r/remote_repository.md index 095adc77..0bda8e56 100644 --- a/docs/r/remote_repository.md +++ b/docs/r/remote_repository.md @@ -31,7 +31,7 @@ Arguments have a one to one mapping with the [JFrog API](https://www.jfrog.com/c * `max_unique_snapshots` - (Optional) * `suppress_pom_consistency_checks` - (Optional) * `username` - (Optional) -* `password` - (Optional) +* `password` - (Optional) Requires password encryption to be turned off `POST /api/system/decrypt` * `proxy` - (Optional) * `hard_fail` - (Optional) * `offline` - (Optional) diff --git a/docs/r/replication_config.md b/docs/r/replication_config.md index 434b23de..9fb55953 100644 --- a/docs/r/replication_config.md +++ b/docs/r/replication_config.md @@ -42,7 +42,7 @@ The following arguments are supported: * `url` - (Required) * `socket_timeout_millis` - (Optional) * `username` - (Optional) - * `password` - (Optional) + * `password` - (Optional) Requires password encryption to be turned off `POST /api/system/decrypt` * `enabled` - (Optional) * `sync_deletes` - (Optional) * `sync_properties` - (Optional) diff --git a/go.mod b/go.mod index 6eb4d2c7..444db7be 100644 --- a/go.mod +++ b/go.mod @@ -32,7 +32,7 @@ require ( github.com/hashicorp/hcl2 v0.0.0-20180801154631-77c0b55a597c // indirect github.com/hashicorp/hil v0.0.0-20170627220502-fa9f258a9250 // indirect github.com/hashicorp/logutils v0.0.0-20150609070431-0dc08b1671f3 // indirect - github.com/hashicorp/terraform v0.11.7 + github.com/hashicorp/terraform v0.11.11 github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb // indirect github.com/jtolds/gls v4.2.1+incompatible // indirect github.com/kylelemons/godebug v0.0.0-20170820004349-d65d576e9348 // indirect diff --git a/go.sum b/go.sum index fdf6e12f..95621a2e 100644 --- a/go.sum +++ b/go.sum @@ -61,8 +61,8 @@ github.com/hashicorp/hil v0.0.0-20170627220502-fa9f258a9250 h1:fooK5IvDL/KIsi4Lx github.com/hashicorp/hil v0.0.0-20170627220502-fa9f258a9250/go.mod h1:KHvg/R2/dPtaePb16oW4qIyzkMxXOL38xjRN64adsts= github.com/hashicorp/logutils v0.0.0-20150609070431-0dc08b1671f3 h1:oD64EFjELI9RY9yoWlfua58r+etdnoIC871z+rr6lkA= github.com/hashicorp/logutils v0.0.0-20150609070431-0dc08b1671f3/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/terraform v0.11.7 h1:NC3flYWAINcKyjTRIyU/Sz6kZLuSdAGEpqoyurZxLN0= -github.com/hashicorp/terraform v0.11.7/go.mod h1:uN1KUiT7Wdg61fPwsGXQwK3c8PmpIVZrt5Vcb1VrSoM= +github.com/hashicorp/terraform v0.11.11 h1:5q1y/a0RB1QmKc1n6E9tnWQqPMb+nEb7Bfol74N2grw= +github.com/hashicorp/terraform v0.11.11/go.mod h1:uN1KUiT7Wdg61fPwsGXQwK3c8PmpIVZrt5Vcb1VrSoM= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb/go.mod h1:+NfK9FKeTrX5uv1uIXGdwYDTeHna2qgaIlx54MXqjAM= github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8 h1:12VvqtR6Aowv3l/EQUlocDHW2Cp4G9WJVH7uyH8QFJE= diff --git a/pkg/artifactory/provider.go b/pkg/artifactory/provider.go index cd9cc308..cb41084a 100644 --- a/pkg/artifactory/provider.go +++ b/pkg/artifactory/provider.go @@ -2,12 +2,15 @@ package artifactory import ( "fmt" + "net/http" "github.com/atlassian/go-artifactory/pkg/artifactory" "github.com/hashicorp/terraform/helper/schema" "github.com/hashicorp/terraform/terraform" ) +// Artifactory Provider that supports configuration via username+password or a token +// Supported resources are repos, users, groups, replications, and permissions func Provider() terraform.ResourceProvider { return &schema.Provider{ Schema: map[string]*schema.Schema{ @@ -55,20 +58,25 @@ func providerConfigure(d *schema.ResourceData) (interface{}, error) { return nil, fmt.Errorf("url cannot be nil") } - if token, ok := d.GetOkExists("token"); ok { - tp := artifactory.TokenAuthTransport{ - Token: token.(string), - } - return artifactory.NewClient(d.Get("url").(string), tp.Client()) - } else if username, ok := d.GetOkExists("username"); !ok { - return nil, fmt.Errorf("error: Missing token and username. One must be set") - } else if password, ok := d.GetOkExists("password"); !ok { - return nil, fmt.Errorf("error: Basic auth used but password not set") - } else { + username := d.Get("username").(string) + password := d.Get("password").(string) + token := d.Get("token").(string) + + var client *http.Client + if username != "" && password != "" { tp := artifactory.BasicAuthTransport{ - Username: username.(string), - Password: password.(string), + Username: username, + Password: password, } - return artifactory.NewClient(d.Get("url").(string), tp.Client()) + client = tp.Client() + } else if token != "" { + tp := &artifactory.TokenAuthTransport{ + Token: token, + } + client = tp.Client() + } else { + return nil, fmt.Errorf("either [username, password] or [token] must be set to use provider") } + + return artifactory.NewClient(d.Get("url").(string), client) } diff --git a/pkg/artifactory/resource_artifactory_group.go b/pkg/artifactory/resource_artifactory_group.go index 9e922738..884b4251 100644 --- a/pkg/artifactory/resource_artifactory_group.go +++ b/pkg/artifactory/resource_artifactory_group.go @@ -59,12 +59,12 @@ func unmarshalGroup(s *schema.ResourceData) (*artifactory.Group, error) { group := new(artifactory.Group) - group.Name = d.GetStringRef("name") - group.Description = d.GetStringRef("description") - group.AutoJoin = d.GetBoolRef("auto_join") - group.AdminPrivileges = d.GetBoolRef("admin_privileges") - group.Realm = d.GetStringRef("realm") - group.RealmAttributes = d.GetStringRef("realm_attributes") + group.Name = d.getStringRef("name") + group.Description = d.getStringRef("description") + group.AutoJoin = d.getBoolRef("auto_join") + group.AdminPrivileges = d.getBoolRef("admin_privileges") + group.Realm = d.getStringRef("realm") + group.RealmAttributes = d.getStringRef("realm_attributes") // Validator if group.AdminPrivileges != nil && group.AutoJoin != nil && *group.AdminPrivileges && *group.AutoJoin { diff --git a/pkg/artifactory/resource_artifactory_group_test.go b/pkg/artifactory/resource_artifactory_group_test.go index a876626c..660dee42 100644 --- a/pkg/artifactory/resource_artifactory_group_test.go +++ b/pkg/artifactory/resource_artifactory_group_test.go @@ -11,7 +11,7 @@ import ( "net/http" ) -const group_basic = ` +const groupBasic = ` resource "artifactory_group" "test-group" { name = "terraform-group" }` @@ -23,7 +23,7 @@ func TestAccGroup_basic(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: group_basic, + Config: groupBasic, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("artifactory_group.test-group", "name", "terraform-group"), ), @@ -32,7 +32,7 @@ func TestAccGroup_basic(t *testing.T) { }) } -const group_full = ` +const groupFull = ` resource "artifactory_group" "test-group" { name = "terraform-group" description = "Test group" @@ -49,7 +49,7 @@ func TestAccGroup_full(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: group_full, + Config: groupFull, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("artifactory_group.test-group", "name", "terraform-group"), resource.TestCheckResourceAttr("artifactory_group.test-group", "auto_join", "true"), diff --git a/pkg/artifactory/resource_artifactory_local_repository.go b/pkg/artifactory/resource_artifactory_local_repository.go index 330a5083..bb96ed6c 100644 --- a/pkg/artifactory/resource_artifactory_local_repository.go +++ b/pkg/artifactory/resource_artifactory_local_repository.go @@ -136,28 +136,28 @@ func unmarshalLocalRepository(s *schema.ResourceData) *artifactory.LocalReposito repo.RClass = artifactory.String("local") - repo.Key = d.GetStringRef("key") - repo.PackageType = d.GetStringRef("package_type") - repo.Description = d.GetStringRef("description") - repo.Notes = d.GetStringRef("notes") - repo.DebianTrivialLayout = d.GetBoolRef("debian_trivial_layout") - repo.IncludesPattern = d.GetStringRef("includes_pattern") - repo.ExcludesPattern = d.GetStringRef("excludes_pattern") - repo.RepoLayoutRef = d.GetStringRef("repo_layout_ref") - repo.MaxUniqueTags = d.GetIntRef("max_unique_tags") - repo.BlackedOut = d.GetBoolRef("blacked_out") - repo.CalculateYumMetadata = d.GetBoolRef("calculate_yum_metadata") - repo.YumRootDepth = d.GetIntRef("yum_root_depth") - repo.ArchiveBrowsingEnabled = d.GetBoolRef("archive_browsing_enabled") - repo.DockerApiVersion = d.GetStringRef("docker_api_verision") - repo.EnableFileListsIndexing = d.GetBoolRef("enable_file_lists_indexing") - repo.PropertySets = d.GetSetRef("property_sets") - repo.HandleReleases = d.GetBoolRef("handle_releases") - repo.HandleSnapshots = d.GetBoolRef("handle_snapshots") - repo.ChecksumPolicyType = d.GetStringRef("checksum_policy_type") - repo.MaxUniqueSnapshots = d.GetIntRef("max_unique_snapshots") - repo.SnapshotVersionBehavior = d.GetStringRef("snapshot_version_behavior") - repo.SuppressPomConsistencyChecks = d.GetBoolRef("suppress_pom_consistency_checks") + repo.Key = d.getStringRef("key") + repo.PackageType = d.getStringRef("package_type") + repo.Description = d.getStringRef("description") + repo.Notes = d.getStringRef("notes") + repo.DebianTrivialLayout = d.getBoolRef("debian_trivial_layout") + repo.IncludesPattern = d.getStringRef("includes_pattern") + repo.ExcludesPattern = d.getStringRef("excludes_pattern") + repo.RepoLayoutRef = d.getStringRef("repo_layout_ref") + repo.MaxUniqueTags = d.getIntRef("max_unique_tags") + repo.BlackedOut = d.getBoolRef("blacked_out") + repo.CalculateYumMetadata = d.getBoolRef("calculate_yum_metadata") + repo.YumRootDepth = d.getIntRef("yum_root_depth") + repo.ArchiveBrowsingEnabled = d.getBoolRef("archive_browsing_enabled") + repo.DockerApiVersion = d.getStringRef("docker_api_verision") + repo.EnableFileListsIndexing = d.getBoolRef("enable_file_lists_indexing") + repo.PropertySets = d.getSetRef("property_sets") + repo.HandleReleases = d.getBoolRef("handle_releases") + repo.HandleSnapshots = d.getBoolRef("handle_snapshots") + repo.ChecksumPolicyType = d.getStringRef("checksum_policy_type") + repo.MaxUniqueSnapshots = d.getIntRef("max_unique_snapshots") + repo.SnapshotVersionBehavior = d.getStringRef("snapshot_version_behavior") + repo.SuppressPomConsistencyChecks = d.getBoolRef("suppress_pom_consistency_checks") return repo } @@ -200,7 +200,7 @@ func resourceLocalRepositoryRead(d *schema.ResourceData, m interface{}) error { d.Set("yum_root_depth", repo.YumRootDepth) d.Set("docker_api_version", repo.DockerApiVersion) d.Set("enable_file_lists_indexing", repo.EnableFileListsIndexing) - d.Set("property_sets", schema.NewSet(schema.HashString, CastToInterfaceArr(*repo.PropertySets))) + d.Set("property_sets", schema.NewSet(schema.HashString, castToInterfaceArr(*repo.PropertySets))) d.Set("handle_releases", repo.HandleReleases) d.Set("handle_snapshots", repo.HandleSnapshots) d.Set("checksum_policy_type", repo.ChecksumPolicyType) diff --git a/pkg/artifactory/resource_artifactory_local_repository_test.go b/pkg/artifactory/resource_artifactory_local_repository_test.go index da531a54..824a1e88 100644 --- a/pkg/artifactory/resource_artifactory_local_repository_test.go +++ b/pkg/artifactory/resource_artifactory_local_repository_test.go @@ -11,7 +11,7 @@ import ( "net/http" ) -const localRepository_basic = ` +const localRepositoryBasic = ` resource "artifactory_local_repository" "terraform-local-test-repo-basic" { key = "terraform-local-test-repo-basic" package_type = "docker" @@ -24,7 +24,7 @@ func TestAccLocalRepository_basic(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: localRepository_basic, + Config: localRepositoryBasic, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("artifactory_local_repository.terraform-local-test-repo-basic", "key", "terraform-local-test-repo-basic"), resource.TestCheckResourceAttr("artifactory_local_repository.terraform-local-test-repo-basic", "package_type", "docker"), @@ -34,7 +34,7 @@ func TestAccLocalRepository_basic(t *testing.T) { }) } -const localRepositoryConfig_full = ` +const localRepositoryConfigFull = ` resource "artifactory_local_repository" "terraform-local-test-repo-full" { key = "terraform-local-test-repo-full" package_type = "npm" @@ -66,7 +66,7 @@ func TestAccLocalRepository_full(t *testing.T) { CheckDestroy: resourceLocalRepositoryCheckDestroy("artifactory_local_repository.terraform-local-test-repo-full"), Steps: []resource.TestStep{ { - Config: localRepositoryConfig_full, + Config: localRepositoryConfigFull, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("artifactory_local_repository.terraform-local-test-repo-full", "key", "terraform-local-test-repo-full"), resource.TestCheckResourceAttr("artifactory_local_repository.terraform-local-test-repo-full", "package_type", "npm"), diff --git a/pkg/artifactory/resource_artifactory_permission_target.go b/pkg/artifactory/resource_artifactory_permission_target.go index 54dd481c..c8cbc4cc 100644 --- a/pkg/artifactory/resource_artifactory_permission_target.go +++ b/pkg/artifactory/resource_artifactory_permission_target.go @@ -74,10 +74,10 @@ func unmarshalPermissionTargets(s *schema.ResourceData) *artifactory.PermissionT pt := new(artifactory.PermissionTargets) - pt.Name = d.GetStringRef("name") - pt.IncludesPattern = d.GetStringRef("includes_pattern") - pt.ExcludesPattern = d.GetStringRef("excludes_pattern") - pt.Repositories = d.GetSetRef("repositories") + pt.Name = d.getStringRef("name") + pt.IncludesPattern = d.getStringRef("includes_pattern") + pt.ExcludesPattern = d.getStringRef("excludes_pattern") + pt.Repositories = d.getSetRef("repositories") if v, ok := d.GetOkExists("users"); ok { if pt.Principals == nil { @@ -104,7 +104,7 @@ func marshalPermissionTargets(permissionTargets *artifactory.PermissionTargets, d.Set("excludes_pattern", permissionTargets.ExcludesPattern) if permissionTargets.Repositories != nil { - d.Set("repositories", schema.NewSet(schema.HashString, CastToInterfaceArr(*permissionTargets.Repositories))) + d.Set("repositories", schema.NewSet(schema.HashString, castToInterfaceArr(*permissionTargets.Repositories))) } if permissionTargets.Principals.Users != nil { @@ -121,7 +121,7 @@ func expandPrincipal(s *schema.Set) map[string][]string { for _, v := range s.List() { o := v.(map[string]interface{}) - principal[o["name"].(string)] = CastToStringArr(o["permissions"].(*schema.Set).List()) + principal[o["name"].(string)] = castToStringArr(o["permissions"].(*schema.Set).List()) } return principal @@ -133,7 +133,7 @@ func flattenPrincipal(principal map[string][]string) *schema.Set { for name, perms := range principal { user := map[string]interface{}{ "name": name, - "permissions": schema.NewSet(schema.HashString, CastToInterfaceArr(perms)), + "permissions": schema.NewSet(schema.HashString, castToInterfaceArr(perms)), } s = append(s, user) @@ -144,7 +144,7 @@ func flattenPrincipal(principal map[string][]string) *schema.Set { func hashPrincipal(o interface{}) int { p := o.(map[string]interface{}) - return hashcode.String(p["name"].(string)) + 31*hashcode.String(hashcode.Strings(CastToStringArr(p["permissions"].(*schema.Set).List()))) + return hashcode.String(p["name"].(string)) + 31*hashcode.String(hashcode.Strings(castToStringArr(p["permissions"].(*schema.Set).List()))) } func resourcePermissionTargetsCreateOrReplace(d *schema.ResourceData, m interface{}) error { diff --git a/pkg/artifactory/resource_artifactory_permission_target_test.go b/pkg/artifactory/resource_artifactory_permission_target_test.go index 436ee419..a13f5aa5 100644 --- a/pkg/artifactory/resource_artifactory_permission_target_test.go +++ b/pkg/artifactory/resource_artifactory_permission_target_test.go @@ -10,13 +10,18 @@ import ( "testing" ) -const permission_basic = ` -resource "artifactory_permission_targets" "terraform-test-permission-basic" { - name = "testpermission" - repositories = ["not-restricted"] +const permissionBasic = ` +resource "artifactory_local_repository" "lib-local" { + key = "lib-local" + package_type = "maven" +} + +resource "artifactory_permission_targets" "test-perm" { + name = "test-perm" + repositories = ["${artifactory_local_repository.lib-local.key}"] users = [ { - name = "test_user" + name = "anonymous" permissions = [ "r", "w" @@ -28,14 +33,14 @@ resource "artifactory_permission_targets" "terraform-test-permission-basic" { func TestAccPermission_basic(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, - CheckDestroy: testPermissionTargetCheckDestroy("artifactory_permission_targets.terraform-test-permission-basic"), + CheckDestroy: testPermissionTargetCheckDestroy("artifactory_permission_targets.test-perm"), Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: permission_basic, + Config: permissionBasic, Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("artifactory_permission_targets.terraform-test-permission-basic", "name", "testpermission"), - resource.TestCheckResourceAttr("artifactory_permission_targets.terraform-test-permission-basic", "repositories.#", "1"), + resource.TestCheckResourceAttr("artifactory_permission_targets.test-perm", "name", "test-perm"), + resource.TestCheckResourceAttr("artifactory_permission_targets.test-perm", "repositories.#", "1"), ), }, }, diff --git a/pkg/artifactory/resource_artifactory_remote_repository.go b/pkg/artifactory/resource_artifactory_remote_repository.go index 21a0c006..d63de5ad 100644 --- a/pkg/artifactory/resource_artifactory_remote_repository.go +++ b/pkg/artifactory/resource_artifactory_remote_repository.go @@ -1,9 +1,8 @@ package artifactory import ( - "fmt" - "context" + "fmt" "github.com/atlassian/go-artifactory/pkg/artifactory" "github.com/hashicorp/terraform/helper/schema" "net/http" @@ -87,10 +86,11 @@ func resourceArtifactoryRemoteRepository() *schema.Resource { Optional: true, }, "password": { - Type: schema.TypeString, - Optional: true, - Sensitive: true, - StateFunc: GetMD5Hash, + Type: schema.TypeString, + Optional: true, + Sensitive: true, + StateFunc: getMD5Hash, + DiffSuppressFunc: mD5Diff, }, "proxy": { Type: schema.TypeString, @@ -215,44 +215,44 @@ func unmarshalRemoteRepository(s *schema.ResourceData) *artifactory.RemoteReposi d := &ResourceData{s} repo := new(artifactory.RemoteRepository) - repo.Key = d.GetStringRef("key") + repo.Key = d.getStringRef("key") repo.RClass = artifactory.String("remote") - repo.PackageType = d.GetStringRef("package_type") - repo.Url = d.GetStringRef("url") - repo.Proxy = d.GetStringRef("proxy") - repo.Username = d.GetStringRef("username") - repo.Password = d.GetStringRef("password") - repo.Description = d.GetStringRef("description") - repo.Notes = d.GetStringRef("notes") - repo.IncludesPattern = d.GetStringRef("includes_pattern") - repo.ExcludesPattern = d.GetStringRef("excludes_pattern") - repo.RepoLayoutRef = d.GetStringRef("repo_layout_ref") - repo.HardFail = d.GetBoolRef("hard_fail") - repo.Offline = d.GetBoolRef("offline") - repo.BlackedOut = d.GetBoolRef("blacked_out") - repo.StoreArtifactsLocally = d.GetBoolRef("store_artifacts_locally") - repo.SocketTimeoutMillis = d.GetIntRef("socket_timeout_millis") - repo.LocalAddress = d.GetStringRef("local_address") - repo.RetrievalCachePeriodSecs = d.GetIntRef("retrieval_cache_period_seconds") - repo.MissedRetrievalCachePeriodSecs = d.GetIntRef("missed_cache_period_seconds") - repo.UnusedArtifactsCleanupPeriodHours = d.GetIntRef("unused_artifacts_cleanup_period_hours") - repo.ShareConfiguration = d.GetBoolRef("share_configuration") - repo.SynchronizeProperties = d.GetBoolRef("synchronize_properties") - repo.BlockMismatchingMimeTypes = d.GetBoolRef("block_mismatching_mime_types") - repo.AllowAnyHostAuth = d.GetBoolRef("allow_any_host_auth") - repo.EnableCookieManagement = d.GetBoolRef("enable_cookie_management") - repo.ClientTLSCertificate = d.GetStringRef("client_tls_certificate") - repo.PropertySets = d.GetSetRef("property_sets") - repo.HandleReleases = d.GetBoolRef("handle_releases") - repo.HandleSnapshots = d.GetBoolRef("handle_snapshots") - //repo.RemoteRepoChecksumPolicyType = d.GetStringRef("remote_repo_checksum_policy_type") - repo.MaxUniqueSnapshots = d.GetIntRef("max_unique_snapshots") - repo.SuppressPomConsistencyChecks = d.GetBoolRef("suppress_pom_consistency_checks") - repo.FetchJarsEagerly = d.GetBoolRef("fetch_jars_eagerly") - repo.FetchSourcesEagerly = d.GetBoolRef("fetch_sources_eagerly") - repo.PyPiRegistryUrl = d.GetStringRef("pypi_registry_url") - repo.BypassHeadRequests = d.GetBoolRef("bypass_head_requests") - repo.EnableTokenAuthentication = d.GetBoolRef("enable_token_authentication") + repo.PackageType = d.getStringRef("package_type") + repo.Url = d.getStringRef("url") + repo.Proxy = d.getStringRef("proxy") + repo.Username = d.getStringRef("username") + repo.Password = d.getStringRef("password") + repo.Description = d.getStringRef("description") + repo.Notes = d.getStringRef("notes") + repo.IncludesPattern = d.getStringRef("includes_pattern") + repo.ExcludesPattern = d.getStringRef("excludes_pattern") + repo.RepoLayoutRef = d.getStringRef("repo_layout_ref") + repo.HardFail = d.getBoolRef("hard_fail") + repo.Offline = d.getBoolRef("offline") + repo.BlackedOut = d.getBoolRef("blacked_out") + repo.StoreArtifactsLocally = d.getBoolRef("store_artifacts_locally") + repo.SocketTimeoutMillis = d.getIntRef("socket_timeout_millis") + repo.LocalAddress = d.getStringRef("local_address") + repo.RetrievalCachePeriodSecs = d.getIntRef("retrieval_cache_period_seconds") + repo.MissedRetrievalCachePeriodSecs = d.getIntRef("missed_cache_period_seconds") + repo.UnusedArtifactsCleanupPeriodHours = d.getIntRef("unused_artifacts_cleanup_period_hours") + repo.ShareConfiguration = d.getBoolRef("share_configuration") + repo.SynchronizeProperties = d.getBoolRef("synchronize_properties") + repo.BlockMismatchingMimeTypes = d.getBoolRef("block_mismatching_mime_types") + repo.AllowAnyHostAuth = d.getBoolRef("allow_any_host_auth") + repo.EnableCookieManagement = d.getBoolRef("enable_cookie_management") + repo.ClientTLSCertificate = d.getStringRef("client_tls_certificate") + repo.PropertySets = d.getSetRef("property_sets") + repo.HandleReleases = d.getBoolRef("handle_releases") + repo.HandleSnapshots = d.getBoolRef("handle_snapshots") + //repo.RemoteRepoChecksumPolicyType = d.getStringRef("remote_repo_checksum_policy_type") + repo.MaxUniqueSnapshots = d.getIntRef("max_unique_snapshots") + repo.SuppressPomConsistencyChecks = d.getBoolRef("suppress_pom_consistency_checks") + repo.FetchJarsEagerly = d.getBoolRef("fetch_jars_eagerly") + repo.FetchSourcesEagerly = d.getBoolRef("fetch_sources_eagerly") + repo.PyPiRegistryUrl = d.getStringRef("pypi_registry_url") + repo.BypassHeadRequests = d.getBoolRef("bypass_head_requests") + repo.EnableTokenAuthentication = d.getBoolRef("enable_token_authentication") return repo } @@ -268,7 +268,7 @@ func marshalRemoteRepository(repo *artifactory.RemoteRepository, d *schema.Resou d.Set("blacked_out", repo.BlackedOut) d.Set("url", repo.Url) d.Set("username", repo.Username) - d.Set("password", GetMD5Hash(*repo.Password)) + d.Set("password", *repo.Password) d.Set("proxy", repo.Proxy) d.Set("hard_fail", repo.HardFail) d.Set("offline", repo.Offline) @@ -284,7 +284,7 @@ func marshalRemoteRepository(repo *artifactory.RemoteRepository, d *schema.Resou d.Set("allow_any_host_auth", repo.AllowAnyHostAuth) d.Set("enable_cookie_management", repo.EnableCookieManagement) d.Set("client_tls_certificate", repo.ClientTLSCertificate) - d.Set("property_sets", schema.NewSet(schema.HashString, CastToInterfaceArr(*repo.PropertySets))) + d.Set("property_sets", schema.NewSet(schema.HashString, castToInterfaceArr(*repo.PropertySets))) d.Set("handle_releases", repo.HandleReleases) d.Set("handle_snapshots", repo.HandleSnapshots) //d.Set("remote_repo_checksum_policy_type", repo.RemoteRepoChecksumPolicyType) diff --git a/pkg/artifactory/resource_artifactory_remote_repository_test.go b/pkg/artifactory/resource_artifactory_remote_repository_test.go index 88383c41..fa6926c5 100644 --- a/pkg/artifactory/resource_artifactory_remote_repository_test.go +++ b/pkg/artifactory/resource_artifactory_remote_repository_test.go @@ -11,7 +11,7 @@ import ( "net/http" ) -const remoteRepository_basic = ` +const remoteRepoBasic = ` resource "artifactory_remote_repository" "terraform-remote-test-repo-basic" { key = "terraform-remote-test-repo-basic" package_type = "npm" @@ -26,7 +26,7 @@ func TestAccRemoteRepository_basic(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: remoteRepository_basic, + Config: remoteRepoBasic, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("artifactory_remote_repository.terraform-remote-test-repo-basic", "key", "terraform-remote-test-repo-basic"), resource.TestCheckResourceAttr("artifactory_remote_repository.terraform-remote-test-repo-basic", "package_type", "npm"), @@ -37,7 +37,7 @@ func TestAccRemoteRepository_basic(t *testing.T) { }) } -const remoteRepositoryConfig_full = ` +const remoteRepoFull = ` resource "artifactory_remote_repository" "terraform-remote-test-repo-full" { key = "terraform-remote-test-repo-full" package_type = "npm" @@ -82,13 +82,13 @@ func TestAccRemoteRepository_full(t *testing.T) { CheckDestroy: resourceRemoteRepositoryCheckDestroy("artifactory_remote_repository.terraform-remote-test-repo-full"), Steps: []resource.TestStep{ { - Config: remoteRepositoryConfig_full, + Config: remoteRepoFull, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("artifactory_remote_repository.terraform-remote-test-repo-full", "key", "terraform-remote-test-repo-full"), resource.TestCheckResourceAttr("artifactory_remote_repository.terraform-remote-test-repo-full", "package_type", "npm"), resource.TestCheckResourceAttr("artifactory_remote_repository.terraform-remote-test-repo-full", "url", "https://registry.npmjs.org/"), resource.TestCheckResourceAttr("artifactory_remote_repository.terraform-remote-test-repo-full", "username", "user"), - resource.TestCheckResourceAttr("artifactory_remote_repository.terraform-remote-test-repo-full", "password", GetMD5Hash("pass")), + resource.TestCheckResourceAttr("artifactory_remote_repository.terraform-remote-test-repo-full", "password", "pass"), resource.TestCheckResourceAttr("artifactory_remote_repository.terraform-remote-test-repo-full", "proxy", ""), resource.TestCheckResourceAttr("artifactory_remote_repository.terraform-remote-test-repo-full", "description", "desc (local file cache)"), resource.TestCheckResourceAttr("artifactory_remote_repository.terraform-remote-test-repo-full", "notes", "notes"), @@ -130,7 +130,7 @@ func resourceRemoteRepositoryCheckDestroy(id string) func(*terraform.State) erro rs, ok := s.RootModule().Resources[id] if !ok { - return fmt.Errorf("Not found %s", id) + return fmt.Errorf("not found %s", id) } _, resp, err := client.Repositories.GetRemote(context.Background(), rs.Primary.ID) @@ -140,7 +140,7 @@ func resourceRemoteRepositoryCheckDestroy(id string) func(*terraform.State) erro } else if err != nil { return fmt.Errorf("error: Request failed: %s", err.Error()) } else { - return fmt.Errorf("Repository %s still exists", rs.Primary.ID) + return fmt.Errorf("repository %s still exists", rs.Primary.ID) } } } diff --git a/pkg/artifactory/resource_artifactory_replication_config.go b/pkg/artifactory/resource_artifactory_replication_config.go index 88584dea..12866721 100644 --- a/pkg/artifactory/resource_artifactory_replication_config.go +++ b/pkg/artifactory/resource_artifactory_replication_config.go @@ -53,10 +53,11 @@ func resourceArtifactoryReplicationConfig() *schema.Resource { Optional: true, }, "password": { - Type: schema.TypeString, - Optional: true, - Sensitive: true, - StateFunc: GetMD5Hash, + Type: schema.TypeString, + Optional: true, + Sensitive: true, + StateFunc: getMD5Hash, + DiffSuppressFunc: mD5Diff, }, "enabled": { Type: schema.TypeBool, @@ -93,7 +94,7 @@ func unmarshalReplicationConfig(s *schema.ResourceData) *artifactory.Replication d := &ResourceData{s} replicationConfig := new(artifactory.ReplicationConfig) - repo := d.GetStringRef("repo_key") + repo := d.getStringRef("repo_key") if v, ok := d.GetOkExists("replications"); ok { arr := v.([]interface{}) @@ -104,8 +105,8 @@ func unmarshalReplicationConfig(s *schema.ResourceData) *artifactory.Replication for i, o := range arr { if i == 0 { replicationConfig.RepoKey = repo - replicationConfig.CronExp = d.GetStringRef("cron_exp") - replicationConfig.EnableEventReplication = d.GetBoolRef("enable_event_replication") + replicationConfig.CronExp = d.getStringRef("cron_exp") + replicationConfig.EnableEventReplication = d.getBoolRef("enable_event_replication") } m := o.(map[string]interface{}) @@ -180,7 +181,7 @@ func marshalReplicationConfig(replicationConfig *artifactory.ReplicationConfig, } if repo.Password != nil { - replication["password"] = GetMD5Hash(*repo.Password) + replication["password"] = *repo.Password } if repo.Enabled != nil { diff --git a/pkg/artifactory/resource_artifactory_replication_config_test.go b/pkg/artifactory/resource_artifactory_replication_config_test.go index ace1e29b..ebbcaa5c 100644 --- a/pkg/artifactory/resource_artifactory_replication_config_test.go +++ b/pkg/artifactory/resource_artifactory_replication_config_test.go @@ -1,30 +1,24 @@ package artifactory import ( - "fmt" - "os" - "testing" - "context" + "fmt" "github.com/atlassian/go-artifactory/pkg/artifactory" "github.com/hashicorp/terraform/helper/resource" "github.com/hashicorp/terraform/terraform" "net/http" + "os" + "testing" ) const replicationConfigTemplate = ` -resource "artifactory_local_repository" "provider_test_source" { - key = "provider_test_source" +resource "artifactory_local_repository" "lib-local" { + key = "lib-local" package_type = "maven" } -resource "artifactory_local_repository" "provider_test_dest" { - key = "provider_test_dest" - package_type = "maven" -} - -resource "artifactory_replication_config" "foo-rep" { - repo_key = "${artifactory_local_repository.provider_test_source.key}" +resource "artifactory_replication_config" "lib-local" { + repo_key = "${artifactory_local_repository.lib-local.key}" cron_exp = "0 0 * * * ?" enable_event_replication = true @@ -40,18 +34,22 @@ resource "artifactory_replication_config" "foo-rep" { func TestAccReplication_full(t *testing.T) { resource.Test(t, resource.TestCase{ - PreCheck: func() {}, - CheckDestroy: testAccCheckReplicationDestroy("artifactory_replication_config.foo"), + CheckDestroy: testAccCheckReplicationDestroy("artifactory_replication_config.lib-local"), Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: fmt.Sprintf(replicationConfigTemplate, os.Getenv("ARTIFACTORY_URL"), os.Getenv("ARTIFACTORY_USERNAME"), os.Getenv("ARTIFACTORY_PASSWORD")), + Config: fmt.Sprintf( + replicationConfigTemplate, + os.Getenv("ARTIFACTORY_URL"), + os.Getenv("ARTIFACTORY_USERNAME"), + os.Getenv("ARTIFACTORY_PASSWORD"), + ), Check: resource.ComposeTestCheckFunc( - resource.TestCheckResourceAttr("artifactory_replication_config.foo-rep", "repo_key", "provider_test_source"), - resource.TestCheckResourceAttr("artifactory_replication_config.foo-rep", "cron_exp", "0 0 * * * ?"), - resource.TestCheckResourceAttr("artifactory_replication_config.foo-rep", "enable_event_replication", "true"), - resource.TestCheckResourceAttr("artifactory_replication_config.foo-rep", "replications.#", "1"), + resource.TestCheckResourceAttr("artifactory_replication_config.lib-local", "repo_key", "lib-local"), + resource.TestCheckResourceAttr("artifactory_replication_config.lib-local", "cron_exp", "0 0 * * * ?"), + resource.TestCheckResourceAttr("artifactory_replication_config.lib-local", "enable_event_replication", "true"), + resource.TestCheckResourceAttr("artifactory_replication_config.lib-local", "replications.#", "1"), ), }, }, @@ -68,7 +66,7 @@ func testAccCheckReplicationDestroy(id string) func(*terraform.State) error { replica, resp, err := client.Artifacts.GetRepositoryReplicationConfig(context.Background(), rs.Primary.ID) - if resp.StatusCode == http.StatusNotFound { + if resp.StatusCode == http.StatusNotFound || resp.StatusCode == http.StatusBadRequest { return nil } else if err != nil { return fmt.Errorf("error: Request failed: %s", err.Error()) diff --git a/pkg/artifactory/resource_artifactory_user.go b/pkg/artifactory/resource_artifactory_user.go index eeb14ae9..1694ce13 100644 --- a/pkg/artifactory/resource_artifactory_user.go +++ b/pkg/artifactory/resource_artifactory_user.go @@ -68,14 +68,14 @@ func unmarshalUser(s *schema.ResourceData) *artifactory.User { d := &ResourceData{s} user := new(artifactory.User) - user.Name = d.GetStringRef("name") - user.Email = d.GetStringRef("email") - user.Admin = d.GetBoolRef("admin") - user.ProfileUpdatable = d.GetBoolRef("profile_updatable") - user.DisableUIAccess = d.GetBoolRef("disable_ui_access") - user.InternalPasswordDisabled = d.GetBoolRef("internal_password_disabled") - user.Realm = d.GetStringRef("realm") - user.Groups = d.GetSetRef("groups") + user.Name = d.getStringRef("name") + user.Email = d.getStringRef("email") + user.Admin = d.getBoolRef("admin") + user.ProfileUpdatable = d.getBoolRef("profile_updatable") + user.DisableUIAccess = d.getBoolRef("disable_ui_access") + user.InternalPasswordDisabled = d.getBoolRef("internal_password_disabled") + user.Realm = d.getStringRef("realm") + user.Groups = d.getSetRef("groups") return user } @@ -90,7 +90,7 @@ func marshalUser(user *artifactory.User, d *schema.ResourceData) { d.Set("internal_password_disabled", user.InternalPasswordDisabled) if user.Groups != nil { - d.Set("groups", schema.NewSet(schema.HashString, CastToInterfaceArr(*user.Groups))) + d.Set("groups", schema.NewSet(schema.HashString, castToInterfaceArr(*user.Groups))) } } diff --git a/pkg/artifactory/resource_artifactory_user_test.go b/pkg/artifactory/resource_artifactory_user_test.go index c2baaddb..478913f8 100644 --- a/pkg/artifactory/resource_artifactory_user_test.go +++ b/pkg/artifactory/resource_artifactory_user_test.go @@ -11,10 +11,11 @@ import ( "net/http" ) -const user_basic = ` +const userBasic = ` resource "artifactory_user" "foobar" { name = "the.dude" email = "the.dude@domain.com" + groups = [ "readers" ] }` func TestAccUser_basic(t *testing.T) { @@ -24,7 +25,7 @@ func TestAccUser_basic(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: user_basic, + Config: userBasic, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("artifactory_user.foobar", "name", "the.dude"), resource.TestCheckResourceAttr("artifactory_user.foobar", "email", "the.dude@domain.com"), @@ -36,7 +37,7 @@ func TestAccUser_basic(t *testing.T) { }) } -const user_full = ` +const userFull = ` resource "artifactory_user" "foobar" { name = "dummy_user" email = "dummy@a.com" @@ -52,7 +53,7 @@ func TestAccUser_full(t *testing.T) { Providers: testAccProviders, Steps: []resource.TestStep{ { - Config: user_full, + Config: userFull, Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("artifactory_user.foobar", "name", "dummy_user"), resource.TestCheckResourceAttr("artifactory_user.foobar", "email", "dummy@a.com"), diff --git a/pkg/artifactory/resource_artifactory_virtual_repository.go b/pkg/artifactory/resource_artifactory_virtual_repository.go index 981c081a..74460298 100644 --- a/pkg/artifactory/resource_artifactory_virtual_repository.go +++ b/pkg/artifactory/resource_artifactory_virtual_repository.go @@ -80,19 +80,19 @@ func unmarshalVirtualRepository(s *schema.ResourceData) *artifactory.VirtualRepo d := &ResourceData{s} repo := new(artifactory.VirtualRepository) - repo.Key = d.GetStringRef("key") + repo.Key = d.getStringRef("key") repo.RClass = artifactory.String("virtual") - repo.PackageType = d.GetStringRef("package_type") - repo.IncludesPattern = d.GetStringRef("includes_pattern") - repo.ExcludesPattern = d.GetStringRef("excludes_pattern") - repo.DebianTrivialLayout = d.GetBoolRef("debian_trivial_layout") - repo.ArtifactoryRequestsCanRetrieveRemoteArtifacts = d.GetBoolRef("artifactory_requests_can_retrieve_remote_artifacts") - repo.Repositories = d.GetListRef("repositories") - repo.Description = d.GetStringRef("description") - repo.Notes = d.GetStringRef("notes") - repo.KeyPair = d.GetStringRef("key_pair") - repo.PomRepositoryReferencesCleanupPolicy = d.GetStringRef("pom_repository_references_cleanup_policy") - repo.DefaultDeploymentRepo = d.GetStringRef("default_deployment_repo") + repo.PackageType = d.getStringRef("package_type") + repo.IncludesPattern = d.getStringRef("includes_pattern") + repo.ExcludesPattern = d.getStringRef("excludes_pattern") + repo.DebianTrivialLayout = d.getBoolRef("debian_trivial_layout") + repo.ArtifactoryRequestsCanRetrieveRemoteArtifacts = d.getBoolRef("artifactory_requests_can_retrieve_remote_artifacts") + repo.Repositories = d.getListRef("repositories") + repo.Description = d.getStringRef("description") + repo.Notes = d.getStringRef("notes") + repo.KeyPair = d.getStringRef("key_pair") + repo.PomRepositoryReferencesCleanupPolicy = d.getStringRef("pom_repository_references_cleanup_policy") + repo.DefaultDeploymentRepo = d.getStringRef("default_deployment_repo") return repo } diff --git a/pkg/artifactory/util.go b/pkg/artifactory/util.go index f92fd061..2fb85a04 100644 --- a/pkg/artifactory/util.go +++ b/pkg/artifactory/util.go @@ -9,44 +9,44 @@ import ( type ResourceData struct{ *schema.ResourceData } -func (d *ResourceData) GetStringRef(key string) *string { - if v, ok := d.GetOkExists(key); ok { +func (d *ResourceData) getStringRef(key string) *string { + if v, ok := d.GetOk(key); ok { return artifactory.String(v.(string)) } return nil } -func (d *ResourceData) GetBoolRef(key string) *bool { +func (d *ResourceData) getBoolRef(key string) *bool { if v, ok := d.GetOkExists(key); ok { return artifactory.Bool(v.(bool)) } return nil } -func (d *ResourceData) GetIntRef(key string) *int { +func (d *ResourceData) getIntRef(key string) *int { if v, ok := d.GetOkExists(key); ok { return artifactory.Int(v.(int)) } return nil } -func (d *ResourceData) GetSetRef(key string) *[]string { +func (d *ResourceData) getSetRef(key string) *[]string { if v, ok := d.GetOkExists(key); ok { - arr := CastToStringArr(v.(*schema.Set).List()) + arr := castToStringArr(v.(*schema.Set).List()) return &arr } return new([]string) } -func (d *ResourceData) GetListRef(key string) *[]string { +func (d *ResourceData) getListRef(key string) *[]string { if v, ok := d.GetOkExists(key); ok { - arr := CastToStringArr(v.([]interface{})) + arr := castToStringArr(v.([]interface{})) return &arr } return new([]string) } -func CastToStringArr(arr []interface{}) []string { +func castToStringArr(arr []interface{}) []string { cpy := make([]string, 0, len(arr)) for _, r := range arr { cpy = append(cpy, r.(string)) @@ -55,7 +55,7 @@ func CastToStringArr(arr []interface{}) []string { return cpy } -func CastToInterfaceArr(arr []string) []interface{} { +func castToInterfaceArr(arr []string) []interface{} { cpy := make([]interface{}, 0, len(arr)) for _, r := range arr { cpy = append(cpy, r) @@ -64,7 +64,7 @@ func CastToInterfaceArr(arr []string) []interface{} { return cpy } -func GetMD5Hash(o interface{}) string { +func getMD5Hash(o interface{}) string { if len(o.(string)) == 0 { // Don't hash empty strings return "" } @@ -74,3 +74,7 @@ func GetMD5Hash(o interface{}) string { hasher.Write([]byte("OQ9@#9i4$c8g$4^n%PKT8hUva3CC^5")) return hex.EncodeToString(hasher.Sum(nil)) } + +func mD5Diff(k, old, new string, d *schema.ResourceData) bool { + return old == new || getMD5Hash(old) == new +} diff --git a/scripts/artifactory.lic.enc b/scripts/artifactory.lic.enc new file mode 100644 index 00000000..425980f1 Binary files /dev/null and b/scripts/artifactory.lic.enc differ diff --git a/scripts/gofmtcheck.sh b/scripts/gofmtcheck.sh new file mode 100755 index 00000000..f43ceefe --- /dev/null +++ b/scripts/gofmtcheck.sh @@ -0,0 +1,13 @@ +#!/usr/bin/env bash + +# Check gofmt +echo "==> Checking that code complies with gofmt requirements..." +gofmt_files=$(find . -name '*.go' | grep -v vendor | xargs gofmt -l -s) +if [[ -n ${gofmt_files} ]]; then + echo 'gofmt needs running on the following files:' + echo "${gofmt_files}" + echo "You can use the command: \`make fmt\` to reformat code." + exit 1 +fi + +exit 0 \ No newline at end of file diff --git a/scripts/run-artifactory.sh b/scripts/run-artifactory.sh new file mode 100755 index 00000000..abd5b339 --- /dev/null +++ b/scripts/run-artifactory.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env sh + +set -euf + +docker run -i -t -d --rm -v "${PWD}/scripts/artifactory.lic:/artifactory_extra_conf/artifactory.lic:ro" -p8080:8081 --name artifactory docker.bintray.io/jfrog/artifactory-pro:6.6.5 + +echo "Waiting for Artifactory to start" +until curl --output /dev/null --silent --head --fail http://localhost:8080/artifactory/webapp/#/login; do + echo '.' + sleep 4 +done + +# Use decrypted passwords +curl -u admin:password --output /dev/null --silent --fail localhost:8080/artifactory/api/system/decrypt -X POST diff --git a/vendor/github.com/atlassian/go-artifactory/pkg/artifactory/repositories.go b/vendor/github.com/atlassian/go-artifactory/pkg/artifactory/repositories.go index fc9ed1fa..e8767245 100644 --- a/vendor/github.com/atlassian/go-artifactory/pkg/artifactory/repositories.go +++ b/vendor/github.com/atlassian/go-artifactory/pkg/artifactory/repositories.go @@ -354,7 +354,7 @@ func (s *RepositoriesService) DeleteVirtual(ctx context.Context, repo string) (* // Generic repo CRUD operations func (s *RepositoriesService) create(ctx context.Context, repo string, v interface{}) (*http.Response, error) { - path := fmt.Sprintf("/api/repositories/%s", repo) + path := fmt.Sprintf("/api/repositories/%v", repo) req, err := s.client.NewJSONEncodedRequest("PUT", path, v) if err != nil { return nil, err diff --git a/vendor/github.com/hashicorp/terraform/config/interpolate_funcs.go b/vendor/github.com/hashicorp/terraform/config/interpolate_funcs.go index 72be817a..b94fca88 100644 --- a/vendor/github.com/hashicorp/terraform/config/interpolate_funcs.go +++ b/vendor/github.com/hashicorp/terraform/config/interpolate_funcs.go @@ -1698,7 +1698,7 @@ func interpolationFuncRsaDecrypt() ast.Function { b, err := base64.StdEncoding.DecodeString(s) if err != nil { - return "", fmt.Errorf("Failed to decode input %q: cipher text must be base64-encoded", key) + return "", fmt.Errorf("Failed to decode input %q: cipher text must be base64-encoded", s) } block, _ := pem.Decode([]byte(key)) diff --git a/vendor/github.com/hashicorp/terraform/config/module/storage.go b/vendor/github.com/hashicorp/terraform/config/module/storage.go index c1588d68..4b828dcb 100644 --- a/vendor/github.com/hashicorp/terraform/config/module/storage.go +++ b/vendor/github.com/hashicorp/terraform/config/module/storage.go @@ -11,7 +11,6 @@ import ( getter "github.com/hashicorp/go-getter" "github.com/hashicorp/terraform/registry" "github.com/hashicorp/terraform/registry/regsrc" - "github.com/hashicorp/terraform/svchost/auth" "github.com/hashicorp/terraform/svchost/disco" "github.com/mitchellh/cli" ) @@ -64,22 +63,19 @@ type Storage struct { // StorageDir is the full path to the directory where all modules will be // stored. StorageDir string - // Services is a required *disco.Disco, which may have services and - // credentials pre-loaded. - Services *disco.Disco - // Creds optionally provides credentials for communicating with service - // providers. - Creds auth.CredentialsSource + // Ui is an optional cli.Ui for user output Ui cli.Ui + // Mode is the GetMode that will be used for various operations. Mode GetMode registry *registry.Client } -func NewStorage(dir string, services *disco.Disco, creds auth.CredentialsSource) *Storage { - regClient := registry.NewClient(services, creds, nil) +// NewStorage returns a new initialized Storage object. +func NewStorage(dir string, services *disco.Disco) *Storage { + regClient := registry.NewClient(services, nil) return &Storage{ StorageDir: dir, diff --git a/vendor/github.com/hashicorp/terraform/helper/logging/transport.go b/vendor/github.com/hashicorp/terraform/helper/logging/transport.go index 44779248..bddabe64 100644 --- a/vendor/github.com/hashicorp/terraform/helper/logging/transport.go +++ b/vendor/github.com/hashicorp/terraform/helper/logging/transport.go @@ -1,9 +1,12 @@ package logging import ( + "bytes" + "encoding/json" "log" "net/http" "net/http/httputil" + "strings" ) type transport struct { @@ -15,7 +18,7 @@ func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) { if IsDebugOrHigher() { reqData, err := httputil.DumpRequestOut(req, true) if err == nil { - log.Printf("[DEBUG] "+logReqMsg, t.name, string(reqData)) + log.Printf("[DEBUG] "+logReqMsg, t.name, prettyPrintJsonLines(reqData)) } else { log.Printf("[ERROR] %s API Request error: %#v", t.name, err) } @@ -29,7 +32,7 @@ func (t *transport) RoundTrip(req *http.Request) (*http.Response, error) { if IsDebugOrHigher() { respData, err := httputil.DumpResponse(resp, true) if err == nil { - log.Printf("[DEBUG] "+logRespMsg, t.name, string(respData)) + log.Printf("[DEBUG] "+logRespMsg, t.name, prettyPrintJsonLines(respData)) } else { log.Printf("[ERROR] %s API Response error: %#v", t.name, err) } @@ -42,6 +45,20 @@ func NewTransport(name string, t http.RoundTripper) *transport { return &transport{name, t} } +// prettyPrintJsonLines iterates through a []byte line-by-line, +// transforming any lines that are complete json into pretty-printed json. +func prettyPrintJsonLines(b []byte) string { + parts := strings.Split(string(b), "\n") + for i, p := range parts { + if b := []byte(p); json.Valid(b) { + var out bytes.Buffer + json.Indent(&out, b, "", " ") + parts[i] = out.String() + } + } + return strings.Join(parts, "\n") +} + const logReqMsg = `%s API Request Details: ---[ REQUEST ]--------------------------------------- %s diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/testing.go b/vendor/github.com/hashicorp/terraform/helper/resource/testing.go index 27bfc9b5..b97673fd 100644 --- a/vendor/github.com/hashicorp/terraform/helper/resource/testing.go +++ b/vendor/github.com/hashicorp/terraform/helper/resource/testing.go @@ -266,6 +266,15 @@ type TestStep struct { // below. PreConfig func() + // Taint is a list of resource addresses to taint prior to the execution of + // the step. Be sure to only include this at a step where the referenced + // address will be present in state, as it will fail the test if the resource + // is missing. + // + // This option is ignored on ImportState tests, and currently only works for + // resources in the root module path. + Taint []string + //--------------------------------------------------------------- // Test modes. One of the following groups of settings must be // set to determine what the test step will do. Ideally we would've @@ -409,6 +418,17 @@ func LogOutput(t TestT) (logOutput io.Writer, err error) { return } +// ParallelTest performs an acceptance test on a resource, allowing concurrency +// with other ParallelTest. +// +// Tests will fail if they do not properly handle conditions to allow multiple +// tests to occur against the same resource or service (e.g. random naming). +// All other requirements of the Test function also apply to this function. +func ParallelTest(t TestT, c TestCase) { + t.Parallel() + Test(t, c) +} + // Test performs an acceptance test on a resource. // // Tests are not run unless an environmental variable "TF_ACC" is @@ -1119,6 +1139,7 @@ type TestT interface { Fatal(args ...interface{}) Skip(args ...interface{}) Name() string + Parallel() } // This is set to true by unit tests to alter some behavior diff --git a/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go b/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go index 300a9ea6..033f1266 100644 --- a/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go +++ b/vendor/github.com/hashicorp/terraform/helper/resource/testing_config.go @@ -1,6 +1,7 @@ package resource import ( + "errors" "fmt" "log" "strings" @@ -21,6 +22,14 @@ func testStep( opts terraform.ContextOpts, state *terraform.State, step TestStep) (*terraform.State, error) { + // Pre-taint any resources that have been defined in Taint, as long as this + // is not a destroy step. + if !step.Destroy { + if err := testStepTaint(state, step); err != nil { + return state, err + } + } + mod, err := testModule(opts, step) if err != nil { return state, err @@ -154,3 +163,19 @@ func testStep( // Made it here? Good job test step! return state, nil } + +func testStepTaint(state *terraform.State, step TestStep) error { + for _, p := range step.Taint { + m := state.RootModule() + if m == nil { + return errors.New("no state") + } + rs, ok := m.Resources[p] + if !ok { + return fmt.Errorf("resource %q not found in state", p) + } + log.Printf("[WARN] Test: Explicitly tainting resource %q", p) + rs.Taint() + } + return nil +} diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/data_source_resource_shim.go b/vendor/github.com/hashicorp/terraform/helper/schema/data_source_resource_shim.go index 5a03d2d8..8d93750a 100644 --- a/vendor/github.com/hashicorp/terraform/helper/schema/data_source_resource_shim.go +++ b/vendor/github.com/hashicorp/terraform/helper/schema/data_source_resource_shim.go @@ -32,7 +32,7 @@ func DataSourceResourceShim(name string, dataSource *Resource) *Resource { // FIXME: Link to some further docs either on the website or in the // changelog, once such a thing exists. - dataSource.deprecationMessage = fmt.Sprintf( + dataSource.DeprecationMessage = fmt.Sprintf( "using %s as a resource is deprecated; consider using the data source instead", name, ) diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource.go index 8f726bfb..d3be2d61 100644 --- a/vendor/github.com/hashicorp/terraform/helper/schema/resource.go +++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource.go @@ -124,9 +124,7 @@ type Resource struct { Importer *ResourceImporter // If non-empty, this string is emitted as a warning during Validate. - // This is a private interface for now, for use by DataSourceResourceShim, - // and not for general use. (But maybe later...) - deprecationMessage string + DeprecationMessage string // Timeouts allow users to specify specific time durations in which an // operation should time out, to allow them to extend an action to suit their @@ -269,8 +267,8 @@ func (r *Resource) Diff( func (r *Resource) Validate(c *terraform.ResourceConfig) ([]string, []error) { warns, errs := schemaMap(r.Schema).Validate(c) - if r.deprecationMessage != "" { - warns = append(warns, r.deprecationMessage) + if r.DeprecationMessage != "" { + warns = append(warns, r.DeprecationMessage) } return warns, errs diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go index 22d57a5e..6cc01ee0 100644 --- a/vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go +++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource_data.go @@ -315,6 +315,7 @@ func (d *ResourceData) State() *terraform.InstanceState { mapW := &MapFieldWriter{Schema: d.schema} if err := mapW.WriteField(nil, rawMap); err != nil { + log.Printf("[ERR] Error writing fields: %s", err) return nil } diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go b/vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go index 92b891fc..7db3decc 100644 --- a/vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go +++ b/vendor/github.com/hashicorp/terraform/helper/schema/resource_diff.go @@ -231,7 +231,7 @@ func (d *ResourceDiff) UpdatedKeys() []string { // Note that this does not wipe an override. This function is only allowed on // computed keys. func (d *ResourceDiff) Clear(key string) error { - if err := d.checkKey(key, "Clear"); err != nil { + if err := d.checkKey(key, "Clear", true); err != nil { return err } @@ -287,7 +287,7 @@ func (d *ResourceDiff) diffChange(key string) (interface{}, interface{}, bool, b // // This function is only allowed on computed attributes. func (d *ResourceDiff) SetNew(key string, value interface{}) error { - if err := d.checkKey(key, "SetNew"); err != nil { + if err := d.checkKey(key, "SetNew", false); err != nil { return err } @@ -299,7 +299,7 @@ func (d *ResourceDiff) SetNew(key string, value interface{}) error { // // This function is only allowed on computed attributes. func (d *ResourceDiff) SetNewComputed(key string) error { - if err := d.checkKey(key, "SetNewComputed"); err != nil { + if err := d.checkKey(key, "SetNewComputed", false); err != nil { return err } @@ -535,12 +535,24 @@ func childAddrOf(child, parent string) bool { } // checkKey checks the key to make sure it exists and is computed. -func (d *ResourceDiff) checkKey(key, caller string) error { - s, ok := d.schema[key] - if !ok { +func (d *ResourceDiff) checkKey(key, caller string, nested bool) error { + var schema *Schema + if nested { + keyParts := strings.Split(key, ".") + schemaL := addrToSchema(keyParts, d.schema) + if len(schemaL) > 0 { + schema = schemaL[len(schemaL)-1] + } + } else { + s, ok := d.schema[key] + if ok { + schema = s + } + } + if schema == nil { return fmt.Errorf("%s: invalid key: %s", caller, key) } - if !s.Computed { + if !schema.Computed { return fmt.Errorf("%s only operates on computed keys - %s is not one", caller, key) } return nil diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/schema.go b/vendor/github.com/hashicorp/terraform/helper/schema/schema.go index f44010ea..0ea5aad5 100644 --- a/vendor/github.com/hashicorp/terraform/helper/schema/schema.go +++ b/vendor/github.com/hashicorp/terraform/helper/schema/schema.go @@ -199,7 +199,7 @@ type Schema struct { Sensitive bool } -// SchemaDiffSuppresFunc is a function which can be used to determine +// SchemaDiffSuppressFunc is a function which can be used to determine // whether a detected diff on a schema element is "valid" or not, and // suppress it from the plan if necessary. // diff --git a/vendor/github.com/hashicorp/terraform/helper/schema/set.go b/vendor/github.com/hashicorp/terraform/helper/schema/set.go index bb194ee6..cba28903 100644 --- a/vendor/github.com/hashicorp/terraform/helper/schema/set.go +++ b/vendor/github.com/hashicorp/terraform/helper/schema/set.go @@ -17,6 +17,12 @@ func HashString(v interface{}) int { return hashcode.String(v.(string)) } +// HashInt hashes integers. If you want a Set of integers, this is the +// SchemaSetFunc you want. +func HashInt(v interface{}) int { + return hashcode.String(strconv.Itoa(v.(int))) +} + // HashResource hashes complex structures that are described using // a *Resource. This is the default set implementation used when a set's // element type is a full resource. diff --git a/vendor/github.com/hashicorp/terraform/registry/client.go b/vendor/github.com/hashicorp/terraform/registry/client.go index aefed84a..a18e6b86 100644 --- a/vendor/github.com/hashicorp/terraform/registry/client.go +++ b/vendor/github.com/hashicorp/terraform/registry/client.go @@ -15,7 +15,6 @@ import ( "github.com/hashicorp/terraform/registry/regsrc" "github.com/hashicorp/terraform/registry/response" "github.com/hashicorp/terraform/svchost" - "github.com/hashicorp/terraform/svchost/auth" "github.com/hashicorp/terraform/svchost/disco" "github.com/hashicorp/terraform/version" ) @@ -37,19 +36,14 @@ type Client struct { // services is a required *disco.Disco, which may have services and // credentials pre-loaded. services *disco.Disco - - // Creds optionally provides credentials for communicating with service - // providers. - creds auth.CredentialsSource } -func NewClient(services *disco.Disco, creds auth.CredentialsSource, client *http.Client) *Client { +// NewClient returns a new initialized registry client. +func NewClient(services *disco.Disco, client *http.Client) *Client { if services == nil { - services = disco.NewDisco() + services = disco.New() } - services.SetCredentialsSource(creds) - if client == nil { client = httpclient.New() client.Timeout = requestTimeout @@ -60,20 +54,19 @@ func NewClient(services *disco.Disco, creds auth.CredentialsSource, client *http return &Client{ client: client, services: services, - creds: creds, } } -// Discover qeuries the host, and returns the url for the registry. -func (c *Client) Discover(host svchost.Hostname) *url.URL { - service := c.services.DiscoverServiceURL(host, serviceID) - if service == nil { - return nil +// Discover queries the host, and returns the url for the registry. +func (c *Client) Discover(host svchost.Hostname) (*url.URL, error) { + service, err := c.services.DiscoverServiceURL(host, serviceID) + if err != nil { + return nil, err } if !strings.HasSuffix(service.Path, "/") { service.Path += "/" } - return service + return service, nil } // Versions queries the registry for a module, and returns the available versions. @@ -83,9 +76,9 @@ func (c *Client) Versions(module *regsrc.Module) (*response.ModuleVersions, erro return nil, err } - service := c.Discover(host) - if service == nil { - return nil, fmt.Errorf("host %s does not provide Terraform modules", host) + service, err := c.Discover(host) + if err != nil { + return nil, err } p, err := url.Parse(path.Join(module.Module(), "versions")) @@ -137,11 +130,7 @@ func (c *Client) Versions(module *regsrc.Module) (*response.ModuleVersions, erro } func (c *Client) addRequestCreds(host svchost.Hostname, req *http.Request) { - if c.creds == nil { - return - } - - creds, err := c.creds.ForHost(host) + creds, err := c.services.CredentialsForHost(host) if err != nil { log.Printf("[WARN] Failed to get credentials for %s: %s (ignoring)", host, err) return @@ -160,9 +149,9 @@ func (c *Client) Location(module *regsrc.Module, version string) (string, error) return "", err } - service := c.Discover(host) - if service == nil { - return "", fmt.Errorf("host %s does not provide Terraform modules", host.ForDisplay()) + service, err := c.Discover(host) + if err != nil { + return "", err } var p *url.URL diff --git a/vendor/github.com/hashicorp/terraform/svchost/auth/credentials.go b/vendor/github.com/hashicorp/terraform/svchost/auth/credentials.go index 0bc6db4f..0372c160 100644 --- a/vendor/github.com/hashicorp/terraform/svchost/auth/credentials.go +++ b/vendor/github.com/hashicorp/terraform/svchost/auth/credentials.go @@ -42,6 +42,9 @@ type HostCredentials interface { // receiving credentials. The usual behavior of this method is to // add some sort of Authorization header to the request. PrepareRequest(req *http.Request) + + // Token returns the authentication token. + Token() string } // ForHost iterates over the contained CredentialsSource objects and diff --git a/vendor/github.com/hashicorp/terraform/svchost/auth/token_credentials.go b/vendor/github.com/hashicorp/terraform/svchost/auth/token_credentials.go index 8f771b0d..9358bcb6 100644 --- a/vendor/github.com/hashicorp/terraform/svchost/auth/token_credentials.go +++ b/vendor/github.com/hashicorp/terraform/svchost/auth/token_credentials.go @@ -18,3 +18,8 @@ func (tc HostCredentialsToken) PrepareRequest(req *http.Request) { } req.Header.Set("Authorization", "Bearer "+string(tc)) } + +// Token returns the authentication token. +func (tc HostCredentialsToken) Token() string { + return string(tc) +} diff --git a/vendor/github.com/hashicorp/terraform/svchost/disco/disco.go b/vendor/github.com/hashicorp/terraform/svchost/disco/disco.go index 144384e0..5de5b717 100644 --- a/vendor/github.com/hashicorp/terraform/svchost/disco/disco.go +++ b/vendor/github.com/hashicorp/terraform/svchost/disco/disco.go @@ -8,6 +8,7 @@ package disco import ( "encoding/json" "errors" + "fmt" "io" "io/ioutil" "log" @@ -17,24 +18,33 @@ import ( "time" cleanhttp "github.com/hashicorp/go-cleanhttp" + "github.com/hashicorp/terraform/httpclient" "github.com/hashicorp/terraform/svchost" "github.com/hashicorp/terraform/svchost/auth" ) const ( - discoPath = "/.well-known/terraform.json" - maxRedirects = 3 // arbitrary-but-small number to prevent runaway redirect loops - discoTimeout = 11 * time.Second // arbitrary-but-small time limit to prevent UI "hangs" during discovery - maxDiscoDocBytes = 1 * 1024 * 1024 // 1MB - to prevent abusive services from using loads of our memory + // Fixed path to the discovery manifest. + discoPath = "/.well-known/terraform.json" + + // Arbitrary-but-small number to prevent runaway redirect loops. + maxRedirects = 3 + + // Arbitrary-but-small time limit to prevent UI "hangs" during discovery. + discoTimeout = 11 * time.Second + + // 1MB - to prevent abusive services from using loads of our memory. + maxDiscoDocBytes = 1 * 1024 * 1024 ) -var httpTransport = cleanhttp.DefaultPooledTransport() // overridden during tests, to skip TLS verification +// httpTransport is overridden during tests, to skip TLS verification. +var httpTransport = cleanhttp.DefaultPooledTransport() // Disco is the main type in this package, which allows discovery on given // hostnames and caches the results by hostname to avoid repeated requests // for the same information. type Disco struct { - hostCache map[svchost.Hostname]Host + hostCache map[svchost.Hostname]*Host credsSrc auth.CredentialsSource // Transport is a custom http.RoundTripper to use. @@ -42,8 +52,18 @@ type Disco struct { Transport http.RoundTripper } -func NewDisco() *Disco { - return &Disco{} +// New returns a new initialized discovery object. +func New() *Disco { + return NewWithCredentialsSource(nil) +} + +// NewWithCredentialsSource returns a new discovery object initialized with +// the given credentials source. +func NewWithCredentialsSource(credsSrc auth.CredentialsSource) *Disco { + return &Disco{ + hostCache: make(map[svchost.Hostname]*Host), + credsSrc: credsSrc, + } } // SetCredentialsSource provides a credentials source that will be used to @@ -55,6 +75,15 @@ func (d *Disco) SetCredentialsSource(src auth.CredentialsSource) { d.credsSrc = src } +// CredentialsForHost returns a non-nil HostCredentials if the embedded source has +// credentials available for the host, and a nil HostCredentials if it does not. +func (d *Disco) CredentialsForHost(hostname svchost.Hostname) (auth.HostCredentials, error) { + if d.credsSrc == nil { + return nil, nil + } + return d.credsSrc.ForHost(hostname) +} + // ForceHostServices provides a pre-defined set of services for a given // host, which prevents the receiver from attempting network-based discovery // for the given host. Instead, the given services map will be returned @@ -65,20 +94,23 @@ func (d *Disco) SetCredentialsSource(src auth.CredentialsSource) { // discovery, yielding the same results as if the given map were published // at the host's default discovery URL, though using absolute URLs is strongly // recommended to make the configured behavior more explicit. -func (d *Disco) ForceHostServices(host svchost.Hostname, services map[string]interface{}) { - if d.hostCache == nil { - d.hostCache = map[svchost.Hostname]Host{} - } +func (d *Disco) ForceHostServices(hostname svchost.Hostname, services map[string]interface{}) { if services == nil { services = map[string]interface{}{} } - d.hostCache[host] = Host{ + transport := d.Transport + if transport == nil { + transport = httpTransport + } + d.hostCache[hostname] = &Host{ discoURL: &url.URL{ Scheme: "https", - Host: string(host), + Host: string(hostname), Path: discoPath, }, - services: services, + hostname: hostname.ForDisplay(), + services: services, + transport: transport, } } @@ -88,141 +120,148 @@ func (d *Disco) ForceHostServices(host svchost.Hostname, services map[string]int // // If a given hostname supports no Terraform services at all, a non-nil but // empty Host object is returned. When giving feedback to the end user about -// such situations, we say e.g. "the host doesn't provide a module -// registry", regardless of whether that is due to that service specifically -// being absent or due to the host not providing Terraform services at all, -// since we don't wish to expose the detail of whole-host discovery to an -// end-user. -func (d *Disco) Discover(host svchost.Hostname) Host { - if d.hostCache == nil { - d.hostCache = map[svchost.Hostname]Host{} - } - if cache, cached := d.hostCache[host]; cached { - return cache - } - - ret := d.discover(host) - d.hostCache[host] = ret - return ret +// such situations, we say "host does not provide a service", +// regardless of whether that is due to that service specifically being absent +// or due to the host not providing Terraform services at all, since we don't +// wish to expose the detail of whole-host discovery to an end-user. +func (d *Disco) Discover(hostname svchost.Hostname) (*Host, error) { + if host, cached := d.hostCache[hostname]; cached { + return host, nil + } + + host, err := d.discover(hostname) + if err != nil { + return nil, err + } + d.hostCache[hostname] = host + + return host, nil } // DiscoverServiceURL is a convenience wrapper for discovery on a given // hostname and then looking up a particular service in the result. -func (d *Disco) DiscoverServiceURL(host svchost.Hostname, serviceID string) *url.URL { - return d.Discover(host).ServiceURL(serviceID) +func (d *Disco) DiscoverServiceURL(hostname svchost.Hostname, serviceID string) (*url.URL, error) { + host, err := d.Discover(hostname) + if err != nil { + return nil, err + } + return host.ServiceURL(serviceID) } // discover implements the actual discovery process, with its result cached // by the public-facing Discover method. -func (d *Disco) discover(host svchost.Hostname) Host { +func (d *Disco) discover(hostname svchost.Hostname) (*Host, error) { discoURL := &url.URL{ Scheme: "https", - Host: string(host), + Host: hostname.String(), Path: discoPath, } - t := d.Transport - if t == nil { - t = httpTransport + transport := d.Transport + if transport == nil { + transport = httpTransport } client := &http.Client{ - Transport: t, + Transport: transport, Timeout: discoTimeout, CheckRedirect: func(req *http.Request, via []*http.Request) error { log.Printf("[DEBUG] Service discovery redirected to %s", req.URL) if len(via) > maxRedirects { - return errors.New("too many redirects") // (this error message will never actually be seen) + return errors.New("too many redirects") // this error will never actually be seen } return nil }, } req := &http.Request{ + Header: make(http.Header), Method: "GET", URL: discoURL, } + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", httpclient.UserAgentString()) - if d.credsSrc != nil { - creds, err := d.credsSrc.ForHost(host) - if err == nil { - if creds != nil { - creds.PrepareRequest(req) // alters req to include credentials - } - } else { - log.Printf("[WARN] Failed to get credentials for %s: %s (ignoring)", host, err) - } + creds, err := d.CredentialsForHost(hostname) + if err != nil { + log.Printf("[WARN] Failed to get credentials for %s: %s (ignoring)", hostname, err) } - - log.Printf("[DEBUG] Service discovery for %s at %s", host, discoURL) - - ret := Host{ - discoURL: discoURL, + if creds != nil { + // Update the request to include credentials. + creds.PrepareRequest(req) } + log.Printf("[DEBUG] Service discovery for %s at %s", hostname, discoURL) + resp, err := client.Do(req) if err != nil { - log.Printf("[WARN] Failed to request discovery document: %s", err) - return ret // empty + return nil, fmt.Errorf("Failed to request discovery document: %v", err) } - if resp.StatusCode != 200 { - log.Printf("[WARN] Failed to request discovery document: %s", resp.Status) - return ret // empty + defer resp.Body.Close() + + host := &Host{ + // Use the discovery URL from resp.Request in + // case the client followed any redirects. + discoURL: resp.Request.URL, + hostname: hostname.ForDisplay(), + transport: transport, } - // If the client followed any redirects, we will have a new URL to use - // as our base for relative resolution. - ret.discoURL = resp.Request.URL + // Return the host without any services. + if resp.StatusCode == 404 { + return host, nil + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("Failed to request discovery document: %s", resp.Status) + } contentType := resp.Header.Get("Content-Type") mediaType, _, err := mime.ParseMediaType(contentType) if err != nil { - log.Printf("[WARN] Discovery URL has malformed Content-Type %q", contentType) - return ret // empty + return nil, fmt.Errorf("Discovery URL has a malformed Content-Type %q", contentType) } if mediaType != "application/json" { - log.Printf("[DEBUG] Discovery URL returned Content-Type %q, rather than application/json", mediaType) - return ret // empty + return nil, fmt.Errorf("Discovery URL returned an unsupported Content-Type %q", mediaType) } - // (this doesn't catch chunked encoding, because ContentLength is -1 in that case...) + // This doesn't catch chunked encoding, because ContentLength is -1 in that case. if resp.ContentLength > maxDiscoDocBytes { // Size limit here is not a contractual requirement and so we may // adjust it over time if we find a different limit is warranted. - log.Printf("[WARN] Discovery doc response is too large (got %d bytes; limit %d)", resp.ContentLength, maxDiscoDocBytes) - return ret // empty + return nil, fmt.Errorf( + "Discovery doc response is too large (got %d bytes; limit %d)", + resp.ContentLength, maxDiscoDocBytes, + ) } - // If the response is using chunked encoding then we can't predict - // its size, but we'll at least prevent reading the entire thing into - // memory. + // If the response is using chunked encoding then we can't predict its + // size, but we'll at least prevent reading the entire thing into memory. lr := io.LimitReader(resp.Body, maxDiscoDocBytes) servicesBytes, err := ioutil.ReadAll(lr) if err != nil { - log.Printf("[WARN] Error reading discovery document body: %s", err) - return ret // empty + return nil, fmt.Errorf("Error reading discovery document body: %v", err) } var services map[string]interface{} err = json.Unmarshal(servicesBytes, &services) if err != nil { - log.Printf("[WARN] Failed to decode discovery document as a JSON object: %s", err) - return ret // empty + return nil, fmt.Errorf("Failed to decode discovery document as a JSON object: %v", err) } + host.services = services - ret.services = services - return ret + return host, nil } // Forget invalidates any cached record of the given hostname. If the host // has no cache entry then this is a no-op. -func (d *Disco) Forget(host svchost.Hostname) { - delete(d.hostCache, host) +func (d *Disco) Forget(hostname svchost.Hostname) { + delete(d.hostCache, hostname) } // ForgetAll is like Forget, but for all of the hostnames that have cache entries. func (d *Disco) ForgetAll() { - d.hostCache = nil + d.hostCache = make(map[svchost.Hostname]*Host) } diff --git a/vendor/github.com/hashicorp/terraform/svchost/disco/host.go b/vendor/github.com/hashicorp/terraform/svchost/disco/host.go index faf58220..7602e3f2 100644 --- a/vendor/github.com/hashicorp/terraform/svchost/disco/host.go +++ b/vendor/github.com/hashicorp/terraform/svchost/disco/host.go @@ -1,51 +1,264 @@ package disco import ( + "encoding/json" + "fmt" + "log" + "net/http" "net/url" + "os" + "strconv" + "strings" + "time" + + "github.com/hashicorp/go-version" + "github.com/hashicorp/terraform/httpclient" ) +const versionServiceID = "versions.v1" + +// Host represents a service discovered host. type Host struct { - discoURL *url.URL - services map[string]interface{} + discoURL *url.URL + hostname string + services map[string]interface{} + transport http.RoundTripper +} + +// Constraints represents the version constraints of a service. +type Constraints struct { + Service string `json:"service"` + Product string `json:"product"` + Minimum string `json:"minimum"` + Excluding []string `json:"excluding"` + Maximum string `json:"maximum"` +} + +// ErrServiceNotProvided is returned when the service is not provided. +type ErrServiceNotProvided struct { + hostname string + service string +} + +// Error returns a customized error message. +func (e *ErrServiceNotProvided) Error() string { + if e.hostname == "" { + return fmt.Sprintf("host does not provide a %s service", e.service) + } + return fmt.Sprintf("host %s does not provide a %s service", e.hostname, e.service) +} + +// ErrVersionNotSupported is returned when the version is not supported. +type ErrVersionNotSupported struct { + hostname string + service string + version string +} + +// Error returns a customized error message. +func (e *ErrVersionNotSupported) Error() string { + if e.hostname == "" { + return fmt.Sprintf("host does not support %s version %s", e.service, e.version) + } + return fmt.Sprintf("host %s does not support %s version %s", e.hostname, e.service, e.version) +} + +// ErrNoVersionConstraints is returned when checkpoint was disabled +// or the endpoint to query for version constraints was unavailable. +type ErrNoVersionConstraints struct { + disabled bool +} + +// Error returns a customized error message. +func (e *ErrNoVersionConstraints) Error() string { + if e.disabled { + return "checkpoint disabled" + } + return "unable to contact versions service" } // ServiceURL returns the URL associated with the given service identifier, // which should be of the form "servicename.vN". // -// A non-nil result is always an absolute URL with a scheme of either https -// or http. -// -// If the requested service is not supported by the host, this method returns -// a nil URL. -// -// If the discovery document entry for the given service is invalid (not a URL), -// it is treated as absent, also returning a nil URL. -func (h Host) ServiceURL(id string) *url.URL { - if h.services == nil { - return nil // no services supported for an empty Host +// A non-nil result is always an absolute URL with a scheme of either HTTPS +// or HTTP. +func (h *Host) ServiceURL(id string) (*url.URL, error) { + svc, ver, err := parseServiceID(id) + if err != nil { + return nil, err + } + + // No services supported for an empty Host. + if h == nil || h.services == nil { + return nil, &ErrServiceNotProvided{service: svc} } urlStr, ok := h.services[id].(string) if !ok { - return nil + // See if we have a matching service as that would indicate + // the service is supported, but not the requested version. + for serviceID := range h.services { + if strings.HasPrefix(serviceID, svc+".") { + return nil, &ErrVersionNotSupported{ + hostname: h.hostname, + service: svc, + version: ver.Original(), + } + } + } + + // No discovered services match the requested service. + return nil, &ErrServiceNotProvided{hostname: h.hostname, service: svc} } - ret, err := url.Parse(urlStr) + u, err := url.Parse(urlStr) if err != nil { - return nil + return nil, fmt.Errorf("Failed to parse service URL: %v", err) + } + + // Make relative URLs absolute using our discovery URL. + if !u.IsAbs() { + u = h.discoURL.ResolveReference(u) } - if !ret.IsAbs() { - ret = h.discoURL.ResolveReference(ret) // make absolute using our discovery doc URL + + if u.Scheme != "https" && u.Scheme != "http" { + return nil, fmt.Errorf("Service URL is using an unsupported scheme: %s", u.Scheme) } - if ret.Scheme != "https" && ret.Scheme != "http" { - return nil + if u.User != nil { + return nil, fmt.Errorf("Embedded username/password information is not permitted") } - if ret.User != nil { - // embedded username/password information is not permitted; credentials - // are handled out of band. - return nil + + // Fragment part is irrelevant, since we're not a browser. + u.Fragment = "" + + return h.discoURL.ResolveReference(u), nil +} + +// VersionConstraints returns the contraints for a given service identifier +// (which should be of the form "servicename.vN") and product. +// +// When an exact (service and version) match is found, the constraints for +// that service are returned. +// +// When the requested version is not provided but the service is, we will +// search for all alternative versions. If mutliple alternative versions +// are found, the contrains of the latest available version are returned. +// +// When a service is not provided at all an error will be returned instead. +// +// When checkpoint is disabled or when a 404 is returned after making the +// HTTP call, an ErrNoVersionConstraints error will be returned. +func (h *Host) VersionConstraints(id, product string) (*Constraints, error) { + svc, _, err := parseServiceID(id) + if err != nil { + return nil, err + } + + // Return early if checkpoint is disabled. + if disabled := os.Getenv("CHECKPOINT_DISABLE"); disabled != "" { + return nil, &ErrNoVersionConstraints{disabled: true} + } + + // No services supported for an empty Host. + if h == nil || h.services == nil { + return nil, &ErrServiceNotProvided{service: svc} + } + + // Try to get the service URL for the version service and + // return early if the service isn't provided by the host. + u, err := h.ServiceURL(versionServiceID) + if err != nil { + return nil, err + } + + // Check if we have an exact (service and version) match. + if _, ok := h.services[id].(string); !ok { + // If we don't have an exact match, we search for all matching + // services and then use the service ID of the latest version. + var services []string + for serviceID := range h.services { + if strings.HasPrefix(serviceID, svc+".") { + services = append(services, serviceID) + } + } + + if len(services) == 0 { + // No discovered services match the requested service. + return nil, &ErrServiceNotProvided{hostname: h.hostname, service: svc} + } + + // Set id to the latest service ID we found. + var latest *version.Version + for _, serviceID := range services { + if _, ver, err := parseServiceID(serviceID); err == nil { + if latest == nil || latest.LessThan(ver) { + id = serviceID + latest = ver + } + } + } + } + + // Set a default timeout of 1 sec for the versions request (in milliseconds) + timeout := 1000 + if _, err := strconv.Atoi(os.Getenv("CHECKPOINT_TIMEOUT")); err == nil { + timeout, _ = strconv.Atoi(os.Getenv("CHECKPOINT_TIMEOUT")) + } + + client := &http.Client{ + Transport: h.transport, + Timeout: time.Duration(timeout) * time.Millisecond, + } + + // Prepare the service URL by setting the service and product. + v := u.Query() + v.Set("product", product) + u.Path += id + u.RawQuery = v.Encode() + + // Create a new request. + req, err := http.NewRequest("GET", u.String(), nil) + if err != nil { + return nil, fmt.Errorf("Failed to create version constraints request: %v", err) + } + req.Header.Set("Accept", "application/json") + req.Header.Set("User-Agent", httpclient.UserAgentString()) + + log.Printf("[DEBUG] Retrieve version constraints for service %s and product %s", id, product) + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("Failed to request version constraints: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode == 404 { + return nil, &ErrNoVersionConstraints{disabled: false} + } + + if resp.StatusCode != 200 { + return nil, fmt.Errorf("Failed to request version constraints: %s", resp.Status) + } + + // Parse the constraints from the response body. + result := &Constraints{} + if err := json.NewDecoder(resp.Body).Decode(result); err != nil { + return nil, fmt.Errorf("Error parsing version constraints: %v", err) + } + + return result, nil +} + +func parseServiceID(id string) (string, *version.Version, error) { + parts := strings.SplitN(id, ".", 2) + if len(parts) != 2 { + return "", nil, fmt.Errorf("Invalid service ID format (i.e. service.vN): %s", id) + } + + version, err := version.NewVersion(parts[1]) + if err != nil { + return "", nil, fmt.Errorf("Invalid service version: %v", err) } - ret.Fragment = "" // fragment part is irrelevant, since we're not a browser - return h.discoURL.ResolveReference(ret) + return parts[0], version, nil } diff --git a/vendor/github.com/hashicorp/terraform/terraform/test_failure b/vendor/github.com/hashicorp/terraform/terraform/test_failure deleted file mode 100644 index 5d3ad1ac..00000000 --- a/vendor/github.com/hashicorp/terraform/terraform/test_failure +++ /dev/null @@ -1,9 +0,0 @@ ---- FAIL: TestContext2Plan_moduleProviderInherit (0.01s) - context_plan_test.go:552: bad: []string{"child"} -map[string]dag.Vertex{} -"module.middle.null" -map[string]dag.Vertex{} -"module.middle.module.inner.null" -map[string]dag.Vertex{} -"aws" -FAIL diff --git a/vendor/github.com/hashicorp/terraform/version/version.go b/vendor/github.com/hashicorp/terraform/version/version.go index 3b982dbd..f9bfa0fb 100644 --- a/vendor/github.com/hashicorp/terraform/version/version.go +++ b/vendor/github.com/hashicorp/terraform/version/version.go @@ -1,7 +1,7 @@ // The version package provides a location to set the release versions for all // packages to consume, without creating import cycles. // -// This pckage should not import any other terraform packages. +// This package should not import any other terraform packages. package version import ( @@ -11,7 +11,7 @@ import ( ) // The main version number that is being run at the moment. -const Version = "0.11.7" +var Version = "0.11.11" // A pre-release marker for the version. If this is "" (empty string) // then it means that it is a final release. Otherwise, this is a pre-release diff --git a/vendor/modules.txt b/vendor/modules.txt index 39bb284e..6e99649c 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -103,7 +103,7 @@ github.com/hashicorp/hil/parser github.com/hashicorp/hil/scanner # github.com/hashicorp/logutils v0.0.0-20150609070431-0dc08b1671f3 github.com/hashicorp/logutils -# github.com/hashicorp/terraform v0.11.7 +# github.com/hashicorp/terraform v0.11.11 github.com/hashicorp/terraform/plugin github.com/hashicorp/terraform/helper/hashcode github.com/hashicorp/terraform/helper/schema @@ -124,11 +124,11 @@ github.com/hashicorp/terraform/config/hcl2shim github.com/hashicorp/terraform/registry github.com/hashicorp/terraform/registry/regsrc github.com/hashicorp/terraform/registry/response -github.com/hashicorp/terraform/svchost/auth github.com/hashicorp/terraform/svchost/disco github.com/hashicorp/terraform/helper/config github.com/hashicorp/terraform/helper/logging github.com/hashicorp/terraform/svchost +github.com/hashicorp/terraform/svchost/auth # github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb github.com/hashicorp/yamux # github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8