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: deployment freezes #822

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
cf62bb2
wip
benPearce1 Nov 20, 2024
fb919c6
fixed casing on project scope
benPearce1 Nov 21, 2024
1ce7c08
refactored state cleanup to support all functions
benPearce1 Nov 21, 2024
63c094b
added date time schema
benPearce1 Nov 21, 2024
9b1ee2f
fixes for deployment freeze resources
benPearce1 Nov 21, 2024
879becd
store the supplied times in state for create and updated
benPearce1 Nov 21, 2024
84b7fe4
updated client library
benPearce1 Nov 22, 2024
a67a0b6
Merge branch 'main' into bp/deployment-freeze
benPearce1 Nov 22, 2024
b2eb8d6
update following merge
benPearce1 Nov 22, 2024
c761241
reverted incorrect change to missing resource handling
benPearce1 Nov 22, 2024
1aa7b68
added update test to project freeze
benPearce1 Nov 24, 2024
e9da4d6
refresh docs
benPearce1 Nov 25, 2024
9b70c40
extracted deployment freeze project scope out to separate resource
benPearce1 Nov 27, 2024
7660848
updated go-octopusdeploy reference
benPearce1 Nov 27, 2024
c22a0c0
updated examples
benPearce1 Nov 27, 2024
70f2a6b
updated build composite id to take params
benPearce1 Nov 28, 2024
f4ac55b
split deployment freeze project scopes to separate resource and added…
benPearce1 Nov 28, 2024
96dc497
updated go-octopusdeploy reference
benPearce1 Nov 28, 2024
e31e4f4
update go.sum
benPearce1 Nov 28, 2024
efd1d83
updated docs
benPearce1 Nov 28, 2024
9e504eb
missed return statement
benPearce1 Nov 28, 2024
b834bc4
missed return statement
benPearce1 Nov 28, 2024
91da299
updated test to perform update
benPearce1 Nov 28, 2024
268cf29
Merge branch 'main' into bp/deployment-freeze
benPearce1 Nov 28, 2024
a82d9d7
fixed up imports
benPearce1 Nov 28, 2024
dfc11c4
chore: fix docs
domenicsim1 Nov 29, 2024
872fc77
fixed formatting string
benPearce1 Nov 29, 2024
7deb89d
Merge remote-tracking branch 'refs/remotes/origin/bp/deployment-freez…
benPearce1 Nov 29, 2024
0b61d02
deleted tests for testing build issue
benPearce1 Nov 29, 2024
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 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.60.0
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.61.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.60.0 h1:9j4IQ1UcAuaTytlBzQ7Mmoy/dLtofYfSGNiM22+sLXs=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.60.0/go.mod h1:ggvOXzMnq+w0pLg6C9zdjz6YBaHfO3B3tqmmB7JQdaw=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.61.0 h1:Qjlsla1okbEOmN3wByh6x+m2B0QJDeL9jKtkKZa1NTo=
github.com/OctopusDeploy/go-octopusdeploy/v2 v2.61.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
2 changes: 1 addition & 1 deletion internal/errors/error.go
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package errors

Check warning on line 1 in internal/errors/error.go

View workflow job for this annotation

GitHub Actions / build

should have a package comment

import (
"context"
"github.com/hashicorp/terraform-plugin-framework/resource"
"log"
"net/http"

"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/core"
"github.com/OctopusDeploy/terraform-provider-octopusdeploy/octopusdeploy_framework/schemas"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)
Expand All @@ -18,7 +18,7 @@
return nil
}

func ProcessApiError(ctx context.Context, d *schema.ResourceData, err error, resource string) diag.Diagnostics {

Check warning on line 21 in internal/errors/error.go

View workflow job for this annotation

GitHub Actions / build

func ProcessApiError should be ProcessAPIError
if err == nil {
return nil
}
Expand All @@ -38,7 +38,7 @@
return nil
}

func ProcessApiErrorV2(ctx context.Context, resp *resource.ReadResponse, resource schemas.IResourceModel, err error, resourceDescription string) error {

Check warning on line 41 in internal/errors/error.go

View workflow job for this annotation

GitHub Actions / build

func ProcessApiErrorV2 should be ProcessAPIErrorV2
if err == nil {
return nil
}
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 @@ -125,6 +125,7 @@ func (p *octopusDeployFrameworkProvider) Resources(ctx context.Context) []func()
NewSSHConnectionWorkerResource,
NewScriptModuleResource,
NewUserResource,
NewDeploymentFreezeResource,
NewServiceAccountOIDCIdentity,
}
}
Expand Down
209 changes: 209 additions & 0 deletions octopusdeploy_framework/resource_deploymentfreeze.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
package octopusdeploy_framework

import (
"context"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/deploymentfreezes"
"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/diag"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"time"
)

type deploymentFreezeModel struct {
Name types.String `tfsdk:"name"`
Start types.String `tfsdk:"start"`
End types.String `tfsdk:"end"`
ProjectEnvironmentScope types.Map `tfsdk:"project_environment_scope"`

schemas.ResourceModel
}

type deploymentFreezeResource struct {
*Config
}

var _ resource.Resource = &deploymentFreezeResource{}

func NewDeploymentFreezeResource() resource.Resource {
return &deploymentFreezeResource{}
}

func (f *deploymentFreezeResource) Metadata(_ context.Context, _ resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = util.GetTypeName(schemas.DeploymentFreezeResourceName)
}

func (f *deploymentFreezeResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schemas.DeploymentFreezeSchema{}.GetResourceSchema()
}

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

func (f *deploymentFreezeResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state *deploymentFreezeModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}

deploymentFreeze, err := deploymentfreezes.GetById(f.Config.Client, state.GetID())
if err != nil {
if err := errors.ProcessApiErrorV2(ctx, resp, state, err, "deployment freeze"); err != nil {
resp.Diagnostics.AddError("unable to load deployment freeze", err.Error())
}
return
}

diags := mapToState(ctx, state, deploymentFreeze, true)
if diags.HasError() {
resp.Diagnostics = diags
return
}

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

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

var deploymentFreeze *deploymentfreezes.DeploymentFreeze
deploymentFreeze, err := mapFromState(plan)
if err != nil {
resp.Diagnostics.AddError("error while creating deployment freeze", err.Error())
return
}

createdFreeze, err := deploymentfreezes.Add(f.Config.Client, deploymentFreeze)
if err != nil {
resp.Diagnostics.AddError("error while creating deployment freeze", err.Error())
return
}

diags = mapToState(ctx, plan, createdFreeze, false)
if diags.HasError() {
return
}

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

func (f *deploymentFreezeResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan *deploymentFreezeModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}

existingFreeze, err := deploymentfreezes.GetById(f.Config.Client, plan.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError("unable to load deployment freeze", err.Error())
return
}

updatedFreeze, err := mapFromState(plan)
if err != nil {
resp.Diagnostics.AddError("error while mapping deployment freeze", err.Error())
Copy link
Contributor

Choose a reason for hiding this comment

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

missing return?

}

updatedFreeze.SetID(existingFreeze.GetID())
updatedFreeze.Links = existingFreeze.Links

updatedFreeze, err = deploymentfreezes.Update(f.Config.Client, updatedFreeze)
if err != nil {
resp.Diagnostics.AddError("error while updating deployment freeze", err.Error())
Copy link
Contributor

Choose a reason for hiding this comment

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

missing return?

}

diags := mapToState(ctx, plan, updatedFreeze, false)
if diags.HasError() {
return
}

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

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

freeze, err := deploymentfreezes.GetById(f.Config.Client, state.GetID())
if err != nil {
resp.Diagnostics.AddError("unable to load deployment freeze", err.Error())
return
}

err = deploymentfreezes.Delete(f.Config.Client, freeze)
if err != nil {
resp.Diagnostics.AddError("unable to delete deployment freeze", err.Error())
Copy link
Contributor

Choose a reason for hiding this comment

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

return?

}

resp.State.RemoveResource(ctx)
}

func mapToState(ctx context.Context, state *deploymentFreezeModel, deploymentFreeze *deploymentfreezes.DeploymentFreeze, useSourceForDates bool) diag.Diagnostics {
state.ID = types.StringValue(deploymentFreeze.ID)
state.Name = types.StringValue(deploymentFreeze.Name)
if useSourceForDates {
state.Start = types.StringValue(deploymentFreeze.Start.Format(time.RFC3339))
state.End = types.StringValue(deploymentFreeze.End.Format(time.RFC3339))
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

if the time contains a timezone the value for the times are returned with a server local timezone, for create and update operations we just store the value provided in the plan, for a refresh (read) operation it will store the value from the api as it may have been changed.


if len(deploymentFreeze.ProjectEnvironmentScope) > 0 {
value, diags := util.ConvertMapStringArrayToMapAttrValue(ctx, deploymentFreeze.ProjectEnvironmentScope)
if diags.HasError() {
return diags
}
state.ProjectEnvironmentScope, diags = types.MapValueFrom(ctx, types.SetType{ElemType: types.StringType}, value)
}

return nil
}

func mapFromState(state *deploymentFreezeModel) (*deploymentfreezes.DeploymentFreeze, error) {
start, err := time.Parse(time.RFC3339, state.Start.ValueString())
if err != nil {
return nil, err
}
end, err := time.Parse(time.RFC3339, state.End.ValueString())
if err != nil {
return nil, err
}

start = start.UTC()
end = end.UTC()

freeze := deploymentfreezes.DeploymentFreeze{
Name: state.Name.ValueString(),
Start: &start,
End: &end,
}

freeze.ID = state.ID.String()
if !state.ProjectEnvironmentScope.IsNull() {
scopeMap := make(map[string][]string)
for k, v := range state.ProjectEnvironmentScope.Elements() {
var scopes []string
for _, s := range v.(types.Set).Elements() {
scopes = append(scopes, s.(types.String).ValueString())
}

scopeMap[k] = scopes
}
freeze.ProjectEnvironmentScope = scopeMap
}

return &freeze, nil
}
103 changes: 103 additions & 0 deletions octopusdeploy_framework/resource_deploymentfreeze_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package octopusdeploy_framework

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

func TestNewDeploymentFreezeResource(t *testing.T) {
localName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
resourceName := "octopusdeploy_deployment_freeze." + localName
name := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
start := fmt.Sprintf("%d-11-21T06:30:00+10:00", time.Now().Year()+1)
end := fmt.Sprintf("%d-11-21T08:30:00+10:00", time.Now().Year()+1)
projectName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
environmentName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
spaceName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
projectGroupName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)
lifecycleName := acctest.RandStringFromCharSet(20, acctest.CharSetAlpha)

resource.Test(t, resource.TestCase{
CheckDestroy: testDeploymentFreezeCheckDestroy,
PreCheck: func() { TestAccPreCheck(t) },
ProtoV6ProviderFactories: ProtoV6ProviderFactories(),
Steps: []resource.TestStep{
{
Check: resource.ComposeTestCheckFunc(
testDeploymentFreezeExists(resourceName),
resource.TestCheckResourceAttr(resourceName, "name", name),
resource.TestCheckResourceAttr(resourceName, "start", start),
resource.TestCheckResourceAttr(resourceName, "end", end)),
Config: testDeploymentFreezeBasic(localName, name, start, end, spaceName, environmentName, projectName, projectGroupName, lifecycleName),
},
Copy link
Contributor

Choose a reason for hiding this comment

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

please add one more step so we can also test update.

},
})
}

