This repository has been archived by the owner on Mar 25, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 8
/
export.go
225 lines (191 loc) · 6.27 KB
/
export.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
package cmd
import (
"fmt"
rancher "github.com/rancher/go-rancher/v2"
log "github.com/sirupsen/logrus"
"github.com/urfave/cli"
)
const (
warnMessage = `Some Rancher v1.x organizational concepts have changed in racher v2.x:
- environments -> projects
- stacks -> namespaces
- services -> workloads
To maintain same organization boundaries on rancher v2.x, is recommended to:
- create a separated project for every rancher v1.x environment.
- create a separated namespace for every rancher v1.x stack.
`
)
func ExportCommand() cli.Command {
return cli.Command{
Name: "export",
Usage: "Export compose files for every stack running on cattle environment on a Rancher v1.6 system",
Action: exportTool,
Flags: []cli.Flag{
cli.StringFlag{
Name: "url",
Usage: "Rancher API endpoint URL",
EnvVar: "RANCHER_URL",
},
cli.StringFlag{
Name: "access-key",
Usage: "Rancher API access key. Using admin API key will export stacks on all cattle environments",
EnvVar: "RANCHER_ACCESS_KEY",
},
cli.StringFlag{
Name: "secret-key",
Usage: "Rancher API secret key",
EnvVar: "RANCHER_SECRET_KEY",
},
cli.StringFlag{
Name: "export-dir",
Usage: "Base directory under which compose files will be exported under sub-directories created for every env/stack",
Value: "export",
},
cli.BoolFlag{
Name: "all,a",
Usage: "Export all stacks. Using this flag stacks with inactive, stopped and removing state, will also be exported",
},
cli.BoolFlag{
Name: "system,s",
Usage: "Export system and infrastructure stacks",
},
},
}
}
func exportTool(ctx *cli.Context) error {
log.Info("---Starting migration helper tool export command---")
opts, err := validateExportFlags(ctx)
if err != nil {
return err
}
log.Debugf("Connecting to Rancher server %s", opts.Url)
client, err := rancher.NewRancherClient(opts)
if err != nil {
return fmt.Errorf("[ERROR] Connecting to Rancher server %s: %v", opts.Url, err)
}
log.Debugf("Getting stacks list")
col, err := client.Stack.List(defaultListOpts(ctx))
if err != nil {
return fmt.Errorf("[ERROR] Getting stacks list: %v", err)
}
colData := col.Data
for {
col, _ = col.Next()
if col == nil {
break
}
colData = append(colData, col.Data...)
if !col.Pagination.Partial {
break
}
}
projectMap := make(map[string]string)
count := map[string]int{
"project": 0,
"stack": 0,
}
log.Debugf("Getting stacks compose files")
for _, item := range colData {
if projectMap[item.AccountId] == "" {
log.Debugf("Getting project ID %s for stack %s", item.AccountId, item.Name)
project, err := client.Project.ById(item.AccountId)
if err != nil {
return fmt.Errorf("[ERROR] Getting project ID %s: %v", item.AccountId, err)
}
if project.Orchestration == "cattle" {
projectMap[item.AccountId] = project.Name
count["project"]++
} else {
projectMap[item.AccountId] = "-"
}
}
// Continue if project orchestrator is nor cattle
if projectMap[item.AccountId] == "-" {
continue
}
count["stack"]++
// Get compose config for the stack
log.Debugf("Getting compose config for stack %s", item.Name)
composeInput := &rancher.ComposeConfigInput{
ServiceIds: item.ServiceIds,
}
composeConfig, err := client.Stack.ActionExportconfig(&item, composeInput)
if err != nil {
return fmt.Errorf("[ERROR] Getting compose config for stack %s: %v", item.Name, err)
}
// Create export dir like export-dir/environment/stack
path := ctx.String("export-dir") + "/" + projectMap[item.AccountId] + "/" + item.Name
log.Debugf("Creating export dir %s for stack %s", path, item.Name)
err = CreateDir(path)
if err != nil {
return fmt.Errorf("[ERROR] Creating export dir %s: %v", path, err)
}
// Saves docker-compose file
log.Debugf("Saving docker compose file %s/docker-compose.yml for stack %s", path, item.Name)
err = SaveFile(path+"/docker-compose.yml", []byte(composeConfig.DockerComposeConfig))
if err != nil {
return fmt.Errorf("[ERROR] Saving docker compose file %s/docker-compose.yml: %v", path, err)
}
//Save rancker-compose file
log.Debugf("Saving rancher compose file %s/rancher-compose.yml for stack %s", path, item.Name)
err = SaveFile(path+"/rancher-compose.yml", []byte(composeConfig.RancherComposeConfig))
if err != nil {
return fmt.Errorf("[ERROR] Saving rancher compose file %s/rancher-compose.yml: %v", path, err)
}
//Save README.md file with advice message
log.Debugf("Saving readme file %s/README.md for stack %s", path, item.Name)
message := warnMessage + "Project: " + projectMap[item.AccountId] + "\n"
message = message + "Namespace: " + item.Name + "\n"
err = SaveFile(path+"/README.md", []byte(message))
if err != nil {
return fmt.Errorf("[ERROR] Saving readme file %s/README.md : %v", path, err)
}
}
log.Infof("---Exported docker compose files for %d stacks from %d environment(s) on %s directory---", count["stack"], count["project"], ctx.String("export-dir"))
log.Infof("---Please check README.md file for Rancher 1.6 to Rancher 2.x organizational changes---")
return nil
}
func validateExportFlags(ctx *cli.Context) (*rancher.ClientOpts, error) {
log.Debugf("Validating flags")
if ctx.String("url") == "" || ctx.String("access-key") == "" || ctx.String("secret-key") == "" {
return nil, fmt.Errorf("[ERROR] url, access-key and secret-key must be provided")
}
ok, err := IsDirEmpty(ctx.String("export-dir"))
if err != nil {
return nil, fmt.Errorf("[ERROR] Accessing output directory %s: %v", ctx.String("export-dir"), err)
}
if !ok {
return nil, fmt.Errorf("[ERROR] Output dir %s is not empty", ctx.String("export-dir"))
}
opts := &rancher.ClientOpts{
Url: ctx.String("url"),
AccessKey: ctx.String("access-key"),
SecretKey: ctx.String("secret-key"),
}
return opts, nil
}
func defaultListOpts(ctx *cli.Context) *rancher.ListOpts {
listOpts := &rancher.ListOpts{
Filters: map[string]interface{}{
"limit": -2,
"all": true,
"system": false,
},
}
if ctx == nil {
return listOpts
}
if !ctx.Bool("all") {
listOpts.Filters["removed_null"] = "1"
listOpts.Filters["state_ne"] = []string{
"inactive",
"stopped",
"removing",
}
listOpts.Filters["all"] = false
}
if ctx.Bool("system") {
listOpts.Filters["system"] = true
}
return listOpts
}