diff --git a/.gitignore b/.gitignore index 501744f78..5487b26cc 100644 --- a/.gitignore +++ b/.gitignore @@ -54,4 +54,6 @@ terraform/**/terrform.tfstate terraform/**/.terraform/* # Ignore the test results file -results.xml \ No newline at end of file +results.xml + +.DS_Store diff --git a/Makefile b/Makefile index 45b2464a6..dd29799b7 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ HOSTNAME=octopus.com NAMESPACE=com NAME=octopusdeploy BINARY=terraform-provider-${NAME} -VERSION=0.7.82 +VERSION=0.7.95 ifeq ($(OS), Windows_NT) OS_ARCH?=windows_386 diff --git a/examples/resources/octopusdeploy_artifactory_generic_feed/import.sh b/examples/resources/octopusdeploy_artifactory_generic_feed/import.sh new file mode 100644 index 000000000..3005f4d31 --- /dev/null +++ b/examples/resources/octopusdeploy_artifactory_generic_feed/import.sh @@ -0,0 +1 @@ +terraform import [options] octopusdeploy_artifactory_generic_feed. \ No newline at end of file diff --git a/examples/resources/octopusdeploy_artifactory_generic_feed/resource.tf b/examples/resources/octopusdeploy_artifactory_generic_feed/resource.tf new file mode 100644 index 000000000..83d6aa6c1 --- /dev/null +++ b/examples/resources/octopusdeploy_artifactory_generic_feed/resource.tf @@ -0,0 +1,8 @@ +resource "octopusdeploy_artifactory_generic_feed" "example" { + feed_uri = "https://example.jfrog.io/" + password = "test-password" + name = "Test Artifactory Generic Feed (OK to Delete)" + username = "test-username" + repository = "repo" + layout_regex = "this is regex" +} diff --git a/go.mod b/go.mod index 22c19bd2a..160c7e7d2 100644 --- a/go.mod +++ b/go.mod @@ -2,6 +2,10 @@ module github.com/OctopusDeploy/terraform-provider-octopusdeploy go 1.20 +replace ( + github.com/OctopusDeploy/go-octopusdeploy/v2 => ../go-octopusdeploy +) + require ( github.com/OctopusDeploy/go-octopusdeploy/v2 v2.33.3 github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework v0.0.0-20230705105638-f5ef7c07973b diff --git a/octopusdeploy/provider.go b/octopusdeploy/provider.go index 4299ce5ea..2000e7faa 100644 --- a/octopusdeploy/provider.go +++ b/octopusdeploy/provider.go @@ -69,6 +69,7 @@ func Provider() *schema.Provider { "octopusdeploy_listening_tentacle_deployment_target": resourceListeningTentacleDeploymentTarget(), "octopusdeploy_machine_policy": resourceMachinePolicy(), "octopusdeploy_maven_feed": resourceMavenFeed(), + "octopusdeploy_artifactory_generic_feed": resourceArtifactoryGenericFeed(), "octopusdeploy_nuget_feed": resourceNuGetFeed(), "octopusdeploy_offline_package_drop_deployment_target": resourceOfflinePackageDropDeploymentTarget(), "octopusdeploy_polling_tentacle_deployment_target": resourcePollingTentacleDeploymentTarget(), diff --git a/octopusdeploy/resource_artifactory_generic_feed.go b/octopusdeploy/resource_artifactory_generic_feed.go new file mode 100644 index 000000000..0980183ec --- /dev/null +++ b/octopusdeploy/resource_artifactory_generic_feed.go @@ -0,0 +1,108 @@ +package octopusdeploy + +import ( + "context" + "fmt" + + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds" + "github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors" + "github.com/hashicorp/terraform-plugin-log/tflog" + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +func resourceArtifactoryGenericFeed() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceArtifactoryGenericFeedCreate, + DeleteContext: resourceArtifactoryGenericFeedDelete, + Description: "This resource manages a Artifactory Generic feed in Octopus Deploy.", + Importer: getImporter(), + ReadContext: resourceArtifactoryGenericFeedRead, + Schema: getArtifactoryGenericFeedSchema(), + UpdateContext: resourceArtifactoryGenericFeedUpdate, + } +} + +func resourceArtifactoryGenericFeedCreate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + artifactoryGenericFeed, err := expandArtifactoryGenericFeed(d) + if err != nil { + return diag.FromErr(err) + } + + tflog.Info(ctx, fmt.Sprintf("creating Artifactory Generic feed: %s", artifactoryGenericFeed.GetName())) + + client := m.(*client.Client) + createdFeed, err := feeds.Add(client, artifactoryGenericFeed) + if err != nil { + return diag.FromErr(err) + } + + if err := setArtifactoryGenericFeed(ctx, d, createdFeed.(*feeds.ArtifactoryGenericFeed)); err != nil { + return diag.FromErr(err) + } + + d.SetId(createdFeed.GetID()) + + tflog.Info(ctx, fmt.Sprintf("Artifactory Generic feed created (%s)", d.Id())) + return nil +} + +func resourceArtifactoryGenericFeedDelete(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + tflog.Info(ctx, fmt.Sprintf("deleting Artifactory Generic feed (%s)", d.Id())) + + client := m.(*client.Client) + err := feeds.DeleteByID(client, d.Get("space_id").(string), d.Id()) + if err != nil { + return diag.FromErr(err) + } + + d.SetId("") + + tflog.Info(ctx, "Artifactory Generic feed deleted") + return nil +} + +func resourceArtifactoryGenericFeedRead(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + tflog.Info(ctx, fmt.Sprintf("reading Artifactory Generic feed (%s)", d.Id())) + + client := m.(*client.Client) + feed, err := feeds.GetByID(client, d.Get("space_id").(string), d.Id()) + + if err != nil { + return errors.ProcessApiError(ctx, d, err, "Artifactory Generic feed") + } + + if err := setArtifactoryGenericFeed(ctx, d, feed.(*feeds.ArtifactoryGenericFeed)); err != nil { + return diag.FromErr(err) + } + + tflog.Info(ctx, fmt.Sprintf("Artifactory Generic feed read (%s)", feed.GetID())) + return nil +} + +func resourceArtifactoryGenericFeedUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.Diagnostics { + feed, err := expandArtifactoryGenericFeed(d) + + tflog.Debug(ctx, fmt.Sprintf("layout_regex from schema: %s", d.Get("layout_regex").(string))) + + tflog.Debug(ctx, fmt.Sprintf("layout regex from feed model: %s", feed.LayoutRegex)) + if err != nil { + return diag.FromErr(err) + } + + tflog.Info(ctx, fmt.Sprintf("updating Artifactory Generic feed (%s)", feed.GetID())) + + client := m.(*client.Client) + updatedFeed, err := feeds.Update(client, feed) + if err != nil { + return diag.FromErr(err) + } + + if err := setArtifactoryGenericFeed(ctx, d, updatedFeed.(*feeds.ArtifactoryGenericFeed)); err != nil { + return diag.FromErr(err) + } + + tflog.Info(ctx, fmt.Sprintf("Artifactory Generic feed updated (%s)", d.Id())) + return nil +} diff --git a/octopusdeploy/resource_artifactory_generic_feed_test.go b/octopusdeploy/resource_artifactory_generic_feed_test.go new file mode 100644 index 000000000..239044128 --- /dev/null +++ b/octopusdeploy/resource_artifactory_generic_feed_test.go @@ -0,0 +1,82 @@ +package octopusdeploy + +import ( + "fmt" + "testing" + + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/client" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +) + +func TestAccOctopusDeployArtifactoryGenericFeed(t *testing.T) { + localName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + prefix := "octopusdeploy_artifactory_generic_feed." + localName + + feedURI := "https://example.jfrog.io" + name := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + password := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + username := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + repository := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + layoutRegex := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha) + + resource.Test(t, resource.TestCase{ + CheckDestroy: testArtifactoryGenericFeedCheckDestroy, + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Check: resource.ComposeTestCheckFunc( + testArtifactoryGenericFeedExists(prefix), + resource.TestCheckResourceAttr(prefix, "feed_uri", feedURI), + resource.TestCheckResourceAttr(prefix, "name", name), + resource.TestCheckResourceAttr(prefix, "password", password), + resource.TestCheckResourceAttr(prefix, "username", username), + resource.TestCheckResourceAttr(prefix, "repository", repository), + resource.TestCheckResourceAttr(prefix, "layout_regex", layoutRegex), + ), + Config: testArtifactoryGenericFeedBasic(localName, feedURI, name, username, password, repository, layoutRegex), + }, + }, + }) +} + +func testArtifactoryGenericFeedBasic(localName string, feedURI string, name string, username string, password string, repository string, layoutRegex string) string { + return fmt.Sprintf(`resource "octopusdeploy_artifactory_generic_feed" "%s" { + feed_uri = "%s" + name = "%s" + password = "%s" + username = "%s" + repository = "%s" + layout_regex = "%s" + }`, localName, feedURI, name, password, username, repository, layoutRegex) +} + +func testArtifactoryGenericFeedExists(prefix string) resource.TestCheckFunc { + return func(s *terraform.State) error { + client := testAccProvider.Meta().(*client.Client) + feedID := s.RootModule().Resources[prefix].Primary.ID + if _, err := client.Feeds.GetByID(feedID); err != nil { + return err + } + + return nil + } +} + +func testArtifactoryGenericFeedCheckDestroy(s *terraform.State) error { + for _, rs := range s.RootModule().Resources { + if rs.Type != "octopusdeploy_artifactory_generic_feed" { + continue + } + + client := testAccProvider.Meta().(*client.Client) + feed, err := client.Feeds.GetByID(rs.Primary.ID) + if err == nil && feed != nil { + return fmt.Errorf("Artifactory Generic feed (%s) still exists", rs.Primary.ID) + } + } + + return nil +} diff --git a/octopusdeploy/schema_artifactory_generic_feed.go b/octopusdeploy/schema_artifactory_generic_feed.go new file mode 100644 index 000000000..118e59fd4 --- /dev/null +++ b/octopusdeploy/schema_artifactory_generic_feed.go @@ -0,0 +1,104 @@ +package octopusdeploy + +import ( + "context" + "fmt" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +func expandArtifactoryGenericFeed(d *schema.ResourceData) (*feeds.ArtifactoryGenericFeed, error) { + name := d.Get("name").(string) + + feed, err := feeds.NewArtifactoryGenericFeed(name) + if err != nil { + return nil, err + } + + feed.ID = d.Id() + + if v, ok := d.GetOk("feed_uri"); ok { + feed.FeedURI = v.(string) + } + + if v, ok := d.GetOk("package_acquisition_location_options"); ok { + feed.PackageAcquisitionLocationOptions = getSliceFromTerraformTypeList(v) + } + + if v, ok := d.GetOk("password"); ok { + feed.Password = core.NewSensitiveValue(v.(string)) + } + + if v, ok := d.GetOk("space_id"); ok { + feed.SpaceID = v.(string) + } + + if v, ok := d.GetOk("username"); ok { + feed.Username = v.(string) + } + + if v, ok := d.GetOk("layout_regex"); ok { + feed.LayoutRegex = v.(string) + } + + if v, ok := d.GetOk("repository"); ok { + feed.Repository = v.(string) + } + + return feed, nil +} + +func getArtifactoryGenericFeedSchema() map[string]*schema.Schema { + return map[string]*schema.Schema{ + "feed_uri": { + Required: true, + Type: schema.TypeString, + }, + "id": getIDSchema(), + "name": { + Description: "A short, memorable, unique name for this feed. Example: ACME Builds.", + Required: true, + Type: schema.TypeString, + ValidateDiagFunc: validation.ToDiagFunc(validation.StringIsNotEmpty), + }, + "package_acquisition_location_options": { + Computed: true, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Type: schema.TypeList, + }, + "password": getPasswordSchema(false), + "space_id": getSpaceIDSchema(), + "username": getUsernameSchema(false), + "repository": { + Computed: false, + Required: true, + Type: schema.TypeString, + }, + "layout_regex": { + Computed: false, + Required: false, + Optional: true, + Type: schema.TypeString, + }, + } +} + +func setArtifactoryGenericFeed(ctx context.Context, d *schema.ResourceData, feed *feeds.ArtifactoryGenericFeed) error { + d.Set("feed_uri", feed.FeedURI) + d.Set("name", feed.Name) + d.Set("space_id", feed.SpaceID) + d.Set("username", feed.Username) + d.Set("repository", feed.Repository) + d.Set("layout_regex", feed.LayoutRegex) + + if err := d.Set("package_acquisition_location_options", feed.PackageAcquisitionLocationOptions); err != nil { + return fmt.Errorf("error setting package_acquisition_location_options: %s", err) + } + + d.SetId(feed.GetID()) + + return nil +} diff --git a/octopusdeploy/schema_docker_container_registry.go b/octopusdeploy/schema_docker_container_registry.go index 0fbc1477e..a28f565cd 100644 --- a/octopusdeploy/schema_docker_container_registry.go +++ b/octopusdeploy/schema_docker_container_registry.go @@ -44,10 +44,6 @@ func expandDockerContainerRegistry(d *schema.ResourceData) (*feeds.DockerContain feed.Password = core.NewSensitiveValue(v.(string)) } - if v, ok := d.GetOk("space_id"); ok { - feed.SpaceID = v.(string) - } - if v, ok := d.GetOk("username"); ok { feed.Username = v.(string) } @@ -62,7 +58,7 @@ func getDockerContainerRegistrySchema() map[string]*schema.Schema { Type: schema.TypeString, }, "feed_uri": { - Description: "The URL to a Maven repository. This URL is the same value defined in the `repositories`/`repository`/`url` element of a Maven `settings.xml` file.", + Description: "The URL to a Docker repository.", Required: true, Type: schema.TypeString, ValidateDiagFunc: validation.ToDiagFunc(validation.IsURLWithHTTPorHTTPS),