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

Commit fa63f4d

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

File tree

8 files changed

+181
-97
lines changed

8 files changed

+181
-97
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: 72 additions & 36 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()
@@ -156,28 +142,22 @@ var RollbackResources = Task{
156142
return fmt.Errorf("The following errors occurred:\n" + strings.Join(system.Context.CurrentDeployment.RollbackErrors, "\n"))
157143
}
158144

159-
// Delete deployment version
160-
//if err := xfs.DeleteFolder("ssh://"+system.Context.CurrentDeployment.GetPath(), true); err != nil {
161-
// return fmt.Errorf("unable to remove deployment folder: " + err.Error())
162-
//}
163-
164145
return nil
165146
},
166147
}
167148

168149
var CreateResources = Task{
169150
Name: "Creating resources",
170151
Run: func(r *Task) error {
171-
var errors []error
152+
var errors []string
172153
var uncompletedSpinners []*ysmrr.Spinner
173154

174155
for _, resourceGroup := range system.Context.CurrentDeployment.ResourceGroups {
175156
for _, resource := range resourceGroup.Resources {
176157
spinner := r.TaskRunner.GetNewSubtaskSpinner(resource.ToString(false))
177-
processed, err := system.ApplyResourceOperation(resource)
158+
processed, err := system.ApplyResourceOperation(resource, false)
178159
if err != nil {
179160
rollback = true
180-
errors = append(errors, err)
181161
if spinner != nil {
182162
spinner.UpdateMessage(err.Error())
183163
spinner.Error()
@@ -201,7 +181,7 @@ var CreateResources = Task{
201181
spinner.Error()
202182
}
203183
rollback = true
204-
errors = append(errors, fmt.Errorf("Unable to complete resource creation: %s", err))
184+
errors = append(errors, fmt.Sprintf("Unable to complete resource creation: %s", err))
205185
}
206186
}
207187
if !rollback {
@@ -219,24 +199,80 @@ var CreateResources = Task{
219199
}
220200
errorMessages := []string{"The following errors occurred:"}
221201
for _, err2 := range errors {
222-
errorMessages = append(errorMessages, "- "+err2.Error())
202+
errorMessages = append(errorMessages, "- "+err2)
223203
}
224204
return fmt.Errorf(strings.Join(errorMessages, "\n"))
225205
},
226206
}
227207

208+
func reverse[S ~[]E, E any](s S) {
209+
for i, j := 0, len(s)-1; i < j; i, j = i+1, j-1 {
210+
s[i], s[j] = s[j], s[i]
211+
}
212+
}
213+
214+
var RemoveResources = func(latestDeployment *system.Deployment) Task {
215+
return Task{
216+
Name: "Removing project resources",
217+
Run: func(r *Task) error {
218+
var uncompletedSpinners []*ysmrr.Spinner
219+
220+
reverse(latestDeployment.ResourceGroups)
221+
for _, group := range latestDeployment.ResourceGroups {
222+
reverse(group.Resources)
223+
for _, resource := range group.Resources {
224+
if resource.ExternalResource {
225+
resource.Operation = system.OperationDelete
226+
spinner := r.TaskRunner.GetNewSubtaskSpinner(resource.ToString(false))
227+
if processed, err := system.PerformOperation(resource, true); err != nil {
228+
if err != nil {
229+
if spinner != nil {
230+
spinner.UpdateMessage(err.Error())
231+
spinner.Error()
232+
}
233+
return err
234+
}
235+
if spinner != nil {
236+
if processed {
237+
spinner.Complete()
238+
} else {
239+
// uncompleted spinners are resolved when resource group finishes
240+
uncompletedSpinners = append(uncompletedSpinners, spinner)
241+
}
242+
}
243+
}
244+
}
245+
}
246+
for _, spinner := range uncompletedSpinners {
247+
spinner.Complete()
248+
}
249+
}
250+
return nil
251+
},
252+
}
253+
}
254+
228255
var FinalizeDeployment = Task{
229256
Name: "Finalizing deployment",
230257
Run: func(r *Task) error {
258+
// set deployment end date
231259
system.Context.CurrentDeployment.DateEnd = time.Now()
232-
resourcesPath := path.Join(system.Context.CurrentDeployment.GetPath(), "deployment.yaml")
260+
261+
// save deployment.yaml file
233262
yamlString, err := yaml.Marshal(system.Context.CurrentDeployment)
234263
if err != nil {
235264
return err
236265
}
237-
if err = xfs.WriteFile("ssh://"+resourcesPath, string(yamlString)); err != nil {
266+
if err = xfs.WriteFile("ssh://"+path.Join(system.Context.CurrentDeployment.GetPath(), "deployment.yaml"), string(yamlString)); err != nil {
238267
return err
239268
}
269+
270+
// update current symlink if deployment was successful
271+
if !system.Context.CurrentDeployment.RolledBack {
272+
if _, err := system.SimpleRemoteRun("ln", system.RemoteRunOpts{Args: []string{"-sfn " + system.Context.CurrentDeployment.GetPath() + " " + path.Join(system.Context.CurrentDeployment.Project.GetDeploymentsPath(), "current")}}); err != nil {
273+
return fmt.Errorf("Unable to symlink current deployment: " + err.Error())
274+
}
275+
}
240276
return nil
241277
},
242278
}

0 commit comments

Comments
 (0)