func testDeploymentFreezeBasic(localName string, freezeName string, start string, end string, spaceName string, environmentName string, projectName string, projectGroupName string, lifecycleName string) string {
spaceLocalName := fmt.Sprintf("space_%s", localName)
environmentLocalName := fmt.Sprintf("environment_%s", localName)
projectLocalName := fmt.Sprintf("project_%s", localName)
lifecycleLocalName := fmt.Sprintf("lifecycle_%s", localName)
projectGroupLocalName := fmt.Sprintf("project_group_%s", localName)

projectScopes := fmt.Sprintf(`{
"${resource.octopusdeploy_project.%s.id}" = [ resource.octopusdeploy_environment.%s.id
}`, projectLocalName, environmentLocalName)

return fmt.Sprintf(`
%s

%s

%s

%s

%s

resource "octopusdeploy_deployment_freeze" "%s" {
name = "%s"
start = "%s"
end = "%s"
project_environment_scope = {
%s
}`,
createSpace(spaceLocalName, spaceName),
createEnvironment(spaceLocalName, environmentLocalName, environmentName),
createLifecycle(spaceLocalName, lifecycleLocalName, lifecycleName),
createProjectGroup(spaceLocalName, projectGroupLocalName, projectGroupName),
createProject(spaceLocalName, projectLocalName, projectName, lifecycleLocalName, projectGroupLocalName),
localName, freezeName, start, end, projectScopes)
}

func testDeploymentFreezeExists(prefix string) resource.TestCheckFunc {
return func(s *terraform.State) error {
freezeId := s.RootModule().Resources[prefix].Primary.ID
if _, err := deploymentfreezes.GetById(octoClient, freezeId); err != nil {
return err
}

return nil
}
}

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

feed, err := deploymentfreezes.GetById(octoClient, rs.Primary.ID)
if err == nil && feed != nil {
return fmt.Errorf("Deployment Freeze (%s) still exists", rs.Primary.ID)
}
}

return nil
}
10 changes: 1 addition & 9 deletions octopusdeploy_framework/resource_project_flatten.go
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,7 @@ func flattenTemplates(templates []actiontemplates.ActionTemplateParameter) types
"default_value": util.StringOrNull(template.DefaultValue.Value),
"display_settings": types.MapValueMust(
types.StringType,
convertMapStringToMapAttrValue(template.DisplaySettings),
util.ConvertMapStringToMapAttrValue(template.DisplaySettings),
),
})

Expand Down Expand Up @@ -310,14 +310,6 @@ func flattenReleaseCreationStrategy(strategy *projects.ReleaseCreationStrategy)
return types.ListValueMust(types.ObjectType{AttrTypes: getReleaseCreationStrategyAttrTypes()}, []attr.Value{obj})
}

func convertMapStringToMapAttrValue(m map[string]string) map[string]attr.Value {
result := make(map[string]attr.Value, len(m))
for k, v := range m {
result[k] = types.StringValue(v)
}
return result
}
Comment on lines -313 to -319
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This was moved to util.go


func flattenDeploymentActionPackage(pkg *packages.DeploymentActionPackage) types.List {
if pkg == nil {
return types.ListNull(types.ObjectType{AttrTypes: getDonorPackageAttrTypes()})
Expand Down
Loading
Loading