From 52688ebbcc13e940d8a7821086b59878853f3e6f Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 19 Dec 2024 11:05:03 -0800 Subject: [PATCH 1/5] Add platform_http_sso_settings resource --- docs/resources/http_sso_settings.md | 45 ++++ .../platform_http_sso_settings/import.sh | 1 + .../platform_http_sso_settings/resource.tf | 7 + pkg/platform/provider.go | 1 + pkg/platform/resource_http_sso_settings.go | 237 ++++++++++++++++++ .../resource_http_sso_settings_test.go | 79 ++++++ 6 files changed, 370 insertions(+) create mode 100644 docs/resources/http_sso_settings.md create mode 100644 examples/resources/platform_http_sso_settings/import.sh create mode 100644 examples/resources/platform_http_sso_settings/resource.tf create mode 100644 pkg/platform/resource_http_sso_settings.go create mode 100644 pkg/platform/resource_http_sso_settings_test.go diff --git a/docs/resources/http_sso_settings.md b/docs/resources/http_sso_settings.md new file mode 100644 index 0000000..93344a0 --- /dev/null +++ b/docs/resources/http_sso_settings.md @@ -0,0 +1,45 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "platform_http_sso_settings Resource - terraform-provider-platform" +subcategory: "" +description: |- + Provides a JFrog HTTP SSO Settings https://jfrog.com/help/r/jfrog-platform-administration-documentation/http-sso resource. This allows you to reuse existing HTTP-based SSO infrastructures with the JFrog Platform Unit (JPD), such as the SSO modules offered by Apache HTTPd. +--- + +# platform_http_sso_settings (Resource) + +Provides a JFrog [HTTP SSO Settings](https://jfrog.com/help/r/jfrog-platform-administration-documentation/http-sso) resource. This allows you to reuse existing HTTP-based SSO infrastructures with the JFrog Platform Unit (JPD), such as the SSO modules offered by Apache HTTPd. + +## Example Usage + +```terraform +resource "platform_http_sso_settings" "my-http-sso-settings" { + proxied = true + auto_create_user = true + allow_user_to_access_profile = true + remote_user_request_variable = "MY_REMOTE_USER" + sync_ldap_groups = false +} +``` + + +## Schema + +### Required + +- `proxied` (Boolean) When set, Artifactory trusts incoming requests and reuses the remote user originally set on the request by the SSO of the HTTP server. This is useful if you want to use existing enterprise SSO integrations, such as the powerful authentication schemes provided by Apache (mod_auth_ldap, mod_auth_ntlm, mod_auth_kerb, etc.). When Artifactory is deployed as a webapp on Tomcat behind Apache: If using mod_jk, be sure to use the `JkEnvVar REMOTE_USER` directive in Apache's configuration. + +### Optional + +- `allow_user_to_access_profile` (Boolean) Auto created users will have access to their profile page and will be able to perform actions such as generating an API key. Default to `false`. +- `auto_create_user` (Boolean) When set, authenticated users are automatically created in Artifactory. When not set, for every request from an SSO user, the user is temporarily associated with default groups (if such groups are defined), and the permissions for these groups apply. Without automatic user creation, you must manually create the user inside Artifactory to manage user permissions not attached to their default groups. Default to `false`. +- `remote_user_request_variable` (String) The name of the HTTP request variable to use for extracting the user identity. Default to `REMOTE_USER`. +- `sync_ldap_groups` (Boolean) When set, the user will be associated with the groups returned in the LDAP login response. Note that the user's association with the returned groups is persistent if the `auto_create_user` is set. Default to `false`. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import platform_http_sso_settings.my-http-sso-settings my-http-sso-settings +``` diff --git a/examples/resources/platform_http_sso_settings/import.sh b/examples/resources/platform_http_sso_settings/import.sh new file mode 100644 index 0000000..f58b31f --- /dev/null +++ b/examples/resources/platform_http_sso_settings/import.sh @@ -0,0 +1 @@ +terraform import platform_http_sso_settings.my-http-sso-settings my-http-sso-settings \ No newline at end of file diff --git a/examples/resources/platform_http_sso_settings/resource.tf b/examples/resources/platform_http_sso_settings/resource.tf new file mode 100644 index 0000000..7820d7c --- /dev/null +++ b/examples/resources/platform_http_sso_settings/resource.tf @@ -0,0 +1,7 @@ +resource "platform_http_sso_settings" "my-http-sso-settings" { + proxied = true + auto_create_user = true + allow_user_to_access_profile = true + remote_user_request_variable = "MY_REMOTE_USER" + sync_ldap_groups = false +} \ No newline at end of file diff --git a/pkg/platform/provider.go b/pkg/platform/provider.go index b76e5b6..a86f63c 100644 --- a/pkg/platform/provider.go +++ b/pkg/platform/provider.go @@ -163,6 +163,7 @@ func (p *PlatformProvider) Resources(ctx context.Context) []func() resource.Reso NewLicenseResource, NewGlobalRoleResource, NewGroupResource, + NewHTTPSSOSettingsResource, NewOIDCConfigurationResource, NewOIDCIdentityMappingResource, NewMyJFrogIPAllowListResource, diff --git a/pkg/platform/resource_http_sso_settings.go b/pkg/platform/resource_http_sso_settings.go new file mode 100644 index 0000000..c3185b9 --- /dev/null +++ b/pkg/platform/resource_http_sso_settings.go @@ -0,0 +1,237 @@ +package platform + +import ( + "context" + "net/http" + + "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/stringdefault" + "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" +) + +func NewHTTPSSOSettingsResource() resource.Resource { + return &HTTPSSOSettingsResource{ + JFrogResource: util.JFrogResource{ + TypeName: "platform_http_sso_settings", + ValidArtifactoryVersion: "7.57.1", + DocumentEndpoint: "access/api/v1/httpsso", + }, + } +} + +type HTTPSSOSettingsResource struct { + util.JFrogResource +} + +type HTTPSSOSettingsResourceModel struct { + Proxied types.Bool `tfsdk:"proxied"` + AutoCreateUser types.Bool `tfsdk:"auto_create_user"` + AllowUserToAccessProfile types.Bool `tfsdk:"allow_user_to_access_profile"` + RemoteUserRequestVariable types.String `tfsdk:"remote_user_request_variable"` + SyncLDAPGroups types.Bool `tfsdk:"sync_ldap_groups"` +} + +func (r *HTTPSSOSettingsResourceModel) toAPIModel(_ context.Context, apiModel *HTTPSSOSettingsAPIModel) diag.Diagnostics { + diags := diag.Diagnostics{} + + apiModel.Proxied = r.Proxied.ValueBool() + apiModel.AutoCreateUser = r.AutoCreateUser.ValueBool() + apiModel.AllowUserToAccessProfile = r.AllowUserToAccessProfile.ValueBool() + apiModel.RemoteUserRequestVariable = r.RemoteUserRequestVariable.ValueString() + apiModel.SyncLDAPGroups = r.SyncLDAPGroups.ValueBool() + + return diags +} + +func (r *HTTPSSOSettingsResourceModel) fromAPIModel(_ context.Context, apiModel *HTTPSSOSettingsAPIModel) (ds diag.Diagnostics) { + r.Proxied = types.BoolValue(apiModel.Proxied) + r.AutoCreateUser = types.BoolValue(apiModel.AutoCreateUser) + r.AllowUserToAccessProfile = types.BoolValue(apiModel.AllowUserToAccessProfile) + r.RemoteUserRequestVariable = types.StringValue(apiModel.RemoteUserRequestVariable) + r.SyncLDAPGroups = types.BoolValue(apiModel.SyncLDAPGroups) + + return +} + +type HTTPSSOSettingsAPIModel struct { + Proxied bool `json:"http_sso_proxied"` + AutoCreateUser bool `json:"auto_create_user"` + AllowUserToAccessProfile bool `json:"allow_user_to_access_profile"` + RemoteUserRequestVariable string `json:"remote_user_request_variable"` + SyncLDAPGroups bool `json:"sync_ldap_groups"` +} + +func (r *HTTPSSOSettingsResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = schema.Schema{ + Attributes: map[string]schema.Attribute{ + "proxied": schema.BoolAttribute{ + Required: true, + MarkdownDescription: "When set, Artifactory trusts incoming requests and reuses the remote user originally set on the request by the SSO of the HTTP server. This is useful if you want to use existing enterprise SSO integrations, such as the powerful authentication schemes provided by Apache (mod_auth_ldap, mod_auth_ntlm, mod_auth_kerb, etc.). When Artifactory is deployed as a webapp on Tomcat behind Apache: If using mod_jk, be sure to use the `JkEnvVar REMOTE_USER` directive in Apache's configuration.", + }, + "auto_create_user": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + MarkdownDescription: "When set, authenticated users are automatically created in Artifactory. When not set, for every request from an SSO user, the user is temporarily associated with default groups (if such groups are defined), and the permissions for these groups apply. Without automatic user creation, you must manually create the user inside Artifactory to manage user permissions not attached to their default groups. Default to `false`.", + }, + "allow_user_to_access_profile": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + MarkdownDescription: "Auto created users will have access to their profile page and will be able to perform actions such as generating an API key. Default to `false`.", + }, + "remote_user_request_variable": schema.StringAttribute{ + Optional: true, + Computed: true, + Validators: []validator.String{ + stringvalidator.LengthAtLeast(1), + }, + Default: stringdefault.StaticString("REMOTE_USER"), + MarkdownDescription: "The name of the HTTP request variable to use for extracting the user identity. Default to `REMOTE_USER`.", + }, + "sync_ldap_groups": schema.BoolAttribute{ + Optional: true, + Computed: true, + Default: booldefault.StaticBool(false), + MarkdownDescription: "When set, the user will be associated with the groups returned in the LDAP login response. Note that the user's association with the returned groups is persistent if the `auto_create_user` is set. Default to `false`.", + }, + }, + MarkdownDescription: "Provides a JFrog [HTTP SSO Settings](https://jfrog.com/help/r/jfrog-platform-administration-documentation/http-sso) resource. This allows you to reuse existing HTTP-based SSO infrastructures with the JFrog Platform Unit (JPD), such as the SSO modules offered by Apache HTTPd.", + } +} + +func (r *HTTPSSOSettingsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + go util.SendUsageResourceCreate(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) + + var plan HTTPSSOSettingsResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var httpSSOSettings HTTPSSOSettingsAPIModel + resp.Diagnostics.Append(plan.toAPIModel(ctx, &httpSSOSettings)...) + if resp.Diagnostics.HasError() { + return + } + + var jfrogErrors util.JFrogErrors + response, err := r.ProviderData.Client.R(). + SetBody(httpSSOSettings). + SetError(&jfrogErrors). + Put(r.DocumentEndpoint) + + if err != nil { + utilfw.UnableToCreateResourceError(resp, err.Error()) + return + } + + if response.IsError() { + utilfw.UnableToCreateResourceError(resp, jfrogErrors.String()) + return + } + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *HTTPSSOSettingsResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + go util.SendUsageResourceRead(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) + + var state HTTPSSOSettingsResourceModel + + resp.Diagnostics.Append(req.State.Get(ctx, &state)...) + if resp.Diagnostics.HasError() { + return + } + + var httpSSOSettings HTTPSSOSettingsAPIModel + + response, err := r.ProviderData.Client.R(). + SetResult(&httpSSOSettings). + Get(r.DocumentEndpoint) + + if err != nil { + utilfw.UnableToRefreshResourceError(resp, err.Error()) + return + } + + // Treat HTTP 404 Not Found status as a signal to recreate resource + // and return early + if response.StatusCode() == http.StatusNotFound { + resp.State.RemoveResource(ctx) + return + } + + if response.IsError() { + 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, &httpSSOSettings)...) + if resp.Diagnostics.HasError() { + return + } + + resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) +} + +func (r *HTTPSSOSettingsResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + go util.SendUsageResourceUpdate(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) + + var plan HTTPSSOSettingsResourceModel + + // Read Terraform plan data into the model + resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) + if resp.Diagnostics.HasError() { + return + } + + var httpSSOSettings HTTPSSOSettingsAPIModel + resp.Diagnostics.Append(plan.toAPIModel(ctx, &httpSSOSettings)...) + if resp.Diagnostics.HasError() { + return + } + + response, err := r.ProviderData.Client.R(). + SetBody(httpSSOSettings). + Put(r.DocumentEndpoint) + + if err != nil { + utilfw.UnableToUpdateResourceError(resp, err.Error()) + return + } + + if response.IsError() { + utilfw.UnableToUpdateResourceError(resp, response.String()) + return + } + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) +} + +func (r *HTTPSSOSettingsResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + go util.SendUsageResourceDelete(ctx, r.ProviderData.Client.R(), r.ProviderData.ProductId, r.TypeName) + + resp.Diagnostics.AddWarning( + "Unable to Delete Resource", + "HTTP SSO settings cannot be deleted.", + ) +} + +func (r *HTTPSSOSettingsResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + resource.ImportStatePassthroughID(ctx, path.Root("remote_user_request_variable"), req, resp) +} diff --git a/pkg/platform/resource_http_sso_settings_test.go b/pkg/platform/resource_http_sso_settings_test.go new file mode 100644 index 0000000..d6e1dcf --- /dev/null +++ b/pkg/platform/resource_http_sso_settings_test.go @@ -0,0 +1,79 @@ +package platform_test + +import ( + "fmt" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/jfrog/terraform-provider-shared/testutil" + "github.com/jfrog/terraform-provider-shared/util" +) + +func TestAccHTTPSSOSettings_full(t *testing.T) { + _, fqrn, name := testutil.MkNames("test-http-sso-settings", "platform_http_sso_settings") + + temp := ` + resource "platform_http_sso_settings" "{{ .name }}" { + proxied = {{ .proxied }} + auto_create_user = {{ .autoCreateUser }} + allow_user_to_access_profile = {{ .allowUserToAccessProfile }} + remote_user_request_variable = "{{ .remoteUserRequestVariable }}" + sync_ldap_groups = {{ .syncLDAPGroups }} + }` + + testData := map[string]string{ + "name": name, + "proxied": "true", + "autoCreateUser": fmt.Sprintf("%t", testutil.RandBool()), + "allowUserToAccessProfile": fmt.Sprintf("%t", testutil.RandBool()), + "remoteUserRequestVariable": "TEST", + "syncLDAPGroups": fmt.Sprintf("%t", testutil.RandBool()), + } + + config := util.ExecuteTemplate(name, temp, testData) + + updatedTestData := map[string]string{ + "name": name, + "proxied": "false", + "autoCreateUser": fmt.Sprintf("%t", testutil.RandBool()), + "allowUserToAccessProfile": fmt.Sprintf("%t", testutil.RandBool()), + "remoteUserRequestVariable": "TEST", + "syncLDAPGroups": fmt.Sprintf("%t", testutil.RandBool()), + } + + updatedConfig := util.ExecuteTemplate(name, temp, updatedTestData) + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + ProtoV6ProviderFactories: testAccProviders(), + Steps: []resource.TestStep{ + { + Config: config, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(fqrn, "proxied", testData["proxied"]), + resource.TestCheckResourceAttr(fqrn, "auto_create_user", testData["autoCreateUser"]), + resource.TestCheckResourceAttr(fqrn, "allow_user_to_access_profile", testData["allowUserToAccessProfile"]), + resource.TestCheckResourceAttr(fqrn, "remote_user_request_variable", testData["remoteUserRequestVariable"]), + resource.TestCheckResourceAttr(fqrn, "sync_ldap_groups", testData["syncLDAPGroups"]), + ), + }, + { + Config: updatedConfig, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(fqrn, "proxied", updatedTestData["proxied"]), + resource.TestCheckResourceAttr(fqrn, "auto_create_user", updatedTestData["autoCreateUser"]), + resource.TestCheckResourceAttr(fqrn, "allow_user_to_access_profile", updatedTestData["allowUserToAccessProfile"]), + resource.TestCheckResourceAttr(fqrn, "remote_user_request_variable", updatedTestData["remoteUserRequestVariable"]), + resource.TestCheckResourceAttr(fqrn, "sync_ldap_groups", updatedTestData["syncLDAPGroups"]), + ), + }, + { + ResourceName: fqrn, + ImportState: true, + ImportStateVerify: true, + ImportStateId: name, + ImportStateVerifyIdentifierAttribute: "remote_user_request_variable", + }, + }, + }) +} From 941ff70bc131fbb598dfb15b415cdc01cee895c1 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 19 Dec 2024 11:07:38 -0800 Subject: [PATCH 2/5] Update CHANGELOG --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16626bc..40c6beb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +## 2.1.0 (December 20, 2024) + +FEATURES: + +**New Resource:** + +* `platform_http_sso_settings` - Resource to manage HTTP SSO settings. PR: [#185](https://github.com/jfrog/terraform-provider-platform/pull/185) + ## 2.0.0 (December 18, 2024). Tested on Artifactory 7.98.11 with Terraform 1.10.2 and OpenTofu 1.8.7 NOTES: From 841401f4c9b0c31a1703a42efb7620c42af16db3 Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Thu, 19 Dec 2024 11:08:05 -0800 Subject: [PATCH 3/5] Update golang.org/x/net --- go.mod | 2 +- go.sum | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/go.mod b/go.mod index e5f1877..5c767ca 100644 --- a/go.mod +++ b/go.mod @@ -84,7 +84,7 @@ require ( golang.org/x/crypto v0.31.0 // indirect golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819 // indirect golang.org/x/mod v0.21.0 // indirect - golang.org/x/net v0.28.0 // indirect + golang.org/x/net v0.33.0 // indirect golang.org/x/sys v0.28.0 // indirect golang.org/x/text v0.21.0 // indirect golang.org/x/tools v0.22.0 // indirect diff --git a/go.sum b/go.sum index c6deb74..6ec1be5 100644 --- a/go.sum +++ b/go.sum @@ -232,6 +232,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= +golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= +golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= 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= From d9f195eb7cafdb178166dfe89d47389f9757c19f Mon Sep 17 00:00:00 2001 From: JFrog CI Date: Thu, 19 Dec 2024 19:13:50 +0000 Subject: [PATCH 4/5] JFrog Pipelines - Add Artifactory version to CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 40c6beb..c7dea9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -## 2.1.0 (December 20, 2024) +## 2.1.0 (December 20, 2024). Tested on Artifactory 7.98.11 with Terraform 1.10.3 and OpenTofu 1.8.7 FEATURES: From 8cb99838c500b6d99ff230198e08ca9b8818d4fb Mon Sep 17 00:00:00 2001 From: Alex Hung Date: Fri, 20 Dec 2024 09:43:17 -0800 Subject: [PATCH 5/5] Refactor provide to use JFrogProvider type --- go.mod | 2 +- go.sum | 6 +- pkg/platform/provider.go | 174 ++------------------------------------- 3 files changed, 12 insertions(+), 170 deletions(-) diff --git a/go.mod b/go.mod index 5c767ca..4b5619c 100644 --- a/go.mod +++ b/go.mod @@ -12,7 +12,7 @@ require ( github.com/hashicorp/terraform-plugin-framework-validators v0.16.0 github.com/hashicorp/terraform-plugin-go v0.25.0 github.com/hashicorp/terraform-plugin-testing v1.11.0 - github.com/jfrog/terraform-provider-shared v1.27.1 + github.com/jfrog/terraform-provider-shared v1.28.0 github.com/samber/lo v1.47.0 ) diff --git a/go.sum b/go.sum index 6ec1be5..f1aa940 100644 --- a/go.sum +++ b/go.sum @@ -130,8 +130,8 @@ 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/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= -github.com/jfrog/terraform-provider-shared v1.27.1 h1:uVp3RN/H3mZm6f9mw917ZjOPLELfb7Zv3I1YrKjC9BI= -github.com/jfrog/terraform-provider-shared v1.27.1/go.mod h1:6J2ITGCAs6hAuFClSDeXHz0sJEIElDIMu4A/+WB1zvs= +github.com/jfrog/terraform-provider-shared v1.28.0 h1:WKsqc13p0RdaR4yjzr2T4OEtAvGpR62hTpnUAV1Obh4= +github.com/jfrog/terraform-provider-shared v1.28.0/go.mod h1:0snzhXJR++SKRxpCtZnIFEIeP/Ud5zuF3Lm6OZncb4k= 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= @@ -230,8 +230,6 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL 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.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= -golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I= golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/pkg/platform/provider.go b/pkg/platform/provider.go index a86f63c..cbe309e 100644 --- a/pkg/platform/provider.go +++ b/pkg/platform/provider.go @@ -2,152 +2,32 @@ package platform import ( "context" - "fmt" - "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/provider" - "github.com/hashicorp/terraform-plugin-framework/provider/schema" "github.com/hashicorp/terraform-plugin-framework/resource" - "github.com/hashicorp/terraform-plugin-framework/schema/validator" - "github.com/hashicorp/terraform-plugin-framework/types" - "github.com/jfrog/terraform-provider-shared/client" "github.com/jfrog/terraform-provider-shared/util" - validator_string "github.com/jfrog/terraform-provider-shared/validator/fw/string" ) -var Version = "2.0.0" - // needs to be exported so make file can update this -var productId = "terraform-provider-platform/" + Version +var Version = "2.0.0" -var _ provider.Provider = (*PlatformProvider)(nil) +var _ provider.Provider = &PlatformProvider{} type PlatformProvider struct { - Meta util.ProviderMetadata -} - -type platformProviderModel struct { - Url types.String `tfsdk:"url"` - AccessToken types.String `tfsdk:"access_token"` - OIDCProviderName types.String `tfsdk:"oidc_provider_name"` - TFCCredentialTagName types.String `tfsdk:"tfc_credential_tag_name"` + util.JFrogProvider } func NewProvider() func() provider.Provider { return func() provider.Provider { - return &PlatformProvider{} - } -} - -func (p *PlatformProvider) Configure(ctx context.Context, req provider.ConfigureRequest, resp *provider.ConfigureResponse) { - // Check environment variables, first available OS variable will be assigned to the var - url := util.CheckEnvVars([]string{"JFROG_URL"}, "") - accessToken := util.CheckEnvVars([]string{"JFROG_ACCESS_TOKEN"}, "") - - var config platformProviderModel - - // Read configuration data into model - resp.Diagnostics.Append(req.Config.Get(ctx, &config)...) - if resp.Diagnostics.HasError() { - return - } - - if config.Url.ValueString() != "" { - url = config.Url.ValueString() - } - - if url == "" { - resp.Diagnostics.AddError( - "Missing URL Configuration", - "While configuring the provider, the url was not found in the JFROG_URL environment variable or provider configuration block url attribute.", - ) - return - } - - restyClient, err := client.Build(url, productId) - if err != nil { - resp.Diagnostics.AddError( - "Error creating Resty client", - err.Error(), - ) - return - } - - oidcProviderName := config.OIDCProviderName.ValueString() - if oidcProviderName != "" { - oidcAccessToken, err := util.OIDCTokenExchange(ctx, restyClient, oidcProviderName, config.TFCCredentialTagName.ValueString()) - if err != nil { - resp.Diagnostics.AddError( - "Failed OIDC ID token exchange", - err.Error(), - ) - return - } - - // use token from OIDC provider, which should take precedence over - // environment variable data, if found. - if oidcAccessToken != "" { - accessToken = oidcAccessToken - } - } - - // use token from configuration, which should take precedence over - // environment variable data or OIDC provider, if found. - if config.AccessToken.ValueString() != "" { - accessToken = config.AccessToken.ValueString() - } - - if accessToken == "" { - resp.Diagnostics.AddWarning( - "Missing JFrog Access Token", - "Access Token was not found in the JFROG_ACCESS_TOKEN environment variable, provider configuration block access_token attribute, or Terraform Cloud TFC_WORKLOAD_IDENTITY_TOKEN environment variable. Platform functionality will be affected.", - ) - } - - artifactoryVersion := "" - if len(accessToken) > 0 { - _, err = client.AddAuth(restyClient, "", accessToken) - if err != nil { - resp.Diagnostics.AddError( - "Error adding Auth to Resty client", - err.Error(), - ) - return - } - - version, err := util.GetArtifactoryVersion(restyClient) - if err != nil { - resp.Diagnostics.AddWarning( - "Error getting Artifactory version", - fmt.Sprintf("Provider functionality might be affected by the absence of Artifactory version. %v", err), - ) + return &PlatformProvider{ + JFrogProvider: util.JFrogProvider{ + TypeName: "platform", + ProductID: "terraform-provider-platform/" + Version, + Version: Version, + }, } - - artifactoryVersion = version - - featureUsage := fmt.Sprintf("Terraform/%s", req.TerraformVersion) - go util.SendUsage(ctx, restyClient.R(), productId, featureUsage) - } - - featureUsage := fmt.Sprintf("Terraform/%s", req.TerraformVersion) - go util.SendUsage(ctx, restyClient.R(), productId, featureUsage) - - meta := util.ProviderMetadata{ - Client: restyClient, - ArtifactoryVersion: artifactoryVersion, - ProductId: productId, } - - p.Meta = meta - - resp.DataSourceData = meta - resp.ResourceData = meta -} - -func (p *PlatformProvider) Metadata(ctx context.Context, req provider.MetadataRequest, resp *provider.MetadataResponse) { - resp.TypeName = "platform" - resp.Version = Version } func (p *PlatformProvider) DataSources(ctx context.Context) []func() datasource.DataSource { @@ -175,39 +55,3 @@ func (p *PlatformProvider) Resources(ctx context.Context) []func() resource.Reso NewWorkerServiceResource, } } - -func (p *PlatformProvider) Schema(ctx context.Context, req provider.SchemaRequest, resp *provider.SchemaResponse) { - resp.Schema = schema.Schema{ - Attributes: map[string]schema.Attribute{ - "url": schema.StringAttribute{ - Optional: true, - Validators: []validator.String{ - validator_string.IsURLHttpOrHttps(), - }, - MarkdownDescription: "JFrog Platform URL. This can also be sourced from the `JFROG_URL` environment variable.", - }, - "access_token": schema.StringAttribute{ - Optional: true, - Sensitive: true, - Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), - }, - MarkdownDescription: "This is a access token that can be given to you by your admin under `Platform Configuration -> User Management -> Access Tokens`. This can also be sourced from the `JFROG_ACCESS_TOKEN` environment variable.", - }, - "oidc_provider_name": schema.StringAttribute{ - Optional: true, - Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), - }, - MarkdownDescription: "OIDC provider name. See [Configure an OIDC Integration](https://jfrog.com/help/r/jfrog-platform-administration-documentation/configure-an-oidc-integration) for more details.", - }, - "tfc_credential_tag_name": schema.StringAttribute{ - Optional: true, - Validators: []validator.String{ - stringvalidator.LengthAtLeast(1), - }, - Description: "Terraform Cloud Workload Identity Token tag name. Use for generating multiple TFC workload identity tokens. When set, the provider will attempt to use env var with this tag name as suffix. **Note:** this is case sensitive, so if set to `JFROG`, then env var `TFC_WORKLOAD_IDENTITY_TOKEN_JFROG` is used instead of `TFC_WORKLOAD_IDENTITY_TOKEN`. See [Generating Multiple Tokens](https://developer.hashicorp.com/terraform/cloud-docs/workspaces/dynamic-provider-credentials/manual-generation#generating-multiple-tokens) on HCP Terraform for more details.", - }, - }, - } -}