diff --git a/pkg/deployments/deploy_release_v1.go b/pkg/deployments/deploy_release_v1.go index d68b3c07..3c52239d 100644 --- a/pkg/deployments/deploy_release_v1.go +++ b/pkg/deployments/deploy_release_v1.go @@ -14,6 +14,8 @@ type CreateExecutionAbstractCommandV1 struct { ForcePackageDownload bool `json:"forcePackageDownload"` SpecificMachineNames []string `json:"specificMachineNames,omitempty"` ExcludedMachineNames []string `json:"excludedMachineNames,omitempty"` + SpecificTargetTagNames []string `json:"specificTargetTagNames,omitempty"` + ExcludedTargetTagNames []string `json:"excludedTargetTagNames,omitempty"` SkipStepNames []string `json:"skipStepNames,omitempty"` UseGuidedFailure *bool `json:"useGuidedFailure"` // note: nil is valid, meaning 'use default' RunAt string `json:"runAt,omitempty"` // contains a datetimeOffset-parseable value diff --git a/pkg/deployments/deployment.go b/pkg/deployments/deployment.go index af0230e0..54e62fa6 100644 --- a/pkg/deployments/deployment.go +++ b/pkg/deployments/deployment.go @@ -21,6 +21,7 @@ type Deployment struct { DeploymentProcessID string `json:"DeploymentProcessId,omitempty"` EnvironmentID string `json:"EnvironmentId" validate:"required"` ExcludedMachineIDs []string `json:"ExcludedMachineIds"` + ExcludedTargetTagIds []string `json:"ExcludedTargetTagIds,omitempty"` FailureEncountered bool `json:"FailureEncountered"` ForcePackageDownload bool `json:"ForcePackageDownload"` ForcePackageRedeployment bool `json:"ForcePackageRedeployment"` @@ -34,6 +35,7 @@ type Deployment struct { SkipActions []string `json:"SkipActions"` SpaceID string `json:"SpaceId,omitempty"` SpecificMachineIDs []string `json:"SpecificMachineIds"` + SpecificTargetTagIds []string `json:"SpecificTargetTagIds,omitempty"` TaskID string `json:"TaskId,omitempty"` TenantID string `json:"TenantId,omitempty"` TentacleRetentionPeriod *core.RetentionPeriod `json:"TentacleRetentionPeriod,omitempty"` diff --git a/pkg/deployments/deployment_preview.go b/pkg/deployments/deployment_preview.go index 26a5b8b8..42ed7b5d 100644 --- a/pkg/deployments/deployment_preview.go +++ b/pkg/deployments/deployment_preview.go @@ -11,11 +11,26 @@ type MachineDeploymentPreview struct { HealthStatus string `json:"HealthStatus,omitempty"` // machines.HealthStatus validate:"omitempty,oneof=HasWarnings Healthy Unavailable Unhealthy Unknown"` } +type TargetTagPreview struct { + TagID string `json:"TagId,omitempty"` + TagName string `json:"TagName,omitempty"` + SortOrder int `json:"SortOrder,omitempty"` +} + +type TagSetPreview struct { + TagSetID string `json:"TagSetId,omitempty"` + TagSetName string `json:"TagSetName,omitempty"` + TagSetType string `json:"TagSetType,omitempty"` + SortOrder int `json:"SortOrder,omitempty"` + AvailableTags []*TargetTagPreview `json:"AvailableTags,omitempty"` +} + type DeploymentTemplateStep struct { ActionID string `json:"ActionId,omitempty"` ActionName string `json:"ActionName,omitempty"` ActionNumber string `json:"ActionNumber,omitempty"` Roles []string `json:"Roles,omitempty"` + AvailableTagSets []*TagSetPreview `json:"AvailableTagSets,omitempty"` MachineNames []string `json:"MachineNames,omitempty"` Machines []*MachineDeploymentPreview `json:"Machines,omitempty"` CanBeSkipped bool `json:"CanBeSkipped"` diff --git a/pkg/deployments/deployment_service_test.go b/pkg/deployments/deployment_service_test.go index 9f94fc82..096ba40c 100644 --- a/pkg/deployments/deployment_service_test.go +++ b/pkg/deployments/deployment_service_test.go @@ -1,10 +1,11 @@ package deployments_test import ( - "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/deployments" + "encoding/json" "testing" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/constants" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/deployments" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/services" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -26,3 +27,289 @@ func createDeploymentService(t *testing.T) *deployments.DeploymentService { services.NewServiceTests(t, service, constants.TestURIDeployments, constants.ServiceDeploymentService) return service } + +func TestCreateExecutionAbstractCommandV1_MarshalJSON(t *testing.T) { + cmd := deployments.CreateExecutionAbstractCommandV1{ + SpaceID: "Spaces-1", + ProjectIDOrName: "MyProject", + SpecificMachineNames: []string{"web1", "web2"}, + ExcludedMachineNames: []string{"web3"}, + SpecificTargetTagNames: []string{"Role/WebServer", "Environment/Production"}, + ExcludedTargetTagNames: []string{"Role/Database"}, + SkipStepNames: []string{"Skip Step 1"}, + } + + jsonBytes, err := json.Marshal(cmd) + require.NoError(t, err) + + expectedJSON := `{ + "spaceId": "Spaces-1", + "projectName": "MyProject", + "specificMachineNames": ["web1", "web2"], + "excludedMachineNames": ["web3"], + "specificTargetTagNames": ["Role/WebServer", "Environment/Production"], + "excludedTargetTagNames": ["Role/Database"], + "skipStepNames": ["Skip Step 1"], + "forcePackageDownload": false, + "useGuidedFailure": null + }` + + require.JSONEq(t, expectedJSON, string(jsonBytes)) +} + +func TestCreateDeploymentTenantedCommandV1_MarshalJSON(t *testing.T) { + cmd := deployments.CreateDeploymentTenantedCommandV1{ + ReleaseVersion: "1.0.0", + EnvironmentName: "Production", + Tenants: []string{"Tenant-1"}, + CreateExecutionAbstractCommandV1: deployments.CreateExecutionAbstractCommandV1{ + SpaceID: "Spaces-1", + ProjectIDOrName: "MyProject", + SpecificTargetTagNames: []string{"Role/WebServer"}, + ExcludedTargetTagNames: []string{"Role/Database"}, + }, + } + + jsonBytes, err := json.Marshal(cmd) + require.NoError(t, err) + + expectedJSON := `{ + "releaseVersion": "1.0.0", + "environmentName": "Production", + "tenants": ["Tenant-1"], + "spaceId": "Spaces-1", + "projectName": "MyProject", + "specificTargetTagNames": ["Role/WebServer"], + "excludedTargetTagNames": ["Role/Database"], + "forcePackageDownload": false, + "forcePackageRedeployment": false, + "updateVariableSnapshot": false, + "useGuidedFailure": null + }` + + require.JSONEq(t, expectedJSON, string(jsonBytes)) +} + +func TestCreateDeploymentUntenantedCommandV1_MarshalJSON(t *testing.T) { + cmd := deployments.CreateDeploymentUntenantedCommandV1{ + ReleaseVersion: "1.0.0", + EnvironmentNames: []string{"Production", "Staging"}, + CreateExecutionAbstractCommandV1: deployments.CreateExecutionAbstractCommandV1{ + SpaceID: "Spaces-1", + ProjectIDOrName: "MyProject", + SpecificTargetTagNames: []string{"Role/WebServer", "Environment/Production"}, + ExcludedTargetTagNames: []string{"Role/Database"}, + }, + } + + jsonBytes, err := json.Marshal(cmd) + require.NoError(t, err) + + expectedJSON := `{ + "releaseVersion": "1.0.0", + "environmentNames": ["Production", "Staging"], + "spaceId": "Spaces-1", + "projectName": "MyProject", + "specificTargetTagNames": ["Role/WebServer", "Environment/Production"], + "excludedTargetTagNames": ["Role/Database"], + "forcePackageDownload": false, + "forcePackageRedeployment": false, + "updateVariableSnapshot": false, + "useGuidedFailure": null + }` + + require.JSONEq(t, expectedJSON, string(jsonBytes)) +} + +func TestDeployment_MarshalJSON(t *testing.T) { + deployment := deployments.Deployment{ + ReleaseID: "Releases-1", + EnvironmentID: "Environments-1", + SpecificMachineIDs: []string{"Machines-1", "Machines-2"}, + ExcludedMachineIDs: []string{"Machines-3"}, + SpecificTargetTagIds: []string{"TagSets-1/Tags-1", "TagSets-2/Tags-2"}, + ExcludedTargetTagIds: []string{"TagSets-1/Tags-3"}, + } + + jsonBytes, err := json.Marshal(deployment) + require.NoError(t, err) + + expectedJSON := `{ + "Changes": null, + "DeployedToMachineIds": null, + "ReleaseId": "Releases-1", + "EnvironmentId": "Environments-1", + "SpecificMachineIds": ["Machines-1", "Machines-2"], + "ExcludedMachineIds": ["Machines-3"], + "SpecificTargetTagIds": ["TagSets-1/Tags-1", "TagSets-2/Tags-2"], + "ExcludedTargetTagIds": ["TagSets-1/Tags-3"], + "FailureEncountered": false, + "ForcePackageDownload": false, + "ForcePackageRedeployment": false, + "SkipActions": null, + "UseGuidedFailure": false + }` + + require.JSONEq(t, expectedJSON, string(jsonBytes)) +} + +func TestDeploymentTemplateStep_MarshalJSON(t *testing.T) { + step := deployments.DeploymentTemplateStep{ + ActionID: "Actions-1", + ActionName: "Deploy Package", + ActionNumber: "1", + Roles: []string{"web-server", "app-server"}, + AvailableTagSets: []*deployments.TagSetPreview{ + { + TagSetID: "TagSets-1", + TagSetName: "Role", + TagSetType: "MultiSelect", + SortOrder: 1, + AvailableTags: []*deployments.TargetTagPreview{ + { + TagID: "Tags-1", + TagName: "WebServer", + SortOrder: 1, + }, + { + TagID: "Tags-2", + TagName: "Database", + SortOrder: 2, + }, + }, + }, + { + TagSetID: "TagSets-2", + TagSetName: "Environment", + TagSetType: "SingleSelect", + SortOrder: 2, + AvailableTags: []*deployments.TargetTagPreview{ + { + TagID: "Tags-3", + TagName: "Production", + SortOrder: 1, + }, + }, + }, + }, + MachineNames: []string{"web-01", "web-02"}, + CanBeSkipped: true, + IsDisabled: false, + HasNoApplicableMachines: false, + } + + jsonBytes, err := json.Marshal(step) + require.NoError(t, err) + + expectedJSON := `{ + "ActionId": "Actions-1", + "ActionName": "Deploy Package", + "ActionNumber": "1", + "Roles": ["web-server", "app-server"], + "AvailableTagSets": [ + { + "TagSetId": "TagSets-1", + "TagSetName": "Role", + "TagSetType": "MultiSelect", + "SortOrder": 1, + "AvailableTags": [ + { + "TagId": "Tags-1", + "TagName": "WebServer", + "SortOrder": 1 + }, + { + "TagId": "Tags-2", + "TagName": "Database", + "SortOrder": 2 + } + ] + }, + { + "TagSetId": "TagSets-2", + "TagSetName": "Environment", + "TagSetType": "SingleSelect", + "SortOrder": 2, + "AvailableTags": [ + { + "TagId": "Tags-3", + "TagName": "Production", + "SortOrder": 1 + } + ] + } + ], + "MachineNames": ["web-01", "web-02"], + "CanBeSkipped": true, + "IsDisabled": false, + "HasNoApplicableMachines": false + }` + + require.JSONEq(t, expectedJSON, string(jsonBytes)) +} + +func TestDeploymentPreview_MarshalJSON(t *testing.T) { + preview := deployments.DeploymentPreview{ + Form: &deployments.Form{ + Values: map[string]string{"Variable1": "Value1"}, + Elements: []*deployments.Element{}, + }, + StepsToExecute: []*deployments.DeploymentTemplateStep{ + { + ActionID: "Actions-1", + ActionName: "Deploy Package", + ActionNumber: "1", + AvailableTagSets: []*deployments.TagSetPreview{ + { + TagSetID: "TagSets-1", + TagSetName: "Role", + AvailableTags: []*deployments.TargetTagPreview{ + { + TagID: "Tags-1", + TagName: "WebServer", + }, + }, + }, + }, + MachineNames: []string{"web-01"}, + }, + }, + UseGuidedFailureModeByDefault: true, + } + + jsonBytes, err := json.Marshal(preview) + require.NoError(t, err) + + expectedJSON := `{ + "Form": { + "Values": {"Variable1": "Value1"}, + "Elements": [] + }, + "StepsToExecute": [ + { + "ActionId": "Actions-1", + "ActionName": "Deploy Package", + "ActionNumber": "1", + "AvailableTagSets": [ + { + "TagSetId": "TagSets-1", + "TagSetName": "Role", + "AvailableTags": [ + { + "TagId": "Tags-1", + "TagName": "WebServer" + } + ] + } + ], + "MachineNames": ["web-01"], + "CanBeSkipped": false, + "IsDisabled": false, + "HasNoApplicableMachines": false + } + ], + "UseGuidedFailureModeByDefault": true + }` + + require.JSONEq(t, expectedJSON, string(jsonBytes)) +} diff --git a/pkg/runbooks/runbook_service_test.go b/pkg/runbooks/runbook_service_test.go index fc751dca..d2f07cf1 100644 --- a/pkg/runbooks/runbook_service_test.go +++ b/pkg/runbooks/runbook_service_test.go @@ -1,10 +1,12 @@ package runbooks import ( + "encoding/json" "testing" "github.com/OctopusDeploy/go-octopusdeploy/v2/internal" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/constants" + "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/deployments" "github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/services" "github.com/dghubble/sling" "github.com/stretchr/testify/assert" @@ -54,3 +56,44 @@ func TestRunbookServiceNew(t *testing.T) { }) } } + +func TestRunbookRunCommandV1_MarshalJSON(t *testing.T) { + cmd := RunbookRunCommandV1{ + RunbookName: "My Runbook", + EnvironmentNames: []string{"Production", "Staging"}, + Tenants: []string{"Tenant-1"}, + TenantTags: []string{"Region/US", "Tier/Premium"}, + Snapshot: "Snapshot-1", + CreateExecutionAbstractCommandV1: deployments.CreateExecutionAbstractCommandV1{ + SpaceID: "Spaces-1", + ProjectIDOrName: "MyProject", + SpecificMachineNames: []string{"runbook-server1"}, + ExcludedMachineNames: []string{"maintenance-server"}, + SpecificTargetTagNames: []string{"Role/RunbookServer", "Environment/Production"}, + ExcludedTargetTagNames: []string{"Role/Database", "Maintenance/True"}, + SkipStepNames: []string{"Optional Step"}, + }, + } + + jsonBytes, err := json.Marshal(cmd) + require.NoError(t, err) + + expectedJSON := `{ + "runbookName": "My Runbook", + "environmentNames": ["Production", "Staging"], + "tenants": ["Tenant-1"], + "tenantTags": ["Region/US", "Tier/Premium"], + "snapshot": "Snapshot-1", + "spaceId": "Spaces-1", + "projectName": "MyProject", + "specificMachineNames": ["runbook-server1"], + "excludedMachineNames": ["maintenance-server"], + "specificTargetTagNames": ["Role/RunbookServer", "Environment/Production"], + "excludedTargetTagNames": ["Role/Database", "Maintenance/True"], + "skipStepNames": ["Optional Step"], + "forcePackageDownload": false, + "useGuidedFailure": null + }` + + require.JSONEq(t, expectedJSON, string(jsonBytes)) +}