Done with: ack '2022+' -l | xargs sed -i -e 's/2022+/2023+/g' Checked manually with: git add -p Hello to future James from 2024, and Happy Hacking!
396 lines
11 KiB
Go
396 lines
11 KiB
Go
// Mgmt
|
|
// Copyright (C) 2013-2023+ James Shubin and the project contributors
|
|
// Written by James Shubin <james@shubin.ca> and the project contributors
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU General Public License
|
|
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package lib
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
"sort"
|
|
|
|
"github.com/purpleidea/mgmt/gapi"
|
|
_ "github.com/purpleidea/mgmt/lang" // import so the GAPI registers
|
|
_ "github.com/purpleidea/mgmt/langpuppet"
|
|
_ "github.com/purpleidea/mgmt/puppet"
|
|
_ "github.com/purpleidea/mgmt/yamlgraph"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
)
|
|
|
|
// CLIArgs is a struct of values that we pass to the main CLI function.
|
|
type CLIArgs struct {
|
|
Program string
|
|
Version string
|
|
Copying string
|
|
Flags Flags
|
|
}
|
|
|
|
// CLI is the entry point for using mgmt normally from the CLI.
|
|
func CLI(cliArgs *CLIArgs) error {
|
|
// test for sanity
|
|
if cliArgs == nil {
|
|
return fmt.Errorf("this CLI was not run correctly")
|
|
}
|
|
if cliArgs.Program == "" || cliArgs.Version == "" {
|
|
return fmt.Errorf("program was not compiled correctly, see Makefile")
|
|
}
|
|
if cliArgs.Copying == "" {
|
|
return fmt.Errorf("program copyrights we're removed, can't run")
|
|
}
|
|
|
|
// All of these flags can be accessed in your GAPI implementation with
|
|
// the `c.Lineage()[1].Type` and `c.Lineage()[1].IsSet` functions. Their own
|
|
// flags can be accessed with `c.Type` and `c.IsSet` directly.
|
|
runFlags := []cli.Flag{
|
|
// common flags which all can use
|
|
|
|
// useful for testing multiple instances on same machine
|
|
&cli.StringFlag{
|
|
Name: "hostname",
|
|
Value: "",
|
|
Usage: "hostname to use",
|
|
},
|
|
|
|
&cli.StringFlag{
|
|
Name: "prefix",
|
|
Usage: "specify a path to the working prefix directory",
|
|
EnvVars: []string{"MGMT_PREFIX"},
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "tmp-prefix",
|
|
Usage: "request a pseudo-random, temporary prefix to be used",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "allow-tmp-prefix",
|
|
Usage: "allow creation of a new temporary prefix if main prefix is unavailable",
|
|
},
|
|
|
|
&cli.BoolFlag{
|
|
Name: "no-watch",
|
|
Usage: "do not update graph under any switch events",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "no-stream-watch",
|
|
Usage: "do not update graph on stream switch events",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "no-deploy-watch",
|
|
Usage: "do not change deploys after an initial deploy",
|
|
},
|
|
|
|
&cli.BoolFlag{
|
|
Name: "noop",
|
|
Usage: "globally force all resources into no-op mode",
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "sema",
|
|
Value: -1,
|
|
Usage: "globally add a semaphore to all resources with this lock count",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "graphviz, g",
|
|
Value: "",
|
|
Usage: "output file for graphviz data",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "graphviz-filter, gf",
|
|
Value: "",
|
|
Usage: "graphviz filter to use",
|
|
},
|
|
&cli.Int64Flag{
|
|
Name: "converged-timeout, t",
|
|
Value: -1,
|
|
Usage: "after approximately this many seconds without activity, we're considered to be in a converged state",
|
|
EnvVars: []string{"MGMT_CONVERGED_TIMEOUT"},
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "converged-timeout-no-exit",
|
|
Usage: "don't exit on converged-timeout",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "converged-status-file",
|
|
Value: "",
|
|
Usage: "file to append the current converged state to, mostly used for testing",
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "max-runtime",
|
|
Value: 0,
|
|
Usage: "exit after a maximum of approximately this many seconds",
|
|
EnvVars: []string{"MGMT_MAX_RUNTIME"},
|
|
},
|
|
|
|
// if empty, it will startup a new server
|
|
&cli.StringSliceFlag{
|
|
Name: "seeds, s",
|
|
Value: &cli.StringSlice{}, // empty slice
|
|
Usage: "default etc client endpoint",
|
|
EnvVars: []string{"MGMT_SEEDS"},
|
|
},
|
|
// port 2379 and 4001 are common
|
|
&cli.StringSliceFlag{
|
|
Name: "client-urls",
|
|
Value: &cli.StringSlice{},
|
|
Usage: "list of URLs to listen on for client traffic",
|
|
EnvVars: []string{"MGMT_CLIENT_URLS"},
|
|
},
|
|
// port 2380 and 7001 are common
|
|
&cli.StringSliceFlag{
|
|
Name: "server-urls, peer-urls",
|
|
Value: &cli.StringSlice{},
|
|
Usage: "list of URLs to listen on for server (peer) traffic",
|
|
EnvVars: []string{"MGMT_SERVER_URLS"},
|
|
},
|
|
// port 2379 and 4001 are common
|
|
&cli.StringSliceFlag{
|
|
Name: "advertise-client-urls",
|
|
Value: &cli.StringSlice{},
|
|
Usage: "list of URLs to listen on for client traffic",
|
|
EnvVars: []string{"MGMT_ADVERTISE_CLIENT_URLS"},
|
|
},
|
|
// port 2380 and 7001 are common
|
|
&cli.StringSliceFlag{
|
|
Name: "advertise-server-urls, advertise-peer-urls",
|
|
Value: &cli.StringSlice{},
|
|
Usage: "list of URLs to listen on for server (peer) traffic",
|
|
EnvVars: []string{"MGMT_ADVERTISE_SERVER_URLS"},
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "ideal-cluster-size",
|
|
Value: -1,
|
|
Usage: "ideal number of server peers in cluster; only read by initial server",
|
|
EnvVars: []string{"MGMT_IDEAL_CLUSTER_SIZE"},
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "no-server",
|
|
Usage: "do not start embedded etcd server (do not promote from client to peer)",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "no-network",
|
|
Usage: "run single node instance without clustering or opening tcp ports to the outside",
|
|
EnvVars: []string{"MGMT_NO_NETWORK"},
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "no-pgp",
|
|
Usage: "don't create pgp keys",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "pgp-key-path",
|
|
Value: "",
|
|
Usage: "path for instance key pair",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "pgp-identity",
|
|
Value: "",
|
|
Usage: "default identity used for generation",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "prometheus",
|
|
Usage: "start a prometheus instance",
|
|
},
|
|
&cli.StringFlag{
|
|
Name: "prometheus-listen",
|
|
Value: "",
|
|
Usage: "specify prometheus instance binding",
|
|
},
|
|
}
|
|
deployFlags := []cli.Flag{
|
|
// common flags which all can use
|
|
&cli.StringSliceFlag{
|
|
Name: "seeds, s",
|
|
Value: &cli.StringSlice{}, // empty slice
|
|
Usage: "default etc client endpoint",
|
|
EnvVars: []string{"MGMT_SEEDS"},
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "noop",
|
|
Usage: "globally force all resources into no-op mode",
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "sema",
|
|
Value: -1,
|
|
Usage: "globally add a semaphore to all resources with this lock count",
|
|
},
|
|
|
|
&cli.BoolFlag{
|
|
Name: "no-git",
|
|
Usage: "don't look at git commit id for safe deploys",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "force",
|
|
Usage: "force a new deploy, even if the safety chain would break",
|
|
},
|
|
}
|
|
getFlags := []cli.Flag{
|
|
// common flags which all can use
|
|
&cli.BoolFlag{
|
|
Name: "noop",
|
|
Usage: "simulate the download (can't recurse)",
|
|
},
|
|
&cli.IntFlag{
|
|
Name: "sema",
|
|
Value: -1, // maximum parallelism
|
|
Usage: "globally add a semaphore to downloads with this lock count",
|
|
},
|
|
&cli.BoolFlag{
|
|
Name: "update",
|
|
Usage: "update all dependencies to the latest versions",
|
|
},
|
|
}
|
|
|
|
subCommandsRun := []*cli.Command{} // run sub commands
|
|
subCommandsDeploy := []*cli.Command{} // deploy sub commands
|
|
subCommandsGet := []*cli.Command{} // get (download) sub commands
|
|
|
|
names := []string{}
|
|
for name := range gapi.RegisteredGAPIs {
|
|
names = append(names, name)
|
|
}
|
|
sort.Strings(names) // ensure deterministic order when parsing
|
|
for _, x := range names {
|
|
name := x // create a copy in this scope
|
|
fn := gapi.RegisteredGAPIs[name]
|
|
gapiObj := fn()
|
|
|
|
commandRun := &cli.Command{
|
|
Name: name,
|
|
Usage: fmt.Sprintf("run using the `%s` frontend", name),
|
|
Action: func(c *cli.Context) error {
|
|
if err := run(c, name, gapiObj); err != nil {
|
|
log.Printf("run: error: %v", err)
|
|
//return cli.NewExitError(err.Error(), 1) // TODO: ?
|
|
return cli.NewExitError("", 1)
|
|
}
|
|
return nil
|
|
},
|
|
Flags: gapiObj.CliFlags(gapi.CommandRun),
|
|
}
|
|
subCommandsRun = append(subCommandsRun, commandRun)
|
|
|
|
commandDeploy := &cli.Command{
|
|
Name: name,
|
|
Usage: fmt.Sprintf("deploy using the `%s` frontend", name),
|
|
Action: func(c *cli.Context) error {
|
|
if err := deploy(c, name, gapiObj); err != nil {
|
|
log.Printf("deploy: error: %v", err)
|
|
//return cli.NewExitError(err.Error(), 1) // TODO: ?
|
|
return cli.NewExitError("", 1)
|
|
}
|
|
return nil
|
|
},
|
|
Flags: gapiObj.CliFlags(gapi.CommandDeploy),
|
|
}
|
|
subCommandsDeploy = append(subCommandsDeploy, commandDeploy)
|
|
|
|
if _, ok := gapiObj.(gapi.GettableGAPI); ok {
|
|
commandGet := &cli.Command{
|
|
Name: name,
|
|
Usage: fmt.Sprintf("get (download) using the `%s` frontend", name),
|
|
Action: func(c *cli.Context) error {
|
|
if err := get(c, name, gapiObj); err != nil {
|
|
log.Printf("get: error: %v", err)
|
|
//return cli.NewExitError(err.Error(), 1) // TODO: ?
|
|
return cli.NewExitError("", 1)
|
|
}
|
|
return nil
|
|
},
|
|
Flags: gapiObj.CliFlags(gapi.CommandGet),
|
|
}
|
|
subCommandsGet = append(subCommandsGet, commandGet)
|
|
}
|
|
}
|
|
|
|
app := cli.NewApp()
|
|
app.Name = cliArgs.Program // App.name and App.version pass these values through
|
|
app.Version = cliArgs.Version
|
|
app.Usage = "next generation config management"
|
|
app.Metadata = map[string]interface{}{ // additional flags
|
|
"flags": cliArgs.Flags,
|
|
}
|
|
|
|
// if no app.Command is specified
|
|
app.Action = func(c *cli.Context) error {
|
|
// print the license
|
|
if c.Bool("license") {
|
|
fmt.Printf("%s", cliArgs.Copying)
|
|
return nil
|
|
}
|
|
|
|
// print help if no flags are set
|
|
cli.ShowAppHelp(c)
|
|
return nil
|
|
}
|
|
|
|
// global flags
|
|
app.Flags = []cli.Flag{
|
|
&cli.BoolFlag{
|
|
Name: "license",
|
|
Usage: "prints the software license",
|
|
},
|
|
}
|
|
|
|
app.Commands = []*cli.Command{
|
|
//{
|
|
// Name: gapi.CommandTODO,
|
|
// Aliases: []string{"TODO"},
|
|
// Usage: "TODO",
|
|
// Action: TODO,
|
|
// Flags: TODOFlags,
|
|
//},
|
|
}
|
|
|
|
// run always requires a frontend to start the engine, but if you don't
|
|
// want a graph, you can use the `empty` frontend. The engine backend is
|
|
// agnostic to which frontend is running, in fact, you can deploy with
|
|
// multiple different frontends, one after another, on the same engine.
|
|
if len(subCommandsRun) > 0 {
|
|
commandRun := &cli.Command{
|
|
Name: gapi.CommandRun,
|
|
Aliases: []string{"r"},
|
|
Usage: "Run code on this machine",
|
|
Subcommands: subCommandsRun,
|
|
Flags: runFlags,
|
|
}
|
|
app.Commands = append(app.Commands, commandRun)
|
|
}
|
|
|
|
if len(subCommandsDeploy) > 0 {
|
|
commandDeploy := &cli.Command{
|
|
Name: gapi.CommandDeploy,
|
|
Aliases: []string{"d"},
|
|
Usage: "Deploy code into the cluster",
|
|
Subcommands: subCommandsDeploy,
|
|
Flags: deployFlags,
|
|
}
|
|
app.Commands = append(app.Commands, commandDeploy)
|
|
}
|
|
|
|
if len(subCommandsGet) > 0 {
|
|
commandGet := &cli.Command{
|
|
Name: gapi.CommandGet,
|
|
Aliases: []string{"g"},
|
|
Usage: "Download code from the internet",
|
|
Subcommands: subCommandsGet,
|
|
Flags: getFlags,
|
|
}
|
|
app.Commands = append(app.Commands, commandGet)
|
|
}
|
|
|
|
app.EnableBashCompletion = true
|
|
return app.Run(os.Args)
|
|
}
|