Skip to content
This repository has been archived by the owner on Feb 26, 2024. It is now read-only.

Commit

Permalink
Folder and Link resource handling
Browse files Browse the repository at this point in the history
  • Loading branch information
saitho committed May 28, 2023
1 parent 5bc75c2 commit d29d25f
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 92 deletions.
4 changes: 4 additions & 0 deletions commands/project/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,14 @@ var DeployApplication = func() *cobra.Command {

err = taskRunner.RunTask(routines.PrepareProjectTask(projectDefinition))
if err != nil {
if system.Context.CurrentDeployment.Version > 0 {
_ = xfs.DeleteFolder("ssh://"+system.Context.CurrentDeployment.GetPath(), true)
}
return
}
err = taskRunner.RunTask(routines.CollectResourcesTask(projectDefinition))
if err != nil {
_ = xfs.DeleteFolder("ssh://"+system.Context.CurrentDeployment.GetPath(), true)
return
}

Expand Down
39 changes: 23 additions & 16 deletions commands/project/destroy.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package project

import (
"fmt"

xfs "github.com/saitho/golang-extended-fs/v2"
"github.com/spf13/cobra"

Expand All @@ -12,6 +10,12 @@ import (
"github.com/getstackhead/stackhead/system"
)

func reverse[S ~[]E, E any](s S) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}

// DestroyApplication is a command object for Cobra that provides the destroy command
var DestroyApplication = &cobra.Command{
Use: "destroy [path to project definition] [ipv4 address]",
Expand All @@ -26,22 +30,28 @@ var DestroyApplication = &cobra.Command{
}
commands.PrepareContext(args[1], system.ContextActionProjectDeploy, projectDefinition)

modules := system.Context.GetModulesInOrder()
for i, j := 0, len(modules)-1; i < j; i, j = i+1, j-1 { // reverse module list
modules[i], modules[j] = modules[j], modules[i]
latestDeployment, err := system.GetLatestDeployment(projectDefinition)
if err != nil {
panic("unable to load latest deployment" + err.Error())
}
system.Context.CurrentDeployment = *latestDeployment

// Init modules
modules := system.Context.GetModulesInOrder()
reverse(modules)

// Run modules destroy steps
for _, module := range modules {
moduleSettings := system.GetModuleSettings(module.GetConfig().Name)
module.Init(moduleSettings)
}
taskRunner := routines.TaskRunner{}

subTasks := []routines.Task{}
subTasks := []routines.Task{
// Remove resources from deployment
routines.RemoveResources(latestDeployment),
}

if hasProjectDir, _ := xfs.HasFolder("ssh://" + projectDefinition.GetDirectoryPath()); hasProjectDir {

// Run destroy scripts from plugins
for _, module := range modules {
moduleSettings := system.GetModuleSettings(module.GetConfig().Name)
Expand All @@ -65,13 +75,10 @@ var DestroyApplication = &cobra.Command{
})
}

_ = taskRunner.RunTask(routines.Task{
Name: fmt.Sprintf("Destroying project \"%s\" on server with IP \"%s\"", args[0], args[1]),
Run: func(r *routines.Task) error {
return nil
},
SubTasks: subTasks,
//RunAllSubTasksDespiteError: true,
})
for _, task := range subTasks {
if err = taskRunner.RunTask(task); err != nil {
panic(err)
}
}
},
}
20 changes: 11 additions & 9 deletions modules/proxy/nginx/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,6 @@ func (Module) Deploy(_modulesSettings interface{}) error {
fmt.Println("Deploy step")
paths := getPaths()

if err := xfs.CreateFolder("ssh://" + paths.CertificatesProjectDirectory); err != nil {
return err
}

serverConfig := buildServerConfig(system.Context.Project, proxy.Context.AllPorts)
nginxConfigResource := system.Resource{
Type: system.TypeFile,
Expand All @@ -132,36 +128,42 @@ func (Module) Deploy(_modulesSettings interface{}) error {
system.Context.CurrentDeployment.ResourceGroups = append(system.Context.CurrentDeployment.ResourceGroups, system.ResourceGroup{
Name: "proxy-nginx-" + system.Context.Project.Name,
Resources: []system.Resource{
system.Resource{
{
Type: system.TypeFolder,
Operation: system.OperationCreate,
Name: paths.CertificatesProjectDirectory,
ExternalResource: true,
},
{
Type: system.TypeFolder,
Operation: system.OperationCreate,
Name: "certificates",
},
nginxConfigResource,
// Symlink project certificate files to snakeoil files after initial creation
system.Resource{
{
Type: system.TypeLink,
Operation: system.OperationCreate,
Name: paths.CertificatesProjectDirectory + "/fullchain.pem",
ExternalResource: true,
LinkSource: paths.SnakeoilFullchainPath,
},
system.Resource{
{
Type: system.TypeLink,
Operation: system.OperationCreate,
Name: paths.CertificatesProjectDirectory + "/privkey.pem",
ExternalResource: true,
LinkSource: paths.SnakeoilPrivkeyPath,
},
system.Resource{
{
Type: system.TypeLink,
Operation: system.OperationCreate,
Name: "/etc/nginx/sites-available/stackhead_" + system.Context.Project.Name + ".conf",
ExternalResource: true,
LinkSource: nginxConfigResourcePath,
EnforceLink: true,
},
system.Resource{
{
Type: system.TypeLink,
Operation: system.OperationCreate,
Name: moduleSettings.Config.VhostPath + "/stackhead_" + system.Context.Project.Name + ".conf",
Expand Down
10 changes: 0 additions & 10 deletions modules/proxy/nginx/destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,6 @@ func (m Module) Destroy(_modulesSettings interface{}) error {
return fmt.Errorf("Unable to remove ACME challenge directory: " + err.Error())
}

if err := xfs.DeleteFile("ssh:///etc/nginx/sites-available/stackhead_" + system.Context.Project.Name + ".conf"); err != nil {
return fmt.Errorf("Unable to remove Nginx symlink: " + err.Error())
}
if err := xfs.DeleteFile("ssh://" + moduleSettings.Config.VhostPath + "/stackhead_" + system.Context.Project.Name + ".conf"); err != nil {
return fmt.Errorf("Unable to remove Nginx symlink: " + err.Error())
}
if err := xfs.DeleteFolder("ssh://"+CertificatesDirectory+"/"+system.Context.Project.Name, true); err != nil {
return fmt.Errorf("Unable to remove certificates directory: " + err.Error())
}

if _, err := system.SimpleRemoteRun("systemctl", system.RemoteRunOpts{Args: []string{"reload", "nginx"}, Sudo: true}); err != nil {
return fmt.Errorf("Unable to reload Nginx service: " + err.Error())
}
Expand Down
113 changes: 82 additions & 31 deletions routines/implementations.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
logger "github.com/sirupsen/logrus"
"gopkg.in/yaml.v3"
"path"
"sort"
"strconv"
"strings"
"time"

Expand Down Expand Up @@ -37,7 +37,7 @@ var ValidateStackHeadVersionTask = Task{

var PrepareProjectTask = func(projectDefinition *project.Project) Task {
return Task{
Name: fmt.Sprintf("Preparing project structure"),
Name: fmt.Sprintf("Preparing deployment"),
Run: func(r *Task) error {
r.PrintLn("Create project directory if not exists")
if err := xfs.CreateFolder("ssh://" + projectDefinition.GetDirectoryPath()); err != nil {
Expand All @@ -47,40 +47,25 @@ var PrepareProjectTask = func(projectDefinition *project.Project) Task {
return err
}

r.PrintLn("Lookup previous deployments")
// Find latest deployment
files, err := xfs.ListFolders("ssh://" + projectDefinition.GetDeploymentsPath())
latestDeployment, err := system.GetLatestDeployment(projectDefinition)
if err != nil {
return err
}
if files != nil {
// newest files at the top
sort.Slice(files, func(i, j int) bool {
return files[i].ModTime().After(files[j].ModTime())
})
for _, file := range files {
if file.IsDir() && system.MatchDeploymentNaming(file.Name()) {
fullPath := path.Join(projectDefinition.GetDeploymentsPath(), file.Name())
latestDeployment, err := system.GetDeploymentByPath(fullPath)
if err != nil {
return err
}
if !latestDeployment.RolledBack {
latestDeployment.Project = system.Context.Project
system.Context.LatestDeployment = latestDeployment
break
}
}
}
}
system.Context.LatestDeployment = latestDeployment
oldVersion := "N/A"
newVersion := 1
if system.Context.LatestDeployment != nil {
oldVersion = "v" + strconv.Itoa(system.Context.LatestDeployment.Version)
newVersion = system.Context.LatestDeployment.Version + 1
}
system.Context.CurrentDeployment = system.Deployment{
Version: newVersion,
DateStart: time.Now(),
Project: system.Context.Project,
}
r.PrintLn(fmt.Sprintf("Previous deployment: %s, new deployment: v%d", oldVersion, newVersion))

// Create folder for new deployment
if err := xfs.CreateFolder("ssh://" + system.Context.CurrentDeployment.GetPath()); err != nil {
Expand All @@ -104,6 +89,7 @@ var CollectResourcesTask = func(projectDefinition *project.Project) Task {
if module.GetConfig().Type == "plugin" {
continue
}
r.PrintLn("Collecting from " + module.GetConfig().Name)
moduleSettings := system.GetModuleSettings(module.GetConfig().Name)
if err := module.Deploy(moduleSettings); err != nil {
return err
Expand Down Expand Up @@ -132,7 +118,7 @@ var RollbackResources = Task{
}
for _, resource := range resourceGroup.Resources {
spinner := r.TaskRunner.GetNewSubtaskSpinner(resource.ToString(true))
matched, err := system.RollbackResourceOperation(resource)
matched, err := system.RollbackResourceOperation(resource, false)
if !matched || err == nil {
if spinner != nil {
spinner.Complete()
Expand Down Expand Up @@ -168,16 +154,16 @@ var RollbackResources = Task{
var CreateResources = Task{
Name: "Creating resources",
Run: func(r *Task) error {
var errors []error
var errors []string
var uncompletedSpinners []*ysmrr.Spinner

for _, resourceGroup := range system.Context.CurrentDeployment.ResourceGroups {
for _, resource := range resourceGroup.Resources {
spinner := r.TaskRunner.GetNewSubtaskSpinner(resource.ToString(false))
processed, err := system.ApplyResourceOperation(resource)
processed, err := system.ApplyResourceOperation(resource, false)
if err != nil {
rollback = true
errors = append(errors, err)
errors = append(errors, err.Error())
if spinner != nil {
spinner.UpdateMessage(err.Error())
spinner.Error()
Expand All @@ -201,7 +187,7 @@ var CreateResources = Task{
spinner.Error()
}
rollback = true
errors = append(errors, fmt.Errorf("Unable to complete resource creation: %s", err))
errors = append(errors, fmt.Sprintf("Unable to complete resource creation: %s", err))
}
}
if !rollback {
Expand All @@ -219,24 +205,89 @@ var CreateResources = Task{
}
errorMessages := []string{"The following errors occurred:"}
for _, err2 := range errors {
errorMessages = append(errorMessages, "- "+err2.Error())
errorMessages = append(errorMessages, "- "+err2)
}
return fmt.Errorf(strings.Join(errorMessages, "\n"))
},
}

func reverse[S ~[]E, E any](s S) {
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
s[i], s[j] = s[j], s[i]
}
}

var RemoveResources = func(latestDeployment *system.Deployment) Task {
return Task{
Name: "Removing project resources",
Run: func(r *Task) error {
var errors []string
var uncompletedSpinners []*ysmrr.Spinner

reverse(latestDeployment.ResourceGroups)
for _, group := range latestDeployment.ResourceGroups {
reverse(group.Resources)
for _, resource := range group.Resources {
if resource.ExternalResource {
resource.Operation = system.OperationDelete
spinner := r.TaskRunner.GetNewSubtaskSpinner(resource.ToString(false))
if processed, err := system.PerformOperation(resource, true); err != nil {
if err != nil {
errors = append(errors, err.Error())
if spinner != nil {
spinner.UpdateMessage(err.Error())
spinner.Error()
}
return err
}
if spinner != nil {
if processed {
spinner.Complete()
} else {
// uncompleted spinners are resolved when resource group finishes
uncompletedSpinners = append(uncompletedSpinners, spinner)
}
}
}
}
}
for _, spinner := range uncompletedSpinners {
spinner.Complete()
}
}
if len(errors) == 0 {
return nil
}
errorMessages := []string{"The following errors occurred:"}
for _, err2 := range errors {
errorMessages = append(errorMessages, "- "+err2)
}
return fmt.Errorf(strings.Join(errorMessages, "\n"))
},
}
}

var FinalizeDeployment = Task{
Name: "Finalizing deployment",
Run: func(r *Task) error {
// set deployment end date
system.Context.CurrentDeployment.DateEnd = time.Now()
resourcesPath := path.Join(system.Context.CurrentDeployment.GetPath(), "deployment.yaml")

// save deployment.yaml file
yamlString, err := yaml.Marshal(system.Context.CurrentDeployment)
if err != nil {
return err
}
if err = xfs.WriteFile("ssh://"+resourcesPath, string(yamlString)); err != nil {
if err = xfs.WriteFile("ssh://"+path.Join(system.Context.CurrentDeployment.GetPath(), "deployment.yaml"), string(yamlString)); err != nil {
return err
}

// update current symlink if deployment was successful
if !system.Context.CurrentDeployment.RolledBack {
if _, err := system.SimpleRemoteRun("ln", system.RemoteRunOpts{Args: []string{"-sfn " + system.Context.CurrentDeployment.GetPath() + " " + path.Join(system.Context.CurrentDeployment.Project.GetDeploymentsPath(), "current")}}); err != nil {
return fmt.Errorf("Unable to symlink current deployment: " + err.Error())
}
}
return nil
},
}
Loading

0 comments on commit d29d25f

Please sign in to comment.