diff --git a/CHANGELOG.md b/CHANGELOG.md
index 99b415b3..d93e22cf 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,9 @@
+## 2.7.0 (May 29, 2024). Tested on Artifactory 7.84.12 and Xray 3.96.1
+
+FEATURES:
+
+* resource/xray_binary_manager_repos and resource/xray_binary_manager_builds: Add new resources to support adding repositories or builds to binary manager indexing configuration. PR: [#194](https://github.com/jfrog/terraform-provider-xray/pull/194) Issue: [#129](https://github.com/jfrog/terraform-provider-xray/issues/129)
+
## 2.6.0 (May 6, 2024). Tested on Artifactory 7.84.11 and Xray 3.95.7
FEATURES:
diff --git a/GNUmakefile b/GNUmakefile
index d52afa93..3fce914f 100644
--- a/GNUmakefile
+++ b/GNUmakefile
@@ -59,7 +59,7 @@ fmt:
@go fmt ./...
doc:
- rm docs/debug.md
+ rm -f docs/debug.md
go generate
.PHONY: build fmt
diff --git a/docs/resources/binary_manager_builds.md b/docs/resources/binary_manager_builds.md
new file mode 100644
index 00000000..d65f7004
--- /dev/null
+++ b/docs/resources/binary_manager_builds.md
@@ -0,0 +1,29 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "xray_binary_manager_builds Resource - terraform-provider-xray"
+subcategory: ""
+description: |-
+ Provides an Xray Binary Manager Builds Indexing configuration resource. See Indexing Xray Resources https://jfrog.com/help/r/jfrog-security-documentation/add-or-remove-resources-from-indexing and REST API https://jfrog.com/help/r/xray-rest-apis/update-builds-indexing-configuration for more details.
+---
+
+# xray_binary_manager_builds (Resource)
+
+Provides an Xray Binary Manager Builds Indexing configuration resource. See [Indexing Xray Resources](https://jfrog.com/help/r/jfrog-security-documentation/add-or-remove-resources-from-indexing) and [REST API](https://jfrog.com/help/r/xray-rest-apis/update-builds-indexing-configuration) for more details.
+
+
+
+
+## Schema
+
+### Required
+
+- `id` (String) ID of the binary manager, e.g. 'default'
+- `indexed_builds` (Set of String) Builds to be indexed.
+
+### Optional
+
+- `project_key` (String) For Xray version 3.21.2 and above with Projects, a Project Admin with Index Resources privilege can maintain the indexed and not indexed repositories in a given binary manger using this resource in the scope of a project.
+
+### Read-Only
+
+- `non_indexed_builds` (Set of String) Non-indexed builds for output.
diff --git a/docs/resources/binary_manager_repos.md b/docs/resources/binary_manager_repos.md
new file mode 100644
index 00000000..9676fad0
--- /dev/null
+++ b/docs/resources/binary_manager_repos.md
@@ -0,0 +1,48 @@
+---
+# generated by https://github.com/hashicorp/terraform-plugin-docs
+page_title: "xray_binary_manager_repos Resource - terraform-provider-xray"
+subcategory: ""
+description: |-
+ Provides an Xray Binary Manager Repository Indexing configuration resource. See Indexing Xray Resources https://jfrog.com/help/r/jfrog-security-documentation/add-or-remove-resources-from-indexing and REST API https://jfrog.com/help/r/xray-rest-apis/update-repos-indexing-configuration for more details.
+---
+
+# xray_binary_manager_repos (Resource)
+
+Provides an Xray Binary Manager Repository Indexing configuration resource. See [Indexing Xray Resources](https://jfrog.com/help/r/jfrog-security-documentation/add-or-remove-resources-from-indexing) and [REST API](https://jfrog.com/help/r/xray-rest-apis/update-repos-indexing-configuration) for more details.
+
+
+
+
+## Schema
+
+### Required
+
+- `id` (String) ID of the binary manager, e.g. 'default'
+- `indexed_repos` (Attributes Set) Repositories to be indexed. (see [below for nested schema](#nestedatt--indexed_repos))
+
+### Optional
+
+- `project_key` (String) For Xray version 3.21.2 and above with Projects, a Project Admin with Index Resources privilege can maintain the indexed and not indexed repositories in a given binary manger using this resource in the scope of a project.
+
+### Read-Only
+
+- `non_indexed_repos` (Attributes Set) Non-indexed repositories for output. (see [below for nested schema](#nestedatt--non_indexed_repos))
+
+
+### Nested Schema for `indexed_repos`
+
+Required:
+
+- `name` (String) Name of the repository
+- `package_type` (String) Artifactory package type. Valid value: Alpine, Bower, Cargo, Chef, Cocoapods, Composer, Conan, Conda, Cran, Debian, Docker, Gems, Generic, Gitlfs, Go, Gradle, Helm, Ivy, Maven, Npm, Nuget, Opkg, P2, Puppet, Pypi, Rpm, Sbt, Swift, Terraform, Terraformbackend, Vagrant, Vcs
+- `type` (String) Repository type. Valid value: local, remote, federated
+
+
+
+### Nested Schema for `non_indexed_repos`
+
+Required:
+
+- `name` (String)
+- `package_type` (String)
+- `type` (String)
diff --git a/go.mod b/go.mod
index c90f14ac..7d09b0a9 100644
--- a/go.mod
+++ b/go.mod
@@ -5,7 +5,7 @@ module github.com/jfrog/terraform-provider-xray
require (
github.com/go-resty/resty/v2 v2.13.1
- github.com/hashicorp/go-version v1.6.0
+ github.com/hashicorp/go-version v1.7.0
github.com/hashicorp/terraform-plugin-docs v0.19.2
github.com/hashicorp/terraform-plugin-framework v1.8.0
github.com/hashicorp/terraform-plugin-framework-validators v0.12.0
@@ -14,9 +14,10 @@ require (
github.com/hashicorp/terraform-plugin-mux v0.16.0
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0
github.com/hashicorp/terraform-plugin-testing v1.5.1
- github.com/jfrog/terraform-provider-shared v1.25.2
+ github.com/jfrog/terraform-provider-shared v1.25.3
github.com/samber/lo v1.39.0
golang.org/x/exp v0.0.0-20230809150735-7b3493d9a819
+ golang.org/x/text v0.15.0
)
require (
@@ -79,7 +80,6 @@ require (
golang.org/x/mod v0.16.0 // indirect
golang.org/x/net v0.25.0 // indirect
golang.org/x/sys v0.20.0 // indirect
- golang.org/x/text v0.15.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-20240227224415-6ceb2ff114de // indirect
diff --git a/go.sum b/go.sum
index 91c7f796..7917b682 100644
--- a/go.sum
+++ b/go.sum
@@ -89,8 +89,8 @@ github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
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/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
+github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hc-install v0.6.4 h1:QLqlM56/+SIIGvGcfFiwMY3z5WGXT066suo/v9Km8e0=
github.com/hashicorp/hc-install v0.6.4/go.mod h1:05LWLy8TD842OtgcfBbOT0WMoInBMUSHjmDx10zuBIA=
github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc=
@@ -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.25.2 h1:OdomF5La2wXEeI7k9muP6d2oclsZI5sgOldkEpVD2sc=
-github.com/jfrog/terraform-provider-shared v1.25.2/go.mod h1:L987Z8XO4cuv7ys4Tw6sP/LESw7z0Dji0U2ysR8FUP4=
+github.com/jfrog/terraform-provider-shared v1.25.3 h1:0P1H5WkNmhrXvXAo5pY1XkVn81CkZxcttDqZTESlKBw=
+github.com/jfrog/terraform-provider-shared v1.25.3/go.mod h1:QthwPRUALElMt2RTGqoeB/3Vztx626YPBzIAoqEp0w0=
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=
diff --git a/pkg/xray/provider/framework.go b/pkg/xray/provider/framework.go
index 7394d5b8..37ae7f50 100644
--- a/pkg/xray/provider/framework.go
+++ b/pkg/xray/provider/framework.go
@@ -184,6 +184,8 @@ func (p *XrayProvider) Configure(ctx context.Context, req provider.ConfigureRequ
// Resources satisfies the provider.Provider interface for ArtifactoryProvider.
func (p *XrayProvider) Resources(ctx context.Context) []func() resource.Resource {
return []func() resource.Resource{
+ xray_resource.NewBinaryManagerReposResource,
+ xray_resource.NewBinaryManagerBuildsResource,
xray_resource.NewSettingsResource,
xray_resource.NewWebhookResource,
xray_resource.NewWorkersCountResource,
diff --git a/pkg/xray/resource/resource_xray_binary_manager_builds.go b/pkg/xray/resource/resource_xray_binary_manager_builds.go
new file mode 100644
index 00000000..6d283a99
--- /dev/null
+++ b/pkg/xray/resource/resource_xray_binary_manager_builds.go
@@ -0,0 +1,322 @@
+package xray
+
+import (
+ "context"
+ "strings"
+
+ "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/planmodifier"
+ "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"
+ validatorfw_string "github.com/jfrog/terraform-provider-shared/validator/fw/string"
+)
+
+const BinaryManagerBuildsEndpoint = "xray/api/v1/binMgr/{id}/builds"
+
+var _ resource.Resource = &WebhookResource{}
+
+func NewBinaryManagerBuildsResource() resource.Resource {
+ return &BinaryManagerBuildsResource{}
+}
+
+type BinaryManagerBuildsResource struct {
+ ProviderData util.ProviderMetadata
+ TypeName string
+}
+
+func (r *BinaryManagerBuildsResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_binary_manager_builds"
+ r.TypeName = resp.TypeName
+}
+
+type BinaryManagerBuildsResourceModel struct {
+ ID types.String `tfsdk:"id"`
+ ProjectKey types.String `tfsdk:"project_key"`
+ IndexedBuilds types.Set `tfsdk:"indexed_builds"`
+ NonIndexedBuilds types.Set `tfsdk:"non_indexed_builds"`
+}
+
+func (m BinaryManagerBuildsResourceModel) toAPIModel(ctx context.Context, apiModel *BinaryManagerBuildsAPIModel) (ds diag.Diagnostics) {
+ var indexedBuilds []string
+ ds.Append(m.IndexedBuilds.ElementsAs(ctx, &indexedBuilds, false)...)
+
+ *apiModel = BinaryManagerBuildsAPIModel{
+ BinManagerID: m.ID.ValueString(),
+ IndexedBuilds: indexedBuilds,
+ }
+
+ return
+}
+
+func (m *BinaryManagerBuildsResourceModel) fromAPIModel(ctx context.Context, apiModel BinaryManagerBuildsAPIModel) (ds diag.Diagnostics) {
+ m.ID = types.StringValue(apiModel.BinManagerID)
+
+ indexedBuilds, d := types.SetValueFrom(ctx, types.StringType, apiModel.IndexedBuilds)
+ if d != nil {
+ ds.Append(d...)
+ }
+ m.IndexedBuilds = indexedBuilds
+
+ nonIndexedBuilds, d := types.SetValueFrom(ctx, types.StringType, apiModel.NonIndexedBuilds)
+ if d != nil {
+ ds.Append(d...)
+ }
+ m.NonIndexedBuilds = nonIndexedBuilds
+
+ return
+}
+
+type BinaryManagerBuildsAPIModel struct {
+ BinManagerID string `json:"bin_mgr_id"`
+ IndexedBuilds []string `json:"indexed_builds"`
+ NonIndexedBuilds []string `json:"non_indexed_builds"`
+}
+
+func (r *BinaryManagerBuildsResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.LengthAtLeast(1),
+ },
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ Description: "ID of the binary manager, e.g. 'default'",
+ },
+ "project_key": schema.StringAttribute{
+ Optional: true,
+ Validators: []validator.String{
+ validatorfw_string.ProjectKey(),
+ },
+ Description: "For Xray version 3.21.2 and above with Projects, a Project Admin with Index Resources privilege can maintain the indexed and not indexed repositories in a given binary manger using this resource in the scope of a project.",
+ },
+ "indexed_builds": schema.SetAttribute{
+ ElementType: types.StringType,
+ Required: true,
+ Description: "Builds to be indexed.",
+ },
+ "non_indexed_builds": schema.SetAttribute{
+ ElementType: types.StringType,
+ Computed: true,
+ Description: "Non-indexed builds for output.",
+ },
+ },
+ Description: "Provides an Xray Binary Manager Builds Indexing configuration resource. See [Indexing Xray Resources](https://jfrog.com/help/r/jfrog-security-documentation/add-or-remove-resources-from-indexing) " +
+ "and [REST API](https://jfrog.com/help/r/xray-rest-apis/update-builds-indexing-configuration) for more details.",
+ }
+}
+
+func (r *BinaryManagerBuildsResource) 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.ProviderMetadata)
+}
+
+func (r *BinaryManagerBuildsResource) 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 BinaryManagerBuildsResourceModel
+
+ // Read Terraform plan data into the model
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ request, err := getRestyRequest(r.ProviderData.Client, plan.ProjectKey.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "failed to get Resty client",
+ err.Error(),
+ )
+ return
+ }
+
+ var builds BinaryManagerBuildsAPIModel
+ resp.Diagnostics.Append(plan.toAPIModel(ctx, &builds)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ response, err := request.
+ SetPathParam("id", plan.ID.ValueString()).
+ SetBody(builds).
+ Put(BinaryManagerBuildsEndpoint)
+ if err != nil {
+ utilfw.UnableToCreateResourceError(resp, err.Error())
+ return
+ }
+ if response.IsError() {
+ utilfw.UnableToCreateResourceError(resp, response.String())
+ return
+ }
+
+ // get the indexed and non-indexed repos list since the PUT
+ // doesn't return the list
+ response, err = request.
+ SetPathParam("id", plan.ID.ValueString()).
+ SetResult(&builds).
+ Get(BinaryManagerBuildsEndpoint)
+ if err != nil {
+ utilfw.UnableToCreateResourceError(resp, err.Error())
+ return
+ }
+
+ if response.IsError() {
+ utilfw.UnableToCreateResourceError(resp, response.String())
+ return
+ }
+
+ resp.Diagnostics.Append(plan.fromAPIModel(ctx, builds)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Save data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
+}
+
+func (r *BinaryManagerBuildsResource) 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 BinaryManagerBuildsResourceModel
+
+ // Read Terraform state data into the model
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ request, err := getRestyRequest(r.ProviderData.Client, state.ProjectKey.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "failed to get Resty client",
+ err.Error(),
+ )
+ return
+ }
+
+ var builds BinaryManagerBuildsAPIModel
+
+ response, err := request.
+ SetPathParam("id", state.ID.ValueString()).
+ SetResult(&builds).
+ Get(BinaryManagerBuildsEndpoint)
+ if err != nil {
+ utilfw.UnableToRefreshResourceError(resp, err.Error())
+ return
+ }
+
+ if response.IsError() {
+ utilfw.UnableToRefreshResourceError(resp, response.String())
+ return
+ }
+
+ resp.Diagnostics.Append(state.fromAPIModel(ctx, builds)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Save data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
+}
+
+func (r *BinaryManagerBuildsResource) 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 BinaryManagerBuildsResourceModel
+
+ // Read Terraform plan data into the model
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ request, err := getRestyRequest(r.ProviderData.Client, plan.ProjectKey.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "failed to get Resty client",
+ err.Error(),
+ )
+ return
+ }
+
+ var builds BinaryManagerBuildsAPIModel
+ resp.Diagnostics.Append(plan.toAPIModel(ctx, &builds)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ response, err := request.
+ SetPathParam("id", plan.ID.ValueString()).
+ SetBody(builds).
+ Put(BinaryManagerBuildsEndpoint)
+ if err != nil {
+ utilfw.UnableToUpdateResourceError(resp, err.Error())
+ return
+ }
+ if response.IsError() {
+ utilfw.UnableToUpdateResourceError(resp, response.String())
+ return
+ }
+
+ // get the indexed and non-indexed repos list since the PUT
+ // doesn't return the list
+ response, err = request.
+ SetPathParam("id", plan.ID.ValueString()).
+ SetResult(&builds).
+ Get(BinaryManagerBuildsEndpoint)
+ if err != nil {
+ utilfw.UnableToUpdateResourceError(resp, err.Error())
+ return
+ }
+
+ if response.IsError() {
+ utilfw.UnableToUpdateResourceError(resp, response.String())
+ return
+ }
+
+ resp.Diagnostics.Append(plan.fromAPIModel(ctx, builds)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Save data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
+}
+
+func (r *BinaryManagerBuildsResource) 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(
+ "Repository indexing configuration cannot be deleted",
+ "The resource is deleted from Terraform but the repository indexing configuration remains unchanged in Xray.",
+ )
+
+ // If the logic reaches here, it implicitly succeeded and will remove
+ // the resource from state if there are no other errors.
+}
+
+// ImportState imports the resource into the Terraform state.
+func (r *BinaryManagerBuildsResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ parts := strings.SplitN(req.ID, ":", 2)
+
+ if len(parts) > 0 && parts[0] != "" {
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), parts[0])...)
+ }
+
+ if len(parts) == 2 && parts[1] != "" {
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_key"), parts[1])...)
+ }
+}
diff --git a/pkg/xray/resource/resource_xray_binary_manager_builds_test.go b/pkg/xray/resource/resource_xray_binary_manager_builds_test.go
new file mode 100644
index 00000000..0083a819
--- /dev/null
+++ b/pkg/xray/resource/resource_xray_binary_manager_builds_test.go
@@ -0,0 +1,282 @@
+package xray_test
+
+import (
+ "fmt"
+ "testing"
+ "time"
+
+ "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+ "github.com/hashicorp/terraform-plugin-testing/terraform"
+ "github.com/jfrog/terraform-provider-shared/testutil"
+ "github.com/jfrog/terraform-provider-shared/util"
+ "github.com/jfrog/terraform-provider-xray/pkg/acctest"
+ "github.com/samber/lo"
+)
+
+func uploadBuild(t *testing.T, name, number, projectKey string) error {
+ type Build struct {
+ Version string `json:"version"`
+ Name string `json:"name"`
+ Number string `json:"number"`
+ Started string `json:"started"`
+ }
+
+ build := Build{
+ Version: "1.0.1",
+ Name: name,
+ Number: number,
+ Started: time.Now().Format("2006-01-02T15:04:05.000Z0700"),
+ }
+
+ restyClient := acctest.GetTestResty(t)
+
+ req := restyClient.R()
+
+ if projectKey != "" {
+ req.SetQueryParam("project", projectKey)
+ }
+
+ res, err := req.
+ SetBody(build).
+ Put("artifactory/api/build")
+
+ if err != nil {
+ return err
+ }
+
+ if res.IsError() {
+ return fmt.Errorf("%s", res.String())
+ }
+
+ return nil
+}
+
+func deleteBuild(t *testing.T, name, number, projectKey string) error {
+ type Build struct {
+ Name string `json:"buildName"`
+ BuildRepo string `json:"buildRepo"`
+ DeleteAll bool `json:"deleteAll"`
+ }
+
+ build := Build{
+ Name: name,
+ DeleteAll: true,
+ }
+
+ restyClient := acctest.GetTestResty(t)
+
+ req := restyClient.R()
+
+ if projectKey != "" {
+ build.BuildRepo = fmt.Sprintf("%s-build-info", projectKey)
+ }
+
+ res, err := req.
+ SetBody(build).
+ Post("artifactory/api/build/delete")
+
+ if err != nil {
+ return err
+ }
+
+ if res.IsError() {
+ return fmt.Errorf("%s", res.String())
+ }
+
+ return nil
+}
+
+func TestAccBinaryManagerBuilds_full(t *testing.T) {
+ _, fqrn, resourceName := testutil.MkNames("test-bin-mgr-builds", "xray_binary_manager_builds")
+
+ build1Name := fmt.Sprintf("test-build-%d", testutil.RandomInt())
+ build2Name := fmt.Sprintf("test-build-%d", testutil.RandomInt())
+
+ const template = `
+ resource "xray_binary_manager_builds" "{{ .name }}" {
+ id = "default"
+ indexed_builds = ["{{ .build1Name }}"]
+ }
+ `
+
+ testData := map[string]string{
+ "name": resourceName,
+ "build1Name": build1Name,
+ }
+
+ config := util.ExecuteTemplate("TestAccBinaryManagerBuilds_full", template, testData)
+
+ const updateTemplate = `
+ resource "xray_binary_manager_builds" "{{ .name }}" {
+ id = "default"
+ indexed_builds = ["{{ .build1Name }}", "{{ .build2Name }}"]
+ }
+
+ `
+ updatedTestData := map[string]string{
+ "name": resourceName,
+ "build1Name": build1Name,
+ "build2Name": build2Name,
+ }
+ updatedConfig := util.ExecuteTemplate("TestAccBinaryManagerBuilds_full", updateTemplate, updatedTestData)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() {
+ acctest.PreCheck(t)
+ if err := uploadBuild(t, build1Name, "1", ""); err != nil {
+ t.Fatalf("failed to upload build: %s", err)
+ }
+ if err := uploadBuild(t, build2Name, "1", ""); err != nil {
+ t.Fatalf("failed to upload build: %s", err)
+ }
+ },
+ ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories,
+ ExternalProviders: map[string]resource.ExternalProvider{
+ "artifactory": {
+ Source: "jfrog/artifactory",
+ },
+ },
+ CheckDestroy: func(*terraform.State) error {
+ if err := deleteBuild(t, build1Name, "1", ""); err != nil {
+ return err
+ }
+
+ if err := deleteBuild(t, build2Name, "1", ""); err != nil {
+ return nil
+ }
+
+ return nil
+ },
+ Steps: []resource.TestStep{
+ {
+ Config: config,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(fqrn, "id", "default"),
+ resource.TestCheckResourceAttr(fqrn, "indexed_builds.#", "1"),
+ resource.TestCheckResourceAttr(fqrn, "indexed_builds.0", build1Name),
+ ),
+ },
+ {
+ Config: updatedConfig,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(fqrn, "id", "default"),
+ resource.TestCheckResourceAttr(fqrn, "indexed_builds.#", "2"),
+ resource.TestCheckTypeSetElemAttr(fqrn, "indexed_builds.*", build1Name),
+ resource.TestCheckTypeSetElemAttr(fqrn, "indexed_builds.*", build2Name),
+ ),
+ },
+ {
+ ResourceName: fqrn,
+ ImportState: true,
+ ImportStateId: resourceName,
+ ImportStateVerify: true,
+ ImportStateVerifyIdentifierAttribute: "id",
+ },
+ },
+ })
+}
+
+func TestAccBinaryManagerBuilds_project_full(t *testing.T) {
+ _, fqrn, resourceName := testutil.MkNames("test-bin-mgr-builds", "xray_binary_manager_builds")
+
+ projectKey := lo.RandomString(6, lo.LowerCaseLettersCharset)
+
+ build1Name := fmt.Sprintf("test-build-%d", testutil.RandomInt())
+ build2Name := fmt.Sprintf("test-build-%d", testutil.RandomInt())
+
+ const template = `
+ resource "xray_binary_manager_builds" "{{ .name }}" {
+ id = "default"
+ project_key = "{{ .projectKey }}"
+ indexed_builds = ["{{ .build1Name }}"]
+ }
+ `
+
+ testData := map[string]string{
+ "name": resourceName,
+ "build1Name": build1Name,
+ "projectKey": projectKey,
+ }
+
+ config := util.ExecuteTemplate("TestAccBinaryManagerBuilds_full", template, testData)
+
+ const updateTemplate = `
+ resource "xray_binary_manager_builds" "{{ .name }}" {
+ id = "default"
+ project_key = "{{ .projectKey }}"
+ indexed_builds = ["{{ .build1Name }}", "{{ .build2Name }}"]
+ }
+
+ `
+ updatedTestData := map[string]string{
+ "name": resourceName,
+ "build1Name": build1Name,
+ "build2Name": build2Name,
+ "projectKey": projectKey,
+ }
+
+ updatedConfig := util.ExecuteTemplate("TestAccBinaryManagerBuilds_full", updateTemplate, updatedTestData)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() {
+ acctest.PreCheck(t)
+ acctest.CreateProject(t, projectKey)
+ if err := uploadBuild(t, build1Name, "1", projectKey); err != nil {
+ t.Fatalf("failed to upload build: %s", err)
+ }
+ if err := uploadBuild(t, build2Name, "1", projectKey); err != nil {
+ t.Fatalf("failed to upload build: %s", err)
+ }
+ },
+ ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories,
+ ExternalProviders: map[string]resource.ExternalProvider{
+ "artifactory": {
+ Source: "jfrog/artifactory",
+ },
+ "project": {
+ Source: "jfrog/project",
+ },
+ },
+ CheckDestroy: func(*terraform.State) error {
+ if err := deleteBuild(t, build1Name, "1", projectKey); err != nil {
+ return err
+ }
+
+ if err := deleteBuild(t, build2Name, "1", projectKey); err != nil {
+ return nil
+ }
+
+ acctest.DeleteProject(t, projectKey)
+
+ return nil
+ },
+ Steps: []resource.TestStep{
+ {
+ Config: config,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(fqrn, "id", "default"),
+ resource.TestCheckResourceAttr(fqrn, "project_key", projectKey),
+ resource.TestCheckResourceAttr(fqrn, "indexed_builds.#", "1"),
+ resource.TestCheckResourceAttr(fqrn, "indexed_builds.0", build1Name),
+ ),
+ },
+ {
+ Config: updatedConfig,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(fqrn, "id", "default"),
+ resource.TestCheckResourceAttr(fqrn, "project_key", projectKey),
+ resource.TestCheckResourceAttr(fqrn, "indexed_builds.#", "2"),
+ resource.TestCheckTypeSetElemAttr(fqrn, "indexed_builds.*", build1Name),
+ resource.TestCheckTypeSetElemAttr(fqrn, "indexed_builds.*", build2Name),
+ ),
+ },
+ {
+ ResourceName: fqrn,
+ ImportState: true,
+ ImportStateId: fmt.Sprintf("%s:%s", resourceName, projectKey),
+ ImportStateVerify: true,
+ ImportStateVerifyIdentifierAttribute: "id",
+ },
+ },
+ })
+}
diff --git a/pkg/xray/resource/resource_xray_binary_manager_repos.go b/pkg/xray/resource/resource_xray_binary_manager_repos.go
new file mode 100644
index 00000000..99ff8d3b
--- /dev/null
+++ b/pkg/xray/resource/resource_xray_binary_manager_repos.go
@@ -0,0 +1,422 @@
+package xray
+
+import (
+ "context"
+ "fmt"
+ "strings"
+
+ "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
+ "github.com/hashicorp/terraform-plugin-framework/attr"
+ "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/planmodifier"
+ "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/hashicorp/terraform-plugin-framework/types/basetypes"
+ "github.com/jfrog/terraform-provider-shared/util"
+ utilfw "github.com/jfrog/terraform-provider-shared/util/fw"
+ validatorfw_string "github.com/jfrog/terraform-provider-shared/validator/fw/string"
+ "github.com/samber/lo"
+ "golang.org/x/text/cases"
+ "golang.org/x/text/language"
+)
+
+const BinaryManagerReposEndpoint = "xray/api/v1/binMgr/{id}/repos"
+
+var _ resource.Resource = &WebhookResource{}
+
+func NewBinaryManagerReposResource() resource.Resource {
+ return &BinaryManagerReposResource{}
+}
+
+type BinaryManagerReposResource struct {
+ ProviderData util.ProviderMetadata
+ TypeName string
+}
+
+func (r *BinaryManagerReposResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
+ resp.TypeName = req.ProviderTypeName + "_binary_manager_repos"
+ r.TypeName = resp.TypeName
+}
+
+type BinaryManagerReposResourceModel struct {
+ ID types.String `tfsdk:"id"`
+ ProjectKey types.String `tfsdk:"project_key"`
+ IndexedRepos types.Set `tfsdk:"indexed_repos"`
+ NonIndexedRepos types.Set `tfsdk:"non_indexed_repos"`
+}
+
+func (m BinaryManagerReposResourceModel) toAPIModel(apiModel *BinaryManagerReposAPIModel) diag.Diagnostics {
+ var mapRepo = func(elem attr.Value, _ int) BinaryManagerRepoAPIModel {
+ attrs := elem.(types.Object).Attributes()
+
+ return BinaryManagerRepoAPIModel{
+ Name: attrs["name"].(types.String).ValueString(),
+ Type: attrs["type"].(types.String).ValueString(),
+ PackageType: attrs["package_type"].(types.String).ValueString(),
+ }
+ }
+
+ indexedRepos := lo.Map(
+ m.IndexedRepos.Elements(),
+ mapRepo,
+ )
+
+ *apiModel = BinaryManagerReposAPIModel{
+ BinManagerID: m.ID.ValueString(),
+ IndexedRepos: indexedRepos,
+ }
+
+ return nil
+}
+
+var repoResourceModelAttributeTypes map[string]attr.Type = map[string]attr.Type{
+ "name": types.StringType,
+ "type": types.StringType,
+ "package_type": types.StringType,
+}
+
+var repoSetResourceModelAttributeTypes types.ObjectType = types.ObjectType{
+ AttrTypes: repoResourceModelAttributeTypes,
+}
+
+func (m *BinaryManagerReposResourceModel) fromAPIModel(apiModel BinaryManagerReposAPIModel) diag.Diagnostics {
+ diags := diag.Diagnostics{}
+
+ m.ID = types.StringValue(apiModel.BinManagerID)
+
+ indexedRepos, ds := m.fromRepoAPIModel(apiModel.IndexedRepos)
+ if ds != nil {
+ diags = append(diags, ds...)
+ }
+ m.IndexedRepos = indexedRepos
+
+ nonIndexedRepos, ds := m.fromRepoAPIModel(apiModel.NonIndexedRepos)
+ if ds != nil {
+ diags = append(diags, ds...)
+ }
+ m.NonIndexedRepos = nonIndexedRepos
+
+ return diags
+}
+
+func (m BinaryManagerReposResourceModel) fromRepoAPIModel(repoAPIModels []BinaryManagerRepoAPIModel) (basetypes.SetValue, diag.Diagnostics) {
+ diags := diag.Diagnostics{}
+
+ repos := lo.Map(
+ repoAPIModels,
+ func(property BinaryManagerRepoAPIModel, _ int) attr.Value {
+ repo, ds := types.ObjectValue(
+ repoResourceModelAttributeTypes,
+ map[string]attr.Value{
+ "name": types.StringValue(property.Name),
+ "type": types.StringValue(property.Type),
+ "package_type": types.StringValue(property.PackageType),
+ },
+ )
+
+ if ds != nil {
+ diags = append(diags, ds...)
+ }
+
+ return repo
+ },
+ )
+
+ return types.SetValue(
+ repoSetResourceModelAttributeTypes,
+ repos,
+ )
+}
+
+type BinaryManagerReposAPIModel struct {
+ BinManagerID string `json:"bin_mgr_id"`
+ IndexedRepos []BinaryManagerRepoAPIModel `json:"indexed_repos"`
+ NonIndexedRepos []BinaryManagerRepoAPIModel `json:"non_indexed_repos"`
+}
+
+type BinaryManagerRepoAPIModel struct {
+ Name string `json:"name"`
+ Type string `json:"type"`
+ PackageType string `json:"pkg_type"`
+}
+
+var validTitledPackageTypes = lo.Map(validPackageTypes, func(packageType string, _ int) string {
+ caser := cases.Title(language.AmericanEnglish, cases.NoLower)
+ return caser.String(packageType)
+})
+
+func (r *BinaryManagerReposResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
+ resp.Schema = schema.Schema{
+ Attributes: map[string]schema.Attribute{
+ "id": schema.StringAttribute{
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.LengthAtLeast(1),
+ },
+ PlanModifiers: []planmodifier.String{
+ stringplanmodifier.RequiresReplace(),
+ },
+ Description: "ID of the binary manager, e.g. 'default'",
+ },
+ "project_key": schema.StringAttribute{
+ Optional: true,
+ Validators: []validator.String{
+ validatorfw_string.ProjectKey(),
+ },
+ Description: "For Xray version 3.21.2 and above with Projects, a Project Admin with Index Resources privilege can maintain the indexed and not indexed repositories in a given binary manger using this resource in the scope of a project.",
+ },
+ "indexed_repos": schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "name": schema.StringAttribute{
+ Required: true,
+ Validators: []validator.String{
+ validatorfw_string.RepoKey(),
+ },
+ Description: "Name of the repository",
+ },
+ "type": schema.StringAttribute{
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf("local", "remote", "federated"),
+ },
+ Description: "Repository type. Valid value: local, remote, federated",
+ },
+ "package_type": schema.StringAttribute{
+ Required: true,
+ Validators: []validator.String{
+ stringvalidator.OneOf(validTitledPackageTypes...),
+ },
+ Description: fmt.Sprintf("Artifactory package type. Valid value: %s", strings.Join(validTitledPackageTypes, ", ")),
+ },
+ },
+ },
+ Required: true,
+ Description: "Repositories to be indexed.",
+ },
+ "non_indexed_repos": schema.SetNestedAttribute{
+ NestedObject: schema.NestedAttributeObject{
+ Attributes: map[string]schema.Attribute{
+ "name": schema.StringAttribute{Required: true},
+ "type": schema.StringAttribute{Required: true},
+ "package_type": schema.StringAttribute{Required: true},
+ },
+ },
+ Computed: true,
+ Description: "Non-indexed repositories for output.",
+ },
+ },
+ Description: "Provides an Xray Binary Manager Repository Indexing configuration resource. See [Indexing Xray Resources](https://jfrog.com/help/r/jfrog-security-documentation/add-or-remove-resources-from-indexing) " +
+ "and [REST API](https://jfrog.com/help/r/xray-rest-apis/update-repos-indexing-configuration) for more details.",
+ }
+}
+
+func (r *BinaryManagerReposResource) 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.ProviderMetadata)
+}
+
+func (r *BinaryManagerReposResource) 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 BinaryManagerReposResourceModel
+
+ // Read Terraform plan data into the model
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ request, err := getRestyRequest(r.ProviderData.Client, plan.ProjectKey.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "failed to get Resty client",
+ err.Error(),
+ )
+ return
+ }
+
+ var repos BinaryManagerReposAPIModel
+ resp.Diagnostics.Append(plan.toAPIModel(&repos)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ response, err := request.
+ SetPathParam("id", plan.ID.ValueString()).
+ SetBody(repos).
+ Put(BinaryManagerReposEndpoint)
+ if err != nil {
+ utilfw.UnableToCreateResourceError(resp, err.Error())
+ return
+ }
+ if response.IsError() {
+ utilfw.UnableToCreateResourceError(resp, response.String())
+ return
+ }
+
+ // get the indexed and non-indexed repos list since the PUT
+ // doesn't return the list
+ response, err = request.
+ SetPathParam("id", plan.ID.ValueString()).
+ SetResult(&repos).
+ Get(BinaryManagerReposEndpoint)
+ if err != nil {
+ utilfw.UnableToCreateResourceError(resp, err.Error())
+ return
+ }
+
+ if response.IsError() {
+ utilfw.UnableToCreateResourceError(resp, response.String())
+ return
+ }
+
+ resp.Diagnostics.Append(plan.fromAPIModel(repos)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Save data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
+}
+
+func (r *BinaryManagerReposResource) 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 BinaryManagerReposResourceModel
+
+ // Read Terraform state data into the model
+ resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ request, err := getRestyRequest(r.ProviderData.Client, state.ProjectKey.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "failed to get Resty client",
+ err.Error(),
+ )
+ return
+ }
+
+ var repos BinaryManagerReposAPIModel
+
+ response, err := request.
+ SetPathParam("id", state.ID.ValueString()).
+ SetResult(&repos).
+ Get(BinaryManagerReposEndpoint)
+ if err != nil {
+ utilfw.UnableToRefreshResourceError(resp, err.Error())
+ return
+ }
+
+ if response.IsError() {
+ utilfw.UnableToRefreshResourceError(resp, response.String())
+ return
+ }
+
+ resp.Diagnostics.Append(state.fromAPIModel(repos)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Save data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
+}
+
+func (r *BinaryManagerReposResource) 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 BinaryManagerReposResourceModel
+
+ // Read Terraform plan data into the model
+ resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ request, err := getRestyRequest(r.ProviderData.Client, plan.ProjectKey.ValueString())
+ if err != nil {
+ resp.Diagnostics.AddError(
+ "failed to get Resty client",
+ err.Error(),
+ )
+ return
+ }
+
+ var repos BinaryManagerReposAPIModel
+ resp.Diagnostics.Append(plan.toAPIModel(&repos)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ response, err := request.
+ SetPathParam("id", plan.ID.ValueString()).
+ SetBody(repos).
+ Put(BinaryManagerReposEndpoint)
+ if err != nil {
+ utilfw.UnableToUpdateResourceError(resp, err.Error())
+ return
+ }
+ if response.IsError() {
+ utilfw.UnableToUpdateResourceError(resp, response.String())
+ return
+ }
+
+ // get the indexed and non-indexed repos list since the PUT
+ // doesn't return the list
+ response, err = request.
+ SetPathParam("id", plan.ID.ValueString()).
+ SetResult(&repos).
+ Get(BinaryManagerReposEndpoint)
+ if err != nil {
+ utilfw.UnableToUpdateResourceError(resp, err.Error())
+ return
+ }
+
+ if response.IsError() {
+ utilfw.UnableToUpdateResourceError(resp, response.String())
+ return
+ }
+
+ resp.Diagnostics.Append(plan.fromAPIModel(repos)...)
+ if resp.Diagnostics.HasError() {
+ return
+ }
+
+ // Save data into Terraform state
+ resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
+}
+
+func (r *BinaryManagerReposResource) 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(
+ "Repository indexing configuration cannot be deleted",
+ "The resource is deleted from Terraform but the repository indexing configuration remains unchanged in Xray.",
+ )
+
+ // If the logic reaches here, it implicitly succeeded and will remove
+ // the resource from state if there are no other errors.
+}
+
+// ImportState imports the resource into the Terraform state.
+func (r *BinaryManagerReposResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
+ parts := strings.SplitN(req.ID, ":", 2)
+
+ if len(parts) > 0 && parts[0] != "" {
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), parts[0])...)
+ }
+
+ if len(parts) == 2 && parts[1] != "" {
+ resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_key"), parts[1])...)
+ }
+}
diff --git a/pkg/xray/resource/resource_xray_binary_manager_repos_test.go b/pkg/xray/resource/resource_xray_binary_manager_repos_test.go
new file mode 100644
index 00000000..db6f1b51
--- /dev/null
+++ b/pkg/xray/resource/resource_xray_binary_manager_repos_test.go
@@ -0,0 +1,335 @@
+package xray_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"
+ "github.com/jfrog/terraform-provider-xray/pkg/acctest"
+ "github.com/samber/lo"
+)
+
+func TestAccBinaryManagerRepos_full(t *testing.T) {
+ _, fqrn, resourceName := testutil.MkNames("test-bin-mgr-repos", "xray_binary_manager_repos")
+ _, _, repo1Name := testutil.MkNames("test-local-generic-repo", "artifactory_local_generic_repository")
+ _, _, repo2Name := testutil.MkNames("test-local-docker-repo", "artifactory_local_docker_repository")
+ _, _, repo3Name := testutil.MkNames("test-local-npm-repo", "artifactory_local_npm_repository")
+
+ const template = `
+ resource "artifactory_local_generic_repository" "{{ .repo1 }}" {
+ key = "{{ .repo1 }}"
+ xray_index = true
+ }
+
+ resource "artifactory_local_docker_v2_repository" "{{ .repo2 }}" {
+ key = "{{ .repo2 }}"
+ xray_index = false
+ }
+
+ resource "xray_binary_manager_repos" "{{ .name }}" {
+ id = "default"
+ indexed_repos = [
+ {
+ name = artifactory_local_generic_repository.{{ .repo1 }}.key
+ type = "local"
+ package_type = "Generic"
+ }
+ ]
+ }
+ `
+
+ testData := map[string]string{
+ "name": resourceName,
+ "repo1": repo1Name,
+ "repo2": repo2Name,
+ }
+
+ config := util.ExecuteTemplate("TestAccBinaryManagerRepos_full", template, testData)
+
+ const updateTemplate = `
+ resource "artifactory_local_generic_repository" "{{ .repo1 }}" {
+ key = "{{ .repo1 }}"
+ xray_index = true
+ }
+
+ resource "artifactory_local_docker_v2_repository" "{{ .repo2 }}" {
+ key = "{{ .repo2 }}"
+ xray_index = true
+ }
+
+ resource "artifactory_local_npm_repository" "{{ .repo3 }}" {
+ key = "{{ .repo3 }}"
+ }
+
+ resource "xray_binary_manager_repos" "{{ .name }}" {
+ id = "default"
+ indexed_repos = [
+ {
+ name = artifactory_local_generic_repository.{{ .repo1 }}.key
+ type = "local"
+ package_type = "Generic"
+ },
+ {
+ name = artifactory_local_docker_v2_repository.{{ .repo2 }}.key
+ type = "local"
+ package_type = "Docker"
+ }
+ ]
+ }
+ `
+ updatedTestData := map[string]string{
+ "name": resourceName,
+ "repo1": repo1Name,
+ "repo2": repo2Name,
+ "repo3": repo3Name,
+ }
+ updatedConfig := util.ExecuteTemplate("TestAccBinaryManagerRepos_full", updateTemplate, updatedTestData)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { acctest.PreCheck(t) },
+ ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories,
+ ExternalProviders: map[string]resource.ExternalProvider{
+ "artifactory": {
+ Source: "jfrog/artifactory",
+ },
+ },
+ Steps: []resource.TestStep{
+ {
+ Config: config,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(fqrn, "id", "default"),
+ resource.TestCheckResourceAttr(fqrn, "indexed_repos.#", "1"),
+ resource.TestCheckResourceAttr(fqrn, "indexed_repos.0.name", repo1Name),
+ resource.TestCheckResourceAttr(fqrn, "indexed_repos.0.type", "local"),
+ resource.TestCheckResourceAttr(fqrn, "indexed_repos.0.package_type", "Generic"),
+ ),
+ },
+ {
+ Config: updatedConfig,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(fqrn, "id", "default"),
+ resource.TestCheckResourceAttr(fqrn, "indexed_repos.#", "2"),
+ resource.TestCheckTypeSetElemNestedAttrs(fqrn, "indexed_repos.*", map[string]string{
+ "name": repo1Name,
+ "type": "local",
+ "package_type": "Generic",
+ }),
+ resource.TestCheckTypeSetElemNestedAttrs(fqrn, "indexed_repos.*", map[string]string{
+ "name": repo2Name,
+ "type": "local",
+ "package_type": "Docker",
+ }),
+ ),
+ ConfigPlanChecks: testutil.ConfigPlanChecks(""),
+ },
+ {
+ ResourceName: fqrn,
+ ImportState: true,
+ ImportStateId: resourceName,
+ ImportStateVerify: true,
+ ImportStateVerifyIdentifierAttribute: "id",
+ },
+ },
+ })
+}
+
+func TestAccBinaryManagerRepos_project_full(t *testing.T) {
+ _, fqrn, resourceName := testutil.MkNames("test-bin-mgr-repos", "xray_binary_manager_repos")
+ _, _, repo1Name := testutil.MkNames("test-local-generic-repo", "artifactory_local_generic_repository")
+ _, _, repo2Name := testutil.MkNames("test-local-docker-repo", "artifactory_local_docker_repository")
+ _, _, repo3Name := testutil.MkNames("test-local-npm-repo", "artifactory_local_npm_repository")
+ _, _, projectName := testutil.MkNames("test-project", "project")
+
+ projectKey := lo.RandomString(6, lo.LowerCaseLettersCharset)
+
+ const template = `
+ resource "artifactory_local_generic_repository" "{{ .repo1 }}" {
+ key = "{{ .repo1 }}"
+ xray_index = true
+
+ lifecycle {
+ ignore_changes = ["project_key"]
+ }
+ }
+
+ resource "artifactory_local_docker_v2_repository" "{{ .repo2 }}" {
+ key = "{{ .repo2 }}"
+ xray_index = false
+
+ lifecycle {
+ ignore_changes = ["project_key"]
+ }
+ }
+
+ resource "project" "{{ .projectName }}" {
+ key = "{{ .projectKey }}"
+ display_name = "{{ .projectName }}"
+ admin_privileges {
+ manage_members = true
+ manage_resources = true
+ index_resources = true
+ }
+ }
+
+ resource "project_repository" "{{ .projectKey }}-{{ .repo1 }}" {
+ project_key = project.{{ .projectName }}.key
+ key = artifactory_local_generic_repository.{{ .repo1 }}.key
+ }
+
+ resource "project_repository" "{{ .projectKey }}-{{ .repo2 }}" {
+ project_key = project.{{ .projectName }}.key
+ key = artifactory_local_docker_v2_repository.{{ .repo2 }}.key
+ }
+
+ resource "xray_binary_manager_repos" "{{ .name }}" {
+ id = "default"
+ project_key = project.{{ .projectName }}.key
+ indexed_repos = [
+ {
+ name = artifactory_local_generic_repository.{{ .repo1 }}.key
+ type = "local"
+ package_type = "Generic"
+ }
+ ]
+
+ depends_on = [
+ project_repository.{{ .projectKey }}-{{ .repo1 }},
+ ]
+ }
+ `
+
+ testData := map[string]string{
+ "name": resourceName,
+ "repo1": repo1Name,
+ "repo2": repo2Name,
+ "projectName": projectName,
+ "projectKey": projectKey,
+ }
+
+ config := util.ExecuteTemplate("TestAccBinaryManagerRepos_full", template, testData)
+
+ const updateTemplate = `
+ resource "artifactory_local_generic_repository" "{{ .repo1 }}" {
+ key = "{{ .repo1 }}"
+ xray_index = true
+
+ lifecycle {
+ ignore_changes = ["project_key"]
+ }
+ }
+
+ resource "artifactory_local_docker_v2_repository" "{{ .repo2 }}" {
+ key = "{{ .repo2 }}"
+ xray_index = true
+
+ lifecycle {
+ ignore_changes = ["project_key"]
+ }
+ }
+
+ resource "project" "{{ .projectName }}" {
+ key = "{{ .projectKey }}"
+ display_name = "{{ .projectName }}"
+ admin_privileges {
+ manage_members = true
+ manage_resources = true
+ index_resources = true
+ }
+ }
+
+ resource "project_repository" "{{ .projectKey }}-{{ .repo1 }}" {
+ project_key = project.{{ .projectName }}.key
+ key = artifactory_local_generic_repository.{{ .repo1 }}.key
+ }
+
+ resource "project_repository" "{{ .projectKey }}-{{ .repo2 }}" {
+ project_key = project.{{ .projectName }}.key
+ key = artifactory_local_docker_v2_repository.{{ .repo2 }}.key
+ }
+
+ resource "xray_binary_manager_repos" "{{ .name }}" {
+ id = "default"
+ project_key = project.{{ .projectName }}.key
+ indexed_repos = [
+ {
+ name = artifactory_local_generic_repository.{{ .repo1 }}.key
+ type = "local"
+ package_type = "Generic"
+ },
+ {
+ name = artifactory_local_docker_v2_repository.{{ .repo2 }}.key
+ type = "local"
+ package_type = "Docker"
+ }
+ ]
+
+ depends_on = [
+ project_repository.{{ .projectKey }}-{{ .repo1 }},
+ project_repository.{{ .projectKey }}-{{ .repo2 }},
+ ]
+ }
+ `
+ updatedTestData := map[string]string{
+ "name": resourceName,
+ "repo1": repo1Name,
+ "repo2": repo2Name,
+ "repo3": repo3Name,
+ "projectName": projectName,
+ "projectKey": projectKey,
+ }
+ updatedConfig := util.ExecuteTemplate("TestAccBinaryManagerRepos_full", updateTemplate, updatedTestData)
+
+ resource.Test(t, resource.TestCase{
+ PreCheck: func() { acctest.PreCheck(t) },
+ ProtoV6ProviderFactories: acctest.ProtoV6ProviderFactories,
+ ExternalProviders: map[string]resource.ExternalProvider{
+ "artifactory": {
+ Source: "jfrog/artifactory",
+ },
+ "project": {
+ Source: "jfrog/project",
+ },
+ },
+ Steps: []resource.TestStep{
+ {
+ Config: config,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(fqrn, "id", "default"),
+ resource.TestCheckResourceAttr(fqrn, "project_key", projectKey),
+ resource.TestCheckResourceAttr(fqrn, "indexed_repos.#", "1"),
+ resource.TestCheckResourceAttr(fqrn, "indexed_repos.0.name", repo1Name),
+ resource.TestCheckResourceAttr(fqrn, "indexed_repos.0.type", "local"),
+ resource.TestCheckResourceAttr(fqrn, "indexed_repos.0.package_type", "Generic"),
+ ),
+ },
+ {
+ Config: updatedConfig,
+ Check: resource.ComposeTestCheckFunc(
+ resource.TestCheckResourceAttr(fqrn, "id", "default"),
+ resource.TestCheckResourceAttr(fqrn, "project_key", projectKey),
+ resource.TestCheckResourceAttr(fqrn, "indexed_repos.#", "2"),
+ resource.TestCheckTypeSetElemNestedAttrs(fqrn, "indexed_repos.*", map[string]string{
+ "name": repo1Name,
+ "type": "local",
+ "package_type": "Generic",
+ }),
+ resource.TestCheckTypeSetElemNestedAttrs(fqrn, "indexed_repos.*", map[string]string{
+ "name": repo2Name,
+ "type": "local",
+ "package_type": "Docker",
+ }),
+ ),
+ ConfigPlanChecks: testutil.ConfigPlanChecks(""),
+ },
+ {
+ ResourceName: fqrn,
+ ImportState: true,
+ ImportStateId: fmt.Sprintf("%s:%s", resourceName, projectKey),
+ ImportStateVerify: true,
+ ImportStateVerifyIdentifierAttribute: "id",
+ },
+ },
+ })
+}