refactor: urfave v3

This commit is contained in:
decentral1se 2024-07-09 13:57:54 +02:00
parent 375e17a4a0
commit 1f8662cd95
Signed by: decentral1se
GPG Key ID: 03789458B3D0C410
336 changed files with 7332 additions and 25145 deletions

View File

@ -1,15 +1,15 @@
package app package app
import ( import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var AppCommand = cli.Command{ var AppCommand = cli.Command{
Name: "app", Name: "app",
Aliases: []string{"a"}, Aliases: []string{"a"},
Usage: "Manage apps", Usage: "Manage apps",
ArgsUsage: "<domain>", UsageText: "abra app [command] [options] [arguments]",
Subcommands: []*cli.Command{ Commands: []*cli.Command{
&appBackupCommand, &appBackupCommand,
&appCheckCommand, &appCheckCommand,
&appCmdCommand, &appCmdCommand,

View File

@ -1,13 +1,14 @@
package app package app
import ( import (
"context"
"fmt" "fmt"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var snapshot string var snapshot string
@ -38,16 +39,16 @@ var appBackupListCommand = cli.Command{
Name: "list", Name: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OfflineFlag,
snapshotFlag, snapshotFlag,
includePathFlag, includePathFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "List all backups", Usage: "List all backups",
BashComplete: autocomplete.AppNameComplete, UsageText: "abra app backup list <domain> [options]",
Action: func(c *cli.Context) error { ShellComplete: autocomplete.AppNameComplete,
app := internal.ValidateApp(c) HideHelp: true,
Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
@ -85,16 +86,16 @@ var appBackupDownloadCommand = cli.Command{
Name: "download", Name: "download",
Aliases: []string{"d"}, Aliases: []string{"d"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OfflineFlag,
snapshotFlag, snapshotFlag,
includePathFlag, includePathFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "Download a backup", Usage: "Download a backup",
BashComplete: autocomplete.AppNameComplete, UsageText: "abra app backup download <domain> [options]",
Action: func(c *cli.Context) error { ShellComplete: autocomplete.AppNameComplete,
app := internal.ValidateApp(c) HideHelp: true,
Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.EnsureExists(); err != nil { if err := app.Recipe.EnsureExists(); err != nil {
log.Fatal(err) log.Fatal(err)
@ -156,15 +157,15 @@ var appBackupCreateCommand = cli.Command{
Name: "create", Name: "create",
Aliases: []string{"c"}, Aliases: []string{"c"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OfflineFlag,
resticRepoFlag, resticRepoFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "Create a new backup", Usage: "Create a new backup",
BashComplete: autocomplete.AppNameComplete, UsageText: "abra app backup create <domain> [options]",
Action: func(c *cli.Context) error { ShellComplete: autocomplete.AppNameComplete,
app := internal.ValidateApp(c) HideHelp: true,
Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.EnsureExists(); err != nil { if err := app.Recipe.EnsureExists(); err != nil {
log.Fatal(err) log.Fatal(err)
@ -214,15 +215,15 @@ var appBackupSnapshotsCommand = cli.Command{
Name: "snapshots", Name: "snapshots",
Aliases: []string{"s"}, Aliases: []string{"s"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OfflineFlag,
snapshotFlag, snapshotFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "List backup snapshots", Usage: "List backup snapshots",
BashComplete: autocomplete.AppNameComplete, UsageText: "abra app backup snapshots <domain> [options]",
Action: func(c *cli.Context) error { ShellComplete: autocomplete.AppNameComplete,
app := internal.ValidateApp(c) HideHelp: true,
Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.EnsureExists(); err != nil { if err := app.Recipe.EnsureExists(); err != nil {
log.Fatal(err) log.Fatal(err)
@ -272,8 +273,8 @@ var appBackupCommand = cli.Command{
Name: "backup", Name: "backup",
Aliases: []string{"b"}, Aliases: []string{"b"},
Usage: "Manage app backups", Usage: "Manage app backups",
ArgsUsage: "<domain>", UsageText: "abra app backup [command] [arguments] [options]",
Subcommands: []*cli.Command{ Commands: []*cli.Command{
&appBackupListCommand, &appBackupListCommand,
&appBackupSnapshotsCommand, &appBackupSnapshotsCommand,
&appBackupDownloadCommand, &appBackupDownloadCommand,

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"fmt" "fmt"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
@ -9,16 +10,15 @@ import (
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/charmbracelet/lipgloss" "github.com/charmbracelet/lipgloss"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var appCheckCommand = cli.Command{ var appCheckCommand = cli.Command{
Name: "check", Name: "check",
Aliases: []string{"chk"}, Aliases: []string{"chk"},
Usage: "Ensure an app is well configured", UsageText: "abra app check <domain> [options]",
Description: ` Usage: "Ensure an app is well configured",
This command compares env vars in both the app ".env" and recipe ".env.sample" Description: `Compare env vars in both the app ".env" and recipe ".env.sample" file.
file.
The goal is to ensure that recipe ".env.sample" env vars are defined in your The goal is to ensure that recipe ".env.sample" env vars are defined in your
app ".env" file. Only env var definitions in the ".env.sample" which are app ".env" file. Only env var definitions in the ".env.sample" which are
@ -28,16 +28,15 @@ these env vars, then "check" will complain.
Recipe maintainers may or may not provide defaults for env vars within their Recipe maintainers may or may not provide defaults for env vars within their
recipes regardless of commenting or not (e.g. through the use of recipes regardless of commenting or not (e.g. through the use of
${FOO:<default>} syntax). "check" does not confirm or deny this for you.`, ${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
ArgsUsage: "<domain>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.ChaosFlag, internal.ChaosFlag,
internal.OfflineFlag, internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, ShellComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { HideHelp: true,
app := internal.ValidateApp(c) Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -14,61 +15,53 @@ import (
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli/v2" "coopcloud.tech/abra/pkg/recipe"
"github.com/urfave/cli/v3"
) )
var appCmdCommand = cli.Command{ var appCmdCommand = cli.Command{
Name: "command", Name: "command",
Aliases: []string{"cmd"}, Aliases: []string{"cmd"},
Usage: "Run app commands", Usage: "Run app commands",
UsageText: "abra app cmd <domain> [<service>] <cmd> [<cmd-args>] [options]",
Description: `Run an app specific command. Description: `Run an app specific command.
These commands are bash functions, defined in the abra.sh of the recipe itself. These commands are bash functions, defined in the abra.sh of the recipe itself.
They can be run within the context of a service (e.g. app) or locally on your They can be run within the context of a service (e.g. app) or locally on your
work station by passing "--local". Arguments can be passed into these functions work station by passing "--local".`,
using the "-- <args>" syntax.
**WARNING**: options must be passed directly after the sub-command "cmd".
EXAMPLE:
abra app cmd --local example.com app create_user -- me@example.com`,
ArgsUsage: "<domain> [<service>] <command> [-- <args>]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.LocalCmdFlag, internal.LocalCmdFlag,
internal.RemoteUserFlag, internal.RemoteUserFlag,
internal.TtyFlag, internal.TtyFlag,
internal.OfflineFlag,
internal.ChaosFlag, internal.ChaosFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Subcommands: []*cli.Command{ Commands: []*cli.Command{
&appCmdListCommand, &appCmdListCommand,
}, },
BashComplete: func(ctx *cli.Context) { ShellComplete: func(ctx context.Context, cmd *cli.Command) {
args := ctx.Args() args := cmd.Args()
switch args.Len() { switch args.Len() {
case 0: case 0:
autocomplete.AppNameComplete(ctx) autocomplete.AppNameComplete(ctx, cmd)
case 1: case 1:
autocomplete.ServiceNameComplete(args.Get(0)) autocomplete.ServiceNameComplete(args.Get(0))
case 2: case 2:
cmdNameComplete(args.Get(0)) cmdNameComplete(args.Get(0))
} }
}, },
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if internal.LocalCmd && internal.RemoteUser != "" { if internal.LocalCmd && internal.RemoteUser != "" {
internal.ShowSubcommandHelpAndError(c, errors.New("cannot use --local & --user together")) internal.ShowSubcommandHelpAndError(cmd, errors.New("cannot use --local & --user together"))
} }
hasCmdArgs, parsedCmdArgs := parseCmdArgs(c.Args().Slice(), internal.LocalCmd) hasCmdArgs, parsedCmdArgs := parseCmdArgs(cmd.Args().Slice(), internal.LocalCmd)
if _, err := os.Stat(app.Recipe.AbraShPath); err != nil { if _, err := os.Stat(app.Recipe.AbraShPath); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
@ -78,11 +71,11 @@ EXAMPLE:
} }
if internal.LocalCmd { if internal.LocalCmd {
if !(c.Args().Len() >= 2) { if !(cmd.Args().Len() >= 2) {
internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments")) internal.ShowSubcommandHelpAndError(cmd, errors.New("missing arguments"))
} }
cmdName := c.Args().Get(1) cmdName := cmd.Args().Get(1)
if err := internal.EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil { if err := internal.EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -114,13 +107,13 @@ EXAMPLE:
log.Fatal(err) log.Fatal(err)
} }
} else { } else {
if !(c.Args().Len() >= 3) { if !(cmd.Args().Len() >= 3) {
internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments")) internal.ShowSubcommandHelpAndError(cmd, errors.New("missing arguments"))
} }
targetServiceName := c.Args().Get(1) targetServiceName := cmd.Args().Get(1)
cmdName := c.Args().Get(2) cmdName := cmd.Args().Get(2)
if err := internal.EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil { if err := internal.EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -200,16 +193,16 @@ var appCmdListCommand = cli.Command{
Name: "list", Name: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Usage: "List all available commands", Usage: "List all available commands",
ArgsUsage: "<domain>", UsageText: "abra app cmd ls <domain> [options]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OfflineFlag,
internal.ChaosFlag, internal.ChaosFlag,
}, },
BashComplete: autocomplete.AppNameComplete, ShellComplete: autocomplete.AppNameComplete,
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { HideHelp: true,
app := internal.ValidateApp(c) Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
r := recipe.Get(app.Recipe.Name)
if err := app.Recipe.EnsureExists(); err != nil { if err := app.Recipe.EnsureExists(); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"errors" "errors"
"os" "os"
"os/exec" "os/exec"
@ -10,24 +11,22 @@ import (
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var appConfigCommand = cli.Command{ var appConfigCommand = cli.Command{
Name: "config", Name: "config",
Aliases: []string{"cfg"}, Aliases: []string{"cfg"},
Usage: "Edit app config", Usage: "Edit app config",
ArgsUsage: "<domain>", UsageText: "abra app config <domain> [options]",
Flags: []cli.Flag{ Before: internal.SubCommandBefore,
internal.DebugFlag, ShellComplete: autocomplete.AppNameComplete,
}, HideHelp: true,
Before: internal.SubCommandBefore, Action: func(ctx context.Context, cmd *cli.Command) error {
BashComplete: autocomplete.AppNameComplete, appName := cmd.Args().First()
Action: func(c *cli.Context) error {
appName := c.Args().First()
if appName == "" { if appName == "" {
internal.ShowSubcommandHelpAndError(c, errors.New("no app provided")) internal.ShowSubcommandHelpAndError(cmd, errors.New("no app provided"))
} }
files, err := appPkg.LoadAppFiles("") files, err := appPkg.LoadAppFiles("")
@ -51,11 +50,11 @@ var appConfigCommand = cli.Command{
} }
} }
cmd := exec.Command(ed, appFile.Path) c := exec.Command(ed, appFile.Path)
cmd.Stdin = os.Stdin c.Stdin = os.Stdin
cmd.Stdout = os.Stdout c.Stdout = os.Stdout
cmd.Stderr = os.Stderr c.Stderr = os.Stderr
if err := cmd.Run(); err != nil { if err := c.Run(); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -22,21 +22,16 @@ import (
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
"github.com/docker/docker/errdefs" "github.com/docker/docker/errdefs"
"github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/archive"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var appCpCommand = cli.Command{ var appCpCommand = cli.Command{
Name: "cp", Name: "cp",
Aliases: []string{"c"}, Aliases: []string{"c"},
ArgsUsage: "<domain> <src> <dst>", Before: internal.SubCommandBefore,
Flags: []cli.Flag{ Usage: "Copy files to/from a deployed app service",
internal.DebugFlag, UsageText: "abra app cp <domain> <src> <dst> [options]",
internal.NoInputFlag, Description: `Copy files to and from any app service file system.
},
Before: internal.SubCommandBefore,
Usage: "Copy files to/from a deployed app service",
Description: `
Copy files to and from any app service file system.
If you want to copy a myfile.txt to the root of the app service: If you want to copy a myfile.txt to the root of the app service:
@ -44,18 +39,17 @@ If you want to copy a myfile.txt to the root of the app service:
And if you want to copy that file back to your current working directory locally: And if you want to copy that file back to your current working directory locally:
abra app cp <domain> app:/myfile.txt . abra app cp <domain> app:/myfile.txt`,
`, ShellComplete: autocomplete.AppNameComplete,
BashComplete: autocomplete.AppNameComplete, HideHelp: true,
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
} }
src := c.Args().Get(1) src := cmd.Args().Get(1)
dst := c.Args().Get(2) dst := cmd.Args().Get(2)
if src == "" { if src == "" {
log.Fatal("missing <src> argument") log.Fatal("missing <src> argument")
} }

View File

@ -17,22 +17,19 @@ import (
"coopcloud.tech/abra/pkg/lint" "coopcloud.tech/abra/pkg/lint"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/upstream/stack" "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var appDeployCommand = cli.Command{ var appDeployCommand = cli.Command{
Name: "deploy", Name: "deploy",
Aliases: []string{"d"}, Aliases: []string{"d"},
Usage: "Deploy an app", Usage: "Deploy an app",
ArgsUsage: "<domain> [<version>]", UsageText: "abra app deploy <domain> [<version>] [options]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.ForceFlag, internal.ForceFlag,
internal.ChaosFlag, internal.ChaosFlag,
internal.NoDomainChecksFlag, internal.NoDomainChecksFlag,
internal.DontWaitConvergeFlag, internal.DontWaitConvergeFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: `Deploy an app. Description: `Deploy an app.
@ -40,21 +37,20 @@ var appDeployCommand = cli.Command{
This command supports chaos operations. Use "--chaos" to deploy your recipe This command supports chaos operations. Use "--chaos" to deploy your recipe
checkout as-is. Recipe commit hashes are also supported values for checkout as-is. Recipe commit hashes are also supported values for
"[<version>]". Please note, "upgrade"/"rollback" do not support chaos "[<version>]". Please note, "upgrade"/"rollback" do not support chaos
operations. operations.`,
ShellComplete: autocomplete.AppNameComplete,
EXAMPLE: HideHelp: true,
Action: func(ctx context.Context, cmd *cli.Command) error {
abra app deploy foo.example.com
abra app deploy foo.example.com 1.2.3+3.2.1
abra app deploy foo.example.com 1e83340e`,
BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error {
var warnMessages []string var warnMessages []string
app := internal.ValidateApp(c) app := internal.ValidateApp(cmd)
stackName := app.StackName() stackName := app.StackName()
specificVersion := c.Args().Get(1) specificVersion := cmd.Args().Get(1)
if specificVersion == "" {
specificVersion = app.Recipe.Version
}
if specificVersion != "" && internal.Chaos { if specificVersion != "" && internal.Chaos {
log.Fatal("cannot use <version> and --chaos together") log.Fatal("cannot use <version> and --chaos together")
} }

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"sort" "sort"
@ -12,7 +13,7 @@ import (
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/tagcmp" "coopcloud.tech/tagcmp"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var ( var (
@ -70,26 +71,24 @@ type serverStatus struct {
} }
var appListCommand = cli.Command{ var appListCommand = cli.Command{
Name: "list", Name: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Usage: "List all managed apps", Usage: "List all managed apps",
Description: ` UsageText: "abra app list [options]",
Read the local file system listing of apps and servers (e.g. ~/.abra/) to Description: `Generate a report of all managed apps.
generate a report of all your apps.
By passing the "--status/-S" flag, you can query all your servers for the By passing the "--status/-S" flag, you can query all your servers for the
actual live deployment status. Depending on how many servers you manage, this actual live deployment status. Depending on how many servers you manage, this
can take some time.`, can take some time.`,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.MachineReadableFlag, internal.MachineReadableFlag,
statusFlag, statusFlag,
listAppServerFlag, listAppServerFlag,
recipeFlag, recipeFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { HideHelp: true,
Action: func(ctx context.Context, cmd *cli.Command) error {
appFiles, err := appPkg.LoadAppFiles(listAppServer) appFiles, err := appPkg.LoadAppFiles(listAppServer)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -19,23 +19,23 @@ import (
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/docker/docker/api/types/swarm" "github.com/docker/docker/api/types/swarm"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var appLogsCommand = cli.Command{ var appLogsCommand = cli.Command{
Name: "logs", Name: "logs",
Aliases: []string{"l"}, Aliases: []string{"l"},
ArgsUsage: "<domain> [<service>]",
Usage: "Tail app logs", Usage: "Tail app logs",
UsageText: "abra app logs <domain> [<service>] [options]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.StdErrOnlyFlag, internal.StdErrOnlyFlag,
internal.SinceLogsFlag, internal.SinceLogsFlag,
internal.DebugFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, ShellComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { HideHelp: true,
app := internal.ValidateApp(c) Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
stackName := app.StackName() stackName := app.StackName()
if err := app.Recipe.EnsureExists(); err != nil { if err := app.Recipe.EnsureExists(); err != nil {
@ -56,7 +56,7 @@ var appLogsCommand = cli.Command{
log.Fatalf("%s is not deployed?", app.Name) log.Fatalf("%s is not deployed?", app.Name)
} }
serviceName := c.Args().Get(1) serviceName := cmd.Args().Get(1)
serviceNames := []string{} serviceNames := []string{}
if serviceName != "" { if serviceName != "" {
serviceNames = []string{serviceName} serviceNames = []string{serviceName}

View File

@ -1,6 +1,7 @@
package app package app
import ( import (
"context"
"fmt" "fmt"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
@ -16,12 +17,13 @@ import (
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/charmbracelet/lipgloss/table" "github.com/charmbracelet/lipgloss/table"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var appNewDescription = ` var appNewDescription = `Creates a new app from a default recipe.
Creates a new app from a default recipe. This new app configuration is stored
in your $ABRA_DIR directory under the appropriate server. This new app configuration is stored in your $ABRA_DIR directory under the
appropriate server.
This command does not deploy your app for you. You will need to run "abra app This command does not deploy your app for you. You will need to run "abra app
deploy <domain>" to do so. deploy <domain>" to do so.
@ -44,30 +46,28 @@ var appNewCommand = cli.Command{
Name: "new", Name: "new",
Aliases: []string{"n"}, Aliases: []string{"n"},
Usage: "Create a new app", Usage: "Create a new app",
UsageText: "abra app new [<recipe>] [<version>] [options]",
Description: appNewDescription, Description: appNewDescription,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.NewAppServerFlag, internal.NewAppServerFlag,
internal.DomainFlag, internal.DomainFlag,
internal.PassFlag, internal.PassFlag,
internal.SecretsFlag, internal.SecretsFlag,
internal.OfflineFlag,
internal.ChaosFlag, internal.ChaosFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
ArgsUsage: "[<recipe>] [<version>]", HideHelp: true,
BashComplete: func(ctx *cli.Context) { ShellComplete: func(ctx context.Context, cmd *cli.Command) {
args := ctx.Args() args := cmd.Args()
switch args.Len() { switch args.Len() {
case 0: case 0:
autocomplete.RecipeNameComplete(ctx) autocomplete.RecipeNameComplete(ctx, cmd)
case 1: case 1:
autocomplete.RecipeVersionComplete(ctx.Args().Get(0)) autocomplete.RecipeVersionComplete(cmd.Args().Get(0))
} }
}, },
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
recipe := internal.ValidateRecipe(c) recipe := internal.ValidateRecipe(cmd)
var version string var version string
if !internal.Chaos { if !internal.Chaos {
@ -80,7 +80,7 @@ var appNewCommand = cli.Command{
} }
} }
if c.Args().Get(1) == "" { if cmd.Args().Get(1) == "" {
recipeVersions, err := recipe.GetRecipeVersions() recipeVersions, err := recipe.GetRecipeVersions()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -101,7 +101,7 @@ var appNewCommand = cli.Command{
} }
} }
} else { } else {
version = c.Args().Get(1) version = cmd.Args().Get(1)
if _, err := recipe.EnsureVersion(version); err != nil { if _, err := recipe.EnsureVersion(version); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -18,25 +18,25 @@ import (
containerTypes "github.com/docker/docker/api/types/container" containerTypes "github.com/docker/docker/api/types/container"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var appPsCommand = cli.Command{ var appPsCommand = cli.Command{
Name: "ps", Name: "ps",
Aliases: []string{"p"}, Aliases: []string{"p"},
Usage: "Check app status", Usage: "Check app status",
ArgsUsage: "<domain>", UsageText: "abra app ps <domain> [options]",
Description: "Show status of a deployed app.", Description: "Show status of a deployed app.",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.MachineReadableFlag, internal.MachineReadableFlag,
internal.DebugFlag,
internal.ChaosFlag, internal.ChaosFlag,
internal.OfflineFlag, internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, ShellComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { HideHelp: true,
app := internal.ValidateApp(c) Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -12,16 +12,15 @@ import (
stack "coopcloud.tech/abra/pkg/upstream/stack" stack "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var appRemoveCommand = cli.Command{ var appRemoveCommand = cli.Command{
Name: "remove", Name: "remove",
Aliases: []string{"rm"}, Aliases: []string{"rm"},
ArgsUsage: "<domain>", UsageText: "abra app remove <domain> [options]",
Usage: "Remove all app data, locally and remotely", Usage: "Remove all app data, locally and remotely",
Description: ` Description: `Remove everything related to an app which is already undeployed.
This command removes everything related to an app which is already undeployed.
By default, it will prompt for confirmation before proceeding. All secrets, By default, it will prompt for confirmation before proceeding. All secrets,
volumes and the local app env file will be deleted. volumes and the local app env file will be deleted.
@ -39,14 +38,12 @@ To delete everything without prompt, use the "--force/-f" or the "--no-input/n"
flag.`, flag.`,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.ForceFlag, internal.ForceFlag,
internal.DebugFlag,
internal.NoInputFlag,
internal.OfflineFlag,
}, },
BashComplete: autocomplete.AppNameComplete, ShellComplete: autocomplete.AppNameComplete,
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { HideHelp: true,
app := internal.ValidateApp(c) Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if !internal.Force && !internal.NoInput { if !internal.Force && !internal.NoInput {
log.Warnf("ALERTA ALERTA: this will completely remove %s data and config locally and remotely", app.Name) log.Warnf("ALERTA ALERTA: this will completely remove %s data and config locally and remotely", app.Name)

View File

@ -12,41 +12,35 @@ import (
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
upstream "coopcloud.tech/abra/pkg/upstream/service" upstream "coopcloud.tech/abra/pkg/upstream/service"
stack "coopcloud.tech/abra/pkg/upstream/stack" stack "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var appRestartCommand = cli.Command{ var appRestartCommand = cli.Command{
Name: "restart", Name: "restart",
Aliases: []string{"re"}, Aliases: []string{"re"},
Usage: "Restart an app", Usage: "Restart an app",
ArgsUsage: "<domain> [<service>]", UsageText: "abra app restart <domain> [<service>] [options]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OfflineFlag,
internal.AllServicesFlag, internal.AllServicesFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `This command restarts services within a deployed app.
This command restarts services within a deployed app.
Run "abra app ps <domain>" to see a list of service names. Run "abra app ps <domain>" to see a list of service names.
Pass "--all-services/-a" to restart all services. Pass "--all-services/-a" to restart all services.`,
ShellComplete: autocomplete.AppNameComplete,
EXAMPLE: HideHelp: true,
Action: func(ctx context.Context, cmd *cli.Command) error {
abra app restart example.com app`, app := internal.ValidateApp(cmd)
BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error {
app := internal.ValidateApp(c)
if err := app.Recipe.Ensure(false, false); err != nil { if err := app.Recipe.Ensure(false, false); err != nil {
log.Fatal(err) log.Fatal(err)
} }
serviceName := c.Args().Get(1) serviceName := cmd.Args().Get(1)
if serviceName == "" && !internal.AllServices { if serviceName == "" && !internal.AllServices {
err := errors.New("missing <service>") err := errors.New("missing <service>")
internal.ShowSubcommandHelpAndError(c, err) internal.ShowSubcommandHelpAndError(cmd, err)
} }
if serviceName != "" && internal.AllServices { if serviceName != "" && internal.AllServices {

View File

@ -1,13 +1,14 @@
package app package app
import ( import (
"context"
"fmt" "fmt"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var targetPath string var targetPath string
@ -22,16 +23,15 @@ var appRestoreCommand = cli.Command{
Name: "restore", Name: "restore",
Aliases: []string{"rs"}, Aliases: []string{"rs"},
Usage: "Restore an app backup", Usage: "Restore an app backup",
ArgsUsage: "<domain> <service>", UsageText: "abra app restore <domain> <service> [options]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OfflineFlag,
targetPathFlag, targetPathFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, ShellComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { HideHelp: true,
app := internal.ValidateApp(c) Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -16,44 +16,36 @@ import (
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var appRollbackCommand = cli.Command{ var appRollbackCommand = cli.Command{
Name: "rollback", Name: "rollback",
Aliases: []string{"rl"}, Aliases: []string{"rl"},
Usage: "Roll an app back to a previous version", Usage: "Roll an app back to a previous version",
ArgsUsage: "<domain> [<version>]", UsageText: "abra app rollback <domain> [<version>] [options]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.ForceFlag, internal.ForceFlag,
internal.NoDomainChecksFlag, internal.NoDomainChecksFlag,
internal.DontWaitConvergeFlag, internal.DontWaitConvergeFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `This command rolls an app back to a previous version.
This command rolls an app back to a previous version.
Unlike "deploy", chaos operations are not supported here. Only recipe versions Unlike "deploy", chaos operations are not supported here. Only recipe versions
are supported values for "[<version>]". are supported values for "[<version>]".
A rollback can be destructive, please ensure you have a copy of your app data A rollback can be destructive, please ensure you have a copy of your app data
beforehand. beforehand.`,
ShellComplete: autocomplete.AppNameComplete,
EXAMPLE: HideHelp: true,
Action: func(ctx context.Context, cmd *cli.Command) error {
abra app rollback foo.example.com
abra app rollback foo.example.com 1.2.3+3.2.1`,
BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error {
var warnMessages []string var warnMessages []string
app := internal.ValidateApp(c) app := internal.ValidateApp(cmd)
stackName := app.StackName() stackName := app.StackName()
specificVersion := c.Args().Get(1) specificVersion := cmd.Args().Get(1)
if specificVersion != "" { if specificVersion != "" {
log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion) log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion)
app.Recipe.Version = specificVersion app.Recipe.Version = specificVersion

View File

@ -14,7 +14,7 @@ import (
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var user string var user string
@ -36,23 +36,23 @@ var appRunCommand = cli.Command{
Name: "run", Name: "run",
Aliases: []string{"r"}, Aliases: []string{"r"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
noTTYFlag, noTTYFlag,
userFlag, userFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
ArgsUsage: "<domain> <service> <args>...", Usage: "Run a command in an app service",
Usage: "Run a command in a service container", UsageText: "abra app run <domain> <service> <args> [options]",
BashComplete: autocomplete.AppNameComplete, ShellComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { HideHelp: true,
app := internal.ValidateApp(c) Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if c.Args().Len() < 2 { if cmd.Args().Len() < 2 {
internal.ShowSubcommandHelpAndError(c, errors.New("no <service> provided?")) internal.ShowSubcommandHelpAndError(cmd, errors.New("no <service> provided?"))
} }
if c.Args().Len() < 3 { if cmd.Args().Len() < 3 {
internal.ShowSubcommandHelpAndError(c, errors.New("no <args> provided?")) internal.ShowSubcommandHelpAndError(cmd, errors.New("no <args> provided?"))
} }
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
@ -60,7 +60,7 @@ var appRunCommand = cli.Command{
log.Fatal(err) log.Fatal(err)
} }
serviceName := c.Args().Get(1) serviceName := cmd.Args().Get(1)
stackAndServiceName := fmt.Sprintf("^%s_%s", app.StackName(), serviceName) stackAndServiceName := fmt.Sprintf("^%s_%s", app.StackName(), serviceName)
filters := filters.NewArgs() filters := filters.NewArgs()
filters.Add("name", stackAndServiceName) filters.Add("name", stackAndServiceName)
@ -70,12 +70,12 @@ var appRunCommand = cli.Command{
log.Fatal(err) log.Fatal(err)
} }
cmd := c.Args().Slice()[2:] c := cmd.Args().Slice()[2:]
execCreateOpts := types.ExecConfig{ execCreateOpts := types.ExecConfig{
AttachStderr: true, AttachStderr: true,
AttachStdin: true, AttachStdin: true,
AttachStdout: true, AttachStdout: true,
Cmd: cmd, Cmd: c,
Detach: false, Detach: false,
Tty: true, Tty: true,
} }

View File

@ -17,7 +17,7 @@ import (
"coopcloud.tech/abra/pkg/secret" "coopcloud.tech/abra/pkg/secret"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var ( var (
@ -44,31 +44,30 @@ var appSecretGenerateCommand = cli.Command{
Name: "generate", Name: "generate",
Aliases: []string{"g"}, Aliases: []string{"g"},
Usage: "Generate secrets", Usage: "Generate secrets",
ArgsUsage: "<domain> <secret> <version>", UsageText: "abra app secret generate <domain> <secret> <version> [options]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
allSecretsFlag, allSecretsFlag,
internal.PassFlag, internal.PassFlag,
internal.MachineReadableFlag, internal.MachineReadableFlag,
internal.OfflineFlag,
internal.ChaosFlag, internal.ChaosFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, ShellComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { HideHelp: true,
app := internal.ValidateApp(c) Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if c.Args().Len() == 1 && !allSecrets { if cmd.Args().Len() == 1 && !allSecrets {
err := errors.New("missing arguments <secret>/<version> or '--all'") err := errors.New("missing arguments <secret>/<version> or '--all'")
internal.ShowSubcommandHelpAndError(c, err) internal.ShowSubcommandHelpAndError(cmd, err)
} }
if c.Args().Get(1) != "" && allSecrets { if cmd.Args().Get(1) != "" && allSecrets {
err := errors.New("cannot use '<secret> <version>' and '--all' together") err := errors.New("cannot use '<secret> <version>' and '--all' together")
internal.ShowSubcommandHelpAndError(c, err) internal.ShowSubcommandHelpAndError(cmd, err)
} }
composeFiles, err := app.Recipe.GetComposeFiles(app.Env) composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
@ -82,8 +81,8 @@ var appSecretGenerateCommand = cli.Command{
} }
if !allSecrets { if !allSecrets {
secretName := c.Args().Get(1) secretName := cmd.Args().Get(1)
secretVersion := c.Args().Get(2) secretVersion := cmd.Args().Get(2)
s, ok := secrets[secretName] s, ok := secrets[secretName]
if !ok { if !ok {
log.Fatalf("%s doesn't exist in the env config?", secretName) log.Fatalf("%s doesn't exist in the env config?", secretName)
@ -154,39 +153,32 @@ var appSecretGenerateCommand = cli.Command{
} }
var appSecretInsertCommand = cli.Command{ var appSecretInsertCommand = cli.Command{
Name: "insert", Name: "insert",
Aliases: []string{"i"}, Aliases: []string{"i"},
Usage: "Insert secret", Usage: "Insert secret",
UsageText: "abra app secret insert <domain> <secret> <version> <data> [options]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.PassFlag, internal.PassFlag,
internal.FileFlag, internal.FileFlag,
internal.TrimFlag, internal.TrimFlag,
internal.ChaosFlag, internal.ChaosFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
ArgsUsage: "<domain> <secret-name> <version> <data>", ShellComplete: autocomplete.AppNameComplete,
BashComplete: autocomplete.AppNameComplete, HideHelpCommand: true,
Description: ` Description: `This command inserts a secret into an app environment.
This command inserts a secret into an app environment.
This can be useful when you want to manually generate secrets for an app This can be useful when you want to manually generate secrets for an app
environment. Typically, you can let Abra generate them for you on app creation environment. Typically, you can let Abra generate them for you on app creation
(see "abra app new --secrets" for more). (see "abra app new --secrets" for more).`,
Action: func(ctx context.Context, cmd *cli.Command) error {
Example: app := internal.ValidateApp(cmd)
abra app secret insert myapp db_pass v1 mySecretPassword
`,
Action: func(c *cli.Context) error {
app := internal.ValidateApp(c)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if c.Args().Len() != 4 { if cmd.Args().Len() != 4 {
internal.ShowSubcommandHelpAndError(c, errors.New("missing arguments?")) internal.ShowSubcommandHelpAndError(cmd, errors.New("missing arguments?"))
} }
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
@ -194,9 +186,9 @@ Example:
log.Fatal(err) log.Fatal(err)
} }
name := c.Args().Get(1) name := cmd.Args().Get(1)
version := c.Args().Get(2) version := cmd.Args().Get(2)
data := c.Args().Get(3) data := cmd.Args().Get(3)
if internal.File { if internal.File {
raw, err := os.ReadFile(data) raw, err := os.ReadFile(data)
@ -247,29 +239,28 @@ func secretRm(cl *dockerClient.Client, app appPkg.App, secretName, parsed string
} }
var appSecretRmCommand = cli.Command{ var appSecretRmCommand = cli.Command{
Name: "remove", Name: "remove",
Aliases: []string{"rm"}, Aliases: []string{"rm"},
Usage: "Remove a secret", Usage: "Remove a secret",
UsageText: "abra app remove <domainabra app remove <domain> [options]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag, internal.NoInputFlag,
rmAllSecretsFlag, rmAllSecretsFlag,
internal.PassRemoveFlag, internal.PassRemoveFlag,
internal.OfflineFlag, internal.OfflineFlag,
internal.ChaosFlag, internal.ChaosFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
ArgsUsage: "<domain> [<secret-name>]", ShellComplete: autocomplete.AppNameComplete,
BashComplete: autocomplete.AppNameComplete,
Description: ` Description: `
This command removes app secrets. This command removes app secrets.
Example: Example:
abra app secret remove myapp db_pass abra app secret remove myapp db_pass`,
`, HideHelp: true,
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -284,12 +275,12 @@ Example:
log.Fatal(err) log.Fatal(err)
} }
if c.Args().Get(1) != "" && rmAllSecrets { if cmd.Args().Get(1) != "" && rmAllSecrets {
internal.ShowSubcommandHelpAndError(c, errors.New("cannot use '<secret-name>' and '--all' together")) internal.ShowSubcommandHelpAndError(cmd, errors.New("cannot use '<secret-name>' and '--all' together"))
} }
if c.Args().Get(1) == "" && !rmAllSecrets { if cmd.Args().Get(1) == "" && !rmAllSecrets {
internal.ShowSubcommandHelpAndError(c, errors.New("no secret(s) specified?")) internal.ShowSubcommandHelpAndError(cmd, errors.New("no secret(s) specified?"))
} }
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
@ -313,7 +304,7 @@ Example:
} }
match := false match := false
secretToRm := c.Args().Get(1) secretToRm := cmd.Args().Get(1)
for secretName, val := range secrets { for secretName, val := range secrets {
secretRemoteName := fmt.Sprintf("%s_%s_%s", app.StackName(), secretName, val.Version) secretRemoteName := fmt.Sprintf("%s_%s_%s", app.StackName(), secretName, val.Version)
if _, ok := remoteSecretNames[secretRemoteName]; ok { if _, ok := remoteSecretNames[secretRemoteName]; ok {
@ -351,16 +342,17 @@ var appSecretLsCommand = cli.Command{
Name: "list", Name: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OfflineFlag, internal.OfflineFlag,
internal.ChaosFlag, internal.ChaosFlag,
internal.MachineReadableFlag, internal.MachineReadableFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "List all secrets", Usage: "List all secrets",
BashComplete: autocomplete.AppNameComplete, UsageText: "abra app secret list [options]",
Action: func(c *cli.Context) error { HideHelp: true,
app := internal.ValidateApp(c) ShellComplete: autocomplete.AppNameComplete,
Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(cmd)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -420,8 +412,8 @@ var appSecretCommand = cli.Command{
Name: "secret", Name: "secret",
Aliases: []string{"s"}, Aliases: []string{"s"},
Usage: "Manage app secrets", Usage: "Manage app secrets",
ArgsUsage: "<domain>", UsageText: "abra app secret [command] [arguments] [options]",
Subcommands: []*cli.Command{ Commands: []*cli.Command{
&appSecretGenerateCommand, &appSecretGenerateCommand,
&appSecretInsertCommand, &appSecretInsertCommand,
&appSecretRmCommand, &appSecretRmCommand,

View File

@ -13,21 +13,19 @@ import (
"coopcloud.tech/abra/pkg/service" "coopcloud.tech/abra/pkg/service"
stack "coopcloud.tech/abra/pkg/upstream/stack" stack "coopcloud.tech/abra/pkg/upstream/stack"
containerTypes "github.com/docker/docker/api/types/container" containerTypes "github.com/docker/docker/api/types/container"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var appServicesCommand = cli.Command{ var appServicesCommand = cli.Command{
Name: "services", Name: "services",
Aliases: []string{"sr"}, Aliases: []string{"sr"},
Usage: "Display all services of an app", Usage: "Display all services of an app",
ArgsUsage: "<domain>", UsageText: "abra app services <domain> [options]",
Flags: []cli.Flag{ Before: internal.SubCommandBefore,
internal.DebugFlag, ShellComplete: autocomplete.AppNameComplete,
}, HideHelp: true,
Before: internal.SubCommandBefore, Action: func(ctx context.Context, cmd *cli.Command) error {
BashComplete: autocomplete.AppNameComplete, app := internal.ValidateApp(cmd)
Action: func(c *cli.Context) error {
app := internal.ValidateApp(c)
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -14,7 +14,7 @@ import (
stack "coopcloud.tech/abra/pkg/upstream/stack" stack "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
dockerClient "github.com/docker/docker/client" dockerClient "github.com/docker/docker/client"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var prune bool var prune bool
@ -65,25 +65,24 @@ func pruneApp(cl *dockerClient.Client, app appPkg.App) error {
var appUndeployCommand = cli.Command{ var appUndeployCommand = cli.Command{
Name: "undeploy", Name: "undeploy",
Aliases: []string{"un"}, Aliases: []string{"un"},
ArgsUsage: "<domain>", UsageText: "abra app undeploy <domain> [options]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag, internal.NoInputFlag,
internal.OfflineFlag, internal.OfflineFlag,
pruneFlag, pruneFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "Undeploy an app", Usage: "Undeploy an app",
BashComplete: autocomplete.AppNameComplete, ShellComplete: autocomplete.AppNameComplete,
Description: ` HideHelp: true,
This does not destroy any of the application data. Description: `This does not destroy any of the application data.
However, you should remain vigilant, as your swarm installation will consider However, you should remain vigilant, as your swarm installation will consider
any previously attached volumes as eligible for pruning once undeployed. any previously attached volumes as eligible for pruning once undeployed.
Passing "-p/--prune" does not remove those volumes.`, Passing "-p/--prune" does not remove those volumes.`,
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(cmd)
stackName := app.StackName() stackName := app.StackName()
cl, err := client.New(app.Server) cl, err := client.New(app.Server)

View File

@ -15,45 +15,37 @@ import (
stack "coopcloud.tech/abra/pkg/upstream/stack" stack "coopcloud.tech/abra/pkg/upstream/stack"
"coopcloud.tech/tagcmp" "coopcloud.tech/tagcmp"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var appUpgradeCommand = cli.Command{ var appUpgradeCommand = cli.Command{
Name: "upgrade", Name: "upgrade",
Aliases: []string{"up"}, Aliases: []string{"up"},
Usage: "Upgrade an app", Usage: "Upgrade an app",
ArgsUsage: "<domain> [<version>]", UsageText: "abra app upgrade <domain> [<version>] [options]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.ForceFlag, internal.ForceFlag,
internal.NoDomainChecksFlag, internal.NoDomainChecksFlag,
internal.DontWaitConvergeFlag, internal.DontWaitConvergeFlag,
internal.OfflineFlag,
internal.ReleaseNotesFlag, internal.ReleaseNotesFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `Upgrade an app.
Upgrade an app.
Unlike "deploy", chaos operations are not supported here. Only recipe versions Unlike "deploy", chaos operations are not supported here. Only recipe versions
are supported values for "[<version>]". are supported values for "[<version>]".
An upgrade can be destructive, please ensure you have a copy of your app data An upgrade can be destructive, please ensure you have a copy of your app data
beforehand. beforehand.`,
HideHelp: true,
EXAMPLE: ShellComplete: autocomplete.AppNameComplete,
Action: func(ctx context.Context, cmd *cli.Command) error {
abra app upgrade foo.example.com
abra app upgrade foo.example.com 1.2.3+3.2.1`,
BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error {
var warnMessages []string var warnMessages []string
app := internal.ValidateApp(c) app := internal.ValidateApp(cmd)
stackName := app.StackName() stackName := app.StackName()
specificVersion := c.Args().Get(1) specificVersion := cmd.Args().Get(1)
if specificVersion != "" { if specificVersion != "" {
log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion) log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion)
app.Recipe.Version = specificVersion app.Recipe.Version = specificVersion

View File

@ -11,22 +11,19 @@ import (
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/upstream/stack" "coopcloud.tech/abra/pkg/upstream/stack"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var appVolumeListCommand = cli.Command{ var appVolumeListCommand = cli.Command{
Name: "list", Name: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
ArgsUsage: "<domain>", UsageText: "abra app volume list <domain> [options]",
Flags: []cli.Flag{ Before: internal.SubCommandBefore,
internal.DebugFlag, Usage: "List volumes associated with an app",
internal.NoInputFlag, ShellComplete: autocomplete.AppNameComplete,
}, HideHelp: true,
Before: internal.SubCommandBefore, Action: func(ctx context.Context, cmd *cli.Command) error {
Usage: "List volumes associated with an app", app := internal.ValidateApp(cmd)
BashComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error {
app := internal.ValidateApp(c)
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {
@ -74,27 +71,26 @@ var appVolumeListCommand = cli.Command{
var appVolumeRemoveCommand = cli.Command{ var appVolumeRemoveCommand = cli.Command{
Name: "remove", Name: "remove",
Usage: "Remove volume(s) associated with an app", Usage: "Remove volume(s) associated with an app",
Description: ` Description: `Remove volumes associated with an app.
This command supports removing volumes associated with an app. The app in
question must be undeployed before you try to remove volumes. See "abra app The app in question must be undeployed before you try to remove volumes. See
undeploy <domain>" for more. "abra app undeploy <domain>" for more.
The command is interactive and will show a multiple select input which allows The command is interactive and will show a multiple select input which allows
you to make a seclection. Use the "?" key to see more help on navigating this you to make a seclection. Use the "?" key to see more help on navigating this
interface. interface.
Passing "--force/-f" will select all volumes for removal. Be careful.`, Passing "--force/-f" will select all volumes for removal. Be careful.`,
ArgsUsage: "<domain>", UsageText: "abra app volume remove [options] <domain>",
Aliases: []string{"rm"}, Aliases: []string{"rm"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag, internal.NoInputFlag,
internal.ForceFlag, internal.ForceFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.AppNameComplete, ShellComplete: autocomplete.AppNameComplete,
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
app := internal.ValidateApp(c) app := internal.ValidateApp(cmd)
cl, err := client.New(app.Server) cl, err := client.New(app.Server)
if err != nil { if err != nil {
@ -158,8 +154,8 @@ var appVolumeCommand = cli.Command{
Name: "volume", Name: "volume",
Aliases: []string{"vl"}, Aliases: []string{"vl"},
Usage: "Manage app volumes", Usage: "Manage app volumes",
ArgsUsage: "<domain>", UsageText: "abra app volume [command] [options] [arguments]",
Subcommands: []*cli.Command{ Commands: []*cli.Command{
&appVolumeListCommand, &appVolumeListCommand,
&appVolumeRemoveCommand, &appVolumeRemoveCommand,
}, },

View File

@ -1,6 +1,7 @@
package catalogue package catalogue
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
@ -15,25 +16,22 @@ import (
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var catalogueGenerateCommand = cli.Command{ var catalogueGenerateCommand = cli.Command{
Name: "generate", Name: "generate",
Aliases: []string{"g"}, Aliases: []string{"g"},
Usage: "Generate the recipe catalogue", Usage: "Generate the recipe catalogue",
UsageText: "abra catalogue generate [<recipe>] [options]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.PublishFlag, internal.PublishFlag,
internal.DryFlag, internal.DryFlag,
internal.SkipUpdatesFlag, internal.SkipUpdatesFlag,
internal.ChaosFlag, internal.ChaosFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `Generate a new copy of the recipe catalogue.
Generate a new copy of the recipe catalogue.
It is possible to generate new metadata for a single recipe by passing It is possible to generate new metadata for a single recipe by passing
<recipe>. The existing local catalogue will be updated, not overwritten. <recipe>. The existing local catalogue will be updated, not overwritten.
@ -45,14 +43,14 @@ If you have a Hub account you can have Abra log you in to avoid this. Pass
Push your new release to git.coopcloud.tech with "-p/--publish". This requires Push your new release to git.coopcloud.tech with "-p/--publish". This requires
that you have permission to git push to these repositories and have your SSH that you have permission to git push to these repositories and have your SSH
keys configured on your account.`, keys configured on your account.`,
ArgsUsage: "[<recipe>]", ShellComplete: autocomplete.RecipeNameComplete,
BashComplete: autocomplete.RecipeNameComplete, HideHelp: true,
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
recipeName := c.Args().First() recipeName := cmd.Args().First()
r := recipe.Get(recipeName) r := recipe.Get(recipeName)
if recipeName != "" { if recipeName != "" {
internal.ValidateRecipe(c) internal.ValidateRecipe(cmd)
} }
if !internal.Chaos { if !internal.Chaos {
@ -208,8 +206,8 @@ var CatalogueCommand = cli.Command{
Name: "catalogue", Name: "catalogue",
Usage: "Manage the recipe catalogue", Usage: "Manage the recipe catalogue",
Aliases: []string{"c"}, Aliases: []string{"c"},
ArgsUsage: "<recipe>", UsageText: "abra catalogue [command] [options] [arguments]",
Subcommands: []*cli.Command{ Commands: []*cli.Command{
&catalogueGenerateCommand, &catalogueGenerateCommand,
}, },
} }

View File

@ -2,6 +2,7 @@
package cli package cli
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -18,31 +19,24 @@ import (
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/web" "coopcloud.tech/abra/pkg/web"
charmLog "github.com/charmbracelet/log" charmLog "github.com/charmbracelet/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// AutoCompleteCommand helps people set up auto-complete in their shells // AutoCompleteCommand helps people set up auto-complete in their shells
var AutoCompleteCommand = cli.Command{ var AutoCompleteCommand = cli.Command{
Name: "autocomplete", Name: "autocomplete",
Aliases: []string{"ac"}, Aliases: []string{"ac"},
Usage: "Configure shell autocompletion", Usage: "Configure shell autocompletion",
Description: ` UsageText: "abra autocomplete <shell> [options]",
Set up shell auto-completion. Description: `Set up shell auto-completion.
Supported shells are: bash, fish, fizsh & zsh. Supported shells are: bash, fish, fizsh & zsh.`,
HideHelp: true,
EXAMPLE: Action: func(ctx context.Context, cmd *cli.Command) error {
shellType := cmd.Args().First()
abra autocomplete bash`,
ArgsUsage: "<shell>",
Flags: []cli.Flag{
internal.DebugFlag,
},
Action: func(c *cli.Context) error {
shellType := c.Args().First()
if shellType == "" { if shellType == "" {
internal.ShowSubcommandHelpAndError(c, errors.New("no shell provided")) internal.ShowSubcommandHelpAndError(cmd, errors.New("no shell provided"))
} }
supportedShells := map[string]bool{ supportedShells := map[string]bool{
@ -113,33 +107,29 @@ source /etc/fish/completions/abra
// UpgradeCommand upgrades abra in-place. // UpgradeCommand upgrades abra in-place.
var UpgradeCommand = cli.Command{ var UpgradeCommand = cli.Command{
Name: "upgrade", Name: "upgrade",
Aliases: []string{"u"}, Aliases: []string{"u"},
Usage: "Upgrade abra", Usage: "Upgrade abra",
Description: ` UsageText: "abra upgrade [options]",
Upgrade abra in-place with the latest stable or release candidate. Description: `Upgrade abra in-place with the latest stable or release candidate.
Use "-r/--rc" to install the latest release candidate. Please bear in mind that Use "--rc/-r" to install the latest release candidate. Please bear in mind that
it may contain absolutely catastrophic deal-breaker bugs. Thank you very much it may contain absolutely catastrophic deal-breaker bugs. Thank you very much
for the testing efforts 💗 for the testing efforts 💗`,
Flags: []cli.Flag{internal.RCFlag},
EXAMPLE: HideHelp: true,
Action: func(ctx context.Context, cmd *cli.Command) error {
abra upgrade
abra upgrade --rc`,
Flags: []cli.Flag{internal.RCFlag},
Action: func(c *cli.Context) error {
mainURL := "https://install.abra.coopcloud.tech" mainURL := "https://install.abra.coopcloud.tech"
cmd := exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash", mainURL)) c := exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash", mainURL))
if internal.RC { if internal.RC {
releaseCandidateURL := "https://git.coopcloud.tech/coop-cloud/abra/raw/branch/main/scripts/installer/installer" releaseCandidateURL := "https://git.coopcloud.tech/coop-cloud/abra/raw/branch/main/scripts/installer/installer"
cmd = exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash -s -- --rc", releaseCandidateURL)) c = exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash -s -- --rc", releaseCandidateURL))
} }
log.Debugf("attempting to run %s", cmd) log.Debugf("attempting to run %s", c)
if err := internal.RunCmd(cmd); err != nil { if err := internal.RunCmd(c); err != nil {
log.Fatal(err) log.Fatal(err)
} }
@ -147,18 +137,16 @@ EXAMPLE:
}, },
} }
func newAbraApp(version, commit string) *cli.App { func newAbraApp(version, commit string) *cli.Command {
app := &cli.App{ app := &cli.Command{
Name: "abra", Name: "abra",
Usage: `the Co-op Cloud command-line utility belt 🎩🐇 Usage: "The Co-op Cloud command-line utility belt 🎩🐇",
____ ____ _ _ UsageText: "abra [command] [arguments] [options]",
/ ___|___ ___ _ __ / ___| | ___ _ _ __| | Version: fmt.Sprintf("%s-%s", version, commit[:7]),
| | / _ \ _____ / _ \| '_ \ | | | |/ _ \| | | |/ _' | Flags: []cli.Flag{
| |__| (_) |_____| (_) | |_) | | |___| | (_) | |_| | (_| | // NOTE(d1): "GLOBAL OPTIONS" flags
\____\___/ \___/| .__/ \____|_|\___/ \__,_|\__,_| internal.DebugFlag,
|_| },
`,
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
Commands: []*cli.Command{ Commands: []*cli.Command{
&app.AppCommand, &app.AppCommand,
&server.ServerCommand, &server.ServerCommand,
@ -167,12 +155,13 @@ func newAbraApp(version, commit string) *cli.App {
&UpgradeCommand, &UpgradeCommand,
&AutoCompleteCommand, &AutoCompleteCommand,
}, },
BashComplete: autocomplete.SubcommandComplete, EnableShellCompletion: true,
UseShortOptionHandling: true,
HideHelpCommand: true,
ShellComplete: autocomplete.SubcommandComplete,
} }
app.EnableBashCompletion = true app.Before = func(ctx context.Context, cmd *cli.Command) error {
app.Before = func(c *cli.Context) error {
paths := []string{ paths := []string{
config.ABRA_DIR, config.ABRA_DIR,
config.SERVERS_DIR, config.SERVERS_DIR,
@ -198,6 +187,12 @@ func newAbraApp(version, commit string) *cli.App {
return nil return nil
} }
cli.HelpFlag = &cli.BoolFlag{
Name: "help",
Aliases: []string{"h, H"},
Usage: "Show help",
}
return app return app
} }
@ -205,7 +200,7 @@ func newAbraApp(version, commit string) *cli.App {
func RunApp(version, commit string) { func RunApp(version, commit string) {
app := newAbraApp(version, commit) app := newAbraApp(version, commit)
if err := app.Run(os.Args); err != nil { if err := app.Run(context.Background(), os.Args); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

View File

@ -1,10 +1,11 @@
package internal package internal
import ( import (
"context"
"os" "os"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// Secrets stores the variable from SecretsFlag // Secrets stores the variable from SecretsFlag
@ -74,7 +75,7 @@ var Chaos bool
var ChaosFlag = &cli.BoolFlag{ var ChaosFlag = &cli.BoolFlag{
Name: "chaos", Name: "chaos",
Aliases: []string{"C"}, Aliases: []string{"C"},
Usage: "Proceed with uncommitted recipes changes. Use with care!", Usage: "Ignore uncommitted recipes changes. Use with care!",
Destination: &Chaos, Destination: &Chaos,
} }
@ -116,7 +117,7 @@ var OfflineFlag = &cli.BoolFlag{
Name: "offline", Name: "offline",
Aliases: []string{"o"}, Aliases: []string{"o"},
Destination: &Offline, Destination: &Offline,
Usage: "Prefer offline & filesystem access when possible", Usage: "Prefer offline & filesystem access",
} }
// ReleaseNotes stores the variable from ReleaseNotesFlag. // ReleaseNotes stores the variable from ReleaseNotesFlag.
@ -138,7 +139,7 @@ var MachineReadableFlag = &cli.BoolFlag{
Name: "machine", Name: "machine",
Aliases: []string{"m"}, Aliases: []string{"m"},
Destination: &MachineReadable, Destination: &MachineReadable,
Usage: "Output in a machine-readable format (where supported)", Usage: "Machine-readable output",
} }
// RC signifies the latest release candidate // RC signifies the latest release candidate
@ -319,7 +320,7 @@ var AllServicesFlag = &cli.BoolFlag{
} }
// SubCommandBefore wires up pre-action machinery (e.g. --debug handling). // SubCommandBefore wires up pre-action machinery (e.g. --debug handling).
func SubCommandBefore(c *cli.Context) error { func SubCommandBefore(ctx context.Context, cmd *cli.Command) error {
if Debug { if Debug {
log.SetLevel(log.DebugLevel) log.SetLevel(log.DebugLevel)
log.SetOutput(os.Stderr) log.SetOutput(os.Stderr)

View File

@ -4,13 +4,13 @@ import (
"os" "os"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// ShowSubcommandHelpAndError exits the program on error, logs the error to the // ShowSubcommandHelpAndError exits the program on error, logs the error to the
// terminal, and shows the help command. // terminal, and shows the help command.
func ShowSubcommandHelpAndError(c *cli.Context, err interface{}) { func ShowSubcommandHelpAndError(cmd *cli.Command, err interface{}) {
if err2 := cli.ShowSubcommandHelp(c); err2 != nil { if err2 := cli.ShowSubcommandHelp(cmd); err2 != nil {
log.Error(err2) log.Error(err2)
} }
log.Error(err) log.Error(err)

View File

@ -9,12 +9,12 @@ import (
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// ValidateRecipe ensures the recipe arg is valid. // ValidateRecipe ensures the recipe arg is valid.
func ValidateRecipe(c *cli.Context) recipe.Recipe { func ValidateRecipe(cmd *cli.Command) recipe.Recipe {
recipeName := c.Args().First() recipeName := cmd.Args().First()
if recipeName == "" && !NoInput { if recipeName == "" && !NoInput {
var recipes []string var recipes []string
@ -54,7 +54,7 @@ func ValidateRecipe(c *cli.Context) recipe.Recipe {
} }
if recipeName == "" { if recipeName == "" {
ShowSubcommandHelpAndError(c, errors.New("no recipe name provided")) ShowSubcommandHelpAndError(cmd, errors.New("no recipe name provided"))
} }
chosenRecipe := recipe.Get(recipeName) chosenRecipe := recipe.Get(recipeName)
@ -64,7 +64,7 @@ func ValidateRecipe(c *cli.Context) recipe.Recipe {
} }
_, err = chosenRecipe.GetComposeConfig(nil) _, err = chosenRecipe.GetComposeConfig(nil)
if err != nil { if err != nil {
if c.Command.Name == "generate" { if cmd.Name == "generate" {
if strings.Contains(err.Error(), "missing a compose") { if strings.Contains(err.Error(), "missing a compose") {
log.Fatal(err) log.Fatal(err)
} }
@ -83,11 +83,11 @@ func ValidateRecipe(c *cli.Context) recipe.Recipe {
} }
// ValidateApp ensures the app name arg is valid. // ValidateApp ensures the app name arg is valid.
func ValidateApp(c *cli.Context) app.App { func ValidateApp(cmd *cli.Command) app.App {
appName := c.Args().First() appName := cmd.Args().First()
if appName == "" { if appName == "" {
ShowSubcommandHelpAndError(c, errors.New("no app provided")) ShowSubcommandHelpAndError(cmd, errors.New("no app provided"))
} }
app, err := app.Get(appName) app, err := app.Get(appName)
@ -101,8 +101,8 @@ func ValidateApp(c *cli.Context) app.App {
} }
// ValidateDomain ensures the domain name arg is valid. // ValidateDomain ensures the domain name arg is valid.
func ValidateDomain(c *cli.Context) string { func ValidateDomain(cmd *cli.Command) string {
domainName := c.Args().First() domainName := cmd.Args().First()
if domainName == "" && !NoInput { if domainName == "" && !NoInput {
prompt := &survey.Input{ prompt := &survey.Input{
@ -115,7 +115,7 @@ func ValidateDomain(c *cli.Context) string {
} }
if domainName == "" { if domainName == "" {
ShowSubcommandHelpAndError(c, errors.New("no domain provided")) ShowSubcommandHelpAndError(cmd, errors.New("no domain provided"))
} }
log.Debugf("validated %s as domain argument", domainName) log.Debugf("validated %s as domain argument", domainName)
@ -124,10 +124,10 @@ func ValidateDomain(c *cli.Context) string {
} }
// ValidateSubCmdFlags ensures flag order conforms to correct order // ValidateSubCmdFlags ensures flag order conforms to correct order
func ValidateSubCmdFlags(c *cli.Context) bool { func ValidateSubCmdFlags(cmd *cli.Command) bool {
for argIdx, arg := range c.Args().Slice() { for argIdx, arg := range cmd.Args().Slice() {
if !strings.HasPrefix(arg, "--") { if !strings.HasPrefix(arg, "--") {
for _, flag := range c.Args().Slice()[argIdx:] { for _, flag := range cmd.Args().Slice()[argIdx:] {
if strings.HasPrefix(flag, "--") { if strings.HasPrefix(flag, "--") {
return false return false
} }
@ -138,8 +138,8 @@ func ValidateSubCmdFlags(c *cli.Context) bool {
} }
// ValidateServer ensures the server name arg is valid. // ValidateServer ensures the server name arg is valid.
func ValidateServer(c *cli.Context) string { func ValidateServer(cmd *cli.Command) string {
serverName := c.Args().First() serverName := cmd.Args().First()
serverNames, err := config.ReadServerNames() serverNames, err := config.ReadServerNames()
if err != nil { if err != nil {
@ -164,11 +164,11 @@ func ValidateServer(c *cli.Context) string {
} }
if serverName == "" { if serverName == "" {
ShowSubcommandHelpAndError(c, errors.New("no server provided")) ShowSubcommandHelpAndError(cmd, errors.New("no server provided"))
} }
if !matched { if !matched {
ShowSubcommandHelpAndError(c, errors.New("server doesn't exist?")) ShowSubcommandHelpAndError(cmd, errors.New("server doesn't exist?"))
} }
log.Debugf("validated %s as server argument", serverName) log.Debugf("validated %s as server argument", serverName)

View File

@ -1,27 +1,26 @@
package recipe package recipe
import ( import (
"context"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
gitPkg "coopcloud.tech/abra/pkg/git" gitPkg "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var recipeDiffCommand = cli.Command{ var recipeDiffCommand = cli.Command{
Name: "diff", Name: "diff",
Usage: "Show unstaged changes in recipe config", Usage: "Show unstaged changes in recipe config",
Description: "This command requires /usr/bin/git.", Description: "This command requires /usr/bin/git.",
Aliases: []string{"d"}, Aliases: []string{"d"},
ArgsUsage: "<recipe>", UsageText: "abra recipe diff <recipe> [options]",
Flags: []cli.Flag{ Before: internal.SubCommandBefore,
internal.DebugFlag, ShellComplete: autocomplete.RecipeNameComplete,
internal.NoInputFlag, HideHelp: true,
}, Action: func(ctx context.Context, cmd *cli.Command) error {
Before: internal.SubCommandBefore, r := internal.ValidateRecipe(cmd)
BashComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error {
r := internal.ValidateRecipe(c)
if err := gitPkg.DiffUnstaged(r.Dir); err != nil { if err := gitPkg.DiffUnstaged(r.Dir); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -1,32 +1,30 @@
package recipe package recipe
import ( import (
"context"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var recipeFetchCommand = cli.Command{ var recipeFetchCommand = cli.Command{
Name: "fetch", Name: "fetch",
Usage: "Fetch recipe(s)", Usage: "Fetch recipe(s)",
Aliases: []string{"f"}, Aliases: []string{"f"},
ArgsUsage: "[<recipe>]", UsageText: "abra recipe fetch [<recipe>] [options]",
Description: "Retrieves all recipes if no <recipe> argument is passed", Description: "Retrieves all recipes if no <recipe> argument is passed",
Flags: []cli.Flag{ Before: internal.SubCommandBefore,
internal.DebugFlag, ShellComplete: autocomplete.RecipeNameComplete,
internal.NoInputFlag, HideHelp: true,
internal.OfflineFlag, Action: func(ctx context.Context, cmd *cli.Command) error {
}, recipeName := cmd.Args().First()
Before: internal.SubCommandBefore,
BashComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error {
recipeName := c.Args().First()
r := recipe.Get(recipeName) r := recipe.Get(recipeName)
if recipeName != "" { if recipeName != "" {
internal.ValidateRecipe(c) internal.ValidateRecipe(cmd)
if err := r.Ensure(false, false); err != nil { if err := r.Ensure(false, false); err != nil {
log.Fatal(err) log.Fatal(err)
} }

View File

@ -1,6 +1,7 @@
package recipe package recipe
import ( import (
"context"
"fmt" "fmt"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
@ -8,25 +9,23 @@ import (
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/lint" "coopcloud.tech/abra/pkg/lint"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var recipeLintCommand = cli.Command{ var recipeLintCommand = cli.Command{
Name: "lint", Name: "lint",
Usage: "Lint a recipe", Usage: "Lint a recipe",
Aliases: []string{"l"}, Aliases: []string{"l"},
ArgsUsage: "<recipe>", UsageText: "abra recipe lint <recipe> [options]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OnlyErrorFlag, internal.OnlyErrorFlag,
internal.OfflineFlag,
internal.NoInputFlag,
internal.ChaosFlag, internal.ChaosFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.RecipeNameComplete, ShellComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error { HideHelp: true,
recipe := internal.ValidateRecipe(c) Action: func(ctx context.Context, cmd *cli.Command) error {
recipe := internal.ValidateRecipe(cmd)
if err := recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -1,6 +1,7 @@
package recipe package recipe
import ( import (
"context"
"fmt" "fmt"
"sort" "sort"
"strconv" "strconv"
@ -10,7 +11,7 @@ import (
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var pattern string var pattern string
@ -23,17 +24,17 @@ var patternFlag = &cli.StringFlag{
} }
var recipeListCommand = cli.Command{ var recipeListCommand = cli.Command{
Name: "list", Name: "list",
Usage: "List available recipes", Usage: "List recipes",
Aliases: []string{"ls"}, UsageText: "abra recipe list [options]",
Aliases: []string{"ls"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.MachineReadableFlag, internal.MachineReadableFlag,
patternFlag, patternFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { HideHelp: true,
Action: func(ctx context.Context, cmd *cli.Command) error {
catl, err := recipe.ReadRecipeCatalogue(internal.Offline) catl, err := recipe.ReadRecipeCatalogue(internal.Offline)
if err != nil { if err != nil {
log.Fatal(err.Error()) log.Fatal(err.Error())

View File

@ -2,6 +2,7 @@ package recipe
import ( import (
"bytes" "bytes"
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -13,7 +14,7 @@ import (
"coopcloud.tech/abra/pkg/git" "coopcloud.tech/abra/pkg/git"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// recipeMetadata is the recipe metadata for the README.md // recipeMetadata is the recipe metadata for the README.md
@ -34,27 +35,24 @@ var recipeNewCommand = cli.Command{
Name: "new", Name: "new",
Aliases: []string{"n"}, Aliases: []string{"n"},
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.OfflineFlag,
internal.GitNameFlag, internal.GitNameFlag,
internal.GitEmailFlag, internal.GitEmailFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Usage: "Create a new recipe", Usage: "Create a new recipe",
ArgsUsage: "<recipe>", UsageText: "abra recipe new <recipe> [options]",
Description: ` Description: `Create a new recipe.
Create a new recipe.
Abra uses the built-in example repository which is available here: Abra uses the built-in example repository which is available here:
https://git.coopcloud.tech/coop-cloud/example`, https://git.coopcloud.tech/coop-cloud/example`,
Action: func(c *cli.Context) error { HideHelp: true,
recipeName := c.Args().First() Action: func(ctx context.Context, cmd *cli.Command) error {
recipeName := cmd.Args().First()
r := recipe.Get(recipeName) r := recipe.Get(recipeName)
if recipeName == "" { if recipeName == "" {
internal.ShowSubcommandHelpAndError(c, errors.New("no recipe name provided")) internal.ShowSubcommandHelpAndError(cmd, errors.New("no recipe name provided"))
} }
if _, err := os.Stat(r.Dir); !os.IsNotExist(err) { if _, err := os.Stat(r.Dir); !os.IsNotExist(err) {

View File

@ -1,7 +1,7 @@
package recipe package recipe
import ( import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// RecipeCommand defines all recipe related sub-commands. // RecipeCommand defines all recipe related sub-commands.
@ -9,17 +9,17 @@ var RecipeCommand = cli.Command{
Name: "recipe", Name: "recipe",
Aliases: []string{"r"}, Aliases: []string{"r"},
Usage: "Manage recipes", Usage: "Manage recipes",
ArgsUsage: "<recipe>", UsageText: "abra recipe [command] [arguments] [options]",
Description: ` Description: `A recipe is a blueprint for an app.
A recipe is a blueprint for an app. It is a bunch of config files which
describe how to deploy and maintain an app. Recipes are maintained by the Co-op It is a bunch of config files which describe how to deploy and maintain an app.
Cloud community and you can use Abra to read them, deploy them and create apps Recipes are maintained by the Co-op Cloud community and you can use Abra to
for you. read them, deploy them and create apps for you.
Anyone who uses a recipe can become a maintainer. Maintainers typically make Anyone who uses a recipe can become a maintainer. Maintainers typically make
sure the recipe is in good working order and the config upgraded in a timely sure the recipe is in good working order and the config upgraded in a timely
manner.`, manner.`,
Subcommands: []*cli.Command{ Commands: []*cli.Command{
&recipeFetchCommand, &recipeFetchCommand,
&recipeLintCommand, &recipeLintCommand,
&recipeListCommand, &recipeListCommand,

View File

@ -1,6 +1,7 @@
package recipe package recipe
import ( import (
"context"
"errors" "errors"
"fmt" "fmt"
"os" "os"
@ -18,17 +19,18 @@ import (
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var recipeReleaseCommand = cli.Command{ var recipeReleaseCommand = cli.Command{
Name: "release", Name: "release",
Aliases: []string{"rl"}, Aliases: []string{"rl"},
Usage: "Release a new recipe version", Usage: "Release a new recipe version",
ArgsUsage: "<recipe> [<version>]", UsageText: "abra recipe release <recipe> [<version>] [options]",
Description: ` Description: `Create a new version of a recipe.
Create a new version of a recipe. These versions are then published on the
Co-op Cloud recipe catalogue. These versions take the following form: These versions are then published on the Co-op Cloud recipe catalogue. These
versions take the following form:
a.b.c+x.y.z a.b.c+x.y.z
@ -46,19 +48,17 @@ Publish your new release to git.coopcloud.tech with "-p/--publish". This
requires that you have permission to git push to these repositories and have requires that you have permission to git push to these repositories and have
your SSH keys configured on your account.`, your SSH keys configured on your account.`,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.DryFlag, internal.DryFlag,
internal.MajorFlag, internal.MajorFlag,
internal.MinorFlag, internal.MinorFlag,
internal.PatchFlag, internal.PatchFlag,
internal.PublishFlag, internal.PublishFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.RecipeNameComplete, ShellComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error { HideHelp: true,
recipe := internal.ValidateRecipe(c) Action: func(ctx context.Context, cmd *cli.Command) error {
recipe := internal.ValidateRecipe(cmd)
imagesTmp, err := getImageVersions(recipe) imagesTmp, err := getImageVersions(recipe)
if err != nil { if err != nil {
@ -75,7 +75,7 @@ your SSH keys configured on your account.`,
log.Fatalf("main app service version for %s is empty?", recipe.Name) log.Fatalf("main app service version for %s is empty?", recipe.Name)
} }
tagString := c.Args().Get(1) tagString := cmd.Args().Get(1)
if tagString != "" { if tagString != "" {
if _, err := tagcmp.Parse(tagString); err != nil { if _, err := tagcmp.Parse(tagString); err != nil {
log.Fatalf("cannot parse %s, invalid tag specified?", tagString) log.Fatalf("cannot parse %s, invalid tag specified?", tagString)

View File

@ -1,32 +1,31 @@
package recipe package recipe
import ( import (
"context"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/autocomplete" "coopcloud.tech/abra/pkg/autocomplete"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var recipeResetCommand = cli.Command{ var recipeResetCommand = cli.Command{
Name: "reset", Name: "reset",
Usage: "Remove all unstaged changes from recipe config", Usage: "Remove all unstaged changes from recipe config",
Description: "WARNING: this will delete your changes. Be Careful.", Description: "WARNING: this will delete your changes. Be Careful.",
Aliases: []string{"rs"}, Aliases: []string{"rs"},
ArgsUsage: "<recipe>", UsageText: "abra recipe reset <recipe> [options]",
Flags: []cli.Flag{ Before: internal.SubCommandBefore,
internal.DebugFlag, ShellComplete: autocomplete.RecipeNameComplete,
internal.NoInputFlag, HideHelp: true,
}, Action: func(ctx context.Context, cmd *cli.Command) error {
Before: internal.SubCommandBefore, recipeName := cmd.Args().First()
BashComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error {
recipeName := c.Args().First()
r := recipe.Get(recipeName) r := recipe.Get(recipeName)
if recipeName != "" { if recipeName != "" {
internal.ValidateRecipe(c) internal.ValidateRecipe(cmd)
} }
repo, err := git.PlainOpen(r.Dir) repo, err := git.PlainOpen(r.Dir)

View File

@ -1,6 +1,7 @@
package recipe package recipe
import ( import (
"context"
"fmt" "fmt"
"strconv" "strconv"
@ -12,35 +13,34 @@ import (
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var recipeSyncCommand = cli.Command{ var recipeSyncCommand = cli.Command{
Name: "sync", Name: "sync",
Aliases: []string{"s"}, Aliases: []string{"s"},
Usage: "Sync recipe version label", Usage: "Sync recipe version label",
ArgsUsage: "<recipe> [<version>]", UsageText: "abra recipe lint <recipe> [<version>] [options]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.DryFlag, internal.DryFlag,
internal.MajorFlag, internal.MajorFlag,
internal.MinorFlag, internal.MinorFlag,
internal.PatchFlag, internal.PatchFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `Generate labels for the main recipe service.
Generate labels for the main recipe service (i.e. by convention, the service
named "app") which corresponds to the following format: By convention, the service named "app" using the following format:
coop-cloud.${STACK_NAME}.version=<version> coop-cloud.${STACK_NAME}.version=<version>
Where <version> can be specifed on the command-line or Abra can attempt to Where <version> can be specifed on the command-line or Abra can attempt to
auto-generate it for you. The <recipe> configuration will be updated on the auto-generate it for you. The <recipe> configuration will be updated on the
local file system.`, local file system.`,
BashComplete: autocomplete.RecipeNameComplete, ShellComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error { HideHelp: true,
recipe := internal.ValidateRecipe(c) Action: func(ctx context.Context, cmd *cli.Command) error {
recipe := internal.ValidateRecipe(cmd)
mainApp, err := internal.GetMainAppImage(recipe) mainApp, err := internal.GetMainAppImage(recipe)
if err != nil { if err != nil {
@ -59,7 +59,7 @@ local file system.`,
log.Fatal(err) log.Fatal(err)
} }
nextTag := c.Args().Get(1) nextTag := cmd.Args().Get(1)
if len(tags) == 0 && nextTag == "" { if len(tags) == 0 && nextTag == "" {
log.Warnf("no git tags found for %s", recipe.Name) log.Warnf("no git tags found for %s", recipe.Name)
if internal.NoInput { if internal.NoInput {

View File

@ -2,6 +2,7 @@ package recipe
import ( import (
"bufio" "bufio"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"os" "os"
@ -19,7 +20,7 @@ import (
"coopcloud.tech/tagcmp" "coopcloud.tech/tagcmp"
"github.com/AlecAivazis/survey/v2" "github.com/AlecAivazis/survey/v2"
"github.com/distribution/reference" "github.com/distribution/reference"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
type imgPin struct { type imgPin struct {
@ -27,8 +28,8 @@ type imgPin struct {
version tagcmp.Tag version tagcmp.Tag
} }
// anUpgrade represents a single service upgrade (as within a recipe), and the list of tags that it can be upgraded to, // anUpgrade represents a single service upgrade (as within a recipe), and the
// for serialization purposes. // list of tags that it can be upgraded to, for serialization purposes.
type anUpgrade struct { type anUpgrade struct {
Service string `json:"service"` Service string `json:"service"`
Image string `json:"image"` Image string `json:"image"`
@ -37,13 +38,13 @@ type anUpgrade struct {
} }
var recipeUpgradeCommand = cli.Command{ var recipeUpgradeCommand = cli.Command{
Name: "upgrade", Name: "upgrade",
Aliases: []string{"u"}, Aliases: []string{"u"},
Usage: "Upgrade recipe image tags", Usage: "Upgrade recipe image tags",
Description: ` UsageText: "abra recipe upgrade [<recipe>] [options]",
Parse all image tags within the given <recipe> configuration and prompt with Description: `Upgrade a given <recipe> configuration.
more recent tags to upgrade to. It will update the relevant compose file tags
on the local file system. It will update the relevant compose file tags on the local file system.
Some image tags cannot be parsed because they do not follow some sort of Some image tags cannot be parsed because they do not follow some sort of
semver-like convention. In this case, all possible tags will be listed and it semver-like convention. In this case, all possible tags will be listed and it
@ -53,25 +54,19 @@ The command is interactive and will show a select input which allows you to
make a seclection. Use the "?" key to see more help on navigating this make a seclection. Use the "?" key to see more help on navigating this
interface. interface.
You may invoke this command in "wizard" mode and be prompted for input. You may invoke this command in "wizard" mode and be prompted for input.`,
EXAMPLE:
abra recipe upgrade`,
ArgsUsage: "<recipe>",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.PatchFlag, internal.PatchFlag,
internal.MinorFlag, internal.MinorFlag,
internal.MajorFlag, internal.MajorFlag,
internal.MachineReadableFlag, internal.MachineReadableFlag,
internal.AllTagsFlag, internal.AllTagsFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.RecipeNameComplete, ShellComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error { HideHelp: true,
recipe := internal.ValidateRecipe(c) Action: func(ctx context.Context, cmd *cli.Command) error {
recipe := internal.ValidateRecipe(cmd)
if err := recipe.Ensure(internal.Chaos, internal.Offline); err != nil { if err := recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -1,6 +1,7 @@
package recipe package recipe
import ( import (
"context"
"fmt" "fmt"
"sort" "sort"
@ -9,7 +10,7 @@ import (
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
recipePkg "coopcloud.tech/abra/pkg/recipe" recipePkg "coopcloud.tech/abra/pkg/recipe"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
func sortServiceByName(versions [][]string) func(i, j int) bool { func sortServiceByName(versions [][]string) func(i, j int) bool {
@ -26,19 +27,17 @@ var recipeVersionCommand = cli.Command{
Name: "versions", Name: "versions",
Aliases: []string{"v"}, Aliases: []string{"v"},
Usage: "List recipe versions", Usage: "List recipe versions",
ArgsUsage: "<recipe>", UsageText: "abra recipe version <recipe> [options]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.OfflineFlag,
internal.NoInputFlag,
internal.MachineReadableFlag, internal.MachineReadableFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.RecipeNameComplete, ShellComplete: autocomplete.RecipeNameComplete,
Action: func(c *cli.Context) error { HideHelp: true,
Action: func(ctx context.Context, cmd *cli.Command) error {
var warnMessages []string var warnMessages []string
recipe := internal.ValidateRecipe(c) recipe := internal.ValidateRecipe(cmd)
catl, err := recipePkg.ReadRecipeCatalogue(internal.Offline) catl, err := recipePkg.ReadRecipeCatalogue(internal.Offline)
if err != nil { if err != nil {

View File

@ -1,6 +1,7 @@
package server package server
import ( import (
"context"
"errors" "errors"
"os" "os"
"path/filepath" "path/filepath"
@ -13,7 +14,7 @@ import (
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/server" "coopcloud.tech/abra/pkg/server"
sshPkg "coopcloud.tech/abra/pkg/ssh" sshPkg "coopcloud.tech/abra/pkg/ssh"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var local bool var local bool
@ -93,15 +94,15 @@ func createServerDir(name string) (bool, error) {
} }
var serverAddCommand = cli.Command{ var serverAddCommand = cli.Command{
Name: "add", Name: "add",
Aliases: []string{"a"}, Aliases: []string{"a"},
Usage: "Add a new server to your configuration", Usage: "Add a new server",
Description: ` UsageText: "abra server add <domain> [options]",
Add a new server to your configuration so that it can be managed by Abra. Description: `Add a new server to your configuration so that it can be managed by Abra.
Abra relies on the standard SSH command-line and ~/.ssh/config for client Abra relies on the standard SSH command-line and ~/.ssh/config for client
connection details. You must configure an entry per-host in your ~/.ssh/config connection details. You must configure an entry per-host in your ~/.ssh/config
for each server. For example: for each server:
Host example.com example Host example.com example
Hostname example.com Hostname example.com
@ -109,10 +110,6 @@ for each server. For example:
Port 12345 Port 12345
IdentityFile ~/.ssh/example@somewhere IdentityFile ~/.ssh/example@somewhere
You can then add a server like so:
abra server add example.com
If "--local" is passed, then Abra assumes that the current local server is If "--local" is passed, then Abra assumes that the current local server is
intended as the target server. This is useful when you want to have your entire intended as the target server. This is useful when you want to have your entire
Co-op Cloud config located on the server itself, and not on your local Co-op Cloud config located on the server itself, and not on your local
@ -120,28 +117,24 @@ developer machine. The domain is then set to "default".
You can also pass "--no-domain-checks/-D" flag to use any arbitrary name You can also pass "--no-domain-checks/-D" flag to use any arbitrary name
instead of a real domain. The host will be resolved with the "Hostname" entry instead of a real domain. The host will be resolved with the "Hostname" entry
of your ~/.ssh/config. Checks for a valid online domain will be skipped: of your ~/.ssh/config. Checks for a valid online domain will be skipped.`,
abra server add -D example`,
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.NoInputFlag,
internal.NoDomainChecksFlag, internal.NoDomainChecksFlag,
localFlag, localFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
ArgsUsage: "<name>", HideHelp: true,
Action: func(c *cli.Context) error { Action: func(ctx context.Context, cmd *cli.Command) error {
if c.Args().Len() > 0 && local || !internal.ValidateSubCmdFlags(c) { if cmd.Args().Len() > 0 && local || !internal.ValidateSubCmdFlags(cmd) {
err := errors.New("cannot use <name> and --local together") err := errors.New("cannot use <name> and --local together")
internal.ShowSubcommandHelpAndError(c, err) internal.ShowSubcommandHelpAndError(cmd, err)
} }
var name string var name string
if local { if local {
name = "default" name = "default"
} else { } else {
name = internal.ValidateDomain(c) name = internal.ValidateDomain(cmd)
} }
// NOTE(d1): reasonable 5 second timeout for connections which can't // NOTE(d1): reasonable 5 second timeout for connections which can't

View File

@ -1,30 +1,31 @@
package server package server
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
"coopcloud.tech/abra/cli/internal" "coopcloud.tech/abra/cli/internal"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/context" contextPkg "coopcloud.tech/abra/pkg/context"
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/docker/cli/cli/connhelper/ssh" "github.com/docker/cli/cli/connhelper/ssh"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var serverListCommand = cli.Command{ var serverListCommand = cli.Command{
Name: "list", Name: "list",
Aliases: []string{"ls"}, Aliases: []string{"ls"},
Usage: "List managed servers", Usage: "List managed servers",
UsageText: "abra server list [options]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.MachineReadableFlag, internal.MachineReadableFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Action: func(c *cli.Context) error { HideHelp: true,
dockerContextStore := context.NewDefaultDockerContextStore() Action: func(ctx context.Context, cmd *cli.Command) error {
dockerContextStore := contextPkg.NewDefaultDockerContextStore()
contexts, err := dockerContextStore.Store.List() contexts, err := dockerContextStore.Store.List()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -46,14 +47,14 @@ var serverListCommand = cli.Command{
var rows [][]string var rows [][]string
for _, serverName := range serverNames { for _, serverName := range serverNames {
var row []string var row []string
for _, ctx := range contexts { for _, dockerCtx := range contexts {
endpoint, err := context.GetContextEndpoint(ctx) endpoint, err := contextPkg.GetContextEndpoint(dockerCtx)
if err != nil && strings.Contains(err.Error(), "does not exist") { if err != nil && strings.Contains(err.Error(), "does not exist") {
// No local context found, we can continue safely // No local context found, we can continue safely
continue continue
} }
if ctx.Name == serverName { if dockerCtx.Name == serverName {
sp, err := ssh.ParseURL(endpoint) sp, err := ssh.ParseURL(endpoint)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -9,7 +9,7 @@ import (
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/docker/docker/api/types/filters" "github.com/docker/docker/api/types/filters"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var allFilter bool var allFilter bool
@ -31,26 +31,23 @@ var volumesFilterFlag = &cli.BoolFlag{
} }
var serverPruneCommand = cli.Command{ var serverPruneCommand = cli.Command{
Name: "prune", Name: "prune",
Aliases: []string{"p"}, Aliases: []string{"p"},
Usage: "Prune resources on a server", Usage: "Prune resources on a server",
Description: ` UsageText: "abra server prune <server> [options]",
Prunes unused containers, networks, and dangling images. Description: `Prunes unused containers, networks, and dangling images.
Use "-v/--volumes" to remove volumes that are not associated with a deployed Use "-v/--volumes" to remove volumes that are not associated with a deployed
app. This can result in unwanted data loss if not used carefully.`, app. This can result in unwanted data loss if not used carefully.`,
ArgsUsage: "[<server>]",
Flags: []cli.Flag{ Flags: []cli.Flag{
allFilterFlag, allFilterFlag,
volumesFilterFlag, volumesFilterFlag,
internal.DebugFlag,
internal.OfflineFlag,
internal.NoInputFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
BashComplete: autocomplete.ServerNameComplete, ShellComplete: autocomplete.ServerNameComplete,
Action: func(c *cli.Context) error { HideHelp: true,
serverName := internal.ValidateServer(c) Action: func(ctx context.Context, cmd *cli.Command) error {
serverName := internal.ValidateServer(cmd)
cl, err := client.New(serverName) cl, err := client.New(serverName)
if err != nil { if err != nil {
@ -59,7 +56,6 @@ app. This can result in unwanted data loss if not used carefully.`,
var args filters.Args var args filters.Args
ctx := context.Background()
cr, err := cl.ContainersPrune(ctx, args) cr, err := cl.ContainersPrune(ctx, args)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -1,6 +1,7 @@
package server package server
import ( import (
"context"
"os" "os"
"path/filepath" "path/filepath"
@ -9,29 +10,24 @@ import (
"coopcloud.tech/abra/pkg/client" "coopcloud.tech/abra/pkg/client"
"coopcloud.tech/abra/pkg/config" "coopcloud.tech/abra/pkg/config"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
var serverRemoveCommand = cli.Command{ var serverRemoveCommand = cli.Command{
Name: "remove", Name: "remove",
Aliases: []string{"rm"}, Aliases: []string{"rm"},
ArgsUsage: "<server>", UsageText: "abra server remove <domain> [options]",
Usage: "Remove a managed server", Usage: "Remove a managed server",
Description: ` Description: `Remove a managed server.
Remove a managed server.
Abra will remove the internal bookkeeping (~/.abra/servers/...) and underlying Abra will remove the internal bookkeeping ($ABRA_DIR/servers/...) and
client connection context. This server will then be lost in time, like tears in underlying client connection context. This server will then be lost in time,
rain.`, like tears in rain.`,
Flags: []cli.Flag{ Before: internal.SubCommandBefore,
internal.DebugFlag, ShellComplete: autocomplete.ServerNameComplete,
internal.NoInputFlag, HideHelp: true,
internal.OfflineFlag, Action: func(ctx context.Context, cmd *cli.Command) error {
}, serverName := internal.ValidateServer(cmd)
Before: internal.SubCommandBefore,
BashComplete: autocomplete.ServerNameComplete,
Action: func(c *cli.Context) error {
serverName := internal.ValidateServer(c)
if err := client.DeleteContext(serverName); err != nil { if err := client.DeleteContext(serverName); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -1,15 +1,16 @@
package server package server
import ( import (
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// ServerCommand defines the `abra server` command and its subcommands // ServerCommand defines the `abra server` command and its subcommands
var ServerCommand = cli.Command{ var ServerCommand = cli.Command{
Name: "server", Name: "server",
Aliases: []string{"s"}, Aliases: []string{"s"},
Usage: "Manage servers", Usage: "Manage servers",
Subcommands: []*cli.Command{ UsageText: "abra server [command] [arguments] [options]",
Commands: []*cli.Command{
&serverAddCommand, &serverAddCommand,
&serverListCommand, &serverListCommand,
&serverRemoveCommand, &serverRemoveCommand,

View File

@ -23,7 +23,7 @@ import (
dockerclient "github.com/docker/docker/client" dockerclient "github.com/docker/docker/client"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
const SERVER = "localhost" const SERVER = "localhost"
@ -46,23 +46,21 @@ var allFlag = &cli.BoolFlag{
// Notify checks for available upgrades // Notify checks for available upgrades
var Notify = cli.Command{ var Notify = cli.Command{
Name: "notify", Name: "notify",
Aliases: []string{"n"}, Aliases: []string{"n"},
Usage: "Check for available upgrades", Usage: "Check for available upgrades",
UsageText: "kadabra notify [options]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
majorFlag, majorFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `Notify on new versions for deployed apps.
Read the deployed app versions and look for new versions in the recipe
catalogue.
If a new patch/minor version is available, a notification is printed. If a new patch/minor version is available, a notification is printed.
Use "--major" to include new major versions.`, Use "--major" to include new major versions.`,
Action: func(c *cli.Context) error { HideHelp: true,
Action: func(ctx context.Context, cmd *cli.Command) error {
cl, err := client.New("default") cl, err := client.New("default")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -97,17 +95,14 @@ var UpgradeApp = cli.Command{
Name: "upgrade", Name: "upgrade",
Aliases: []string{"u"}, Aliases: []string{"u"},
Usage: "Upgrade apps", Usage: "Upgrade apps",
ArgsUsage: "<stack-name> <recipe>", UsageText: "kadabra notify <stack> <recipe> [options]",
Flags: []cli.Flag{ Flags: []cli.Flag{
internal.DebugFlag,
internal.ChaosFlag, internal.ChaosFlag,
majorFlag, majorFlag,
allFlag, allFlag,
internal.OfflineFlag,
}, },
Before: internal.SubCommandBefore, Before: internal.SubCommandBefore,
Description: ` Description: `Upgrade an app by specifying stack name and recipe.
Upgrade an app by specifying stack name and recipe.
Use "--all" to upgrade every deployed app. Use "--all" to upgrade every deployed app.
@ -118,15 +113,16 @@ available, the app is upgraded.
To include major versions use the "--major" flag. You probably don't want that To include major versions use the "--major" flag. You probably don't want that
as it will break things. Only apps that are not deployed with "--chaos" are as it will break things. Only apps that are not deployed with "--chaos" are
upgraded, to update chaos deployments use the "--chaos" flag. Use it with care.`, upgraded, to update chaos deployments use the "--chaos" flag. Use it with care.`,
Action: func(c *cli.Context) error { HideHelp: true,
Action: func(ctx context.Context, cmd *cli.Command) error {
cl, err := client.New("default") cl, err := client.New("default")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if !updateAll { if !updateAll {
stackName := c.Args().Get(0) stackName := cmd.Args().Get(0)
recipeName := c.Args().Get(1) recipeName := cmd.Args().Get(1)
err = tryUpgrade(cl, stackName, recipeName) err = tryUpgrade(cl, stackName, recipeName)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
@ -470,25 +466,25 @@ func upgrade(cl *dockerclient.Client, stackName, recipeName, upgradeVersion stri
return err return err
} }
func newAbraApp(version, commit string) *cli.App { func newKadabraApp(version, commit string) *cli.Command {
app := &cli.App{ app := &cli.Command{
Name: "kadabra", Name: "kadabra",
Usage: `The Co-op Cloud auto-updater Version: fmt.Sprintf("%s-%s", version, commit[:7]),
____ ____ _ _ Usage: "The Co-op Cloud auto-updater 🤖 🚀",
/ ___|___ ___ _ __ / ___| | ___ _ _ __| | UsageText: "kadabra [command] [options]",
| | / _ \ _____ / _ \| '_ \ | | | |/ _ \| | | |/ _' | UseShortOptionHandling: true,
| |__| (_) |_____| (_) | |_) | | |___| | (_) | |_| | (_| | HideHelpCommand: true,
\____\___/ \___/| .__/ \____|_|\___/ \__,_|\__,_| Flags: []cli.Flag{
|_| // NOTE(d1): "GLOBAL OPTIONS" flags
`, internal.DebugFlag,
Version: fmt.Sprintf("%s-%s", version, commit[:7]), },
Commands: []*cli.Command{ Commands: []*cli.Command{
&Notify, &Notify,
&UpgradeApp, &UpgradeApp,
}, },
} }
app.Before = func(c *cli.Context) error { app.Before = func(ctx context.Context, cmd *cli.Command) error {
log.Logger.SetStyles(log.Styles()) log.Logger.SetStyles(log.Styles())
charmLog.SetDefault(log.Logger) charmLog.SetDefault(log.Logger)
@ -497,14 +493,20 @@ func newAbraApp(version, commit string) *cli.App {
return nil return nil
} }
cli.HelpFlag = &cli.BoolFlag{
Name: "help",
Aliases: []string{"h, H"},
Usage: "Show help",
}
return app return app
} }
// RunApp runs CLI abra app. // RunApp runs CLI abra app.
func RunApp(version, commit string) { func RunApp(version, commit string) {
app := newAbraApp(version, commit) app := newKadabraApp(version, commit)
if err := app.Run(os.Args); err != nil { if err := app.Run(context.Background(), os.Args); err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }

7
go.mod
View File

@ -2,6 +2,8 @@ module coopcloud.tech/abra
go 1.21 go 1.21
replace github.com/urfave/cli/v3 => github.com/urfave/cli/v3 v3.0.0-alpha9.1.0.20241019193437-5053ec708a44
require ( require (
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb
git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100509-01bff8284355 git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100509-01bff8284355
@ -18,7 +20,7 @@ require (
github.com/moby/term v0.5.0 github.com/moby/term v0.5.0
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/schollz/progressbar/v3 v3.14.4 github.com/schollz/progressbar/v3 v3.14.4
github.com/urfave/cli/v2 v2.27.2 github.com/urfave/cli/v3 v3.0.0-alpha9
golang.org/x/term v0.22.0 golang.org/x/term v0.22.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
gotest.tools/v3 v3.5.1 gotest.tools/v3 v3.5.1
@ -37,7 +39,6 @@ require (
github.com/charmbracelet/x/ansi v0.1.3 // indirect github.com/charmbracelet/x/ansi v0.1.3 // indirect
github.com/cloudflare/circl v1.3.9 // indirect github.com/cloudflare/circl v1.3.9 // indirect
github.com/containerd/log v0.1.0 // indirect github.com/containerd/log v0.1.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.4 // indirect
github.com/cyphar/filepath-securejoin v0.2.5 // indirect github.com/cyphar/filepath-securejoin v0.2.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/docker/distribution v2.8.3+incompatible // indirect github.com/docker/distribution v2.8.3+incompatible // indirect
@ -85,14 +86,12 @@ require (
github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/common v0.55.0 // indirect
github.com/prometheus/procfs v0.15.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sirupsen/logrus v1.9.3 // indirect github.com/sirupsen/logrus v1.9.3 // indirect
github.com/skeema/knownhosts v1.2.2 // indirect github.com/skeema/knownhosts v1.2.2 // indirect
github.com/spf13/pflag v1.0.5 // indirect github.com/spf13/pflag v1.0.5 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect
github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect
github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xeipuuv/gojsonschema v1.2.0 // indirect
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect
go.opentelemetry.io/otel v1.28.0 // indirect go.opentelemetry.io/otel v1.28.0 // indirect
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect

8
go.sum
View File

@ -273,7 +273,6 @@ github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfc
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4=
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
@ -799,7 +798,6 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4=
github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0=
@ -883,8 +881,8 @@ github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijb
github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/cli v1.22.4/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
github.com/urfave/cli/v2 v2.27.2 h1:6e0H+AkS+zDckwPCUrZkKX38mRaau4nL2uipkJpbkcI= github.com/urfave/cli/v3 v3.0.0-alpha9.1.0.20241019193437-5053ec708a44 h1:BeSTAZEDkDVNv9EOrycIGCkEg+6EhRRgSsbdc93Q3OM=
github.com/urfave/cli/v2 v2.27.2/go.mod h1:g0+79LmHHATl7DAcHO99smiR/T7uGLw84w8Y42x+4eM= github.com/urfave/cli/v3 v3.0.0-alpha9.1.0.20241019193437-5053ec708a44/go.mod h1:FnIeEMYu+ko8zP1F9Ypr3xkZMIDqW3DR92yUtY39q1Y=
github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI= github.com/vbatts/tar-split v0.11.2/go.mod h1:vV3ZuO2yWSVsz+pfFzDG/upWH1JhjOiEaWq6kXyQ3VI=
github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk=
github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE=
@ -906,8 +904,6 @@ github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913 h1:+qGGcbkzsfDQNPPe9UDgpxAWQrhbbBXOYJFQDq/dtJw=
github.com/xrash/smetrics v0.0.0-20240312152122-5f08fbb34913/go.mod h1:4aEEwZQutDLsQv2Deui4iYQ6DWTxR14g6m8Wv88+Xqk=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k=

View File

@ -1,22 +1,23 @@
package autocomplete package autocomplete
import ( import (
"context"
"fmt" "fmt"
"coopcloud.tech/abra/pkg/app" "coopcloud.tech/abra/pkg/app"
"coopcloud.tech/abra/pkg/log" "coopcloud.tech/abra/pkg/log"
"coopcloud.tech/abra/pkg/recipe" "coopcloud.tech/abra/pkg/recipe"
"github.com/urfave/cli/v2" "github.com/urfave/cli/v3"
) )
// AppNameComplete copletes app names. // AppNameComplete copletes app names.
func AppNameComplete(c *cli.Context) { func AppNameComplete(ctx context.Context, cmd *cli.Command) {
appNames, err := app.GetAppNames() appNames, err := app.GetAppNames()
if err != nil { if err != nil {
log.Warn(err) log.Warn(err)
} }
if c.NArg() > 0 { if cmd.NArg() > 0 {
return return
} }
@ -36,13 +37,13 @@ func ServiceNameComplete(appName string) {
} }
// RecipeNameComplete completes recipe names. // RecipeNameComplete completes recipe names.
func RecipeNameComplete(c *cli.Context) { func RecipeNameComplete(ctx context.Context, cmd *cli.Command) {
catl, err := recipe.ReadRecipeCatalogue(false) catl, err := recipe.ReadRecipeCatalogue(false)
if err != nil { if err != nil {
log.Warn(err) log.Warn(err)
} }
if c.NArg() > 0 { if cmd.NArg() > 0 {
return return
} }
@ -66,13 +67,13 @@ func RecipeVersionComplete(recipeName string) {
} }
// ServerNameComplete completes server names. // ServerNameComplete completes server names.
func ServerNameComplete(c *cli.Context) { func ServerNameComplete(ctx context.Context, cmd *cli.Command) {
files, err := app.LoadAppFiles("") files, err := app.LoadAppFiles("")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
if c.NArg() > 0 { if cmd.NArg() > 0 {
return return
} }
@ -82,8 +83,8 @@ func ServerNameComplete(c *cli.Context) {
} }
// SubcommandComplete completes sub-commands. // SubcommandComplete completes sub-commands.
func SubcommandComplete(c *cli.Context) { func SubcommandComplete(ctx context.Context, cmd *cli.Command) {
if c.NArg() > 0 { if cmd.NArg() > 0 {
return return
} }

View File

@ -2,17 +2,31 @@
: ${PROG:=$(basename ${BASH_SOURCE})} : ${PROG:=$(basename ${BASH_SOURCE})}
# Macs have bash3 for which the bash-completion package doesn't include
# _init_completion. This is a minimal version of that function.
_cli_init_completion() {
COMPREPLY=()
_get_comp_words_by_ref "$@" cur prev words cword
}
_cli_bash_autocomplete() { _cli_bash_autocomplete() {
if [[ "${COMP_WORDS[0]}" != "source" ]]; then if [[ "${COMP_WORDS[0]}" != "source" ]]; then
local cur opts base local cur opts base words
COMPREPLY=() COMPREPLY=()
cur="${COMP_WORDS[COMP_CWORD]}" cur="${COMP_WORDS[COMP_CWORD]}"
if [[ "$cur" == "-"* ]]; then if declare -F _init_completion >/dev/null 2>&1; then
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} ${cur} --generate-bash-completion ) _init_completion -n "=:" || return
else else
opts=$( ${COMP_WORDS[@]:0:$COMP_CWORD} --generate-bash-completion ) _cli_init_completion -n "=:" || return
fi fi
COMPREPLY=( $(compgen -W "${opts}" -- ${cur}) ) words=("${words[@]:0:$cword}")
if [[ "$cur" == "-"* ]]; then
requestComp="${words[*]} ${cur} --generate-shell-completion"
else
requestComp="${words[*]} --generate-shell-completion"
fi
opts=$(eval "${requestComp}" 2>/dev/null)
COMPREPLY=($(compgen -W "${opts}" -- ${cur}))
return 0 return 0
fi fi
} }

View File

@ -1,5 +1,5 @@
function complete_abra_args function complete_abra_args
set -l cmd (commandline -poc) --generate-bash-completion set -l cmd (commandline -poc) --generate-shell-completion
$cmd $cmd
end end
complete -c abra -f -n "not __fish_seen_subcommand_from -h --help -v --version complete_abra_args" -a "(complete_abra_args)" complete -c abra -f -n "not __fish_seen_subcommand_from -h --help -v --version complete_abra_args" -a "(complete_abra_args)"

View File

@ -2,7 +2,7 @@ $fn = $($MyInvocation.MyCommand.Name)
$name = $fn -replace "(.*)\.ps1$", '$1' $name = $fn -replace "(.*)\.ps1$", '$1'
Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock { Register-ArgumentCompleter -Native -CommandName $name -ScriptBlock {
param($commandName, $wordToComplete, $cursorPosition) param($commandName, $wordToComplete, $cursorPosition)
$other = "$wordToComplete --generate-bash-completion" $other = "$wordToComplete --generate-shell-completion"
Invoke-Expression $other | ForEach-Object { Invoke-Expression $other | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_) [System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
} }

View File

@ -1,23 +1,26 @@
#compdef $PROG #compdef abra
compdef _abra abra
_cli_zsh_autocomplete() { # https://github.com/urfave/cli/blob/main/autocomplete/zsh_autocomplete
local -a opts _abra() {
local cur local -a opts
cur=${words[-1]} local cur
if [[ "$cur" == "-"* ]]; then cur=${words[-1]}
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} ${cur} --generate-bash-completion)}") if [[ "$cur" == "-"* ]]; then
else opts=("${(@f)$(${words[@]:0:#words[@]-1} ${cur} --generate-shell-completion)}")
opts=("${(@f)$(_CLI_ZSH_AUTOCOMPLETE_HACK=1 ${words[@]:0:#words[@]-1} --generate-bash-completion)}") else
fi opts=("${(@f)$(${words[@]:0:#words[@]-1} --generate-shell-completion)}")
fi
if [[ "${opts[1]}" != "" ]]; then if [[ "${opts[1]}" != "" ]]; then
_describe 'values' opts _describe 'values' opts
else else
_files _files
fi fi
return
} }
compdef _cli_zsh_autocomplete $PROG # don't run the completion function when being source-ed or eval-ed
if [ "$funcstack[1]" = "_abra" ]; then
_abra
fi

View File

@ -1,4 +1,3 @@
//go:build windows
// +build windows // +build windows
package winterm package winterm

View File

@ -1,4 +1,3 @@
//go:build windows
// +build windows // +build windows
package winterm package winterm

View File

@ -1,4 +1,3 @@
//go:build windows
// +build windows // +build windows
package winterm package winterm

View File

@ -1,4 +1,3 @@
//go:build windows
// +build windows // +build windows
package winterm package winterm

View File

@ -1,4 +1,3 @@
//go:build windows
// +build windows // +build windows
package winterm package winterm

View File

@ -1,4 +1,3 @@
//go:build windows
// +build windows // +build windows
package winterm package winterm

View File

@ -1,4 +1,3 @@
//go:build windows
// +build windows // +build windows
package winterm package winterm

View File

@ -1,4 +1,3 @@
//go:build windows
// +build windows // +build windows
package winterm package winterm

View File

@ -259,7 +259,7 @@ func (e *Entity) EncryptPrivateKeys(passphrase []byte, config *packet.Config) er
var keysToEncrypt []*packet.PrivateKey var keysToEncrypt []*packet.PrivateKey
// Add entity private key to encrypt. // Add entity private key to encrypt.
if e.PrivateKey != nil && !e.PrivateKey.Dummy() && !e.PrivateKey.Encrypted { if e.PrivateKey != nil && !e.PrivateKey.Dummy() && !e.PrivateKey.Encrypted {
keysToEncrypt = append(keysToEncrypt, e.PrivateKey) keysToEncrypt = append(keysToEncrypt, e.PrivateKey)
} }
// Add subkeys to encrypt. // Add subkeys to encrypt.
@ -284,7 +284,7 @@ func (e *Entity) DecryptPrivateKeys(passphrase []byte) error {
// Add subkeys to decrypt. // Add subkeys to decrypt.
for _, sub := range e.Subkeys { for _, sub := range e.Subkeys {
if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() && sub.PrivateKey.Encrypted { if sub.PrivateKey != nil && !sub.PrivateKey.Dummy() && sub.PrivateKey.Encrypted {
keysToDecrypt = append(keysToDecrypt, sub.PrivateKey) keysToDecrypt = append(keysToDecrypt, sub.PrivateKey)
} }
} }
return packet.DecryptPrivateKeys(keysToDecrypt, passphrase) return packet.DecryptPrivateKeys(keysToDecrypt, passphrase)

View File

@ -458,7 +458,7 @@ func (pk *PrivateKey) Decrypt(passphrase []byte) error {
} }
// DecryptPrivateKeys decrypts all encrypted keys with the given config and passphrase. // DecryptPrivateKeys decrypts all encrypted keys with the given config and passphrase.
// Avoids recomputation of similar s2k key derivations. // Avoids recomputation of similar s2k key derivations.
func DecryptPrivateKeys(keys []*PrivateKey, passphrase []byte) error { func DecryptPrivateKeys(keys []*PrivateKey, passphrase []byte) error {
// Create a cache to avoid recomputation of key derviations for the same passphrase. // Create a cache to avoid recomputation of key derviations for the same passphrase.
s2kCache := &s2k.Cache{} s2kCache := &s2k.Cache{}
@ -485,7 +485,7 @@ func (pk *PrivateKey) encrypt(key []byte, params *s2k.Params, cipherFunction Cip
if len(key) != cipherFunction.KeySize() { if len(key) != cipherFunction.KeySize() {
return errors.InvalidArgumentError("supplied encryption key has the wrong size") return errors.InvalidArgumentError("supplied encryption key has the wrong size")
} }
priv := bytes.NewBuffer(nil) priv := bytes.NewBuffer(nil)
err := pk.serializePrivateKey(priv) err := pk.serializePrivateKey(priv)
if err != nil { if err != nil {
@ -497,7 +497,7 @@ func (pk *PrivateKey) encrypt(key []byte, params *s2k.Params, cipherFunction Cip
pk.s2k, err = pk.s2kParams.Function() pk.s2k, err = pk.s2kParams.Function()
if err != nil { if err != nil {
return err return err
} }
privateKeyBytes := priv.Bytes() privateKeyBytes := priv.Bytes()
pk.sha1Checksum = true pk.sha1Checksum = true
@ -581,7 +581,7 @@ func (pk *PrivateKey) Encrypt(passphrase []byte) error {
S2KMode: s2k.IteratedSaltedS2K, S2KMode: s2k.IteratedSaltedS2K,
S2KCount: 65536, S2KCount: 65536,
Hash: crypto.SHA256, Hash: crypto.SHA256,
}, } ,
DefaultCipher: CipherAES256, DefaultCipher: CipherAES256,
} }
return pk.EncryptWithConfig(passphrase, config) return pk.EncryptWithConfig(passphrase, config)

View File

@ -87,10 +87,10 @@ func decodeCount(c uint8) int {
// encodeMemory converts the Argon2 "memory" in the range parallelism*8 to // encodeMemory converts the Argon2 "memory" in the range parallelism*8 to
// 2**31, inclusive, to an encoded memory. The return value is the // 2**31, inclusive, to an encoded memory. The return value is the
// octet that is actually stored in the GPG file. encodeMemory panics // octet that is actually stored in the GPG file. encodeMemory panics
// if is not in the above range // if is not in the above range
// See OpenPGP crypto refresh Section 3.7.1.4. // See OpenPGP crypto refresh Section 3.7.1.4.
func encodeMemory(memory uint32, parallelism uint8) uint8 { func encodeMemory(memory uint32, parallelism uint8) uint8 {
if memory < (8*uint32(parallelism)) || memory > uint32(2147483648) { if memory < (8 * uint32(parallelism)) || memory > uint32(2147483648) {
panic("Memory argument memory is outside the required range") panic("Memory argument memory is outside the required range")
} }
@ -199,8 +199,8 @@ func Generate(rand io.Reader, c *Config) (*Params, error) {
} }
params = &Params{ params = &Params{
mode: SaltedS2K, mode: SaltedS2K,
hashId: hashId, hashId: hashId,
} }
} else { // Enforce IteratedSaltedS2K method otherwise } else { // Enforce IteratedSaltedS2K method otherwise
hashId, ok := algorithm.HashToHashId(c.hash()) hashId, ok := algorithm.HashToHashId(c.hash())
@ -211,7 +211,7 @@ func Generate(rand io.Reader, c *Config) (*Params, error) {
c.S2KMode = IteratedSaltedS2K c.S2KMode = IteratedSaltedS2K
} }
params = &Params{ params = &Params{
mode: IteratedSaltedS2K, mode: IteratedSaltedS2K,
hashId: hashId, hashId: hashId,
countByte: c.EncodedCount(), countByte: c.EncodedCount(),
} }
@ -306,12 +306,9 @@ func (params *Params) Dummy() bool {
func (params *Params) salt() []byte { func (params *Params) salt() []byte {
switch params.mode { switch params.mode {
case SaltedS2K, IteratedSaltedS2K: case SaltedS2K, IteratedSaltedS2K: return params.saltBytes[:8]
return params.saltBytes[:8] case Argon2S2K: return params.saltBytes[:Argon2SaltSize]
case Argon2S2K: default: return nil
return params.saltBytes[:Argon2SaltSize]
default:
return nil
} }
} }

View File

@ -5,7 +5,7 @@ package s2k
// the same parameters. // the same parameters.
type Cache map[Params][]byte type Cache map[Params][]byte
// GetOrComputeDerivedKey tries to retrieve the key // GetOrComputeDerivedKey tries to retrieve the key
// for the given s2k parameters from the cache. // for the given s2k parameters from the cache.
// If there is no hit, it derives the key with the s2k function from the passphrase, // If there is no hit, it derives the key with the s2k function from the passphrase,
// updates the cache, and returns the key. // updates the cache, and returns the key.

View File

@ -50,9 +50,9 @@ type Config struct {
type Argon2Config struct { type Argon2Config struct {
NumberOfPasses uint8 NumberOfPasses uint8
DegreeOfParallelism uint8 DegreeOfParallelism uint8
// The memory parameter for Argon2 specifies desired memory usage in kibibytes. // The memory parameter for Argon2 specifies desired memory usage in kibibytes.
// For example memory=64*1024 sets the memory cost to ~64 MB. // For example memory=64*1024 sets the memory cost to ~64 MB.
Memory uint32 Memory uint32
} }
func (c *Config) Mode() Mode { func (c *Config) Mode() Mode {
@ -115,7 +115,7 @@ func (c *Argon2Config) EncodedMemory() uint8 {
} }
memory := c.Memory memory := c.Memory
lowerBound := uint32(c.Parallelism()) * 8 lowerBound := uint32(c.Parallelism())*8
upperBound := uint32(2147483648) upperBound := uint32(2147483648)
switch { switch {

View File

@ -9,7 +9,7 @@
// //
// For more detailed information about the algorithm used, see: // For more detailed information about the algorithm used, see:
// //
// # Effective Computation of Biased Quantiles over Data Streams // Effective Computation of Biased Quantiles over Data Streams
// //
// http://www.cs.rutgers.edu/~muthu/bquant.pdf // http://www.cs.rutgers.edu/~muthu/bquant.pdf
package quantile package quantile

View File

@ -11,17 +11,17 @@ period for each retry attempt using a randomization function that grows exponent
NextBackOff() is calculated using the following formula: NextBackOff() is calculated using the following formula:
randomized interval = randomized interval =
RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor]) RetryInterval * (random value in range [1 - RandomizationFactor, 1 + RandomizationFactor])
In other words NextBackOff() will range between the randomization factor In other words NextBackOff() will range between the randomization factor
percentage below and above the retry interval. percentage below and above the retry interval.
For example, given the following parameters: For example, given the following parameters:
RetryInterval = 2 RetryInterval = 2
RandomizationFactor = 0.5 RandomizationFactor = 0.5
Multiplier = 2 Multiplier = 2
the actual backoff period used in the next retry attempt will range between 1 and 3 seconds, the actual backoff period used in the next retry attempt will range between 1 and 3 seconds,
multiplied by the exponential, that is, between 2 and 6 seconds. multiplied by the exponential, that is, between 2 and 6 seconds.
@ -36,18 +36,18 @@ The elapsed time can be reset by calling Reset().
Example: Given the following default arguments, for 10 tries the sequence will be, Example: Given the following default arguments, for 10 tries the sequence will be,
and assuming we go over the MaxElapsedTime on the 10th try: and assuming we go over the MaxElapsedTime on the 10th try:
Request # RetryInterval (seconds) Randomized Interval (seconds) Request # RetryInterval (seconds) Randomized Interval (seconds)
1 0.5 [0.25, 0.75] 1 0.5 [0.25, 0.75]
2 0.75 [0.375, 1.125] 2 0.75 [0.375, 1.125]
3 1.125 [0.562, 1.687] 3 1.125 [0.562, 1.687]
4 1.687 [0.8435, 2.53] 4 1.687 [0.8435, 2.53]
5 2.53 [1.265, 3.795] 5 2.53 [1.265, 3.795]
6 3.795 [1.897, 5.692] 6 3.795 [1.897, 5.692]
7 5.692 [2.846, 8.538] 7 5.692 [2.846, 8.538]
8 8.538 [4.269, 12.807] 8 8.538 [4.269, 12.807]
9 12.807 [6.403, 19.210] 9 12.807 [6.403, 19.210]
10 19.210 backoff.Stop 10 19.210 backoff.Stop
Note: Implementation is not thread-safe. Note: Implementation is not thread-safe.
*/ */
@ -167,8 +167,7 @@ func (b *ExponentialBackOff) Reset() {
} }
// NextBackOff calculates the next backoff interval using the formula: // NextBackOff calculates the next backoff interval using the formula:
// // Randomized interval = RetryInterval * (1 ± RandomizationFactor)
// Randomized interval = RetryInterval * (1 ± RandomizationFactor)
func (b *ExponentialBackOff) NextBackOff() time.Duration { func (b *ExponentialBackOff) NextBackOff() time.Duration {
// Make sure we have not gone over the maximum elapsed time. // Make sure we have not gone over the maximum elapsed time.
elapsed := b.GetElapsedTime() elapsed := b.GetElapsedTime()
@ -201,8 +200,7 @@ func (b *ExponentialBackOff) incrementCurrentInterval() {
} }
// Returns a random value from the following interval: // Returns a random value from the following interval:
// // [currentInterval - randomizationFactor * currentInterval, currentInterval + randomizationFactor * currentInterval].
// [currentInterval - randomizationFactor * currentInterval, currentInterval + randomizationFactor * currentInterval].
func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration { func getRandomValueFromInterval(randomizationFactor, random float64, currentInterval time.Duration) time.Duration {
if randomizationFactor == 0 { if randomizationFactor == 0 {
return currentInterval // make sure no randomness is used when randomizationFactor is 0. return currentInterval // make sure no randomness is used when randomizationFactor is 0.

View File

@ -3,13 +3,13 @@
// //
// Grammar // Grammar
// //
// reference := name [ ":" tag ] [ "@" digest ] // reference := name [ ":" tag ] [ "@" digest ]
// name := [domain '/'] path-component ['/' path-component]* // name := [domain '/'] path-component ['/' path-component]*
// domain := domain-component ['.' domain-component]* [':' port-number] // domain := domain-component ['.' domain-component]* [':' port-number]
// domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/ // domain-component := /([a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9])/
// port-number := /[0-9]+/ // port-number := /[0-9]+/
// path-component := alpha-numeric [separator alpha-numeric]* // path-component := alpha-numeric [separator alpha-numeric]*
// alpha-numeric := /[a-z0-9]+/ // alpha-numeric := /[a-z0-9]+/
// separator := /[_.]|__|[-]*/ // separator := /[_.]|__|[-]*/
// //
// tag := /[\w][\w.-]{0,127}/ // tag := /[\w][\w.-]{0,127}/

View File

@ -1,4 +1,3 @@
//go:build !linux && (!386 || !amd64)
// +build !linux // +build !linux
// +build !386 !amd64 // +build !386 !amd64

View File

@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build linux
// +build linux // +build linux
package keyctl package keyctl

View File

@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build linux
// +build linux // +build linux
// Package keyctl is a Go interface to linux kernel keyrings (keyctl interface) // Package keyctl is a Go interface to linux kernel keyrings (keyctl interface)

View File

@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build linux
// +build linux // +build linux
package keyctl package keyctl

View File

@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//go:build linux
// +build linux // +build linux
package keyctl package keyctl

View File

@ -131,25 +131,24 @@ type BICReplacementCandidate struct {
// BlobInfoCache records data useful for reusing blobs, or substituing equivalent ones, to avoid unnecessary blob copies. // BlobInfoCache records data useful for reusing blobs, or substituing equivalent ones, to avoid unnecessary blob copies.
// //
// It records two kinds of data: // It records two kinds of data:
// - Sets of corresponding digest vs. uncompressed digest ("DiffID") pairs:
// One of the two digests is known to be uncompressed, and a single uncompressed digest may correspond to more than one compressed digest.
// This allows matching compressed layer blobs to existing local uncompressed layers (to avoid unnecessary download and decompresssion),
// or uncompressed layer blobs to existing remote compressed layers (to avoid unnecessary compression and upload)/
// //
// - Sets of corresponding digest vs. uncompressed digest ("DiffID") pairs: // It is allowed to record an (uncompressed digest, the same uncompressed digest) correspondence, to express that the digest is known
// One of the two digests is known to be uncompressed, and a single uncompressed digest may correspond to more than one compressed digest. // to be uncompressed (i.e. that a conversion from schema1 does not have to decompress the blob to compute a DiffID value).
// This allows matching compressed layer blobs to existing local uncompressed layers (to avoid unnecessary download and decompresssion),
// or uncompressed layer blobs to existing remote compressed layers (to avoid unnecessary compression and upload)/
// //
// It is allowed to record an (uncompressed digest, the same uncompressed digest) correspondence, to express that the digest is known // This mapping is primarily maintained in generic copy.Image code, but transports may want to contribute more data points if they independently
// to be uncompressed (i.e. that a conversion from schema1 does not have to decompress the blob to compute a DiffID value). // compress/decompress blobs for their own purposes.
// //
// This mapping is primarily maintained in generic copy.Image code, but transports may want to contribute more data points if they independently // - Known blob locations, managed by individual transports:
// compress/decompress blobs for their own purposes. // The transports call RecordKnownLocation when encountering a blob that could possibly be reused (typically in GetBlob/PutBlob/TryReusingBlob),
// recording transport-specific information that allows the transport to reuse the blob in the future;
// then, TryReusingBlob implementations can call CandidateLocations to look up previously recorded blob locations that could be reused.
// //
// - Known blob locations, managed by individual transports: // Each transport defines its own “scopes” within which blob reuse is possible (e.g. in, the docker/distribution case, blobs
// The transports call RecordKnownLocation when encountering a blob that could possibly be reused (typically in GetBlob/PutBlob/TryReusingBlob), // can be directly reused within a registry, or mounted across registries within a registry server.)
// recording transport-specific information that allows the transport to reuse the blob in the future;
// then, TryReusingBlob implementations can call CandidateLocations to look up previously recorded blob locations that could be reused.
//
// Each transport defines its own “scopes” within which blob reuse is possible (e.g. in, the docker/distribution case, blobs
// can be directly reused within a registry, or mounted across registries within a registry server.)
// //
// None of the methods return an error indication: errors when neither reading from, nor writing to, the cache, should be fatal; // None of the methods return an error indication: errors when neither reading from, nor writing to, the cache, should be fatal;
// users of the cahce should just fall back to copying the blobs the usual way. // users of the cahce should just fall back to copying the blobs the usual way.

View File

@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014 Brian Goff
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@ -1,16 +0,0 @@
package md2man
import (
"github.com/russross/blackfriday/v2"
)
// Render converts a markdown document into a roff formatted document.
func Render(doc []byte) []byte {
renderer := NewRoffRenderer()
return blackfriday.Run(doc,
[]blackfriday.Option{
blackfriday.WithRenderer(renderer),
blackfriday.WithExtensions(renderer.GetExtensions()),
}...)
}

View File

@ -1,382 +0,0 @@
package md2man
import (
"bufio"
"bytes"
"fmt"
"io"
"os"
"strings"
"github.com/russross/blackfriday/v2"
)
// roffRenderer implements the blackfriday.Renderer interface for creating
// roff format (manpages) from markdown text
type roffRenderer struct {
extensions blackfriday.Extensions
listCounters []int
firstHeader bool
firstDD bool
listDepth int
}
const (
titleHeader = ".TH "
topLevelHeader = "\n\n.SH "
secondLevelHdr = "\n.SH "
otherHeader = "\n.SS "
crTag = "\n"
emphTag = "\\fI"
emphCloseTag = "\\fP"
strongTag = "\\fB"
strongCloseTag = "\\fP"
breakTag = "\n.br\n"
paraTag = "\n.PP\n"
hruleTag = "\n.ti 0\n\\l'\\n(.lu'\n"
linkTag = "\n\\[la]"
linkCloseTag = "\\[ra]"
codespanTag = "\\fB"
codespanCloseTag = "\\fR"
codeTag = "\n.EX\n"
codeCloseTag = ".EE\n" // Do not prepend a newline character since code blocks, by definition, include a newline already (or at least as how blackfriday gives us on).
quoteTag = "\n.PP\n.RS\n"
quoteCloseTag = "\n.RE\n"
listTag = "\n.RS\n"
listCloseTag = "\n.RE\n"
dtTag = "\n.TP\n"
dd2Tag = "\n"
tableStart = "\n.TS\nallbox;\n"
tableEnd = ".TE\n"
tableCellStart = "T{\n"
tableCellEnd = "\nT}\n"
tablePreprocessor = `'\" t`
)
// NewRoffRenderer creates a new blackfriday Renderer for generating roff documents
// from markdown
func NewRoffRenderer() *roffRenderer { // nolint: golint
var extensions blackfriday.Extensions
extensions |= blackfriday.NoIntraEmphasis
extensions |= blackfriday.Tables
extensions |= blackfriday.FencedCode
extensions |= blackfriday.SpaceHeadings
extensions |= blackfriday.Footnotes
extensions |= blackfriday.Titleblock
extensions |= blackfriday.DefinitionLists
return &roffRenderer{
extensions: extensions,
}
}
// GetExtensions returns the list of extensions used by this renderer implementation
func (r *roffRenderer) GetExtensions() blackfriday.Extensions {
return r.extensions
}
// RenderHeader handles outputting the header at document start
func (r *roffRenderer) RenderHeader(w io.Writer, ast *blackfriday.Node) {
// We need to walk the tree to check if there are any tables.
// If there are, we need to enable the roff table preprocessor.
ast.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
if node.Type == blackfriday.Table {
out(w, tablePreprocessor+"\n")
return blackfriday.Terminate
}
return blackfriday.GoToNext
})
// disable hyphenation
out(w, ".nh\n")
}
// RenderFooter handles outputting the footer at the document end; the roff
// renderer has no footer information
func (r *roffRenderer) RenderFooter(w io.Writer, ast *blackfriday.Node) {
}
// RenderNode is called for each node in a markdown document; based on the node
// type the equivalent roff output is sent to the writer
func (r *roffRenderer) RenderNode(w io.Writer, node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
walkAction := blackfriday.GoToNext
switch node.Type {
case blackfriday.Text:
escapeSpecialChars(w, node.Literal)
case blackfriday.Softbreak:
out(w, crTag)
case blackfriday.Hardbreak:
out(w, breakTag)
case blackfriday.Emph:
if entering {
out(w, emphTag)
} else {
out(w, emphCloseTag)
}
case blackfriday.Strong:
if entering {
out(w, strongTag)
} else {
out(w, strongCloseTag)
}
case blackfriday.Link:
// Don't render the link text for automatic links, because this
// will only duplicate the URL in the roff output.
// See https://daringfireball.net/projects/markdown/syntax#autolink
if !bytes.Equal(node.LinkData.Destination, node.FirstChild.Literal) {
out(w, string(node.FirstChild.Literal))
}
// Hyphens in a link must be escaped to avoid word-wrap in the rendered man page.
escapedLink := strings.ReplaceAll(string(node.LinkData.Destination), "-", "\\-")
out(w, linkTag+escapedLink+linkCloseTag)
walkAction = blackfriday.SkipChildren
case blackfriday.Image:
// ignore images
walkAction = blackfriday.SkipChildren
case blackfriday.Code:
out(w, codespanTag)
escapeSpecialChars(w, node.Literal)
out(w, codespanCloseTag)
case blackfriday.Document:
break
case blackfriday.Paragraph:
// roff .PP markers break lists
if r.listDepth > 0 {
return blackfriday.GoToNext
}
if entering {
out(w, paraTag)
} else {
out(w, crTag)
}
case blackfriday.BlockQuote:
if entering {
out(w, quoteTag)
} else {
out(w, quoteCloseTag)
}
case blackfriday.Heading:
r.handleHeading(w, node, entering)
case blackfriday.HorizontalRule:
out(w, hruleTag)
case blackfriday.List:
r.handleList(w, node, entering)
case blackfriday.Item:
r.handleItem(w, node, entering)
case blackfriday.CodeBlock:
out(w, codeTag)
escapeSpecialChars(w, node.Literal)
out(w, codeCloseTag)
case blackfriday.Table:
r.handleTable(w, node, entering)
case blackfriday.TableHead:
case blackfriday.TableBody:
case blackfriday.TableRow:
// no action as cell entries do all the nroff formatting
return blackfriday.GoToNext
case blackfriday.TableCell:
r.handleTableCell(w, node, entering)
case blackfriday.HTMLSpan:
// ignore other HTML tags
case blackfriday.HTMLBlock:
if bytes.HasPrefix(node.Literal, []byte("<!--")) {
break // ignore comments, no warning
}
fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
default:
fmt.Fprintln(os.Stderr, "WARNING: go-md2man does not handle node type "+node.Type.String())
}
return walkAction
}
func (r *roffRenderer) handleHeading(w io.Writer, node *blackfriday.Node, entering bool) {
if entering {
switch node.Level {
case 1:
if !r.firstHeader {
out(w, titleHeader)
r.firstHeader = true
break
}
out(w, topLevelHeader)
case 2:
out(w, secondLevelHdr)
default:
out(w, otherHeader)
}
}
}
func (r *roffRenderer) handleList(w io.Writer, node *blackfriday.Node, entering bool) {
openTag := listTag
closeTag := listCloseTag
if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
// tags for definition lists handled within Item node
openTag = ""
closeTag = ""
}
if entering {
r.listDepth++
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
r.listCounters = append(r.listCounters, 1)
}
out(w, openTag)
} else {
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
r.listCounters = r.listCounters[:len(r.listCounters)-1]
}
out(w, closeTag)
r.listDepth--
}
}
func (r *roffRenderer) handleItem(w io.Writer, node *blackfriday.Node, entering bool) {
if entering {
if node.ListFlags&blackfriday.ListTypeOrdered != 0 {
out(w, fmt.Sprintf(".IP \"%3d.\" 5\n", r.listCounters[len(r.listCounters)-1]))
r.listCounters[len(r.listCounters)-1]++
} else if node.ListFlags&blackfriday.ListTypeTerm != 0 {
// DT (definition term): line just before DD (see below).
out(w, dtTag)
r.firstDD = true
} else if node.ListFlags&blackfriday.ListTypeDefinition != 0 {
// DD (definition description): line that starts with ": ".
//
// We have to distinguish between the first DD and the
// subsequent ones, as there should be no vertical
// whitespace between the DT and the first DD.
if r.firstDD {
r.firstDD = false
} else {
out(w, dd2Tag)
}
} else {
out(w, ".IP \\(bu 2\n")
}
} else {
out(w, "\n")
}
}
func (r *roffRenderer) handleTable(w io.Writer, node *blackfriday.Node, entering bool) {
if entering {
out(w, tableStart)
// call walker to count cells (and rows?) so format section can be produced
columns := countColumns(node)
out(w, strings.Repeat("l ", columns)+"\n")
out(w, strings.Repeat("l ", columns)+".\n")
} else {
out(w, tableEnd)
}
}
func (r *roffRenderer) handleTableCell(w io.Writer, node *blackfriday.Node, entering bool) {
if entering {
var start string
if node.Prev != nil && node.Prev.Type == blackfriday.TableCell {
start = "\t"
}
if node.IsHeader {
start += strongTag
} else if nodeLiteralSize(node) > 30 {
start += tableCellStart
}
out(w, start)
} else {
var end string
if node.IsHeader {
end = strongCloseTag
} else if nodeLiteralSize(node) > 30 {
end = tableCellEnd
}
if node.Next == nil && end != tableCellEnd {
// Last cell: need to carriage return if we are at the end of the
// header row and content isn't wrapped in a "tablecell"
end += crTag
}
out(w, end)
}
}
func nodeLiteralSize(node *blackfriday.Node) int {
total := 0
for n := node.FirstChild; n != nil; n = n.FirstChild {
total += len(n.Literal)
}
return total
}
// because roff format requires knowing the column count before outputting any table
// data we need to walk a table tree and count the columns
func countColumns(node *blackfriday.Node) int {
var columns int
node.Walk(func(node *blackfriday.Node, entering bool) blackfriday.WalkStatus {
switch node.Type {
case blackfriday.TableRow:
if !entering {
return blackfriday.Terminate
}
case blackfriday.TableCell:
if entering {
columns++
}
default:
}
return blackfriday.GoToNext
})
return columns
}
func out(w io.Writer, output string) {
io.WriteString(w, output) // nolint: errcheck
}
func escapeSpecialChars(w io.Writer, text []byte) {
scanner := bufio.NewScanner(bytes.NewReader(text))
// count the number of lines in the text
// we need to know this to avoid adding a newline after the last line
n := bytes.Count(text, []byte{'\n'})
idx := 0
for scanner.Scan() {
dt := scanner.Bytes()
if idx < n {
idx++
dt = append(dt, '\n')
}
escapeSpecialCharsLine(w, dt)
}
if err := scanner.Err(); err != nil {
panic(err)
}
}
func escapeSpecialCharsLine(w io.Writer, text []byte) {
for i := 0; i < len(text); i++ {
// escape initial apostrophe or period
if len(text) >= 1 && (text[0] == '\'' || text[0] == '.') {
out(w, "\\&")
}
// directly copy normal characters
org := i
for i < len(text) && text[i] != '\\' {
i++
}
if i > org {
w.Write(text[org:i]) // nolint: errcheck
}
// escape a character
if i >= len(text) {
break
}
w.Write([]byte{'\\', text[i]}) // nolint: errcheck
}
}

View File

@ -18,7 +18,6 @@
// tag is deprecated and thus should not be used. // tag is deprecated and thus should not be used.
// Go versions prior to 1.4 are disabled because they use a different layout // Go versions prior to 1.4 are disabled because they use a different layout
// for interfaces which make the implementation of unsafeReflectValue more complex. // for interfaces which make the implementation of unsafeReflectValue more complex.
//go:build !js && !appengine && !safe && !disableunsafe && go1.4
// +build !js,!appengine,!safe,!disableunsafe,go1.4 // +build !js,!appengine,!safe,!disableunsafe,go1.4
package spew package spew

View File

@ -16,7 +16,6 @@
// when the code is running on Google App Engine, compiled by GopherJS, or // when the code is running on Google App Engine, compiled by GopherJS, or
// "-tags safe" is added to the go build command line. The "disableunsafe" // "-tags safe" is added to the go build command line. The "disableunsafe"
// tag is deprecated and thus should not be used. // tag is deprecated and thus should not be used.
//go:build js || appengine || safe || disableunsafe || !go1.4
// +build js appengine safe disableunsafe !go1.4 // +build js appengine safe disableunsafe !go1.4
package spew package spew

View File

@ -254,15 +254,15 @@ pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt following features over the built-in printing facilities provided by the fmt
package: package:
- Pointers are dereferenced and followed * Pointers are dereferenced and followed
- Circular data structures are detected and handled properly * Circular data structures are detected and handled properly
- Custom Stringer/error interfaces are optionally invoked, including * Custom Stringer/error interfaces are optionally invoked, including
on unexported types on unexported types
- Custom types which only implement the Stringer/error interfaces via * Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer a pointer receiver are optionally invoked when passing non-pointer
variables variables
- Byte arrays and slices are dumped like the hexdump -C command which * Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by modifying the public members The configuration options are controlled by modifying the public members
of c. See ConfigState for options documentation. of c. See ConfigState for options documentation.
@ -295,12 +295,12 @@ func (c *ConfigState) convertArgs(args []interface{}) (formatters []interface{})
// NewDefaultConfig returns a ConfigState with the following default settings. // NewDefaultConfig returns a ConfigState with the following default settings.
// //
// Indent: " " // Indent: " "
// MaxDepth: 0 // MaxDepth: 0
// DisableMethods: false // DisableMethods: false
// DisablePointerMethods: false // DisablePointerMethods: false
// ContinueOnMethod: false // ContinueOnMethod: false
// SortKeys: false // SortKeys: false
func NewDefaultConfig() *ConfigState { func NewDefaultConfig() *ConfigState {
return &ConfigState{Indent: " "} return &ConfigState{Indent: " "}
} }

View File

@ -21,36 +21,35 @@ debugging.
A quick overview of the additional features spew provides over the built-in A quick overview of the additional features spew provides over the built-in
printing facilities for Go data types are as follows: printing facilities for Go data types are as follows:
- Pointers are dereferenced and followed * Pointers are dereferenced and followed
- Circular data structures are detected and handled properly * Circular data structures are detected and handled properly
- Custom Stringer/error interfaces are optionally invoked, including * Custom Stringer/error interfaces are optionally invoked, including
on unexported types on unexported types
- Custom types which only implement the Stringer/error interfaces via * Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer a pointer receiver are optionally invoked when passing non-pointer
variables variables
- Byte arrays and slices are dumped like the hexdump -C command which * Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output (only when using includes offsets, byte values in hex, and ASCII output (only when using
Dump style) Dump style)
There are two different approaches spew allows for dumping Go data structures: There are two different approaches spew allows for dumping Go data structures:
- Dump style which prints with newlines, customizable indentation, * Dump style which prints with newlines, customizable indentation,
and additional debug information such as types and all pointer addresses and additional debug information such as types and all pointer addresses
used to indirect to the final value used to indirect to the final value
- A custom Formatter interface that integrates cleanly with the standard fmt * A custom Formatter interface that integrates cleanly with the standard fmt
package and replaces %v, %+v, %#v, and %#+v to provide inline printing package and replaces %v, %+v, %#v, and %#+v to provide inline printing
similar to the default %v while providing the additional functionality similar to the default %v while providing the additional functionality
outlined above and passing unsupported format verbs such as %x and %q outlined above and passing unsupported format verbs such as %x and %q
along to fmt along to fmt
# Quick Start Quick Start
This section demonstrates how to quickly get started with spew. See the This section demonstrates how to quickly get started with spew. See the
sections below for further details on formatting and configuration options. sections below for further details on formatting and configuration options.
To dump a variable with full newlines, indentation, type, and pointer To dump a variable with full newlines, indentation, type, and pointer
information use Dump, Fdump, or Sdump: information use Dump, Fdump, or Sdump:
spew.Dump(myVar1, myVar2, ...) spew.Dump(myVar1, myVar2, ...)
spew.Fdump(someWriter, myVar1, myVar2, ...) spew.Fdump(someWriter, myVar1, myVar2, ...)
str := spew.Sdump(myVar1, myVar2, ...) str := spew.Sdump(myVar1, myVar2, ...)
@ -59,13 +58,12 @@ Alternatively, if you would prefer to use format strings with a compacted inline
printing style, use the convenience wrappers Printf, Fprintf, etc with printing style, use the convenience wrappers Printf, Fprintf, etc with
%v (most compact), %+v (adds pointer addresses), %#v (adds types), or %v (most compact), %+v (adds pointer addresses), %#v (adds types), or
%#+v (adds types and pointer addresses): %#+v (adds types and pointer addresses):
spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2) spew.Printf("myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) spew.Printf("myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2) spew.Fprintf(someWriter, "myVar1: %v -- myVar2: %+v", myVar1, myVar2)
spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4) spew.Fprintf(someWriter, "myVar3: %#v -- myVar4: %#+v", myVar3, myVar4)
# Configuration Options Configuration Options
Configuration of spew is handled by fields in the ConfigState type. For Configuration of spew is handled by fields in the ConfigState type. For
convenience, all of the top-level functions use a global state available convenience, all of the top-level functions use a global state available
@ -76,52 +74,51 @@ equivalent to the top-level functions. This allows concurrent configuration
options. See the ConfigState documentation for more details. options. See the ConfigState documentation for more details.
The following configuration options are available: The following configuration options are available:
* Indent
String to use for each indentation level for Dump functions.
It is a single space by default. A popular alternative is "\t".
- Indent * MaxDepth
String to use for each indentation level for Dump functions. Maximum number of levels to descend into nested data structures.
It is a single space by default. A popular alternative is "\t". There is no limit by default.
- MaxDepth * DisableMethods
Maximum number of levels to descend into nested data structures. Disables invocation of error and Stringer interface methods.
There is no limit by default. Method invocation is enabled by default.
- DisableMethods * DisablePointerMethods
Disables invocation of error and Stringer interface methods. Disables invocation of error and Stringer interface methods on types
Method invocation is enabled by default. which only accept pointer receivers from non-pointer variables.
Pointer method invocation is enabled by default.
- DisablePointerMethods * DisablePointerAddresses
Disables invocation of error and Stringer interface methods on types DisablePointerAddresses specifies whether to disable the printing of
which only accept pointer receivers from non-pointer variables. pointer addresses. This is useful when diffing data structures in tests.
Pointer method invocation is enabled by default.
- DisablePointerAddresses * DisableCapacities
DisablePointerAddresses specifies whether to disable the printing of DisableCapacities specifies whether to disable the printing of
pointer addresses. This is useful when diffing data structures in tests. capacities for arrays, slices, maps and channels. This is useful when
diffing data structures in tests.
- DisableCapacities * ContinueOnMethod
DisableCapacities specifies whether to disable the printing of Enables recursion into types after invoking error and Stringer interface
capacities for arrays, slices, maps and channels. This is useful when methods. Recursion after method invocation is disabled by default.
diffing data structures in tests.
- ContinueOnMethod * SortKeys
Enables recursion into types after invoking error and Stringer interface Specifies map keys should be sorted before being printed. Use
methods. Recursion after method invocation is disabled by default. this to have a more deterministic, diffable output. Note that
only native types (bool, int, uint, floats, uintptr and string)
and types which implement error or Stringer interfaces are
supported with other types sorted according to the
reflect.Value.String() output which guarantees display
stability. Natural map order is used by default.
- SortKeys * SpewKeys
Specifies map keys should be sorted before being printed. Use Specifies that, as a last resort attempt, map keys should be
this to have a more deterministic, diffable output. Note that spewed to strings and sorted by those strings. This is only
only native types (bool, int, uint, floats, uintptr and string) considered if SortKeys is true.
and types which implement error or Stringer interfaces are
supported with other types sorted according to the
reflect.Value.String() output which guarantees display
stability. Natural map order is used by default.
- SpewKeys Dump Usage
Specifies that, as a last resort attempt, map keys should be
spewed to strings and sorted by those strings. This is only
considered if SortKeys is true.
# Dump Usage
Simply call spew.Dump with a list of variables you want to dump: Simply call spew.Dump with a list of variables you want to dump:
@ -136,7 +133,7 @@ A third option is to call spew.Sdump to get the formatted output as a string:
str := spew.Sdump(myVar1, myVar2, ...) str := spew.Sdump(myVar1, myVar2, ...)
# Sample Dump Output Sample Dump Output
See the Dump example for details on the setup of the types and variables being See the Dump example for details on the setup of the types and variables being
shown here. shown here.
@ -153,14 +150,13 @@ shown here.
Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C Byte (and uint8) arrays and slices are displayed uniquely like the hexdump -C
command as shown. command as shown.
([]uint8) (len=32 cap=32) { ([]uint8) (len=32 cap=32) {
00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... | 00000000 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f 20 |............... |
00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0| 00000010 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f 30 |!"#$%&'()*+,-./0|
00000020 31 32 |12| 00000020 31 32 |12|
} }
# Custom Formatter Custom Formatter
Spew provides a custom formatter that implements the fmt.Formatter interface Spew provides a custom formatter that implements the fmt.Formatter interface
so that it integrates cleanly with standard fmt package printing functions. The so that it integrates cleanly with standard fmt package printing functions. The
@ -174,7 +170,7 @@ standard fmt package for formatting. In addition, the custom formatter ignores
the width and precision arguments (however they will still work on the format the width and precision arguments (however they will still work on the format
specifiers not handled by the custom formatter). specifiers not handled by the custom formatter).
# Custom Formatter Usage Custom Formatter Usage
The simplest way to make use of the spew custom formatter is to call one of the The simplest way to make use of the spew custom formatter is to call one of the
convenience functions such as spew.Printf, spew.Println, or spew.Printf. The convenience functions such as spew.Printf, spew.Println, or spew.Printf. The
@ -188,17 +184,15 @@ functions have syntax you are most likely already familiar with:
See the Index for the full list convenience functions. See the Index for the full list convenience functions.
# Sample Formatter Output Sample Formatter Output
Double pointer to a uint8: Double pointer to a uint8:
%v: <**>5 %v: <**>5
%+v: <**>(0xf8400420d0->0xf8400420c8)5 %+v: <**>(0xf8400420d0->0xf8400420c8)5
%#v: (**uint8)5 %#v: (**uint8)5
%#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5 %#+v: (**uint8)(0xf8400420d0->0xf8400420c8)5
Pointer to circular struct with a uint8 field and a pointer to itself: Pointer to circular struct with a uint8 field and a pointer to itself:
%v: <*>{1 <*><shown>} %v: <*>{1 <*><shown>}
%+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>} %+v: <*>(0xf84003e260){ui8:1 c:<*>(0xf84003e260)<shown>}
%#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>} %#v: (*main.circular){ui8:(uint8)1 c:(*main.circular)<shown>}
@ -207,7 +201,7 @@ Pointer to circular struct with a uint8 field and a pointer to itself:
See the Printf example for details on the setup of variables being shown See the Printf example for details on the setup of variables being shown
here. here.
# Errors Errors
Since it is possible for custom Stringer/error interfaces to panic, spew Since it is possible for custom Stringer/error interfaces to panic, spew
detects them and handles them internally by printing the panic information detects them and handles them internally by printing the panic information

View File

@ -488,15 +488,15 @@ pointer addresses used to indirect to the final value. It provides the
following features over the built-in printing facilities provided by the fmt following features over the built-in printing facilities provided by the fmt
package: package:
- Pointers are dereferenced and followed * Pointers are dereferenced and followed
- Circular data structures are detected and handled properly * Circular data structures are detected and handled properly
- Custom Stringer/error interfaces are optionally invoked, including * Custom Stringer/error interfaces are optionally invoked, including
on unexported types on unexported types
- Custom types which only implement the Stringer/error interfaces via * Custom types which only implement the Stringer/error interfaces via
a pointer receiver are optionally invoked when passing non-pointer a pointer receiver are optionally invoked when passing non-pointer
variables variables
- Byte arrays and slices are dumped like the hexdump -C command which * Byte arrays and slices are dumped like the hexdump -C command which
includes offsets, byte values in hex, and ASCII output includes offsets, byte values in hex, and ASCII output
The configuration options are controlled by an exported package global, The configuration options are controlled by an exported package global,
spew.Config. See ConfigState for options documentation. spew.Config. See ConfigState for options documentation.

View File

@ -37,8 +37,8 @@ type Namespace struct {
// WithConstLabels returns a namespace with the provided set of labels merged // WithConstLabels returns a namespace with the provided set of labels merged
// with the existing constant labels on the namespace. // with the existing constant labels on the namespace.
// //
// Only metrics created with the returned namespace will get the new constant // Only metrics created with the returned namespace will get the new constant
// labels. The returned namespace must be registered separately. // labels. The returned namespace must be registered separately.
func (n *Namespace) WithConstLabels(labels Labels) *Namespace { func (n *Namespace) WithConstLabels(labels Labels) *Namespace {
n.mu.Lock() n.mu.Lock()
ns := &Namespace{ ns := &Namespace{

View File

@ -74,13 +74,14 @@ import (
// //
// The JSON null value unmarshals into an interface, map, pointer, or slice // The JSON null value unmarshals into an interface, map, pointer, or slice
// by setting that Go value to nil. Because null is often used in JSON to mean // by setting that Go value to nil. Because null is often used in JSON to mean
// “not present,” unmarshaling a JSON null into any other Go type has no effect // ``not present,'' unmarshaling a JSON null into any other Go type has no effect
// on the value and produces no error. // on the value and produces no error.
// //
// When unmarshaling quoted strings, invalid UTF-8 or // When unmarshaling quoted strings, invalid UTF-8 or
// invalid UTF-16 surrogate pairs are not treated as an error. // invalid UTF-16 surrogate pairs are not treated as an error.
// Instead, they are replaced by the Unicode replacement // Instead, they are replaced by the Unicode replacement
// character U+FFFD. // character U+FFFD.
//
func Unmarshal(data []byte, v interface{}) error { func Unmarshal(data []byte, v interface{}) error {
// Check for well-formedness. // Check for well-formedness.
// Avoids filling out half a data structure // Avoids filling out half a data structure

View File

@ -58,7 +58,6 @@ import (
// becomes a member of the object unless // becomes a member of the object unless
// - the field's tag is "-", or // - the field's tag is "-", or
// - the field is empty and its tag specifies the "omitempty" option. // - the field is empty and its tag specifies the "omitempty" option.
//
// The empty values are false, 0, any // The empty values are false, 0, any
// nil pointer or interface value, and any array, slice, map, or string of // nil pointer or interface value, and any array, slice, map, or string of
// length zero. The object's default key string is the struct field name // length zero. The object's default key string is the struct field name
@ -66,28 +65,28 @@ import (
// the struct field's tag value is the key name, followed by an optional comma // the struct field's tag value is the key name, followed by an optional comma
// and options. Examples: // and options. Examples:
// //
// // Field is ignored by this package. // // Field is ignored by this package.
// Field int `json:"-"` // Field int `json:"-"`
// //
// // Field appears in JSON as key "myName". // // Field appears in JSON as key "myName".
// Field int `json:"myName"` // Field int `json:"myName"`
// //
// // Field appears in JSON as key "myName" and // // Field appears in JSON as key "myName" and
// // the field is omitted from the object if its value is empty, // // the field is omitted from the object if its value is empty,
// // as defined above. // // as defined above.
// Field int `json:"myName,omitempty"` // Field int `json:"myName,omitempty"`
// //
// // Field appears in JSON as key "Field" (the default), but // // Field appears in JSON as key "Field" (the default), but
// // the field is skipped if empty. // // the field is skipped if empty.
// // Note the leading comma. // // Note the leading comma.
// Field int `json:",omitempty"` // Field int `json:",omitempty"`
// //
// The "string" option signals that a field is stored as JSON inside a // The "string" option signals that a field is stored as JSON inside a
// JSON-encoded string. It applies only to fields of string, floating point, // JSON-encoded string. It applies only to fields of string, floating point,
// integer, or boolean types. This extra level of encoding is sometimes used // integer, or boolean types. This extra level of encoding is sometimes used
// when communicating with JavaScript programs: // when communicating with JavaScript programs:
// //
// Int64String int64 `json:",string"` // Int64String int64 `json:",string"`
// //
// The key name will be used if it's a non-empty string consisting of // The key name will be used if it's a non-empty string consisting of
// only Unicode letters, digits, dollar signs, percent signs, hyphens, // only Unicode letters, digits, dollar signs, percent signs, hyphens,
@ -134,6 +133,7 @@ import (
// JSON cannot represent cyclic data structures and Marshal does not // JSON cannot represent cyclic data structures and Marshal does not
// handle them. Passing cyclic structures to Marshal will result in // handle them. Passing cyclic structures to Marshal will result in
// an infinite recursion. // an infinite recursion.
//
func Marshal(v interface{}) ([]byte, error) { func Marshal(v interface{}) ([]byte, error) {
return marshal(v, false) return marshal(v, false)
} }

View File

@ -24,9 +24,8 @@ const (
// 4) simpleLetterEqualFold, no specials, no non-letters. // 4) simpleLetterEqualFold, no specials, no non-letters.
// //
// The letters S and K are special because they map to 3 runes, not just 2: // The letters S and K are special because they map to 3 runes, not just 2:
// - S maps to s and to U+017F 'ſ' Latin small letter long s // * S maps to s and to U+017F 'ſ' Latin small letter long s
// - k maps to K and to U+212A '' Kelvin sign // * k maps to K and to U+212A '' Kelvin sign
//
// See https://play.golang.org/p/tTxjOc0OGo // See https://play.golang.org/p/tTxjOc0OGo
// //
// The returned function is specialized for matching against s and // The returned function is specialized for matching against s and

View File

@ -242,6 +242,7 @@ var _ Unmarshaler = (*RawMessage)(nil)
// Number, for JSON numbers // Number, for JSON numbers
// string, for JSON string literals // string, for JSON string literals
// nil, for JSON null // nil, for JSON null
//
type Token interface{} type Token interface{}
const ( const (

View File

@ -72,7 +72,7 @@ type IteratorWithKey interface {
// //
// Essentially it is the same as IteratorWithIndex, but provides additional: // Essentially it is the same as IteratorWithIndex, but provides additional:
// //
// # Prev() function to enable traversal in reverse // Prev() function to enable traversal in reverse
// //
// Last() function to move the iterator to the last element. // Last() function to move the iterator to the last element.
// //
@ -105,7 +105,7 @@ type ReverseIteratorWithIndex interface {
// //
// Essentially it is the same as IteratorWithKey, but provides additional: // Essentially it is the same as IteratorWithKey, but provides additional:
// //
// # Prev() function to enable traversal in reverse // Prev() function to enable traversal in reverse
// //
// Last() function to move the iterator to the last element. // Last() function to move the iterator to the last element.
type ReverseIteratorWithKey interface { type ReverseIteratorWithKey interface {

View File

@ -102,7 +102,7 @@ func (list *List) Values() []interface{} {
return newElements return newElements
} }
// IndexOf returns index of provided element //IndexOf returns index of provided element
func (list *List) IndexOf(value interface{}) int { func (list *List) IndexOf(value interface{}) int {
if list.size == 0 { if list.size == 0 {
return -1 return -1

View File

@ -10,10 +10,9 @@ import "time"
// which will panic if a or b are not of the asserted type. // which will panic if a or b are not of the asserted type.
// //
// Should return a number: // Should return a number:
// // negative , if a < b
// negative , if a < b // zero , if a == b
// zero , if a == b // positive , if a > b
// positive , if a > b
type Comparator func(a, b interface{}) int type Comparator func(a, b interface{}) int
// StringComparator provides a fast comparison on strings // StringComparator provides a fast comparison on strings

View File

@ -1,6 +1,4 @@
//go:build go1.8
// +build go1.8 // +build go1.8
// Code generated by "httpsnoop/codegen"; DO NOT EDIT. // Code generated by "httpsnoop/codegen"; DO NOT EDIT.
package httpsnoop package httpsnoop

View File

@ -1,6 +1,4 @@
//go:build !go1.8
// +build !go1.8 // +build !go1.8
// Code generated by "httpsnoop/codegen"; DO NOT EDIT. // Code generated by "httpsnoop/codegen"; DO NOT EDIT.
package httpsnoop package httpsnoop

View File

@ -347,9 +347,8 @@ const (
// 4) simpleLetterEqualFold, no specials, no non-letters. // 4) simpleLetterEqualFold, no specials, no non-letters.
// //
// The letters S and K are special because they map to 3 runes, not just 2: // The letters S and K are special because they map to 3 runes, not just 2:
// - S maps to s and to U+017F 'ſ' Latin small letter long s // * S maps to s and to U+017F 'ſ' Latin small letter long s
// - k maps to K and to U+212A '' Kelvin sign // * k maps to K and to U+212A '' Kelvin sign
//
// See http://play.golang.org/p/tTxjOc0OGo // See http://play.golang.org/p/tTxjOc0OGo
// //
// The returned function is specialized for matching against s and // The returned function is specialized for matching against s and

View File

@ -64,12 +64,12 @@ func JSONToYAML(j []byte) ([]byte, error) {
// this method should be a no-op. // this method should be a no-op.
// //
// Things YAML can do that are not supported by JSON: // Things YAML can do that are not supported by JSON:
// - In YAML you can have binary and null keys in your maps. These are invalid // * In YAML you can have binary and null keys in your maps. These are invalid
// in JSON. (int and float keys are converted to strings.) // in JSON. (int and float keys are converted to strings.)
// - Binary data in YAML with the !!binary tag is not supported. If you want to // * Binary data in YAML with the !!binary tag is not supported. If you want to
// use binary data with this library, encode the data as base64 as usual but do // use binary data with this library, encode the data as base64 as usual but do
// not use the !!binary tag in your YAML. This will ensure the original base64 // not use the !!binary tag in your YAML. This will ensure the original base64
// encoded data makes it all the way through to the JSON. // encoded data makes it all the way through to the JSON.
func YAMLToJSON(y []byte) ([]byte, error) { func YAMLToJSON(y []byte) ([]byte, error) {
return yamlToJSON(y, nil) return yamlToJSON(y, nil)
} }

89
vendor/github.com/go-git/gcfg/doc.go generated vendored
View File

@ -4,29 +4,29 @@
// This package is still a work in progress; see the sections below for planned // This package is still a work in progress; see the sections below for planned
// changes. // changes.
// //
// # Syntax // Syntax
// //
// The syntax is based on that used by git config: // The syntax is based on that used by git config:
// http://git-scm.com/docs/git-config#_syntax . // http://git-scm.com/docs/git-config#_syntax .
// There are some (planned) differences compared to the git config format: // There are some (planned) differences compared to the git config format:
// - improve data portability: // - improve data portability:
// - must be encoded in UTF-8 (for now) and must not contain the 0 byte // - must be encoded in UTF-8 (for now) and must not contain the 0 byte
// - include and "path" type is not supported // - include and "path" type is not supported
// (path type may be implementable as a user-defined type) // (path type may be implementable as a user-defined type)
// - internationalization // - internationalization
// - section and variable names can contain unicode letters, unicode digits // - section and variable names can contain unicode letters, unicode digits
// (as defined in http://golang.org/ref/spec#Characters ) and hyphens // (as defined in http://golang.org/ref/spec#Characters ) and hyphens
// (U+002D), starting with a unicode letter // (U+002D), starting with a unicode letter
// - disallow potentially ambiguous or misleading definitions: // - disallow potentially ambiguous or misleading definitions:
// - `[sec.sub]` format is not allowed (deprecated in gitconfig) // - `[sec.sub]` format is not allowed (deprecated in gitconfig)
// - `[sec ""]` is not allowed // - `[sec ""]` is not allowed
// - use `[sec]` for section name "sec" and empty subsection name // - use `[sec]` for section name "sec" and empty subsection name
// - (planned) within a single file, definitions must be contiguous for each: // - (planned) within a single file, definitions must be contiguous for each:
// - section: '[secA]' -> '[secB]' -> '[secA]' is an error // - section: '[secA]' -> '[secB]' -> '[secA]' is an error
// - subsection: '[sec "A"]' -> '[sec "B"]' -> '[sec "A"]' is an error // - subsection: '[sec "A"]' -> '[sec "B"]' -> '[sec "A"]' is an error
// - multivalued variable: 'multi=a' -> 'other=x' -> 'multi=b' is an error // - multivalued variable: 'multi=a' -> 'other=x' -> 'multi=b' is an error
// //
// # Data structure // Data structure
// //
// The functions in this package read values into a user-defined struct. // The functions in this package read values into a user-defined struct.
// Each section corresponds to a struct field in the config struct, and each // Each section corresponds to a struct field in the config struct, and each
@ -56,7 +56,7 @@
// or when a field is not of a suitable type (either a struct or a map with // or when a field is not of a suitable type (either a struct or a map with
// string keys and pointer-to-struct values). // string keys and pointer-to-struct values).
// //
// # Parsing of values // Parsing of values
// //
// The section structs in the config struct may contain single-valued or // The section structs in the config struct may contain single-valued or
// multi-valued variables. Variables of unnamed slice type (that is, a type // multi-valued variables. Variables of unnamed slice type (that is, a type
@ -98,17 +98,17 @@
// The types subpackage for provides helpers for parsing "enum-like" and integer // The types subpackage for provides helpers for parsing "enum-like" and integer
// types. // types.
// //
// # Error handling // Error handling
// //
// There are 3 types of errors: // There are 3 types of errors:
// //
// - programmer errors / panics: // - programmer errors / panics:
// - invalid configuration structure // - invalid configuration structure
// - data errors: // - data errors:
// - fatal errors: // - fatal errors:
// - invalid configuration syntax // - invalid configuration syntax
// - warnings: // - warnings:
// - data that doesn't belong to any part of the config structure // - data that doesn't belong to any part of the config structure
// //
// Programmer errors trigger panics. These are should be fixed by the programmer // Programmer errors trigger panics. These are should be fixed by the programmer
// before releasing code that uses gcfg. // before releasing code that uses gcfg.
@ -122,23 +122,24 @@
// filtered out programmatically. To ignore extra data warnings, wrap the // filtered out programmatically. To ignore extra data warnings, wrap the
// gcfg.Read*Into invocation into a call to gcfg.FatalOnly. // gcfg.Read*Into invocation into a call to gcfg.FatalOnly.
// //
// # TODO // TODO
// //
// The following is a list of changes under consideration: // The following is a list of changes under consideration:
// - documentation // - documentation
// - self-contained syntax documentation // - self-contained syntax documentation
// - more practical examples // - more practical examples
// - move TODOs to issue tracker (eventually) // - move TODOs to issue tracker (eventually)
// - syntax // - syntax
// - reconsider valid escape sequences // - reconsider valid escape sequences
// (gitconfig doesn't support \r in value, \t in subsection name, etc.) // (gitconfig doesn't support \r in value, \t in subsection name, etc.)
// - reading / parsing gcfg files // - reading / parsing gcfg files
// - define internal representation structure // - define internal representation structure
// - support multiple inputs (readers, strings, files) // - support multiple inputs (readers, strings, files)
// - support declaring encoding (?) // - support declaring encoding (?)
// - support varying fields sets for subsections (?) // - support varying fields sets for subsections (?)
// - writing gcfg files // - writing gcfg files
// - error handling // - error handling
// - make error context accessible programmatically? // - make error context accessible programmatically?
// - limit input size? // - limit input size?
//
package gcfg // import "github.com/go-git/gcfg" package gcfg // import "github.com/go-git/gcfg"

View File

@ -8,9 +8,10 @@ import (
// fatal errors. That is, errors (warnings) indicating data for unknown // fatal errors. That is, errors (warnings) indicating data for unknown
// sections / variables is ignored. Example invocation: // sections / variables is ignored. Example invocation:
// //
// err := gcfg.FatalOnly(gcfg.ReadFileInto(&cfg, configFile)) // err := gcfg.FatalOnly(gcfg.ReadFileInto(&cfg, configFile))
// if err != nil { // if err != nil {
// ... // ...
//
func FatalOnly(err error) error { func FatalOnly(err error) error {
return warnings.FatalOnly(err) return warnings.FatalOnly(err)
} }

View File

@ -18,6 +18,7 @@ import (
// The position Pos, if valid, points to the beginning of // The position Pos, if valid, points to the beginning of
// the offending token, and the error condition is described // the offending token, and the error condition is described
// by Msg. // by Msg.
//
type Error struct { type Error struct {
Pos token.Position Pos token.Position
Msg string Msg string
@ -35,6 +36,7 @@ func (e Error) Error() string {
// ErrorList is a list of *Errors. // ErrorList is a list of *Errors.
// The zero value for an ErrorList is an empty ErrorList ready to use. // The zero value for an ErrorList is an empty ErrorList ready to use.
//
type ErrorList []*Error type ErrorList []*Error
// Add adds an Error with given position and error message to an ErrorList. // Add adds an Error with given position and error message to an ErrorList.
@ -64,6 +66,7 @@ func (p ErrorList) Less(i, j int) bool {
// Sort sorts an ErrorList. *Error entries are sorted by position, // Sort sorts an ErrorList. *Error entries are sorted by position,
// other errors are sorted by error message, and before any *Error // other errors are sorted by error message, and before any *Error
// entry. // entry.
//
func (p ErrorList) Sort() { func (p ErrorList) Sort() {
sort.Sort(p) sort.Sort(p)
} }
@ -106,6 +109,7 @@ func (p ErrorList) Err() error {
// PrintError is a utility function that prints a list of errors to w, // PrintError is a utility function that prints a list of errors to w,
// one error per line, if the err parameter is an ErrorList. Otherwise // one error per line, if the err parameter is an ErrorList. Otherwise
// it prints the err string. // it prints the err string.
//
func PrintError(w io.Writer, err error) { func PrintError(w io.Writer, err error) {
if list, ok := err.(ErrorList); ok { if list, ok := err.(ErrorList); ok {
for _, e := range list { for _, e := range list {

View File

@ -216,7 +216,7 @@ func newValue(c *warnings.Collector, sect string, vCfg reflect.Value,
} }
func set(c *warnings.Collector, cfg interface{}, sect, sub, name string, func set(c *warnings.Collector, cfg interface{}, sect, sub, name string,
value string, blankValue bool, subsectPass bool) error { value string, blankValue bool, subsectPass bool) error {
// //
vPCfg := reflect.ValueOf(cfg) vPCfg := reflect.ValueOf(cfg)
if vPCfg.Kind() != reflect.Ptr || vPCfg.Elem().Kind() != reflect.Struct { if vPCfg.Kind() != reflect.Ptr || vPCfg.Elem().Kind() != reflect.Struct {

View File

@ -18,6 +18,7 @@ import (
// Position describes an arbitrary source position // Position describes an arbitrary source position
// including the file, line, and column location. // including the file, line, and column location.
// A Position is valid if the line number is > 0. // A Position is valid if the line number is > 0.
//
type Position struct { type Position struct {
Filename string // filename, if any Filename string // filename, if any
Offset int // offset, starting at 0 Offset int // offset, starting at 0
@ -34,6 +35,7 @@ func (pos *Position) IsValid() bool { return pos.Line > 0 }
// line:column valid position without file name // line:column valid position without file name
// file invalid position with file name // file invalid position with file name
// - invalid position without file name // - invalid position without file name
//
func (pos Position) String() string { func (pos Position) String() string {
s := pos.Filename s := pos.Filename
if pos.IsValid() { if pos.IsValid() {
@ -67,12 +69,14 @@ func (pos Position) String() string {
// equivalent to comparing the respective source file offsets. If p and q // equivalent to comparing the respective source file offsets. If p and q
// are in different files, p < q is true if the file implied by p was added // are in different files, p < q is true if the file implied by p was added
// to the respective file set before the file implied by q. // to the respective file set before the file implied by q.
//
type Pos int type Pos int
// The zero value for Pos is NoPos; there is no file and line information // The zero value for Pos is NoPos; there is no file and line information
// associated with it, and NoPos().IsValid() is false. NoPos is always // associated with it, and NoPos().IsValid() is false. NoPos is always
// smaller than any other Pos value. The corresponding Position value // smaller than any other Pos value. The corresponding Position value
// for NoPos is the zero value for Position. // for NoPos is the zero value for Position.
//
const NoPos Pos = 0 const NoPos Pos = 0
// IsValid returns true if the position is valid. // IsValid returns true if the position is valid.
@ -85,6 +89,7 @@ func (p Pos) IsValid() bool {
// A File is a handle for a file belonging to a FileSet. // A File is a handle for a file belonging to a FileSet.
// A File has a name, size, and line offset table. // A File has a name, size, and line offset table.
//
type File struct { type File struct {
set *FileSet set *FileSet
name string // file name as provided to AddFile name string // file name as provided to AddFile
@ -122,6 +127,7 @@ func (f *File) LineCount() int {
// AddLine adds the line offset for a new line. // AddLine adds the line offset for a new line.
// The line offset must be larger than the offset for the previous line // The line offset must be larger than the offset for the previous line
// and smaller than the file size; otherwise the line offset is ignored. // and smaller than the file size; otherwise the line offset is ignored.
//
func (f *File) AddLine(offset int) { func (f *File) AddLine(offset int) {
f.set.mutex.Lock() f.set.mutex.Lock()
if i := len(f.lines); (i == 0 || f.lines[i-1] < offset) && offset < f.size { if i := len(f.lines); (i == 0 || f.lines[i-1] < offset) && offset < f.size {
@ -137,6 +143,7 @@ func (f *File) AddLine(offset int) {
// Each line offset must be larger than the offset for the previous line // Each line offset must be larger than the offset for the previous line
// and smaller than the file size; otherwise SetLines fails and returns // and smaller than the file size; otherwise SetLines fails and returns
// false. // false.
//
func (f *File) SetLines(lines []int) bool { func (f *File) SetLines(lines []int) bool {
// verify validity of lines table // verify validity of lines table
size := f.size size := f.size
@ -190,6 +197,7 @@ type lineInfo struct {
// //
// AddLineInfo is typically used to register alternative position // AddLineInfo is typically used to register alternative position
// information for //line filename:line comments in source files. // information for //line filename:line comments in source files.
//
func (f *File) AddLineInfo(offset int, filename string, line int) { func (f *File) AddLineInfo(offset int, filename string, line int) {
f.set.mutex.Lock() f.set.mutex.Lock()
if i := len(f.infos); i == 0 || f.infos[i-1].Offset < offset && offset < f.size { if i := len(f.infos); i == 0 || f.infos[i-1].Offset < offset && offset < f.size {
@ -201,6 +209,7 @@ func (f *File) AddLineInfo(offset int, filename string, line int) {
// Pos returns the Pos value for the given file offset; // Pos returns the Pos value for the given file offset;
// the offset must be <= f.Size(). // the offset must be <= f.Size().
// f.Pos(f.Offset(p)) == p. // f.Pos(f.Offset(p)) == p.
//
func (f *File) Pos(offset int) Pos { func (f *File) Pos(offset int) Pos {
if offset > f.size { if offset > f.size {
panic("illegal file offset") panic("illegal file offset")
@ -211,6 +220,7 @@ func (f *File) Pos(offset int) Pos {
// Offset returns the offset for the given file position p; // Offset returns the offset for the given file position p;
// p must be a valid Pos value in that file. // p must be a valid Pos value in that file.
// f.Offset(f.Pos(offset)) == offset. // f.Offset(f.Pos(offset)) == offset.
//
func (f *File) Offset(p Pos) int { func (f *File) Offset(p Pos) int {
if int(p) < f.base || int(p) > f.base+f.size { if int(p) < f.base || int(p) > f.base+f.size {
panic("illegal Pos value") panic("illegal Pos value")
@ -220,6 +230,7 @@ func (f *File) Offset(p Pos) int {
// Line returns the line number for the given file position p; // Line returns the line number for the given file position p;
// p must be a Pos value in that file or NoPos. // p must be a Pos value in that file or NoPos.
//
func (f *File) Line(p Pos) int { func (f *File) Line(p Pos) int {
// TODO(gri) this can be implemented much more efficiently // TODO(gri) this can be implemented much more efficiently
return f.Position(p).Line return f.Position(p).Line
@ -257,6 +268,7 @@ func (f *File) position(p Pos) (pos Position) {
// Position returns the Position value for the given file position p; // Position returns the Position value for the given file position p;
// p must be a Pos value in that file or NoPos. // p must be a Pos value in that file or NoPos.
//
func (f *File) Position(p Pos) (pos Position) { func (f *File) Position(p Pos) (pos Position) {
if p != NoPos { if p != NoPos {
if int(p) < f.base || int(p) > f.base+f.size { if int(p) < f.base || int(p) > f.base+f.size {
@ -273,6 +285,7 @@ func (f *File) Position(p Pos) (pos Position) {
// A FileSet represents a set of source files. // A FileSet represents a set of source files.
// Methods of file sets are synchronized; multiple goroutines // Methods of file sets are synchronized; multiple goroutines
// may invoke them concurrently. // may invoke them concurrently.
//
type FileSet struct { type FileSet struct {
mutex sync.RWMutex // protects the file set mutex sync.RWMutex // protects the file set
base int // base offset for the next file base int // base offset for the next file
@ -289,6 +302,7 @@ func NewFileSet() *FileSet {
// Base returns the minimum base offset that must be provided to // Base returns the minimum base offset that must be provided to
// AddFile when adding the next file. // AddFile when adding the next file.
//
func (s *FileSet) Base() int { func (s *FileSet) Base() int {
s.mutex.RLock() s.mutex.RLock()
b := s.base b := s.base
@ -311,6 +325,7 @@ func (s *FileSet) Base() int {
// with offs in the range [0, size] and thus p in the range [base, base+size]. // with offs in the range [0, size] and thus p in the range [base, base+size].
// For convenience, File.Pos may be used to create file-specific position // For convenience, File.Pos may be used to create file-specific position
// values from a file offset. // values from a file offset.
//
func (s *FileSet) AddFile(filename string, base, size int) *File { func (s *FileSet) AddFile(filename string, base, size int) *File {
s.mutex.Lock() s.mutex.Lock()
defer s.mutex.Unlock() defer s.mutex.Unlock()
@ -332,6 +347,7 @@ func (s *FileSet) AddFile(filename string, base, size int) *File {
// Iterate calls f for the files in the file set in the order they were added // Iterate calls f for the files in the file set in the order they were added
// until f returns false. // until f returns false.
//
func (s *FileSet) Iterate(f func(*File) bool) { func (s *FileSet) Iterate(f func(*File) bool) {
for i := 0; ; i++ { for i := 0; ; i++ {
var file *File var file *File
@ -370,6 +386,7 @@ func (s *FileSet) file(p Pos) *File {
// File returns the file that contains the position p. // File returns the file that contains the position p.
// If no such file is found (for instance for p == NoPos), // If no such file is found (for instance for p == NoPos),
// the result is nil. // the result is nil.
//
func (s *FileSet) File(p Pos) (f *File) { func (s *FileSet) File(p Pos) (f *File) {
if p != NoPos { if p != NoPos {
s.mutex.RLock() s.mutex.RLock()

View File

@ -7,6 +7,7 @@
// //
// Note that the API for the token package may change to accommodate new // Note that the API for the token package may change to accommodate new
// features or implementation changes in gcfg. // features or implementation changes in gcfg.
//
package token package token
import "strconv" import "strconv"
@ -57,6 +58,7 @@ var tokens = [...]string{
// sequence (e.g., for the token ASSIGN, the string is "="). For all other // sequence (e.g., for the token ASSIGN, the string is "="). For all other
// tokens the string corresponds to the token constant name (e.g. for the // tokens the string corresponds to the token constant name (e.g. for the
// token IDENT, the string is "IDENT"). // token IDENT, the string is "IDENT").
//
func (tok Token) String() string { func (tok Token) String() string {
s := "" s := ""
if 0 <= tok && tok < Token(len(tokens)) { if 0 <= tok && tok < Token(len(tokens)) {
@ -72,8 +74,10 @@ func (tok Token) String() string {
// IsLiteral returns true for tokens corresponding to identifiers // IsLiteral returns true for tokens corresponding to identifiers
// and basic type literals; it returns false otherwise. // and basic type literals; it returns false otherwise.
//
func (tok Token) IsLiteral() bool { return literal_beg < tok && tok < literal_end } func (tok Token) IsLiteral() bool { return literal_beg < tok && tok < literal_end }
// IsOperator returns true for tokens corresponding to operators and // IsOperator returns true for tokens corresponding to operators and
// delimiters; it returns false otherwise. // delimiters; it returns false otherwise.
//
func (tok Token) IsOperator() bool { return operator_beg < tok && tok < operator_end } func (tok Token) IsOperator() bool { return operator_beg < tok && tok < operator_end }

View File

@ -25,7 +25,7 @@ type Memory struct {
tempCount int tempCount int
} }
// New returns a new Memory filesystem. //New returns a new Memory filesystem.
func New() billy.Filesystem { func New() billy.Filesystem {
fs := &Memory{s: newStorage()} fs := &Memory{s: newStorage()}
return chroot.New(fs, string(separator)) return chroot.New(fs, string(separator))

View File

@ -46,7 +46,7 @@ func walk(fs billy.Filesystem, path string, info os.FileInfo, walkFn filepath.Wa
return nil return nil
} }
// Walk walks the file tree rooted at root, calling fn for each file or // Walk walks the file tree rooted at root, calling fn for each file or
// directory in the tree, including root. All errors that arise visiting files // directory in the tree, including root. All errors that arise visiting files
// and directories are filtered by fn: see the WalkFunc documentation for // and directories are filtered by fn: see the WalkFunc documentation for
// details. // details.
@ -54,7 +54,7 @@ func walk(fs billy.Filesystem, path string, info os.FileInfo, walkFn filepath.Wa
// The files are walked in lexical order, which makes the output deterministic // The files are walked in lexical order, which makes the output deterministic
// but requires Walk to read an entire directory into memory before proceeding // but requires Walk to read an entire directory into memory before proceeding
// to walk that directory. Walk does not follow symbolic links. // to walk that directory. Walk does not follow symbolic links.
// //
// Function adapted from https://github.com/golang/go/blob/3b770f2ccb1fa6fecc22ea822a19447b10b70c5c/src/path/filepath/path.go#L500 // Function adapted from https://github.com/golang/go/blob/3b770f2ccb1fa6fecc22ea822a19447b10b70c5c/src/path/filepath/path.go#L500
func Walk(fs billy.Filesystem, root string, walkFn filepath.WalkFunc) error { func Walk(fs billy.Filesystem, root string, walkFn filepath.WalkFunc) error {
info, err := fs.Lstat(root) info, err := fs.Lstat(root)
@ -63,10 +63,10 @@ func Walk(fs billy.Filesystem, root string, walkFn filepath.WalkFunc) error {
} else { } else {
err = walk(fs, root, info, walkFn) err = walk(fs, root, info, walkFn)
} }
if err == filepath.SkipDir { if err == filepath.SkipDir {
return nil return nil
} }
return err return err
} }

Some files were not shown because too many files have changed in this diff Show More