diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f9f638c..4e03151 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -30,7 +30,7 @@ jobs: name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.19 + go-version: 1.21 - name: Import GPG key id: import_gpg diff --git a/.gitignore b/.gitignore index 4b4956d..3293a3a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -terraform-provider-jfrog dist/ vendor/ .idea/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d10eff..012b3f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,16 @@ +## 1.1.0 (Dec 14, 2023). Tested on Artifactory 7.71.8 with Terraform CLI v1.6.6 + +FEATURES: + +* **New Resource:** `platform_reverse_proxy`: PR: [#13](https://github.com/jfrog/terraform-provider-platform/pull/13) Issue: [#11](https://github.com/jfrog/terraform-provider-platform/issues/11) + ## 1.0.1 (Nov 28, 2023). Tested on Artifactory 7.71.5 with Terraform CLI v1.6.4 IMPROVEMENTS: -* Bump github.com/hashicorp/terraform-plugin-go from 0.19.0 to 0.19.1: PR [6](https://github.com/jfrog/terraform-provider-platform/pull/6) -* Bump github.com/hashicorp/terraform-plugin-testing from 1.5.0 to 1.5.1: PR [5](https://github.com/jfrog/terraform-provider-platform/pull/5) -* Bump github.com/go-resty/resty/v2 from 2.7.0 to 2.10.0: PR [4](https://github.com/jfrog/terraform-provider-platform/pull/4) +* Bump github.com/hashicorp/terraform-plugin-go from 0.19.0 to 0.19.1: PR: [6](https://github.com/jfrog/terraform-provider-platform/pull/6) +* Bump github.com/hashicorp/terraform-plugin-testing from 1.5.0 to 1.5.1: PR: [5](https://github.com/jfrog/terraform-provider-platform/pull/5) +* Bump github.com/go-resty/resty/v2 from 2.7.0 to 2.10.0: PR: [4](https://github.com/jfrog/terraform-provider-platform/pull/4) ## 1.0.0 (Nov 27, 2023) diff --git a/docs/resources/reverse_proxy.md b/docs/resources/reverse_proxy.md new file mode 100644 index 0000000..8ccee0b --- /dev/null +++ b/docs/resources/reverse_proxy.md @@ -0,0 +1,57 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "platform_reverse_proxy Resource - terraform-provider-platform" +subcategory: "" +description: |- + Provides a JFrog Reverse Proxy https://jfrog.com/help/r/jfrog-artifactory-documentation/reverse-proxy-settings resource. + ~>Only available for self-hosted instances. +--- + +# platform_reverse_proxy (Resource) + +Provides a JFrog [Reverse Proxy](https://jfrog.com/help/r/jfrog-artifactory-documentation/reverse-proxy-settings) resource. + +~>Only available for self-hosted instances. + +## Example Usage + +```terraform +resource "platform_reverse_proxy" "my-reverse-proxy" { + docker_reverse_proxy_method = "SUBDOMAIN" + server_name_expression = "*.jfrog.com" + server_provider = "NGINX" + public_server_name = "jfrog.com" + internal_hostname = "localhost" + use_https = true + http_port = 80 + https_port = 443 + ssl_key_path = "/etc/ssl/private/myserver.key" + ssl_certificate_path = "/etc/ssl/certs/myserver.crt" +} +``` + + +## Schema + +### Required + +- `server_provider` (String) Set the server provider type. Supported values: DIRECT, NGINX, APACHE. + +### Optional + +- `docker_reverse_proxy_method` (String) Docker access method. The default value is SUBDOMAIN. Supported values: SUBDOMAIN, REPOPATHPREFIX, PORTPERREPO. +- `http_port` (Number) The port for access via HTTP. The default value is 80. Only settable when `server_provider` is set to `NIGNIX` or `APACHE` +- `https_port` (Number) The port for access via HTTPS. The default value is 443. Only settable when `use_https` is set to `true` +- `internal_hostname` (String) The internal server name for Artifactory which will be used by the web server to access the Artifactory machine. If the web server is installed on the same machine as Artifactory you can use localhost, otherwise use the IP or hostname. Must be set when `server_provider` is set to `NIGNIX` or `APACHE` +- `public_server_name` (String) The server name that will be used to access Artifactory. Should be correlated with the base URL value. Must be set when `server_provider` is set to `NIGNIX` or `APACHE` +- `ssl_certificate_path` (String) The full path of the certificate file on the web server, e.g. `/etc/ssl/certs/myserver.crt`. Must be set when `use_https` is set to `true` +- `ssl_key_path` (String) The full path of the key file on the web server, e.g. `/etc/ssl/private/myserver.key`. Must be set when `use_https` is set to `true` +- `use_https` (Boolean) When set, Artifactory will be accessible via HTTPS at the corresponding port that is set. Only settable when `server_provider` is set to `NIGNIX` or `APACHE` + +## Import + +Import is supported using the following syntax: + +```shell +terraform import platform_reverse_proxy.my-reverse-proxy DIRECT +``` diff --git a/docs/resources/workers_service.md b/docs/resources/workers_service.md index 5b35f34..9e7a3da 100644 --- a/docs/resources/workers_service.md +++ b/docs/resources/workers_service.md @@ -86,3 +86,11 @@ Required: - `key` (String) The name of the secret. - `value` (String) The name of the secret. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import platform_workers_service.my-worker-service my-worker-service-key +``` diff --git a/examples/resources/platform_reverse_proxy/import.sh b/examples/resources/platform_reverse_proxy/import.sh new file mode 100644 index 0000000..d6e70a3 --- /dev/null +++ b/examples/resources/platform_reverse_proxy/import.sh @@ -0,0 +1 @@ +terraform import platform_reverse_proxy.my-reverse-proxy DIRECT \ No newline at end of file diff --git a/examples/resources/platform_reverse_proxy/resource.tf b/examples/resources/platform_reverse_proxy/resource.tf new file mode 100644 index 0000000..376776d --- /dev/null +++ b/examples/resources/platform_reverse_proxy/resource.tf @@ -0,0 +1,12 @@ +resource "platform_reverse_proxy" "my-reverse-proxy" { + docker_reverse_proxy_method = "SUBDOMAIN" + server_name_expression = "*.jfrog.com" + server_provider = "NGINX" + public_server_name = "jfrog.com" + internal_hostname = "localhost" + use_https = true + http_port = 80 + https_port = 443 + ssl_key_path = "/etc/ssl/private/myserver.key" + ssl_certificate_path = "/etc/ssl/certs/myserver.crt" +} \ No newline at end of file diff --git a/examples/resources/platform_workers_service/import.sh b/examples/resources/platform_workers_service/import.sh new file mode 100644 index 0000000..fcb3189 --- /dev/null +++ b/examples/resources/platform_workers_service/import.sh @@ -0,0 +1 @@ +terraform import platform_workers_service.my-worker-service my-worker-service-key \ No newline at end of file diff --git a/go.mod b/go.mod index 3b5160e..4b13d0b 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/jfrog/terraform-provider-platform // if you need to do local dev, literally just uncomment the line below // replace github.com/jfrog/terraform-provider-shared => ../terraform-provider-shared -go 1.19 +go 1.21 require ( - github.com/go-resty/resty/v2 v2.10.0 + github.com/go-resty/resty/v2 v2.9.1 github.com/hashicorp/terraform-plugin-docs v0.16.0 github.com/hashicorp/terraform-plugin-framework v1.4.2 github.com/hashicorp/terraform-plugin-framework-validators v0.12.0 diff --git a/go.sum b/go.sum index 75f3879..1a502ba 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,5 @@ dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= github.com/Masterminds/semver/v3 v3.1.1 h1:hLg3sBzpNErnxhQtUy/mmLR2I9foDujNK030IGemrRc= @@ -7,9 +8,11 @@ github.com/Masterminds/sprig/v3 v3.2.1/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFP github.com/Masterminds/sprig/v3 v3.2.2 h1:17jRggJu518dr3QaafizSXOjKYp94wKfABxUmyxvxX8= github.com/Masterminds/sprig/v3 v3.2.2/go.mod h1:UoaO7Yp8KlPnJIYWTFkMaqPUYKTfGFPhxNuwnnxkKlk= 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/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= +github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= github.com/agext/levenshtein v1.2.2 h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE= github.com/agext/levenshtein v1.2.2/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558= github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec= @@ -21,25 +24,34 @@ github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgI github.com/bgentry/speakeasy v0.1.0 h1:ByYyxL9InA1OWqxJqqp2A5pYHUrCiAL6K3J+LKSsQkY= 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 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w= github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= github.com/go-git/go-billy/v5 v5.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.9.0 h1:cD9SFA7sHVRdJ7AYck1ZaAa/yeuBvGPxwXDL8cxrObY= -github.com/go-resty/resty/v2 v2.10.0 h1:Qla4W/+TMmv0fOeeRqzEpXPLfTUnR5HZ1+lGs+CkiCo= -github.com/go-resty/resty/v2 v2.10.0/go.mod h1:iiP/OpA0CkcL3IGt1O0+/SIItFUbkkyw5BGXiVdTu+A= +github.com/go-git/go-git/v5 v5.9.0/go.mod h1:RKIqga24sWdMGZF+1Ekv9kylsDz6LzdTSI2s/OsZWE0= +github.com/go-resty/resty/v2 v2.9.1 h1:PIgGx4VrHvag0juCJ4dDv3MiFRlDmP0vicBucwf+gLM= +github.com/go-resty/resty/v2 v2.9.1/go.mod h1:4/GYJVjh9nhkhGR6AUNW3XhpDYNUr+Uvy9gV/VGZIy4= 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= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= @@ -114,16 +126,22 @@ github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH github.com/imdario/mergo v0.3.15 h1:M8XP7IuFNsqUx6VPK2P9OSmsYsI/YFaGil0uD21V3dM= github.com/imdario/mergo v0.3.15/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= -github.com/jfrog/terraform-provider-shared v1.21.0 h1:E653Oos09FWBP5Pnw1D1vGSY93pd9pYbKlH3Jkl23tE= -github.com/jfrog/terraform-provider-shared v1.21.0/go.mod h1:Ioq1OBLb2h9uj4t4KzBEfA1piHbUg+Lgvn84Vbzk5uI= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/jfrog/terraform-provider-shared v1.21.1 h1:hBlk7087fjMVk7fdx2BkpZM3RnLIzS6fGNZEJc5HLYc= +github.com/jfrog/terraform-provider-shared v1.21.1/go.mod h1:Ioq1OBLb2h9uj4t4KzBEfA1piHbUg+Lgvn84Vbzk5uI= github.com/jhump/protoreflect v1.15.1 h1:HUMERORf3I3ZdX05WaQ6MIpd/NJ434hTp5YiKgfCL6c= +github.com/jhump/protoreflect v1.15.1/go.mod h1:jD/2GMKKE6OqX8qTjhADU1e6DShO+gavG9e0Q693nKo= github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= @@ -151,21 +169,25 @@ github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx github.com/oklog/run v1.0.0 h1:Ru7dDtJNOyC66gQ5dQmaCa0qIsAUFY3sFpK1Xk8igrw= github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= github.com/posener/complete v1.2.3 h1:NP0eAhjcjImqslEwo/1hq7gpajME0fTLTezBKDqfXqo= github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww= github.com/russross/blackfriday v1.6.0/go.mod h1:ti0ldHuxg49ri4ksnFxlkCfN+hvslNlmVHqNRXXJNAY= github.com/samber/lo v1.39.0 h1:4gTz1wUhNYLhFSKl6O+8peW0v2F4BCY034GRpU9WnuA= github.com/samber/lo v1.39.0/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM= +github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= @@ -184,6 +206,7 @@ github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21 github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g= github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zclconf/go-cty v1.14.1 h1:t9fyA35fwjjUMcmL5hLER+e/rEPqrbCK1/OSE4SI9KA= github.com/zclconf/go-cty v1.14.1/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= @@ -193,7 +216,7 @@ golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 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.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY= golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 h1:EDuYyU/MkFXllv9QF9819VlI9a4tzGuCbhG0ExK9o1U= @@ -211,6 +234,7 @@ 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.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -234,7 +258,7 @@ 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.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc= golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -243,7 +267,7 @@ 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.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= 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= @@ -255,13 +279,14 @@ golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= -golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M= +golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= 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= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= @@ -279,6 +304,7 @@ gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8 gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/platform/provider.go b/pkg/platform/provider.go index 3be70e5..975f2cc 100644 --- a/pkg/platform/provider.go +++ b/pkg/platform/provider.go @@ -142,6 +142,7 @@ func (p *PlatformProvider) DataSources(ctx context.Context) []func() datasource. func (p *PlatformProvider) Resources(ctx context.Context) []func() resource.Resource { return []func() resource.Resource{ NewWorkerServiceResource, + NewReverseProxyResource, } } diff --git a/pkg/platform/resource_reverse_proxy.go b/pkg/platform/resource_reverse_proxy.go new file mode 100644 index 0000000..3465089 --- /dev/null +++ b/pkg/platform/resource_reverse_proxy.go @@ -0,0 +1,369 @@ +package platform + +import ( + "context" + "fmt" + "net/http" + "strings" + + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + "github.com/hashicorp/terraform-plugin-framework/resource" + "github.com/hashicorp/terraform-plugin-framework/resource/schema" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64default" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringdefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + "github.com/hashicorp/terraform-plugin-framework/types" + "github.com/jfrog/terraform-provider-shared/util" + utilfw "github.com/jfrog/terraform-provider-shared/util/fw" +) + +const reversProxyEndpoint = "/artifactory/api/system/configuration/webServer" +const maxPortNumber = 65535 + +var supportedDockerProxyMethods = []string{"SUBDOMAIN", "REPOPATHPREFIX", "PORTPERREPO"} +var supportedServerProviderTypes = []string{"DIRECT", "NGINX", "APACHE"} + +var _ resource.Resource = (*reverseProxyResource)(nil) + +type reverseProxyResource struct { + ProviderData util.ProvderMetadata +} + +func NewReverseProxyResource() resource.Resource { + return &reverseProxyResource{} +} + +func (r *reverseProxyResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_reverse_proxy" +} +func (r *reverseProxyResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "docker_reverse_proxy_method": schema.StringAttribute{ + Optional: true, + Computed: true, + Description: fmt.Sprintf("Docker access method. The default value is SUBDOMAIN. Supported values: %s.", strings.Join(supportedDockerProxyMethods, ", ")), + Default: stringdefault.StaticString("SUBDOMAIN"), + Validators: []validator.String{stringvalidator.OneOf(supportedDockerProxyMethods...)}, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "server_provider": schema.StringAttribute{ + Required: true, + Description: fmt.Sprintf("Set the server provider type. Supported values: %s.", strings.Join(supportedServerProviderTypes, ", ")), + Validators: []validator.String{stringvalidator.OneOf(supportedServerProviderTypes...)}, + }, + "public_server_name": schema.StringAttribute{ + Optional: true, + Description: "The server name that will be used to access Artifactory. Should be correlated with the base URL value. Must be set when `server_provider` is set to `NIGNIX` or `APACHE`", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "internal_hostname": schema.StringAttribute{ + Optional: true, + Description: "The internal server name for Artifactory which will be used by the web server to access the Artifactory machine. If the web server is installed on the same machine as Artifactory you can use localhost, otherwise use the IP or hostname. Must be set when `server_provider` is set to `NIGNIX` or `APACHE`", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "use_https": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + Description: "When set, Artifactory will be accessible via HTTPS at the corresponding port that is set. Only settable when `server_provider` is set to `NIGNIX` or `APACHE`", + }, + "http_port": schema.Int64Attribute{ + Optional: true, + Computed: true, + Default: int64default.StaticInt64(80), + Validators: []validator.Int64{ + int64validator.AtMost(maxPortNumber), + }, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + Description: "The port for access via HTTP. The default value is 80. Only settable when `server_provider` is set to `NIGNIX` or `APACHE`", + }, + "https_port": schema.Int64Attribute{ + Optional: true, + Computed: true, + Default: int64default.StaticInt64(443), + Validators: []validator.Int64{ + int64validator.AtMost(maxPortNumber), + }, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.UseStateForUnknown(), + }, + Description: "The port for access via HTTPS. The default value is 443. Only settable when `use_https` is set to `true`", + }, + "ssl_key_path": schema.StringAttribute{ + Optional: true, + Description: "The full path of the key file on the web server, e.g. `/etc/ssl/private/myserver.key`. Must be set when `use_https` is set to `true`", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + "ssl_certificate_path": schema.StringAttribute{ + Optional: true, + Description: "The full path of the certificate file on the web server, e.g. `/etc/ssl/certs/myserver.crt`. Must be set when `use_https` is set to `true`", + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, + }, + }, + MarkdownDescription: "Provides a JFrog [Reverse Proxy](https://jfrog.com/help/r/jfrog-artifactory-documentation/reverse-proxy-settings) resource.\n\n~>Only available for self-hosted instances.", + } +} + +type reverseProxyResourceModel struct { + DockerReverseProxyMethod types.String `tfsdk:"docker_reverse_proxy_method"` + ServerProvider types.String `tfsdk:"server_provider"` + PublicServerName types.String `tfsdk:"public_server_name"` + InternalHostname types.String `tfsdk:"internal_hostname"` + UseHttps types.Bool `tfsdk:"use_https"` + HttpPort types.Int64 `tfsdk:"http_port"` + HttpsPort types.Int64 `tfsdk:"https_port"` + SslKeyPath types.String `tfsdk:"ssl_key_path"` + SslCertificatePath types.String `tfsdk:"ssl_certificate_path"` +} + +func (r *reverseProxyResourceModel) toAPIModel(ctx context.Context, apiModel *reverseProxyAPIModel) (ds diag.Diagnostics) { + *apiModel = reverseProxyAPIModel{ + Key: strings.ToLower(r.ServerProvider.ValueString()), + WebServerType: r.ServerProvider.ValueString(), + ArtifactoryAppContext: "artifactory", + PublicAppContext: "artifactory", + ServerName: r.PublicServerName.ValueString(), + ArtifactoryServerName: r.InternalHostname.ValueString(), + DockerReverseProxyMethod: r.DockerReverseProxyMethod.ValueString(), + UseHttp: true, + UseHttps: r.UseHttps.ValueBool(), + HttpPort: r.HttpPort.ValueInt64(), + HttpsPort: r.HttpsPort.ValueInt64(), + SslKey: r.SslKeyPath.ValueString(), + SslCertificate: r.SslCertificatePath.ValueString(), + } + + return nil +} + +func (r *reverseProxyResourceModel) fromAPIModel(ctx context.Context, apiModel *reverseProxyAPIModel) (ds diag.Diagnostics) { + r.ServerProvider = types.StringValue(apiModel.WebServerType) + r.DockerReverseProxyMethod = types.StringValue(apiModel.DockerReverseProxyMethod) + r.PublicServerName = types.StringValue(apiModel.ServerName) + r.InternalHostname = types.StringValue(apiModel.ArtifactoryServerName) + r.UseHttps = types.BoolValue(apiModel.UseHttps) + r.HttpPort = types.Int64Value(apiModel.HttpPort) + r.HttpsPort = types.Int64Value(apiModel.HttpsPort) + + // API will return empty string if not configured. Therefore we only update if it isn't empty + if apiModel.SslKey != "" { + r.SslKeyPath = types.StringValue(apiModel.SslKey) + } + + // API will return empty string if not configured. Therefore we only update if it isn't empty + if apiModel.SslCertificate != "" { + r.SslCertificatePath = types.StringValue(apiModel.SslCertificate) + } + + return +} + +type reverseProxyAPIModel struct { + Key string `json:"key"` + WebServerType string `json:"webServerType"` + ArtifactoryAppContext string `json:"artifactoryAppContext"` + PublicAppContext string `json:"publicAppContext"` + ServerName string `json:"serverName"` + ArtifactoryServerName string `json:"artifactoryServerName"` + DockerReverseProxyMethod string `json:"dockerReverseProxyMethod"` + UseHttp bool `json:"useHttp"` + UseHttps bool `json:"useHttps"` + HttpPort int64 `json:"httpPort"` + HttpsPort int64 `json:"httpsPort"` + SslKey string `json:"sslKey,omitempty"` + SslCertificate string `json:"sslCertificate,omitempty"` +} + +func (r *reverseProxyResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + r.ProviderData = req.ProviderData.(util.ProvderMetadata) +} + +func (r *reverseProxyResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + var plan reverseProxyResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var reverseProxy reverseProxyAPIModel + resp.Diagnostics.Append(plan.toAPIModel(ctx, &reverseProxy)...) + if resp.Diagnostics.HasError() { + return + } + + response, err := r.ProviderData.Client.R(). + SetBody(&reverseProxy). + Post(reversProxyEndpoint) + if err != nil { + utilfw.UnableToCreateResourceError(resp, err.Error()) + return + } + + // Return error if the HTTP status code is not 201 Created + if response.StatusCode() != http.StatusCreated { + utilfw.UnableToCreateResourceError(resp, response.String()) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *reverseProxyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + var state reverseProxyResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + var reverseProxy reverseProxyAPIModel + + response, err := r.ProviderData.Client.R(). + SetResult(&reverseProxy). + Get(reversProxyEndpoint) + + // Treat HTTP 404 Not Found status as a signal to recreate resource + // and return early + if err != nil { + utilfw.UnableToRefreshResourceError(resp, response.String()) + return + } + + // Convert from the API data model to the Terraform data model + // and refresh any attribute values. + resp.Diagnostics.Append(state.fromAPIModel(ctx, &reverseProxy)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *reverseProxyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + var plan reverseProxyResourceModel + + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var reverseProxy reverseProxyAPIModel + resp.Diagnostics.Append(plan.toAPIModel(ctx, &reverseProxy)...) + if resp.Diagnostics.HasError() { + return + } + + response, err := r.ProviderData.Client.R(). + SetBody(&reverseProxy). + Post(reversProxyEndpoint) + if err != nil { + utilfw.UnableToUpdateResourceError(resp, err.Error()) + return + } + + // Return error if the HTTP status code is not 201 Created + if response.StatusCode() != http.StatusCreated { + utilfw.UnableToUpdateResourceError(resp, response.String()) + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *reverseProxyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + resp.Diagnostics.AddWarning( + "Unable to Delete Resource", + "Reverse proxy cannot be deleted.", + ) +} + +func (r *reverseProxyResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("server_provider"), req, resp) +} + +func (r reverseProxyResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + var config reverseProxyResourceModel + + resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) + if resp.Diagnostics.HasError() { + return + } + + switch serverProvider := config.ServerProvider.ValueString(); serverProvider { + case "NGINX", "APACHE": + if config.InternalHostname.IsNull() || config.InternalHostname.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("internal_hostname"), + "Missing Attribute Configuration", + fmt.Sprintf("internal_hostname must be configured when server_provider is set to '%s'.", serverProvider), + ) + } + + if config.PublicServerName.IsNull() || config.PublicServerName.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("public_server_name"), + "Missing Attribute Configuration", + fmt.Sprintf("public_server_name must be configured when server_provider is set to '%s'.", serverProvider), + ) + } + } + + if config.UseHttps.ValueBool() { + if config.SslKeyPath.IsNull() || config.SslKeyPath.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("ssl_key_path"), + "Missing Attribute Configuration", + "ssl_key_path must be configured when use_https is set to 'true'.", + ) + } + + if config.SslCertificatePath.IsNull() || config.SslCertificatePath.IsUnknown() { + resp.Diagnostics.AddAttributeError( + path.Root("ssl_certificate_path"), + "Missing Attribute Configuration", + "ssl_certificate_path must be configured when use_https is set to 'true'.", + ) + } + } +} diff --git a/pkg/platform/resource_reverse_proxy_test.go b/pkg/platform/resource_reverse_proxy_test.go new file mode 100644 index 0000000..736ac88 --- /dev/null +++ b/pkg/platform/resource_reverse_proxy_test.go @@ -0,0 +1,246 @@ +package platform_test + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/jfrog/terraform-provider-shared/testutil" +) + +func TestAccReverseProxy_full(t *testing.T) { + _, fqrn, reverseProxyName := testutil.MkNames("test-reverse-proxy", "platform_reverse_proxy") + + temp := ` + resource "platform_reverse_proxy" "{{ .name }}" { + docker_reverse_proxy_method = "{{ .dockerProxyMethod }}" + server_provider = "{{ .serverProvider }}" + public_server_name = "{{ .serverName }}" + internal_hostname = "localhost" + }` + + testData := map[string]string{ + "name": reverseProxyName, + "dockerProxyMethod": "SUBDOMAIN", + "serverProvider": "NGINX", + "serverName": "tempurl.org", + } + + config := testutil.ExecuteTemplate(reverseProxyName, temp, testData) + + updatedTemp := ` + resource "platform_reverse_proxy" "{{ .name }}" { + docker_reverse_proxy_method = "{{ .dockerProxyMethod }}" + server_provider = "{{ .serverProvider }}" + public_server_name = "{{ .serverName }}" + internal_hostname = "localhost" + http_port = {{ .httpPort }} + use_https = {{ .useHttps }} + https_port = {{ .httpsPort }} + ssl_key_path = "{{ .sslKeyPath }}" + ssl_certificate_path = "{{ .sslCertPath }}" + }` + + updatedTestData := map[string]string{ + "name": reverseProxyName, + "dockerProxyMethod": "REPOPATHPREFIX", + "serverProvider": "NGINX", + "serverName": "tempurl.org", + "httpPort": "88", + "useHttps": "true", + "httpsPort": "666", + "sslKeyPath": "foo/bar.key", + "sslCertPath": "foo/bar.crt", + } + updatedConfig := testutil.ExecuteTemplate(reverseProxyName, updatedTemp, updatedTestData) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders(), + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(fqrn, "docker_reverse_proxy_method", testData["dockerProxyMethod"]), + resource.TestCheckResourceAttr(fqrn, "server_provider", testData["serverProvider"]), + resource.TestCheckResourceAttr(fqrn, "public_server_name", testData["serverName"]), + resource.TestCheckResourceAttr(fqrn, "internal_hostname", "localhost"), + resource.TestCheckResourceAttr(fqrn, "use_https", "false"), + resource.TestCheckResourceAttr(fqrn, "http_port", "80"), + resource.TestCheckResourceAttr(fqrn, "https_port", "443"), + resource.TestCheckNoResourceAttr(fqrn, "ssl_key_path"), + resource.TestCheckNoResourceAttr(fqrn, "ssl_certificate_path"), + ), + }, + { + Config: updatedConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(fqrn, "docker_reverse_proxy_method", updatedTestData["dockerProxyMethod"]), + resource.TestCheckResourceAttr(fqrn, "server_provider", updatedTestData["serverProvider"]), + resource.TestCheckResourceAttr(fqrn, "public_server_name", updatedTestData["serverName"]), + resource.TestCheckResourceAttr(fqrn, "internal_hostname", "localhost"), + resource.TestCheckResourceAttr(fqrn, "use_https", updatedTestData["useHttps"]), + resource.TestCheckResourceAttr(fqrn, "http_port", updatedTestData["httpPort"]), + resource.TestCheckResourceAttr(fqrn, "https_port", updatedTestData["httpsPort"]), + resource.TestCheckResourceAttr(fqrn, "ssl_key_path", updatedTestData["sslKeyPath"]), + resource.TestCheckResourceAttr(fqrn, "ssl_certificate_path", updatedTestData["sslCertPath"]), + ), + }, + { + ResourceName: fqrn, + ImportState: true, + ImportStateId: reverseProxyName, + ImportStateVerify: true, + ImportStateVerifyIdentifierAttribute: "server_provider", + }, + }, + }) +} + +func TestAccReverseProxy_invalid_server_provider(t *testing.T) { + for _, serverProvider := range []string{"NGINX", "APACHE"} { + t.Run(serverProvider, func(t *testing.T) { + resource.Test(testAccReverseProxy_missing_internal_hostname(t, serverProvider)) + resource.Test(testAccReverseProxy_missing_public_server_name(t, serverProvider)) + }) + } +} + +func testAccReverseProxy_missing_internal_hostname(t *testing.T, serverProvider string) (*testing.T, resource.TestCase) { + _, _, reverseProxyName := testutil.MkNames("test-reverse-proxy", "platform_reverse_proxy") + + temp := ` + resource "platform_reverse_proxy" "{{ .name }}" { + docker_reverse_proxy_method = "SUBDOMAIN" + server_provider = "{{ .serverProvider }}" + public_server_name = "{{ .serverName }}" + }` + + testData := map[string]string{ + "name": reverseProxyName, + "dockerProxyMethod": "SUBDOMAIN", + "serverProvider": serverProvider, + "serverName": "tempurl.org", + } + + config := testutil.ExecuteTemplate(reverseProxyName, temp, testData) + + return t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders(), + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile(fmt.Sprintf(`.*internal_hostname must be configured when server_provider is set to '%s'.*`, serverProvider)), + }, + }, + } +} + +func testAccReverseProxy_missing_public_server_name(t *testing.T, serverProvider string) (*testing.T, resource.TestCase) { + _, _, reverseProxyName := testutil.MkNames("test-reverse-proxy", "platform_reverse_proxy") + + temp := ` + resource "platform_reverse_proxy" "{{ .name }}" { + docker_reverse_proxy_method = "SUBDOMAIN" + server_provider = "{{ .serverProvider }}" + internal_hostname = "localhost" + }` + + testData := map[string]string{ + "name": reverseProxyName, + "dockerProxyMethod": "SUBDOMAIN", + "serverProvider": serverProvider, + } + + config := testutil.ExecuteTemplate(reverseProxyName, temp, testData) + + return t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders(), + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile(`.*public_server_name must be configured when server_provider is set to.*`), + }, + }, + } +} + +func TestAccReverseProxy_invalid_https(t *testing.T) { + for _, serverProvider := range []string{"NGINX", "APACHE"} { + t.Run(serverProvider, func(t *testing.T) { + resource.Test(testAccReverseProxy_missing_ssl_key_path(t, serverProvider)) + resource.Test(testAccReverseProxy_missing_ssl_certificate_path(t, serverProvider)) + }) + } +} + +func testAccReverseProxy_missing_ssl_key_path(t *testing.T, serverProvider string) (*testing.T, resource.TestCase) { + _, _, reverseProxyName := testutil.MkNames("test-reverse-proxy", "platform_reverse_proxy") + + temp := ` + resource "platform_reverse_proxy" "{{ .name }}" { + docker_reverse_proxy_method = "SUBDOMAIN" + server_provider = "NGINX" + public_server_name = "{{ .serverName }}" + internal_hostname = "localhost" + use_https = true + ssl_certificate_path = "/foo/bar.crt" + }` + + testData := map[string]string{ + "name": reverseProxyName, + "dockerProxyMethod": "SUBDOMAIN", + "serverProvider": serverProvider, + "serverName": "tempurl.org", + } + + config := testutil.ExecuteTemplate(reverseProxyName, temp, testData) + + return t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders(), + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile(`ssl_key_path must be configured when use_https is set to 'true'.`), + }, + }, + } +} + +func testAccReverseProxy_missing_ssl_certificate_path(t *testing.T, serverProvider string) (*testing.T, resource.TestCase) { + _, _, reverseProxyName := testutil.MkNames("test-reverse-proxy", "platform_reverse_proxy") + + temp := ` + resource "platform_reverse_proxy" "{{ .name }}" { + docker_reverse_proxy_method = "SUBDOMAIN" + server_provider = "NGINX" + public_server_name = "{{ .serverName }}" + internal_hostname = "localhost" + use_https = true + ssl_key_path = "/foo/bar.key" + }` + + testData := map[string]string{ + "name": reverseProxyName, + "dockerProxyMethod": "SUBDOMAIN", + "serverProvider": serverProvider, + "serverName": "tempurl.org", + } + + config := testutil.ExecuteTemplate(reverseProxyName, temp, testData) + + return t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders(), + Steps: []resource.TestStep{ + { + Config: config, + ExpectError: regexp.MustCompile(`ssl_certificate_path must be configured when use_https is set to 'true'.`), + }, + }, + } +} diff --git a/pkg/platform/resource_workers_service.go b/pkg/platform/resource_workers_service.go index c461c88..3654e16 100644 --- a/pkg/platform/resource_workers_service.go +++ b/pkg/platform/resource_workers_service.go @@ -355,15 +355,13 @@ func (r *workersServiceResource) Create(ctx context.Context, req resource.Create return } - diags = resp.State.Set(ctx, &plan) - resp.Diagnostics.Append(diags...) + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } func (r *workersServiceResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var state workersServiceResourceModel - diags := req.State.Get(ctx, &state) - resp.Diagnostics.Append(diags...) + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) if resp.Diagnostics.HasError() { return } @@ -393,8 +391,7 @@ func (r *workersServiceResource) Read(ctx context.Context, req resource.ReadRequ return } - diags = resp.State.Set(ctx, &state) - resp.Diagnostics.Append(diags...) + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } func (r *workersServiceResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {