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: project versioning strategy resource #834

Merged
merged 5 commits into from
Dec 4, 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
2 changes: 1 addition & 1 deletion docs/resources/project.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ resource "octopusdeploy_project" "example" {
- `space_id` (String) The space ID associated with this project.
- `template` (Block List) (see [below for nested schema](#nestedblock--template))
- `tenanted_deployment_participation` (String) The tenanted deployment mode of the resource. Valid account types are `Untenanted`, `TenantedOrUntenanted`, or `Tenanted`.
- `versioning_strategy` (Block List) (see [below for nested schema](#nestedblock--versioning_strategy))
- `versioning_strategy` (Block List, Deprecated) (see [below for nested schema](#nestedblock--versioning_strategy))

### Read-Only

Expand Down
100 changes: 100 additions & 0 deletions docs/resources/project_versioning_strategy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "octopusdeploy_project_versioning_strategy Resource - terraform-provider-octopusdeploy"
subcategory: ""
description: |-

---

# octopusdeploy_project_versioning_strategy (Resource)



## Example Usage

```terraform
resource "octopusdeploy_project_group" "tp" {
name = "DevOps Projects"
description = "My DevOps projects group"
}

resource "octopusdeploy_project" "tp" {
name = "My DevOps Project"
description = "test project"
lifecycle_id = "Lifecycles-1"
project_group_id = octopusdeploy_project_group.tp.id

depends_on = [octopusdeploy_project_group.tp]
}

resource "octopusdeploy_deployment_process" "process" {
project_id = octopusdeploy_project.tp.id

step {
name = "Hello World"
target_roles = [ "hello-world" ]
start_trigger = "StartAfterPrevious"
package_requirement = "LetOctopusDecide"
condition = "Success"

run_script_action {
name = "Hello World"
is_disabled = false
is_required = true
script_body = "Write-Host 'hello world'"
script_syntax = "PowerShell"
can_be_used_for_project_versioning = true
sort_order = 1


package {
name = "Package"
feed_id = "feeds-builtin"
package_id = "myExpressApp"
acquisition_location = "Server"
extract_during_deployment = true
}
}
}

depends_on = [octopusdeploy_project.tp]
}

resource "octopusdeploy_project_versioning_strategy" "tp" {
project_id = octopusdeploy_project.tp.id
space_id = octopusdeploy_project.tp.space_id
donor_package_step_id = octopusdeploy_deployment_process.process.step[0].run_script_action[0].id
donor_package = {
deployment_action = "Hello World"
package_reference = "Package"
}
depends_on = [
octopusdeploy_project_group.tp,
octopusdeploy_deployment_process.process
]
}
```

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

### Required

- `donor_package` (Attributes) Donor Packages. (see [below for nested schema](#nestedatt--donor_package))
- `project_id` (String) The associated project ID.
- `space_id` (String) Space ID of the associated project.

### Optional

- `donor_package_step_id` (String) The associated donor package step ID.
- `template` (String)

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

Optional:

- `deployment_action` (String) Deployment action.
- `package_reference` (String) Package reference.


Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
resource "octopusdeploy_project_group" "tp" {
name = "DevOps Projects"
description = "My DevOps projects group"
}

resource "octopusdeploy_project" "tp" {
name = "My DevOps Project"
description = "test project"
lifecycle_id = "Lifecycles-1"
project_group_id = octopusdeploy_project_group.tp.id

depends_on = [octopusdeploy_project_group.tp]
}

resource "octopusdeploy_deployment_process" "process" {
project_id = octopusdeploy_project.tp.id

step {
name = "Hello World"
target_roles = [ "hello-world" ]
start_trigger = "StartAfterPrevious"
package_requirement = "LetOctopusDecide"
condition = "Success"

run_script_action {
name = "Hello World"
is_disabled = false
is_required = true
script_body = "Write-Host 'hello world'"
script_syntax = "PowerShell"
can_be_used_for_project_versioning = true
sort_order = 1


package {
name = "Package"
feed_id = "feeds-builtin"
package_id = "myExpressApp"
acquisition_location = "Server"
extract_during_deployment = true
}
}
}

depends_on = [octopusdeploy_project.tp]
}

resource "octopusdeploy_project_versioning_strategy" "tp" {
project_id = octopusdeploy_project.tp.id
space_id = octopusdeploy_project.tp.space_id
donor_package_step_id = octopusdeploy_deployment_process.process.step[0].run_script_action[0].id
donor_package = {
deployment_action = "Hello World"
package_reference = "Package"
}
depends_on = [
octopusdeploy_project_group.tp,
octopusdeploy_deployment_process.process
]
}
1 change: 1 addition & 0 deletions octopusdeploy_framework/framework_provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ func (p *octopusDeployFrameworkProvider) Resources(ctx context.Context) []func()
NewLibraryVariableSetFeedResource,
NewVariableResource,
NewProjectResource,
NewProjectVersioningStrategyResource,
NewMachineProxyResource,
NewTagResource,
NewDockerContainerRegistryFeedResource,
Expand Down
190 changes: 190 additions & 0 deletions octopusdeploy_framework/resource_project_versioning_strategy.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
package octopusdeploy_framework

import (
"context"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/packages"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects"
"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"
"log"
"net/http"
)

var _ resource.Resource = &projectVersioningStrategyResource{}

type projectVersioningStrategyResource struct {
*Config
}

func NewProjectVersioningStrategyResource() resource.Resource {
return &projectVersioningStrategyResource{}
}

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

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

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

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

project, err := projects.GetByID(r.Client, plan.SpaceID.ValueString(), plan.ProjectID.ValueString())
if err != nil {
if apiError, ok := err.(*core.APIError); ok {
if apiError.StatusCode == http.StatusNotFound {
log.Printf("[INFO] associated project (%s) not found; deleting version strategy from state", plan.ProjectID.ValueString())
resp.State.RemoveResource(ctx)
}
} else {
resp.Diagnostics.AddError("Failed to read associated project", err.Error())
}
return
}
versioningStrategy := mapStateToProjectVersioningStrategy(&plan)
project.VersioningStrategy = versioningStrategy

_, err = projects.Update(r.Client, project)
if err != nil {
resp.Diagnostics.AddError("Error updating associated project", err.Error())
return
}

updatedProject, err := projects.GetByID(r.Client, plan.SpaceID.ValueString(), plan.ProjectID.ValueString())
if err != nil {
if apiError, ok := err.(*core.APIError); ok {
if apiError.StatusCode == http.StatusNotFound {
log.Printf("[INFO] associated project (%s) not found; deleting version strategy from state", plan.ProjectID.ValueString())
resp.State.RemoveResource(ctx)
}
} else {
resp.Diagnostics.AddError("Failed to read associated project", err.Error())
}
return
}

mapProjectVersioningStrategyToState(updatedProject.VersioningStrategy, &plan)
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
}

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

project, err := projects.GetByID(r.Client, state.SpaceID.ValueString(), state.ProjectID.ValueString())
if err != nil {
if apiError, ok := err.(*core.APIError); ok {
if apiError.StatusCode == http.StatusNotFound {
log.Printf("[INFO] associated project (%s) not found; deleting version strategy from state", state.ProjectID.ValueString())
resp.State.RemoveResource(ctx)
}
} else {
resp.Diagnostics.AddError("Failed to read associated project", err.Error())
}
return
}
mapProjectVersioningStrategyToState(project.VersioningStrategy, &state)

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

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

existingProject, err := projects.GetByID(r.Client, plan.SpaceID.ValueString(), plan.ProjectID.ValueString())
if err != nil {
resp.Diagnostics.AddError("Error retrieving associated project", err.Error())
return
}

versioningStrategy := mapStateToProjectVersioningStrategy(&plan)
existingProject.VersioningStrategy = versioningStrategy

_, err = projects.Update(r.Client, existingProject)
if err != nil {
resp.Diagnostics.AddError("Error updating associated project", err.Error())
return
}

updatedProject, err := projects.GetByID(r.Client, plan.SpaceID.ValueString(), plan.ProjectID.ValueString())
if err != nil {
resp.Diagnostics.AddError("Error retrieving associated project", err.Error())
return
}

mapProjectVersioningStrategyToState(updatedProject.VersioningStrategy, &plan)
resp.Diagnostics.Append(resp.State.Set(ctx, plan)...)
}

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

project, err := projects.GetByID(r.Client, state.SpaceID.ValueString(), state.ProjectID.ValueString())
if err != nil {
resp.Diagnostics.AddError("Error retrieving project", err.Error())
return
}

project.VersioningStrategy = &projects.VersioningStrategy{}
_, err = projects.Update(r.Client, project)
if err != nil {
resp.Diagnostics.AddError("Error updating project to remove versioning strategy", err.Error())
return
}

resp.State.RemoveResource(ctx)
}

func mapStateToProjectVersioningStrategy(state *schemas.ProjectVersioningStrategyModel) *projects.VersioningStrategy {
var donorPackageStepID *string
donorPackageStepIDString := state.DonorPackageStepID.ValueString()
if donorPackageStepIDString != "" {
donorPackageStepID = &donorPackageStepIDString
}

return &projects.VersioningStrategy{
Template: state.Template.ValueString(),
DonorPackageStepID: donorPackageStepID,
DonorPackage: &packages.DeploymentActionPackage{
DeploymentAction: state.DonorPackage.DeploymentAction.ValueString(),
PackageReference: state.DonorPackage.PackageReference.ValueString(),
},
}
}

func mapProjectVersioningStrategyToState(versioningStrategy *projects.VersioningStrategy, state *schemas.ProjectVersioningStrategyModel) {
if versioningStrategy.DonorPackageStepID != nil {
state.DonorPackageStepID = types.StringValue(*versioningStrategy.DonorPackageStepID)
}
state.Template = types.StringValue(versioningStrategy.Template)
state.DonorPackage.PackageReference = types.StringValue(versioningStrategy.DonorPackage.PackageReference)
state.DonorPackage.DeploymentAction = types.StringValue(versioningStrategy.DonorPackage.DeploymentAction)
}
2 changes: 1 addition & 1 deletion octopusdeploy_framework/schemas/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,8 +147,8 @@ func (p ProjectSchema) GetResourceSchema() resourceSchema.Schema {
},
},
"versioning_strategy": resourceSchema.ListNestedBlock{
DeprecationMessage: "versioning_strategy is deprecated in favor of resource project_versioning strategy",
NestedObject: resourceSchema.NestedBlockObject{

Attributes: map[string]resourceSchema.Attribute{
"donor_package_step_id": util.ResourceString().Optional().Build(),
"template": util.ResourceString().Optional().Computed().Build(),
Expand Down
Loading
Loading