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

Commit d29d25f

Browse files
committed
Folder and Link resource handling
1 parent 5bc75c2 commit d29d25f

File tree

8 files changed

+191
-92
lines changed

8 files changed

+191
-92
lines changed

commands/project/deploy.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,14 @@ var DeployApplication = func() *cobra.Command {
4343

4444
err = taskRunner.RunTask(routines.PrepareProjectTask(projectDefinition))
4545
if err != nil {
46+
if system.Context.CurrentDeployment.Version > 0 {
47+
_ = xfs.DeleteFolder("ssh://"+system.Context.CurrentDeployment.GetPath(), true)
48+
}
4649
return
4750
}
4851
err = taskRunner.RunTask(routines.CollectResourcesTask(projectDefinition))
4952
if err != nil {
53+
_ = xfs.DeleteFolder("ssh://"+system.Context.CurrentDeployment.GetPath(), true)
5054
return
5155
}
5256

commands/project/destroy.go

Lines changed: 23 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
package project
22

33
import (
4-
"fmt"
5-
64
xfs "github.com/saitho/golang-extended-fs/v2"
75
"github.com/spf13/cobra"
86

@@ -12,6 +10,12 @@ import (
1210
"github.com/getstackhead/stackhead/system"
1311
)
1412

13+
func reverse[S ~[]E, E any](s S) {
14+
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
15+
s[i], s[j] = s[j], s[i]
16+
}
17+
}
18+
1519
// DestroyApplication is a command object for Cobra that provides the destroy command
1620
var DestroyApplication = &cobra.Command{
1721
Use: "destroy [path to project definition] [ipv4 address]",
@@ -26,22 +30,28 @@ var DestroyApplication = &cobra.Command{
2630
}
2731
commands.PrepareContext(args[1], system.ContextActionProjectDeploy, projectDefinition)
2832

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

34-
// Init modules
39+
modules := system.Context.GetModulesInOrder()
40+
reverse(modules)
41+
42+
// Run modules destroy steps
3543
for _, module := range modules {
3644
moduleSettings := system.GetModuleSettings(module.GetConfig().Name)
3745
module.Init(moduleSettings)
3846
}
3947
taskRunner := routines.TaskRunner{}
4048

41-
subTasks := []routines.Task{}
49+
subTasks := []routines.Task{
50+
// Remove resources from deployment
51+
routines.RemoveResources(latestDeployment),
52+
}
4253

4354
if hasProjectDir, _ := xfs.HasFolder("ssh://" + projectDefinition.GetDirectoryPath()); hasProjectDir {
44-
4555
// Run destroy scripts from plugins
4656
for _, module := range modules {
4757
moduleSettings := system.GetModuleSettings(module.GetConfig().Name)
@@ -65,13 +75,10 @@ var DestroyApplication = &cobra.Command{
6575
})
6676
}
6777

68-
_ = taskRunner.RunTask(routines.Task{
69-
Name: fmt.Sprintf("Destroying project \"%s\" on server with IP \"%s\"", args[0], args[1]),
70-
Run: func(r *routines.Task) error {
71-
return nil
72-
},
73-
SubTasks: subTasks,
74-
//RunAllSubTasksDespiteError: true,
75-
})
78+
for _, task := range subTasks {
79+
if err = taskRunner.RunTask(task); err != nil {
80+
panic(err)
81+
}
82+
}
7683
},
7784
}

