Skip to content

Commit

Permalink
feat: Adds support for running Git runbooks (#278)
Browse files Browse the repository at this point in the history
  • Loading branch information
geofflamrock authored Nov 20, 2024
1 parent 2b00f2b commit 067514c
Show file tree
Hide file tree
Showing 6 changed files with 364 additions and 8 deletions.
49 changes: 49 additions & 0 deletions pkg/projects/git_persistence_settings.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ type GitPersistenceSettings interface {
Credential() credentials.GitCredential
SetCredential(credential credentials.GitCredential)

VariablesAreInGit() bool
RunbooksAreInGit() bool

// Deprecated: This is not settable against a real Octopus project it is only used for testing purposes.
SetRunbooksAreInGit()

PersistenceSettings
}

Expand All @@ -35,10 +41,16 @@ type gitPersistenceSettings struct {
defaultBranch string
protectedBranchNamePatterns []string
url *url.URL
conversionState gitPersistenceSettingsConversionState

persistenceSettings
}

type gitPersistenceSettingsConversionState struct {
VariablesAreInGit bool `json:"VariablesAreInGit,omitempty"`
RunbooksAreInGit bool `json:"RunbooksAreInGit,omitempty"`
}

// NewGitPersistenceSettings creates an instance of persistence settings.
func NewGitPersistenceSettings(
basePath string,
Expand All @@ -52,6 +64,7 @@ func NewGitPersistenceSettings(
defaultBranch: defaultBranch,
protectedBranchNamePatterns: protectedBranchNamePatterns,
url: url,
conversionState: gitPersistenceSettingsConversionState{VariablesAreInGit: false, RunbooksAreInGit: false},
persistenceSettings: persistenceSettings{SettingsType: PersistenceSettingsTypeVersionControlled},
}
}
Expand Down Expand Up @@ -101,6 +114,18 @@ func (g *gitPersistenceSettings) SetCredential(credential credentials.GitCredent
g.credential = credential
}

func (g *gitPersistenceSettings) VariablesAreInGit() bool {
return g.conversionState.VariablesAreInGit
}

func (g *gitPersistenceSettings) RunbooksAreInGit() bool {
return g.conversionState.RunbooksAreInGit
}

func (g *gitPersistenceSettings) SetRunbooksAreInGit() {
g.conversionState.RunbooksAreInGit = true
}

// MarshalJSON returns persistence settings as its JSON encoding.
func (p *gitPersistenceSettings) MarshalJSON() ([]byte, error) {
defaultBranch := p.DefaultBranch()
Expand All @@ -120,6 +145,7 @@ func (p *gitPersistenceSettings) MarshalJSON() ([]byte, error) {
ProtectedBranchNamePatterns []string `json:"ProtectedBranchNamePatterns"`
URL string `json:"Url,omitempty"`
Type PersistenceSettingsType `json:"Type,omitempty"`
ConversionState map[string]interface{} `json:"ConversionState,omitempty"`
}{
BasePath: p.BasePath(),
Credentials: p.Credential(),
Expand All @@ -128,6 +154,10 @@ func (p *gitPersistenceSettings) MarshalJSON() ([]byte, error) {
ProtectedBranchNamePatterns: protectedBranches,
URL: p.URL().String(),
Type: p.Type(),
ConversionState: map[string]interface{}{
"VariablesAreInGit": p.conversionState.VariablesAreInGit,
"RunbooksAreInGit": p.conversionState.RunbooksAreInGit,
},
}

return json.Marshal(persistenceSettings)
Expand Down Expand Up @@ -228,6 +258,25 @@ func (p *gitPersistenceSettings) UnmarshalJSON(b []byte) error {
p.credential = usernamePasswordGitCredential
}

var conversionState *json.RawMessage
var conversionStateFields gitPersistenceSettingsConversionState

if persistenceSettings["ConversionState"] != nil {
conversionStateValue := persistenceSettings["ConversionState"]

err = json.Unmarshal(*conversionStateValue, &conversionState)
if err != nil {
return err
}

err = json.Unmarshal(*conversionState, &conversionStateFields)
if err != nil {
return err
}

p.conversionState = conversionStateFields
}

return nil
}

Expand Down
54 changes: 51 additions & 3 deletions pkg/projects/git_persistence_settings_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ func TestGitPersistenceSettingsMarshalJSONWithProtectedDefaultBranch(t *testing.
"ProtectedBranchNamePatterns": [],
"ProtectedDefaultBranch": true,
"Type": "%s",
"Url": "%s"
"Url": "%s",
"ConversionState": {
"VariablesAreInGit": false,
"RunbooksAreInGit": false
}
}`, basePath, gitCredentialsAsJSON, defaultBranch, projects.PersistenceSettingsTypeVersionControlled, url.String())

gitPersistenceSettings := projects.NewGitPersistenceSettings(basePath, gitCredentials, defaultBranch, protectedBranchNamePatterns, url)
Expand Down Expand Up @@ -121,7 +125,11 @@ func TestGitPersistenceSettingsMarshalJSONWithProtectedDefaultBranchAsLastItem(t
"ProtectedBranchNamePatterns": ["foo"],
"ProtectedDefaultBranch": true,
"Type": "%s",
"Url": "%s"
"Url": "%s",
"ConversionState": {
"VariablesAreInGit": false,
"RunbooksAreInGit": false
}
}`, basePath, gitCredentialsAsJSON, defaultBranch, projects.PersistenceSettingsTypeVersionControlled, url.String())

gitPersistenceSettings := projects.NewGitPersistenceSettings(basePath, gitCredentials, defaultBranch, protectedBranchNamePatterns, url)
Expand Down Expand Up @@ -155,7 +163,11 @@ func TestGitPersistenceSettingsMarshalJSONWithoutProtectedDefaultBranch(t *testi
"ProtectedBranchNamePatterns": ["%s"],
"ProtectedDefaultBranch": false,
"Type": "%s",
"Url": "%s"
"Url": "%s",
"ConversionState": {
"VariablesAreInGit": false,
"RunbooksAreInGit": false
}
}`, basePath, gitCredentialsAsJSON, defaultBranch, protectedBranchName, projects.PersistenceSettingsTypeVersionControlled, url.String())

gitPersistenceSettings := projects.NewGitPersistenceSettings(basePath, gitCredentials, defaultBranch, protectedBranchNamePatterns, url)
Expand Down Expand Up @@ -341,3 +353,39 @@ func TestGitPersistenceSettingsUnmarshalJSONWithProtectedBranchIsDefault(t *test
require.Equal(t, url, gitPersistenceSettings.URL())
require.Equal(t, []string{defaultBranch}, gitPersistenceSettings.ProtectedBranchNamePatterns())
}

func TestGitPersistenceSettingsUnmarshalJSON_ConversionState(t *testing.T) {
password := core.NewSensitiveValue(internal.GetRandomName())
username := internal.GetRandomName()

basePath := internal.GetRandomName()
defaultBranch := internal.GetRandomName()
url, err := url.Parse("https://example.com/")
usernamePasswordGitCredential := credentials.NewUsernamePassword(username, password)
require.NoError(t, err)

gitCredentialsAsJSON, err := json.Marshal(usernamePasswordGitCredential)
require.NoError(t, err)
require.NotNil(t, gitCredentialsAsJSON)

inputJSON := fmt.Sprintf(`{
"BasePath": "%s",
"Credentials": %s,
"DefaultBranch": "%s",
"ProtectedBranchNamePatterns": [],
"ProtectedDefaultBranch": false,
"Type": "%s",
"Url": "%s",
"ConversionState": {
"VariablesAreInGit": true,
"RunbooksAreInGit": true
}
}`, basePath, gitCredentialsAsJSON, defaultBranch, projects.PersistenceSettingsTypeVersionControlled, url.String())

gitPersistenceSettings := projects.NewGitPersistenceSettings("", nil, "", []string{}, nil)
err = json.Unmarshal([]byte(inputJSON), &gitPersistenceSettings)
require.NoError(t, err)
require.NotNil(t, gitPersistenceSettings)
require.Equal(t, true, gitPersistenceSettings.VariablesAreInGit())
require.Equal(t, true, gitPersistenceSettings.RunbooksAreInGit())
}
67 changes: 67 additions & 0 deletions pkg/runbooks/run_git_runbook_v1.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package runbooks

import (
"encoding/json"

"github.com/OctopusDeploy/go-octopusdeploy/v2/internal"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/deployments"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/newclient"
"github.com/OctopusDeploy/go-octopusdeploy/v2/uritemplates"
)

type GitRunbookRunCommandV1 struct {
RunbookName string `json:"runbookName"` // required
EnvironmentNames []string `json:"environmentNames,omitempty"`
Tenants []string `json:"tenants,omitempty"`
TenantTags []string `json:"tenantTags,omitempty"`
GitRef string `json:"gitRef"` // required
PackageVersion string `json:"packageVersion,omitempty"`
Packages []string `json:"packages,omitempty"`
GitResources []string `json:"gitResources,omitempty"`
deployments.CreateExecutionAbstractCommandV1
}

type GitRunbookRunServerTask struct {
RunbookRunID string `json:"RunbookRunId"`
ServerTaskID string `json:"ServerTaskId"`
}

type GitRunbookRunResponseV1 struct {
RunbookRunServerTasks []*RunbookRunServerTask `json:"RunbookRunServerTasks,omitempty"`
}

func NewGitRunbookRunCommandV1(spaceID string, projectIDOrName string) *GitRunbookRunCommandV1 {
return &GitRunbookRunCommandV1{
CreateExecutionAbstractCommandV1: deployments.CreateExecutionAbstractCommandV1{
SpaceID: spaceID,
ProjectIDOrName: projectIDOrName,
},
}
}

// MarshalJSON adds the redundant 'spaceIdOrName' parameter which is required by the server
func (r *GitRunbookRunCommandV1) MarshalJSON() ([]byte, error) {
command := struct {
SpaceIDOrName string `json:"spaceIdOrName"`
GitRunbookRunCommandV1
}{
SpaceIDOrName: r.SpaceID,
GitRunbookRunCommandV1: *r,
}
return json.Marshal(command)
}

func GitRunbookRunV1(client newclient.Client, command *GitRunbookRunCommandV1) (*GitRunbookRunResponseV1, error) {
if command == nil {
return nil, internal.CreateInvalidParameterError("GitRunbookRunV1", "command")
}
if command.SpaceID == "" {
return nil, internal.CreateInvalidParameterError("GitRunbookRunV1", "command.SpaceID")
}

expandedUri, err := client.URITemplateCache().Expand(uritemplates.CreateRunGitRunbookCommand, map[string]any{"spaceId": command.SpaceID})
if err != nil {
return nil, err
}
return newclient.Post[GitRunbookRunResponseV1](client.HttpSession(), expandedUri, command)
}
Loading

0 comments on commit 067514c

Please sign in to comment.