• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

mendersoftware / mender-setup / 1928815316

17 Jun 2025 01:11PM UTC coverage: 31.588% (-68.4%) from 100.0%
1928815316

push

gitlab-ci

web-flow
Merge pull request #45 from mendersoftware/dependabot/go_modules/golang-dependencies-15ce89a103

chore: Bump the golang-dependencies group across 1 directory with 2 updates

2 of 7 new or added lines in 3 files covered. (28.57%)

362 of 1146 relevant lines covered (31.59%)

2.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/cli/cli.go
1
// Copyright 2023 Northern.tech AS
2
//
3
//        Licensed under the Apache License, Version 2.0 (the "License");
4
//        you may not use this file except in compliance with the License.
5
//        You may obtain a copy of the License at
6
//
7
//            http://www.apache.org/licenses/LICENSE-2.0
8
//
9
//        Unless required by applicable law or agreed to in writing, software
10
//        distributed under the License is distributed on an "AS IS" BASIS,
11
//        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
//        See the License for the specific language governing permissions and
13
//        limitations under the License.
14
package cli
15

16
import (
17
        "bytes"
18
        "fmt"
19
        "io"
20
        "os"
21
        "path"
22
        "runtime"
23
        "strings"
24

25
        log "github.com/sirupsen/logrus"
26

27
        "github.com/pkg/errors"
28
        "github.com/urfave/cli/v2"
29
        terminal "golang.org/x/term"
30

31
        setup_conf "github.com/mendersoftware/mender-setup/conf"
32

33
        "github.com/mendersoftware/mender-setup/conf"
34
)
35

36
const (
37
        appDescription = `mender-setup is a cli tool for generating the mender.conf` +
38
                ` configuration files, either through specifying the parameters to the CLI,` +
39
                `or through running it interactively`
40
)
41

42
const (
43
        errMsgAmbiguousArgumentsGivenF = "Ambiguous arguments given - " +
44
                "unrecognized argument: %s"
45
        errMsgConflictingArgumentsF = "Conflicting arguments given, only one " +
46
                "of the following flags may be given: {%q, %q}"
47
)
48

49
type runOptionsType struct {
50
        config         string
51
        fallbackConfig string
52
        dataStore      string
53
        conf.HttpConfig
54
        setupOptions setupOptionsType // Options for setup subcommand
55
        logOptions   logOptionsType   // Options for logging
56
}
57

58
func ShowVersion() string {
×
59
        return fmt.Sprintf("%s\truntime: %s",
×
60
                setup_conf.VersionString(), runtime.Version())
×
61
}
×
62

