From d7c6ce90aa5f478965875d70383abb3c42dfb0a1 Mon Sep 17 00:00:00 2001 From: "denys.kostynchuk" Date: Fri, 6 Mar 2026 12:01:01 +1300 Subject: [PATCH 1/8] Introduce platform hub policies operations --- .../platform_hub_policy.go | 130 +++++++++++ .../platform_hub_policy_service.go | 126 ++++++++++ .../platform_hub_policy_service_test.go | 105 +++++++++ .../platform_hub_policy_test.go | 220 ++++++++++++++++++ .../platform_hub_policy_version_service.go | 118 ++++++++++ ...latform_hub_policy_version_service_test.go | 103 ++++++++ play | Bin 0 -> 11634205 bytes 7 files changed, 802 insertions(+) create mode 100644 pkg/platformhubpolicies/platform_hub_policy.go create mode 100644 pkg/platformhubpolicies/platform_hub_policy_service.go create mode 100644 pkg/platformhubpolicies/platform_hub_policy_service_test.go create mode 100644 pkg/platformhubpolicies/platform_hub_policy_test.go create mode 100644 pkg/platformhubpolicies/platform_hub_policy_version_service.go create mode 100644 pkg/platformhubpolicies/platform_hub_policy_version_service_test.go create mode 100644 play diff --git a/pkg/platformhubpolicies/platform_hub_policy.go b/pkg/platformhubpolicies/platform_hub_policy.go new file mode 100644 index 00000000..b8e98b09 --- /dev/null +++ b/pkg/platformhubpolicies/platform_hub_policy.go @@ -0,0 +1,130 @@ +package platformhubpolicies + +import ( + "sync" + + "github.com/go-playground/validator/v10" + "github.com/go-playground/validator/v10/non-standard/validators" +) + +// PlatformHubPolicy represents a Platform Hub policy resource. +type PlatformHubPolicy struct { + Name string `json:"Name" validate:"required,notblank"` + GitRef string `json:"GitRef" validate:"required,notblank"` + Slug string `json:"Slug" validate:"required,notblank"` + Description string `json:"Description,omitempty"` + ScopeRego string `json:"ScopeRego" validate:"required,notblank"` + ConditionsRego string `json:"ConditionsRego" validate:"required,notblank"` + ViolationReason string `json:"ViolationReason,omitempty"` + ViolationAction string `json:"ViolationAction" validate:"required,notblank"` +} + +// NewPlatformHubPolicy creates and initializes a Platform Hub policy. +func NewPlatformHubPolicy(name, gitRef, slug, scopeRego, conditionsRego, violationAction string) (*PlatformHubPolicy, error) { + policy := PlatformHubPolicy{ + Name: name, + GitRef: gitRef, + Slug: slug, + ScopeRego: scopeRego, + ConditionsRego: conditionsRego, + ViolationAction: violationAction, + } + + if validationError := policy.Validate(); validationError != nil { + return nil, validationError + } + + return &policy, nil +} + +// GetName returns the name of the policy. +func (p *PlatformHubPolicy) GetName() string { + return p.Name +} + +// SetName sets the name of the policy. +func (p *PlatformHubPolicy) SetName(name string) { + p.Name = name +} + +// GetGitRef returns the git ref of the policy. +func (p *PlatformHubPolicy) GetGitRef() string { + return p.GitRef +} + +// GetSlug returns the slug of the policy. +func (p *PlatformHubPolicy) GetSlug() string { + return p.Slug +} + +// GetDescription returns the description of the policy. +func (p *PlatformHubPolicy) GetDescription() string { + return p.Description +} + +// SetDescription sets the description of the policy. +func (p *PlatformHubPolicy) SetDescription(description string) { + p.Description = description +} + +// GetScopeRego returns the scope Rego of the policy. +func (p *PlatformHubPolicy) GetScopeRego() string { + return p.ScopeRego +} + +// SetScopeRego sets the scope Rego of the policy. +func (p *PlatformHubPolicy) SetScopeRego(scopeRego string) { + p.ScopeRego = scopeRego +} + +// GetConditionsRego returns the conditions rego of the policy. +func (p *PlatformHubPolicy) GetConditionsRego() string { + return p.ConditionsRego +} + +// SetConditionsRego sets the conditions rego of the policy. +func (p *PlatformHubPolicy) SetConditionsRego(conditionsRego string) { + p.ConditionsRego = conditionsRego +} + +// GetViolationReason returns the violation reason of the policy. +func (p *PlatformHubPolicy) GetViolationReason() string { + return p.ViolationReason +} + +// SetViolationReason sets the violation reason of the policy. +func (p *PlatformHubPolicy) SetViolationReason(violationReason string) { + p.ViolationReason = violationReason +} + +// GetViolationAction returns the violation action of the policy. +func (p *PlatformHubPolicy) GetViolationAction() string { + return p.ViolationAction +} + +// SetViolationAction sets the violation action of the policy. +func (p *PlatformHubPolicy) SetViolationAction(violationAction string) { + p.ViolationAction = violationAction +} + +// Validate checks the state of the policy and returns an error if invalid. +func (p *PlatformHubPolicy) Validate() error { + validate, err := getValidator() + if err != nil { + return err + } + + return validate.Struct(p) +} + +var getValidator = sync.OnceValues(buildValidator) + +func buildValidator() (*validator.Validate, error) { + v := validator.New() + err := v.RegisterValidation("notblank", validators.NotBlank) + if err != nil { + return nil, err + } + + return v, nil +} diff --git a/pkg/platformhubpolicies/platform_hub_policy_service.go b/pkg/platformhubpolicies/platform_hub_policy_service.go new file mode 100644 index 00000000..4884855e --- /dev/null +++ b/pkg/platformhubpolicies/platform_hub_policy_service.go @@ -0,0 +1,126 @@ +package platformhubpolicies + +import ( + "github.com/OctopusDeploy/go-octopusdeploy/v2/internal" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/newclient" +) + +const template = "/api/platformhub/{gitRef}/policies{/slug}{?skip,take,partialName}" + +// Add creates a new Platform Hub policy. +func Add(client newclient.Client, policy PlatformHubPolicy, commitMessage string) (*PlatformHubPolicy, error) { + command, path, commandError := buildAddCommand(client, policy, commitMessage) + if commandError != nil { + return nil, commandError + } + + createdPolicy, addError := newclient.Post[PlatformHubPolicy](client.HttpSession(), path, command) + if addError != nil { + return nil, addError + } + + return createdPolicy, nil +} + +// PoliciesQuery represents query parameters for listing policies. +type PoliciesQuery struct { + GitRef string `uri:"gitRef" json:"gitRef"` + PartialName string `uri:"partialName,omitempty" json:"partialName,omitempty"` + Skip int `uri:"skip,omitempty" json:"skip,omitempty"` + Take int `uri:"take,omitempty" json:"take,omitempty"` +} + +// PoliciesResponse represents the response from listing Platform Hub policies. +type PoliciesResponse struct { + Policies []PlatformHubPolicy `json:"Policies"` + ItemsPerPage int `json:"ItemsPerPage"` + FilteredItemsCount int `json:"FilteredItemsCount"` + TotalItemsCount int `json:"TotalItemsCount"` +} + +// Get returns a collection of Platform Hub policies based on the provided query. +func Get(client newclient.Client, query PoliciesQuery) (*PoliciesResponse, error) { + path, pathError := client.URITemplateCache().Expand(template, query) + if pathError != nil { + return nil, pathError + } + + response, responseError := newclient.Get[PoliciesResponse](client.HttpSession(), path) + if responseError != nil { + return nil, responseError + } + + return response, nil +} + +// GetBySlug returns the Platform Hub policy that matches given GitRef and Slug. +func GetBySlug(client newclient.Client, gitRef string, slug string) (*PlatformHubPolicy, error) { + path, pathError := client.URITemplateCache().Expand(template, map[string]any{"gitRef": gitRef, "slug": slug}) + if pathError != nil { + return nil, pathError + } + + policy, err := newclient.Get[PlatformHubPolicy](client.HttpSession(), path) + if err != nil { + return nil, err + } + + return policy, nil +} + +// Update modifies an existing Platform Hub policy. +func Update(client newclient.Client, policy PlatformHubPolicy, commitMessage string) (*PlatformHubPolicy, error) { + command, path, commandError := buildUpdateCommand(client, policy, commitMessage) + if commandError != nil { + return nil, commandError + } + + updatedPolicy, updateError := newclient.Put[PlatformHubPolicy](client.HttpSession(), path, command) + if updateError != nil { + return nil, updateError + } + + return updatedPolicy, nil +} + +func buildAddCommand(client newclient.Client, policy PlatformHubPolicy, commitMessage string) (platformHubPolicyUpsertCommand, string, error) { + if validationError := policy.Validate(); validationError != nil { + return platformHubPolicyUpsertCommand{}, "", internal.CreateValidationFailureError("Add", validationError) + } + + path, pathError := client.URITemplateCache().Expand(template, map[string]any{"gitRef": policy.GitRef}) + if pathError != nil { + return platformHubPolicyUpsertCommand{}, "", pathError + } + + command := platformHubPolicyUpsertCommand{ + ChangeDescription: commitMessage, + PlatformHubPolicy: policy, + } + + return command, path, nil +} + +func buildUpdateCommand(client newclient.Client, policy PlatformHubPolicy, commitMessage string) (platformHubPolicyUpsertCommand, string, error) { + if validationError := policy.Validate(); validationError != nil { + return platformHubPolicyUpsertCommand{}, "", internal.CreateValidationFailureError("Update", validationError) + } + + path, pathError := client.URITemplateCache().Expand(template, map[string]any{"gitRef": policy.GitRef, "slug": policy.Slug}) + if pathError != nil { + return platformHubPolicyUpsertCommand{}, "", pathError + } + + command := platformHubPolicyUpsertCommand{ + ChangeDescription: commitMessage, + PlatformHubPolicy: policy, + } + + return command, path, nil +} + +type platformHubPolicyUpsertCommand struct { + ChangeDescription string `json:"ChangeDescription,omitempty"` + + PlatformHubPolicy +} diff --git a/pkg/platformhubpolicies/platform_hub_policy_service_test.go b/pkg/platformhubpolicies/platform_hub_policy_service_test.go new file mode 100644 index 00000000..e7dbd30a --- /dev/null +++ b/pkg/platformhubpolicies/platform_hub_policy_service_test.go @@ -0,0 +1,105 @@ +package platformhubpolicies + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/newclient" + "github.com/kinbiko/jsonassert" + "github.com/stretchr/testify/require" +) + +func TestPlatformHubPolicyService_BuildAddCommand_Valid(t *testing.T) { + var client = newclient.NewClient(&newclient.HttpSession{}) + + expectedPolicy := newPolicyBuilder(). + WithName("ValidPolicyName"). + WithGitRef("refs/heads/test/branch"). + WithDescription("Testing policy"). + WithViolationReason("Missing manual intervention") + urlEncodedGitRef := "refs%2Fheads%2Ftest%2Fbranch" + commitMessage := "Create new policy" + + // Act + newPolicy := expectedPolicy.Create() + command, path, commandError := buildAddCommand(client, *newPolicy, commitMessage) + + require.NoError(t, commandError) + require.Equal(t, fmt.Sprintf("/api/platformhub/%s/policies", urlEncodedGitRef), path) + + testAssertUpsertCommand(t, command, expectedPolicy, commitMessage) + testAssertUpsertCommandMarshalJSON(t, command, expectedPolicy, commitMessage) +} + +func TestPlatformHubPolicyService_BuildAddCommand_Invalid(t *testing.T) { + var client = newclient.NewClient(&newclient.HttpSession{}) + + invalidPolicy := newPolicyBuilder().WithName("InvalidPolicyName").WithConditionsRego("").Create() + + _, _, invalidCommandError := buildAddCommand(client, *invalidPolicy, "commit invalid command") + + require.ErrorContains(t, invalidCommandError, "ConditionsRego") +} + +func TestPlatformHubPolicyService_BuildUpdateCommand_Valid(t *testing.T) { + var client = newclient.NewClient(&newclient.HttpSession{}) + + expectedPolicy := newPolicyBuilder().WithName("Valid Policy"). + WithGitRef("refs/heads/main"). + WithSlug("valid_policy"). + WithDescription("Ok"). + WithViolationReason("None") + urlEncodedGitRef := "refs%2Fheads%2Fmain" + commitMessage := "Update valid policy" + + // Act + newPolicy := expectedPolicy.Create() + command, path, commandError := buildUpdateCommand(client, *newPolicy, commitMessage) + + require.NoError(t, commandError) + require.Equal(t, fmt.Sprintf("/api/platformhub/%s/policies/%s", urlEncodedGitRef, expectedPolicy.slug), path) + + testAssertUpsertCommand(t, command, expectedPolicy, commitMessage) + testAssertUpsertCommandMarshalJSON(t, command, expectedPolicy, commitMessage) +} + +func TestPlatformHubPolicyService_BuildUpdateCommand_Invalid(t *testing.T) { + var client = newclient.NewClient(&newclient.HttpSession{}) + + invalidPolicy := newPolicyBuilder().WithName("Invalid Action").WithViolationAction("").Create() + _, _, invalidCommandError := buildUpdateCommand(client, *invalidPolicy, "commit invalid command") + + require.ErrorContains(t, invalidCommandError, "ViolationAction") +} + +func testAssertUpsertCommand(t *testing.T, command platformHubPolicyUpsertCommand, expected *policyBuilder, expectedCommit string) { + require.NotNil(t, command) + require.Equal(t, expected.name, command.GetName()) + require.Equal(t, expected.gitRef, command.GetGitRef()) + require.Equal(t, expected.slug, command.GetSlug()) + require.Equal(t, expected.scopeRego, command.GetScopeRego()) + require.Equal(t, expected.conditionsRego, command.GetConditionsRego()) + require.Equal(t, expected.violationAction, command.GetViolationAction()) + require.Equal(t, expectedCommit, command.ChangeDescription) +} + +func testAssertUpsertCommandMarshalJSON(t *testing.T, command platformHubPolicyUpsertCommand, expected *policyBuilder, expectedCommit string) { + jsonCommand, jsonError := json.Marshal(command) + require.NoError(t, jsonError) + require.NotNil(t, jsonCommand) + + expectedJson := fmt.Sprintf(`{ + "ChangeDescription": "%s", + "Name": "%s", + "GitRef": "%s", + "Slug": "%s", + "Description": "%s", + "ScopeRego": "%s", + "ConditionsRego": "%s", + "ViolationReason": "%s", + "ViolationAction": "%s" + }`, expectedCommit, expected.name, expected.gitRef, expected.slug, expected.description, expected.scopeRego, expected.conditionsRego, expected.violationReason, expected.violationAction) + + jsonassert.New(t).Assertf(expectedJson, string(jsonCommand)) +} diff --git a/pkg/platformhubpolicies/platform_hub_policy_test.go b/pkg/platformhubpolicies/platform_hub_policy_test.go new file mode 100644 index 00000000..d32ff623 --- /dev/null +++ b/pkg/platformhubpolicies/platform_hub_policy_test.go @@ -0,0 +1,220 @@ +package platformhubpolicies + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestPlatformHubPolicyNew_Valid(t *testing.T) { + newPolicy, err := NewPlatformHubPolicy("Policy One", "refs/heads/main", "policy_one", "package policy_one", "package policy_one", "block") + require.NoError(t, err) + require.NotNil(t, newPolicy) + require.NoError(t, newPolicy.Validate()) + + require.Equal(t, "Policy One", newPolicy.GetName()) + require.Equal(t, "refs/heads/main", newPolicy.GetGitRef()) + require.Equal(t, "policy_one", newPolicy.GetSlug()) + require.Empty(t, newPolicy.GetDescription()) + require.Equal(t, "package policy_one", newPolicy.GetScopeRego()) + require.Equal(t, "package policy_one", newPolicy.GetConditionsRego()) + require.Equal(t, "block", newPolicy.GetViolationAction()) + require.Empty(t, newPolicy.GetViolationReason()) +} + +func TestPlatformHubPolicyNew_Invalid(t *testing.T) { + // Invalid GitRef + gitRef, gitRefError := NewPlatformHubPolicy("Policy One", "", "policy_one", "package policy_one", "package policy_one", "block") + require.ErrorContains(t, gitRefError, "GitRef") + require.Nil(t, gitRef) + + // Invalid GitRef + slug, slugError := NewPlatformHubPolicy("Policy One", "main", " ", "package policy_one", "package policy_one", "block") + require.ErrorContains(t, slugError, "Slug") + require.Nil(t, slug) +} + +func TestPlatformHubPolicyValidate_Name(t *testing.T) { + policy := newPolicyBuilder().Create() + + // Valid + policy.SetName("Valid Name") + require.NoError(t, policy.Validate()) + + // Invalid + policy.SetName("") + require.ErrorContains(t, policy.Validate(), "Name") + + policy.SetName(" ") + require.ErrorContains(t, policy.Validate(), "Name") +} + +func TestPlatformHubPolicyValidate_GitRef(t *testing.T) { + policy := newPolicyBuilder().WithGitRef("main").Create() + + // Valid + require.NoError(t, policy.Validate()) + + // Invalid + policy = newPolicyBuilder().WithGitRef("").Create() + require.ErrorContains(t, policy.Validate(), "GitRef") + + policy = newPolicyBuilder().WithGitRef(" ").Create() + require.ErrorContains(t, policy.Validate(), "GitRef") +} + +func TestPlatformHubPolicyValidate_Slug(t *testing.T) { + policy := newPolicyBuilder().WithSlug("valid_slug").Create() + + // Valid + require.NoError(t, policy.Validate()) + + // Invalid + policy = newPolicyBuilder().WithSlug("").Create() + require.ErrorContains(t, policy.Validate(), "Slug") + + policy = newPolicyBuilder().WithSlug(" ").Create() + require.ErrorContains(t, policy.Validate(), "Slug") +} + +func TestPlatformHubPolicyValidate_Description(t *testing.T) { + policy := newPolicyBuilder().Create() + + // Description is optional + policy.SetDescription("Description") + require.NoError(t, policy.Validate()) + + policy.SetDescription("") + require.NoError(t, policy.Validate()) +} + +func TestPlatformHubPolicyValidate_ScopeRego(t *testing.T) { + policy := newPolicyBuilder().Create() + + // Valid + policy.SetScopeRego("package scope") + require.NoError(t, policy.Validate()) + + // Invalid + policy.SetScopeRego("") + require.ErrorContains(t, policy.Validate(), "ScopeRego") + + policy.SetScopeRego(" ") + require.ErrorContains(t, policy.Validate(), "ScopeRego") +} + +func TestPlatformHubPolicyValidate_ConditionsRego(t *testing.T) { + policy := newPolicyBuilder().Create() + + // Valid + policy.SetConditionsRego("package conditions") + require.NoError(t, policy.Validate()) + + // Invalid + policy.SetConditionsRego("") + require.ErrorContains(t, policy.Validate(), "ConditionsRego") + + policy.SetConditionsRego(" ") + require.ErrorContains(t, policy.Validate(), "ConditionsRego") +} + +func TestPlatformHubPolicyValidate_ViolationAction(t *testing.T) { + policy := newPolicyBuilder().Create() + + // Valid + policy.SetViolationAction("block") + require.NoError(t, policy.Validate()) + + // Invalid + policy.SetViolationAction("") + require.ErrorContains(t, policy.Validate(), "ViolationAction") + + policy.SetViolationAction(" ") + require.ErrorContains(t, policy.Validate(), "ViolationAction") +} + +func TestPlatformHubPolicyValidate_ViolationReason(t *testing.T) { + policy := newPolicyBuilder().Create() + + // ViolationReason is optional + policy.SetViolationReason("Some reason") + require.NoError(t, policy.Validate()) + + policy.SetViolationReason("") + require.NoError(t, policy.Validate()) +} + +type policyBuilder struct { + name string + gitRef string + slug string + description string + scopeRego string + conditionsRego string + violationReason string + violationAction string +} + +func newPolicyBuilder() *policyBuilder { + return &policyBuilder{ + name: "Dummy", + gitRef: "main", + slug: "dummy", + scopeRego: "package dummy", + conditionsRego: "package dummy", + violationAction: "block", + } +} + +func (b *policyBuilder) WithName(name string) *policyBuilder { + b.name = name + return b +} + +func (b *policyBuilder) WithGitRef(gitRef string) *policyBuilder { + b.gitRef = gitRef + return b +} + +func (b *policyBuilder) WithSlug(slug string) *policyBuilder { + b.slug = slug + return b +} + +func (b *policyBuilder) WithDescription(description string) *policyBuilder { + b.description = description + return b +} + +func (b *policyBuilder) WithScopeRego(scopeRego string) *policyBuilder { + b.scopeRego = scopeRego + return b +} + +func (b *policyBuilder) WithConditionsRego(conditionsRego string) *policyBuilder { + b.conditionsRego = conditionsRego + return b +} + +func (b *policyBuilder) WithViolationReason(violationReason string) *policyBuilder { + b.violationReason = violationReason + return b +} + +func (b *policyBuilder) WithViolationAction(violationAction string) *policyBuilder { + b.violationAction = violationAction + return b +} + +func (b *policyBuilder) Create() *PlatformHubPolicy { + return &PlatformHubPolicy{ + Name: b.name, + GitRef: b.gitRef, + Slug: b.slug, + ScopeRego: b.scopeRego, + ConditionsRego: b.conditionsRego, + ViolationAction: b.violationAction, + Description: b.description, + ViolationReason: b.violationReason, + } +} diff --git a/pkg/platformhubpolicies/platform_hub_policy_version_service.go b/pkg/platformhubpolicies/platform_hub_policy_version_service.go new file mode 100644 index 00000000..779e6af9 --- /dev/null +++ b/pkg/platformhubpolicies/platform_hub_policy_version_service.go @@ -0,0 +1,118 @@ +package platformhubpolicies + +import ( + "time" + + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/newclient" +) + +// PlatformHubPolicyVersion represents an immutable published version of a Platform Hub policy. +type PlatformHubPolicyVersion struct { + ID string `json:"Id"` + Slug string `json:"Slug"` + Version string `json:"Version"` + PublishedDate time.Time `json:"PublishedDate"` + GitRef string `json:"GitRef"` + GitCommit string `json:"GitCommit"` + Name string `json:"Name"` + Description string `json:"Description,omitempty"` + ViolationReason string `json:"ViolationReason,omitempty"` + ViolationAction string `json:"ViolationAction"` + RegoScope string `json:"RegoScope"` + RegoConditions string `json:"RegoConditions"` + IsActive bool `json:"IsActive"` +} + +type publishCommand struct { + Version string `json:"Version"` +} + +// Publish publishes a Platform Hub policy version. +func Publish(client newclient.Client, gitRef string, slug string, version string) (PlatformHubPolicyVersion, error) { + command, path, err := buildPublishCommand(client, gitRef, slug, version) + if err != nil { + return PlatformHubPolicyVersion{}, err + } + + publishedVersion, postErr := newclient.Post[PlatformHubPolicyVersion](client.HttpSession(), path, command) + if postErr != nil { + return PlatformHubPolicyVersion{}, postErr + } + + return *publishedVersion, nil +} + +func buildPublishCommand(client newclient.Client, gitRef string, slug string, version string) (publishCommand, string, error) { + path, pathError := client.URITemplateCache().Expand("/api/platformhub/{gitRef}/policies/{slug}/publish", map[string]any{"gitRef": gitRef, "slug": slug}) + if pathError != nil { + return publishCommand{}, "", pathError + } + + return publishCommand{Version: version}, path, nil +} + +// VersionsQuery represents query parameters for listing policy versions. +type VersionsQuery struct { + Slug string `uri:"slug"` + Skip int `uri:"skip,omitempty"` + Take int `uri:"take,omitempty"` +} + +// GetVersions returns published versions of a Platform Hub policy. +func GetVersions(client newclient.Client, query VersionsQuery) ([]PlatformHubPolicyVersion, error) { + path, pathError := buildGetVersionsPath(client, query) + if pathError != nil { + return nil, pathError + } + + versions, err := newclient.Get[[]PlatformHubPolicyVersion](client.HttpSession(), path) + if err != nil { + return nil, err + } + + return *versions, nil +} + +func buildGetVersionsPath(client newclient.Client, query VersionsQuery) (string, error) { + return client.URITemplateCache().Expand("/api/platformhub/policies/{slug}/versions{?skip,take}", query) +} + +// ActivateVersion activates a published Platform Hub policy version. +func ActivateVersion(client newclient.Client, version PlatformHubPolicyVersion) (PlatformHubPolicyVersion, error) { + return modifyVersionStatus(client, version, true) +} + +// DeactivateVersion deactivates a published Platform Hub policy version. +func DeactivateVersion(client newclient.Client, version PlatformHubPolicyVersion) (PlatformHubPolicyVersion, error) { + return modifyVersionStatus(client, version, false) +} + +type modifyVersionStatusCommand struct { + IsActive bool `json:"IsActive"` +} + +func modifyVersionStatus(client newclient.Client, version PlatformHubPolicyVersion, isActive bool) (PlatformHubPolicyVersion, error) { + command, path, err := buildModifyVersionStatusCommand(client, version, isActive) + if err != nil { + return version, err + } + + modifiedVersion, postErr := newclient.Post[PlatformHubPolicyVersion](client.HttpSession(), path, command) + if postErr != nil { + return version, postErr + } + + return *modifiedVersion, nil +} + +func buildModifyVersionStatusCommand(client newclient.Client, version PlatformHubPolicyVersion, isActive bool) (modifyVersionStatusCommand, string, error) { + path, pathError := client.URITemplateCache().Expand("/api/platformhub/policies/{slug}/versions/{version}/modify-status", map[string]any{ + "slug": version.Slug, + "version": version.Version, + }) + if pathError != nil { + return modifyVersionStatusCommand{}, "", pathError + } + + return modifyVersionStatusCommand{IsActive: isActive}, path, nil +} diff --git a/pkg/platformhubpolicies/platform_hub_policy_version_service_test.go b/pkg/platformhubpolicies/platform_hub_policy_version_service_test.go new file mode 100644 index 00000000..d34767ae --- /dev/null +++ b/pkg/platformhubpolicies/platform_hub_policy_version_service_test.go @@ -0,0 +1,103 @@ +package platformhubpolicies + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/newclient" + "github.com/stretchr/testify/require" +) + +func TestPlatformHubPolicyVersionService_BuildPublishCommand(t *testing.T) { + var client = newclient.NewClient(&newclient.HttpSession{}) + + gitRef := "refs/heads/main" + slug := "my_policy" + version := "1.0.1" + urlEncodedGitRef := "refs%2Fheads%2Fmain" + + // Act + command, path, commandError := buildPublishCommand(client, gitRef, slug, version) + + require.NoError(t, commandError) + require.Equal(t, fmt.Sprintf("/api/platformhub/%s/policies/%s/publish", urlEncodedGitRef, slug), path) + require.Equal(t, version, command.Version) + + // Verify JSON serialization + jsonBytes, jsonErr := json.Marshal(command) + require.NoError(t, jsonErr) + require.JSONEq(t, fmt.Sprintf(`{"Version":"%s"}`, version), string(jsonBytes)) +} + +func TestPlatformHubPolicyVersionService_BuildActivateCommand(t *testing.T) { + var client = newclient.NewClient(&newclient.HttpSession{}) + + version := PlatformHubPolicyVersion{ + Slug: "my_policy", + Version: "1.0.0", + } + + // Act + command, path, commandError := buildModifyVersionStatusCommand(client, version, true) + + require.NoError(t, commandError) + require.Equal(t, fmt.Sprintf("/api/platformhub/policies/%s/versions/%s/modify-status", version.Slug, version.Version), path) + require.True(t, command.IsActive) + + // Verify JSON serialization + jsonBytes, jsonErr := json.Marshal(command) + require.NoError(t, jsonErr) + require.JSONEq(t, fmt.Sprintf(`{"IsActive":%t}`, true), string(jsonBytes)) +} + +func TestPlatformHubPolicyVersionService_BuildDeactivateCommand(t *testing.T) { + var client = newclient.NewClient(&newclient.HttpSession{}) + + version := PlatformHubPolicyVersion{ + Slug: "my_policy", + Version: "2.0.0", + } + + // Act + command, path, commandError := buildModifyVersionStatusCommand(client, version, false) + + require.NoError(t, commandError) + require.Equal(t, fmt.Sprintf("/api/platformhub/policies/%s/versions/%s/modify-status", version.Slug, version.Version), path) + require.False(t, command.IsActive) + + // Verify JSON serialization + jsonBytes, jsonErr := json.Marshal(command) + require.NoError(t, jsonErr) + require.JSONEq(t, fmt.Sprintf(`{"IsActive":%t}`, false), string(jsonBytes)) +} + +func TestPlatformHubPolicyVersionService_BuildGetVersionsPath_All(t *testing.T) { + var client = newclient.NewClient(&newclient.HttpSession{}) + + // All parameters + query := VersionsQuery{ + Slug: "my_policy", + Skip: 10, + Take: 5, + } + + path, err := buildGetVersionsPath(client, query) + + require.NoError(t, err) + require.Equal(t, "/api/platformhub/policies/my_policy/versions?skip=10&take=5", path) +} + +func TestPlatformHubPolicyVersionService_BuildGetVersionsPath_OnlySlug(t *testing.T) { + var client = newclient.NewClient(&newclient.HttpSession{}) + + query := VersionsQuery{ + Slug: "my_policy", + } + + // Act + path, err := buildGetVersionsPath(client, query) + + require.NoError(t, err) + require.Equal(t, "/api/platformhub/policies/my_policy/versions", path) +} diff --git a/play b/play new file mode 100644 index 0000000000000000000000000000000000000000..cc5efdc799b2ec9ab475a97f20191090436d26d8 GIT binary patch literal 11634205 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