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

feat: machine proxy resource and data source #809

Merged
merged 5 commits into from
Nov 1, 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
43 changes: 43 additions & 0 deletions docs/data-sources/machine_proxies.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "octopusdeploy_machine_proxies Data Source - terraform-provider-octopusdeploy"
subcategory: ""
description: |-
Provides information about existing Octopus Deploy machine proxies.
---

# octopusdeploy_machine_proxies (Data Source)

Provides information about existing Octopus Deploy machine proxies.



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

### Optional

- `ids` (List of String) A filter to search by a list of IDs.
- `partial_name` (String) A filter to search by a partial name.
- `skip` (Number) A filter to specify the number of items to skip in the response.
- `space_id` (String) A Space ID to filter by. Will revert what is specified on the provider if not set
- `take` (Number) A filter to specify the number of items to take (or return) in the response.

### Read-Only

- `id` (String) An auto-generated identifier that includes the timestamp when this data source was last modified.
- `machine_proxies` (Attributes List) A list of machine proxies that match the filter(s). (see [below for nested schema](#nestedatt--machine_proxies))

<a id="nestedatt--machine_proxies"></a>
### Nested Schema for `machine_proxies`

Read-Only:

- `host` (String) DNS hostname of the proxy server
- `id` (String)
- `name` (String)
- `port` (Number) The port number for the proxy server.
- `space_id` (String) The space ID associated with this machine proxy.
- `username` (String) Username for the proxy server


34 changes: 34 additions & 0 deletions docs/resources/machine_proxy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "octopusdeploy_machine_proxy Resource - terraform-provider-octopusdeploy"
subcategory: ""
description: |-
This resource manages machine proxies in Octopus Deploy.
---

# octopusdeploy_machine_proxy (Resource)

This resource manages machine proxies in Octopus Deploy.



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

### Required

- `host` (String) DNS hostname of the proxy server
- `name` (String) The name of this resource.
- `password` (String, Sensitive) Password of the proxy server
- `username` (String) Username of the proxy server

### Optional

- `port` (Number) The port number for the proxy server.
- `space_id` (String) The space ID associated with this machine_proxy.

### Read-Only

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


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.53.1
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.55.0
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.53.1 h1:9c5qKji5R/sFmjqVQ1Nxt+vKITsj42CCCs0bfqJvETc=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.53.1/go.mod h1:ggvOXzMnq+w0pLg6C9zdjz6YBaHfO3B3tqmmB7JQdaw=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.55.0 h1:kX6qRRy8AgbqTiYdenqVNe69pGhntwJGEgJx9rtn9/8=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.55.0/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
86 changes: 86 additions & 0 deletions octopusdeploy_framework/datasource_machine_proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package octopusdeploy_framework

import (
"context"
"fmt"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/proxies"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/util"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/types"
"time"
)

var _ datasource.DataSource = &machineProxyDataSource{}

type machineProxyDataSource struct {
*Config
}

func NewMachineProxyDataSource() datasource.DataSource {
return &machineProxyDataSource{}
}

func (p *machineProxyDataSource) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
resp.TypeName = util.GetTypeName(schemas.MachineProxyDataSourceName)
}

func (p *machineProxyDataSource) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
resp.Schema = schemas.MachineProxySchema{}.GetDatasourceSchema()
}

func (p *machineProxyDataSource) Configure(_ context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
p.Config = DataSourceConfiguration(req, resp)
}

func (p *machineProxyDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
var data schemas.MachineProxyDataSourceModel
resp.Diagnostics.Append(req.Config.Get(ctx, &data)...)
if resp.Diagnostics.HasError() {
return
}

query := proxies.ProxiesQuery{
PartialName: data.PartialName.ValueString(),
Skip: int(data.Skip.ValueInt64()),
Take: int(data.Take.ValueInt64()),
}

util.DatasourceReading(ctx, "machine proxies", query)

if !data.IDs.IsNull() {
var ids []string
resp.Diagnostics.Append(data.IDs.ElementsAs(ctx, &ids, false)...)
if resp.Diagnostics.HasError() {
return
}
query.IDs = ids
}

spaceID := data.SpaceID.ValueString()

proxiesData, err := proxies.Get(p.Client, spaceID, query)
if err != nil {
resp.Diagnostics.AddError("Unable to query proxies", err.Error())
return
}

util.DatasourceResultCount(ctx, "proxies", len(proxiesData.Items))

data.Proxies = make([]schemas.ProxyDatasourceModel, 0, len(proxiesData.Items))
for _, proxy := range proxiesData.Items {
proxyModel := mapMachineProxyRequestToModel(proxy, &schemas.MachineProxyResourceModel{})
data.Proxies = append(data.Proxies, schemas.ProxyDatasourceModel{
ID: proxyModel.ID,
SpaceID: proxyModel.SpaceID,
Name: proxyModel.Name,
Host: proxyModel.Host,
Username: proxyModel.Username,
Port: proxyModel.Port,
})
}

data.ID = types.StringValue(fmt.Sprintf("Proxies-%s", time.Now().UTC().String()))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this do for TF state if, for example, the datasource was directly set as the value of an output? Do we do this form of ID'ing in other datasources?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is how terraform recommends handling state for data sources, we do it in all our data sources. This ID is internal for terraform, it uses it to understand and track when the data source changes.


resp.Diagnostics.Append(resp.State.Set(ctx, &data)...)
}
2 changes: 2 additions & 0 deletions octopusdeploy_framework/framework_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ func (p *octopusDeployFrameworkProvider) DataSources(ctx context.Context) []func
NewLibraryVariableSetDataSource,
NewVariablesDataSource,
NewProjectsDataSource,
NewMachineProxyDataSource,
NewTenantsDataSource,
NewTagSetsDataSource,
NewScriptModuleDataSource,
Expand Down Expand Up @@ -102,6 +103,7 @@ func (p *octopusDeployFrameworkProvider) Resources(ctx context.Context) []func()
NewLibraryVariableSetFeedResource,
NewVariableResource,
NewProjectResource,
NewMachineProxyResource,
NewTagResource,
NewDockerContainerRegistryFeedResource,
NewTagSetResource,
Expand Down
143 changes: 143 additions & 0 deletions octopusdeploy_framework/resource_machine_proxy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
package octopusdeploy_framework