63
func SetupCLI(args []string) error {
×
64
        runOptions := &runOptionsType{}
×
65

×
66
        app := &cli.App{
×
67
                Description: appDescription,
×
68
                Name:        "mender-setup",
×
69
                Usage:       "Run to create a working mender-configuration file",
×
70
                ArgsUsage:   "[options]",
×
71
                Action:      runOptions.setupCLIHandler,
×
72
                Version:     ShowVersion(),
×
73
                Flags: []cli.Flag{
×
74
                        &cli.StringFlag{
×
75
                                Name:        "config",
×
76
                                Aliases:     []string{"c"},
×
77
                                Destination: &runOptions.setupOptions.configPath,
×
78
                                Value:       conf.DefaultConfFile,
×
79
                                Usage:       "`PATH` to configuration file.",
×
80
                        },
×
81
                        &cli.StringFlag{
×
82
                                Name:    "data",
×
83
                                Aliases: []string{"d"},
×
84
                                Usage:   "Mender state data `DIR`ECTORY path.",
×
85
                                Value:   conf.DefaultDataStore,
×
86
                        },
×
87
                        &cli.StringFlag{
×
88
                                Name:        "device-type",
×
89
                                Destination: &runOptions.setupOptions.deviceType,
×
90
                                Usage:       "Name of the device `type`.",
×
91
                        },
×
92
                        &cli.StringFlag{
×
93
                                Name:        "username",
×
94
                                Destination: &runOptions.setupOptions.username,
×
95
                                Usage:       "User `E-Mail` at hosted.mender.io.",
×
96
                        },
×
97
                        &cli.StringFlag{
×
98
                                Name:        "password",
×
99
                                Destination: &runOptions.setupOptions.password,
×
100
                                Usage:       "User `PASSWORD` at hosted.mender.io.",
×
101
                        },
×
102
                        &cli.StringFlag{
×
103
                                Name:        "server-url",
×
104
                                Aliases:     []string{"url"},
×
105
                                Destination: &runOptions.setupOptions.serverURL,
×
106
                                Usage:       "`URL` to Mender server.",
×
107
                                Value:       "https://docker.mender.io",
×
108
                        },
×
109
                        &cli.StringFlag{
×
110
                                Name:        "server-ip",
×
111
                                Destination: &runOptions.setupOptions.serverIP,
×
112
                                Usage:       "Server ip address.",
×
113
                        },
×
114
                        &cli.StringFlag{
×
115
                                Name:        "server-cert",
×
116
                                Aliases:     []string{"E"},
×
117
                                Destination: &runOptions.setupOptions.serverCert,
×
118
                                Usage:       "`PATH` to trusted server certificates",
×
119
                        },
×
120
                        &cli.StringFlag{
×
121
                                Name:        "tenant-token",
×
122
                                Destination: &runOptions.setupOptions.tenantToken,
×
123
                                Usage:       "Hosted Mender tenant `token`",
×
124
                        },
×
125
                        &cli.IntFlag{
×
126
                                Name:        "inventory-poll",
×
127
                                Destination: &runOptions.setupOptions.invPollInterval,
×
128
                                Usage:       "Inventory poll interval in `sec`onds.",
×
129
                                Value:       defaultInventoryPoll,
×
130
                        },
×
131
                        &cli.IntFlag{
×
132
                                Name:        "retry-poll",
×
133
                                Destination: &runOptions.setupOptions.retryPollInterval,
×
134
                                Usage:       "Retry poll interval in `sec`onds.",
×
135
                                Value:       defaultRetryPoll,
×
136
                        },
×
137
                        &cli.IntFlag{
×
138
                                Name:        "update-poll",
×
139
                                Destination: &runOptions.setupOptions.updatePollInterval,
×
140
                                Usage:       "Update poll interval in `sec`onds.",
×
141
                                Value:       defaultUpdatePoll,
×
142
                        },
×
143
                        &cli.BoolFlag{
×
144
                                Name:        "hosted-mender",
×
145
                                Destination: &runOptions.setupOptions.hostedMender,
×
146
                                Usage:       "Setup device towards Hosted Mender.",
×
147
                        },
×
148
                        &cli.BoolFlag{
×
149
                                Name:        "demo",
×
150
                                Destination: &runOptions.setupOptions.demo,
×
151
                                Usage: "Use demo configuration. DEPRECATED: use --demo-server and/or" +
×
152
                                        " --demo-polling instead",
×
153
                        },
×
154
                        &cli.BoolFlag{
×
155
                                Name:        "demo-server",
×
156
                                Destination: &runOptions.setupOptions.demoServer,
×
157
                                Usage:       "Use demo server configuration.",
×
158
                        },
×
159
                        &cli.BoolFlag{
×
160
                                Name:        "demo-polling",
×
161
                                Destination: &runOptions.setupOptions.demoIntervals,
×
162
                                Usage:       "Use demo polling intervals.",
×
163
                        },
×
164
                        &cli.BoolFlag{
×
165
                                Name:  "quiet",
×
166
                                Usage: "Suppress informative prompts.",
×
167
                        },
×
168
                        &cli.StringFlag{
×
169
                                Name:        "log-level",
×
170
                                Aliases:     []string{"l"},
×
171
                                Usage:       "Set logging `level`.",
×
172
                                Value:       "warning",
×
173
                                Destination: &runOptions.logOptions.logLevel,
×
174
                        },
×
175
                },
×
176
        }
×
177

×
178
        cli.HelpPrinter = upgradeHelpPrinter(cli.HelpPrinter)
×
179
        cli.VersionPrinter = func(c *cli.Context) {
×
180
                fmt.Fprintf(c.App.Writer, "%s\n", ShowVersion())
×
181
        }
×
182
        return app.Run(args)
×
183
}
184

