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

Commit e14beac

Browse files
committed
feat: implement backups for external files during deployment
1 parent 82cce26 commit e14beac

File tree

9 files changed

+149
-31
lines changed

9 files changed

+149
-31
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ require (
2020
github.com/knadh/koanf v1.4.4
2121
github.com/phayes/freeport v0.0.0-20220201140144-74d24b5ae9f5
2222
github.com/saitho/diff-docker-compose v1.1.3
23-
github.com/saitho/golang-extended-fs/v2 v2.0.2
23+
github.com/saitho/golang-extended-fs/v2 v2.1.0
2424
github.com/shibukawa/configdir v0.0.0-20170330084843-e180dbdc8da0
2525
github.com/sirupsen/logrus v1.9.2
2626
github.com/spf13/cast v1.3.1

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -862,8 +862,8 @@ github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIH
862862
github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 h1:GHRpF1pTW19a8tTFrMLUcfWwyC0pnifVo2ClaLq+hP8=
863863
github.com/saitho/diff-docker-compose v1.1.3 h1:ZKx+F7yMmoAKzI76BFpr7kY+Thd7quk7d/ETug0qNY8=
864864
github.com/saitho/diff-docker-compose v1.1.3/go.mod h1:O3V+mwGtlXQ7UERA4+yunV319kyNLwIKeNSw8PODyEU=
865-
github.com/saitho/golang-extended-fs/v2 v2.0.2 h1:QWGOIP4wFTaSODIU54qDCuH1IQXWcjL6irLezJiHqOA=
866-
github.com/saitho/golang-extended-fs/v2 v2.0.2/go.mod h1:aTmESz9Z7Rwbx80zxGWTeuBmW6MLdzYrBZMJOfXsJVw=
865+
github.com/saitho/golang-extended-fs/v2 v2.1.0 h1:j1X3hu5LQRUM0WQzis8Kyh6zZhqCjYQox7Gw1h/ruX0=
866+
github.com/saitho/golang-extended-fs/v2 v2.1.0/go.mod h1:aTmESz9Z7Rwbx80zxGWTeuBmW6MLdzYrBZMJOfXsJVw=
867867
github.com/saitho/jsonschema-validator v1.2.0 h1:bWSvTla54F5cziZsxLBR0mlN3gTw9hjrkFuZU55kO+E=
868868
github.com/saitho/jsonschema-validator v1.2.0/go.mod h1:W/1Q1xQ+vyYxANlTEpi6hQBEHJPEi4wJmCBkdIehFvQ=
869869
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=

modules/container/docker/deploy.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ func (m Module) Deploy(modulesSettings interface{}) error {
147147
oldComposeFilePath := ""
148148
var remoteComposeObjMap map[string]interface{}
149149
if system.Context.LatestDeployment != nil {
150-
dockerComposeFilePathOld, err := system.Context.LatestDeployment.GetResourcePath(dockerComposeResource)
150+
dockerComposeFilePathOld, err := system.Context.LatestDeployment.GetResourcePath(&dockerComposeResource)
151151
if err != nil {
152152
return err
153153
}
@@ -166,7 +166,7 @@ func (m Module) Deploy(modulesSettings interface{}) error {
166166
if err != nil {
167167
return fmt.Errorf("unable to process remote docker-compose.yaml file from previous deployment: " + err.Error())
168168
}
169-
} else if err != nil && err.Error() != "file does not exist" {
169+
} else if err != nil {
170170
return fmt.Errorf("Unable to check state of remote docker-compose.yaml from previous deployment: " + err.Error())
171171
}
172172
}
@@ -190,7 +190,7 @@ func (m Module) Deploy(modulesSettings interface{}) error {
190190
}
191191
dockerComposeResource.Content = composeFileContent
192192

193-
dockerComposeFilePathNew, _ := system.Context.CurrentDeployment.GetResourcePath(dockerComposeResource)
193+
dockerComposeFilePathNew, _ := system.Context.CurrentDeployment.GetResourcePath(&dockerComposeResource)
194194

195195
system.Context.CurrentDeployment.ResourceGroups = append(system.Context.CurrentDeployment.ResourceGroups, system.ResourceGroup{
196196
Name: "container-docker-" + system.Context.Project.Name + "-composefile",

modules/proxy/caddy/deploy.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func (Module) Deploy(modulesSettings interface{}) error {
2424
Content: caddyDirectives,
2525
}
2626

27-
caddyFilePath, err := system.Context.CurrentDeployment.GetResourcePath(caddyFileResource)
27+
caddyFilePath, err := system.Context.CurrentDeployment.GetResourcePath(&caddyFileResource)
2828
if err != nil {
2929
return err
3030
}

modules/proxy/nginx/deploy.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ func (Module) Deploy(_modulesSettings interface{}) error {
124124
Name: "nginx.conf",
125125
Content: serverConfig,
126126
}
127-
nginxConfigResourcePath, _ := system.Context.CurrentDeployment.GetResourcePath(nginxConfigResource)
127+
nginxConfigResourcePath, _ := system.Context.CurrentDeployment.GetResourcePath(&nginxConfigResource)
128128
system.Context.CurrentDeployment.ResourceGroups = append(system.Context.CurrentDeployment.ResourceGroups, system.ResourceGroup{
129129
Name: "proxy-nginx-" + system.Context.Project.Name,
130130
Resources: []system.Resource{

routines/implementations.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,9 +147,9 @@ func processResourceGroup(taskRunner *TaskRunner, resourceGroup system.ResourceG
147147
var err error
148148
var processed bool
149149
if isRollbackMode {
150-
processed, err = system.RollbackResourceOperation(resource, ignoreBackup)
150+
processed, err = system.RollbackResourceOperation(&resource, ignoreBackup)
151151
} else {
152-
processed, err = system.ApplyResourceOperation(resource, ignoreBackup)
152+
processed, err = system.ApplyResourceOperation(&resource, ignoreBackup)
153153
}
154154
if err != nil {
155155
if spinner != nil {
@@ -260,8 +260,15 @@ var FinalizeDeployment = Task{
260260
return err
261261
}
262262

263-
// update current symlink if deployment was successful
264263
if !system.Context.CurrentDeployment.RolledBack {
264+
// Remove external backups
265+
for _, resourceGroup := range system.Context.CurrentDeployment.ResourceGroups {
266+
for _, resource := range resourceGroup.Resources {
267+
fmt.Println(resource.BackupFilePath) // todo: remove
268+
}
269+
}
270+
271+
// update current symlink if deployment was successful
265272
if _, err := system.SimpleRemoteRun("ln", system.RemoteRunOpts{Args: []string{"-sfn " + system.Context.CurrentDeployment.GetPath() + " " + path.Join(system.Context.CurrentDeployment.Project.GetDeploymentsPath(), "current")}}); err != nil {
266273
return fmt.Errorf("Unable to symlink current deployment: " + err.Error())
267274
}

system/context.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -43,9 +43,9 @@ type Deployment struct {
4343
ResourceGroups []ResourceGroup
4444
}
4545

46-
func (d Deployment) GetResourcePath(resource Resource) (string, error) {
46+
func (d Deployment) GetResourcePath(resource *Resource) (string, error) {
4747
if resource.Type != TypeFile && resource.Type != TypeFolder && resource.Type != TypeLink {
48-
return "", fmt.Errorf("not a file, folder or link resource")
48+
return "", fmt.Errorf("unsupported resouce type \"%s\". expected file, folder or link", resource.Type)
4949
}
5050
if resource.ExternalResource {
5151
if !path.IsAbs(resource.Name) {

system/resource.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,9 @@ type ResourceGroup struct {
3535
}
3636

3737
type Resource struct {
38-
Type Type
39-
Operation Operation `yaml:"-"`
38+
Type Type
39+
Operation Operation `yaml:"-"`
40+
BackupFilePath string `yaml:"-"`
4041

4142
// if set the Name refers to an external resource. for files an absolute path is expected
4243
ExternalResource bool `yaml:"externalResource,omitempty"`

system/resource_manager.go

Lines changed: 126 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,35 +2,147 @@ package system
22

33
import (
44
"fmt"
5+
log "github.com/sirupsen/logrus"
56

67
xfs "github.com/saitho/golang-extended-fs/v2"
78
)
89

9-
func ApplyResourceOperation(resource Resource, ignoreBackup bool) (bool, error) {
10-
return PerformOperation(resource, ignoreBackup)
10+
func ApplyResourceOperation(resource *Resource, ignoreBackup bool) (bool, error) {
11+
if !ignoreBackup {
12+
// Backup existing file
13+
backupPath, err := backupResource(resource)
14+
if err != nil {
15+
return true, err
16+
}
17+
fmt.Println(backupPath)
18+
}
19+
return PerformOperation(resource)
1120
}
1221

13-
func RollbackResourceOperation(resource Resource, ignoreBackup bool) (bool, error) {
22+
func RollbackResourceOperation(resource *Resource, ignoreBackup bool) (bool, error) {
1423
if resource.Operation == OperationCreate {
1524
resource.Operation = OperationDelete
16-
return PerformOperation(resource, ignoreBackup)
25+
found, err := PerformOperation(resource)
26+
if err != nil {
27+
return found, err
28+
}
29+
if !ignoreBackup {
30+
// Restore backup
31+
if err = restoreBackup(resource); err != nil {
32+
return found, err
33+
}
34+
}
35+
return found, err
1736
}
1837
return true, fmt.Errorf(fmt.Sprintf("unupported rollback for operation %s", resource.Operation))
1938
}
2039

21-
func PerformOperation(resource Resource, ignoreBackup bool) (bool, error) {
40+
func backupResource(resource *Resource) (string, error) {
41+
// && resource.Type != TypeLink todo: make it available for symlinks again
42+
// issue with symlinks: cannot stat symlink: permission denied
43+
if resource.Type != TypeFile && resource.Type != TypeFolder {
44+
return "", nil
45+
}
46+
if !resource.ExternalResource {
47+
return "", nil
48+
}
49+
resourceFilePath, err := Context.CurrentDeployment.GetResourcePath(resource)
50+
if err != nil {
51+
return "", err
52+
}
53+
log.Info("Creating backup of resource " + resourceFilePath)
54+
backupFilePath := resourceFilePath + ".bak"
55+
xfsFilePath := "ssh://" + resourceFilePath
56+
switch resource.Type {
57+
case TypeFile:
58+
hasFile, err := xfs.HasFile(xfsFilePath)
59+
if err != nil {
60+
return "", fmt.Errorf("unable to check status of file %s: %s", resourceFilePath, err)
61+
}
62+
if !hasFile {
63+
return "", nil
64+
}
65+
if _, err = SimpleRemoteRun("cp", RemoteRunOpts{Args: []string{resourceFilePath, backupFilePath}}); err != nil {
66+
return backupFilePath, fmt.Errorf("unable to backup file %s: %s", resourceFilePath, err)
67+
}
68+
return backupFilePath, nil
69+
case TypeLink:
70+
hasFile, err := xfs.HasLink(xfsFilePath)
71+
if err != nil {
72+
return "", fmt.Errorf("unable to check status of link %s: %s", resourceFilePath, err)
73+
}
74+
if !hasFile {
75+
return "", nil
76+
}
77+
if _, err = SimpleRemoteRun("cp", RemoteRunOpts{Args: []string{resourceFilePath, backupFilePath}}); err != nil {
78+
return backupFilePath, fmt.Errorf("unable to backup link %s: %s", resourceFilePath, err)
79+
}
80+
return backupFilePath, nil
81+
case TypeFolder:
82+
hasFolder, err := xfs.HasFolder(xfsFilePath)
83+
if err != nil {
84+
return "", fmt.Errorf("unable to check status of folder %s: %s", resourceFilePath, err)
85+
}
86+
if !hasFolder {
87+
return "", nil
88+
}
89+
if _, err = SimpleRemoteRun("cp", RemoteRunOpts{Args: []string{"-R", resourceFilePath, backupFilePath}}); err != nil {
90+
return backupFilePath, fmt.Errorf("unable to backup folder %s: %s", resourceFilePath, err)
91+
}
92+
return backupFilePath, nil
93+
}
94+
return "", fmt.Errorf("unknown backup handler for resource type %s", resource.Type)
95+
}
96+
97+
func restoreBackup(resource *Resource) error {
98+
if resource.Type != TypeFile && resource.Type != TypeFolder && resource.Type != TypeLink {
99+
return nil
100+
}
101+
if resource.BackupFilePath == "" {
102+
return nil
103+
}
22104
resourceFilePath, _ := Context.CurrentDeployment.GetResourcePath(resource)
105+
xfsBackupFilePath := "ssh://" + resource.BackupFilePath
106+
log.Info("Restoring backup of resource " + resourceFilePath)
107+
108+
switch resource.Type {
109+
case TypeFile, TypeLink:
110+
hasFile, err := xfs.HasFile(xfsBackupFilePath)
111+
if err != nil {
112+
return err
113+
}
114+
if !hasFile {
115+
return fmt.Errorf("backup not found for " + resource.Name)
116+
}
117+
return xfs.CopyFile(xfsBackupFilePath, "ssh://"+resourceFilePath)
118+
case TypeFolder:
119+
backupFileName := "ssh://" + resourceFilePath + ".bak"
120+
hasFolder, err := xfs.HasFolder(backupFileName)
121+
if err != nil {
122+
return err
123+
}
124+
if !hasFolder {
125+
return fmt.Errorf("backup not found for " + resource.Name)
126+
}
127+
if _, err = SimpleRemoteRun("cp", RemoteRunOpts{Args: []string{"-R", backupFileName, resourceFilePath}}); err != nil {
128+
return err
129+
}
130+
return nil
131+
}
132+
return fmt.Errorf("unknown restore backup handler for resource type %s", resource.Type)
133+
}
134+
135+
func PerformOperation(resource *Resource) (bool, error) {
136+
resourceFilePath, _ := Context.CurrentDeployment.GetResourcePath(resource)
137+
xfsResourceFilePath := "ssh://" + resourceFilePath
23138
switch resource.Type {
24139
case TypeFile:
25140
if resource.Operation == OperationCreate {
26-
// TODO: backup if file exists
27-
if err := xfs.WriteFile("ssh://"+resourceFilePath, resource.Content); err != nil {
141+
if err := xfs.WriteFile(xfsResourceFilePath, resource.Content); err != nil {
28142
return true, fmt.Errorf("unable to create file at %s: %s", resource.Name, err)
29143
}
30144
} else if resource.Operation == OperationDelete {
31-
// TODO: restore backup if file exists
32-
resourcePath, _ := Context.CurrentDeployment.GetResourcePath(resource)
33-
if err := xfs.DeleteFile("ssh://" + resourcePath); err != nil {
145+
if err := xfs.DeleteFile(xfsResourceFilePath); err != nil {
34146
if err.Error() == "file does not exist" {
35147
return true, nil
36148
}
@@ -40,13 +152,11 @@ func PerformOperation(resource Resource, ignoreBackup bool) (bool, error) {
40152
return true, nil
41153
case TypeFolder:
42154
if resource.Operation == OperationCreate {
43-
// TODO: backup if file exists
44-
if err := xfs.CreateFolder("ssh://" + resourceFilePath); err != nil {
155+
if err := xfs.CreateFolder(xfsResourceFilePath); err != nil {
45156
return true, fmt.Errorf("unable to create folder at %s: %s", resource.Name, err)
46157
}
47158
} else if resource.Operation == OperationDelete {
48-
// TODO: restore backup if file exists
49-
if err := xfs.DeleteFolder("ssh://"+resourceFilePath, true); err != nil {
159+
if err := xfs.DeleteFolder(xfsResourceFilePath, true); err != nil {
50160
return true, fmt.Errorf("unable to remove folder at %s: %s", resource.Name, err)
51161
}
52162
}
@@ -61,14 +171,14 @@ func PerformOperation(resource Resource, ignoreBackup bool) (bool, error) {
61171
return true, fmt.Errorf("Unable to symlink " + resource.LinkSource + " -> " + resourceFilePath + ": " + err.Error())
62172
}
63173
} else if resource.Operation == OperationDelete {
64-
// TODO: restore backup if file exists
65-
if err := xfs.DeleteFile("ssh://" + resourceFilePath); err != nil {
174+
if err := xfs.DeleteFile(xfsResourceFilePath); err != nil {
66175
if err.Error() == "file does not exist" {
67176
return true, nil
68177
}
69178
return true, fmt.Errorf("unable to remove symlink at %s: %s", resource.Name, err)
70179
}
71180
}
181+
return true, nil
72182
}
73183
// CONTAINER via ResourceGroup (see StackHead container module)
74184
return false, nil

0 commit comments

Comments
 (0)