import (
"context"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/proxies"
"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"
)

var _ resource.Resource = &machineProxyResource{}

type machineProxyResource struct {
*Config
}

func NewMachineProxyResource() resource.Resource {
return &machineProxyResource{}
}

func (r *machineProxyResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = util.GetTypeName(schemas.MachineProxyResourceName)
}

func (r *machineProxyResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schemas.MachineProxySchema{}.GetResourceSchema()
}

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

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

machineProxy := mapMachineProxyModelToRequest(&plan)
createdProxy, err := proxies.Add(r.Client, machineProxy)
if err != nil {
resp.Diagnostics.AddError("Error creating machine proxy", err.Error())
return
}

proxyModel := mapMachineProxyRequestToModel(createdProxy, &plan)

diags := resp.State.Set(ctx, proxyModel)
resp.Diagnostics.Append(diags...)
}

func (r *machineProxyResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state schemas.MachineProxyResourceModel
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}

machineProxy, err := proxies.GetByID(r.Client, state.SpaceID.ValueString(), state.ID.ValueString())
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure it's handled, but just for my clarity: if the user doesn't provide a SpaceId on the Datasource, will this state.SpaceID be automatically set to the default Space from the Provider config?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that happens inside the Go client, if the space ID is "" (go will not let it be nil) we will use the space provided on the provider, if one is not provided by the provider it will use default space, if you don't have a default space set it will error gracefully.

if err != nil {
if err := errors.ProcessApiErrorV2(ctx, resp, state, err, "machine proxy"); err != nil {
resp.Diagnostics.AddError("Error reading machine proxy", err.Error())
}
return
}

proxyModel := mapMachineProxyRequestToModel(machineProxy, &state)

resp.Diagnostics.Append(resp.State.Set(ctx, proxyModel)...)
}

func (r *machineProxyResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan schemas.MachineProxyResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

existingProxy, err := proxies.GetByID(r.Client, plan.SpaceID.ValueString(), plan.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError("Error retrieving machine proxy", err.Error())
return
}

updatedProxy := mapMachineProxyModelToRequest(&plan)
updatedProxy.ID = existingProxy.ID
updatedProxy.Links = existingProxy.Links
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Eww :) do we have to? I assume so, but would love if TFP didn't need to concern itself with this detail.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same. At least its not exposed to user.


updatedProxy, err = proxies.Update(r.Client, updatedProxy)
if err != nil {
resp.Diagnostics.AddError("Error updating machine proxy", err.Error())
return
}

proxyModel := mapMachineProxyRequestToModel(updatedProxy, &plan)

diags := resp.State.Set(ctx, proxyModel)
resp.Diagnostics.Append(diags...)
}

func (r *machineProxyResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state schemas.MachineProxyResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

err := proxies.DeleteByID(r.Client, state.SpaceID.ValueString(), state.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError("Error deleting machine proxy", err.Error())
return
}

resp.State.RemoveResource(ctx)
}

func mapMachineProxyModelToRequest(model *schemas.MachineProxyResourceModel) *proxies.Proxy {
password := core.NewSensitiveValue(model.Password.ValueString())
proxy := proxies.NewProxy(model.Name.ValueString(), model.Host.ValueString(), model.Username.ValueString(), password)
proxy.SpaceID = model.SpaceID.ValueString()
portNumber := model.Port.ValueInt32()
proxy.Port = int(portNumber)
return proxy
}

func mapMachineProxyRequestToModel(proxy *proxies.Proxy, state *schemas.MachineProxyResourceModel) *schemas.MachineProxyResourceModel {
proxyModel := &schemas.MachineProxyResourceModel{
SpaceID: types.StringValue(proxy.SpaceID),
Name: types.StringValue(proxy.Name),
Host: types.StringValue(proxy.Host),
Username: types.StringValue(proxy.Username),
Password: types.StringValue(state.Password.ValueString()),
Port: types.Int32Value(int32(proxy.Port)),
}
proxyModel.ID = types.StringValue(proxy.ID)

return proxyModel
}
Loading
Loading