Skip to content

Commit

Permalink
Added step to run the new runbooks
Browse files Browse the repository at this point in the history
  • Loading branch information
Matthew Casperson committed Jul 24, 2024
1 parent dc6527e commit 0c03362
Show file tree
Hide file tree
Showing 4 changed files with 422 additions and 1 deletion.
294 changes: 294 additions & 0 deletions internal/infrastructure/octopus.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,294 @@
package infrastructure

import (
"bytes"
"encoding/json"
"errors"
"fmt"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/environments"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/feeds"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/projects"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/runbooks"
"github.com/OctopusDeploy/go-octopusdeploy/v2/pkg/tasks"
"github.com/mcasperson/OctoterraWizard/internal/octoclient"
"github.com/mcasperson/OctoterraWizard/internal/state"
"github.com/samber/lo"
"io"
"net/http"
"strings"
"time"
)

func WaitForTask(state state.State, taskId string) error {
myclient, err := octoclient.CreateClient(state)

if err != nil {
return err
}

// wait up to 10 minutes for the task to complete
for i := 0; i < 600; i++ {
mytasks, err := myclient.Tasks.Get(tasks.TasksQuery{
Environment: "",
HasPendingInterruptions: false,
HasWarningsOrErrors: false,
IDs: []string{taskId},
IncludeSystem: false,
IsActive: false,
IsRunning: false,
Name: "",
Node: "",
PartialName: "",
Project: "",
Runbook: "",
Skip: 0,
Spaces: nil,
States: nil,
Take: 1,
Tenant: "",
})

if err != nil {
return err
}

if mytasks.Items[0].IsCompleted != nil && *mytasks.Items[0].IsCompleted {
if mytasks.Items[0].State != "Success" {
return errors.New("task was not successful")
}
return nil
}

fmt.Println(mytasks.Items[0].State)

time.Sleep(10 * time.Second)
}

return errors.New("task did not complete in 10 minutes")
}

func RunRunbook(state state.State, runbookName string, projectName string) (string, error) {
myclient, err := octoclient.CreateClient(state)

if err != nil {
return "", err
}

environment, err := environments.GetAll(myclient, myclient.GetSpaceID())

if err != nil {
return "", err
}

project, err := projects.GetByName(myclient, myclient.GetSpaceID(), projectName)

if err != nil {
return "", err
}

runbook, err := runbooks.GetByName(myclient, myclient.GetSpaceID(), project.GetID(), runbookName)

if err != nil {
return "", err
}

url := state.Server + runbook.GetLinks()["RunbookRunPreview"]
url = strings.ReplaceAll(url, "{environment}", environment[0].GetID())
url = strings.ReplaceAll(url, "{?includeDisabledSteps}", "")

runbookRunPreviewRequest, err := http.NewRequest("GET", url, nil)

if err != nil {
return "", err
}

runbookRunPreviewResponse, err := myclient.HttpSession().DoRawRequest(runbookRunPreviewRequest)

if err != nil {
return "", err
}

runbookRunPreviewRaw, err := io.ReadAll(runbookRunPreviewResponse.Body)

if err != nil {
return "", err
}

runbookRunPreview := map[string]any{}
err = json.Unmarshal(runbookRunPreviewRaw, &runbookRunPreview)

if err != nil {
return "", err
}

runbookFormNames := lo.Map(runbookRunPreview["Form"].(map[string]any)["Elements"].([]any), func(value any, index int) any {
return value.(map[string]any)["Name"]
})

runbookFormValues := map[string]string{}

for _, name := range runbookFormNames {
runbookFormValues[name.(string)] = "dummy"
}

runbookBody := map[string]any{
"RunbookId": runbook.GetID(),
"RunbookSnapShotId": runbook.PublishedRunbookSnapshotID,
"FrozenRunbookProcessId": nil,
"EnvironmentId": environment[0].GetID(),
"TenantId": nil,
"SkipActions": []string{},
"QueueTime": nil,
"QueueTimeExpiry": nil,
"FormValues": runbookFormValues,
"ForcePackageDownload": false,
"ForcePackageRedeployment": true,
"UseGuidedFailure": false,
"SpecificMachineIds": []string{},
"ExcludedMachineIds": []string{},
}

runbookBodyJson, err := json.Marshal(runbookBody)

if err != nil {
return "", err
}

url = state.Server + "/api/" + state.Space + "/runbookRuns"
runbookRunRequest, err := http.NewRequest("POST", url, bytes.NewReader(runbookBodyJson))

if err != nil {
return "", err
}

runbookRunResponse, err := myclient.HttpSession().DoRawRequest(runbookRunRequest)

if err != nil {
return "", err
}

runbookRunRaw, err := io.ReadAll(runbookRunResponse.Body)

if err != nil {
return "", err
}

runbookRun := map[string]any{}
err = json.Unmarshal(runbookRunRaw, &runbookRun)

if err != nil {
return "", err
}

return runbookRun["TaskId"].(string), nil

}

