-
Notifications
You must be signed in to change notification settings - Fork 0
/
copycloud.go
188 lines (148 loc) · 5.18 KB
/
copycloud.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
package cmd
import (
"errors"
"fmt"
"io/fs"
"syscall"
"dario.cat/mergo"
"github.com/gophercloud/utils/openstack/clientconfig"
"github.com/mchlumsky/mracek/config"
"github.com/spf13/cobra"
"github.com/spf13/viper"
"golang.org/x/term"
"gopkg.in/yaml.v3"
)
func NewCopyCloudCommand() *cobra.Command {
cloudFlags := clientconfig.Cloud{AuthInfo: &clientconfig.AuthInfo{}, Verify: new(bool)}
cmd := &cobra.Command{
Use: "copy-cloud [flags] SOURCE-CLOUD DESTINATION-CLOUD",
Short: "Copy cloud",
Long: "Copy a source cloud into a new destination cloud while optionally overriding cloud properties",
Args: cobra.ExactArgs(2), //nolint:gomnd
RunE: copyCloudCommandRunE(&cloudFlags),
ValidArgsFunction: func() ValidArgsFunc {
return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
opts := config.YAMLOpts{Directory: viper.GetString("os-config-dir")}
return validArgsFunction(opts.AllCloudNames)(cmd, args, toComplete)
}
}(),
}
addAllFlags(cmd, &cloudFlags)
cmd.Flags().Bool("force", false, "force copying of source cloud over existing destination cloud")
return cmd
}
//nolint:funlen,gocognit,cyclop
func copyCloudCommandRunE(cloudFlags *clientconfig.Cloud) func(cmd *cobra.Command, args []string) error {
return func(cmd *cobra.Command, args []string) (err error) {
defer func() {
if err != nil {
err = fmt.Errorf("failed to copy cloud: %w", err)
}
}()
opts := config.YAMLOpts{Directory: viper.GetString("os-config-dir")}
co := clientconfig.ClientOpts{Cloud: args[0], YAMLOpts: opts}
// gets the cloud constructed from clouds.yaml+secure.yaml
cloud, err := clientconfig.GetCloudFromYAML(&co)
if err != nil {
if errors.Is(err, fs.ErrNotExist) {
return fmt.Errorf("try creating some clouds first with 'mracek create-cloud': %w", err)
}
return err
}
// override cloud's fields with flags that are not zero-valued
err = mergo.Merge(cloud, cloudFlags, mergo.WithOverride)
if err != nil {
return err
}
// only change the verify field if --verify is explicitly passed
// verify is a special case because it's a boolean, and it's zero value is false (a valid value for verify)
if isFlagPassed(cmd, "verify") {
v, err := cmd.Flags().GetBool("verify")
if err != nil {
return err
}
cloud.Verify = &v
}
// only change the allowReauth field if --allow-reauth is explicitly passed
// allowReauth is a special case because it's a boolean, and it's zero value is false (a valid value for
// allowReauth)
if isFlagPassed(cmd, "allow-reauth") {
ar, err := cmd.Flags().GetBool("allow-reauth")
if err != nil {
return err
}
cloud.AuthInfo.AllowReauth = ar
}
clouds, err := config.LoadAndCheckOSConfigfile("clouds.yaml", opts.LoadCloudsYAML, "")
if err != nil {
return err
}
if _, ok := clouds[args[1]]; ok { // destination cloud exists
if f, err := cmd.Flags().GetBool("force"); err == nil {
if !f {
return fmt.Errorf("cloud %s already exists, use --force to overwrite", args[1])
}
} else {
return err
}
}
// create or overwrite a new cloud in clouds.yaml with our newly constructed
// cloud struct. Anything that was in secure.yaml and didn't belong there is
// injected into clouds.yaml here.
clouds[args[1]] = *cloud
secure, err := config.LoadAndCheckOSConfigfile("secure.yaml", opts.LoadSecureCloudsYAML, "")
if err != nil {
return err
}
delete(secure, args[1])
if cloud.AuthInfo != nil {
secure[args[1]] = clientconfig.Cloud{AuthInfo: &clientconfig.AuthInfo{}}
}
// move password from clouds.yaml to secure.yaml
if cloud.AuthInfo.Password != "" {
secure[args[1]].AuthInfo.Password = cloud.AuthInfo.Password
cloud.AuthInfo.Password = ""
}
// override password if provided by the flag
if cloudFlags.AuthInfo.Password != "" {
secure[args[1]].AuthInfo.Password = cloudFlags.AuthInfo.Password
}
passPrompt, err := cmd.Flags().GetBool("password-prompt")
if err != nil {
return err
}
if passPrompt {
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "Enter password:")
bytepw, err := term.ReadPassword(syscall.Stdin)
if err != nil {
return err
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "\n")
secure[args[1]].AuthInfo.Password = string(bytepw)
}
// move application credential secret from clouds.yaml to secure.yaml
if cloud.AuthInfo.ApplicationCredentialSecret != "" {
secure[args[1]].AuthInfo.ApplicationCredentialSecret = cloud.AuthInfo.ApplicationCredentialSecret
cloud.AuthInfo.ApplicationCredentialSecret = ""
}
// override application credential secret if provided by the flag
if cloudFlags.AuthInfo.ApplicationCredentialSecret != "" {
secure[args[1]].AuthInfo.ApplicationCredentialSecret = cloudFlags.AuthInfo.ApplicationCredentialSecret
}
c := map[string]map[string]clientconfig.Cloud{"clouds": clouds}
cloudsOut, err := yaml.Marshal(&c)
if err != nil {
return err
}
s := map[string]map[string]clientconfig.Cloud{"clouds": secure}
secureOut, err := yaml.Marshal(&s)
if err != nil {
return err
}
err = config.WriteOSConfig(viper.GetString("os-config-dir"), cloudsOut, secureOut, nil)
if err != nil {
return err
}
return nil
}
}