-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathconf.go
296 lines (245 loc) · 8.99 KB
/
conf.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
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
// Copyright (c) 2020, Ben Morgan. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
package main
import (
"errors"
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"
"github.com/cassava/repoctl/conf"
"github.com/cassava/repoctl/internal/term"
"github.com/cassava/repoctl/pacman/alpm"
"github.com/goulash/osutil"
"github.com/spf13/cobra"
)
var (
confVar *conf.Configuration
confPath string
confShowTemplate bool
confEditorPath string
)
func init() {
MainCmd.AddCommand(confCmd)
confCmd.PersistentFlags().StringVarP(&confPath, "config", "c", conf.HomeConf(), "path to configuration file")
confCmd.AddCommand(confShowCmd)
confShowCmd.Flags().BoolVar(&confShowTemplate, "template", false, "show configuration as a TOML template")
confCmd.AddCommand(confEditCmd)
confEditCmd.Flags().StringVarP(&confEditorPath, "editor", "e", os.Getenv("EDITOR"), "editor path")
confCmd.AddCommand(confMigrateCmd)
confCmd.AddCommand(confNewCmd)
}
var (
// ErrInvalidConfPath is returned when an invalid path has been specified as
// the configuration file.
ErrInvalidConfPath = errors.New("invalid configuration path")
)
var confCmd = &cobra.Command{
Use: "conf {show | new | edit | migrate } [options]",
Short: "Create, edit, or show the repoctl configuration",
Long: `Create, edit, or show the repoctl configuration.
There are several places that repoctl reads its configuration from.
If $REPOCTL_CONFIG is set, then only this path is loaded. Otherwise,
the following paths are checked for repoctl/config.toml:
1. All the paths in $XDG_CONFIG_DIRS, where a colon ":" acts as
the separator. If $XDG_CONFIG_DIRS is not set or empty, then
it defaults to /etc/xdg.
2. The path given by $XDG_CONFIG_HOME. If $XDG_CONFIG_HOME is not
set, it defaults to $HOME/.config.
In most systems then, repoctl will read:
/etc/xdg/repoctl/config.toml
/home/you/.config/repoctl/config.toml
For repoctl to work, there must be at least one repository and one
configuration file. These can be created manually, or repoctl can
create them for you.
If you already have a repository, creating a new config is sufficient:
repoctl conf new /path/to/repository/database.db.tar.gz
Otherwise, make sure to run
repoctl reset
afterwards.
Repoctl supports multiple repository configuration through profiles.
You can add a profile by editing the configuration file manually:
repoctl conf edit
`,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
// Prevent errors that we print being printed a second time by cobra.
cmd.SilenceErrors = true
cmd.SilenceUsage = true
// This may end up reading the same data as Conf, but that's OK.
if ex, _ := osutil.Exists(confPath); ex {
var err error
confVar, err = conf.Read(confPath)
return err
}
// We are using the default configuration then, which is OK for
// certain operations, such as show, but not for others.
confVar = conf.Default()
return nil
},
}
var confShowCmd = &cobra.Command{
Use: "show",
Short: "Show current configuration",
Long: `Show current repoctl configuration.
If the configuration path is set to the empty string "", then this command
shows the default repoctl configuration. The default configuration is
sufficient for several commands, such as down or version.
`,
Args: cobra.ExactArgs(0),
ValidArgsFunction: completeNoFiles,
RunE: func(cmd *cobra.Command, args []string) error {
exceptQuiet()
if confShowTemplate {
confVar.WriteTemplate(os.Stdout)
} else {
confVar.WriteProperties(os.Stdout)
}
return nil
},
}
var confMigrateCmd = &cobra.Command{
Use: "migrate",
Short: "Migrate current configuration format to the latest one",
Long: `Migrate current configuration format to the latest format.
The configuration format has changed in version 0.21 to support multiple
repository profiles. This is a breaking change from previous versions, but
repoctl is able to understand old format versions if you have no need for
multiple profiles. If you want to take advantage of profiles, you can migrate
your old configuration file. Your previous configuration will be backed up.
Warning: This command will unconditionally rewrite the configuration file,
erasing any comments you have made in the file. It will do so even if the
configuration file does not need to be migrated!
`,
Args: cobra.ExactArgs(0),
ValidArgsFunction: completeNoFiles,
RunE: func(cmd *cobra.Command, args []string) error {
if ex, _ := osutil.Exists(confPath); !ex {
return fmt.Errorf("cannot migrate %s: file does not exist", confPath)
}
if ex, _ := osutil.FileExists(confPath); ex {
backup := generateBackupPath(confPath, ".bak")
term.Printf("Backing up current configuration to: %s\n", backup)
os.Rename(confPath, backup)
}
term.Printf("Writing new configuration file to: %s\n", confPath)
return confVar.WriteFile(confPath)
},
}
var confEditCmd = &cobra.Command{
Use: "edit",
Short: "Edit current configuration",
Long: `Edit repoctl configuration, creating it if necessary.
This command will launch your preferred editor with the file given as its
only argument. It will use the environment variable EDITOR as its default,
unless you specify an editor explicitely with the --editor flag.
If the configuration file does not exist, repoctl will create a template
for you.
`,
Args: cobra.ExactArgs(0),
ValidArgsFunction: completeNoFiles,
RunE: func(cmd *cobra.Command, args []string) error {
if confPath == "" {
return ErrInvalidConfPath
}
if confEditorPath == "" {
return fmt.Errorf("editor must be specified in $EDITOR or with -e flag")
}
// Create configuration if missing
if ex, _ := osutil.Exists(confPath); !ex {
term.Printf("Writing new configuration file to: %s\n", confPath)
err := newConfig(confPath, "")
if err != nil {
return fmt.Errorf("cannot create default config: %w", err)
}
}
// Launch editor
term.Debugf("Executing: %s %s\n", confEditorPath, confPath)
sys := exec.Command(confEditorPath, confPath)
sys.Stdin = os.Stdin
sys.Stdout = os.Stdout
sys.Stderr = os.Stderr
return sys.Run()
},
}
var confNewCmd = &cobra.Command{
Use: "new DBPATH",
Short: "Create a new configuration file",
Long: `Create a new initial configuration file.
The minimal configuration of repoctl is read from a configuration
file, which tells repoctl where your repositories are. The absolute
path to the repository database must be given as the only argument.
The database file specified must have an extension of one of:
- ".db.tar" - ".db.tar.gz"
- ".db.tar.xz" - ".db.tar.bz2"
- ".db.tar.zst"
The recommended database extension to use is ".db.tar.gz".
The default location to create a repoctl configuration file is in
your $XDG_CONFIG_HOME directory.
When creating a configuration file, repoctl will create a backup of
any existing files.
`,
Example: ` repoctl conf new /srv/abs/atlas.db.tar.gz
repoctl conf new -c /etc/xdg/repoctl/config.toml /srv/abs/atlas.db.tar.gz
REPOCTL_CONFIG=/etc/repoctl.conf repoctl conf new /srv/abs/atlas.db.tar.gz`,
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
if confPath == "" {
return ErrInvalidConfPath
}
// Check that the repo specified is according to the specifications.
r := args[0]
if !filepath.IsAbs(r) {
var err error
r, err = filepath.Abs(r)
if err != nil {
return err
}
}
if !alpm.HasDatabaseFormat(r) {
fmt.Fprintf(os.Stderr, "Warning: Specified repository database %q has an unexpected extension.\n", r)
fmt.Fprintf(os.Stderr, " It should conform to this pattern: .db.tar.(zst|xz|gz|bz2).\n")
base := filepath.Base(r)
if i := strings.IndexRune(base, '.'); i != -1 {
base = base[:i]
}
fmt.Fprintf(os.Stderr, " For example: %s.db.tar.zst\n", filepath.Join(filepath.Dir(r), base))
fmt.Fprintf(os.Stderr, "Warning: Continuing anyway.\n")
}
// Create a new configuration.
return newConfig(confPath, r)
},
}
func newConfig(file, repo string) error {
dir := filepath.Dir(file)
if ex, _ := osutil.DirExists(dir); !ex {
term.Debugf("Creating directory structure", dir, "...\n")
err := os.MkdirAll(dir, os.ModePerm)
if err != nil {
fmt.Fprintf(os.Stderr, "Warning: %s.\n", err)
}
}
if ex, _ := osutil.FileExists(file); ex {
backup := generateBackupPath(file, ".bak")
term.Printf("Backing up current configuration to: %s\n", backup)
os.Rename(file, backup)
}
term.Printf("Writing new configuration file at: %s\n", file)
if repo == "" {
return conf.Default().WriteFile(file)
}
return conf.New(repo).WriteFile(file)
}
func generateBackupPath(filepath string, suffix string) string {
backupPath := filepath + suffix
ex, _ := osutil.FileExists(backupPath)
if ex {
for i := 1; ex; i++ {
backupPath = filepath + suffix + "." + strconv.Itoa(i)
ex, _ = osutil.FileExists(backupPath)
}
}
return backupPath
}