func PublishRunbook(state state.State, runbookName string, projectName string) error {
myclient, err := octoclient.CreateClient(state)

if err != nil {
return err
}

project, err := projects.GetByName(myclient, myclient.GetSpaceID(), projectName)

if err != nil {
return err
}

runbook, err := runbooks.GetByName(myclient, myclient.GetSpaceID(), project.GetID(), runbookName)

if err != nil {
return err
}

url := state.Server + runbook.GetLinks()["RunbookSnapshotTemplate"]
runbookSnapshotTemplateRequest, err := http.NewRequest("GET", url, nil)

if err != nil {
return err
}

runbookSnapshotTemplateResponse, err := myclient.HttpSession().DoRawRequest(runbookSnapshotTemplateRequest)

if err != nil {
return err
}

runbookSnapshotTemplateRaw, err := io.ReadAll(runbookSnapshotTemplateResponse.Body)

if err != nil {
return err
}

runbookSnapshotTemplate := map[string]any{}
err = json.Unmarshal(runbookSnapshotTemplateRaw, &runbookSnapshotTemplate)

if err != nil {
return err
}

snapshot := map[string]any{
"ProjectId": project.GetID(),
"RunbookId": runbook.GetID(),
"Name": runbookSnapshotTemplate["NextNameIncrement"],
}

var packageErrors error = nil
snapshot["SelectedPackages"] = lo.Map(runbookSnapshotTemplate["Packages"].([]any), func(pkg any, index int) any {
snapshotPackage := pkg.(map[string]any)
versions, err := feeds.SearchPackageVersions(myclient, myclient.GetSpaceID(), snapshotPackage["FeedId"].(string), snapshotPackage["PackageId"].(string), "", 1)

if err != nil {
packageErrors = errors.Join(packageErrors, err)
return nil
}

return map[string]any{
"ActionName": snapshotPackage["ActionName"],
"Version": versions.Items[0].Version,
"PackageReferenceName": snapshotPackage["PackageReferenceName"],
}
})

if packageErrors != nil {
return packageErrors
}

snapshotJson, err := json.Marshal(snapshot)

if err != nil {
return err
}

url = state.Server + "/api/" + state.Space + "/runbookSnapshots?publish=true"
runbookSnapshotRequest, err := http.NewRequest("POST", url+"?publish=true", bytes.NewBuffer(snapshotJson))

if err != nil {
return err
}

runbookSnapshotResponse, err := myclient.HttpSession().DoRawRequest(runbookSnapshotRequest)

if err != nil {
return err
}

runbookSnapshotResponseRaw, err := io.ReadAll(runbookSnapshotResponse.Body)

if err != nil {
return err
}

runbookSnapshot := map[string]any{}
err = json.Unmarshal(runbookSnapshotResponseRaw, &runbookSnapshot)

if err != nil {
return err
}

fmt.Println(runbookSnapshot)

return nil
}
2 changes: 1 addition & 1 deletion internal/steps/project_export.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ func (s ProjectExportStep) GetContainer(parent fyne.Window) *fyne.Container {
Wizard: s.Wizard,
BaseStep: BaseStep{State: s.State}})
}, func() {
s.Wizard.ShowWizardStep(FinishStep{
s.Wizard.ShowWizardStep(StartSpaceExportStep{
Wizard: s.Wizard,
BaseStep: BaseStep{State: s.State}})
})
Expand Down
83 changes: 83 additions & 0 deletions internal/steps/start_space_export.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package steps

import (
"fmt"
"fyne.io/fyne/v2"
"fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/layout"
"fyne.io/fyne/v2/widget"
"github.com/mcasperson/OctoterraWizard/internal/infrastructure"
"github.com/mcasperson/OctoterraWizard/internal/strutil"
"github.com/mcasperson/OctoterraWizard/internal/wizard"
)

type StartSpaceExportStep struct {
BaseStep
Wizard wizard.Wizard
exportSpace *widget.Button
}

func (s StartSpaceExportStep) GetContainer(parent fyne.Window) *fyne.Container {

bottom, previous, next := s.BuildNavigation(func() {
s.Wizard.ShowWizardStep(ProjectExportStep{
Wizard: s.Wizard,
BaseStep: BaseStep{State: s.State}})
}, func() {
s.Wizard.ShowWizardStep(FinishStep{
Wizard: s.Wizard,
BaseStep: BaseStep{State: s.State}})
})
next.Disable()

label1 := widget.NewLabel(strutil.TrimMultilineWhitespace(`
The source space is now ready to begin exporting to the destination space.
We start by serializing the space level resources (feeds, accounts, tenants, certificates, targets etc) using the runbooks in the "Octoterra Space Management" project.
First, we run the "__ 1. Serialize Space" runbook to create the Terraform module.
Then we run the "__ 2. Deploy Space" runbook to apply the Terraform module to the destination space.
Click the "Export Space" button to execute these runbooks.
`))
result := widget.NewLabel("")
infinite := widget.NewProgressBarInfinite()
infinite.Hide()
infinite.Start()
s.exportSpace = widget.NewButton("Export Space", func() {
s.exportSpace.Disable()
next.Disable()
previous.Disable()
infinite.Show()
defer s.exportSpace.Enable()
defer previous.Enable()
defer infinite.Hide()

result.SetText("🔵 Running the runbooks.")

if err := s.Execute(); err != nil {
result.SetText(fmt.Sprintf("🔴 Failed to publish and run the runbooks: %s", err))
} else {
result.SetText("🟢 Runbooks ran successfully.")
next.Enable()
}
})
middle := container.New(layout.NewVBoxLayout(), label1, s.exportSpace, infinite, result)

content := container.NewBorder(nil, bottom, nil, nil, middle)

return content
}

func (s StartSpaceExportStep) Execute() (executeError error) {
if err := infrastructure.PublishRunbook(s.State, "__ 1. Serialize Space", "Octoterra Space Management"); err != nil {
return err
}

if taskId, err := infrastructure.RunRunbook(s.State, "__ 1. Serialize Space", "Octoterra Space Management"); err != nil {
return err
} else {
if err := infrastructure.WaitForTask(s.State, taskId); err != nil {
return err
}
}

return nil
}
Loading

0 comments on commit 0c03362

Please sign in to comment.