modules/proxy/nginx/deploy.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,6 @@ func (Module) Deploy(_modulesSettings interface{}) error {
117117
fmt.Println("Deploy step")
118118
paths := getPaths()
119119

120-
if err := xfs.CreateFolder("ssh://" + paths.CertificatesProjectDirectory); err != nil {
121-
return err
122-
}
123-
124120
serverConfig := buildServerConfig(system.Context.Project, proxy.Context.AllPorts)
125121
nginxConfigResource := system.Resource{
126122
Type: system.TypeFile,
@@ -132,36 +128,42 @@ func (Module) Deploy(_modulesSettings interface{}) error {
132128
system.Context.CurrentDeployment.ResourceGroups = append(system.Context.CurrentDeployment.ResourceGroups, system.ResourceGroup{
133129
Name: "proxy-nginx-" + system.Context.Project.Name,
134130
Resources: []system.Resource{
135-
system.Resource{
131+
{
132+
Type: system.TypeFolder,
133+
Operation: system.OperationCreate,
134+
Name: paths.CertificatesProjectDirectory,
135+
ExternalResource: true,
136+
},
137+
{
136138
Type: system.TypeFolder,
137139
Operation: system.OperationCreate,
138140
Name: "certificates",
139141
},
140142
nginxConfigResource,
141143
// Symlink project certificate files to snakeoil files after initial creation
142-
system.Resource{
144+
{
143145
Type: system.TypeLink,
144146
Operation: system.OperationCreate,
145147
Name: paths.CertificatesProjectDirectory + "/fullchain.pem",
146148
ExternalResource: true,
147149
LinkSource: paths.SnakeoilFullchainPath,
148150
},
149-
system.Resource{
151+
{
150152
Type: system.TypeLink,
151153
Operation: system.OperationCreate,
152154
Name: paths.CertificatesProjectDirectory + "/privkey.pem",
153155
ExternalResource: true,
154156
LinkSource: paths.SnakeoilPrivkeyPath,
155157
},
156-
system.Resource{
158+
{
157159
Type: system.TypeLink,
158160
Operation: system.OperationCreate,
159161
Name: "/etc/nginx/sites-available/stackhead_" + system.Context.Project.Name + ".conf",
160162
ExternalResource: true,
161163
LinkSource: nginxConfigResourcePath,
162164
EnforceLink: true,
163165
},
164-
system.Resource{
166+
{
165167
Type: system.TypeLink,
166168
Operation: system.OperationCreate,
167169
Name: moduleSettings.Config.VhostPath + "/stackhead_" + system.Context.Project.Name + ".conf",

modules/proxy/nginx/destroy.go

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,16 +28,6 @@ func (m Module) Destroy(_modulesSettings interface{}) error {
2828
return fmt.Errorf("Unable to remove ACME challenge directory: " + err.Error())
2929
}
3030

31-
if err := xfs.DeleteFile("ssh:///etc/nginx/sites-available/stackhead_" + system.Context.Project.Name + ".conf"); err != nil {
32-
return fmt.Errorf("Unable to remove Nginx symlink: " + err.Error())
33-
}
34-
if err := xfs.DeleteFile("ssh://" + moduleSettings.Config.VhostPath + "/stackhead_" + system.Context.Project.Name + ".conf"); err != nil {
35-
return fmt.Errorf("Unable to remove Nginx symlink: " + err.Error())
36-
}
37-
if err := xfs.DeleteFolder("ssh://"+CertificatesDirectory+"/"+system.Context.Project.Name, true); err != nil {
38-
return fmt.Errorf("Unable to remove certificates directory: " + err.Error())
39-
}
40-
4131
if _, err := system.SimpleRemoteRun("systemctl", system.RemoteRunOpts{Args: []string{"reload", "nginx"}, Sudo: true}); err != nil {
4232
return fmt.Errorf("Unable to reload Nginx service: " + err.Error())
4333
}

routines/implementations.go

Lines changed: 82 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
logger "github.com/sirupsen/logrus"
88
"gopkg.in/yaml.v3"
99
"path"
10-
"sort"
10+
"strconv"
1111
"strings"
1212
"time"
1313

@@ -37,7 +37,7 @@ var ValidateStackHeadVersionTask = Task{
3737

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

50+
r.PrintLn("Lookup previous deployments")
5051
// Find latest deployment
51-
files, err := xfs.ListFolders("ssh://" + projectDefinition.GetDeploymentsPath())
52+
latestDeployment, err := system.GetLatestDeployment(projectDefinition)
5253
if err != nil {
5354
return err
5455
}
55-
if files != nil {
56-
// newest files at the top
57-
sort.Slice(files, func(i, j int) bool {
58-
return files[i].ModTime().After(files[j].ModTime())
59-
})
60-
for _, file := range files {
61-
if file.IsDir() && system.MatchDeploymentNaming(file.Name()) {
62-
fullPath := path.Join(projectDefinition.GetDeploymentsPath(), file.Name())
63-
latestDeployment, err := system.GetDeploymentByPath(fullPath)
64-
if err != nil {
65-
return err
66-
}
67-
if !latestDeployment.RolledBack {
68-
latestDeployment.Project = system.Context.Project
69-
system.Context.LatestDeployment = latestDeployment
70-
break
71-
}
72-
}
73-
}
74-
}
56+
system.Context.LatestDeployment = latestDeployment
57+
oldVersion := "N/A"
7558
newVersion := 1
7659
if system.Context.LatestDeployment != nil {
60+
oldVersion = "v" + strconv.Itoa(system.Context.LatestDeployment.Version)
7761
newVersion = system.Context.LatestDeployment.Version + 1
7862
}
7963
system.Context.CurrentDeployment = system.Deployment{
8064
Version: newVersion,
8165
DateStart: time.Now(),
8266
Project: system.Context.Project,
8367
}
68+
r.PrintLn(fmt.Sprintf("Previous deployment: %s, new deployment: v%d", oldVersion, newVersion))
8469

8570
// Create folder for new deployment
8671
if err := xfs.CreateFolder("ssh://" + system.Context.CurrentDeployment.GetPath()); err != nil {
@@ -104,6 +89,7 @@ var CollectResourcesTask = func(projectDefinition *project.Project) Task {
10489
if module.GetConfig().Type == "plugin" {
10590
continue
10691
}
92+
r.PrintLn("Collecting from " + module.GetConfig().Name)
10793
moduleSettings := system.GetModuleSettings(module.GetConfig().Name)
10894
if err := module.Deploy(moduleSettings); err != nil {
10995
return err
@@ -132,7 +118,7 @@ var RollbackResources = Task{
132118
}
133119
for _, resource := range resourceGroup.Resources {
134120
spinner := r.TaskRunner.GetNewSubtaskSpinner(resource.ToString(true))
135-
matched, err := system.RollbackResourceOperation(resource)
121+
matched, err := system.RollbackResourceOperation(resource, false)
136122
if !matched || err == nil {
137123
if spinner != nil {
138124
spinner.Complete()
@@ -168,16 +154,16 @@ var RollbackResources = Task{
168154
var CreateResources = Task{
169155
Name: "Creating resources",
170156
Run: func(r *Task) error {
171-
var errors []error
157+
var errors []string
172158
var uncompletedSpinners []*ysmrr.Spinner
173159

174160
for _, resourceGroup := range system.Context.CurrentDeployment.ResourceGroups {
175161
for _, resource := range resourceGroup.Resources {
176162
spinner := r.TaskRunner.GetNewSubtaskSpinner(resource.ToString(false))
177-
processed, err := system.ApplyResourceOperation(resource)
163+
processed, err := system.ApplyResourceOperation(resource, false)
178164
if err != nil {
179165
rollback = true
180-
errors = append(errors, err)
166+
errors = append(errors, err.Error())
181167
if spinner != nil {
182168
spinner.UpdateMessage(err.Error())
183169
spinner.Error()
@@ -201,7 +187,7 @@ var CreateResources = Task{
201187
spinner.Error()
202188
}
203189
rollback = true
204-
errors = append(errors, fmt.Errorf("Unable to complete resource creation: %s", err))
190+
errors = append(errors, fmt.Sprintf("Unable to complete resource creation: %s", err))
205191
}
206192
}
207193
if !rollback {
@@ -219,24 +205,89 @@ var CreateResources = Task{
219205
}
220206
errorMessages := []string{"The following errors occurred:"}
221207
for _, err2 := range errors {
222-
errorMessages = append(errorMessages, "- "+err2.Error())
208+
errorMessages = append(errorMessages, "- "+err2)
223209
}
224210
return fmt.Errorf(strings.Join(errorMessages, "\n"))
225211
},
226212
}
227213

214+
func reverse[S ~[]E, E any](s S) {
215+
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
216+
s[i], s[j] = s[j], s[i]
217+
}
218+
}
219+
220+
var RemoveResources = func(latestDeployment *system.Deployment) Task {
221+
return Task{
222+
Name: "Removing project resources",
223+
Run: func(r *Task) error {
224+
var errors []string
225+
var uncompletedSpinners []*ysmrr.Spinner
226+
227+
reverse(latestDeployment.ResourceGroups)
228+
for _, group := range latestDeployment.ResourceGroups {
229+
reverse(group.Resources)
230+
for _, resource := range group.Resources {
231+
if resource.ExternalResource {
232+
resource.Operation = system.OperationDelete
233+
spinner := r.TaskRunner.GetNewSubtaskSpinner(resource.ToString(false))
234+
if processed, err := system.PerformOperation(resource, true); err != nil {
235+
if err != nil {
236+
errors = append(errors, err.Error())
237+
if spinner != nil {
238+
spinner.UpdateMessage(err.Error())
239+
spinner.Error()
240+
}
241+
return err
242+
}
243+
if spinner != nil {
244+
if processed {
245+
spinner.Complete()
246+
} else {
247+
// uncompleted spinners are resolved when resource group finishes
248+
uncompletedSpinners = append(uncompletedSpinners, spinner)
249+
}
250+
}
251+
}
252+
}
253+
}
254+
for _, spinner := range uncompletedSpinners {
255+
spinner.Complete()
256+
}
257+
}
258+
if len(errors) == 0 {
259+
return nil
260+
}
261+
errorMessages := []string{"The following errors occurred:"}
262+
for _, err2 := range errors {
263+
errorMessages = append(errorMessages, "- "+err2)
264+
}
265+
return fmt.Errorf(strings.Join(errorMessages, "\n"))
266+
},
267+
}
268+
}
269+
228270
var FinalizeDeployment = Task{
229271
Name: "Finalizing deployment",
230272
Run: func(r *Task) error {
273+
// set deployment end date
231274
system.Context.CurrentDeployment.DateEnd = time.Now()
232-
resourcesPath := path.Join(system.Context.CurrentDeployment.GetPath(), "deployment.yaml")
275+
276+
// save deployment.yaml file
233277
yamlString, err := yaml.Marshal(system.Context.CurrentDeployment)
234278
if err != nil {
235279
return err
236280
}
237-
if err = xfs.WriteFile("ssh://"+resourcesPath, string(yamlString)); err != nil {
281+
if err = xfs.WriteFile("ssh://"+path.Join(system.Context.CurrentDeployment.GetPath(), "deployment.yaml"), string(yamlString)); err != nil {
238282
return err
239283
}
284+
285+
// update current symlink if deployment was successful
286+
if !system.Context.CurrentDeployment.RolledBack {
287+
if _, err := system.SimpleRemoteRun("ln", system.RemoteRunOpts{Args: []string{"-sfn " + system.Context.CurrentDeployment.GetPath() + " " + path.Join(system.Context.CurrentDeployment.Project.GetDeploymentsPath(), "current")}}); err != nil {
288+
return fmt.Errorf("Unable to symlink current deployment: " + err.Error())
289+
}
290+
}
240291
return nil
241292
},
242293
}

0 commit comments

Comments
 (0)