cli, lib, lang: Port to new cli library
The new version of the urfave/cli library is moving to generics, and it's completely unclear to me why this is an improvement. Their new API is very complicated to understand, which for me, defeats the purpose of golang. In parallel, I needed to do some upcoming cli API refactoring, so this was a good time to look into new libraries. After a review of the landscape, I found the alexflint/go-arg library which has a delightfully elegant API. It does have a few rough edges, but it's otherwise very usable, and I think it would be straightforward to add features and fix issues. Thanks Alex!
This commit is contained in:
442
cli/cli.go
442
cli/cli.go
@@ -21,379 +21,127 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"os"
|
||||||
"sort"
|
|
||||||
|
|
||||||
cliUtil "github.com/purpleidea/mgmt/cli/util"
|
cliUtil "github.com/purpleidea/mgmt/cli/util"
|
||||||
"github.com/purpleidea/mgmt/gapi"
|
"github.com/purpleidea/mgmt/util/errwrap"
|
||||||
_ "github.com/purpleidea/mgmt/lang" // import so the GAPI registers
|
|
||||||
_ "github.com/purpleidea/mgmt/yamlgraph"
|
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
"github.com/alexflint/go-arg"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CLI is the entry point for using mgmt normally from the CLI.
|
// CLI is the entry point for using mgmt normally from the CLI.
|
||||||
func CLI(data *cliUtil.Data) error {
|
func CLI(ctx context.Context, data *cliUtil.Data) error {
|
||||||
// test for sanity
|
// test for sanity
|
||||||
if data == nil {
|
if data == nil {
|
||||||
return fmt.Errorf("this CLI was not run correctly")
|
return fmt.Errorf("this CLI was not run correctly")
|
||||||
}
|
}
|
||||||
if data.Program == "" || data.Version == "" {
|
if data.Program == "" || data.Version == "" {
|
||||||
return fmt.Errorf("program was not compiled correctly, see Makefile")
|
return fmt.Errorf("program was not compiled correctly")
|
||||||
}
|
}
|
||||||
if data.Copying == "" {
|
if data.Copying == "" {
|
||||||
return fmt.Errorf("program copyrights were removed, can't run")
|
return fmt.Errorf("program copyrights were removed, can't run")
|
||||||
}
|
}
|
||||||
|
|
||||||
// All of these flags can be accessed in your GAPI implementation with
|
args := Args{}
|
||||||
// the `c.Lineage()[1].Type` and `c.Lineage()[1].IsSet` functions. Their
|
args.version = data.Version // copy this in
|
||||||
// own flags can be accessed with `c.Type` and `c.IsSet` directly.
|
args.description = data.Tagline
|
||||||
runFlags := []cli.Flag{
|
|
||||||
// common flags which all can use
|
|
||||||
|
|
||||||
// useful for testing multiple instances on same machine
|
config := arg.Config{
|
||||||
&cli.StringFlag{
|
Program: data.Program,
|
||||||
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{
|
parser, err := arg.NewParser(config, &args)
|
||||||
// common flags which all can use
|
if err != nil {
|
||||||
&cli.StringSliceFlag{
|
// programming error
|
||||||
Name: "seeds, s",
|
return errwrap.Wrapf(err, "cli config error")
|
||||||
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{
|
err = parser.Parse(data.Args[1:]) // XXX: args[0] needs to be dropped
|
||||||
// common flags which all can use
|
if err == arg.ErrHelp {
|
||||||
&cli.BoolFlag{
|
parser.WriteHelp(os.Stdout)
|
||||||
Name: "noop",
|
return nil
|
||||||
Usage: "simulate the download (can't recurse)",
|
}
|
||||||
},
|
if err == arg.ErrVersion {
|
||||||
&cli.IntFlag{
|
fmt.Printf("%s\n", data.Version) // byon: bring your own newline
|
||||||
Name: "sema",
|
return nil
|
||||||
Value: -1, // maximum parallelism
|
}
|
||||||
Usage: "globally add a semaphore to downloads with this lock count",
|
if err != nil {
|
||||||
},
|
//parser.WriteHelp(os.Stdout) // TODO: is doing this helpful?
|
||||||
&cli.BoolFlag{
|
return cliUtil.CliParseError(err) // consistent errors
|
||||||
Name: "update",
|
|
||||||
Usage: "update all dependencies to the latest versions",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
subCommandsRun := []*cli.Command{} // run sub commands
|
// display the license
|
||||||
subCommandsDeploy := []*cli.Command{} // deploy sub commands
|
if args.License {
|
||||||
subCommandsGet := []*cli.Command{} // get (download) sub commands
|
fmt.Printf("%s", data.Copying) // file comes with a trailing nl
|
||||||
|
|
||||||
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 = data.Program // App.name and App.version pass these values through
|
|
||||||
app.Version = data.Version
|
|
||||||
app.Usage = "next generation config management"
|
|
||||||
app.Metadata = map[string]interface{}{ // additional flags
|
|
||||||
"flags": data.Flags,
|
|
||||||
}
|
|
||||||
|
|
||||||
// if no app.Command is specified
|
|
||||||
app.Action = func(c *cli.Context) error {
|
|
||||||
// print the license
|
|
||||||
if c.Bool("license") {
|
|
||||||
fmt.Printf("%s", data.Copying)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// print help if no flags are set
|
|
||||||
cli.ShowAppHelp(c)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// global flags
|
if ok, err := args.Run(ctx, data); err != nil {
|
||||||
app.Flags = []cli.Flag{
|
return err
|
||||||
&cli.BoolFlag{
|
} else if ok { // did we activate one of the commands?
|
||||||
Name: "license",
|
return nil
|
||||||
Usage: "prints the software license",
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
app.Commands = []*cli.Command{
|
// print help if no subcommands are set
|
||||||
//{
|
parser.WriteHelp(os.Stdout)
|
||||||
// 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
|
return nil
|
||||||
// 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(data.Args)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Args is the CLI parsing structure and type of the parsed result. This
|
||||||
|
// particular struct is the top-most one.
|
||||||
|
type Args struct {
|
||||||
|
// XXX: We cannot have both subcommands and a positional argument.
|
||||||
|
// XXX: I think it's a bug of this library that it can't handle argv[0].
|
||||||
|
//Argv0 string `arg:"positional"`
|
||||||
|
|
||||||
|
License bool `arg:"--license" help:"display the license and exit"`
|
||||||
|
|
||||||
|
RunCmd *RunArgs `arg:"subcommand:run" help:"run code on this machine"`
|
||||||
|
|
||||||
|
DeployCmd *DeployArgs `arg:"subcommand:deploy" help:"deploy code into a cluster"`
|
||||||
|
|
||||||
|
// This never runs, it gets preempted in the real main() function.
|
||||||
|
// XXX: Can we do it nicely with the new arg parser? can it ignore all args?
|
||||||
|
EtcdCmd *EtcdArgs `arg:"subcommand:etcd" help:"run standalone etcd"`
|
||||||
|
|
||||||
|
// version is a private handle for our version string.
|
||||||
|
version string `arg:"-"` // ignored from parsing
|
||||||
|
|
||||||
|
// description is a private handle for our description string.
|
||||||
|
description string `arg:"-"` // ignored from parsing
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version returns the version string. Implementing this signature is part of
|
||||||
|
// the API for the cli library.
|
||||||
|
func (obj *Args) Version() string {
|
||||||
|
return obj.version
|
||||||
|
}
|
||||||
|
|
||||||
|
// Description returns a description string. Implementing this signature is part
|
||||||
|
// of the API for the cli library.
|
||||||
|
func (obj *Args) Description() string {
|
||||||
|
return obj.description
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run executes the correct subcommand. It errors if there's ever an error. It
|
||||||
|
// returns true if we did activate one of the subcommands. It returns false if
|
||||||
|
// we did not. This information is used so that the top-level parser can return
|
||||||
|
// usage or help information if no subcommand activates.
|
||||||
|
func (obj *Args) Run(ctx context.Context, data *cliUtil.Data) (bool, error) {
|
||||||
|
if cmd := obj.RunCmd; cmd != nil {
|
||||||
|
return cmd.Run(ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd := obj.DeployCmd; cmd != nil {
|
||||||
|
return cmd.Run(ctx, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: we could return true, fmt.Errorf("...") if more than one did
|
||||||
|
return false, nil // nobody activated
|
||||||
|
}
|
||||||
|
|
||||||
|
// EtcdArgs is the CLI parsing structure and type of the parsed result. This
|
||||||
|
// particular one is empty because the `etcd` subcommand is preempted in the
|
||||||
|
// real main() function.
|
||||||
|
type EtcdArgs struct{}
|
||||||
|
|||||||
134
cli/deploy.go
134
cli/deploy.go
@@ -22,58 +22,103 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
|
||||||
cliUtil "github.com/purpleidea/mgmt/cli/util"
|
cliUtil "github.com/purpleidea/mgmt/cli/util"
|
||||||
"github.com/purpleidea/mgmt/etcd/client"
|
"github.com/purpleidea/mgmt/etcd/client"
|
||||||
"github.com/purpleidea/mgmt/etcd/deployer"
|
"github.com/purpleidea/mgmt/etcd/deployer"
|
||||||
etcdfs "github.com/purpleidea/mgmt/etcd/fs"
|
etcdfs "github.com/purpleidea/mgmt/etcd/fs"
|
||||||
"github.com/purpleidea/mgmt/gapi"
|
"github.com/purpleidea/mgmt/gapi"
|
||||||
|
emptyGAPI "github.com/purpleidea/mgmt/gapi/empty"
|
||||||
|
langGAPI "github.com/purpleidea/mgmt/lang/gapi"
|
||||||
"github.com/purpleidea/mgmt/lib"
|
"github.com/purpleidea/mgmt/lib"
|
||||||
"github.com/purpleidea/mgmt/util/errwrap"
|
"github.com/purpleidea/mgmt/util/errwrap"
|
||||||
|
yamlGAPI "github.com/purpleidea/mgmt/yamlgraph"
|
||||||
|
|
||||||
"github.com/pborman/uuid"
|
"github.com/pborman/uuid"
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
git "gopkg.in/src-d/go-git.v4"
|
git "gopkg.in/src-d/go-git.v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
// deploy is the cli target to manage deploys to our cluster.
|
// DeployArgs is the CLI parsing structure and type of the parsed result. This
|
||||||
// TODO: add a timeout and/or cancel signal to replace context.TODO()
|
// particular one contains all the common flags for the `deploy` subcommand
|
||||||
func deploy(c *cli.Context, name string, gapiObj gapi.GAPI) error {
|
// which all frontends can use.
|
||||||
cliContext := c.Lineage()[1]
|
type DeployArgs struct {
|
||||||
if cliContext == nil {
|
Seeds []string `arg:"--seeds,env:MGMT_SEEDS" help:"default etc client endpoint"`
|
||||||
return fmt.Errorf("could not get cli context")
|
Noop bool `arg:"--noop" help:"globally force all resources into no-op mode"`
|
||||||
|
Sema int `arg:"--sema" default:"-1" help:"globally add a semaphore to all resources with this lock count"`
|
||||||
|
NoGit bool `arg:"--no-git" help:"don't look at git commit id for safe deploys"`
|
||||||
|
Force bool `arg:"--force" help:"force a new deploy, even if the safety chain would break"`
|
||||||
|
|
||||||
|
DeployEmpty *emptyGAPI.Args `arg:"subcommand:empty" help:"deploy empty payload"`
|
||||||
|
DeployLang *langGAPI.Args `arg:"subcommand:lang" help:"deploy lang (mcl) payload"`
|
||||||
|
DeployYaml *yamlGAPI.Args `arg:"subcommand:yaml" help:"deploy yaml graph payload"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run executes the correct subcommand. It errors if there's ever an error. It
|
||||||
|
// returns true if we did activate one of the subcommands. It returns false if
|
||||||
|
// we did not. This information is used so that the top-level parser can return
|
||||||
|
// usage or help information if no subcommand activates. This particular Run is
|
||||||
|
// the run for the main `deploy` subcommand. This always requires a frontend to
|
||||||
|
// deploy to the cluster, but if you don't want a graph, you can use the `empty`
|
||||||
|
// frontend. The engine backend is agnostic to which frontend is deployed, in
|
||||||
|
// fact, you can deploy with multiple different frontends, one after another, on
|
||||||
|
// the same engine.
|
||||||
|
func (obj *DeployArgs) Run(ctx context.Context, data *cliUtil.Data) (bool, error) {
|
||||||
|
var name string
|
||||||
|
var args interface{}
|
||||||
|
if cmd := obj.DeployEmpty; cmd != nil {
|
||||||
|
name = emptyGAPI.Name
|
||||||
|
args = cmd
|
||||||
|
}
|
||||||
|
if cmd := obj.DeployLang; cmd != nil {
|
||||||
|
name = langGAPI.Name
|
||||||
|
args = cmd
|
||||||
|
}
|
||||||
|
if cmd := obj.DeployYaml; cmd != nil {
|
||||||
|
name = yamlGAPI.Name
|
||||||
|
args = cmd
|
||||||
}
|
}
|
||||||
|
|
||||||
program, version := cliUtil.SafeProgram(c.App.Name), c.App.Version
|
// XXX: workaround https://github.com/alexflint/go-arg/issues/239
|
||||||
var flags cliUtil.Flags
|
if l := len(obj.Seeds); name == "" && l > 1 {
|
||||||
var debug bool
|
elem := obj.Seeds[l-2] // second to last element
|
||||||
if val, exists := c.App.Metadata["flags"]; exists {
|
if elem == emptyGAPI.Name || elem == langGAPI.Name || elem == yamlGAPI.Name {
|
||||||
if f, ok := val.(cliUtil.Flags); ok {
|
return false, cliUtil.CliParseError(cliUtil.MissingEquals) // consistent errors
|
||||||
flags = f
|
|
||||||
debug = flags.Debug
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn, exists := gapi.RegisteredGAPIs[name]
|
||||||
|
if !exists {
|
||||||
|
return false, nil // did not activate
|
||||||
|
}
|
||||||
|
gapiObj := fn()
|
||||||
|
|
||||||
|
program, version := data.Program, data.Version
|
||||||
Logf := func(format string, v ...interface{}) {
|
Logf := func(format string, v ...interface{}) {
|
||||||
log.Printf("deploy: "+format, v...)
|
log.Printf("deploy: "+format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
cliUtil.Hello(program, version, flags) // say hello!
|
// TODO: consider adding a timeout based on an args.Timeout flag ?
|
||||||
|
ctx, cancel := signal.NotifyContext(ctx, os.Interrupt)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
cliUtil.Hello(program, version, data.Flags) // say hello!
|
||||||
defer Logf("goodbye!")
|
defer Logf("goodbye!")
|
||||||
|
|
||||||
var hash, pHash string
|
var hash, pHash string
|
||||||
if !cliContext.Bool("no-git") {
|
if !obj.NoGit {
|
||||||
wd, err := os.Getwd()
|
wd, err := os.Getwd()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errwrap.Wrapf(err, "could not get current working directory")
|
return false, errwrap.Wrapf(err, "could not get current working directory")
|
||||||
}
|
}
|
||||||
repo, err := git.PlainOpen(wd)
|
repo, err := git.PlainOpen(wd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errwrap.Wrapf(err, "could not open git repo")
|
return false, errwrap.Wrapf(err, "could not open git repo")
|
||||||
}
|
}
|
||||||
|
|
||||||
head, err := repo.Head()
|
head, err := repo.Head()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errwrap.Wrapf(err, "could not read git HEAD")
|
return false, errwrap.Wrapf(err, "could not read git HEAD")
|
||||||
}
|
}
|
||||||
|
|
||||||
hash = head.Hash().String() // current commit id
|
hash = head.Hash().String() // current commit id
|
||||||
@@ -84,32 +129,32 @@ func deploy(c *cli.Context, name string, gapiObj gapi.GAPI) error {
|
|||||||
}
|
}
|
||||||
commits, err := repo.Log(lo)
|
commits, err := repo.Log(lo)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errwrap.Wrapf(err, "could not read git log")
|
return false, errwrap.Wrapf(err, "could not read git log")
|
||||||
}
|
}
|
||||||
if _, err := commits.Next(); err != nil { // skip over HEAD
|
if _, err := commits.Next(); err != nil { // skip over HEAD
|
||||||
return errwrap.Wrapf(err, "could not read HEAD in git log") // weird!
|
return false, errwrap.Wrapf(err, "could not read HEAD in git log") // weird!
|
||||||
}
|
}
|
||||||
commit, err := commits.Next()
|
commit, err := commits.Next()
|
||||||
if err == nil { // errors are okay, we might be empty
|
if err == nil { // errors are okay, we might be empty
|
||||||
pHash = commit.Hash.String() // previous commit id
|
pHash = commit.Hash.String() // previous commit id
|
||||||
}
|
}
|
||||||
Logf("previous deploy hash: %s", pHash)
|
Logf("previous deploy hash: %s", pHash)
|
||||||
if cliContext.Bool("force") {
|
if obj.Force {
|
||||||
pHash = "" // don't check this :(
|
pHash = "" // don't check this :(
|
||||||
}
|
}
|
||||||
if hash == "" {
|
if hash == "" {
|
||||||
return errwrap.Wrapf(err, "could not get git deploy hash")
|
return false, errwrap.Wrapf(err, "could not get git deploy hash")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
uniqueid := uuid.New() // panic's if it can't generate one :P
|
uniqueid := uuid.New() // panic's if it can't generate one :P
|
||||||
|
|
||||||
etcdClient := client.NewClientFromSeedsNamespace(
|
etcdClient := client.NewClientFromSeedsNamespace(
|
||||||
cliContext.StringSlice("seeds"), // endpoints
|
obj.Seeds, // endpoints
|
||||||
lib.NS,
|
lib.NS,
|
||||||
)
|
)
|
||||||
if err := etcdClient.Init(); err != nil {
|
if err := etcdClient.Init(); err != nil {
|
||||||
return errwrap.Wrapf(err, "client Init failed")
|
return false, errwrap.Wrapf(err, "client Init failed")
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
err := errwrap.Wrapf(etcdClient.Close(), "client Close failed")
|
err := errwrap.Wrapf(etcdClient.Close(), "client Close failed")
|
||||||
@@ -121,13 +166,13 @@ func deploy(c *cli.Context, name string, gapiObj gapi.GAPI) error {
|
|||||||
|
|
||||||
simpleDeploy := &deployer.SimpleDeploy{
|
simpleDeploy := &deployer.SimpleDeploy{
|
||||||
Client: etcdClient,
|
Client: etcdClient,
|
||||||
Debug: debug,
|
Debug: data.Flags.Debug,
|
||||||
Logf: func(format string, v ...interface{}) {
|
Logf: func(format string, v ...interface{}) {
|
||||||
Logf("deploy: "+format, v...)
|
Logf("deploy: "+format, v...)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
if err := simpleDeploy.Init(); err != nil {
|
if err := simpleDeploy.Init(); err != nil {
|
||||||
return errwrap.Wrapf(err, "deploy Init failed")
|
return false, errwrap.Wrapf(err, "deploy Init failed")
|
||||||
}
|
}
|
||||||
defer func() {
|
defer func() {
|
||||||
err := errwrap.Wrapf(simpleDeploy.Close(), "deploy Close failed")
|
err := errwrap.Wrapf(simpleDeploy.Close(), "deploy Close failed")
|
||||||
@@ -138,9 +183,9 @@ func deploy(c *cli.Context, name string, gapiObj gapi.GAPI) error {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
// get max id (from all the previous deploys)
|
// get max id (from all the previous deploys)
|
||||||
max, err := simpleDeploy.GetMaxDeployID(context.TODO())
|
max, err := simpleDeploy.GetMaxDeployID(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errwrap.Wrapf(err, "error getting max deploy id")
|
return false, errwrap.Wrapf(err, "error getting max deploy id")
|
||||||
}
|
}
|
||||||
// find the latest id
|
// find the latest id
|
||||||
var id = max + 1 // next id
|
var id = max + 1 // next id
|
||||||
@@ -152,44 +197,49 @@ func deploy(c *cli.Context, name string, gapiObj gapi.GAPI) error {
|
|||||||
Metadata: lib.MetadataPrefix + fmt.Sprintf("/deploy/%d-%s", id, uniqueid),
|
Metadata: lib.MetadataPrefix + fmt.Sprintf("/deploy/%d-%s", id, uniqueid),
|
||||||
DataPrefix: lib.StoragePrefix,
|
DataPrefix: lib.StoragePrefix,
|
||||||
|
|
||||||
Debug: debug,
|
Debug: data.Flags.Debug,
|
||||||
Logf: func(format string, v ...interface{}) {
|
Logf: func(format string, v ...interface{}) {
|
||||||
Logf("fs: "+format, v...)
|
Logf("fs: "+format, v...)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cliInfo := &gapi.CliInfo{
|
info := &gapi.Info{
|
||||||
CliContext: c, // don't pass in the parent context
|
Args: args,
|
||||||
|
Flags: &gapi.Flags{
|
||||||
|
Noop: obj.Noop,
|
||||||
|
Sema: obj.Sema,
|
||||||
|
//Update: obj.Update,
|
||||||
|
},
|
||||||
|
|
||||||
Fs: etcdFs,
|
Fs: etcdFs,
|
||||||
Debug: debug,
|
Debug: data.Flags.Debug,
|
||||||
Logf: func(format string, v ...interface{}) {
|
Logf: func(format string, v ...interface{}) {
|
||||||
// TODO: is this a sane prefix to use here?
|
// TODO: is this a sane prefix to use here?
|
||||||
log.Printf("cli: "+format, v...)
|
log.Printf("cli: "+format, v...)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
deploy, err := gapiObj.Cli(cliInfo)
|
deploy, err := gapiObj.Cli(info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errwrap.Wrapf(err, "cli parse error")
|
return false, cliUtil.CliParseError(err) // consistent errors
|
||||||
}
|
}
|
||||||
if deploy == nil { // not used
|
if deploy == nil { // not used
|
||||||
return fmt.Errorf("not enough information specified")
|
return false, fmt.Errorf("not enough information specified")
|
||||||
}
|
}
|
||||||
|
|
||||||
// redundant
|
// redundant
|
||||||
deploy.Noop = cliContext.Bool("noop")
|
deploy.Noop = obj.Noop
|
||||||
deploy.Sema = cliContext.Int("sema")
|
deploy.Sema = obj.Sema
|
||||||
|
|
||||||
str, err := deploy.ToB64()
|
str, err := deploy.ToB64()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errwrap.Wrapf(err, "encoding error")
|
return false, errwrap.Wrapf(err, "encoding error")
|
||||||
}
|
}
|
||||||
|
|
||||||
// this nominally checks the previous git hash matches our expectation
|
// this nominally checks the previous git hash matches our expectation
|
||||||
if err := simpleDeploy.AddDeploy(context.TODO(), id, hash, pHash, &str); err != nil {
|
if err := simpleDeploy.AddDeploy(ctx, id, hash, pHash, &str); err != nil {
|
||||||
return errwrap.Wrapf(err, "could not create deploy id `%d`", id)
|
return false, errwrap.Wrapf(err, "could not create deploy id `%d`", id)
|
||||||
}
|
}
|
||||||
Logf("success, id: %d", id)
|
Logf("success, id: %d", id)
|
||||||
return nil
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|||||||
74
cli/get.go
74
cli/get.go
@@ -1,74 +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 cli
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
|
|
||||||
cliUtil "github.com/purpleidea/mgmt/cli/util"
|
|
||||||
"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 := cliUtil.SafeProgram(c.App.Name), c.App.Version
|
|
||||||
var flags cliUtil.Flags
|
|
||||||
var debug bool
|
|
||||||
if val, exists := c.App.Metadata["flags"]; exists {
|
|
||||||
if f, ok := val.(cliUtil.Flags); ok {
|
|
||||||
flags = f
|
|
||||||
debug = flags.Debug
|
|
||||||
}
|
|
||||||
}
|
|
||||||
cliUtil.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
|
|
||||||
}
|
|
||||||
230
cli/run.go
230
cli/run.go
@@ -18,6 +18,7 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
@@ -27,50 +28,140 @@ import (
|
|||||||
|
|
||||||
cliUtil "github.com/purpleidea/mgmt/cli/util"
|
cliUtil "github.com/purpleidea/mgmt/cli/util"
|
||||||
"github.com/purpleidea/mgmt/gapi"
|
"github.com/purpleidea/mgmt/gapi"
|
||||||
|
emptyGAPI "github.com/purpleidea/mgmt/gapi/empty"
|
||||||
|
langGAPI "github.com/purpleidea/mgmt/lang/gapi"
|
||||||
"github.com/purpleidea/mgmt/lib"
|
"github.com/purpleidea/mgmt/lib"
|
||||||
"github.com/purpleidea/mgmt/util"
|
"github.com/purpleidea/mgmt/util"
|
||||||
"github.com/purpleidea/mgmt/util/errwrap"
|
"github.com/purpleidea/mgmt/util/errwrap"
|
||||||
|
yamlGAPI "github.com/purpleidea/mgmt/yamlgraph"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// run is the main run target.
|
// RunArgs is the CLI parsing structure and type of the parsed result. This
|
||||||
func run(c *cli.Context, name string, gapiObj gapi.GAPI) error {
|
// particular one contains all the common flags for the `run` subcommand which
|
||||||
cliContext := c.Lineage()[1] // these are the flags from `run`
|
// all frontends can use.
|
||||||
if cliContext == nil {
|
type RunArgs struct {
|
||||||
return fmt.Errorf("could not get cli context")
|
|
||||||
|
// useful for testing multiple instances on same machine
|
||||||
|
Hostname *string `arg:"--hostname" help:"hostname to use"`
|
||||||
|
Prefix *string `arg:"--prefix,env:MGMT_PREFIX" help:"specify a path to the working prefix directory"`
|
||||||
|
TmpPrefix bool `arg:"--tmp-prefix" help:"request a pseudo-random, temporary prefix to be used"`
|
||||||
|
AllowTmpPrefix bool `arg:"--allow-tmp-prefix" help:"allow creation of a new temporary prefix if main prefix is unavailable"`
|
||||||
|
|
||||||
|
NoWatch bool `arg:"--no-watch" help:"do not update graph under any switch events"`
|
||||||
|
NoStreamWatch bool `arg:"--no-stream-watch" help:"do not update graph on stream switch events"`
|
||||||
|
NoDeployWatch bool `arg:"--no-deploy-watch" help:"do not change deploys after an initial deploy"`
|
||||||
|
|
||||||
|
Noop bool `arg:"--noop" help:"globally force all resources into no-op mode"`
|
||||||
|
Sema int `arg:"--sema" default:"-1" help:"globally add a semaphore to downloads with this lock count"`
|
||||||
|
|
||||||
|
Graphviz string `arg:"--graphviz" help:"output file for graphviz data"`
|
||||||
|
GraphvizFilter string `arg:"--graphviz-filter" help:"graphviz filter to use"`
|
||||||
|
ConvergedTimeout int `arg:"--converged-timeout,env:MGMT_CONVERGED_TIMEOUT" default:"-1" help:"after approximately this many seconds without activity, we're considered to be in a converged state"`
|
||||||
|
ConvergedTimeoutNoExit bool `arg:"--converged-timeout-no-exit" help:"don't exit on converged-timeout"`
|
||||||
|
ConvergedStatusFile string `arg:"--converged-status-file" help:"file to append the current converged state to, mostly used for testing"`
|
||||||
|
MaxRuntime uint `arg:"--max-runtime,env:MGMT_MAX_RUNTIME" help:"exit after a maximum of approximately this many seconds"`
|
||||||
|
|
||||||
|
// if empty, it will startup a new server
|
||||||
|
Seeds []string `arg:"--seeds,env:MGMT_SEEDS" help:"default etc client endpoint"`
|
||||||
|
|
||||||
|
// port 2379 and 4001 are common
|
||||||
|
ClientURLs []string `arg:"--client-urls,env:MGMT_CLIENT_URLS" help:"list of URLs to listen on for client traffic"`
|
||||||
|
|
||||||
|
// port 2380 and 7001 are common
|
||||||
|
// etcd now uses --peer-urls
|
||||||
|
ServerURLs []string `arg:"--server-urls,env:MGMT_SERVER_URLS" help:"list of URLs to listen on for server (peer) traffic"`
|
||||||
|
|
||||||
|
// port 2379 and 4001 are common
|
||||||
|
AdvertiseClientURLs []string `arg:"--advertise-client-urls,env:MGMT_ADVERTISE_CLIENT_URLS" help:"list of URLs to listen on for client traffic"`
|
||||||
|
|
||||||
|
// port 2380 and 7001 are common
|
||||||
|
// etcd now uses --advertise-peer-urls
|
||||||
|
AdvertiseServerURLs []string `arg:"--advertise-server-urls,env:MGMT_ADVERTISE_SERVER_URLS" help:"list of URLs to listen on for server (peer) traffic"`
|
||||||
|
|
||||||
|
IdealClusterSize int `arg:"--ideal-cluster-size,env:MGMT_IDEAL_CLUSTER_SIZE" default:"-1" help:"ideal number of server peers in cluster; only read by initial server"`
|
||||||
|
NoServer bool `arg:"--no-server" help:"do not start embedded etcd server (do not promote from client to peer)"`
|
||||||
|
NoNetwork bool `arg:"--no-network,env:MGMT_NO_NETWORK" help:"run single node instance without clustering or opening tcp ports to the outside"`
|
||||||
|
|
||||||
|
NoPgp bool `arg:"--no-pgp" help:"don't create pgp keys"`
|
||||||
|
PgpKeyPath *string `arg:"--pgp-key-path" help:"path for instance key pair"`
|
||||||
|
PgpIdentity *string `arg:"--pgp-identity" help:"default identity used for generation"`
|
||||||
|
|
||||||
|
Prometheus bool `arg:"--prometheus" help:"start a prometheus instance"`
|
||||||
|
PrometheusListen string `arg:"--prometheus-listen" help:"specify prometheus instance binding"`
|
||||||
|
|
||||||
|
RunEmpty *emptyGAPI.Args `arg:"subcommand:empty" help:"run empty payload"`
|
||||||
|
RunLang *langGAPI.Args `arg:"subcommand:lang" help:"run lang (mcl) payload"`
|
||||||
|
RunYaml *yamlGAPI.Args `arg:"subcommand:yaml" help:"run yaml graph payload"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run executes the correct subcommand. It errors if there's ever an error. It
|
||||||
|
// returns true if we did activate one of the subcommands. It returns false if
|
||||||
|
// we did not. This information is used so that the top-level parser can return
|
||||||
|
// usage or help information if no subcommand activates. This particular Run is
|
||||||
|
// the run for the main `run` subcommand. This 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.
|
||||||
|
func (obj *RunArgs) Run(ctx context.Context, data *cliUtil.Data) (bool, error) {
|
||||||
|
var name string
|
||||||
|
var args interface{}
|
||||||
|
if cmd := obj.RunEmpty; cmd != nil {
|
||||||
|
name = emptyGAPI.Name
|
||||||
|
args = cmd
|
||||||
}
|
}
|
||||||
|
if cmd := obj.RunLang; cmd != nil {
|
||||||
|
name = langGAPI.Name
|
||||||
|
args = cmd
|
||||||
|
}
|
||||||
|
if cmd := obj.RunYaml; cmd != nil {
|
||||||
|
name = yamlGAPI.Name
|
||||||
|
args = cmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX: workaround https://github.com/alexflint/go-arg/issues/239
|
||||||
|
lists := [][]string{
|
||||||
|
obj.Seeds,
|
||||||
|
obj.ClientURLs,
|
||||||
|
obj.ServerURLs,
|
||||||
|
obj.AdvertiseClientURLs,
|
||||||
|
obj.AdvertiseServerURLs,
|
||||||
|
}
|
||||||
|
for _, list := range lists {
|
||||||
|
if l := len(list); name == "" && l > 1 {
|
||||||
|
elem := list[l-2] // second to last element
|
||||||
|
if elem == emptyGAPI.Name || elem == langGAPI.Name || elem == yamlGAPI.Name {
|
||||||
|
return false, cliUtil.CliParseError(cliUtil.MissingEquals) // consistent errors
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn, exists := gapi.RegisteredGAPIs[name]
|
||||||
|
if !exists {
|
||||||
|
return false, nil // did not activate
|
||||||
|
}
|
||||||
|
gapiObj := fn()
|
||||||
|
|
||||||
main := &lib.Main{}
|
main := &lib.Main{}
|
||||||
|
|
||||||
main.Program, main.Version = cliUtil.SafeProgram(c.App.Name), c.App.Version
|
main.Program, main.Version = data.Program, data.Version
|
||||||
var flags cliUtil.Flags
|
main.Flags = lib.Flags{
|
||||||
if val, exists := c.App.Metadata["flags"]; exists {
|
Debug: data.Flags.Debug,
|
||||||
if f, ok := val.(cliUtil.Flags); ok {
|
Verbose: data.Flags.Verbose,
|
||||||
flags = f
|
|
||||||
main.Flags = lib.Flags{
|
|
||||||
Debug: f.Debug,
|
|
||||||
Verbose: f.Verbose,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Logf := func(format string, v ...interface{}) {
|
Logf := func(format string, v ...interface{}) {
|
||||||
log.Printf("main: "+format, v...)
|
log.Printf("main: "+format, v...)
|
||||||
}
|
}
|
||||||
|
|
||||||
cliUtil.Hello(main.Program, main.Version, flags) // say hello!
|
cliUtil.Hello(main.Program, main.Version, data.Flags) // say hello!
|
||||||
defer Logf("goodbye!")
|
defer Logf("goodbye!")
|
||||||
|
|
||||||
if h := cliContext.String("hostname"); cliContext.IsSet("hostname") && h != "" {
|
main.Hostname = obj.Hostname
|
||||||
main.Hostname = &h
|
main.Prefix = obj.Prefix
|
||||||
}
|
main.TmpPrefix = obj.TmpPrefix
|
||||||
|
main.AllowTmpPrefix = obj.AllowTmpPrefix
|
||||||
if s := cliContext.String("prefix"); cliContext.IsSet("prefix") && s != "" {
|
|
||||||
main.Prefix = &s
|
|
||||||
}
|
|
||||||
main.TmpPrefix = cliContext.Bool("tmp-prefix")
|
|
||||||
main.AllowTmpPrefix = cliContext.Bool("allow-tmp-prefix")
|
|
||||||
|
|
||||||
// create a memory backed temporary filesystem for storing runtime data
|
// create a memory backed temporary filesystem for storing runtime data
|
||||||
mmFs := afero.NewMemMapFs()
|
mmFs := afero.NewMemMapFs()
|
||||||
@@ -78,8 +169,14 @@ func run(c *cli.Context, name string, gapiObj gapi.GAPI) error {
|
|||||||
standaloneFs := &util.AferoFs{Afero: afs}
|
standaloneFs := &util.AferoFs{Afero: afs}
|
||||||
main.DeployFs = standaloneFs
|
main.DeployFs = standaloneFs
|
||||||
|
|
||||||
cliInfo := &gapi.CliInfo{
|
info := &gapi.Info{
|
||||||
CliContext: c, // don't pass in the parent context
|
Args: args,
|
||||||
|
Flags: &gapi.Flags{
|
||||||
|
Hostname: obj.Hostname,
|
||||||
|
Noop: obj.Noop,
|
||||||
|
Sema: obj.Sema,
|
||||||
|
//Update: obj.Update,
|
||||||
|
},
|
||||||
|
|
||||||
Fs: standaloneFs,
|
Fs: standaloneFs,
|
||||||
Debug: main.Flags.Debug,
|
Debug: main.Flags.Debug,
|
||||||
@@ -88,12 +185,16 @@ func run(c *cli.Context, name string, gapiObj gapi.GAPI) error {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
deploy, err := gapiObj.Cli(cliInfo)
|
deploy, err := gapiObj.Cli(info)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errwrap.Wrapf(err, "cli parse error")
|
return false, cliUtil.CliParseError(err) // consistent errors
|
||||||
}
|
}
|
||||||
if c.Bool("only-unify") && deploy == nil {
|
|
||||||
return nil // we end early
|
if cmd := obj.RunLang; cmd != nil && cmd.OnlyUnify && deploy == nil {
|
||||||
|
return true, nil // we end early
|
||||||
|
}
|
||||||
|
if cmd := obj.RunLang; cmd != nil && cmd.OnlyDownload && deploy == nil {
|
||||||
|
return true, nil // we end early
|
||||||
}
|
}
|
||||||
main.Deploy = deploy
|
main.Deploy = deploy
|
||||||
if main.Deploy == nil {
|
if main.Deploy == nil {
|
||||||
@@ -102,47 +203,41 @@ func run(c *cli.Context, name string, gapiObj gapi.GAPI) error {
|
|||||||
log.Printf("main: no frontend selected (no GAPI activated)")
|
log.Printf("main: no frontend selected (no GAPI activated)")
|
||||||
}
|
}
|
||||||
|
|
||||||
main.NoWatch = cliContext.Bool("no-watch")
|
main.NoWatch = obj.NoWatch
|
||||||
main.NoStreamWatch = cliContext.Bool("no-stream-watch")
|
main.NoStreamWatch = obj.NoStreamWatch
|
||||||
main.NoDeployWatch = cliContext.Bool("no-deploy-watch")
|
main.NoDeployWatch = obj.NoDeployWatch
|
||||||
|
|
||||||
main.Noop = cliContext.Bool("noop")
|
main.Noop = obj.Noop
|
||||||
main.Sema = cliContext.Int("sema")
|
main.Sema = obj.Sema
|
||||||
main.Graphviz = cliContext.String("graphviz")
|
main.Graphviz = obj.Graphviz
|
||||||
main.GraphvizFilter = cliContext.String("graphviz-filter")
|
main.GraphvizFilter = obj.GraphvizFilter
|
||||||
main.ConvergedTimeout = cliContext.Int64("converged-timeout")
|
main.ConvergedTimeout = obj.ConvergedTimeout
|
||||||
main.ConvergedTimeoutNoExit = cliContext.Bool("converged-timeout-no-exit")
|
main.ConvergedTimeoutNoExit = obj.ConvergedTimeoutNoExit
|
||||||
main.ConvergedStatusFile = cliContext.String("converged-status-file")
|
main.ConvergedStatusFile = obj.ConvergedStatusFile
|
||||||
main.MaxRuntime = uint(cliContext.Int("max-runtime"))
|
main.MaxRuntime = obj.MaxRuntime
|
||||||
|
|
||||||
main.Seeds = cliContext.StringSlice("seeds")
|
main.Seeds = obj.Seeds
|
||||||
main.ClientURLs = cliContext.StringSlice("client-urls")
|
main.ClientURLs = obj.ClientURLs
|
||||||
main.ServerURLs = cliContext.StringSlice("server-urls")
|
main.ServerURLs = obj.ServerURLs
|
||||||
main.AdvertiseClientURLs = cliContext.StringSlice("advertise-client-urls")
|
main.AdvertiseClientURLs = obj.AdvertiseClientURLs
|
||||||
main.AdvertiseServerURLs = cliContext.StringSlice("advertise-server-urls")
|
main.AdvertiseServerURLs = obj.AdvertiseServerURLs
|
||||||
main.IdealClusterSize = cliContext.Int("ideal-cluster-size")
|
main.IdealClusterSize = obj.IdealClusterSize
|
||||||
main.NoServer = cliContext.Bool("no-server")
|
main.NoServer = obj.NoServer
|
||||||
main.NoNetwork = cliContext.Bool("no-network")
|
main.NoNetwork = obj.NoNetwork
|
||||||
|
|
||||||
main.NoPgp = cliContext.Bool("no-pgp")
|
main.NoPgp = obj.Prometheus
|
||||||
|
main.PgpKeyPath = obj.PgpKeyPath
|
||||||
|
main.PgpIdentity = obj.PgpIdentity
|
||||||
|
|
||||||
if kp := cliContext.String("pgp-key-path"); cliContext.IsSet("pgp-key-path") {
|
main.Prometheus = obj.Prometheus
|
||||||
main.PgpKeyPath = &kp
|
main.PrometheusListen = obj.PrometheusListen
|
||||||
}
|
|
||||||
|
|
||||||
if us := cliContext.String("pgp-identity"); cliContext.IsSet("pgp-identity") {
|
|
||||||
main.PgpIdentity = &us
|
|
||||||
}
|
|
||||||
|
|
||||||
main.Prometheus = cliContext.Bool("prometheus")
|
|
||||||
main.PrometheusListen = cliContext.String("prometheus-listen")
|
|
||||||
|
|
||||||
if err := main.Validate(); err != nil {
|
if err := main.Validate(); err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := main.Init(); err != nil {
|
if err := main.Init(); err != nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// install the exit signal handler
|
// install the exit signal handler
|
||||||
@@ -200,10 +295,13 @@ func run(c *cli.Context, name string, gapiObj gapi.GAPI) error {
|
|||||||
log.Printf("main: Close: %+v", err)
|
log.Printf("main: Close: %+v", err)
|
||||||
}
|
}
|
||||||
if reterr == nil {
|
if reterr == nil {
|
||||||
return err
|
return false, err
|
||||||
}
|
}
|
||||||
reterr = errwrap.Append(reterr, err)
|
reterr = errwrap.Append(reterr, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return reterr
|
if reterr != nil {
|
||||||
|
return false, reterr
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,9 +20,29 @@ package util
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/purpleidea/mgmt/util/errwrap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Error is a constant error type that implements error.
|
||||||
|
type Error string
|
||||||
|
|
||||||
|
// Error fulfills the error interface of this type.
|
||||||
|
func (e Error) Error() string { return string(e) }
|
||||||
|
|
||||||
|
const (
|
||||||
|
// MissingEquals means we probably hit the parsing bug.
|
||||||
|
// XXX: see: https://github.com/alexflint/go-arg/issues/239
|
||||||
|
MissingEquals = Error("missing equals sign for list element")
|
||||||
|
)
|
||||||
|
|
||||||
|
// CliParseError returns a consistent error if we have a CLI parsing issue.
|
||||||
|
func CliParseError(err error) error {
|
||||||
|
return errwrap.Wrapf(err, "cli parse error")
|
||||||
|
}
|
||||||
|
|
||||||
// Flags are some constant flags which are used throughout the program.
|
// Flags are some constant flags which are used throughout the program.
|
||||||
|
// TODO: Unify this with Debug and Logf ?
|
||||||
type Flags struct {
|
type Flags struct {
|
||||||
Debug bool // add additional log messages
|
Debug bool // add additional log messages
|
||||||
Verbose bool // add extra log message output
|
Verbose bool // add extra log message output
|
||||||
@@ -33,6 +53,7 @@ type Data struct {
|
|||||||
Program string
|
Program string
|
||||||
Version string
|
Version string
|
||||||
Copying string
|
Copying string
|
||||||
|
Tagline string
|
||||||
Flags Flags
|
Flags Flags
|
||||||
Args []string // os.Args usually
|
Args []string // os.Args usually
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// New builds a new converger coordinator.
|
// New builds a new converger coordinator.
|
||||||
func New(timeout int64) *Coordinator {
|
func New(timeout int) *Coordinator {
|
||||||
return &Coordinator{
|
return &Coordinator{
|
||||||
timeout: timeout,
|
timeout: timeout,
|
||||||
|
|
||||||
@@ -61,7 +61,7 @@ func New(timeout int64) *Coordinator {
|
|||||||
type Coordinator struct {
|
type Coordinator struct {
|
||||||
// timeout must be zero (instant) or greater seconds to run. If it's -1
|
// timeout must be zero (instant) or greater seconds to run. If it's -1
|
||||||
// then this is disabled, and we never run stateFns.
|
// then this is disabled, and we never run stateFns.
|
||||||
timeout int64
|
timeout int
|
||||||
|
|
||||||
// mutex is used for controlling access to status and lastid.
|
// mutex is used for controlling access to status and lastid.
|
||||||
mutex *sync.RWMutex
|
mutex *sync.RWMutex
|
||||||
@@ -365,7 +365,7 @@ func (obj *Coordinator) Status() map[*UID]bool {
|
|||||||
// Timeout returns the timeout in seconds that converger was created with. This
|
// Timeout returns the timeout in seconds that converger was created with. This
|
||||||
// is useful to avoid passing in the timeout value separately when you're
|
// is useful to avoid passing in the timeout value separately when you're
|
||||||
// already passing in the Coordinator struct.
|
// already passing in the Coordinator struct.
|
||||||
func (obj *Coordinator) Timeout() int64 {
|
func (obj *Coordinator) Timeout() int {
|
||||||
return obj.timeout
|
return obj.timeout
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -375,7 +375,7 @@ func (obj *Coordinator) Timeout() int64 {
|
|||||||
type UID struct {
|
type UID struct {
|
||||||
// timeout is a copy of the main timeout. It could eventually be used
|
// timeout is a copy of the main timeout. It could eventually be used
|
||||||
// for per-UID timeouts too.
|
// for per-UID timeouts too.
|
||||||
timeout int64
|
timeout int
|
||||||
// isConverged stores the convergence state of this particular UID.
|
// isConverged stores the convergence state of this particular UID.
|
||||||
isConverged bool
|
isConverged bool
|
||||||
|
|
||||||
|
|||||||
26
docs/faq.md
26
docs/faq.md
@@ -346,6 +346,32 @@ print "hello" {
|
|||||||
Yes we know the compiler gives horrible error messages, and yes we would
|
Yes we know the compiler gives horrible error messages, and yes we would
|
||||||
absolutely love your help improving this.
|
absolutely love your help improving this.
|
||||||
|
|
||||||
|
### The run and deploy commands don't parse correctly when used with `--seeds`.
|
||||||
|
|
||||||
|
If you're running a command with `--seeds`, `--server-urls`, or `--client-urls`,
|
||||||
|
then make sure you are using an equals sign between the flag name and the value.
|
||||||
|
For example, if you were to run:
|
||||||
|
|
||||||
|
```
|
||||||
|
# wrong invocation!
|
||||||
|
mgmt deploy --no-git --seeds http://127.0.0.1:2379 lang code/test.mcl
|
||||||
|
```
|
||||||
|
|
||||||
|
Then the `--seeds` flag would interpret `lang` and `code/test.mcl` as additional
|
||||||
|
seeds. This flag as well as the other aforementioned ones all accept multiple
|
||||||
|
values. Use an equals sign to guarantee you enter the correct data, eg:
|
||||||
|
|
||||||
|
```
|
||||||
|
# better invocation! (note the equals sign)
|
||||||
|
mgmt deploy --no-git --seeds=http://127.0.0.1:2379 lang code/test.mcl
|
||||||
|
```
|
||||||
|
|
||||||
|
This is caused by a parsing peculiarity of the CLI library that we are using.
|
||||||
|
This is tracked upstream at: [https://github.com/alexflint/go-arg/issues/239](https://github.com/alexflint/go-arg/issues/239).
|
||||||
|
We have a workaround in place to mitigate it and attempt to show you a helpful
|
||||||
|
error message, but it's also documented here in the meantime. The error you will
|
||||||
|
see is: `cli parse error: missing equals sign for list element`.
|
||||||
|
|
||||||
### The docs speaks of `--remote` but the CLI errors out?
|
### The docs speaks of `--remote` but the CLI errors out?
|
||||||
|
|
||||||
The `--remote` flag existed in an earlier version of mgmt. It was removed and
|
The `--remote` flag existed in an earlier version of mgmt. It was removed and
|
||||||
|
|||||||
@@ -70,11 +70,11 @@
|
|||||||
// Here is a simple way to test etcd clustering basics...
|
// Here is a simple way to test etcd clustering basics...
|
||||||
//
|
//
|
||||||
// ./mgmt run --tmp-prefix --no-pgp --hostname h1 empty
|
// ./mgmt run --tmp-prefix --no-pgp --hostname h1 empty
|
||||||
// ./mgmt run --tmp-prefix --no-pgp --hostname h2 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2381 --server-urls http://127.0.0.1:2382 empty
|
// ./mgmt run --tmp-prefix --no-pgp --hostname h2 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2381 --server-urls=http://127.0.0.1:2382 empty
|
||||||
// ./mgmt run --tmp-prefix --no-pgp --hostname h3 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2383 --server-urls http://127.0.0.1:2384 empty
|
// ./mgmt run --tmp-prefix --no-pgp --hostname h3 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2383 --server-urls=http://127.0.0.1:2384 empty
|
||||||
// ETCDCTL_API=3 etcdctl --endpoints 127.0.0.1:2379 put /_mgmt/chooser/dynamicsize/idealclustersize 3
|
// ETCDCTL_API=3 etcdctl --endpoints 127.0.0.1:2379 put /_mgmt/chooser/dynamicsize/idealclustersize 3
|
||||||
// ./mgmt run --tmp-prefix --no-pgp --hostname h4 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2385 --server-urls http://127.0.0.1:2386 empty
|
// ./mgmt run --tmp-prefix --no-pgp --hostname h4 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2385 --server-urls=http://127.0.0.1:2386 empty
|
||||||
// ./mgmt run --tmp-prefix --no-pgp --hostname h5 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2387 --server-urls http://127.0.0.1:2388 empty
|
// ./mgmt run --tmp-prefix --no-pgp --hostname h5 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2387 --server-urls=http://127.0.0.1:2388 empty
|
||||||
// ETCDCTL_API=3 etcdctl --endpoints 127.0.0.1:2379 member list
|
// ETCDCTL_API=3 etcdctl --endpoints 127.0.0.1:2379 member list
|
||||||
// ETCDCTL_API=3 etcdctl --endpoints 127.0.0.1:2381 put /_mgmt/chooser/dynamicsize/idealclustersize 5
|
// ETCDCTL_API=3 etcdctl --endpoints 127.0.0.1:2381 put /_mgmt/chooser/dynamicsize/idealclustersize 5
|
||||||
// ETCDCTL_API=3 etcdctl --endpoints 127.0.0.1:2381 member list
|
// ETCDCTL_API=3 etcdctl --endpoints 127.0.0.1:2381 member list
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# run this example with these commands
|
# run this example with these commands
|
||||||
# watch -n 0.1 'tail *' # run this in /tmp/mgmt/
|
# watch -n 0.1 'tail *' # run this in /tmp/mgmt/
|
||||||
# time ./mgmt run --hostname h1 --tmp-prefix --no-pgp empty
|
# time ./mgmt run --hostname h1 --tmp-prefix --no-pgp empty
|
||||||
# time ./mgmt run --hostname h2 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2381 --server-urls http://127.0.0.1:2382 --tmp-prefix --no-pgp empty
|
# time ./mgmt run --hostname h2 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2381 --server-urls=http://127.0.0.1:2382 --tmp-prefix --no-pgp empty
|
||||||
# time ./mgmt run --hostname h3 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2383 --server-urls http://127.0.0.1:2384 --tmp-prefix --no-pgp empty
|
# time ./mgmt run --hostname h3 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2383 --server-urls=http://127.0.0.1:2384 --tmp-prefix --no-pgp empty
|
||||||
# time ./mgmt run --hostname h4 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2385 --server-urls http://127.0.0.1:2386 --tmp-prefix --no-pgp empty
|
# time ./mgmt run --hostname h4 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2385 --server-urls=http://127.0.0.1:2386 --tmp-prefix --no-pgp empty
|
||||||
# time ./mgmt deploy --no-git --seeds http://127.0.0.1:2379 lang examples/lang/exchange0.mcl
|
# time ./mgmt deploy --no-git --seeds=http://127.0.0.1:2379 lang examples/lang/exchange0.mcl
|
||||||
|
|
||||||
import "sys"
|
import "sys"
|
||||||
import "world"
|
import "world"
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ import (
|
|||||||
"github.com/purpleidea/mgmt/gapi"
|
"github.com/purpleidea/mgmt/gapi"
|
||||||
mgmt "github.com/purpleidea/mgmt/lib"
|
mgmt "github.com/purpleidea/mgmt/lib"
|
||||||
"github.com/purpleidea/mgmt/pgraph"
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// XXX: this has not been updated to latest GAPI/Deploy API. Patches welcome!
|
// XXX: this has not been updated to latest GAPI/Deploy API. Patches welcome!
|
||||||
@@ -46,31 +44,20 @@ func NewMyGAPI(data *gapi.Data, name string, interval uint) (*MyGAPI, error) {
|
|||||||
return obj, obj.Init(data)
|
return obj, obj.Init(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CliFlags returns a list of flags used by the passed in subcommand.
|
// Cli takes an *Info struct, and returns our deploy if activated, and if there
|
||||||
func (obj *MyGAPI) CliFlags(string) []cli.Flag {
|
// are any validation problems, you should return an error. If there is no
|
||||||
return []cli.Flag{
|
// deploy, then you should return a nil deploy and a nil error.
|
||||||
&cli.StringFlag{
|
func (obj *MyGAPI) Cli(info *gapi.CliInfo) (*gapi.Deploy, error) {
|
||||||
Name: obj.Name,
|
//fs := info.Fs // copy files from local filesystem *into* this fs...
|
||||||
Value: "",
|
//debug := info.Debug
|
||||||
Usage: "run",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cli takes a cli.Context and some other info, and returns our GAPI. If there
|
|
||||||
// are any validation problems, you should return an error.
|
|
||||||
func (obj *MyGAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
|
|
||||||
c := cliInfo.CliContext
|
|
||||||
//fs := cliInfo.Fs // copy files from local filesystem *into* this fs...
|
|
||||||
//debug := cliInfo.Debug
|
|
||||||
//logf := func(format string, v ...interface{}) {
|
//logf := func(format string, v ...interface{}) {
|
||||||
// cliInfo.Logf(Name+": "+format, v...)
|
// info.Logf(Name+": "+format, v...)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
return &gapi.Deploy{
|
return &gapi.Deploy{
|
||||||
Name: obj.Name,
|
Name: obj.Name,
|
||||||
Noop: c.Bool("noop"),
|
Noop: info.Flags.Noop,
|
||||||
Sema: c.Int("sema"),
|
Sema: info.Flags.Sema,
|
||||||
GAPI: &MyGAPI{},
|
GAPI: &MyGAPI{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,8 +16,6 @@ import (
|
|||||||
mgmt "github.com/purpleidea/mgmt/lib"
|
mgmt "github.com/purpleidea/mgmt/lib"
|
||||||
"github.com/purpleidea/mgmt/pgraph"
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
"github.com/purpleidea/mgmt/util/errwrap"
|
"github.com/purpleidea/mgmt/util/errwrap"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// XXX: this has not been updated to latest GAPI/Deploy API. Patches welcome!
|
// XXX: this has not been updated to latest GAPI/Deploy API. Patches welcome!
|
||||||
@@ -51,31 +49,20 @@ func NewMyGAPI(data *gapi.Data, name string, interval uint) (*MyGAPI, error) {
|
|||||||
return obj, obj.Init(data)
|
return obj, obj.Init(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CliFlags returns a list of flags used by the passed in subcommand.
|
// Cli takes an *Info struct, and returns our deploy if activated, and if there
|
||||||
func (obj *MyGAPI) CliFlags(string) []cli.Flag {
|
// are any validation problems, you should return an error. If there is no
|
||||||
return []cli.Flag{
|
// deploy, then you should return a nil deploy and a nil error.
|
||||||
&cli.StringFlag{
|
func (obj *MyGAPI) Cli(info *gapi.CliInfo) (*gapi.Deploy, error) {
|
||||||
Name: obj.Name,
|
//fs := info.Fs // copy files from local filesystem *into* this fs...
|
||||||
Value: "",
|
//debug := info.Debug
|
||||||
Usage: "run",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cli takes a cli.Context and some other info, and returns our GAPI. If there
|
|
||||||
// are any validation problems, you should return an error.
|
|
||||||
func (obj *MyGAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
|
|
||||||
c := cliInfo.CliContext
|
|
||||||
//fs := cliInfo.Fs // copy files from local filesystem *into* this fs...
|
|
||||||
//debug := cliInfo.Debug
|
|
||||||
//logf := func(format string, v ...interface{}) {
|
//logf := func(format string, v ...interface{}) {
|
||||||
// cliInfo.Logf(Name+": "+format, v...)
|
// info.Logf(Name+": "+format, v...)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
return &gapi.Deploy{
|
return &gapi.Deploy{
|
||||||
Name: obj.Name,
|
Name: obj.Name,
|
||||||
Noop: c.Bool("noop"),
|
Noop: info.Flags.Noop,
|
||||||
Sema: c.Int("sema"),
|
Sema: info.Flags.Sema,
|
||||||
GAPI: &MyGAPI{},
|
GAPI: &MyGAPI{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ import (
|
|||||||
"github.com/purpleidea/mgmt/gapi"
|
"github.com/purpleidea/mgmt/gapi"
|
||||||
mgmt "github.com/purpleidea/mgmt/lib"
|
mgmt "github.com/purpleidea/mgmt/lib"
|
||||||
"github.com/purpleidea/mgmt/pgraph"
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// XXX: this has not been updated to latest GAPI/Deploy API. Patches welcome!
|
// XXX: this has not been updated to latest GAPI/Deploy API. Patches welcome!
|
||||||
@@ -46,31 +44,20 @@ func NewMyGAPI(data *gapi.Data, name string, interval uint) (*MyGAPI, error) {
|
|||||||
return obj, obj.Init(data)
|
return obj, obj.Init(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CliFlags returns a list of flags used by the passed in subcommand.
|
// Cli takes an *Info struct, and returns our deploy if activated, and if there
|
||||||
func (obj *MyGAPI) CliFlags(string) []cli.Flag {
|
// are any validation problems, you should return an error. If there is no
|
||||||
return []cli.Flag{
|
// deploy, then you should return a nil deploy and a nil error.
|
||||||
&cli.StringFlag{
|
func (obj *MyGAPI) Cli(info *gapi.CliInfo) (*gapi.Deploy, error) {
|
||||||
Name: obj.Name,
|
//fs := info.Fs // copy files from local filesystem *into* this fs...
|
||||||
Value: "",
|
//debug := info.Debug
|
||||||
Usage: "run",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cli takes a cli.Context and some other info, and returns our GAPI. If there
|
|
||||||
// are any validation problems, you should return an error.
|
|
||||||
func (obj *MyGAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
|
|
||||||
c := cliInfo.CliContext
|
|
||||||
//fs := cliInfo.Fs // copy files from local filesystem *into* this fs...
|
|
||||||
//debug := cliInfo.Debug
|
|
||||||
//logf := func(format string, v ...interface{}) {
|
//logf := func(format string, v ...interface{}) {
|
||||||
// cliInfo.Logf(Name+": "+format, v...)
|
// info.Logf(Name+": "+format, v...)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
return &gapi.Deploy{
|
return &gapi.Deploy{
|
||||||
Name: obj.Name,
|
Name: obj.Name,
|
||||||
Noop: c.Bool("noop"),
|
Noop: info.Flags.Noop,
|
||||||
Sema: c.Int("sema"),
|
Sema: info.Flags.Sema,
|
||||||
GAPI: &MyGAPI{},
|
GAPI: &MyGAPI{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ import (
|
|||||||
"github.com/purpleidea/mgmt/gapi"
|
"github.com/purpleidea/mgmt/gapi"
|
||||||
mgmt "github.com/purpleidea/mgmt/lib"
|
mgmt "github.com/purpleidea/mgmt/lib"
|
||||||
"github.com/purpleidea/mgmt/pgraph"
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// XXX: this has not been updated to latest GAPI/Deploy API. Patches welcome!
|
// XXX: this has not been updated to latest GAPI/Deploy API. Patches welcome!
|
||||||
@@ -48,31 +46,20 @@ func NewMyGAPI(data *gapi.Data, name string, interval uint, count uint) (*MyGAPI
|
|||||||
return obj, obj.Init(data)
|
return obj, obj.Init(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CliFlags returns a list of flags used by the passed in subcommand.
|
// Cli takes an *Info struct, and returns our deploy if activated, and if there
|
||||||
func (obj *MyGAPI) CliFlags(string) []cli.Flag {
|
// are any validation problems, you should return an error. If there is no
|
||||||
return []cli.Flag{
|
// deploy, then you should return a nil deploy and a nil error.
|
||||||
&cli.StringFlag{
|
func (obj *MyGAPI) Cli(info *gapi.CliInfo) (*gapi.Deploy, error) {
|
||||||
Name: obj.Name,
|
//fs := info.Fs // copy files from local filesystem *into* this fs...
|
||||||
Value: "",
|
//debug := info.Debug
|
||||||
Usage: "run",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cli takes a cli.Context and some other info, and returns our GAPI. If there
|
|
||||||
// are any validation problems, you should return an error.
|
|
||||||
func (obj *MyGAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
|
|
||||||
c := cliInfo.CliContext
|
|
||||||
//fs := cliInfo.Fs // copy files from local filesystem *into* this fs...
|
|
||||||
//debug := cliInfo.Debug
|
|
||||||
//logf := func(format string, v ...interface{}) {
|
//logf := func(format string, v ...interface{}) {
|
||||||
// cliInfo.Logf(Name+": "+format, v...)
|
// info.Logf(Name+": "+format, v...)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
return &gapi.Deploy{
|
return &gapi.Deploy{
|
||||||
Name: obj.Name,
|
Name: obj.Name,
|
||||||
Noop: c.Bool("noop"),
|
Noop: info.Flags.Noop,
|
||||||
Sema: c.Int("sema"),
|
Sema: info.Flags.Sema,
|
||||||
GAPI: &MyGAPI{},
|
GAPI: &MyGAPI{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,6 @@ import (
|
|||||||
"github.com/purpleidea/mgmt/gapi"
|
"github.com/purpleidea/mgmt/gapi"
|
||||||
mgmt "github.com/purpleidea/mgmt/lib"
|
mgmt "github.com/purpleidea/mgmt/lib"
|
||||||
"github.com/purpleidea/mgmt/pgraph"
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// XXX: this has not been updated to latest GAPI/Deploy API. Patches welcome!
|
// XXX: this has not been updated to latest GAPI/Deploy API. Patches welcome!
|
||||||
@@ -46,31 +44,20 @@ func NewMyGAPI(data *gapi.Data, name string, interval uint) (*MyGAPI, error) {
|
|||||||
return obj, obj.Init(data)
|
return obj, obj.Init(data)
|
||||||
}
|
}
|
||||||
|
|
||||||
// CliFlags returns a list of flags used by the passed in subcommand.
|
// Cli takes an *Info struct, and returns our deploy if activated, and if there
|
||||||
func (obj *MyGAPI) CliFlags(string) []cli.Flag {
|
// are any validation problems, you should return an error. If there is no
|
||||||
return []cli.Flag{
|
// deploy, then you should return a nil deploy and a nil error.
|
||||||
&cli.StringFlag{
|
func (obj *MyGAPI) Cli(info *gapi.CliInfo) (*gapi.Deploy, error) {
|
||||||
Name: obj.Name,
|
//fs := info.Fs // copy files from local filesystem *into* this fs...
|
||||||
Value: "",
|
//debug := info.Debug
|
||||||
Usage: "run",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cli takes a cli.Context and some other info, and returns our GAPI. If there
|
|
||||||
// are any validation problems, you should return an error.
|
|
||||||
func (obj *MyGAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
|
|
||||||
c := cliInfo.CliContext
|
|
||||||
//fs := cliInfo.Fs // copy files from local filesystem *into* this fs...
|
|
||||||
//debug := cliInfo.Debug
|
|
||||||
//logf := func(format string, v ...interface{}) {
|
//logf := func(format string, v ...interface{}) {
|
||||||
// cliInfo.Logf(Name+": "+format, v...)
|
// info.Logf(Name+": "+format, v...)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
return &gapi.Deploy{
|
return &gapi.Deploy{
|
||||||
Name: obj.Name,
|
Name: obj.Name,
|
||||||
Noop: c.Bool("noop"),
|
Noop: info.Flags.Noop,
|
||||||
Sema: c.Int("sema"),
|
Sema: info.Flags.Sema,
|
||||||
GAPI: &MyGAPI{},
|
GAPI: &MyGAPI{},
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,8 +23,6 @@ import (
|
|||||||
|
|
||||||
"github.com/purpleidea/mgmt/gapi"
|
"github.com/purpleidea/mgmt/gapi"
|
||||||
"github.com/purpleidea/mgmt/pgraph"
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -36,6 +34,9 @@ func init() {
|
|||||||
gapi.Register(Name, func() gapi.GAPI { return &GAPI{} }) // register
|
gapi.Register(Name, func() gapi.GAPI { return &GAPI{} }) // register
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Args is the CLI parsing structure and type of the parsed result.
|
||||||
|
type Args struct{}
|
||||||
|
|
||||||
// GAPI implements the main lang GAPI interface.
|
// GAPI implements the main lang GAPI interface.
|
||||||
type GAPI struct {
|
type GAPI struct {
|
||||||
data *gapi.Data
|
data *gapi.Data
|
||||||
@@ -44,16 +45,10 @@ type GAPI struct {
|
|||||||
wg *sync.WaitGroup // sync group for tunnel go routines
|
wg *sync.WaitGroup // sync group for tunnel go routines
|
||||||
}
|
}
|
||||||
|
|
||||||
// CliFlags returns a list of flags used by the specified subcommand.
|
// Cli takes an *Info struct, and returns our deploy if activated, and if there
|
||||||
func (obj *GAPI) CliFlags(command string) []cli.Flag {
|
// are any validation problems, you should return an error. If there is no
|
||||||
return []cli.Flag{}
|
// deploy, then you should return a nil deploy and a nil error.
|
||||||
}
|
func (obj *GAPI) Cli(*gapi.Info) (*gapi.Deploy, error) {
|
||||||
|
|
||||||
// Cli takes a cli.Context, and returns our GAPI if activated. All arguments
|
|
||||||
// should take the prefix of the registered name. On activation, if there are
|
|
||||||
// any validation problems, you should return an error. If this was not
|
|
||||||
// activated, then you should return a nil GAPI and a nil error.
|
|
||||||
func (obj *GAPI) Cli(*gapi.CliInfo) (*gapi.Deploy, error) {
|
|
||||||
return &gapi.Deploy{
|
return &gapi.Deploy{
|
||||||
Name: Name,
|
Name: Name,
|
||||||
//Noop: false,
|
//Noop: false,
|
||||||
|
|||||||
81
gapi/gapi.go
81
gapi/gapi.go
@@ -25,20 +25,6 @@ import (
|
|||||||
"github.com/purpleidea/mgmt/engine"
|
"github.com/purpleidea/mgmt/engine"
|
||||||
"github.com/purpleidea/mgmt/engine/local"
|
"github.com/purpleidea/mgmt/engine/local"
|
||||||
"github.com/purpleidea/mgmt/pgraph"
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
// CommandRun is the identifier for the "run" command. It is distinct
|
|
||||||
// from the other commands, because it can run with any front-end.
|
|
||||||
CommandRun = "run"
|
|
||||||
|
|
||||||
// CommandDeploy is the identifier for the "deploy" command.
|
|
||||||
CommandDeploy = "deploy"
|
|
||||||
|
|
||||||
// CommandGet is the identifier for the "get" (download) command.
|
|
||||||
CommandGet = "get"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// RegisteredGAPIs is a global map of all possible GAPIs which can be used. You
|
// RegisteredGAPIs is a global map of all possible GAPIs which can be used. You
|
||||||
@@ -55,15 +41,32 @@ func Register(name string, fn func() GAPI) {
|
|||||||
RegisteredGAPIs[name] = fn
|
RegisteredGAPIs[name] = fn
|
||||||
}
|
}
|
||||||
|
|
||||||
// CliInfo is the set of input values passed into the Cli method so that the
|
// Flags is some common data that comes from a higher-level command, and is used
|
||||||
// GAPI can decide if it wants to activate, and if it does, the initial handles
|
// by a subcommand. By type circularity, the subcommands can't easily access the
|
||||||
// it needs to use to do so.
|
// data in the parent command struct, so instead, the data that the parent wants
|
||||||
type CliInfo struct {
|
// to pass down, it wraps up in a struct (for API convenience) and sends it out.
|
||||||
// CliContext is the struct that is used to transfer in user input.
|
type Flags struct {
|
||||||
CliContext *cli.Context
|
Hostname *string
|
||||||
|
|
||||||
|
Noop bool
|
||||||
|
Sema int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info is the set of input values passed into the Cli method so that the GAPI
|
||||||
|
// can decide if it wants to activate, and if it does, the initial handles it
|
||||||
|
// needs to use to do so.
|
||||||
|
type Info struct {
|
||||||
|
// Args are the CLI args that are populated after parsing the args list.
|
||||||
|
// They need to be converted to the struct you are expecting to read it.
|
||||||
|
Args interface{}
|
||||||
|
|
||||||
|
// Flags are the common data which is passed down into the sub command.
|
||||||
|
Flags *Flags
|
||||||
|
|
||||||
// Fs is the filesystem the Cli method should copy data into. It usually
|
// Fs is the filesystem the Cli method should copy data into. It usually
|
||||||
// copies *from* the local filesystem using standard io functionality.
|
// copies *from* the local filesystem using standard io functionality.
|
||||||
Fs engine.Fs
|
Fs engine.Fs
|
||||||
|
|
||||||
Debug bool
|
Debug bool
|
||||||
Logf func(format string, v ...interface{})
|
Logf func(format string, v ...interface{})
|
||||||
}
|
}
|
||||||
@@ -99,18 +102,11 @@ type Next struct {
|
|||||||
// GAPI is a Graph API that represents incoming graphs and change streams. It is
|
// GAPI is a Graph API that represents incoming graphs and change streams. It is
|
||||||
// the frontend interface that needs to be implemented to use the engine.
|
// the frontend interface that needs to be implemented to use the engine.
|
||||||
type GAPI interface {
|
type GAPI interface {
|
||||||
// CliFlags is passed a Command constant specifying which command it is
|
|
||||||
// requesting the flags for. If an invalid or unsupported command is
|
|
||||||
// passed in, simply return an empty list. Similarly, it is not required
|
|
||||||
// to ever return any flags, and the GAPI may always return an empty
|
|
||||||
// list.
|
|
||||||
CliFlags(string) []cli.Flag
|
|
||||||
|
|
||||||
// Cli is run on each GAPI to give it a chance to decide if it wants to
|
// Cli is run on each GAPI to give it a chance to decide if it wants to
|
||||||
// activate, and if it does, then it will return a deploy struct. During
|
// activate, and if it does, then it will return a deploy struct. During
|
||||||
// this time, it uses the CliInfo struct as useful information to decide
|
// this time, it uses the Info struct as useful information to decide
|
||||||
// what to do.
|
// what to do.
|
||||||
Cli(*CliInfo) (*Deploy, error)
|
Cli(*Info) (*Deploy, error)
|
||||||
|
|
||||||
// Init initializes the GAPI and passes in some useful data.
|
// Init initializes the GAPI and passes in some useful data.
|
||||||
Init(*Data) error
|
Init(*Data) error
|
||||||
@@ -130,28 +126,3 @@ type GAPI interface {
|
|||||||
// TODO: change Close to Cleanup
|
// TODO: change Close to Cleanup
|
||||||
Close() error
|
Close() error
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInfo is the set of input values passed into the Get method for it to run.
|
|
||||||
type GetInfo struct {
|
|
||||||
// CliContext is the struct that is used to transfer in user input.
|
|
||||||
CliContext *cli.Context
|
|
||||||
|
|
||||||
Noop bool
|
|
||||||
Sema int
|
|
||||||
Update bool
|
|
||||||
|
|
||||||
Debug bool
|
|
||||||
Logf func(format string, v ...interface{})
|
|
||||||
}
|
|
||||||
|
|
||||||
// GettableGAPI represents additional methods that need to be implemented in
|
|
||||||
// this GAPI so that it can be used with the `get` Command. The methods in this
|
|
||||||
// interface are called independently from the rest of the GAPI interface, and
|
|
||||||
// you must not rely on shared state from those methods. Logically, this should
|
|
||||||
// probably be named "Getable", however the correct modern word is "Gettable".
|
|
||||||
type GettableGAPI interface {
|
|
||||||
GAPI // the base interface must be implemented
|
|
||||||
|
|
||||||
// Get runs the get/download method.
|
|
||||||
Get(*GetInfo) error
|
|
||||||
}
|
|
||||||
|
|||||||
3
go.mod
3
go.mod
@@ -27,7 +27,6 @@ require (
|
|||||||
github.com/prometheus/client_golang v1.17.0
|
github.com/prometheus/client_golang v1.17.0
|
||||||
github.com/sanity-io/litter v1.5.5
|
github.com/sanity-io/litter v1.5.5
|
||||||
github.com/spf13/afero v1.10.0
|
github.com/spf13/afero v1.10.0
|
||||||
github.com/urfave/cli/v2 v2.25.7
|
|
||||||
github.com/vishvananda/netlink v1.2.1-beta.2
|
github.com/vishvananda/netlink v1.2.1-beta.2
|
||||||
go.etcd.io/etcd/api/v3 v3.5.10
|
go.etcd.io/etcd/api/v3 v3.5.10
|
||||||
go.etcd.io/etcd/client/pkg/v3 v3.5.10
|
go.etcd.io/etcd/client/pkg/v3 v3.5.10
|
||||||
@@ -44,6 +43,8 @@ require (
|
|||||||
require (
|
require (
|
||||||
cloud.google.com/go v0.110.4 // indirect
|
cloud.google.com/go v0.110.4 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.0 // indirect
|
github.com/Microsoft/go-winio v0.6.0 // indirect
|
||||||
|
github.com/alexflint/go-arg v1.4.3 // indirect
|
||||||
|
github.com/alexflint/go-scalar v1.1.0 // indirect
|
||||||
github.com/armon/go-metrics v0.4.1 // indirect
|
github.com/armon/go-metrics v0.4.1 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
github.com/cenkalti/backoff/v4 v4.1.3 // indirect
|
||||||
|
|||||||
10
go.sum
10
go.sum
@@ -61,6 +61,10 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
|
|||||||
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
|
||||||
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
|
||||||
|
github.com/alexflint/go-arg v1.4.3 h1:9rwwEBpMXfKQKceuZfYcwuc/7YY7tWJbFsgG5cAU/uo=
|
||||||
|
github.com/alexflint/go-arg v1.4.3/go.mod h1:3PZ/wp/8HuqRZMUUgu7I+e1qcpUbvmS258mRXkFH4IA=
|
||||||
|
github.com/alexflint/go-scalar v1.1.0 h1:aaAouLLzI9TChcPXotr6gUhq+Scr8rl0P9P4PnltbhM=
|
||||||
|
github.com/alexflint/go-scalar v1.1.0/go.mod h1:LoFvNMqS1CPrMVltza4LvnGKhaSpc3oyLEBUZVhhS2o=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA=
|
||||||
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c=
|
||||||
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
|
||||||
@@ -782,12 +786,6 @@ github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4 h1:hl6sK6aFgTLISijk6xIz
|
|||||||
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
github.com/u-root/uio v0.0.0-20220204230159-dac05f7d2cb4/go.mod h1:LpEX5FO/cB+WF4TYGY1V5qktpaZLkKkSegbr0V4eYXA=
|
||||||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg=
|
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63 h1:YcojQL98T/OO+rybuzn2+5KrD5dBwXIvYBvQ2cD3Avg=
|
||||||
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
github.com/u-root/uio v0.0.0-20230305220412-3e8cd9d6bf63/go.mod h1:eLL9Nub3yfAho7qB0MzZizFhTU2QkLeoVsWdHtDW264=
|
||||||
github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M=
|
|
||||||
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
|
|
||||||
github.com/urfave/cli/v2 v2.20.2 h1:dKA0LUjznZpwmmbrc0pOgcLTEilnHeM8Av9Yng77gHM=
|
|
||||||
github.com/urfave/cli/v2 v2.20.2/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
|
|
||||||
github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs=
|
|
||||||
github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ=
|
|
||||||
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 h1:8mhqcHPqTMhSPoslhGYihEgSfc77+7La1P6kiB6+9So=
|
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54 h1:8mhqcHPqTMhSPoslhGYihEgSfc77+7La1P6kiB6+9So=
|
||||||
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
github.com/vishvananda/netlink v1.1.1-0.20211118161826-650dca95af54/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho=
|
||||||
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
|
github.com/vishvananda/netlink v1.2.1-beta.2 h1:Llsql0lnQEbHj0I1OuKyp8otXp0r3q0mPkuhwHfStVs=
|
||||||
|
|||||||
@@ -428,7 +428,7 @@ func (obj *Instance) DeployLang(code string) error {
|
|||||||
cmdArgs := []string{
|
cmdArgs := []string{
|
||||||
"deploy", // mode
|
"deploy", // mode
|
||||||
"--no-git",
|
"--no-git",
|
||||||
"--seeds", obj.clientURL,
|
fmt.Sprintf("--seeds=%s", obj.clientURL),
|
||||||
"lang", filename,
|
"lang", filename,
|
||||||
}
|
}
|
||||||
obj.Logf("run: %s %s", cmdName, strings.Join(cmdArgs, " "))
|
obj.Logf("run: %s %s", cmdName, strings.Join(cmdArgs, " "))
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
|
|
||||||
// test with:
|
// test with:
|
||||||
// time ./mgmt run --hostname h1 --tmp-prefix --no-pgp lang examples/lang/schedule0.mcl
|
// time ./mgmt run --hostname h1 --tmp-prefix --no-pgp lang examples/lang/schedule0.mcl
|
||||||
// time ./mgmt run --hostname h2 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2381 --server-urls http://127.0.0.1:2382 --tmp-prefix --no-pgp lang examples/lang/schedule0.mcl
|
// time ./mgmt run --hostname h2 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2381 --server-urls=http://127.0.0.1:2382 --tmp-prefix --no-pgp lang examples/lang/schedule0.mcl
|
||||||
// time ./mgmt run --hostname h3 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2383 --server-urls http://127.0.0.1:2384 --tmp-prefix --no-pgp lang examples/lang/schedule0.mcl
|
// time ./mgmt run --hostname h3 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2383 --server-urls=http://127.0.0.1:2384 --tmp-prefix --no-pgp lang examples/lang/schedule0.mcl
|
||||||
// kill h2 (should see h1 and h3 pick [h1, h3] instead)
|
// kill h2 (should see h1 and h3 pick [h1, h3] instead)
|
||||||
// restart h2 (should see [h1, h3] as before)
|
// restart h2 (should see [h1, h3] as before)
|
||||||
// kill h3 (should see h1 and h2 pick [h1, h2] instead)
|
// kill h3 (should see h1 and h2 pick [h1, h2] instead)
|
||||||
|
|||||||
@@ -42,24 +42,40 @@ import (
|
|||||||
"github.com/purpleidea/mgmt/util/errwrap"
|
"github.com/purpleidea/mgmt/util/errwrap"
|
||||||
|
|
||||||
"github.com/spf13/afero"
|
"github.com/spf13/afero"
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Name is the name of this frontend.
|
// Name is the name of this frontend.
|
||||||
Name = "lang"
|
Name = "lang"
|
||||||
|
|
||||||
// flagModulePath is the name of the module-path flag.
|
|
||||||
flagModulePath = "module-path"
|
|
||||||
|
|
||||||
// flagDownload is the name of the download flag.
|
|
||||||
flagDownload = "download"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
gapi.Register(Name, func() gapi.GAPI { return &GAPI{} }) // register
|
gapi.Register(Name, func() gapi.GAPI { return &GAPI{} }) // register
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Args is the CLI parsing structure and type of the parsed result.
|
||||||
|
type Args struct {
|
||||||
|
// Input is the input mcl code or file path or any input specification.
|
||||||
|
Input string `arg:"positional,required"`
|
||||||
|
|
||||||
|
// TODO: removed (temporarily?)
|
||||||
|
//Stdin bool `arg:"--stdin" help:"use passthrough stdin"`
|
||||||
|
|
||||||
|
Download bool `arg:"--download" help:"download any missing imports"`
|
||||||
|
OnlyDownload bool `arg:"--only-download" help:"stop after downloading any missing imports"`
|
||||||
|
Update bool `arg:"--update" help:"update all dependencies to the latest versions"`
|
||||||
|
|
||||||
|
OnlyUnify bool `arg:"--only-unify" help:"stop after type unification"`
|
||||||
|
SkipUnify bool `arg:"--skip-unify" help:"skip type unification"`
|
||||||
|
|
||||||
|
Depth int `arg:"--depth" default:"-1" help:"max recursion depth limit (-1 is unlimited)"`
|
||||||
|
|
||||||
|
// The default of 0 means any error is a failure by default.
|
||||||
|
Retry int `arg:"--depth" help:"max number of retries (-1 is unlimited)"`
|
||||||
|
|
||||||
|
ModulePath string `arg:"--module-path,env:MGMT_MODULE_PATH" help:"choose the modules path (absolute)"`
|
||||||
|
}
|
||||||
|
|
||||||
// GAPI implements the main lang GAPI interface.
|
// GAPI implements the main lang GAPI interface.
|
||||||
type GAPI struct {
|
type GAPI struct {
|
||||||
InputURI string // input URI of code file system to run
|
InputURI string // input URI of code file system to run
|
||||||
@@ -78,85 +94,9 @@ type GAPI struct {
|
|||||||
wg *sync.WaitGroup // sync group for tunnel go routines
|
wg *sync.WaitGroup // sync group for tunnel go routines
|
||||||
}
|
}
|
||||||
|
|
||||||
// CliFlags returns a list of flags used by the specified subcommand.
|
// Cli takes an *Info struct, and returns our deploy if activated, and if there
|
||||||
func (obj *GAPI) CliFlags(command string) []cli.Flag {
|
// are any validation problems, you should return an error. If there is no
|
||||||
result := []cli.Flag{}
|
// deploy, then you should return a nil deploy and a nil error. This is passed
|
||||||
modulePath := &cli.StringFlag{
|
|
||||||
Name: flagModulePath,
|
|
||||||
Value: "", // empty by default
|
|
||||||
Usage: "choose the modules path (absolute)",
|
|
||||||
EnvVars: []string{"MGMT_MODULE_PATH"},
|
|
||||||
}
|
|
||||||
|
|
||||||
// add this only to run (not needed for get or deploy)
|
|
||||||
if command == gapi.CommandRun {
|
|
||||||
runFlags := []cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: flagDownload,
|
|
||||||
Usage: "download any missing imports (as the get command does)",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "update",
|
|
||||||
Usage: "update all dependencies to the latest versions",
|
|
||||||
},
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "only-unify",
|
|
||||||
Usage: "stop after type unification",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
result = append(result, runFlags...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if command == gapi.CommandRun || command == gapi.CommandDeploy {
|
|
||||||
flags := []cli.Flag{
|
|
||||||
&cli.BoolFlag{
|
|
||||||
Name: "skip-unify",
|
|
||||||
Usage: "skip type unification",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
result = append(result, flags...)
|
|
||||||
}
|
|
||||||
|
|
||||||
switch command {
|
|
||||||
case gapi.CommandGet:
|
|
||||||
flags := []cli.Flag{
|
|
||||||
&cli.IntFlag{
|
|
||||||
Name: "depth d",
|
|
||||||
Value: -1,
|
|
||||||
Usage: "max recursion depth limit (-1 is unlimited)",
|
|
||||||
},
|
|
||||||
&cli.IntFlag{
|
|
||||||
Name: "retry r",
|
|
||||||
Value: 0, // any error is a failure by default
|
|
||||||
Usage: "max number of retries (-1 is unlimited)",
|
|
||||||
},
|
|
||||||
//modulePath, // already defined below in fallthrough
|
|
||||||
}
|
|
||||||
result = append(result, flags...)
|
|
||||||
fallthrough // at the moment, we want the same code input arg...
|
|
||||||
case gapi.CommandRun:
|
|
||||||
fallthrough
|
|
||||||
case gapi.CommandDeploy:
|
|
||||||
flags := []cli.Flag{
|
|
||||||
// TODO: removed (temporarily?)
|
|
||||||
//*cli.BoolFlag{
|
|
||||||
// Name: "stdin",
|
|
||||||
// Usage: "use passthrough stdin",
|
|
||||||
//},
|
|
||||||
modulePath,
|
|
||||||
}
|
|
||||||
result = append(result, flags...)
|
|
||||||
default:
|
|
||||||
return []cli.Flag{}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cli takes a cli.Context, and returns our GAPI if activated. All arguments
|
|
||||||
// should take the prefix of the registered name. On activation, if there are
|
|
||||||
// any validation problems, you should return an error. If this was not
|
|
||||||
// activated, then you should return a nil GAPI and a nil error. This is passed
|
|
||||||
// in a functional file system interface. For standalone usage, this will be a
|
// in a functional file system interface. For standalone usage, this will be a
|
||||||
// temporary memory-backed filesystem so that the same deploy API is used, and
|
// temporary memory-backed filesystem so that the same deploy API is used, and
|
||||||
// for normal clustered usage, this will be the normal implementation which is
|
// for normal clustered usage, this will be the normal implementation which is
|
||||||
@@ -167,29 +107,22 @@ func (obj *GAPI) CliFlags(command string) []cli.Flag {
|
|||||||
// or from `run` (both of which can activate this GAPI) is that `deploy` copies
|
// or from `run` (both of which can activate this GAPI) is that `deploy` copies
|
||||||
// to an etcdFs, and `run` copies to a memFs. All GAPI's run off of the fs that
|
// to an etcdFs, and `run` copies to a memFs. All GAPI's run off of the fs that
|
||||||
// is passed in.
|
// is passed in.
|
||||||
func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
|
func (obj *GAPI) Cli(info *gapi.Info) (*gapi.Deploy, error) {
|
||||||
c := cliInfo.CliContext
|
args, ok := info.Args.(*Args)
|
||||||
cliContext := c.Lineage()[1]
|
if !ok {
|
||||||
if cliContext == nil {
|
// programming error
|
||||||
return nil, fmt.Errorf("could not get cli context")
|
return nil, fmt.Errorf("could not convert to our struct")
|
||||||
}
|
|
||||||
fs := cliInfo.Fs // copy files from local filesystem *into* this fs...
|
|
||||||
prefix := "" // TODO: do we need this?
|
|
||||||
debug := cliInfo.Debug
|
|
||||||
logf := func(format string, v ...interface{}) {
|
|
||||||
cliInfo.Logf(Name+": "+format, v...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if l := c.NArg(); l != 1 {
|
fs := info.Fs // copy files from local filesystem *into* this fs...
|
||||||
if l > 1 {
|
prefix := "" // TODO: do we need this?
|
||||||
return nil, fmt.Errorf("input program must be a single arg")
|
debug := info.Debug
|
||||||
}
|
logf := func(format string, v ...interface{}) {
|
||||||
return nil, fmt.Errorf("must specify input program")
|
info.Logf(Name+": "+format, v...)
|
||||||
}
|
}
|
||||||
input := c.Args().Get(0)
|
|
||||||
|
|
||||||
// empty by default (don't set for deploy, only download)
|
// empty by default (don't set for deploy, only download)
|
||||||
modules := c.String(flagModulePath)
|
modules := args.ModulePath
|
||||||
if modules != "" && (!strings.HasPrefix(modules, "/") || !strings.HasSuffix(modules, "/")) {
|
if modules != "" && (!strings.HasPrefix(modules, "/") || !strings.HasSuffix(modules, "/")) {
|
||||||
return nil, fmt.Errorf("module path is not an absolute directory")
|
return nil, fmt.Errorf("module path is not an absolute directory")
|
||||||
}
|
}
|
||||||
@@ -207,7 +140,7 @@ func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
|
|||||||
|
|
||||||
// the fs input here is the local fs we're reading to get the files from
|
// the fs input here is the local fs we're reading to get the files from
|
||||||
// this is different from the fs variable which is our output dest!!!
|
// this is different from the fs variable which is our output dest!!!
|
||||||
output, err := inputs.ParseInput(input, localFs)
|
output, err := inputs.ParseInput(args.Input, localFs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errwrap.Wrapf(err, "could not activate an input parser")
|
return nil, errwrap.Wrapf(err, "could not activate an input parser")
|
||||||
}
|
}
|
||||||
@@ -224,15 +157,17 @@ func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
|
|||||||
logf("behold, the AST: %+v", xast)
|
logf("behold, the AST: %+v", xast)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// This runs the necessary downloads. It passes a downloader in, which
|
||||||
|
// can be used to pull down or update any missing imports.
|
||||||
var downloader interfaces.Downloader
|
var downloader interfaces.Downloader
|
||||||
if c.IsSet(flagDownload) && c.Bool(flagDownload) {
|
if args.Download {
|
||||||
downloadInfo := &interfaces.DownloadInfo{
|
downloadInfo := &interfaces.DownloadInfo{
|
||||||
Fs: downloadFs, // the local fs!
|
Fs: downloadFs, // the local fs!
|
||||||
|
|
||||||
// flags are passed in during Init()
|
// flags are passed in during Init()
|
||||||
Noop: cliContext.Bool("noop"),
|
Noop: info.Flags.Noop,
|
||||||
Sema: cliContext.Int("sema"),
|
Sema: info.Flags.Sema,
|
||||||
Update: c.Bool("update"),
|
Update: args.Update,
|
||||||
|
|
||||||
Debug: debug,
|
Debug: debug,
|
||||||
Logf: func(format string, v ...interface{}) {
|
Logf: func(format string, v ...interface{}) {
|
||||||
@@ -242,8 +177,8 @@ func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
|
|||||||
}
|
}
|
||||||
// this fulfills the interfaces.Downloader interface
|
// this fulfills the interfaces.Downloader interface
|
||||||
downloader = &download.Downloader{
|
downloader = &download.Downloader{
|
||||||
Depth: c.Int("depth"), // default of infinite is -1
|
Depth: args.Depth, // default of infinite is -1
|
||||||
Retry: c.Int("retry"), // infinite is -1
|
Retry: args.Retry, // infinite is -1
|
||||||
}
|
}
|
||||||
if err := downloader.Init(downloadInfo); err != nil {
|
if err := downloader.Init(downloadInfo); err != nil {
|
||||||
return nil, errwrap.Wrapf(err, "could not initialize downloader")
|
return nil, errwrap.Wrapf(err, "could not initialize downloader")
|
||||||
@@ -297,10 +232,14 @@ func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
|
|||||||
return nil, errwrap.Wrapf(err, "could not interpolate AST")
|
return nil, errwrap.Wrapf(err, "could not interpolate AST")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
hostname := ""
|
||||||
|
if h := info.Flags.Hostname; h != nil {
|
||||||
|
hostname = *h // it's optional, since this value is not used...
|
||||||
|
}
|
||||||
variables := map[string]interfaces.Expr{
|
variables := map[string]interfaces.Expr{
|
||||||
"purpleidea": &ast.ExprStr{V: "hello world!"}, // james says hi
|
"purpleidea": &ast.ExprStr{V: "hello world!"}, // james says hi
|
||||||
// TODO: change to a func when we can change hostname dynamically!
|
// TODO: change to a func when we can change hostname dynamically!
|
||||||
"hostname": &ast.ExprStr{V: ""}, // NOTE: empty b/c not used
|
"hostname": &ast.ExprStr{V: hostname}, // NOTE: can be empty b/c not used
|
||||||
}
|
}
|
||||||
consts := ast.VarPrefixToVariablesScope(vars.ConstNamespace) // strips prefix!
|
consts := ast.VarPrefixToVariablesScope(vars.ConstNamespace) // strips prefix!
|
||||||
addback := vars.ConstNamespace + interfaces.ModuleSep // add it back...
|
addback := vars.ConstNamespace + interfaces.ModuleSep // add it back...
|
||||||
@@ -327,7 +266,12 @@ func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
|
|||||||
return nil, errwrap.Wrapf(err, "could not set scope")
|
return nil, errwrap.Wrapf(err, "could not set scope")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !c.Bool("skip-unify") {
|
// Previously the `get` command would stop here.
|
||||||
|
if args.OnlyDownload {
|
||||||
|
return nil, nil // success!
|
||||||
|
}
|
||||||
|
|
||||||
|
if !args.SkipUnify {
|
||||||
// apply type unification
|
// apply type unification
|
||||||
unificationLogf := func(format string, v ...interface{}) {
|
unificationLogf := func(format string, v ...interface{}) {
|
||||||
if debug { // unification only has debug messages...
|
if debug { // unification only has debug messages...
|
||||||
@@ -349,13 +293,13 @@ func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
|
|||||||
formatted = delta.Truncate(time.Millisecond).String()
|
formatted = delta.Truncate(time.Millisecond).String()
|
||||||
}
|
}
|
||||||
if unifyErr != nil {
|
if unifyErr != nil {
|
||||||
if c.Bool("only-unify") {
|
if args.OnlyUnify {
|
||||||
logf("type unification failed after %s", formatted)
|
logf("type unification failed after %s", formatted)
|
||||||
}
|
}
|
||||||
return nil, errwrap.Wrapf(unifyErr, "could not unify types")
|
return nil, errwrap.Wrapf(unifyErr, "could not unify types")
|
||||||
}
|
}
|
||||||
|
|
||||||
if c.Bool("only-unify") {
|
if args.OnlyUnify {
|
||||||
logf("type unification succeeded in %s", formatted)
|
logf("type unification succeeded in %s", formatted)
|
||||||
return nil, nil // we end early
|
return nil, nil // we end early
|
||||||
}
|
}
|
||||||
@@ -458,7 +402,7 @@ func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
|
|||||||
|
|
||||||
// display the deploy fs tree
|
// display the deploy fs tree
|
||||||
if debug || true { // TODO: should this only be shown on debug?
|
if debug || true { // TODO: should this only be shown on debug?
|
||||||
logf("input: %s", input)
|
logf("input: %s", args.Input)
|
||||||
tree, err := util.FsTree(fs, "/")
|
tree, err := util.FsTree(fs, "/")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -468,8 +412,8 @@ func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
|
|||||||
|
|
||||||
return &gapi.Deploy{
|
return &gapi.Deploy{
|
||||||
Name: Name,
|
Name: Name,
|
||||||
Noop: c.Bool("noop"),
|
Noop: info.Flags.Noop,
|
||||||
Sema: c.Int("sema"),
|
Sema: info.Flags.Sema,
|
||||||
GAPI: &GAPI{
|
GAPI: &GAPI{
|
||||||
InputURI: fs.URI(),
|
InputURI: fs.URI(),
|
||||||
// TODO: add properties here...
|
// TODO: add properties here...
|
||||||
@@ -691,142 +635,3 @@ func (obj *GAPI) Close() error {
|
|||||||
obj.initialized = false // closed = true
|
obj.initialized = false // closed = true
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get runs the necessary downloads. This basically runs the lexer, parser and
|
|
||||||
// sets the scope so that all the imports are followed. It passes a downloader
|
|
||||||
// in, which can be used to pull down or update any missing imports. This will
|
|
||||||
// also work when called with the download flag during a normal execution run.
|
|
||||||
func (obj *GAPI) Get(getInfo *gapi.GetInfo) error {
|
|
||||||
c := getInfo.CliContext
|
|
||||||
cliContext := c.Lineage()[1]
|
|
||||||
if cliContext == nil {
|
|
||||||
return fmt.Errorf("could not get cli context")
|
|
||||||
}
|
|
||||||
prefix := "" // TODO: do we need this?
|
|
||||||
debug := getInfo.Debug
|
|
||||||
logf := getInfo.Logf
|
|
||||||
|
|
||||||
if l := c.NArg(); l != 1 {
|
|
||||||
if l > 1 {
|
|
||||||
return fmt.Errorf("input program must be a single arg")
|
|
||||||
}
|
|
||||||
return fmt.Errorf("must specify input program")
|
|
||||||
}
|
|
||||||
input := c.Args().Get(0)
|
|
||||||
|
|
||||||
// empty by default (don't set for deploy, only download)
|
|
||||||
modules := c.String(flagModulePath)
|
|
||||||
if modules != "" && (!strings.HasPrefix(modules, "/") || !strings.HasSuffix(modules, "/")) {
|
|
||||||
return fmt.Errorf("module path is not an absolute directory")
|
|
||||||
}
|
|
||||||
|
|
||||||
osFs := afero.NewOsFs()
|
|
||||||
readOnlyOsFs := afero.NewReadOnlyFs(osFs) // can't be readonly to dl!
|
|
||||||
//bp := afero.NewBasePathFs(osFs, base) // TODO: can this prevent parent dir access?
|
|
||||||
afs := &afero.Afero{Fs: readOnlyOsFs} // wrap so that we're implementing ioutil
|
|
||||||
localFs := &util.AferoFs{Afero: afs} // always the local fs
|
|
||||||
downloadAfs := &afero.Afero{Fs: osFs}
|
|
||||||
downloadFs := &util.AferoFs{Afero: downloadAfs} // TODO: use with a parent path preventer?
|
|
||||||
|
|
||||||
// the fs input here is the local fs we're reading to get the files from
|
|
||||||
// this is different from the fs variable which is our output dest!!!
|
|
||||||
output, err := inputs.ParseInput(input, localFs)
|
|
||||||
if err != nil {
|
|
||||||
return errwrap.Wrapf(err, "could not activate an input parser")
|
|
||||||
}
|
|
||||||
|
|
||||||
// no need to run recursion detection since this is the beginning
|
|
||||||
// TODO: do the paths need to be cleaned for "../" before comparison?
|
|
||||||
|
|
||||||
logf("lexing/parsing...")
|
|
||||||
ast, err := parser.LexParse(bytes.NewReader(output.Main))
|
|
||||||
if err != nil {
|
|
||||||
return errwrap.Wrapf(err, "could not generate AST")
|
|
||||||
}
|
|
||||||
if debug {
|
|
||||||
logf("behold, the AST: %+v", ast)
|
|
||||||
}
|
|
||||||
|
|
||||||
downloadInfo := &interfaces.DownloadInfo{
|
|
||||||
Fs: downloadFs, // the local fs!
|
|
||||||
|
|
||||||
// flags are passed in during Init()
|
|
||||||
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?
|
|
||||||
logf("get: "+format, v...)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// this fulfills the interfaces.Downloader interface
|
|
||||||
downloader := &download.Downloader{
|
|
||||||
Depth: c.Int("depth"), // default of infinite is -1
|
|
||||||
Retry: c.Int("retry"), // infinite is -1
|
|
||||||
}
|
|
||||||
if err := downloader.Init(downloadInfo); err != nil {
|
|
||||||
return errwrap.Wrapf(err, "could not initialize downloader")
|
|
||||||
}
|
|
||||||
|
|
||||||
importGraph, err := pgraph.NewGraph("importGraph")
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
importVertex := &pgraph.SelfVertex{
|
|
||||||
Name: "", // first node is the empty string
|
|
||||||
Graph: importGraph, // store a reference to ourself
|
|
||||||
}
|
|
||||||
importGraph.AddVertex(importVertex)
|
|
||||||
|
|
||||||
logf("init...")
|
|
||||||
// init and validate the structure of the AST
|
|
||||||
data := &interfaces.Data{
|
|
||||||
// TODO: add missing fields here if/when needed
|
|
||||||
Fs: output.FS, // formerly: localFs // the local fs!
|
|
||||||
FsURI: output.FS.URI(), // formerly: localFs.URI() // TODO: is this right?
|
|
||||||
Base: output.Base, // base dir (absolute path) that this is rooted in
|
|
||||||
Files: output.Files,
|
|
||||||
Imports: importVertex,
|
|
||||||
Metadata: output.Metadata,
|
|
||||||
Modules: modules,
|
|
||||||
|
|
||||||
LexParser: parser.LexParse,
|
|
||||||
Downloader: downloader,
|
|
||||||
StrInterpolater: interpolate.StrInterpolate,
|
|
||||||
//Local: obj.Local, // TODO: do we need this?
|
|
||||||
//World: obj.World, // TODO: do we need this?
|
|
||||||
|
|
||||||
Prefix: prefix,
|
|
||||||
Debug: debug,
|
|
||||||
Logf: func(format string, v ...interface{}) {
|
|
||||||
// TODO: is this a sane prefix to use here?
|
|
||||||
logf("ast: "+format, v...)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
// some of this might happen *after* interpolate in SetScope or Unify...
|
|
||||||
if err := ast.Init(data); err != nil {
|
|
||||||
return errwrap.Wrapf(err, "could not init and validate AST")
|
|
||||||
}
|
|
||||||
|
|
||||||
logf("interpolating...")
|
|
||||||
// interpolate strings and other expansionable nodes in AST
|
|
||||||
iast, err := ast.Interpolate()
|
|
||||||
if err != nil {
|
|
||||||
return errwrap.Wrapf(err, "could not interpolate AST")
|
|
||||||
}
|
|
||||||
|
|
||||||
logf("building scope...")
|
|
||||||
// propagate the scope down through the AST...
|
|
||||||
// we use SetScope because it follows all of the imports through. i
|
|
||||||
// don't think we need to pass in an initial scope because the download
|
|
||||||
// operation shouldn't depend on any initial scope values, since those
|
|
||||||
// would all be runtime changes, and we do not support dynamic imports!
|
|
||||||
// XXX: Add non-empty scope?
|
|
||||||
if err := iast.SetScope(nil); err != nil { // empty initial scope!
|
|
||||||
return errwrap.Wrapf(err, "could not set scope")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil // success!
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -2107,7 +2107,7 @@ func TestAstFunc3(t *testing.T) {
|
|||||||
t.Logf("test #%d: graph: %+v", index, ograph)
|
t.Logf("test #%d: graph: %+v", index, ograph)
|
||||||
|
|
||||||
// setup converger
|
// setup converger
|
||||||
convergedTimeout := int64(5)
|
convergedTimeout := 5
|
||||||
converger := converger.New(
|
converger := converger.New(
|
||||||
convergedTimeout,
|
convergedTimeout,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ type Main struct {
|
|||||||
Sema int // add a semaphore with this lock count to each resource
|
Sema int // add a semaphore with this lock count to each resource
|
||||||
Graphviz string // output file for graphviz data
|
Graphviz string // output file for graphviz data
|
||||||
GraphvizFilter string // graphviz filter to use
|
GraphvizFilter string // graphviz filter to use
|
||||||
ConvergedTimeout int64 // approximately this many seconds of inactivity means we're in a converged state; -1 to disable
|
ConvergedTimeout int // approximately this many seconds of inactivity means we're in a converged state; -1 to disable
|
||||||
ConvergedTimeoutNoExit bool // don't exit on converged timeout
|
ConvergedTimeoutNoExit bool // don't exit on converged timeout
|
||||||
ConvergedStatusFile string // file to append converged status to
|
ConvergedStatusFile string // file to append converged status to
|
||||||
MaxRuntime uint // exit after a maximum of approximately this many seconds
|
MaxRuntime uint // exit after a maximum of approximately this many seconds
|
||||||
|
|||||||
13
main.go
13
main.go
@@ -18,6 +18,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
_ "embed"
|
_ "embed"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
@@ -29,8 +30,9 @@ import (
|
|||||||
|
|
||||||
// These constants are some global variables that are used throughout the code.
|
// These constants are some global variables that are used throughout the code.
|
||||||
const (
|
const (
|
||||||
Debug = false // add additional log messages
|
tagline = "next generation config management"
|
||||||
Verbose = false // add extra log message output
|
debug = false // add additional log messages
|
||||||
|
verbose = false // add extra log message output
|
||||||
)
|
)
|
||||||
|
|
||||||
// set at compile time
|
// set at compile time
|
||||||
@@ -60,13 +62,14 @@ func main() {
|
|||||||
Program: program,
|
Program: program,
|
||||||
Version: version,
|
Version: version,
|
||||||
Copying: copying,
|
Copying: copying,
|
||||||
|
Tagline: tagline,
|
||||||
Flags: cliUtil.Flags{
|
Flags: cliUtil.Flags{
|
||||||
Debug: Debug,
|
Debug: debug,
|
||||||
Verbose: Verbose,
|
Verbose: verbose,
|
||||||
},
|
},
|
||||||
Args: os.Args,
|
Args: os.Args,
|
||||||
}
|
}
|
||||||
if err := cli.CLI(data); err != nil {
|
if err := cli.CLI(context.Background(), data); err != nil {
|
||||||
fmt.Println(err)
|
fmt.Println(err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -16,16 +16,16 @@ fi
|
|||||||
trap 'pkill -9 mgmt' EXIT
|
trap 'pkill -9 mgmt' EXIT
|
||||||
|
|
||||||
$TIMEOUT "$MGMT" run --hostname h1 --tmp-prefix --no-pgp empty &
|
$TIMEOUT "$MGMT" run --hostname h1 --tmp-prefix --no-pgp empty &
|
||||||
$TIMEOUT "$MGMT" run --hostname h2 --tmp-prefix --no-pgp --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2381 --server-urls http://127.0.0.1:2382 empty &
|
$TIMEOUT "$MGMT" run --hostname h2 --tmp-prefix --no-pgp --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2381 --server-urls=http://127.0.0.1:2382 empty &
|
||||||
$TIMEOUT "$MGMT" run --hostname h3 --tmp-prefix --no-pgp --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2383 --server-urls http://127.0.0.1:2384 empty &
|
$TIMEOUT "$MGMT" run --hostname h3 --tmp-prefix --no-pgp --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2383 --server-urls=http://127.0.0.1:2384 empty &
|
||||||
|
|
||||||
# wait for everything to converge
|
# wait for everything to converge
|
||||||
sleep 30s
|
sleep 30s
|
||||||
|
|
||||||
ETCDCTL_API=3 etcdctl --endpoints 127.0.0.1:2379 put /_mgmt/chooser/dynamicsize/idealclustersize 3
|
ETCDCTL_API=3 etcdctl --endpoints 127.0.0.1:2379 put /_mgmt/chooser/dynamicsize/idealclustersize 3
|
||||||
|
|
||||||
$TIMEOUT "$MGMT" run --hostname h4 --tmp-prefix --no-pgp --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2385 --server-urls http://127.0.0.1:2386 empty &
|
$TIMEOUT "$MGMT" run --hostname h4 --tmp-prefix --no-pgp --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2385 --server-urls=http://127.0.0.1:2386 empty &
|
||||||
$TIMEOUT "$MGMT" run --hostname h5 --tmp-prefix --no-pgp --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2387 --server-urls http://127.0.0.1:2388 empty &
|
$TIMEOUT "$MGMT" run --hostname h5 --tmp-prefix --no-pgp --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2387 --server-urls=http://127.0.0.1:2388 empty &
|
||||||
|
|
||||||
# wait for everything to converge
|
# wait for everything to converge
|
||||||
sleep 30s
|
sleep 30s
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ $TIMEOUT "$MGMT" run --hostname h1 --tmp-prefix empty &
|
|||||||
pid1=$!
|
pid1=$!
|
||||||
sleep 45s # let it startup
|
sleep 45s # let it startup
|
||||||
|
|
||||||
$TIMEOUT "$MGMT" run --hostname h2 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2381 --server-urls http://127.0.0.1:2382 --tmp-prefix empty &
|
$TIMEOUT "$MGMT" run --hostname h2 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2381 --server-urls=http://127.0.0.1:2382 --tmp-prefix empty &
|
||||||
pid2=$!
|
pid2=$!
|
||||||
sleep 45s
|
sleep 45s
|
||||||
|
|
||||||
$TIMEOUT "$MGMT" run --hostname h3 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2383 --server-urls http://127.0.0.1:2384 --tmp-prefix empty &
|
$TIMEOUT "$MGMT" run --hostname h3 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2383 --server-urls=http://127.0.0.1:2384 --tmp-prefix empty &
|
||||||
pid3=$!
|
pid3=$!
|
||||||
sleep 45s
|
sleep 45s
|
||||||
|
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ $TIMEOUT "$MGMT" run --hostname h1 --tmp-prefix empty &
|
|||||||
pid1=$!
|
pid1=$!
|
||||||
sleep 15s # let it startup
|
sleep 15s # let it startup
|
||||||
|
|
||||||
$TIMEOUT "$MGMT" run --hostname h2 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2381 --server-urls http://127.0.0.1:2382 --tmp-prefix empty &
|
$TIMEOUT "$MGMT" run --hostname h2 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2381 --server-urls=http://127.0.0.1:2382 --tmp-prefix empty &
|
||||||
pid2=$!
|
pid2=$!
|
||||||
sleep 15s
|
sleep 15s
|
||||||
|
|
||||||
$TIMEOUT "$MGMT" run --hostname h3 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2383 --server-urls http://127.0.0.1:2384 --tmp-prefix empty &
|
$TIMEOUT "$MGMT" run --hostname h3 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2383 --server-urls=http://127.0.0.1:2384 --tmp-prefix empty &
|
||||||
pid3=$!
|
pid3=$!
|
||||||
sleep 15s
|
sleep 15s
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ $TIMEOUT "$MGMT" run --hostname h1 --tmp-prefix empty &
|
|||||||
pid1=$!
|
pid1=$!
|
||||||
sleep 45s # let it startup
|
sleep 45s # let it startup
|
||||||
|
|
||||||
$TIMEOUT "$MGMT" run --hostname h2 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2381 --server-urls http://127.0.0.1:2382 --tmp-prefix empty &
|
$TIMEOUT "$MGMT" run --hostname h2 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2381 --server-urls=http://127.0.0.1:2382 --tmp-prefix empty &
|
||||||
pid2=$!
|
pid2=$!
|
||||||
sleep 45s
|
sleep 45s
|
||||||
|
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ $TIMEOUT "$MGMT" run --hostname h1 --tmp-prefix empty &
|
|||||||
pid1=$!
|
pid1=$!
|
||||||
sleep 15s # let it startup
|
sleep 15s # let it startup
|
||||||
|
|
||||||
$TIMEOUT "$MGMT" run --hostname h2 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2381 --server-urls http://127.0.0.1:2382 --tmp-prefix empty &
|
$TIMEOUT "$MGMT" run --hostname h2 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2381 --server-urls=http://127.0.0.1:2382 --tmp-prefix empty &
|
||||||
pid2=$!
|
pid2=$!
|
||||||
sleep 15s
|
sleep 15s
|
||||||
|
|
||||||
|
|||||||
@@ -14,16 +14,16 @@ set -o pipefail
|
|||||||
$TIMEOUT "$MGMT" run --hostname h1 --tmp-prefix --no-pgp empty &
|
$TIMEOUT "$MGMT" run --hostname h1 --tmp-prefix --no-pgp empty &
|
||||||
pid1=$!
|
pid1=$!
|
||||||
sleep 10s
|
sleep 10s
|
||||||
$TIMEOUT "$MGMT" run --hostname h2 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2381 --server-urls http://127.0.0.1:2382 --tmp-prefix --no-pgp empty &
|
$TIMEOUT "$MGMT" run --hostname h2 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2381 --server-urls=http://127.0.0.1:2382 --tmp-prefix --no-pgp empty &
|
||||||
pid2=$!
|
pid2=$!
|
||||||
sleep 10s
|
sleep 10s
|
||||||
$TIMEOUT "$MGMT" run --hostname h3 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2383 --server-urls http://127.0.0.1:2384 --tmp-prefix --no-pgp empty &
|
$TIMEOUT "$MGMT" run --hostname h3 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2383 --server-urls=http://127.0.0.1:2384 --tmp-prefix --no-pgp empty &
|
||||||
pid3=$!
|
pid3=$!
|
||||||
sleep 10s
|
sleep 10s
|
||||||
$TIMEOUT "$MGMT" run --hostname h4 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2385 --server-urls http://127.0.0.1:2386 --tmp-prefix --no-pgp empty &
|
$TIMEOUT "$MGMT" run --hostname h4 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2385 --server-urls=http://127.0.0.1:2386 --tmp-prefix --no-pgp empty &
|
||||||
pid4=$!
|
pid4=$!
|
||||||
sleep 10s
|
sleep 10s
|
||||||
$TIMEOUT "$MGMT" deploy --no-git --seeds http://127.0.0.1:2379 lang exchange0.mcl
|
$TIMEOUT "$MGMT" deploy --no-git --seeds=http://127.0.0.1:2379 lang exchange0.mcl
|
||||||
|
|
||||||
# kill servers on error/exit
|
# kill servers on error/exit
|
||||||
#trap 'pkill -9 mgmt' EXIT
|
#trap 'pkill -9 mgmt' EXIT
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
# run this example with these commands
|
# run this example with these commands
|
||||||
# watch -n 0.1 'tail *' # run this in /tmp/mgmt/
|
# watch -n 0.1 'tail *' # run this in /tmp/mgmt/
|
||||||
# time ./mgmt run --hostname h1 --tmp-prefix --no-pgp empty
|
# time ./mgmt run --hostname h1 --tmp-prefix --no-pgp empty
|
||||||
# time ./mgmt run --hostname h2 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2381 --server-urls http://127.0.0.1:2382 --tmp-prefix --no-pgp empty
|
# time ./mgmt run --hostname h2 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2381 --server-urls=http://127.0.0.1:2382 --tmp-prefix --no-pgp empty
|
||||||
# time ./mgmt run --hostname h3 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2383 --server-urls http://127.0.0.1:2384 --tmp-prefix --no-pgp empty
|
# time ./mgmt run --hostname h3 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2383 --server-urls=http://127.0.0.1:2384 --tmp-prefix --no-pgp empty
|
||||||
# time ./mgmt run --hostname h4 --seeds http://127.0.0.1:2379 --client-urls http://127.0.0.1:2385 --server-urls http://127.0.0.1:2386 --tmp-prefix --no-pgp empty
|
# time ./mgmt run --hostname h4 --seeds=http://127.0.0.1:2379 --client-urls=http://127.0.0.1:2385 --server-urls=http://127.0.0.1:2386 --tmp-prefix --no-pgp empty
|
||||||
# time ./mgmt deploy --no-git --seeds http://127.0.0.1:2379 lang examples/lang/exchange0.mcl
|
# time ./mgmt deploy --no-git --seeds=http://127.0.0.1:2379 lang examples/lang/exchange0.mcl
|
||||||
|
|
||||||
import "sys"
|
import "sys"
|
||||||
import "world"
|
import "world"
|
||||||
|
|||||||
@@ -26,13 +26,12 @@ import (
|
|||||||
"github.com/purpleidea/mgmt/gapi"
|
"github.com/purpleidea/mgmt/gapi"
|
||||||
"github.com/purpleidea/mgmt/pgraph"
|
"github.com/purpleidea/mgmt/pgraph"
|
||||||
"github.com/purpleidea/mgmt/util/errwrap"
|
"github.com/purpleidea/mgmt/util/errwrap"
|
||||||
|
|
||||||
"github.com/urfave/cli/v2"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Name is the name of this frontend.
|
// Name is the name of this frontend.
|
||||||
Name = "yaml"
|
Name = "yaml"
|
||||||
|
|
||||||
// Start is the entry point filename that we use. It is arbitrary.
|
// Start is the entry point filename that we use. It is arbitrary.
|
||||||
Start = "/start.yaml"
|
Start = "/start.yaml"
|
||||||
)
|
)
|
||||||
@@ -41,6 +40,12 @@ func init() {
|
|||||||
gapi.Register(Name, func() gapi.GAPI { return &GAPI{} }) // register
|
gapi.Register(Name, func() gapi.GAPI { return &GAPI{} }) // register
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Args is the CLI parsing structure and type of the parsed result.
|
||||||
|
type Args struct {
|
||||||
|
// Input is the input yaml code or file path or any input specification.
|
||||||
|
Input string `arg:"positional,required"`
|
||||||
|
}
|
||||||
|
|
||||||
// GAPI implements the main yamlgraph GAPI interface.
|
// GAPI implements the main yamlgraph GAPI interface.
|
||||||
type GAPI struct {
|
type GAPI struct {
|
||||||
InputURI string // input URI of file system containing yaml graph to use
|
InputURI string // input URI of file system containing yaml graph to use
|
||||||
@@ -51,56 +56,36 @@ type GAPI struct {
|
|||||||
wg sync.WaitGroup // sync group for tunnel go routines
|
wg sync.WaitGroup // sync group for tunnel go routines
|
||||||
}
|
}
|
||||||
|
|
||||||
// CliFlags returns a list of flags used by the specified subcommand.
|
// Cli takes an *Info struct, and returns our deploy if activated, and if there
|
||||||
func (obj *GAPI) CliFlags(command string) []cli.Flag {
|
// are any validation problems, you should return an error. If there is no
|
||||||
switch command {
|
// deploy, then you should return a nil deploy and a nil error.
|
||||||
case gapi.CommandRun:
|
func (obj *GAPI) Cli(info *gapi.Info) (*gapi.Deploy, error) {
|
||||||
fallthrough
|
args, ok := info.Args.(*Args)
|
||||||
case gapi.CommandDeploy:
|
if !ok {
|
||||||
return []cli.Flag{}
|
// programming error
|
||||||
//case gapi.CommandGet:
|
return nil, fmt.Errorf("could not convert to our struct")
|
||||||
default:
|
|
||||||
return []cli.Flag{}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// Cli takes a cli.Context, and returns our GAPI if activated. All arguments
|
fs := info.Fs
|
||||||
// should take the prefix of the registered name. On activation, if there are
|
//debug := info.Debug
|
||||||
// any validation problems, you should return an error. If this was not
|
|
||||||
// activated, then you should return a nil GAPI and a nil error.
|
|
||||||
func (obj *GAPI) Cli(cliInfo *gapi.CliInfo) (*gapi.Deploy, error) {
|
|
||||||
c := cliInfo.CliContext
|
|
||||||
fs := cliInfo.Fs
|
|
||||||
//debug := cliInfo.Debug
|
|
||||||
//logf := func(format string, v ...interface{}) {
|
//logf := func(format string, v ...interface{}) {
|
||||||
// cliInfo.Logf(Name + ": "+format, v...)
|
// info.Logf(Name + ": "+format, v...)
|
||||||
//}
|
//}
|
||||||
|
|
||||||
if l := c.NArg(); l != 1 {
|
|
||||||
if l > 1 {
|
|
||||||
return nil, fmt.Errorf("input program must be a single arg")
|
|
||||||
}
|
|
||||||
return nil, fmt.Errorf("must specify input program")
|
|
||||||
}
|
|
||||||
s := c.Args().Get(0)
|
|
||||||
if s == "" {
|
|
||||||
return nil, fmt.Errorf("input yaml is empty")
|
|
||||||
}
|
|
||||||
|
|
||||||
writeableFS, ok := fs.(engine.WriteableFS)
|
writeableFS, ok := fs.(engine.WriteableFS)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("the FS was not writeable")
|
return nil, fmt.Errorf("the FS was not writeable")
|
||||||
}
|
}
|
||||||
|
|
||||||
// single file input only
|
// single file input only
|
||||||
if err := gapi.CopyFileToFs(writeableFS, s, Start); err != nil {
|
if err := gapi.CopyFileToFs(writeableFS, args.Input, Start); err != nil {
|
||||||
return nil, errwrap.Wrapf(err, "can't copy yaml from `%s` to `%s`", s, Start)
|
return nil, errwrap.Wrapf(err, "can't copy yaml from `%s` to `%s`", args.Input, Start)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &gapi.Deploy{
|
return &gapi.Deploy{
|
||||||
Name: Name,
|
Name: Name,
|
||||||
Noop: c.Bool("noop"),
|
Noop: info.Flags.Noop,
|
||||||
Sema: c.Int("sema"),
|
Sema: info.Flags.Sema,
|
||||||
GAPI: &GAPI{
|
GAPI: &GAPI{
|
||||||
InputURI: fs.URI(),
|
InputURI: fs.URI(),
|
||||||
// TODO: add properties here...
|
// TODO: add properties here...
|
||||||
|
|||||||
Reference in New Issue
Block a user