diff --git a/.github/workflows/changelog.yml b/.github/workflows/changelog.yml index b59768b..73ddb61 100644 --- a/.github/workflows/changelog.yml +++ b/.github/workflows/changelog.yml @@ -19,3 +19,5 @@ on: jobs: changelog: uses: ansible-network/github_actions/.github/workflows/changelog.yml@main + with: + custom_paths: "provider/providerutils" diff --git a/.github/workflows/integration.yml b/.github/workflows/integration.yml index 35e99b3..4ad0990 100644 --- a/.github/workflows/integration.yml +++ b/.github/workflows/integration.yml @@ -32,4 +32,4 @@ jobs: - name: Run tests run: | - make test + make testacc diff --git a/.golangci.yml b/.golangci.yml index 1ea15a9..405d9dd 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,39 +1,74 @@ -linters: - enable-all: true +linters-settings: + goconst: + min-len: 2 + min-occurrences: 3 + gocyclo: + min-complexity: 20 + gosec: + excludes: + - G402 + govet: + check-shadowing: true + settings: + printf: + funcs: + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Infof + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Warnf + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Errorf + - (github.com/golangci/golangci-lint/pkg/logutils.Log).Fatalf + lll: + line-length: 160 + misspell: + locale: US + nolintlint: + allow-unused: false # report any unused nolint directives + require-explanation: false # don't require an explanation for nolint directives + require-specific: false # don't require nolint directives to be specific about which linter is being skipped + revive: + rules: + - name: unexported-return + disabled: true + - name: unused-parameter + stylecheck: + checks: ["all", "-ST1000", "-ST1003", "-ST1016", "-ST1020", "-ST1021", "-ST1022"] - disable: - # Abandoned, replaced by `unused`. - - deadcode - # Checks if package imports are in a list of acceptable packages. - - depguard - # Forces to handle more cases. - - exhaustivestruct # Deprecated, replaced by check below. - - exhaustruct - # Drop-in replacement of `golint`. - - revive - - golint - # Deprecated. - - ifshort - - interfacer - # Deprecated. Use govet `fieldalignment`. - - maligned - # Deprecated. Replaced by `revive`. - - nosnakecase - - scopelint - - structcheck - - varcheck - # Imports order - - gci - - funlen - # Tool for code clone detection. - - dupl - - rowserrcheck - - sqlclosecheck - # Finds wasted assignment statements. - - wastedassign - # Deeply nested if statements. - - nestif - # Same as `cyclop` linter. - - gocognit +linters: + disable-all: true + enable: + - dogsled + - errcheck + - exportloopref + - gocheckcompilerdirectives + - gochecknoinits + - goconst + - gocritic - gocyclo - - cyclop + - gofmt + - goimports + - gomnd + - goprintffuncname + - gosec + - gosimple + - govet + - ineffassign + - lll + - misspell + - nakedret + - noctx + - nolintlint + - revive + - staticcheck + - stylecheck + - typecheck + - unconvert + - unparam + - unused + - whitespace + +issues: + # Excluding configuration per-path, per-linter, per-text and per-source + exclude-rules: [] + +run: + timeout: 5m + skip-dirs: [] diff --git a/Makefile b/Makefile index c5da1a8..253bb65 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ build: go build -o terraform-provider-ansible -test: build - cd tests/terraform_tests && ./run_tftest.sh +testacc: + TF_ACC=1 go test -v ./... diff --git a/changelogs/fragments/20240417-refactor-code.yml b/changelogs/fragments/20240417-refactor-code.yml new file mode 100644 index 0000000..70e4c9c --- /dev/null +++ b/changelogs/fragments/20240417-refactor-code.yml @@ -0,0 +1,7 @@ +--- +minor_changes: + - Code refactoring in order for the provider to be easier to test. Aggregate redundant functions into `utils.go` (https://github.com/ansible/terraform-provider-ansible/pull/110). + - Add unit tests for critical functions (https://github.com/ansible/terraform-provider-ansible/pull/110). + - Replace integration tests by acceptance tests, this is the Terraform recommended way for testing providers (https://github.com/ansible/terraform-provider-ansible/pull/110). +bugfixes: + - resource/ansible_playbook - On `resourcePlaybookUpdate()` function, remove the call to the `Wait()` function as the command is start using `CombinedOutput` (https://github.com/ansible/terraform-provider-ansible/pull/110). \ No newline at end of file diff --git a/go.mod b/go.mod index 9b57a23..7b58a52 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/ansible/terraform-provider-ansible go 1.21 require ( - github.com/Jeffail/gabs v1.4.0 github.com/hashicorp/terraform-plugin-docs v0.18.0 - github.com/hashicorp/terraform-plugin-sdk/v2 v2.32.0 - github.com/stretchr/testify v1.8.1 + github.com/hashicorp/terraform-plugin-log v0.9.0 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 + github.com/hashicorp/terraform-plugin-testing v1.7.0 gopkg.in/ini.v1 v1.67.0 ) @@ -15,13 +15,12 @@ require ( github.com/Masterminds/goutils v1.1.1 // indirect github.com/Masterminds/semver/v3 v3.2.1 // indirect github.com/Masterminds/sprig/v3 v3.2.3 // indirect - github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect + github.com/ProtonMail/go-crypto v1.1.0-alpha.0 // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/armon/go-radix v1.0.0 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/cloudflare/circl v1.3.7 // indirect - github.com/davecgh/go-spew v1.1.1 // indirect github.com/fatih/color v1.16.0 // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/go-cmp v0.6.0 // indirect @@ -36,13 +35,12 @@ require ( github.com/hashicorp/go-plugin v1.6.0 // indirect github.com/hashicorp/go-uuid v1.0.3 // indirect github.com/hashicorp/go-version v1.6.0 // indirect - github.com/hashicorp/hc-install v0.6.2 // indirect - github.com/hashicorp/hcl/v2 v2.19.1 // indirect + github.com/hashicorp/hc-install v0.6.3 // indirect + github.com/hashicorp/hcl/v2 v2.20.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect github.com/hashicorp/terraform-exec v0.20.0 // indirect github.com/hashicorp/terraform-json v0.21.0 // indirect - github.com/hashicorp/terraform-plugin-go v0.21.0 // indirect - github.com/hashicorp/terraform-plugin-log v0.9.0 // indirect + github.com/hashicorp/terraform-plugin-go v0.22.0 // indirect github.com/hashicorp/terraform-registry-address v0.2.3 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect github.com/hashicorp/yamux v0.1.1 // indirect @@ -57,7 +55,6 @@ require ( github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/oklog/run v1.1.0 // indirect - github.com/pmezard/go-difflib v1.0.0 // indirect github.com/posener/complete v1.2.3 // indirect github.com/russross/blackfriday v1.6.0 // indirect github.com/shopspring/decimal v1.3.1 // indirect @@ -67,18 +64,18 @@ require ( github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/yuin/goldmark v1.6.0 // indirect github.com/yuin/goldmark-meta v1.1.0 // indirect - github.com/zclconf/go-cty v1.14.2 // indirect - golang.org/x/crypto v0.19.0 // indirect + github.com/zclconf/go-cty v1.14.3 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect - golang.org/x/mod v0.14.0 // indirect + golang.org/x/mod v0.15.0 // indirect golang.org/x/net v0.21.0 // indirect - golang.org/x/sys v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect + golang.org/x/tools v0.13.0 // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240213162025-012b6fc9bca9 // indirect google.golang.org/grpc v1.61.1 // indirect google.golang.org/protobuf v1.32.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v2 v2.3.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index aa785af..647216d 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= -github.com/Jeffail/gabs v1.4.0 h1://5fYRRTq1edjfIrQGvdkcd22pkYUrHZ5YC/H2GJVAo= -github.com/Jeffail/gabs v1.4.0/go.mod h1:6xMvQMK4k33lb7GUUpaAPh6nKMmemQeg5d4gn7/bOXc= github.com/Kunde21/markdownfmt/v3 v3.1.0 h1:KiZu9LKs+wFFBQKhrZJrFZwtLnCCWJahL+S+E/3VnM0= github.com/Kunde21/markdownfmt/v3 v3.1.0/go.mod h1:tPXN1RTyOzJwhfHoon9wUr4HGYmWgVxSQN6VBJDkrVc= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= @@ -13,8 +11,8 @@ github.com/Masterminds/sprig/v3 v3.2.3 h1:eL2fZNezLomi0uOLqjQoN6BfsDD+fyLtgbJMAj github.com/Masterminds/sprig/v3 v3.2.3/go.mod h1:rXcFaZ2zZbLRJv/xSysmlgIM1u11eBaRMhvYXJNkGuM= github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= -github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/ProtonMail/go-crypto v1.1.0-alpha.0 h1:nHGfwXmFvJrSR9xu8qL7BkO4DqTHXE9N5vPhgY2I+j0= +github.com/ProtonMail/go-crypto v1.1.0-alpha.0/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE= github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo= github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= @@ -26,8 +24,6 @@ github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQ github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= github.com/bufbuild/protocompile v0.4.0 h1:LbFKd2XowZvQ/kajzguUp2DC9UEIQhIq77fZZlaQsNA= github.com/bufbuild/protocompile v0.4.0/go.mod h1:3v93+mbWn/v3xzN+31nwkJfrEpAUwp+BagBSZWx+TP8= -github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= -github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU= github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= @@ -46,8 +42,8 @@ github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66D github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= -github.com/go-git/go-git/v5 v5.10.1 h1:tu8/D8i+TWxgKpzQ3Vc43e+kkhXqtsZCKI/egajKnxk= -github.com/go-git/go-git/v5 v5.10.1/go.mod h1:uEuHjxkHap8kAl//V5F/nNWwqIYtP/402ddd05mp0wg= +github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4= +github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= @@ -88,10 +84,10 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hc-install v0.6.2 h1:V1k+Vraqz4olgZ9UzKiAcbman9i9scg9GgSt/U3mw/M= -github.com/hashicorp/hc-install v0.6.2/go.mod h1:2JBpd+NCFKiHiu/yYCGaPyPHhZLxXTpz8oreHa/a3Ps= -github.com/hashicorp/hcl/v2 v2.19.1 h1://i05Jqznmb2EXqa39Nsvyan2o5XyMowW5fnCKW5RPI= -github.com/hashicorp/hcl/v2 v2.19.1/go.mod h1:ThLC89FV4p9MPW804KVbe/cEXoQ8NZEh+JtMeeGErHE= +github.com/hashicorp/hc-install v0.6.3 h1:yE/r1yJvWbtrJ0STwScgEnCanb0U9v7zp0Gbkmcoxqs= +github.com/hashicorp/hc-install v0.6.3/go.mod h1:KamGdbodYzlufbWh4r9NRo8y6GLHWZP2GBtdnms1Ln0= +github.com/hashicorp/hcl/v2 v2.20.0 h1:l++cRs/5jQOiKVvqXZm/P1ZEfVXJmvLS9WSVxkaeTb4= +github.com/hashicorp/hcl/v2 v2.20.0/go.mod h1:WmcD/Ym72MDOOx5F62Ly+leloeu6H7m0pG7VBiU6pQk= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.20.0 h1:DIZnPsqzPGuUnq6cH8jWcPunBfY+C+M8JyYF3vpnuEo= @@ -100,12 +96,14 @@ github.com/hashicorp/terraform-json v0.21.0 h1:9NQxbLNqPbEMze+S6+YluEdXgJmhQykRy github.com/hashicorp/terraform-json v0.21.0/go.mod h1:qdeBs11ovMzo5puhrRibdD6d2Dq6TyE/28JiU4tIQxk= github.com/hashicorp/terraform-plugin-docs v0.18.0 h1:2bINhzXc+yDeAcafurshCrIjtdu1XHn9zZ3ISuEhgpk= github.com/hashicorp/terraform-plugin-docs v0.18.0/go.mod h1:iIUfaJpdUmpi+rI42Kgq+63jAjI8aZVTyxp3Bvk9Hg8= -github.com/hashicorp/terraform-plugin-go v0.21.0 h1:VSjdVQYNDKR0l2pi3vsFK1PdMQrw6vGOshJXMNFeVc0= -github.com/hashicorp/terraform-plugin-go v0.21.0/go.mod h1:piJp8UmO1uupCvC9/H74l2C6IyKG0rW4FDedIpwW5RQ= +github.com/hashicorp/terraform-plugin-go v0.22.0 h1:1OS1Jk5mO0f5hrziWJGXXIxBrMe2j/B8E+DVGw43Xmc= +github.com/hashicorp/terraform-plugin-go v0.22.0/go.mod h1:mPULV91VKss7sik6KFEcEu7HuTogMLLO/EvWCuFkRVE= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.32.0 h1:7xdO9aOXVmhvMxNAq8UloyyqW0EEzyAY37llSTHJgjo= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.32.0/go.mod h1:LxQzs7AQl/5JE1IGFd6LX8E4A0InRJ/7s245gOmsejA= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 h1:qHprzXy/As0rxedphECBEQAh3R4yp6pKksKHcqZx5G8= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0/go.mod h1:H+8tjs9TjV2w57QFVSMBQacf8k/E1XwLXGCARgViC6A= +github.com/hashicorp/terraform-plugin-testing v1.7.0 h1:I6aeCyZ30z4NiI3tzyDoO6fS7YxP5xSL1ceOon3gTe8= +github.com/hashicorp/terraform-plugin-testing v1.7.0/go.mod h1:sbAreCleJNOCz+y5vVHV8EJkIWZKi/t4ndKiUjM9vao= github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI= github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= @@ -180,14 +178,10 @@ github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkU github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/vmihailenco/msgpack v3.3.3+incompatible/go.mod h1:fy3FlTQTDXWkZ7Bh6AcGMlsjHatGryHQYUTf1ShIgkk= @@ -204,34 +198,30 @@ github.com/yuin/goldmark v1.6.0 h1:boZcn2GTjpsynOsC0iJHnBWa4Bi0qzfJjthwauItG68= github.com/yuin/goldmark v1.6.0/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark-meta v1.1.0 h1:pWw+JLHGZe8Rk0EGsMVssiNb/AaPMHfSRszZeUeiOUc= github.com/yuin/goldmark-meta v1.1.0/go.mod h1:U4spWENafuA7Zyg+Lj5RqK/MF+ovMYtBvXi1lBb2VP0= -github.com/zclconf/go-cty v1.14.2 h1:kTG7lqmBou0Zkx35r6HJHUQTvaRPr5bIAf3AoHS0izI= -github.com/zclconf/go-cty v1.14.2/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= +github.com/zclconf/go-cty v1.14.3 h1:1JXy1XroaGrzZuG6X9dt7HL6s9AwbY+l4UNL8o5B6ho= +github.com/zclconf/go-cty v1.14.3/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= -golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME= golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.14.0 h1:dGoOF9QVLYng8IHTm7BAyWqCqSheQ5pYWGhzW00YJr0= -golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.15.0 h1:SernR4v+D55NyBH2QiEQrlBAnj1ECL6AGrA5+dPaMY8= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= -golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.5.0 h1:60k92dhOjHxJkrqnwsfl8KuaHbn/5dl0lUPUklKo3qE= +golang.org/x/sync v0.5.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -244,30 +234,25 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= -golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= -golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -294,6 +279,5 @@ gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRN gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/provider/provider_test.go b/provider/provider_test.go new file mode 100644 index 0000000..2c19b96 --- /dev/null +++ b/provider/provider_test.go @@ -0,0 +1,23 @@ +package provider_test + +import ( + "os/exec" + "testing" + + "github.com/ansible/terraform-provider-ansible/provider" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var TestAccProviders = map[string]*schema.Provider{ + "ansible": provider.Provider(), +} + +func testAccPreCheck(t *testing.T) { + // Ensure the required executable are present + requiredExecutables := []string{"ansible-playbook", "ansible-vault"} + for _, binFile := range requiredExecutables { + if _, validateBinPath := exec.LookPath(binFile); validateBinPath != nil { + t.Fatalf("couldn't find executable %s: %v", binFile, validateBinPath) + } + } +} diff --git a/provider/resource_group.go b/provider/resource_group.go index 214827b..6e2f37d 100644 --- a/provider/resource_group.go +++ b/provider/resource_group.go @@ -3,6 +3,7 @@ package provider import ( "context" + "github.com/ansible/terraform-provider-ansible/providerutils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -44,13 +45,17 @@ func resourceGroup() *schema.Resource { func resourceGroupCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - groupName, ok := data.Get("name").(string) + dataParser := providerutils.ResourceDataParser{ + Data: data, + Detail: "ansible_group", + } + // required settings + var groupName string + + dataParser.ReadString("name", &groupName) - if !ok { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [ansible-group]: couldn't get 'name'!", - }) + if dataParser.HasError() { + return dataParser.Diags } data.SetId(groupName) diff --git a/provider/resource_group_test.go b/provider/resource_group_test.go new file mode 100644 index 0000000..7131027 --- /dev/null +++ b/provider/resource_group_test.go @@ -0,0 +1,30 @@ +package provider_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccResourceGroup(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: TestAccProviders, + Steps: []resource.TestStep{ + { + Config: ` + resource "ansible_group" "main" { + name = "somegroup" + children = ["somechild"] + variables = { + hello = "from group!" + } + }`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("ansible_group.main", "name", "somegroup"), + resource.TestCheckResourceAttr("ansible_group.main", "id", "somegroup"), + ), + }, + }, + }) +} diff --git a/provider/resource_host.go b/provider/resource_host.go index 8c47641..a3ea3c0 100644 --- a/provider/resource_host.go +++ b/provider/resource_host.go @@ -3,6 +3,7 @@ package provider import ( "context" + "github.com/ansible/terraform-provider-ansible/providerutils" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -44,13 +45,17 @@ func resourceHost() *schema.Resource { func resourceHostCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { var diags diag.Diagnostics - hostName, ok := data.Get("name").(string) + dataParser := providerutils.ResourceDataParser{ + Data: data, + Detail: "ansible_host", + } + // required settings + var hostName string + + dataParser.ReadString("name", &hostName) - if !ok { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [ansible-group]: couldn't get 'name'!", - }) + if dataParser.HasError() { + return dataParser.Diags } data.SetId(hostName) diff --git a/provider/resource_host_test.go b/provider/resource_host_test.go new file mode 100644 index 0000000..cf46b31 --- /dev/null +++ b/provider/resource_host_test.go @@ -0,0 +1,30 @@ +package provider_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +func TestAccResourceHost(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: TestAccProviders, + Steps: []resource.TestStep{ + { + Config: ` + resource "ansible_host" "main" { + name = "localhost" + groups = ["some_group", "another_group"] + variables = { + greetings = "from host!" + some = "variable" + } + }`, + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("ansible_host.main", "id", "localhost"), + ), + }, + }, + }) +} diff --git a/provider/resource_playbook.go b/provider/resource_playbook.go index 89b5328..8392af4 100644 --- a/provider/resource_playbook.go +++ b/provider/resource_playbook.go @@ -3,7 +3,6 @@ package provider import ( "context" "fmt" - "log" "os/exec" "strings" "time" @@ -14,7 +13,7 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) -const ansiblePlaybook = "ansible-playbook" +const ansiblePlaybookBinary = "ansible-playbook" func resourcePlaybook() *schema.Resource { return &schema.Resource{ @@ -37,7 +36,7 @@ func resourcePlaybook() *schema.Resource { Type: schema.TypeString, Required: false, Optional: true, - Default: "ansible-playbook", + Default: ansiblePlaybookBinary, Description: "Path to ansible-playbook executable (binary).", }, @@ -209,496 +208,245 @@ func resourcePlaybook() *schema.Resource { } } -//nolint:maintidx -func resourcePlaybookCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - // required settings - playbook, okay := data.Get("playbook").(string) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'playbook'!", - Detail: ansiblePlaybook, - }) - } - - // optional settings - name, okay := data.Get("name").(string) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'name'!", - Detail: ansiblePlaybook, - }) - } - - verbosity, okay := data.Get("verbosity").(int) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'verbosity'!", - Detail: ansiblePlaybook, - }) - } - - tags, okay := data.Get("tags").([]interface{}) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'tags'!", - Detail: ansiblePlaybook, - }) - } - - limit, okay := data.Get("limit").([]interface{}) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'limit'!", - Detail: ansiblePlaybook, - }) - } - - checkMode, okay := data.Get("check_mode").(bool) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'check_mode'!", - Detail: ansiblePlaybook, - }) - } - - diffMode, okay := data.Get("diff_mode").(bool) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'diff_mode'!", - Detail: ansiblePlaybook, - }) - } - - forceHandlers, okay := data.Get("force_handlers").(bool) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'force_handlers'!", - Detail: ansiblePlaybook, - }) - } - - extraVars, okay := data.Get("extra_vars").(map[string]interface{}) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'extra_vars'!", - Detail: ansiblePlaybook, - }) - } +type PlaybookModel struct { + Playbook string + AnsiblePlaybookBinary string + Name string + Groups []string + Replayable bool + IgnorePlaybookFailure bool + Verbosity int + Tags []string + Limit []string + CheckMode bool + DiffMode bool + ForceHandlers bool + ExtraVars map[string]string + VarFiles []string + VaultFiles []string + VaultPasswordFile string + VaultID string +} - varFiles, okay := data.Get("var_files").([]interface{}) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'var_files'!", - Detail: ansiblePlaybook, - }) - } +func (d *PlaybookModel) ReadTerraformResourceData(data *schema.ResourceData) diag.Diagnostics { + dataParser := providerutils.ResourceDataParser{ + Data: data, + Detail: "ansible_playbook", + } + + dataParser.ReadString("ansible_playbook_binary", &d.AnsiblePlaybookBinary) + dataParser.ReadString("playbook", &d.Playbook) + dataParser.ReadString("name", &d.Name) + dataParser.ReadString("vault_password_file", &d.VaultPasswordFile) + dataParser.ReadString("vault_id", &d.VaultID) + dataParser.ReadInt("verbosity", &d.Verbosity) + dataParser.ReadBool("check_mode", &d.CheckMode) + dataParser.ReadBool("diff_mode", &d.DiffMode) + dataParser.ReadBool("force_handlers", &d.ForceHandlers) + dataParser.ReadBool("replayable", &d.Replayable) + dataParser.ReadBool("ignore_playbook_failure", &d.IgnorePlaybookFailure) + + dataParser.ReadStringList("tags", &d.Tags) + dataParser.ReadStringList("groups", &d.Groups) + dataParser.ReadStringList("limit", &d.Limit) + dataParser.ReadStringList("var_files", &d.VarFiles) + dataParser.ReadStringList("vault_files", &d.VaultFiles) + + dataParser.ReadMapString("extra_vars", &d.ExtraVars) + + return dataParser.Diags +} - vaultFiles, okay := data.Get("vault_files").([]interface{}) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'vault_files'!", - Detail: ansiblePlaybook, - }) +func appendArg[T bool | string](args []string, argKey string, data T) []string { + result := args + switch t := any(data).(type) { + case bool: + if t { + result = append(result, argKey) + } + case string: + if t != "" { + result = append(result, fmt.Sprintf("%s '%s'", argKey, t)) + } } + return result +} - vaultPasswordFile, okay := data.Get("vault_password_file").(string) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'vault_password_file'!", - Detail: ansiblePlaybook, - }) +func appendListArg(args []string, argKey string, data []string) []string { + result := args + if len(data) > 0 { + result = append(result, fmt.Sprintf("%s %s", argKey, strings.Join(data, ","))) } + return result +} - vaultID, okay := data.Get("vault_id").(string) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'vault_id'!", - Detail: ansiblePlaybook, - }) +func appendFilesListArg(args []string, argKey string, dataFiles []string) []string { + result := args + if len(dataFiles) > 0 { + for _, iFile := range dataFiles { + result = append(result, fmt.Sprintf("%s @%s", argKey, iFile)) + } } + return result +} - // Generate ID - data.SetId(time.Now().String()) - - /******************** - * PREP THE OPTIONS (ARGS) - */ - args := []string{} +func (d *PlaybookModel) BuildArgs() ([]string, diag.Diagnostics) { + var diags diag.Diagnostics + args := []string{fmt.Sprintf("-e hostname=%s", d.Name)} - verbose := providerutils.CreateVerboseSwitch(verbosity) + // Verbosity + verbose := providerutils.CreateVerboseSwitch(d.Verbosity) if verbose != "" { args = append(args, verbose) } - - if forceHandlers { - args = append(args, "--force-handlers") - } - - args = append(args, "-e", "hostname="+name) - - if len(tags) > 0 { - tmpTags := []string{} - - for _, tag := range tags { - tagStr, okay := tag.(string) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't assert type: string", - Detail: ansiblePlaybook, - }) - } - - tmpTags = append(tmpTags, tagStr) - } - - tagsStr := strings.Join(tmpTags, ",") - args = append(args, "--tags", tagsStr) - } - - if len(limit) > 0 { - tmpLimit := []string{} - - for _, l := range limit { - limitStr, okay := l.(string) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't assert type: string", - Detail: ansiblePlaybook, - }) - } - - tmpLimit = append(tmpLimit, limitStr) - } - - limitStr := strings.Join(tmpLimit, ",") - args = append(args, "--limit", limitStr) - } - - if checkMode { - args = append(args, "--check") - } - - if diffMode { - args = append(args, "--diff") - } - - if len(varFiles) != 0 { - for _, varFile := range varFiles { - varFileString, okay := varFile.(string) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't assert type: string", - Detail: ansiblePlaybook, - }) - } - - args = append(args, "-e", "@"+varFileString) - } - } - + // Force handlers + args = appendArg(args, "--force-handlers", d.ForceHandlers) + // Tags + args = appendListArg(args, "--tags", d.Tags) + // Limit + args = appendListArg(args, "--limit", d.Limit) + // Check mode + args = appendArg(args, "--check", d.CheckMode) + // Diff mode + args = appendArg(args, "--diff", d.DiffMode) + // Var Files + args = appendFilesListArg(args, "-e", d.VarFiles) // Ansible vault - if len(vaultFiles) != 0 { - for _, vaultFile := range vaultFiles { - vaultFileString, okay := vaultFile.(string) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't assert type: string", - Detail: ansiblePlaybook, - }) - } - - args = append(args, "-e", "@"+vaultFileString) - } - - args = append(args, "--vault-id") - - vaultIDArg := "" - if vaultID != "" { - vaultIDArg += vaultID - } - - if vaultPasswordFile != "" { - vaultIDArg += "@" + vaultPasswordFile - } else { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [ansible-playbook]: can't access vault file(s)! Missing 'vault_password_file'!", - Detail: ansiblePlaybook, - }) + args = appendFilesListArg(args, "-e", d.VaultFiles) + if len(d.VaultFiles) > 0 { + if d.VaultPasswordFile == "" { + diags = append(diags, diag.Errorf("can't access vault file(s)! Missing 'vault_password_file'!")...) + return nil, diags } - - args = append(args, vaultIDArg) + args = append(args, fmt.Sprintf("--vault-id %s@%s", d.VaultID, d.VaultPasswordFile)) } - if len(extraVars) != 0 { - for key, val := range extraVars { - tmpVal, okay := val.(string) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [ansible-playbook]: couldn't assert type: string", - Detail: ansiblePlaybook, - }) - } - - args = append(args, "-e", fmt.Sprintf("%s='%s'", key, tmpVal)) + // Extra Vars + if len(d.ExtraVars) > 0 { + for key, val := range d.ExtraVars { + args = append(args, fmt.Sprintf("-e %s='%s'", key, val)) } } - args = append(args, playbook) - - // set up the args - log.Print("[ANSIBLE ARGS]:") - log.Print(args) - - if err := data.Set("args", args); err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("ERROR [ansible-playbook]: couldn't set 'args'! %v", err), - Detail: ansiblePlaybook, - }) - } - - if err := data.Set("temp_inventory_file", ""); err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("ERROR [ansible-playbook]: couldn't set 'temp_inventory_file'! %v", err), - Detail: ansiblePlaybook, - }) - } - - diagsFromUpdate := resourcePlaybookUpdate(ctx, data, meta) - diags = append(diags, diagsFromUpdate...) + args = append(args, d.Playbook) - return diags + return args, diags } -func resourcePlaybookRead(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - - replayable, okay := data.Get("replayable").(bool) - - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'replayable'!", - Detail: ansiblePlaybook, - }) - } - // if (replayable == true) --> then we want to recreate (reapply) this resource: exits == false - // if (replayable == false) --> we don't want to recreate (reapply) this resource: exists == true - if replayable { - // make sure to do destroy of this resource. - resourcePlaybookDelete(ctx, data, meta) - } +func resourcePlaybookCreate(ctx context.Context, data *schema.ResourceData, _ interface{}) diag.Diagnostics { + // Generate ID + data.SetId(time.Now().String()) + return runPlaybook(ctx, data, true) +} - return diags +func resourcePlaybookRead(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { + return nil } func resourcePlaybookUpdate(ctx context.Context, data *schema.ResourceData, _ interface{}) diag.Diagnostics { - var diags diag.Diagnostics - - name, okay := data.Get("name").(string) - - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'name'!", - Detail: ansiblePlaybook, - }) - } - - groups, okay := data.Get("groups").([]interface{}) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'groups'!", - Detail: ansiblePlaybook, - }) - } + return runPlaybook(ctx, data, false) +} - ansiblePlaybookBinary, okay := data.Get("ansible_playbook_binary").(string) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'ansible_playbook_binary'!", - Detail: ansiblePlaybook, - }) - } +func runPlaybook(ctx context.Context, data *schema.ResourceData, fromCreate bool) diag.Diagnostics { + var resourceInfo PlaybookModel + var diags diag.Diagnostics - playbook, okay := data.Get("playbook").(string) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'playbook'!", - Detail: ansiblePlaybook, - }) + diags = append(diags, resourceInfo.ReadTerraformResourceData(data)...) + if diags.HasError() { + return diags } - tflog.Info(ctx, fmt.Sprintf("LOG [ansible-playbook]: playbook = %s", playbook)) - - ignorePlaybookFailure, okay := data.Get("ignore_playbook_failure").(bool) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'ignore_playbook_failure'!", - Detail: ansiblePlaybook, - }) + // replayable=true, the playbook is always executed + // replayable=false, the playbook is executed only in the first apply (fromCreate=true) + if !resourceInfo.Replayable && !fromCreate { + return diags } - argsTf, okay := data.Get("args").([]interface{}) - - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'args'!", - Detail: ansiblePlaybook, - }) + // Build command args + args, diagsArgs := resourceInfo.BuildArgs() + diags = append(diags, diagsArgs...) + if diags.HasError() { + return diags } - tempInventoryFile, okay := data.Get("temp_inventory_file").(string) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't get 'temp_inventory_file'!", - Detail: ansiblePlaybook, - }) - } + tflog.Info(ctx, fmt.Sprintf("Ansible ARGS = %v", args)) inventoryFileNamePrefix := ".inventory-" - - if tempInventoryFile == "" { - tempFileName, diagsFromUtils := providerutils.BuildPlaybookInventory( - inventoryFileNamePrefix+"*.ini", - name, - -1, - groups, - ) - tempInventoryFile = tempFileName - - diags = append(diags, diagsFromUtils...) - - if err := data.Set("temp_inventory_file", tempInventoryFile); err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [ansible-playbook]: couldn't set 'temp_inventory_file'!", - Detail: ansiblePlaybook, - }) - } - } + tempInventoryFile, diagsFromUtils := providerutils.BuildPlaybookInventory( + inventoryFileNamePrefix+"*.ini", + resourceInfo.Name, + -1, + resourceInfo.Groups, + ) + diags = append(diags, diagsFromUtils...) tflog.Debug(ctx, fmt.Sprintf("Temp Inventory File: %s", tempInventoryFile)) // ********************************* RUN PLAYBOOK ******************************** // Validate ansible-playbook binary - if _, validateBinPath := exec.LookPath(ansiblePlaybookBinary); validateBinPath != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("ERROR [ansible-playbook]: couldn't find executable %s", ansiblePlaybookBinary), - }) - } + tflog.Info(ctx, fmt.Sprintf("Look ansible-playbook binary path [%s]", resourceInfo.AnsiblePlaybookBinary)) + _, validateBinPath := exec.LookPath(resourceInfo.AnsiblePlaybookBinary) + if validateBinPath != nil { + errorDiags := diag.Errorf("couldn't find executable %s: %v", resourceInfo.AnsiblePlaybookBinary, validateBinPath) + diags = append(diags, errorDiags...) - if diags.HasError() { return diags } - args := []string{} - args = append(args, "-i", tempInventoryFile) - for _, arg := range argsTf { - tmpArg, okay := arg.(string) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [ansible-playbook]: couldn't assert type: string", - Detail: ansiblePlaybook, - }) - } - - args = append(args, tmpArg) - } - - tflog.Info(ctx, fmt.Sprintf("Running Command <%s %s>", ansiblePlaybookBinary, strings.Join(args, " "))) runAnsiblePlay := exec.Command(ansiblePlaybookBinary, args...) + tflog.Info(ctx, fmt.Sprintf("Running command <%s> and waiting for it to finish...", runAnsiblePlay.String())) runAnsiblePlayOut, runAnsiblePlayErr := runAnsiblePlay.CombinedOutput() - ansiblePlayStderrString := "" + tflog.Info(ctx, fmt.Sprintf("Command stdout = %s", runAnsiblePlayOut)) + + var ansiblePlayStderrString string if runAnsiblePlayErr != nil { playbookFailMsg := string(runAnsiblePlayOut) - if !ignorePlaybookFailure { + if !resourceInfo.IgnorePlaybookFailure { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: playbookFailMsg, - Detail: ansiblePlaybook, - }) - } else { - log.Print(playbookFailMsg) - diags = append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: playbookFailMsg, - Detail: ansiblePlaybook, + Detail: "ansible-playbook", }) + return diags } - ansiblePlayStderrString = runAnsiblePlayErr.Error() - } - // Set the ansible_playbook_stdout to the CLI stdout of call "ansible-playbook" command above - if err := data.Set("ansible_playbook_stdout", string(runAnsiblePlayOut)); err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't set 'ansible_playbook_stdout' ", - Detail: ansiblePlaybook, - }) - } + tflog.Info(ctx, playbookFailMsg) - // Set the ansible_playbook_stderr to the CLI stderr of call "ansible-playbook" command above - if err := data.Set("ansible_playbook_stderr", ansiblePlayStderrString); err != nil { diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [%s]: couldn't set 'ansible_playbook_stderr' ", - Detail: ansiblePlaybook, + Severity: diag.Warning, + Summary: playbookFailMsg, + Detail: "ansible-playbook", }) + + ansiblePlayStderrString = runAnsiblePlayErr.Error() } - tflog.Debug(ctx, fmt.Sprintf("LOG [ansible-playbook]: %s", runAnsiblePlayOut)) + tflog.Info(ctx, fmt.Sprintf("Command stderr = %s", ansiblePlayStderrString)) + + // Remove temporary file + diags = append(diags, providerutils.RemoveFile(tempInventoryFile)...) - // Wait for playbook execution to finish, then remove the temporary file - err := runAnsiblePlay.Wait() - if err != nil { - tflog.Error(ctx, fmt.Sprintf("LOG [ansible-playbook]: didn't wait for playbook to execute: %v", err)) + if err := data.Set("args", args); err != nil { + diags = append(diags, diag.Errorf("couldn't set 'args'! %v", err)...) } - diagsFromUtils := providerutils.RemoveFile(tempInventoryFile) + // Set the ansible_playbook_stdout to the CLI stdout of call "ansible-playbook" command above + if err := data.Set("ansible_playbook_stdout", string(runAnsiblePlayOut)); err != nil { + diags = append(diags, diag.Errorf("couldn't set 'ansible_playbook_stdout'")...) + } - diags = append(diags, diagsFromUtils...) + // Set the ansible_playbook_stderr to the CLI stderr of call "ansible-playbook" command above + if err := data.Set("ansible_playbook_stderr", ansiblePlayStderrString); err != nil { + diags = append(diags, diag.Errorf("couldn't set 'ansible_playbook_stderr' ")...) + } if err := data.Set("temp_inventory_file", ""); err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "ERROR [ansible-playbook]: couldn't set 'temp_inventory_file'!", - Detail: ansiblePlaybook, - }) + diags = append(diags, diag.Errorf("couldn't set 'temp_inventory_file'!")...) } // ******************************************************************************* @@ -707,12 +455,9 @@ func resourcePlaybookUpdate(ctx context.Context, data *schema.ResourceData, _ in // data.SetId(""), so when replayable is true, the resource gets created and then immediately deleted. // This causes provider to fail, therefore we essentially can't call data.SetId("") during a create task - // diagsFromRead := resourcePlaybookRead(ctx, data, meta) - // diags = append(diags, diagsFromRead...) return diags } -// On "terraform destroy", every resource removes its temporary inventory file. func resourcePlaybookDelete(_ context.Context, data *schema.ResourceData, _ interface{}) diag.Diagnostics { data.SetId("") diff --git a/provider/resource_playbook_test.go b/provider/resource_playbook_test.go new file mode 100644 index 0000000..e126ae5 --- /dev/null +++ b/provider/resource_playbook_test.go @@ -0,0 +1,368 @@ +package provider_test + +import ( + "errors" + "fmt" + "log" + "os" + "path/filepath" + "slices" + "strconv" + "testing" + + "github.com/ansible/terraform-provider-ansible/provider" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func testBooleanArg(t *testing.T, args []string, argKey string, argValue bool) { + if slices.Contains(args, argKey) && !argValue { + t.Errorf("arg (%s) found while it should not.", argKey) + } + + if !slices.Contains(args, argKey) && argValue { + t.Errorf("missing arg (%s).", argKey) + } +} + +func TestResourcePlaybookBuildArgs(t *testing.T) { + testTable := []struct { + name string + data provider.PlaybookModel + expected []string + }{ + { + name: "Verbose_v", + data: provider.PlaybookModel{ + Playbook: "playbook.yaml", + Name: acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum), + Verbosity: 1, + DiffMode: false, + CheckMode: true, + ForceHandlers: true, + VarFiles: []string{ + "secret_files.txt", "password_files.txt", + }, + VaultFiles: []string{ + "variables_files.yaml", + "configuration.yml", + }, + VaultPasswordFile: "vault_password.txt", + }, + expected: []string{ + "playbook.yaml", + "-v", + "-e @secret_files.txt", + "-e @password_files.txt", + "-e @variables_files.yaml", + "-e @configuration.yml", + }, + }, + { + name: "Verbose_vvv", + data: provider.PlaybookModel{ + Playbook: "another.yml", + Name: acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum), + Verbosity: 3, + DiffMode: true, + CheckMode: false, + ForceHandlers: false, + Tags: []string{ + "some_tag", "another_tag", + }, + Limit: []string{ + "terraform", "redhat", "fedora", + }, + ExtraVars: map[string]string{ + "first": "variable", + "second": "another_variable", + }, + VaultPasswordFile: "vault_password.txt", + VaultID: "ansible-test-vault-id", + }, + expected: []string{ + "another.yml", + "-vvv", + "--tags some_tag,another_tag", + "--limit terraform,redhat,fedora", + "-e first='variable'", + "-e second='another_variable'", + }, + }, + } + + for _, test := range testTable { + t.Run(test.name, func(t *testing.T) { + args, diags := test.data.BuildArgs() + if diags.HasError() { + for i := range diags { + if diags[i].Severity == diag.Error { + t.Fatalf("Summary = %s - Detail = %s", diags[i].Summary, diags[i].Detail) + } + } + } + + // Force handlers + testBooleanArg(t, args, "--force-handlers", test.data.ForceHandlers) + // Check mode + testBooleanArg(t, args, "--check", test.data.CheckMode) + // Diff mode + testBooleanArg(t, args, "--diff", test.data.DiffMode) + + // Ansible vault + vault_arg := fmt.Sprintf("--vault-id %s@%s", test.data.VaultID, test.data.VaultPasswordFile) + if len(test.data.VaultFiles) > 0 { + if !slices.Contains(args, vault_arg) { + t.Errorf("Arg (%s) is missing from arguments list %v", vault_arg, args) + } + } else if slices.Contains(args, vault_arg) { + t.Errorf("arg (%s) is present from arguments list %v while it should not", vault_arg, args) + } + + expected := test.expected + expected = append(expected, []string{fmt.Sprintf("-e hostname=%s", test.data.Name)}...) + for _, arg := range expected { + if !slices.Contains(args, arg) { + t.Errorf("Arg (%s) is missing from arguments list %v", arg, args) + } + } + }) + } +} + +type TestResourcePlaybookConfig struct { + PlaybookFile string + Name string + Groups []string + Replayable bool + IgnorePlaybookFailure bool + Verbosity int + Tags []string + Limit []string + CheckMode bool + DiffMode bool + ForceHandlers bool + ExtraVars map[string]string + VarFiles []string + VaultFiles []string + VaultPasswordFile string + VaultID string +} + +func TestAccResourcePlaybook_Minimal(t *testing.T) { + dirName, err := os.MkdirTemp("", "terraform_resource_playbook_minimal_*") + + if err != nil { + log.Fatal(err) + } + + defer os.RemoveAll(dirName) + + playbookFile := filepath.Join(dirName, "playbook.yaml") + testFile := filepath.Join(dirName, "iteration.txt") + + resourceConfig := TestResourcePlaybookConfig{ + PlaybookFile: playbookFile, + Name: acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum), + Replayable: true, + Verbosity: 1, + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: TestAccProviders, + Steps: []resource.TestStep{ + { + Config: resourceConfig.createTestConfig(testFile), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("ansible_playbook.minimal", "name", resourceConfig.Name), + resource.TestCheckResourceAttr("ansible_playbook.minimal", "ansible_playbook_binary", "ansible-playbook"), + resource.TestCheckResourceAttr("ansible_playbook.minimal", "replayable", "true"), + testAccCheckIncrement(testFile, "1"), + ), + }, + }, + }) +} + +func TestAccResourcePlaybook_NotReplayable(t *testing.T) { + dirName, err := os.MkdirTemp("", "terraform_resource_playbook_not_replayable_*") + + if err != nil { + log.Fatal(err) + } + + defer os.RemoveAll(dirName) + + playbookFile := filepath.Join(dirName, "playbook.yaml") + testFile := filepath.Join(dirName, "iteration.txt") + + resourceInitial := TestResourcePlaybookConfig{ + PlaybookFile: playbookFile, + Name: acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum), + Replayable: false, + Verbosity: 1, + } + + resourceUpdate := TestResourcePlaybookConfig{ + PlaybookFile: playbookFile, + Name: acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum), + Replayable: false, + Verbosity: 2, + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: TestAccProviders, + Steps: []resource.TestStep{ + { + // Run playbook + Config: resourceInitial.createTestConfig(testFile), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("ansible_playbook.minimal", "name", resourceInitial.Name), + resource.TestCheckResourceAttr("ansible_playbook.minimal", "ansible_playbook_binary", "ansible-playbook"), + resource.TestCheckResourceAttr("ansible_playbook.minimal", "replayable", "false"), + testAccCheckIncrement(testFile, "1"), + ), + }, + { + // Run once again and ensure playbook was not replayed + Config: resourceUpdate.createTestConfig(testFile), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("ansible_playbook.minimal", "name", resourceUpdate.Name), + resource.TestCheckResourceAttr("ansible_playbook.minimal", "ansible_playbook_binary", "ansible-playbook"), + resource.TestCheckResourceAttr("ansible_playbook.minimal", "replayable", "false"), + testAccCheckIncrement(testFile, "1"), + ), + }, + }, + }) +} + +func TestAccResourcePlaybook_Replayable(t *testing.T) { + dirName, err := os.MkdirTemp("", "terraform_resource_playbook_replayable_*") + + if err != nil { + log.Fatal(err) + } + + defer os.RemoveAll(dirName) + + playbookFile := filepath.Join(dirName, "playbook.yaml") + testFile := filepath.Join(dirName, "iteration.txt") + + resourceInitial := TestResourcePlaybookConfig{ + PlaybookFile: playbookFile, + Name: acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum), + Replayable: true, + Verbosity: 1, + } + + resourceUpdate := TestResourcePlaybookConfig{ + PlaybookFile: playbookFile, + Name: acctest.RandStringFromCharSet(10, acctest.CharSetAlphaNum), + Replayable: true, + Verbosity: 2, + } + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: TestAccProviders, + Steps: []resource.TestStep{ + { + // Run playbook + Config: resourceInitial.createTestConfig(testFile), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("ansible_playbook.minimal", "name", resourceInitial.Name), + resource.TestCheckResourceAttr("ansible_playbook.minimal", "ansible_playbook_binary", "ansible-playbook"), + resource.TestCheckResourceAttr("ansible_playbook.minimal", "replayable", "true"), + testAccCheckIncrement(testFile, "1"), + ), + }, + { + // Run once again and ensure playbook was not replayed + Config: resourceUpdate.createTestConfig(testFile), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("ansible_playbook.minimal", "name", resourceUpdate.Name), + resource.TestCheckResourceAttr("ansible_playbook.minimal", "ansible_playbook_binary", "ansible-playbook"), + resource.TestCheckResourceAttr("ansible_playbook.minimal", "replayable", "true"), + testAccCheckIncrement(testFile, "2"), + ), + }, + }, + }) +} + +func writeFile(filePath string, content string) error { + // create file in case it does not already exists + if _, err := os.Stat(filePath); errors.Is(err, os.ErrNotExist) { + fileHandler, err := os.Create(filePath) + if err != nil { + return err + } + + _, err = fileHandler.WriteString(content) + + if err != nil { + return err + } + if err = fileHandler.Close(); err != nil { + return err + } + } + + return nil +} + +// createTestConfig returns a configuration for an ansible_playbook resource. +func (d *TestResourcePlaybookConfig) createTestConfig(testFile string) string { + // create test file + if err := writeFile(testFile, "0"); err != nil { + log.Fatal(err) + } + + // create playbook file + playbookContent := fmt.Sprintf(` +- hosts: localhost + gather_facts: false + + tasks: + - set_fact: + content: "{{ lookup('file', '%s') }}" + + - name: Increment content + copy: + dest: '%s' + content: "{{ content | int + 1 }}" +`, testFile, testFile) + if err := writeFile(d.PlaybookFile, playbookContent); err != nil { + log.Fatal(err) + } + + // Create terraform configuration + return fmt.Sprintf(` +resource "ansible_playbook" "minimal" { + name = "%s" + playbook = "%s" + replayable = "%s" + verbosity = %d +}`, d.Name, d.PlaybookFile, strconv.FormatBool(d.Replayable), d.Verbosity) +} + +func testAccCheckIncrement(testFile string, expectedValue string) func(s *terraform.State) error { + return func(_ *terraform.State) error { + var content []byte + var err error + + if content, err = os.ReadFile(testFile); err != nil { + return err + } + if string(content) != expectedValue { + return fmt.Errorf("Data differ, expected [%s] found [%s]", expectedValue, string(content)) + } + + return nil + } +} diff --git a/provider/resource_vault.go b/provider/resource_vault.go index 414776c..f46ac76 100644 --- a/provider/resource_vault.go +++ b/provider/resource_vault.go @@ -3,14 +3,19 @@ package provider import ( "context" "fmt" - "log" "os/exec" "github.com/ansible/terraform-provider-ansible/providerutils" + "github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +const ( + ansibleVault = "ansible_vault" + ansibleVaultBinary = "ansible-vault" +) + func resourceVault() *schema.Resource { return &schema.Resource{ CreateContext: resourceVaultCreate, @@ -58,121 +63,99 @@ func resourceVault() *schema.Resource { } func resourceVaultCreate(ctx context.Context, data *schema.ResourceData, meta interface{}) diag.Diagnostics { - var diags diag.Diagnostics - - vaultFile, okay := data.Get("vault_file").(string) - - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "WARNING [ansible-vault]: couldn't get 'vault_file'!", - }) + dataParser := providerutils.ResourceDataParser{ + Data: data, + Detail: ansibleVault, } + // required settings + var vaultFile, vaultPasswordFile, vaultID string - vaultPasswordFile, okay := data.Get("vault_password_file").(string) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "WARNING [ansible-vault]: couldn't get 'vault_password_file'!", - }) - } + dataParser.ReadString("vault_file", &vaultFile) + dataParser.ReadString("vault_password_file", &vaultPasswordFile) + dataParser.ReadString("vault_id", &vaultID) - vaultID, okay := data.Get("vault_id").(string) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "WARNING [ansible-vault]: couldn't get 'vault_id'!", - }) + if dataParser.HasError() { + return dataParser.Diags } data.SetId(vaultFile) - var args interface{} + args := []string{"view"} // Compute arguments (args) if vaultID != "" { - args = []string{ - "view", - "--vault-id", - vaultID + "@" + vaultPasswordFile, - vaultFile, - } + args = append(args, []string{"--vault-id", fmt.Sprintf("%s@%s", vaultID, vaultPasswordFile), vaultFile}...) } else { - args = []string{ - "view", - "--vault-password-file", - vaultPasswordFile, - vaultFile, - } + args = append(args, []string{"--vault-password-file", vaultPasswordFile, vaultFile}...) } - log.Print("LOG [ansible-vault]: ARGS") - log.Print(args) + tflog.Info(ctx, fmt.Sprintf("ARGS = %v", args)) + var diags diag.Diagnostics if err := data.Set("args", args); err != nil { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, - Summary: fmt.Sprintf("ERROR [ansible-vault]: couldn't calculate 'args' variable! %s", err), - Detail: ansiblePlaybook, + Summary: fmt.Sprintf("couldn't calculate 'args' variable! %s", err), + Detail: ansibleVault, }) + + return diags } - diagsFromRead := resourceVaultRead(ctx, data, meta) - diags = append(diags, diagsFromRead...) + diags = append(diags, resourceVaultRead(ctx, data, meta)...) return diags } -func resourceVaultRead(_ context.Context, data *schema.ResourceData, _ interface{}) diag.Diagnostics { +func resourceVaultRead(ctx context.Context, data *schema.ResourceData, _ interface{}) diag.Diagnostics { var diags diag.Diagnostics - vaultFile, okay := data.Get("vault_file").(string) - - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: "WARNING [ansible-vault]: couldn't get 'vault_file'!", - }) + dataParser := providerutils.ResourceDataParser{ + Data: data, + Detail: ansibleVault, } - vaultPasswordFile, okay := data.Get("vault_password_file").(string) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: "WARNING [ansible-vault]: couldn't get 'vault_password_file'!", - }) - } + // required settings + var vaultFile, vaultPasswordFile string + var args []string - argsTerraform, okay := data.Get("args").([]interface{}) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Warning, - Summary: "WARNING [ansible-vault]: couldn't get 'args'!", - }) + dataParser.ReadString("vault_file", &vaultFile) + dataParser.ReadString("vault_password_file", &vaultPasswordFile) + dataParser.ReadStringList("args", &args) + + if dataParser.HasError() { + return dataParser.Diags } - log.Printf("LOG [ansible-vault]: vault_file = %s, vault_password_file = %s\n", vaultFile, vaultPasswordFile) + tflog.Info(ctx, fmt.Sprintf("vault_file = %s, vault_password_file = %s\n", vaultFile, vaultPasswordFile)) - args, diagsFromUtils := providerutils.InterfaceToString(argsTerraform) + // Validate ansible-vault binary + _, validateBinPath := exec.LookPath(ansibleVaultBinary) + if validateBinPath != nil { + diags = append(diags, diag.Diagnostic{ + Severity: diag.Error, + Summary: fmt.Sprintf("[ansible-vault]: couldn't find executable %s: %v", ansibleVaultBinary, validateBinPath), + }) - diags = append(diags, diagsFromUtils...) + return diags + } - cmd := exec.Command("ansible-vault", args...) + cmd := exec.Command(ansibleVaultBinary, args...) yamlString, err := cmd.CombinedOutput() if err != nil { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, Summary: string(yamlString), - Detail: ansiblePlaybook, + Detail: ansibleVault, }) } if err := data.Set("yaml", string(yamlString)); err != nil { diags = append(diags, diag.Diagnostic{ Severity: diag.Error, - Summary: fmt.Sprintf("ERROR [ansible-vault]: couldn't calculate 'yaml' variable! %s", err), - Detail: ansiblePlaybook, + Summary: fmt.Sprintf("[ansible-vault]: couldn't calculate 'yaml' variable! %s", err), + Detail: ansibleVault, }) } diff --git a/provider/resource_vault_test.go b/provider/resource_vault_test.go new file mode 100644 index 0000000..25b3be5 --- /dev/null +++ b/provider/resource_vault_test.go @@ -0,0 +1,97 @@ +package provider_test + +import ( + "fmt" + "log" + "os" + "os/exec" + "path/filepath" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" +) + +const ansibleVaultContent = ` +some: ansible_variable +another: testing_variable_23@ +` + +func TestAccResourceVault(t *testing.T) { + // Create temporary directory + vaultDirName, err := os.MkdirTemp("", "*-vault") + if err != nil { + log.Fatal(err) + } + defer os.RemoveAll(vaultDirName) + vaultPasswordFile, vaultFile, vaultID := initTestConfiguration(vaultDirName) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: TestAccProviders, + Steps: []resource.TestStep{ + { + Config: fmt.Sprintf(` + resource "ansible_vault" "secrets" { + vault_password_file = "%s" + vault_file = "%s" + vault_id = "%s" + }`, vaultPasswordFile, vaultFile, vaultID), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("ansible_vault.secrets", "id", vaultFile), + resource.TestCheckResourceAttr("ansible_vault.secrets", "yaml", ansibleVaultContent), + resource.TestCheckResourceAttr("ansible_vault.secrets", "args.0", "view"), + resource.TestCheckResourceAttr("ansible_vault.secrets", "args.1", "--vault-id"), + resource.TestCheckResourceAttr("ansible_vault.secrets", "args.2", fmt.Sprintf("%s@%s", vaultID, vaultPasswordFile)), + resource.TestCheckResourceAttr("ansible_vault.secrets", "args.3", vaultFile), + ), + }, + }, + }) +} + +func createFile(path string, content string) { + fileHandler, err := os.Create(path) + if err != nil { + log.Fatal(err) + } + _, err = fileHandler.WriteString(content) + if err != nil { + log.Fatal(err) + } + if err = fileHandler.Close(); err != nil { + log.Fatal(err) + } +} + +// This function creates the following resources +// - A vault password file used to encrypt the vault file +// - A vault encrypted file +// - A vault id use to encrypt the file. +func initTestConfiguration(vaultDirName string) (string, string, string) { + vaultPasswordFile := filepath.Join(vaultDirName, "vault_password") + vaultFile := filepath.Join(vaultDirName, "vault.yaml") + + // Create vault file + createFile(vaultFile, ansibleVaultContent) + + // Create vault password file + createFile(vaultPasswordFile, acctest.RandString(30)) + + // Encrypt the vault file + vaultId := acctest.RandomWithPrefix(acctest.RandString(10)) + args := []string{ + "encrypt", + vaultFile, + "--vault-password-file", + vaultPasswordFile, + "--vault-id", + vaultId, + } + cmd := exec.Command("ansible-vault", args...) + _, err := cmd.CombinedOutput() + if err != nil { + log.Fatalf("Failed to encrypt vault file. Command = [%s] Error = [%v]", cmd.String(), err) + } + return vaultPasswordFile, vaultFile, vaultId +} diff --git a/providerutils/utils.go b/providerutils/utils.go index f4bdf51..4435158 100644 --- a/providerutils/utils.go +++ b/providerutils/utils.go @@ -2,13 +2,14 @@ package providerutils import ( "fmt" - "log" "os" "path/filepath" + "reflect" "strconv" "strings" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "gopkg.in/ini.v1" ) @@ -18,26 +19,6 @@ import ( const DefaultHostGroup = "default" -func InterfaceToString(arr []interface{}) ([]string, diag.Diagnostics) { - var diags diag.Diagnostics - - result := []string{} - - for _, val := range arr { - tmpVal, ok := val.(string) - if !ok { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Error: couldn't parse value to string!", - }) - } - - result = append(result, tmpVal) - } - - return result, diags -} - // Create a "verbpse" switch // example: verbosity = 2 --> verbose_switch = "-vv" func CreateVerboseSwitch(verbosity int) string { @@ -60,29 +41,22 @@ func BuildPlaybookInventory( inventoryDest string, hostname string, port int, - hostgroups []interface{}, + hostgroups []string, ) (string, diag.Diagnostics) { var diags diag.Diagnostics // Check if inventory file is already present // if not, create one fileInfo, err := os.CreateTemp("", inventoryDest) if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("Fail to create inventory file: %v", err), - }) + diags = append(diags, diag.Errorf("Fail to create inventory file: %v", err)...) } tempFileName := fileInfo.Name() - log.Printf("Inventory %s was created", fileInfo.Name()) // Then, read inventory and add desired settings to it inventory, err := ini.Load(tempFileName) if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("Fail to read inventory: %v", err), - }) + diags = append(diags, diag.Errorf("Fail to read inventory: %v", err)...) } tempHostgroups := hostgroups @@ -93,42 +67,27 @@ func BuildPlaybookInventory( if len(tempHostgroups) > 0 { // if there is a list of groups specified for the desired host for _, hostgroup := range tempHostgroups { - hostgroupStr, okay := hostgroup.(string) - if !okay { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: "Couldn't assert type: string", - }) - } - - if !inventory.HasSection(hostgroupStr) { - _, err := inventory.NewRawSection(hostgroupStr, "") + if !inventory.HasSection(hostgroup) { + _, err = inventory.NewRawSection(hostgroup, "") if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("Fail to create a hostgroup: %v", err), - }) + diags = append(diags, diag.Errorf("Fail to create a hostgroup: %v", err)...) } } - if !inventory.Section(hostgroupStr).HasKey(hostname) { + if !inventory.Section(hostgroup).HasKey(hostname) { body := hostname if port != -1 { body += " ansible_port=" + strconv.Itoa(port) } - inventory.Section(hostgroupStr).SetBody(body) + inventory.Section(hostgroup).SetBody(body) } } } err = inventory.SaveTo(tempFileName) - if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("Fail to create inventory: %v", err), - }) + diags = append(diags, diag.Errorf("Fail to create inventory: %v", err)...) } return tempFileName, diags @@ -139,10 +98,7 @@ func RemoveFile(filename string) diag.Diagnostics { err := os.Remove(filename) if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("Fail to remove file %s: %v", filename, err), - }) + diags = append(diags, diag.Errorf("Fail to remove file %s: %v", filename, err)...) } return diags @@ -153,14 +109,9 @@ func GetAllInventories(inventoryPrefix string) ([]string, diag.Diagnostics) { tempDir := os.TempDir() - log.Printf("[TEMP DIR]: %s", tempDir) - files, err := os.ReadDir(tempDir) if err != nil { - diags = append(diags, diag.Diagnostic{ - Severity: diag.Error, - Summary: fmt.Sprintf("Fail to read dir %s: %v", tempDir, err), - }) + diags = append(diags, diag.Errorf("Fail to read dir %s: %v", tempDir, err)...) } inventories := []string{} @@ -174,3 +125,92 @@ func GetAllInventories(inventoryPrefix string) ([]string, diag.Diagnostics) { return inventories, diags } + +type ResourceDataParser struct { + Data *schema.ResourceData + Detail string + Diags diag.Diagnostics +} + +func (p *ResourceDataParser) HasError() bool { + return p.Diags.HasError() +} + +func (p *ResourceDataParser) Get(key string) interface{} { + result := p.Data.Get(key) + if result == nil { + p.Diags = append(p.Diags, diag.Errorf("couldn't get key '%s'!", key)...) + } + + return result +} + +func assertType[T int | string | bool](key string, data interface{}, value *T) diag.Diagnostics { + var diags diag.Diagnostics + if data == nil { + return diags + } + + tmpValue, ok := data.(T) + if !ok { + diags = diag.Errorf("couldn't assert %T: for key [%s]", reflect.TypeOf(*value), key) + + return diags + } + + *value = tmpValue + + return diags +} + +func (p *ResourceDataParser) ReadString(key string, value *string) { + p.Diags = append(p.Diags, assertType(key, p.Get(key), value)...) +} + +func (p *ResourceDataParser) ReadInt(key string, value *int) { + p.Diags = append(p.Diags, assertType(key, p.Get(key), value)...) +} + +func (p *ResourceDataParser) ReadBool(key string, value *bool) { + p.Diags = append(p.Diags, assertType(key, p.Get(key), value)...) +} + +func (p *ResourceDataParser) ReadStringList(key string, value *[]string) { + data := p.Get(key) + if data != nil { + dataList, ok := data.([]interface{}) + if !ok { + p.Diags = append(p.Diags, diag.Errorf("Could not assert type []string for key [%s]", key)...) + } else if len(dataList) > 0 { + tmpResult := []string{} + for idx, item := range dataList { + var itemStr string + p.Diags = append(p.Diags, assertType(fmt.Sprintf("%s.%d", key, idx), item, &itemStr)...) + if len(itemStr) > 0 { + tmpResult = append(tmpResult, itemStr) + } + } + *value = tmpResult + } + } +} + +func (p *ResourceDataParser) ReadMapString(key string, value *map[string]string) { + data := p.Get(key) + if data != nil { + dataMap, ok := data.(map[string]interface{}) + if !ok { + p.Diags = append(p.Diags, diag.Errorf("Could not assert type map[string]interface{} for key [%s]", key)...) + } else { + tmpMap := make(map[string]string) + for keyItem, valItem := range dataMap { + var itemStr string + p.Diags = append(p.Diags, assertType(fmt.Sprintf("%s.%s", key, keyItem), valItem, &itemStr)...) + if len(itemStr) > 0 { + tmpMap[keyItem] = itemStr + } + } + *value = tmpMap + } + } +} diff --git a/tests/expected_tfstate.json b/tests/expected_tfstate.json deleted file mode 100644 index 9881c79..0000000 --- a/tests/expected_tfstate.json +++ /dev/null @@ -1,129 +0,0 @@ -{ - "version": 4, - "terraform_version": "1.3.6", - "serial": 4, - "lineage": "e0b0c2aa-ac5f-e583-831f-a491e8b2929b", - "outputs": {}, - "resources": [ - { - "mode": "managed", - "type": "ansible_group", - "name": "group", - "provider": "provider[\"registry.terraform.io/ansible/ansible\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "children": [ - "somechild" - ], - "id": "somegroup", - "name": "somegroup", - "variables": { - "hello": "from group!" - } - }, - "sensitive_attributes": [], - "private": "bnVsbA==" - } - ] - }, - { - "mode": "managed", - "type": "ansible_host", - "name": "host", - "provider": "provider[\"registry.terraform.io/ansible/ansible\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "groups": [ - "somegroup" - ], - "id": "somehost", - "name": "somehost", - "variables": { - "greetings": "from host!", - "some": "variable", - "yaml_hello": "from vault!", - "yaml_list": "[\"some\",\"nice\",\"list\"]", - "yaml_number": "24356" - } - }, - "sensitive_attributes": [ - [ - { - "type": "get_attr", - "value": "variables" - }, - { - "type": "index", - "value": { - "value": "yaml_hello", - "type": "string" - } - } - ], - [ - { - "type": "get_attr", - "value": "variables" - }, - { - "type": "index", - "value": { - "value": "yaml_list", - "type": "string" - } - } - ], - [ - { - "type": "get_attr", - "value": "variables" - }, - { - "type": "index", - "value": { - "value": "yaml_number", - "type": "string" - } - } - ] - ], - "private": "bnVsbA==", - "dependencies": [ - "ansible_vault.secrets" - ] - } - ] - }, - { - "mode": "managed", - "type": "ansible_vault", - "name": "secrets", - "provider": "provider[\"registry.terraform.io/ansible/ansible\"]", - "instances": [ - { - "schema_version": 0, - "attributes": { - "args": [ - "view", - "--vault-id", - "testvault@vault_password", - "vault-encrypted.yml" - ], - "id": "vault-encrypted.yml", - "vault_file": "vault-encrypted.yml", - "vault_id": "testvault", - "vault_password_file": "vault_password", - "yaml": "hello: from vault!\na_number: 24356\na_list:\n - some\n - nice\n - list\n" - }, - "sensitive_attributes": [], - "private": "bnVsbA==" - } - ] - } - ], - "check_results": null -} diff --git a/tests/integration/provider_test.go b/tests/integration/provider_test.go deleted file mode 100644 index efcd090..0000000 --- a/tests/integration/provider_test.go +++ /dev/null @@ -1,47 +0,0 @@ -// integration tests package -package integration_tests_test - -import ( - "log" - "testing" - - "github.com/Jeffail/gabs" - "github.com/stretchr/testify/assert" -) - -const ( - actualJSON = "../terraform_tests/actual_tfstate.json" - expectedJSON = "../expected_tfstate.json" -) - -func TestAnsibleProviderOutputs(t *testing.T) { - t.Parallel() - - actual, errAct := gabs.ParseJSONFile(actualJSON) - expected, errExp := gabs.ParseJSONFile(expectedJSON) - - // "serial" is a changing variable (it changes after - // every 'terraform destroy'), so we're not testing that. - if _, err := expected.Set(actual.Path("serial").Data(), "serial"); err != nil { - log.Fatalf("Error: couldn't ignore 'serial' field! %s", err) - } - - // "lineage" is a changing variable (it is dependent on the - // terraform working directory), so we're not testing that. - if _, err := expected.Set(actual.Path("lineage").Data(), "lineage"); err != nil { - log.Fatalf("Error: couldn't ignore 'lineage' field! %s", err) - } - - if errAct != nil { - log.Fatal("Error in " + actualJSON + "!") - } - - if errExp != nil { - log.Fatal("Error in " + expectedJSON + "!") - } - - assert.JSONEq(t, - expected.Path("resources").String(), - actual.Path("resources").String(), - "Actual and Expected JSON files don't match!") -} diff --git a/tests/terraform_tests/ansible-dev.tfrc b/tests/terraform_tests/ansible-dev.tfrc deleted file mode 100644 index f6c56f3..0000000 --- a/tests/terraform_tests/ansible-dev.tfrc +++ /dev/null @@ -1,7 +0,0 @@ -provider_installation { - dev_overrides { - "ansible/ansible" = "../../.." - } - - direct {} -} diff --git a/tests/terraform_tests/main.tf b/tests/terraform_tests/main.tf deleted file mode 100644 index 7263c41..0000000 --- a/tests/terraform_tests/main.tf +++ /dev/null @@ -1,47 +0,0 @@ -terraform { - required_providers { - ansible = { - version = "~> 1.0.0" - source = "ansible/ansible" - } - } -} - - -resource "ansible_vault" "secrets" { - # required options - vault_file = "vault-encrypted.yml" - vault_password_file = "vault_password" - - # optional options - vault_id = "testvault" -} - - -locals { - decoded_vault_yaml = yamldecode(ansible_vault.secrets.yaml) -} - -resource "ansible_host" "host" { - name = "somehost" - groups = ["somegroup"] - - variables = { - greetings = "from host!" - some = "variable" - yaml_hello = local.decoded_vault_yaml.hello - yaml_number = local.decoded_vault_yaml.a_number - - # using jsonencode() here is needed to stringify - # a list that looks like: [ element_1, element_2, ..., element_N ] - yaml_list = jsonencode(local.decoded_vault_yaml.a_list) - } -} - -resource "ansible_group" "group" { - name = "somegroup" - children = ["somechild"] - variables = { - hello = "from group!" - } -} diff --git a/tests/terraform_tests/playbooks/play-test-default.yml b/tests/terraform_tests/playbooks/play-test-default.yml deleted file mode 100644 index a2eed0a..0000000 --- a/tests/terraform_tests/playbooks/play-test-default.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -- name: A test playbook - hosts: localhost - - vars: - docker_name: "{{ docker_container }}" - diff --git a/tests/terraform_tests/playbooks/play-test-other.yml b/tests/terraform_tests/playbooks/play-test-other.yml deleted file mode 100644 index 99ea406..0000000 --- a/tests/terraform_tests/playbooks/play-test-other.yml +++ /dev/null @@ -1,7 +0,0 @@ ---- -- name: A test playbook - hosts: localhost - - vars: - docker_name: "{{ docker_container }}" - diff --git a/tests/terraform_tests/playbooks/play-vault-test.yml b/tests/terraform_tests/playbooks/play-vault-test.yml deleted file mode 100644 index 2d72eb2..0000000 --- a/tests/terraform_tests/playbooks/play-vault-test.yml +++ /dev/null @@ -1,13 +0,0 @@ ---- -- name: A vault test playbook - hosts: localhost - - vars: - docker_name: "{{ docker_container }}" - tasks: - - name: Hello there - ansible.builtin.debug: - msg: - - "Hello there! The secret is:" - - "{{ var_1 }}" - - "{{ var_2 }}" diff --git a/tests/terraform_tests/run_tftest.sh b/tests/terraform_tests/run_tftest.sh deleted file mode 100755 index a740637..0000000 --- a/tests/terraform_tests/run_tftest.sh +++ /dev/null @@ -1,35 +0,0 @@ -#!/bin/bash - -set -eux - -dir=$(pwd) -tempdir="$(mktemp -d $dir/temp.XXXXXX)" -export TF_CLI_CONFIG_FILE="$dir/ansible-dev.tfrc" - -function teardown() -{ - rm -rf "$tempdir" -} - -trap teardown EXIT - -cat vault-decrypted.yml > vault-encrypted.yml -ansible-vault encrypt --vault-id testvault@vault_password vault-encrypted.yml - -cp -v main.tf $tempdir -mv vault-encrypted.yml $tempdir -cp vault_password $tempdir - -cd $tempdir - -terraform init || true # expected to fail -terraform apply --auto-approve -cat terraform.tfstate > ../actual_tfstate.json - -cd ../../integration -set +e -go test -v -exit_code="$?" -set -e - -exit "$exit_code" diff --git a/tests/terraform_tests/vault-decrypted.yml b/tests/terraform_tests/vault-decrypted.yml deleted file mode 100644 index 5a2fbae..0000000 --- a/tests/terraform_tests/vault-decrypted.yml +++ /dev/null @@ -1,6 +0,0 @@ -hello: from vault! -a_number: 24356 -a_list: - - some - - nice - - list \ No newline at end of file diff --git a/tests/terraform_tests/vault_password b/tests/terraform_tests/vault_password deleted file mode 100644 index 7aa311a..0000000 --- a/tests/terraform_tests/vault_password +++ /dev/null @@ -1 +0,0 @@ -password \ No newline at end of file diff --git a/tests/vault-encrypted.yml b/tests/vault-encrypted.yml deleted file mode 100644 index e69de29..0000000