185
func (runOptions *runOptionsType) commonCLIHandler(
186
        ctx *cli.Context) (*conf.MenderConfig, error) {
×
187

×
188
        log.Debug("commonCLIHandler config file: ", runOptions.config)
×
189

×
190
        // Handle config flags
×
191
        config, err := conf.LoadConfig(
×
192
                runOptions.config, runOptions.fallbackConfig)
×
193
        if err != nil {
×
194
                return nil, err
×
195
        }
×
196

197
        // Make sure that paths that are not configurable via the config file is consistent with
198
        // --data flag
199
        config.ArtifactScriptsPath = path.Join(runOptions.dataStore, "scripts")
×
200
        config.ModulesWorkPath = path.Join(runOptions.dataStore, "modules", "v3")
×
201

×
202
        // Checks if the DeviceTypeFile is defined in config file.
×
203
        if config.MenderConfigFromFile.DeviceTypeFile != "" {
×
204
                // Sets the config.DeviceTypeFile to the value in config file.
×
205
                config.DeviceTypeFile = config.MenderConfigFromFile.DeviceTypeFile
×
206

×
207
        } else {
×
208
                config.MenderConfigFromFile.DeviceTypeFile = path.Join(
×
209
                        runOptions.dataStore, "device_type")
×
210
                config.DeviceTypeFile = path.Join(
×
211
                        runOptions.dataStore, "device_type")
×
212
        }
×
213

214
        if runOptions.HttpConfig.NoVerify {
×
215
                config.SkipVerify = true
×
216
        }
×
217

218
        return config, nil
×
219
}
220

221
func (runOptions *runOptionsType) handleCLIOptions(ctx *cli.Context) error {
×
222
        config, err := runOptions.commonCLIHandler(ctx)
×
223
        if err != nil {
×
224
                return err
×
225
        }
×
226

227
        // Execute commands
228
        // switch ctx.Command.Name {
229

230
        // Check that user has permission to directories so that
231
        // the user doesn't have to perform the setup before raising
232
        // an error.
233
        log.Debug("handleCLIOptions config file: ", runOptions.config)
×
234
        if err = checkWritePermissions(path.Dir(runOptions.config)); err != nil {
×
235
                return err
×
236
        }
×
237
        log.Debug("handleCLIOptions dataStore file: ", runOptions.dataStore)
×
238
        if err = checkWritePermissions(runOptions.dataStore); err != nil {
×
239
                return err
×
240
        }
×
241
        // Run cli setup prompts.
242
        if err := doSetup(ctx, &config.MenderConfigFromFile,
×
243
                &runOptions.setupOptions); err != nil {
×
244
                return err
×
245
        }
×
246
        if !ctx.Bool("quiet") {
×
247
                fmt.Println(promptDone)
×
248
        }
×
249

250
        return err
×
251
}
252

253
func (runOptions *runOptionsType) setupCLIHandler(ctx *cli.Context) error {
×
254
        if ctx.Args().Len() > 0 {
×
255
                return errors.Errorf(
×
256
                        errMsgAmbiguousArgumentsGivenF,
×
257
                        ctx.Args().First())
×
258
        }
×
259

260
        if ctx.Bool("quiet") {
×
261
                log.SetLevel(log.ErrorLevel)
×
262
        } else {
×
263
                if lvl, err := log.ParseLevel(ctx.String("log-level")); err == nil {
×
264
                        log.SetLevel(lvl)
×
265
                } else {
×
266
                        log.Warnf(
×
267
                                "Failed to parse set log level '%s'.", ctx.String("log-level"))
×
268
                }
×
269
        }
270

271
        if err := runOptions.setupOptions.handleImplicitFlags(ctx); err != nil {
×
272
                return err
×
273
        }
×
274

275
        // Handle overlapping global flags
276
        if ctx.IsSet("config") && !ctx.IsSet("config") {
×
277
                runOptions.setupOptions.configPath = runOptions.config
×
278
        } else {
×
279
                runOptions.config = runOptions.setupOptions.configPath
×
280
        }
×
281
        runOptions.dataStore = ctx.String("data")
×
282
        if runOptions.HttpConfig.ServerCert != "" &&
×
283
                runOptions.setupOptions.serverCert == "" {
×
284
                runOptions.setupOptions.serverCert = runOptions.HttpConfig.ServerCert
×
285
        } else {
×
286
                runOptions.HttpConfig.ServerCert = runOptions.setupOptions.serverCert
×
287
        }
×
288
        return runOptions.handleCLIOptions(ctx)
×
289
}
290

