lib: Split off the CLI portions into a separate package
This cleans things up a bit more and forces the lib package to not contain any accidental CLI parsing code.
This commit is contained in:
406
lib/cli.go
406
lib/cli.go
@@ -1,406 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ 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)
|
||||
}
|
||||
|
||||
commandEtcd := &cli.Command{
|
||||
Name: "etcd",
|
||||
//Aliases: []string{"e"},
|
||||
Usage: "Run standalone etcd",
|
||||
Action: func(*cli.Context) error {
|
||||
// this never runs, it gets preempted in the real main()
|
||||
return nil
|
||||
},
|
||||
}
|
||||
app.Commands = append(app.Commands, commandEtcd)
|
||||
|
||||
app.EnableBashCompletion = true
|
||||
return app.Run(os.Args)
|
||||
}
|
||||
200
lib/deploy.go
200
lib/deploy.go
@@ -1,200 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ 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 (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/purpleidea/mgmt/etcd/client"
|
||||
"github.com/purpleidea/mgmt/etcd/deployer"
|
||||
etcdfs "github.com/purpleidea/mgmt/etcd/fs"
|
||||
"github.com/purpleidea/mgmt/gapi"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
|
||||
"github.com/pborman/uuid"
|
||||
"github.com/urfave/cli/v2"
|
||||
git "gopkg.in/src-d/go-git.v4"
|
||||
)
|
||||
|
||||
const (
|
||||
// MetadataPrefix is the etcd prefix where all our fs superblocks live.
|
||||
MetadataPrefix = "/fs"
|
||||
// StoragePrefix is the etcd prefix where all our fs data lives.
|
||||
StoragePrefix = "/storage"
|
||||
)
|
||||
|
||||
// deploy is the cli target to manage deploys to our cluster.
|
||||
// TODO: add a timeout and/or cancel signal to replace context.TODO()
|
||||
func deploy(c *cli.Context, name string, gapiObj gapi.GAPI) error {
|
||||
cliContext := c.Lineage()[1]
|
||||
if cliContext == nil {
|
||||
return fmt.Errorf("could not get cli context")
|
||||
}
|
||||
|
||||
program, version := safeProgram(c.App.Name), c.App.Version
|
||||
var flags Flags
|
||||
var debug bool
|
||||
if val, exists := c.App.Metadata["flags"]; exists {
|
||||
if f, ok := val.(Flags); ok {
|
||||
flags = f
|
||||
debug = flags.Debug
|
||||
}
|
||||
}
|
||||
Logf := func(format string, v ...interface{}) {
|
||||
log.Printf("deploy: "+format, v...)
|
||||
}
|
||||
|
||||
hello(program, version, flags) // say hello!
|
||||
defer Logf("goodbye!")
|
||||
|
||||
var hash, pHash string
|
||||
if !cliContext.Bool("no-git") {
|
||||
wd, err := os.Getwd()
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not get current working directory")
|
||||
}
|
||||
repo, err := git.PlainOpen(wd)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not open git repo")
|
||||
}
|
||||
|
||||
head, err := repo.Head()
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not read git HEAD")
|
||||
}
|
||||
|
||||
hash = head.Hash().String() // current commit id
|
||||
Logf("hash: %s", hash)
|
||||
|
||||
lo := &git.LogOptions{
|
||||
From: head.Hash(),
|
||||
}
|
||||
commits, err := repo.Log(lo)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "could not read git log")
|
||||
}
|
||||
if _, err := commits.Next(); err != nil { // skip over HEAD
|
||||
return errwrap.Wrapf(err, "could not read HEAD in git log") // weird!
|
||||
}
|
||||
commit, err := commits.Next()
|
||||
if err == nil { // errors are okay, we might be empty
|
||||
pHash = commit.Hash.String() // previous commit id
|
||||
}
|
||||
Logf("previous deploy hash: %s", pHash)
|
||||
if cliContext.Bool("force") {
|
||||
pHash = "" // don't check this :(
|
||||
}
|
||||
if hash == "" {
|
||||
return errwrap.Wrapf(err, "could not get git deploy hash")
|
||||
}
|
||||
}
|
||||
|
||||
uniqueid := uuid.New() // panic's if it can't generate one :P
|
||||
|
||||
etcdClient := client.NewClientFromSeedsNamespace(
|
||||
cliContext.StringSlice("seeds"), // endpoints
|
||||
NS,
|
||||
)
|
||||
if err := etcdClient.Init(); err != nil {
|
||||
return errwrap.Wrapf(err, "client Init failed")
|
||||
}
|
||||
defer func() {
|
||||
err := errwrap.Wrapf(etcdClient.Close(), "client Close failed")
|
||||
if err != nil {
|
||||
// TODO: cause the final exit code to be non-zero
|
||||
Logf("client cleanup error: %+v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
simpleDeploy := &deployer.SimpleDeploy{
|
||||
Client: etcdClient,
|
||||
Debug: debug,
|
||||
Logf: func(format string, v ...interface{}) {
|
||||
Logf("deploy: "+format, v...)
|
||||
},
|
||||
}
|
||||
if err := simpleDeploy.Init(); err != nil {
|
||||
return errwrap.Wrapf(err, "deploy Init failed")
|
||||
}
|
||||
defer func() {
|
||||
err := errwrap.Wrapf(simpleDeploy.Close(), "deploy Close failed")
|
||||
if err != nil {
|
||||
// TODO: cause the final exit code to be non-zero
|
||||
Logf("deploy cleanup error: %+v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
// get max id (from all the previous deploys)
|
||||
max, err := simpleDeploy.GetMaxDeployID(context.TODO())
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "error getting max deploy id")
|
||||
}
|
||||
// find the latest id
|
||||
var id = max + 1 // next id
|
||||
Logf("previous max deploy id: %d", max)
|
||||
|
||||
etcdFs := &etcdfs.Fs{
|
||||
Client: etcdClient,
|
||||
// TODO: using a uuid is meant as a temporary measure, i hate them
|
||||
Metadata: MetadataPrefix + fmt.Sprintf("/deploy/%d-%s", id, uniqueid),
|
||||
DataPrefix: StoragePrefix,
|
||||
|
||||
Debug: debug,
|
||||
Logf: func(format string, v ...interface{}) {
|
||||
Logf("fs: "+format, v...)
|
||||
},
|
||||
}
|
||||
|
||||
cliInfo := &gapi.CliInfo{
|
||||
CliContext: c, // don't pass in the parent context
|
||||
|
||||
Fs: etcdFs,
|
||||
Debug: debug,
|
||||
Logf: func(format string, v ...interface{}) {
|
||||
// TODO: is this a sane prefix to use here?
|
||||
log.Printf("cli: "+format, v...)
|
||||
},
|
||||
}
|
||||
|
||||
deploy, err := gapiObj.Cli(cliInfo)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "cli parse error")
|
||||
}
|
||||
if deploy == nil { // not used
|
||||
return fmt.Errorf("not enough information specified")
|
||||
}
|
||||
|
||||
// redundant
|
||||
deploy.Noop = cliContext.Bool("noop")
|
||||
deploy.Sema = cliContext.Int("sema")
|
||||
|
||||
str, err := deploy.ToB64()
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "encoding error")
|
||||
}
|
||||
|
||||
// this nominally checks the previous git hash matches our expectation
|
||||
if err := simpleDeploy.AddDeploy(context.TODO(), id, hash, pHash, &str); err != nil {
|
||||
return errwrap.Wrapf(err, "could not create deploy id `%d`", id)
|
||||
}
|
||||
Logf("success, id: %d", id)
|
||||
return nil
|
||||
}
|
||||
73
lib/get.go
73
lib/get.go
@@ -1,73 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ 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"
|
||||
|
||||
"github.com/purpleidea/mgmt/gapi"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// get is the cli target to run code/import downloads.
|
||||
func get(c *cli.Context, name string, gapiObj gapi.GAPI) error {
|
||||
cliContext := c.Lineage()[1]
|
||||
if cliContext == nil {
|
||||
return fmt.Errorf("could not get cli context")
|
||||
}
|
||||
|
||||
program, version := safeProgram(c.App.Name), c.App.Version
|
||||
var flags Flags
|
||||
var debug bool
|
||||
if val, exists := c.App.Metadata["flags"]; exists {
|
||||
if f, ok := val.(Flags); ok {
|
||||
flags = f
|
||||
debug = flags.Debug
|
||||
}
|
||||
}
|
||||
hello(program, version, flags) // say hello!
|
||||
|
||||
gettable, ok := gapiObj.(gapi.GettableGAPI)
|
||||
if !ok {
|
||||
// this is a programming bug as this should not get called...
|
||||
return fmt.Errorf("the `%s` GAPI does not implement: %s", name, gapi.CommandGet)
|
||||
}
|
||||
|
||||
getInfo := &gapi.GetInfo{
|
||||
CliContext: c, // don't pass in the parent context
|
||||
|
||||
Noop: cliContext.Bool("noop"),
|
||||
Sema: cliContext.Int("sema"),
|
||||
Update: cliContext.Bool("update"),
|
||||
|
||||
Debug: debug,
|
||||
Logf: func(format string, v ...interface{}) {
|
||||
// TODO: is this a sane prefix to use here?
|
||||
log.Printf(name+": "+format, v...)
|
||||
},
|
||||
}
|
||||
|
||||
if err := gettable.Get(getInfo); err != nil {
|
||||
return err // no need to errwrap here
|
||||
}
|
||||
|
||||
log.Printf("%s: success!", name)
|
||||
return nil
|
||||
}
|
||||
46
lib/hello.go
46
lib/hello.go
@@ -1,46 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ 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"
|
||||
"time"
|
||||
)
|
||||
|
||||
func hello(program, version string, flags Flags) {
|
||||
var start = time.Now().UnixNano()
|
||||
|
||||
logFlags := log.LstdFlags
|
||||
if flags.Debug {
|
||||
logFlags = logFlags + log.Lshortfile
|
||||
}
|
||||
logFlags = logFlags - log.Ldate // remove the date for now
|
||||
log.SetFlags(logFlags)
|
||||
|
||||
log.SetOutput(os.Stderr)
|
||||
|
||||
if program == "" {
|
||||
program = "<unknown>"
|
||||
}
|
||||
fmt.Println(fmt.Sprintf("This is: %s, version: %s", program, version))
|
||||
fmt.Println("Copyright (C) 2013-2024+ James Shubin and the project contributors")
|
||||
fmt.Println("Written by James Shubin <james@shubin.ca> and the project contributors")
|
||||
log.Printf("main: start: %v", start)
|
||||
}
|
||||
@@ -55,6 +55,12 @@ import (
|
||||
const (
|
||||
// NS is the root namespace for etcd operations. All keys must use it!
|
||||
NS = "/_mgmt" // must not end with a slash!
|
||||
|
||||
// MetadataPrefix is the etcd prefix where all our fs superblocks live.
|
||||
MetadataPrefix = "/fs"
|
||||
|
||||
// StoragePrefix is the etcd prefix where all our fs data lives.
|
||||
StoragePrefix = "/storage"
|
||||
)
|
||||
|
||||
// Flags are some constant flags which are used throughout the program.
|
||||
@@ -202,9 +208,6 @@ func (obj *Main) Run() error {
|
||||
log.Printf("main: "+format, v...)
|
||||
}
|
||||
|
||||
hello(obj.Program, obj.Version, obj.Flags) // say hello!
|
||||
defer Logf("goodbye!")
|
||||
|
||||
exitCtx := obj.exit.Context() // local exit signal
|
||||
defer obj.exit.Done(nil) // ensure this gets called even if Exit doesn't
|
||||
|
||||
|
||||
196
lib/run.go
196
lib/run.go
@@ -1,196 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ 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"
|
||||
"os/signal"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"github.com/purpleidea/mgmt/gapi"
|
||||
"github.com/purpleidea/mgmt/util"
|
||||
"github.com/purpleidea/mgmt/util/errwrap"
|
||||
|
||||
"github.com/spf13/afero"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// run is the main run target.
|
||||
func run(c *cli.Context, name string, gapiObj gapi.GAPI) error {
|
||||
cliContext := c.Lineage()[1] // these are the flags from `run`
|
||||
if cliContext == nil {
|
||||
return fmt.Errorf("could not get cli context")
|
||||
}
|
||||
|
||||
obj := &Main{}
|
||||
|
||||
obj.Program, obj.Version = safeProgram(c.App.Name), c.App.Version
|
||||
if val, exists := c.App.Metadata["flags"]; exists {
|
||||
if flags, ok := val.(Flags); ok {
|
||||
obj.Flags = flags
|
||||
}
|
||||
}
|
||||
|
||||
if h := cliContext.String("hostname"); cliContext.IsSet("hostname") && h != "" {
|
||||
obj.Hostname = &h
|
||||
}
|
||||
|
||||
if s := cliContext.String("prefix"); cliContext.IsSet("prefix") && s != "" {
|
||||
obj.Prefix = &s
|
||||
}
|
||||
obj.TmpPrefix = cliContext.Bool("tmp-prefix")
|
||||
obj.AllowTmpPrefix = cliContext.Bool("allow-tmp-prefix")
|
||||
|
||||
// create a memory backed temporary filesystem for storing runtime data
|
||||
mmFs := afero.NewMemMapFs()
|
||||
afs := &afero.Afero{Fs: mmFs} // wrap so that we're implementing ioutil
|
||||
standaloneFs := &util.AferoFs{Afero: afs}
|
||||
obj.DeployFs = standaloneFs
|
||||
|
||||
cliInfo := &gapi.CliInfo{
|
||||
CliContext: c, // don't pass in the parent context
|
||||
|
||||
Fs: standaloneFs,
|
||||
Debug: obj.Flags.Debug,
|
||||
Logf: func(format string, v ...interface{}) {
|
||||
log.Printf("cli: "+format, v...)
|
||||
},
|
||||
}
|
||||
|
||||
deploy, err := gapiObj.Cli(cliInfo)
|
||||
if err != nil {
|
||||
return errwrap.Wrapf(err, "cli parse error")
|
||||
}
|
||||
if c.Bool("only-unify") && deploy == nil {
|
||||
return nil // we end early
|
||||
}
|
||||
obj.Deploy = deploy
|
||||
if obj.Deploy == nil {
|
||||
// nobody activated, but we'll still watch the etcd deploy chan,
|
||||
// and if there is deployed code that's ready to run, we'll run!
|
||||
log.Printf("main: no frontend selected (no GAPI activated)")
|
||||
}
|
||||
|
||||
obj.NoWatch = cliContext.Bool("no-watch")
|
||||
obj.NoStreamWatch = cliContext.Bool("no-stream-watch")
|
||||
obj.NoDeployWatch = cliContext.Bool("no-deploy-watch")
|
||||
|
||||
obj.Noop = cliContext.Bool("noop")
|
||||
obj.Sema = cliContext.Int("sema")
|
||||
obj.Graphviz = cliContext.String("graphviz")
|
||||
obj.GraphvizFilter = cliContext.String("graphviz-filter")
|
||||
obj.ConvergedTimeout = cliContext.Int64("converged-timeout")
|
||||
obj.ConvergedTimeoutNoExit = cliContext.Bool("converged-timeout-no-exit")
|
||||
obj.ConvergedStatusFile = cliContext.String("converged-status-file")
|
||||
obj.MaxRuntime = uint(cliContext.Int("max-runtime"))
|
||||
|
||||
obj.Seeds = cliContext.StringSlice("seeds")
|
||||
obj.ClientURLs = cliContext.StringSlice("client-urls")
|
||||
obj.ServerURLs = cliContext.StringSlice("server-urls")
|
||||
obj.AdvertiseClientURLs = cliContext.StringSlice("advertise-client-urls")
|
||||
obj.AdvertiseServerURLs = cliContext.StringSlice("advertise-server-urls")
|
||||
obj.IdealClusterSize = cliContext.Int("ideal-cluster-size")
|
||||
obj.NoServer = cliContext.Bool("no-server")
|
||||
obj.NoNetwork = cliContext.Bool("no-network")
|
||||
|
||||
obj.NoPgp = cliContext.Bool("no-pgp")
|
||||
|
||||
if kp := cliContext.String("pgp-key-path"); cliContext.IsSet("pgp-key-path") {
|
||||
obj.PgpKeyPath = &kp
|
||||
}
|
||||
|
||||
if us := cliContext.String("pgp-identity"); cliContext.IsSet("pgp-identity") {
|
||||
obj.PgpIdentity = &us
|
||||
}
|
||||
|
||||
obj.Prometheus = cliContext.Bool("prometheus")
|
||||
obj.PrometheusListen = cliContext.String("prometheus-listen")
|
||||
|
||||
if err := obj.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := obj.Init(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// install the exit signal handler
|
||||
wg := &sync.WaitGroup{}
|
||||
defer wg.Wait()
|
||||
exit := make(chan struct{})
|
||||
defer close(exit)
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
// must have buffer for max number of signals
|
||||
signals := make(chan os.Signal, 3+1) // 3 * ^C + 1 * SIGTERM
|
||||
signal.Notify(signals, os.Interrupt) // catch ^C
|
||||
//signal.Notify(signals, os.Kill) // catch signals
|
||||
signal.Notify(signals, syscall.SIGTERM)
|
||||
var count uint8
|
||||
for {
|
||||
select {
|
||||
case sig := <-signals: // any signal will do
|
||||
if sig != os.Interrupt {
|
||||
log.Printf("interrupted by signal")
|
||||
obj.Interrupt(fmt.Errorf("killed by %v", sig))
|
||||
return
|
||||
}
|
||||
|
||||
switch count {
|
||||
case 0:
|
||||
log.Printf("interrupted by ^C")
|
||||
obj.Exit(nil)
|
||||
case 1:
|
||||
log.Printf("interrupted by ^C (fast pause)")
|
||||
obj.FastExit(nil)
|
||||
case 2:
|
||||
log.Printf("interrupted by ^C (hard interrupt)")
|
||||
obj.Interrupt(nil)
|
||||
}
|
||||
count++
|
||||
|
||||
case <-exit:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
reterr := obj.Run()
|
||||
if reterr != nil {
|
||||
// log the error message returned
|
||||
if obj.Flags.Debug {
|
||||
log.Printf("main: %+v", reterr)
|
||||
}
|
||||
}
|
||||
|
||||
if err := obj.Close(); err != nil {
|
||||
if obj.Flags.Debug {
|
||||
log.Printf("main: Close: %+v", err)
|
||||
}
|
||||
if reterr == nil {
|
||||
return err
|
||||
}
|
||||
reterr = errwrap.Append(reterr, err)
|
||||
}
|
||||
|
||||
return reterr
|
||||
}
|
||||
35
lib/util.go
35
lib/util.go
@@ -1,35 +0,0 @@
|
||||
// Mgmt
|
||||
// Copyright (C) 2013-2024+ 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 (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// safeProgram returns the correct program string when given a buggy variant.
|
||||
func safeProgram(program string) string {
|
||||
// FIXME: in sub commands, the cli package appends a space and the sub
|
||||
// command name at the end. hack around this by only using the first bit
|
||||
// see: https://github.com/urfave/cli/issues/783 for more details...
|
||||
split := strings.Split(program, " ")
|
||||
program = split[0]
|
||||
//if program == "" {
|
||||
// program = "<unknown>"
|
||||
//}
|
||||
return program
|
||||
}
|
||||
Reference in New Issue
Block a user