Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for OCI Registry feed #801

Merged
merged 7 commits into from
Oct 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/data-sources/feeds.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ data "octopusdeploy_feeds" "example" {

### Optional

- `feed_type` (String) A filter to search by feed type. Valid feed types are `AwsElasticContainerRegistry`, `BuiltIn`, `Docker`, `GitHub`, `Helm`, `Maven`, `NuGet`, or `OctopusProject`.
- `feed_type` (String) A filter to search by feed type. Valid feed types are `AwsElasticContainerRegistry`, `BuiltIn`, `Docker`, `GitHub`, `Helm`, `Maven`, `NuGet`, `OciRegistry` or `OctopusProject`.
- `ids` (List of String) A filter to search by a list of IDs.
- `name` (String) The name of this resource.
- `partial_name` (String) A filter to search by a partial name.
Expand All @@ -49,7 +49,7 @@ Read-Only:
- `delete_unreleased_packages_after_days` (Number)
- `download_attempts` (Number) The number of times a deployment should attempt to download a package from this feed before failing.
- `download_retry_backoff_seconds` (Number) The number of seconds to apply as a linear back off between download attempts.
- `feed_type` (String) A filter to search by feed type. Valid feed types are `AwsElasticContainerRegistry`, `BuiltIn`, `Docker`, `GitHub`, `Helm`, `Maven`, `NuGet`, or `OctopusProject`.
- `feed_type` (String) A filter to search by feed type. Valid feed types are `AwsElasticContainerRegistry`, `BuiltIn`, `Docker`, `GitHub`, `Helm`, `Maven`, `NuGet`, `OciRegistry` or `OctopusProject`.
- `feed_uri` (String)
- `id` (String) The unique ID for this resource.
- `is_enhanced_mode` (Boolean)
Expand Down
48 changes: 48 additions & 0 deletions docs/resources/oci_registry_feed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "octopusdeploy_oci_registry_feed Resource - terraform-provider-octopusdeploy"
subcategory: ""
description: |-
This resource manages a OCI Registry feed in Octopus Deploy.
---

# octopusdeploy_oci_registry_feed (Resource)

This resource manages a OCI Registry feed in Octopus Deploy.

## Example Usage

```terraform
resource "octopusdeploy_oci_registry_feed" "example" {
feed_uri = "oci://test-registry.docker.io"
password = "test-password"
name = "Test oci Registry Feed (OK to Delete)"
username = "test-username"
}
```

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `feed_uri` (String)
- `name` (String) The name of this resource.

### Optional

- `password` (String, Sensitive) The password associated with this resource.
- `space_id` (String) The space ID associated with this OCI registry.
- `username` (String, Sensitive) The username associated with this resource.

### Read-Only

- `id` (String) The unique ID for this resource.

## Import

Import is supported using the following syntax:

```shell
terraform import [options] octopusdeploy_oci_registry_feed.<name> <feed-id>
```
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
terraform import [options] octopusdeploy_oci_registry_feed.<name> <feed-id>
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
resource "octopusdeploy_oci_registry_feed" "example" {
feed_uri = "oci://test-registry.docker.io"
password = "test-password"
name = "Test oci Registry Feed (OK to Delete)"
username = "test-username"
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/OctopusDeploy/terraform-provider-octopusdeploy
go 1.21

require (
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.52.1
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.53.1
github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework v0.0.0-20240729041805-46db6fb717b4
github.com/google/uuid v1.6.0
github.com/hashicorp/go-cty v1.4.1-0.20200723130312-85980079f637
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ github.com/Microsoft/hcsshim v0.12.4 h1:Ev7YUMHAHoWNm+aDSPzc5W9s6E2jyL1szpVDJeZ/
github.com/Microsoft/hcsshim v0.12.4/go.mod h1:Iyl1WVpZzr+UkzjekHZbV8o5Z9ZkxNGx6CtY2Qg/JVQ=
github.com/OctopusDeploy/go-octodiff v1.0.0 h1:U+ORg6azniwwYo+O44giOw6TiD5USk8S4VDhOQ0Ven0=
github.com/OctopusDeploy/go-octodiff v1.0.0/go.mod h1:Mze0+EkOWTgTmi8++fyUc6r0aLZT7qD9gX+31t8MmIU=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.52.1 h1:GeWNIPn59JZggkjZD/VKpt3oJNuYezdJPbIqyl+MVRw=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.52.1/go.mod h1:ggvOXzMnq+w0pLg6C9zdjz6YBaHfO3B3tqmmB7JQdaw=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.53.1 h1:9c5qKji5R/sFmjqVQ1Nxt+vKITsj42CCCs0bfqJvETc=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.53.1/go.mod h1:ggvOXzMnq+w0pLg6C9zdjz6YBaHfO3B3tqmmB7JQdaw=
github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework v0.0.0-20240729041805-46db6fb717b4 h1:QfbVf0bOIRMp/WHAWsuVDB7KHoWnRsGbvDuOf2ua7k4=
github.com/OctopusSolutionsEngineering/OctopusTerraformTestFramework v0.0.0-20240729041805-46db6fb717b4/go.mod h1:Oq9KbiRNDBB5jFmrwnrgLX0urIqR/1ptY18TzkqXm7M=
github.com/ProtonMail/go-crypto v1.1.0-alpha.2 h1:bkyFVUP+ROOARdgCiJzNQo2V2kiB97LyUpzH9P6Hrlg=
Expand Down
1 change: 1 addition & 0 deletions octopusdeploy_framework/framework_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ func (p *octopusDeployFrameworkProvider) Resources(ctx context.Context) []func()
NewSpaceResource,
NewProjectGroupResource,
NewMavenFeedResource,
NewOCIRegistryFeedResource,
NewLifecycleResource,
NewEnvironmentResource,
NewStepTemplateResource,
Expand Down
168 changes: 168 additions & 0 deletions octopusdeploy_framework/resource_oci_registry_feed.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
package octopusdeploy_framework

import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework/path"

"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/internal/errors"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
)

type ociRegistryFeedTypeResource struct {
*Config
}

func NewOCIRegistryFeedResource() resource.Resource {
return &ociRegistryFeedTypeResource{}
}

var _ resource.ResourceWithImportState = &ociRegistryFeedTypeResource{}

func (r *ociRegistryFeedTypeResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = util.GetTypeName("oci_registry_feed")
}

func (r *ociRegistryFeedTypeResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schemas.OCIRegistryFeedSchema{}.GetResourceSchema()
}

func (r *ociRegistryFeedTypeResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
r.Config = ResourceConfiguration(req, resp)
}

func (r *ociRegistryFeedTypeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var data *schemas.OCIRegistryFeedTypeResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

feed, err := createOCIRegistryResourceFromData(data)
if err != nil {
return
}

tflog.Info(ctx, fmt.Sprintf("creating OCI Registry feed: %s", feed.GetName()))

client := r.Config.Client
createdFeed, err := feeds.Add(client, feed)
if err != nil {
resp.Diagnostics.AddError("unable to create OCI Registry feed", err.Error())
return
}

updateDataFromOCIRegistryFeed(data, data.SpaceID.ValueString(), createdFeed.(*feeds.OCIRegistryFeed))

tflog.Info(ctx, fmt.Sprintf("OCI Registry feed created (%s)", data.ID))
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *ociRegistryFeedTypeResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var data *schemas.OCIRegistryFeedTypeResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

tflog.Info(ctx, fmt.Sprintf("reading OCI Registry feed (%s)", data.ID))

client := r.Config.Client
feed, err := feeds.GetByID(client, data.SpaceID.ValueString(), data.ID.ValueString())
if err != nil {
if err := errors.ProcessApiErrorV2(ctx, resp, data, err, "OCI Registry feed"); err != nil {
resp.Diagnostics.AddError("unable to load OCI Registry feed", err.Error())
}
return
}

loadedFeed := feed.(*feeds.OCIRegistryFeed)
updateDataFromOCIRegistryFeed(data, data.SpaceID.ValueString(), loadedFeed)

tflog.Info(ctx, fmt.Sprintf("OCI Registry feed read (%s)", loadedFeed.GetID()))
resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *ociRegistryFeedTypeResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var data, state *schemas.OCIRegistryFeedTypeResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

tflog.Debug(ctx, fmt.Sprintf("updating OCI Registry feed '%s'", data.ID.ValueString()))

feed, err := createOCIRegistryResourceFromData(data)
feed.ID = state.ID.ValueString()
if err != nil {
resp.Diagnostics.AddError("unable to load OCI Registry feed", err.Error())
return
}

tflog.Info(ctx, fmt.Sprintf("updating OCI Registry feed (%s)", data.ID))

client := r.Config.Client
updatedFeed, err := feeds.Update(client, feed)
if err != nil {
resp.Diagnostics.AddError("unable to update OCI Registry feed", err.Error())
return
}

updateDataFromOCIRegistryFeed(data, state.SpaceID.ValueString(), updatedFeed.(*feeds.OCIRegistryFeed))

tflog.Info(ctx, fmt.Sprintf("OCI Registry feed updated (%s)", data.ID))

resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}

func (r *ociRegistryFeedTypeResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var data schemas.OCIRegistryFeedTypeResourceModel

resp.Diagnostics.Append(req.State.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

if err := feeds.DeleteByID(r.Config.Client, data.SpaceID.ValueString(), data.ID.ValueString()); err != nil {
resp.Diagnostics.AddError("unable to delete OCI Registry feed", err.Error())
return
}
}

func createOCIRegistryResourceFromData(data *schemas.OCIRegistryFeedTypeResourceModel) (*feeds.OCIRegistryFeed, error) {
feed, err := feeds.NewOCIRegistryFeed(data.Name.ValueString())
if err != nil {
return nil, err
}

feed.ID = data.ID.ValueString()
feed.FeedURI = data.FeedUri.ValueString()

feed.Username = data.Username.ValueString()
feed.Password = core.NewSensitiveValue(data.Password.ValueString())
feed.SpaceID = data.SpaceID.ValueString()

return feed, nil
}

func updateDataFromOCIRegistryFeed(data *schemas.OCIRegistryFeedTypeResourceModel, spaceId string, feed *feeds.OCIRegistryFeed) {
data.FeedUri = types.StringValue(feed.FeedURI)
data.Name = types.StringValue(feed.Name)
data.SpaceID = types.StringValue(spaceId)
if feed.Username != "" {
data.Username = types.StringValue(feed.Username)
}

data.ID = types.StringValue(feed.ID)
}

func (*ociRegistryFeedTypeResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}
91 changes: 91 additions & 0 deletions octopusdeploy_framework/resource_oci_registry_feed_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package octopusdeploy_framework

import (
"fmt"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds"
"github.com/hashicorp/terraform-plugin-testing/helper/acctest"
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/terraform"
"testing"
)

type ociRegistryFeedTestData struct {
name string
uri string
username string
password string
}

func TestAccOctopusDeployOCIRegistryFeed(t *testing.T) {
localName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
prefix := "octopusdeploy_oci_registry_feed." + localName
createData := ociRegistryFeedTestData{
name: acctest.RandStringFromCharSet(20, acctest.CharSetAlpha),
uri: "oci://integration-test-registry.docker.io",
username: acctest.RandStringFromCharSet(20, acctest.CharSetAlpha),
password: acctest.RandStringFromCharSet(20, acctest.CharSetAlphaNum),
}
updateData := ociRegistryFeedTestData{
name: createData.name + "-updated",
uri: "oci://integration-test-registry-updated.docker.io",
username: createData.username + "-changed",
password: createData.password + "-generated",
}

resource.Test(t, resource.TestCase{
CheckDestroy: func(s *terraform.State) error { return testOCIRegistryFeedCheckDestroy(s) },
PreCheck: func() { TestAccPreCheck(t) },
ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
Steps: []resource.TestStep{
{
Config: testOCIRegistryFeedBasic(createData, localName),
Check: testAssertOCIRegistryAttributes(createData, prefix),
},
{
Config: testOCIRegistryFeedBasic(updateData, localName),
Check: testAssertOCIRegistryAttributes(updateData, prefix),
},
},
})
}

func testAssertOCIRegistryAttributes(expected ociRegistryFeedTestData, prefix string) resource.TestCheckFunc {
return resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr(prefix, "name", expected.name),
resource.TestCheckResourceAttr(prefix, "feed_uri", expected.uri),
resource.TestCheckResourceAttr(prefix, "username", expected.username),
resource.TestCheckResourceAttr(prefix, "password", expected.password),
)
}

func testOCIRegistryFeedBasic(data ociRegistryFeedTestData, localName string) string {
return fmt.Sprintf(`
resource "octopusdeploy_oci_registry_feed" "%s" {
name = "%s"
feed_uri = "%s"
username = "%s"
password = "%s"
}
`,
localName,
data.name,
data.uri,
data.username,
data.password,
)
}

func testOCIRegistryFeedCheckDestroy(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "octopusdeploy_oci_registry_feed" {
continue
}

feed, err := feeds.GetByID(octoClient, octoClient.GetSpaceID(), rs.Primary.ID)
if err == nil && feed != nil {
return fmt.Errorf("OCI Registry feed (%s) still exists", rs.Primary.ID)
}
}

return nil
}
6 changes: 4 additions & 2 deletions octopusdeploy_framework/schemas/feeds.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func (f FeedsSchema) GetDatasourceSchema() datasourceSchema.Schema {
Description: "Provides information about existing feeds.",
Attributes: map[string]datasourceSchema.Attribute{
"feed_type": datasourceSchema.StringAttribute{
Description: "A filter to search by feed type. Valid feed types are `AwsElasticContainerRegistry`, `BuiltIn`, `Docker`, `GitHub`, `Helm`, `Maven`, `NuGet`, or `OctopusProject`.",
Description: "A filter to search by feed type. Valid feed types are `AwsElasticContainerRegistry`, `BuiltIn`, `Docker`, `GitHub`, `Helm`, `Maven`, `NuGet`, `OciRegistry` or `OctopusProject`.",
Optional: true,
Validators: []validator.String{
stringvalidator.OneOf(
Expand All @@ -32,6 +32,7 @@ func (f FeedsSchema) GetDatasourceSchema() datasourceSchema.Schema {
"Helm",
"Maven",
"NuGet",
"OciRegistry",
"OctopusProject"),
},
},
Expand All @@ -50,7 +51,7 @@ func (f FeedsSchema) GetDatasourceSchema() datasourceSchema.Schema {
NestedObject: datasourceSchema.NestedAttributeObject{
Attributes: map[string]datasourceSchema.Attribute{
"feed_type": datasourceSchema.StringAttribute{
Description: "A filter to search by feed type. Valid feed types are `AwsElasticContainerRegistry`, `BuiltIn`, `Docker`, `GitHub`, `Helm`, `Maven`, `NuGet`, or `OctopusProject`.",
Description: "A filter to search by feed type. Valid feed types are `AwsElasticContainerRegistry`, `BuiltIn`, `Docker`, `GitHub`, `Helm`, `Maven`, `NuGet`, `OciRegistry` or `OctopusProject`.",
Computed: true,
Validators: []validator.String{
stringvalidator.OneOf(
Expand All @@ -61,6 +62,7 @@ func (f FeedsSchema) GetDatasourceSchema() datasourceSchema.Schema {
"Helm",
"Maven",
"NuGet",
"OciRegistry",
"OctopusProject"),
},
},
Expand Down
Loading
Loading