291
func upgradeHelpPrinter(defaultPrinter func(w io.Writer, templ string, data interface{})) func(
292
        w io.Writer, templ string, data interface{}) {
×
293
        // Applies the ordinary help printer with column post processing
×
294
        return func(stdout io.Writer, templ string, data interface{}) {
×
295
                // Need at least 10 characters for last column in order to
×
296
                // pretty print; otherwise the output is unreadable.
×
297
                const minColumnWidth = 10
×
298
                isLowerCase := func(c rune) bool {
×
299
                        // returns true if c in [a-z] else false
×
300
                        asciiVal := int(c)
×
301
                        if asciiVal >= 0x61 && asciiVal <= 0x7A {
×
302
                                return true
×
303
                        }
×
304
                        return false
×
305
                }
306
                // defaultPrinter parses the text-template and outputs to buffer
307
                var buf bytes.Buffer
×
308
                defaultPrinter(&buf, templ, data)
×
309
                terminalWidth, _, err := terminal.GetSize(int(os.Stdout.Fd()))
×
310
                if err != nil || terminalWidth <= 0 {
×
311
                        // Just write help as is.
×
312
                        stdout.Write(buf.Bytes())
×
313
                        return
×
314
                }
×
315
                for line, err := buf.ReadString('\n'); err == nil; line, err = buf.ReadString('\n') {
×
316
                        if len(line) <= terminalWidth+1 {
×
317
                                stdout.Write([]byte(line))
×
318
                                continue
×
319
                        }
320
                        newLine := line
×
321
                        indent := strings.LastIndex(
×
322
                                line[:terminalWidth], "  ")
×
323
                        // find indentation of last column
×
324
                        if indent == -1 {
×
325
                                indent = 0
×
326
                        }
×
327
                        indent += strings.IndexFunc(
×
328
                                strings.ToLower(line[indent:]), isLowerCase) - 1
×
329
                        if indent >= terminalWidth-minColumnWidth ||
×
330
                                indent == -1 {
×
331
                                indent = 0
×
332
                        }
×
333
                        // Format the last column to be aligned
334
                        for len(newLine) > terminalWidth {
×
335
                                // find word to insert newline
×
336
                                idx := strings.LastIndex(newLine[:terminalWidth], " ")
×
337
                                if idx == indent || idx == -1 {
×
338
                                        idx = terminalWidth
×
339
                                }
×
340
                                stdout.Write([]byte(newLine[:idx] + "\n"))
×
341
                                newLine = newLine[idx:]
×
342
                                newLine = strings.Repeat(" ", indent) + newLine
×
343
                        }
344
                        stdout.Write([]byte(newLine))
×
345
                }
346
                if err != nil {
×
347
                        log.Fatalf("CLI HELP: error writing help string: %v\n", err)
×
348
                }
×
349
        }
350
}
351

352
func checkWritePermissions(dir string) error {
×
353
        log.Debug("Checking the permissions for: ", dir)
×
354
        _, err := os.Stat(dir)
×
355
        if os.IsNotExist(err) {
×
356
                err := os.MkdirAll(dir, 0755)
×
357
                if err != nil {
×
358
                        return errors.Wrapf(err, "Error creating "+
×
359
                                "directory %q", dir)
×
360
                }
×
361
        } else if os.IsPermission(err) {
×
362
                return errors.Wrapf(os.ErrPermission,
×
363
                        "Error trying to stat directory %q", dir)
×
364
        } else if err != nil {
×
365
                return errors.Errorf("Error trying to stat directory %q", dir)
×
366
        }
×
NEW
367
        f, err := os.MkdirTemp(dir, "temporaryFile")
×
368
        if os.IsPermission(err) {
×
369
                return errors.Wrapf(err, "User does not have "+
×
370
                        "permission to write to data store "+
×
371
                        "directory %q", dir)
×
372
        } else if err != nil {
×
373
                return errors.Wrapf(err,
×
374
                        "Error checking write permissions to "+
×
375
                                "directory %q", dir)
×
376
        }
×
NEW
377
        os.Remove(f)
×
378
        return nil
×
379
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc