forked from toolshed/abra
Compare commits
2 Commits
abra-app-m
...
deploy-rel
Author | SHA1 | Date | |
---|---|---|---|
5a4dac7e76 | |||
628a9a4b3f |
47
.drone.yml
47
.drone.yml
@ -3,14 +3,14 @@ kind: pipeline
|
|||||||
name: coopcloud.tech/abra
|
name: coopcloud.tech/abra
|
||||||
steps:
|
steps:
|
||||||
- name: make check
|
- name: make check
|
||||||
image: golang:1.24
|
image: golang:1.22
|
||||||
commands:
|
commands:
|
||||||
- make check
|
- make check
|
||||||
|
|
||||||
- name: make test
|
- name: make test
|
||||||
image: golang:1.24
|
image: golang:1.22
|
||||||
environment:
|
environment:
|
||||||
CATL_URL: https://git.coopcloud.tech/toolshed/recipes-catalogue-json.git
|
CATL_URL: https://git.coopcloud.tech/coop-cloud/recipes-catalogue-json.git
|
||||||
commands:
|
commands:
|
||||||
- mkdir -p $HOME/.abra
|
- mkdir -p $HOME/.abra
|
||||||
- git clone $CATL_URL $HOME/.abra/catalogue
|
- git clone $CATL_URL $HOME/.abra/catalogue
|
||||||
@ -29,7 +29,7 @@ steps:
|
|||||||
event: tag
|
event: tag
|
||||||
|
|
||||||
- name: release
|
- name: release
|
||||||
image: goreleaser/goreleaser:v2.5.1
|
image: goreleaser/goreleaser:v1.24.0
|
||||||
environment:
|
environment:
|
||||||
GITEA_TOKEN:
|
GITEA_TOKEN:
|
||||||
from_secret: goreleaser_gitea_token
|
from_secret: goreleaser_gitea_token
|
||||||
@ -47,10 +47,10 @@ steps:
|
|||||||
image: plugins/docker
|
image: plugins/docker
|
||||||
settings:
|
settings:
|
||||||
auto_tag: true
|
auto_tag: true
|
||||||
username: abra-bot
|
username: 3wordchant
|
||||||
password:
|
password:
|
||||||
from_secret: git_coopcloud_tech_token_abra_bot
|
from_secret: git_coopcloud_tech_token_3wc
|
||||||
repo: git.coopcloud.tech/toolshed/abra
|
repo: git.coopcloud.tech/coop-cloud/abra
|
||||||
tags: dev
|
tags: dev
|
||||||
registry: git.coopcloud.tech
|
registry: git.coopcloud.tech
|
||||||
when:
|
when:
|
||||||
@ -60,7 +60,7 @@ steps:
|
|||||||
- make check
|
- make check
|
||||||
- make test
|
- make test
|
||||||
|
|
||||||
- name: on-demand integration test
|
- name: integration test
|
||||||
image: appleboy/drone-ssh
|
image: appleboy/drone-ssh
|
||||||
settings:
|
settings:
|
||||||
host:
|
host:
|
||||||
@ -74,31 +74,7 @@ steps:
|
|||||||
request_pty: true
|
request_pty: true
|
||||||
script:
|
script:
|
||||||
- |
|
- |
|
||||||
wget https://git.coopcloud.tech/toolshed/abra/raw/branch/main/scripts/tests/run-ci-int -O run-ci-int
|
wget https://git.coopcloud.tech/coop-cloud/abra/raw/branch/main/scripts/tests/run-ci-int -O run-ci-int
|
||||||
chmod +x run-ci-int
|
|
||||||
sh run-ci-int
|
|
||||||
when:
|
|
||||||
ref:
|
|
||||||
- refs/heads/int-*
|
|
||||||
depends_on:
|
|
||||||
- make check
|
|
||||||
- make test
|
|
||||||
|
|
||||||
- name: nightly integration test
|
|
||||||
image: appleboy/drone-ssh
|
|
||||||
settings:
|
|
||||||
host:
|
|
||||||
- int.coopcloud.tech
|
|
||||||
username: abra
|
|
||||||
key:
|
|
||||||
from_secret: abra_int_private_key
|
|
||||||
port: 22
|
|
||||||
command_timeout: 60m
|
|
||||||
script_stop: true
|
|
||||||
request_pty: true
|
|
||||||
script:
|
|
||||||
- |
|
|
||||||
wget https://git.coopcloud.tech/toolshed/abra/raw/branch/main/scripts/tests/run-ci-int -O run-ci-int
|
|
||||||
chmod +x run-ci-int
|
chmod +x run-ci-int
|
||||||
sh run-ci-int
|
sh run-ci-int
|
||||||
when:
|
when:
|
||||||
@ -111,8 +87,3 @@ steps:
|
|||||||
volumes:
|
volumes:
|
||||||
- name: deps
|
- name: deps
|
||||||
temp: {}
|
temp: {}
|
||||||
|
|
||||||
trigger:
|
|
||||||
action:
|
|
||||||
exclude:
|
|
||||||
- synchronized
|
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
# integration test suite
|
# integration test suite
|
||||||
# export ABRA_DIR="$HOME/.abra_test"
|
# export ABRA_DIR="$HOME/.abra_test"
|
||||||
# export TEST_SERVER=test.example.com
|
# export ABRA_TEST_DOMAIN=test.example.com
|
||||||
# export ABRA_CI=1
|
# export ABRA_CI=1
|
||||||
|
|
||||||
# release automation
|
# release automation
|
||||||
|
8
.gitea/ISSUE_TEMPLATE.md
Normal file
8
.gitea/ISSUE_TEMPLATE.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
name: "Do not use this issue tracker"
|
||||||
|
about: "Do not use this issue tracker"
|
||||||
|
title: "Do not use this issue tracker"
|
||||||
|
labels: []
|
||||||
|
---
|
||||||
|
|
||||||
|
Please report your issue on [`coop-cloud/organising`](https://git.coopcloud.tech/coop-cloud/organising)
|
@ -4,7 +4,6 @@
|
|||||||
> please do add yourself! This is a community project, let's show some 💞
|
> please do add yourself! This is a community project, let's show some 💞
|
||||||
|
|
||||||
- 3wordchant
|
- 3wordchant
|
||||||
- ammaratef45
|
|
||||||
- cassowary
|
- cassowary
|
||||||
- codegod100
|
- codegod100
|
||||||
- decentral1se
|
- decentral1se
|
||||||
@ -18,5 +17,3 @@
|
|||||||
- roxxers
|
- roxxers
|
||||||
- vera
|
- vera
|
||||||
- yksflip
|
- yksflip
|
||||||
- basebuilder
|
|
||||||
- mayel
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# Build image
|
# Build image
|
||||||
FROM golang:1.24-alpine AS build
|
FROM golang:1.22-alpine AS build
|
||||||
|
|
||||||
ENV GOPRIVATE=coopcloud.tech
|
ENV GOPRIVATE coopcloud.tech
|
||||||
|
|
||||||
RUN apk add --no-cache \
|
RUN apk add --no-cache \
|
||||||
gcc \
|
gcc \
|
||||||
|
2
Makefile
2
Makefile
@ -2,7 +2,7 @@ ABRA := ./cmd/abra
|
|||||||
KADABRA := ./cmd/kadabra
|
KADABRA := ./cmd/kadabra
|
||||||
COMMIT := $(shell git rev-list -1 HEAD)
|
COMMIT := $(shell git rev-list -1 HEAD)
|
||||||
GOPATH := $(shell go env GOPATH)
|
GOPATH := $(shell go env GOPATH)
|
||||||
GOVERSION := 1.24
|
GOVERSION := 1.22
|
||||||
LDFLAGS := "-X 'main.Commit=$(COMMIT)'"
|
LDFLAGS := "-X 'main.Commit=$(COMMIT)'"
|
||||||
DIST_LDFLAGS := $(LDFLAGS)" -s -w"
|
DIST_LDFLAGS := $(LDFLAGS)" -s -w"
|
||||||
GCFLAGS := "all=-l -B"
|
GCFLAGS := "all=-l -B"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
# `abra`
|
# `abra`
|
||||||
|
|
||||||
[](https://build.coopcloud.tech/toolshed/abra)
|
[](https://build.coopcloud.tech/coop-cloud/abra)
|
||||||
[](https://goreportcard.com/report/git.coopcloud.tech/toolshed/abra)
|
[](https://goreportcard.com/report/git.coopcloud.tech/coop-cloud/abra)
|
||||||
[](https://pkg.go.dev/coopcloud.tech/abra)
|
[](https://pkg.go.dev/coopcloud.tech/abra)
|
||||||
|
|
||||||
The Co-op Cloud utility belt 🎩🐇
|
The Co-op Cloud utility belt 🎩🐇
|
||||||
|
@ -1,12 +1,11 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/leonelquinteros/gotext"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var AppCommand = &cobra.Command{
|
var AppCommand = &cobra.Command{
|
||||||
Use: "app [cmd] [args] [flags]",
|
Use: "app [cmd] [args] [flags]",
|
||||||
Aliases: []string{"a"},
|
Aliases: []string{"a"},
|
||||||
Short: gotext.Get("Manage apps"),
|
Short: "Manage apps",
|
||||||
}
|
}
|
||||||
|
@ -11,7 +11,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var AppBackupListCommand = &cobra.Command{
|
var AppBackupListCommand = &cobra.Command{
|
||||||
Use: "list <domain> [flags]",
|
Use: "list <app> [flags]",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Short: "List the contents of a snapshot",
|
Short: "List the contents of a snapshot",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
@ -61,7 +61,7 @@ var AppBackupListCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var AppBackupDownloadCommand = &cobra.Command{
|
var AppBackupDownloadCommand = &cobra.Command{
|
||||||
Use: "download <domain> [flags]",
|
Use: "download <app> [flags]",
|
||||||
Aliases: []string{"d"},
|
Aliases: []string{"d"},
|
||||||
Short: "Download a snapshot",
|
Short: "Download a snapshot",
|
||||||
Long: `Downloads a backup.tar.gz to the current working directory.
|
Long: `Downloads a backup.tar.gz to the current working directory.
|
||||||
@ -78,7 +78,7 @@ var AppBackupDownloadCommand = &cobra.Command{
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -130,7 +130,7 @@ var AppBackupDownloadCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var AppBackupCreateCommand = &cobra.Command{
|
var AppBackupCreateCommand = &cobra.Command{
|
||||||
Use: "create <domain> [flags]",
|
Use: "create <app> [flags]",
|
||||||
Aliases: []string{"c"},
|
Aliases: []string{"c"},
|
||||||
Short: "Create a new snapshot",
|
Short: "Create a new snapshot",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
@ -143,7 +143,7 @@ var AppBackupCreateCommand = &cobra.Command{
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -174,7 +174,7 @@ var AppBackupCreateCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var AppBackupSnapshotsCommand = &cobra.Command{
|
var AppBackupSnapshotsCommand = &cobra.Command{
|
||||||
Use: "snapshots <domain> [flags]",
|
Use: "snapshots <app> [flags]",
|
||||||
Aliases: []string{"s"},
|
Aliases: []string{"s"},
|
||||||
Short: "List all snapshots",
|
Short: "List all snapshots",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
|
@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var AppCheckCommand = &cobra.Command{
|
var AppCheckCommand = &cobra.Command{
|
||||||
Use: "check <domain> [flags]",
|
Use: "check <app> [flags]",
|
||||||
Aliases: []string{"chk"},
|
Aliases: []string{"chk"},
|
||||||
Short: "Ensure an app is well configured",
|
Short: "Ensure an app is well configured",
|
||||||
Long: `Compare env vars in both the app ".env" and recipe ".env.sample" file.
|
Long: `Compare env vars in both the app ".env" and recipe ".env.sample" file.
|
||||||
@ -36,7 +36,7 @@ ${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var AppCmdCommand = &cobra.Command{
|
var AppCmdCommand = &cobra.Command{
|
||||||
Use: "command <domain> [service | --local] <cmd> [[args] [flags] | [flags] -- [args]]",
|
Use: "command <app> [service | --local] <cmd> [[args] [flags] | [flags] -- [args]]",
|
||||||
Aliases: []string{"cmd"},
|
Aliases: []string{"cmd"},
|
||||||
Short: "Run app commands",
|
Short: "Run app commands",
|
||||||
Long: `Run an app specific command.
|
Long: `Run an app specific command.
|
||||||
@ -92,7 +92,7 @@ does not).`,
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,7 +183,7 @@ does not).`,
|
|||||||
if err := internal.RunCmdRemote(
|
if err := internal.RunCmdRemote(
|
||||||
cl,
|
cl,
|
||||||
app,
|
app,
|
||||||
disableTTY,
|
requestTTY,
|
||||||
app.Recipe.AbraShPath,
|
app.Recipe.AbraShPath,
|
||||||
targetServiceName, cmdName, parsedCmdArgs, remoteUser); err != nil {
|
targetServiceName, cmdName, parsedCmdArgs, remoteUser); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -192,14 +192,14 @@ does not).`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
var AppCmdListCommand = &cobra.Command{
|
var AppCmdListCommand = &cobra.Command{
|
||||||
Use: "list <domain> [flags]",
|
Use: "list <app> [flags]",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Short: "List all available commands",
|
Short: "List all available commands",
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -238,7 +238,7 @@ func parseCmdArgs(args []string, isLocal bool) (bool, string) {
|
|||||||
var (
|
var (
|
||||||
local bool
|
local bool
|
||||||
remoteUser string
|
remoteUser string
|
||||||
disableTTY bool
|
requestTTY bool
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -259,11 +259,11 @@ func init() {
|
|||||||
)
|
)
|
||||||
|
|
||||||
AppCmdCommand.Flags().BoolVarP(
|
AppCmdCommand.Flags().BoolVarP(
|
||||||
&disableTTY,
|
&requestTTY,
|
||||||
"tty",
|
"tty",
|
||||||
"T",
|
"t",
|
||||||
false,
|
false,
|
||||||
"disable remote TTY",
|
"request remote TTY",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppCmdCommand.Flags().BoolVarP(
|
AppCmdCommand.Flags().BoolVarP(
|
||||||
|
@ -13,7 +13,7 @@ func TestParseCmdArgs(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
// `--` is not parsed when passed in from the command-line e.g. -- foo bar baz
|
// `--` is not parsed when passed in from the command-line e.g. -- foo bar baz
|
||||||
// so we need to eumlate that as missing when testing if bash args are passed in
|
// so we need to eumlate that as missing when testing if bash args are passed in
|
||||||
// see https://git.coopcloud.tech/toolshed/organising/issues/336 for more
|
// see https://git.coopcloud.tech/coop-cloud/organising/issues/336 for more
|
||||||
{[]string{"foo.com", "app", "test"}, false, ""},
|
{[]string{"foo.com", "app", "test"}, false, ""},
|
||||||
{[]string{"foo.com", "app", "test", "foo"}, true, "foo "},
|
{[]string{"foo.com", "app", "test", "foo"}, true, "foo "},
|
||||||
{[]string{"foo.com", "app", "test", "foo", "bar", "baz"}, true, "foo bar baz "},
|
{[]string{"foo.com", "app", "test", "foo", "bar", "baz"}, true, "foo bar baz "},
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var AppConfigCommand = &cobra.Command{
|
var AppConfigCommand = &cobra.Command{
|
||||||
Use: "config <domain> [flags]",
|
Use: "config <app> [flags]",
|
||||||
Aliases: []string{"cfg"},
|
Aliases: []string{"cfg"},
|
||||||
Short: "Edit app config",
|
Short: "Edit app config",
|
||||||
Example: " abra config 1312.net",
|
Example: " abra config 1312.net",
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/upstream/container"
|
"coopcloud.tech/abra/pkg/upstream/container"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
containertypes "github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types"
|
||||||
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"
|
||||||
@ -26,14 +26,14 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var AppCpCommand = &cobra.Command{
|
var AppCpCommand = &cobra.Command{
|
||||||
Use: "cp <domain> <src> <dst> [flags]",
|
Use: "cp <app> <src> <dst> [flags]",
|
||||||
Aliases: []string{"c"},
|
Aliases: []string{"c"},
|
||||||
Short: "Copy files to/from a deployed app service",
|
Short: "Copy files to/from a deployed app service",
|
||||||
Example: ` # copy myfile.txt to the root of the app service
|
Example: ` # copy myfile.txt to the root of the app service
|
||||||
abra app cp 1312.net myfile.txt app:/
|
abra app cp 1312.net myfile.txt app:/
|
||||||
|
|
||||||
# copy that file back to your current working directory locally
|
# copy that file back to your current working directory locally
|
||||||
abra app cp 1312.net app:/myfile.txt ./`,
|
abra app cp 1312.net app:/myfile.txt`,
|
||||||
Args: cobra.ExactArgs(3),
|
Args: cobra.ExactArgs(3),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -49,7 +49,7 @@ var AppCpCommand = &cobra.Command{
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,7 +134,7 @@ func CopyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath stri
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := container.RunExec(dcli, cl, containerID, &containertypes.ExecOptions{
|
if _, err := container.RunExec(dcli, cl, containerID, &types.ExecConfig{
|
||||||
AttachStderr: true,
|
AttachStderr: true,
|
||||||
AttachStdin: true,
|
AttachStdin: true,
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
@ -162,7 +162,7 @@ func CopyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("copy %s from local to %s on container", srcPath, dstPath)
|
log.Debugf("copy %s from local to %s on container", srcPath, dstPath)
|
||||||
copyOpts := containertypes.CopyToContainerOptions{AllowOverwriteDirWithFile: false, CopyUIDGID: false}
|
copyOpts := types.CopyToContainerOptions{AllowOverwriteDirWithFile: false, CopyUIDGID: false}
|
||||||
if err := cl.CopyToContainer(context.Background(), containerID, dstPath, content, copyOpts); err != nil {
|
if err := cl.CopyToContainer(context.Background(), containerID, dstPath, content, copyOpts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -173,7 +173,7 @@ func CopyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath stri
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if _, err := container.RunExec(dcli, cl, containerID, &containertypes.ExecOptions{
|
if _, err := container.RunExec(dcli, cl, containerID, &types.ExecConfig{
|
||||||
AttachStderr: true,
|
AttachStderr: true,
|
||||||
AttachStdin: true,
|
AttachStdin: true,
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
|
@ -2,15 +2,15 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/app"
|
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/envfile"
|
"coopcloud.tech/abra/pkg/envfile"
|
||||||
"coopcloud.tech/abra/pkg/secret"
|
"coopcloud.tech/abra/pkg/secret"
|
||||||
|
"coopcloud.tech/tagcmp"
|
||||||
|
|
||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
@ -19,19 +19,18 @@ 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"
|
||||||
dockerClient "github.com/docker/docker/client"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var AppDeployCommand = &cobra.Command{
|
var AppDeployCommand = &cobra.Command{
|
||||||
Use: "deploy <domain> [version] [flags]",
|
Use: "deploy <app> [version] [flags]",
|
||||||
Aliases: []string{"d"},
|
Aliases: []string{"d"},
|
||||||
Short: "Deploy an app",
|
Short: "Deploy an app",
|
||||||
Long: `Deploy an app.
|
Long: `Deploy an app.
|
||||||
|
|
||||||
This command supports chaos operations. Use "--chaos/-C" to deploy your recipe
|
This command supports chaos operations. Use "--chaos/-c" to deploy your recipe
|
||||||
checkout as-is. Recipe commit hashes are also supported as values for
|
checkout as-is. Recipe commit hashes are also supported values for "[version]".
|
||||||
"[version]". Please note, "upgrade"/"rollback" do not support chaos operations.`,
|
Please note, "upgrade"/"rollback" do not support chaos operations.`,
|
||||||
Example: ` # standard deployment
|
Example: ` # standard deployment
|
||||||
abra app deploy 1312.net
|
abra app deploy 1312.net
|
||||||
|
|
||||||
@ -64,61 +63,149 @@ checkout as-is. Recipe commit hashes are also supported as values for
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
var (
|
var warnMessages []string
|
||||||
deployWarnMessages []string
|
|
||||||
toDeployVersion string
|
|
||||||
)
|
|
||||||
|
|
||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
stackName := app.StackName()
|
||||||
|
|
||||||
if err := validateArgsAndFlags(args); err != nil {
|
ok, err := validateChaosXORVersion(args)
|
||||||
|
if !ok {
|
||||||
|
log.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
specificVersion := getSpecifiedVersion(args)
|
||||||
|
|
||||||
|
if specificVersion != "" {
|
||||||
|
log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion)
|
||||||
|
app.Recipe.Version = specificVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if specificVersion == "" && app.Recipe.Version != "" && !internal.Chaos {
|
||||||
|
log.Debugf("retrieved %s as version from env file", app.Recipe.Version)
|
||||||
|
specificVersion = app.Recipe.Version
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
if err := lint.LintForErrors(app.Recipe); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debugf("checking whether %s is already deployed", stackName)
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
cl, err := client.New(app.Server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("checking whether %s is already deployed", app.StackName())
|
deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName)
|
||||||
|
|
||||||
deployMeta, err := stack.IsDeployed(context.Background(), cl, app.StackName())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if deployMeta.IsDeployed && !(internal.Force || internal.Chaos) {
|
// NOTE(d1): handles "<version> as git hash" use case
|
||||||
log.Fatalf("%s is already deployed", app.Name)
|
var isChaosCommit bool
|
||||||
}
|
|
||||||
|
|
||||||
toDeployVersion, err = getDeployVersion(args, deployMeta, app)
|
// NOTE(d1): check out specific version before dealing with secrets. This
|
||||||
if err != nil {
|
// is because we need to deal with GetComposeFiles under the hood and these
|
||||||
log.Fatal(fmt.Errorf("get deploy version: %s", err))
|
// files change from version to version which therefore affects which
|
||||||
}
|
// secrets might be generated
|
||||||
|
toDeployVersion := deployMeta.Version
|
||||||
|
if specificVersion != "" {
|
||||||
|
toDeployVersion = specificVersion
|
||||||
|
log.Debugf("choosing %s as version to deploy", toDeployVersion)
|
||||||
|
|
||||||
if !internal.Chaos {
|
var err error
|
||||||
_, err = app.Recipe.EnsureVersion(toDeployVersion)
|
isChaosCommit, err = app.Recipe.EnsureVersion(toDeployVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("ensure recipe: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := lint.LintForErrors(app.Recipe); err != nil {
|
|
||||||
if internal.Chaos {
|
|
||||||
log.Warn(err)
|
|
||||||
} else {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if isChaosCommit {
|
||||||
|
log.Debugf("assuming '%s' is a chaos commit", toDeployVersion)
|
||||||
|
internal.Chaos = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := validateSecrets(cl, app); err != nil {
|
secStats, err := secret.PollSecretsStatus(cl, app)
|
||||||
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for _, secStat := range secStats {
|
||||||
|
if !secStat.CreatedOnRemote {
|
||||||
|
log.Fatalf("unable to deploy, secrets not generated (%s)?", secStat.LocalName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !internal.Chaos && specificVersion == "" {
|
||||||
|
versions, err := app.Recipe.Tags()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(versions) > 0 && !internal.Chaos {
|
||||||
|
toDeployVersion = versions[len(versions)-1]
|
||||||
|
log.Debugf("choosing %s as version to deploy", toDeployVersion)
|
||||||
|
if _, err := app.Recipe.EnsureVersion(toDeployVersion); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
head, err := app.Recipe.Head()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
toDeployVersion = formatter.SmallSHA(head.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toDeployChaosVersion := config.CHAOS_DEFAULT
|
||||||
|
if internal.Chaos {
|
||||||
|
if isChaosCommit {
|
||||||
|
toDeployChaosVersion = specificVersion
|
||||||
|
versionLabelLocal, err := app.Recipe.GetVersionLabelLocal()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
toDeployVersion = versionLabelLocal
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
toDeployChaosVersion, err = app.Recipe.ChaosVersion()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if deployMeta.IsDeployed {
|
||||||
|
if !internal.Force {
|
||||||
|
appStatus, err := app.Status()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if appStatus.Chaos && !internal.Chaos {
|
||||||
|
log.Fatalf("%s is deployed from a chaos version. Are you sure the local changes are in the current version (%s)?", app.Name, toDeployVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if toDeployVersion != "" && appStatus.Version != "" {
|
||||||
|
localVersion, err := tagcmp.Parse(toDeployVersion)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
remoteVersion, err := tagcmp.Parse(appStatus.Version)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if localVersion.IsLessThan(remoteVersion) {
|
||||||
|
log.Fatalf("%s is deployed at %s. Are you sure you want to downgrade to %s?", app.Name, appStatus.Version, toDeployVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
abraShEnv, err := envfile.ReadAbraShEnvVars(app.Recipe.AbraShPath)
|
abraShEnv, err := envfile.ReadAbraShEnvVars(app.Recipe.AbraShPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -132,7 +219,6 @@ checkout as-is. Recipe commit hashes are also supported as values for
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stackName := app.StackName()
|
|
||||||
deployOpts := stack.Deploy{
|
deployOpts := stack.Deploy{
|
||||||
Composefiles: composeFiles,
|
Composefiles: composeFiles,
|
||||||
Namespace: stackName,
|
Namespace: stackName,
|
||||||
@ -148,11 +234,8 @@ checkout as-is. Recipe commit hashes are also supported as values for
|
|||||||
appPkg.ExposeAllEnv(stackName, compose, app.Env)
|
appPkg.ExposeAllEnv(stackName, compose, app.Env)
|
||||||
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
|
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
|
||||||
appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
|
appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
|
||||||
if internal.Chaos {
|
appPkg.SetChaosVersionLabel(compose, stackName, toDeployChaosVersion)
|
||||||
appPkg.SetChaosVersionLabel(compose, stackName, toDeployVersion)
|
|
||||||
}
|
|
||||||
appPkg.SetUpdateLabel(compose, stackName, app.Env)
|
appPkg.SetUpdateLabel(compose, stackName, app.Env)
|
||||||
appPkg.SetVersionLabel(compose, stackName, toDeployVersion)
|
|
||||||
|
|
||||||
envVars, err := appPkg.CheckEnv(app)
|
envVars, err := appPkg.CheckEnv(app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -161,22 +244,23 @@ checkout as-is. Recipe commit hashes are also supported as values for
|
|||||||
|
|
||||||
for _, envVar := range envVars {
|
for _, envVar := range envVars {
|
||||||
if !envVar.Present {
|
if !envVar.Present {
|
||||||
deployWarnMessages = append(deployWarnMessages,
|
warnMessages = append(warnMessages,
|
||||||
fmt.Sprintf("%s missing from %s.env", envVar.Name, app.Domain),
|
fmt.Sprintf("env var %s missing from %s.env, present in recipe .env.sample", envVar.Name, app.Domain),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !internal.NoDomainChecks {
|
if !internal.NoDomainChecks {
|
||||||
if domainName, ok := app.Env["DOMAIN"]; ok {
|
domainName, ok := app.Env["DOMAIN"]
|
||||||
|
if ok {
|
||||||
if _, err = dns.EnsureDomainsResolveSameIPv4(domainName, app.Server); err != nil {
|
if _, err = dns.EnsureDomainsResolveSameIPv4(domainName, app.Server); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Debug("skipping domain checks, no DOMAIN=... configured")
|
log.Debug("skipping domain checks as no DOMAIN=... configured for app")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Debug("skipping domain checks")
|
log.Debug("skipping domain checks as requested")
|
||||||
}
|
}
|
||||||
|
|
||||||
deployedVersion := config.NO_VERSION_DEFAULT
|
deployedVersion := config.NO_VERSION_DEFAULT
|
||||||
@ -186,11 +270,11 @@ checkout as-is. Recipe commit hashes are also supported as values for
|
|||||||
|
|
||||||
if err := internal.DeployOverview(
|
if err := internal.DeployOverview(
|
||||||
app,
|
app,
|
||||||
|
warnMessages,
|
||||||
deployedVersion,
|
deployedVersion,
|
||||||
|
deployMeta.ChaosVersion,
|
||||||
toDeployVersion,
|
toDeployVersion,
|
||||||
"",
|
toDeployChaosVersion); err != nil {
|
||||||
deployWarnMessages,
|
|
||||||
); err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,28 +282,9 @@ checkout as-is. Recipe commit hashes are also supported as values for
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
log.Debugf("set waiting timeout to %d s", stack.WaitTimeout)
|
||||||
|
|
||||||
log.Debugf("set waiting timeout to %d second(s)", stack.WaitTimeout)
|
if err := stack.RunDeploy(cl, deployOpts, compose, app.Name, internal.DontWaitConverge); err != nil {
|
||||||
|
|
||||||
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := app.Filters(true, false, serviceNames...)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := stack.RunDeploy(
|
|
||||||
cl,
|
|
||||||
deployOpts,
|
|
||||||
compose,
|
|
||||||
app.Name,
|
|
||||||
app.Server,
|
|
||||||
internal.DontWaitConverge,
|
|
||||||
f,
|
|
||||||
); err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,92 +296,31 @@ checkout as-is. Recipe commit hashes are also supported as values for
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.WriteRecipeVersion(toDeployVersion, false); err != nil {
|
app.Recipe.Version = toDeployVersion
|
||||||
log.Fatalf("writing recipe version failed: %s", err)
|
if toDeployChaosVersion != config.CHAOS_DEFAULT {
|
||||||
|
app.Recipe.Version = toDeployChaosVersion
|
||||||
|
}
|
||||||
|
log.Debugf("choosing %s as version to save to env file", app.Recipe.Version)
|
||||||
|
if err := app.WriteRecipeVersion(app.Recipe.Version, false); err != nil {
|
||||||
|
log.Fatalf("writing new recipe version in env file: %s", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLatestVersionOrCommit(app app.App) (string, error) {
|
// validateChaosXORVersion xor checks version/chaos mode
|
||||||
versions, err := app.Recipe.Tags()
|
func validateChaosXORVersion(args []string) (bool, error) {
|
||||||
if err != nil {
|
if getSpecifiedVersion(args) != "" && internal.Chaos {
|
||||||
return "", err
|
return false, errors.New("cannot use <version> and --chaos together")
|
||||||
}
|
}
|
||||||
|
return true, nil
|
||||||
if len(versions) > 0 && !internal.Chaos {
|
|
||||||
return versions[len(versions)-1], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
head, err := app.Recipe.Head()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
|
|
||||||
return formatter.SmallSHA(head.String()), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateArgsAndFlags ensures compatible args/flags.
|
// getSpecifiedVersion retrieves the specific version if available
|
||||||
func validateArgsAndFlags(args []string) error {
|
func getSpecifiedVersion(args []string) string {
|
||||||
if len(args) == 2 && args[1] != "" && internal.Chaos {
|
if len(args) >= 2 {
|
||||||
return fmt.Errorf("cannot use [version] and --chaos together")
|
return args[1]
|
||||||
}
|
}
|
||||||
|
return ""
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func validateSecrets(cl *dockerClient.Client, app app.App) error {
|
|
||||||
secStats, err := secret.PollSecretsStatus(cl, app)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, secStat := range secStats {
|
|
||||||
if !secStat.CreatedOnRemote {
|
|
||||||
return fmt.Errorf("secret not generated: %s", secStat.LocalName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getDeployVersion(cliArgs []string, deployMeta stack.DeployMeta, app app.App) (string, error) {
|
|
||||||
// Chaos mode overrides everything
|
|
||||||
if internal.Chaos {
|
|
||||||
v, err := app.Recipe.ChaosVersion()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
log.Debugf("version: taking chaos version: %s", v)
|
|
||||||
return v, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the deploy version is set with a cli argument
|
|
||||||
if len(cliArgs) == 2 && cliArgs[1] != "" {
|
|
||||||
log.Debugf("version: taking version from cli arg: %s", cliArgs[1])
|
|
||||||
return cliArgs[1], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if the recipe has a version in the .env file
|
|
||||||
if app.Recipe.EnvVersion != "" && !internal.IgnoreEnvVersion {
|
|
||||||
if strings.HasSuffix(app.Recipe.EnvVersionRaw, "+U") {
|
|
||||||
return "", fmt.Errorf("version: can not redeploy chaos version %s", app.Recipe.EnvVersionRaw)
|
|
||||||
}
|
|
||||||
log.Debugf("version: taking version from .env file: %s", app.Recipe.EnvVersion)
|
|
||||||
return app.Recipe.EnvVersion, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// Take deployed version
|
|
||||||
if deployMeta.IsDeployed {
|
|
||||||
log.Debugf("version: taking deployed version: %s", deployMeta.Version)
|
|
||||||
return deployMeta.Version, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
v, err := getLatestVersionOrCommit(app)
|
|
||||||
log.Debugf("version: taking new recipe version: %s", v)
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return v, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -349,6 +353,6 @@ func init() {
|
|||||||
"no-converge-checks",
|
"no-converge-checks",
|
||||||
"c",
|
"c",
|
||||||
false,
|
false,
|
||||||
"disable converge logic checks",
|
"do not wait for converge logic checks",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
57
cli/app/deploy_test.go
Normal file
57
cli/app/deploy_test.go
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"coopcloud.tech/abra/cli/internal"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetSpecificVersion(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input []string
|
||||||
|
expectedOutput string
|
||||||
|
}{
|
||||||
|
// No specified version when command has one or less args
|
||||||
|
{[]string{}, ""},
|
||||||
|
{[]string{"arg0"}, ""},
|
||||||
|
// Second in arg (index-1) is the specified result when command has more than 1 args
|
||||||
|
{[]string{"arg0", "arg1"}, "arg1"},
|
||||||
|
{[]string{"arg0", "arg1", "arg2"}, "arg1"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
if test.expectedOutput != getSpecifiedVersion(test.input) {
|
||||||
|
t.Fatalf("result for %s should be %s", test.input, test.expectedOutput)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestValidateChaosXORVersion(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
input []string
|
||||||
|
isChaos bool
|
||||||
|
expectedResult bool
|
||||||
|
}{
|
||||||
|
// Chaos = true, Specified Version absent
|
||||||
|
{[]string{}, true, true},
|
||||||
|
// Chaos = false, Specified Version absent
|
||||||
|
{[]string{}, false, true},
|
||||||
|
// Chaos = true, Specified Version present
|
||||||
|
{[]string{"arg0", "arg1"}, true, false},
|
||||||
|
// Chaos = false, Specified Version present
|
||||||
|
{[]string{"arg0", "arg1", "arg2"}, false, true},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
internal.Chaos = test.isChaos
|
||||||
|
res, _ := validateChaosXORVersion(test.input)
|
||||||
|
if res != test.expectedResult {
|
||||||
|
t.Fatalf(
|
||||||
|
"When args are %s and Chaos mode is %t result needs to be %t",
|
||||||
|
test.input,
|
||||||
|
test.isChaos,
|
||||||
|
test.expectedResult,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,43 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var AppEnvCommand = &cobra.Command{
|
|
||||||
Use: "env <domain> [flags]",
|
|
||||||
Aliases: []string{"e"},
|
|
||||||
Short: "Show app .env values",
|
|
||||||
Example: " abra app env 1312.net",
|
|
||||||
Args: cobra.ExactArgs(1),
|
|
||||||
ValidArgsFunction: func(
|
|
||||||
cmd *cobra.Command,
|
|
||||||
args []string,
|
|
||||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
||||||
return autocomplete.AppNameComplete()
|
|
||||||
},
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
app := internal.ValidateApp(args)
|
|
||||||
|
|
||||||
var envKeys []string
|
|
||||||
for k := range app.Env {
|
|
||||||
envKeys = append(envKeys, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(envKeys)
|
|
||||||
|
|
||||||
var rows [][]string
|
|
||||||
for _, k := range envKeys {
|
|
||||||
rows = append(rows, []string{k, app.Env[k]})
|
|
||||||
}
|
|
||||||
|
|
||||||
overview := formatter.CreateOverview("ENV OVERVIEW", rows)
|
|
||||||
fmt.Println(overview)
|
|
||||||
},
|
|
||||||
}
|
|
@ -1,139 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"sort"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
|
||||||
"coopcloud.tech/abra/pkg/client"
|
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
|
||||||
"coopcloud.tech/abra/pkg/upstream/convert"
|
|
||||||
composetypes "github.com/docker/cli/cli/compose/types"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
dockerClient "github.com/docker/docker/client"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var AppLabelsCommand = &cobra.Command{
|
|
||||||
Use: "labels <domain> [flags]",
|
|
||||||
Aliases: []string{"lb"},
|
|
||||||
Short: "Show deployment labels",
|
|
||||||
Long: "Both local recipe and live deployment labels are shown.",
|
|
||||||
Example: " abra app labels 1312.net",
|
|
||||||
Args: cobra.ExactArgs(1),
|
|
||||||
ValidArgsFunction: func(
|
|
||||||
cmd *cobra.Command,
|
|
||||||
args []string,
|
|
||||||
toComplete string) ([]string, cobra.ShellCompDirective) {
|
|
||||||
return autocomplete.AppNameComplete()
|
|
||||||
},
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
app := internal.ValidateApp(args)
|
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteLabels, err := getLabels(cl, app.StackName())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
rows := [][]string{
|
|
||||||
{"DEPLOYED LABELS", "---"},
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteLabelKeys := make([]string, 0, len(remoteLabels))
|
|
||||||
for k := range remoteLabels {
|
|
||||||
remoteLabelKeys = append(remoteLabelKeys, k)
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(remoteLabelKeys)
|
|
||||||
|
|
||||||
for _, k := range remoteLabelKeys {
|
|
||||||
rows = append(rows, []string{
|
|
||||||
k,
|
|
||||||
remoteLabels[k],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(remoteLabelKeys) == 0 {
|
|
||||||
rows = append(rows, []string{"unknown"})
|
|
||||||
}
|
|
||||||
|
|
||||||
rows = append(rows, []string{"RECIPE LABELS", "---"})
|
|
||||||
|
|
||||||
config, err := app.Recipe.GetComposeConfig(app.Env)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var localLabelKeys []string
|
|
||||||
var appServiceConfig composetypes.ServiceConfig
|
|
||||||
for _, service := range config.Services {
|
|
||||||
if service.Name == "app" {
|
|
||||||
appServiceConfig = service
|
|
||||||
|
|
||||||
for k := range service.Deploy.Labels {
|
|
||||||
localLabelKeys = append(localLabelKeys, k)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Strings(localLabelKeys)
|
|
||||||
|
|
||||||
for _, k := range localLabelKeys {
|
|
||||||
rows = append(rows, []string{
|
|
||||||
k,
|
|
||||||
appServiceConfig.Deploy.Labels[k],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
overview := formatter.CreateOverview("LABELS OVERVIEW", rows)
|
|
||||||
fmt.Println(overview)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// getLabels reads docker labels from running services in the format of "coop-cloud.${STACK_NAME}.${LABEL}".
|
|
||||||
func getLabels(cl *dockerClient.Client, stackName string) (map[string]string, error) {
|
|
||||||
labels := make(map[string]string)
|
|
||||||
|
|
||||||
filter := filters.NewArgs()
|
|
||||||
filter.Add("label", fmt.Sprintf("%s=%s", convert.LabelNamespace, stackName))
|
|
||||||
|
|
||||||
services, err := cl.ServiceList(context.Background(), types.ServiceListOptions{Filters: filter})
|
|
||||||
if err != nil {
|
|
||||||
return labels, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, service := range services {
|
|
||||||
if service.Spec.Name != fmt.Sprintf("%s_app", stackName) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range service.Spec.Labels {
|
|
||||||
labels[k] = v
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return labels, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
AppLabelsCommand.Flags().BoolVarP(
|
|
||||||
&internal.Chaos,
|
|
||||||
"chaos",
|
|
||||||
"C",
|
|
||||||
false,
|
|
||||||
"ignore uncommitted recipes changes",
|
|
||||||
)
|
|
||||||
}
|
|
@ -142,14 +142,10 @@ Use "--status/-S" flag to query all servers for the live deployment status.`,
|
|||||||
appStats.AutoUpdate = autoUpdate
|
appStats.AutoUpdate = autoUpdate
|
||||||
|
|
||||||
var newUpdates []string
|
var newUpdates []string
|
||||||
if version != "unknown" && chaos == "false" {
|
if version != "unknown" {
|
||||||
if err := app.Recipe.EnsureExists(); err != nil {
|
|
||||||
log.Fatalf("unable to clone %s: %s", app.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
updates, err := app.Recipe.Tags()
|
updates, err := app.Recipe.Tags()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("unable to retrieve tags for %s: %s", app.Name, err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedVersion, err := tagcmp.Parse(version)
|
parsedVersion, err := tagcmp.Parse(version)
|
||||||
|
@ -3,19 +3,28 @@ package app
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"slices"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
"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"
|
||||||
"coopcloud.tech/abra/pkg/logs"
|
|
||||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
containerTypes "github.com/docker/docker/api/types/container"
|
||||||
|
"github.com/docker/docker/api/types/filters"
|
||||||
|
"github.com/docker/docker/api/types/swarm"
|
||||||
|
dockerClient "github.com/docker/docker/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var AppLogsCommand = &cobra.Command{
|
var AppLogsCommand = &cobra.Command{
|
||||||
Use: "logs <domain> [service] [flags]",
|
Use: "logs <app> [service] [flags]",
|
||||||
Aliases: []string{"l"},
|
Aliases: []string{"l"},
|
||||||
Short: "Tail app logs",
|
Short: "Tail app logs",
|
||||||
Args: cobra.RangeArgs(1, 2),
|
Args: cobra.RangeArgs(1, 2),
|
||||||
@ -64,25 +73,80 @@ var AppLogsCommand = &cobra.Command{
|
|||||||
serviceNames = []string{args[1]}
|
serviceNames = []string{args[1]}
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := app.Filters(true, false, serviceNames...)
|
if err = tailLogs(cl, app, serviceNames); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := logs.TailOpts{
|
|
||||||
AppName: app.Name,
|
|
||||||
Services: serviceNames,
|
|
||||||
StdErr: stdErr,
|
|
||||||
Since: sinceLogs,
|
|
||||||
Filters: f,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := logs.TailLogs(cl, opts); err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tailLogs prints logs for the given app with optional service names to be
|
||||||
|
// filtered on. It also checks if the latest task is not runnning and then
|
||||||
|
// prints the past tasks.
|
||||||
|
func tailLogs(cl *dockerClient.Client, app appPkg.App, serviceNames []string) error {
|
||||||
|
f, err := app.Filters(true, false, serviceNames...)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
services, err := cl.ServiceList(context.Background(), types.ServiceListOptions{Filters: f})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
for _, service := range services {
|
||||||
|
filters := filters.NewArgs()
|
||||||
|
filters.Add("name", service.Spec.Name)
|
||||||
|
tasks, err := cl.TaskList(context.Background(), types.TaskListOptions{Filters: f})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(tasks) > 0 {
|
||||||
|
// Need to sort the tasks by the CreatedAt field in the inverse order.
|
||||||
|
// Otherwise they are in the reversed order and not sorted properly.
|
||||||
|
slices.SortFunc[[]swarm.Task](tasks, func(t1, t2 swarm.Task) int {
|
||||||
|
return int(t2.Meta.CreatedAt.Unix() - t1.Meta.CreatedAt.Unix())
|
||||||
|
})
|
||||||
|
lastTask := tasks[0].Status
|
||||||
|
if lastTask.State != swarm.TaskStateRunning {
|
||||||
|
for _, task := range tasks {
|
||||||
|
log.Errorf("[%s] %s State %s: %s", service.Spec.Name, task.Meta.CreatedAt.Format(time.RFC3339), task.Status.State, task.Status.Err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Collect the logs in a go routine, so the logs from all services are
|
||||||
|
// collected in parallel.
|
||||||
|
wg.Add(1)
|
||||||
|
go func(serviceID string) {
|
||||||
|
logs, err := cl.ServiceLogs(context.Background(), serviceID, containerTypes.LogsOptions{
|
||||||
|
ShowStderr: true,
|
||||||
|
ShowStdout: !stdErr,
|
||||||
|
Since: sinceLogs,
|
||||||
|
Until: "",
|
||||||
|
Timestamps: true,
|
||||||
|
Follow: true,
|
||||||
|
Tail: "20",
|
||||||
|
Details: false,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer logs.Close()
|
||||||
|
|
||||||
|
_, err = io.Copy(os.Stdout, logs)
|
||||||
|
if err != nil && err != io.EOF {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}(service.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all log streams to be closed.
|
||||||
|
wg.Wait()
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
stdErr bool
|
stdErr bool
|
||||||
sinceLogs string
|
sinceLogs string
|
||||||
|
313
cli/app/move.go
313
cli/app/move.go
@ -1,313 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"os/exec"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
|
||||||
"coopcloud.tech/abra/pkg/app"
|
|
||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
|
||||||
"coopcloud.tech/abra/pkg/client"
|
|
||||||
containerPkg "coopcloud.tech/abra/pkg/container"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
|
||||||
"coopcloud.tech/abra/pkg/secret"
|
|
||||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
containertypes "github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
"github.com/docker/docker/api/types/mount"
|
|
||||||
"github.com/docker/docker/api/types/swarm"
|
|
||||||
"github.com/docker/docker/api/types/volume"
|
|
||||||
dockerclient "github.com/docker/docker/client"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
var AppMoveCommand = &cobra.Command{
|
|
||||||
Use: "move <domain> <server> [flags]",
|
|
||||||
Short: "Moves an app to a different server",
|
|
||||||
Long: `Move an app to a differnt server.
|
|
||||||
|
|
||||||
This will copy secrets and volumes from the old server to the new one. It will also undeploy the app from old server but not deploy it on the new. You will have to do that your self, after the move finished.
|
|
||||||
|
|
||||||
Use "--dry-run/-r" to see which secrets and volumes will be moved.`,
|
|
||||||
Example: ` # moving an app
|
|
||||||
abra app move nextcloud.example.com myserver.com`,
|
|
||||||
Args: cobra.RangeArgs(1, 2),
|
|
||||||
ValidArgsFunction: func(
|
|
||||||
cmd *cobra.Command,
|
|
||||||
args []string,
|
|
||||||
toComplete string,
|
|
||||||
) ([]string, cobra.ShellCompDirective) {
|
|
||||||
switch l := len(args); l {
|
|
||||||
case 0:
|
|
||||||
return autocomplete.AppNameComplete()
|
|
||||||
case 1:
|
|
||||||
return autocomplete.ServerNameComplete()
|
|
||||||
default:
|
|
||||||
return nil, cobra.ShellCompDirectiveDefault
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
app := internal.ValidateApp(args)
|
|
||||||
if len(args) <= 1 {
|
|
||||||
log.Fatal("no server provided")
|
|
||||||
}
|
|
||||||
newServer := args[1]
|
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resources, err := getAppResources(cl, app)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal.MoveOverview(app, newServer, resources.SecretNames(), resources.VolumeNames())
|
|
||||||
if err := internal.PromptProcced(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: wait timeout will be removed, until it actually is just set it to a high value.
|
|
||||||
stack.WaitTimeout = 500
|
|
||||||
rmOpts := stack.Remove{
|
|
||||||
Namespaces: []string{app.StackName()},
|
|
||||||
Detach: false,
|
|
||||||
}
|
|
||||||
if err := stack.RunRemove(context.Background(), cl, rmOpts); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cl2, err := client.New(newServer)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range resources.SecretList {
|
|
||||||
sname := strings.Split(strings.TrimPrefix(s.Spec.Name, app.StackName()+"_"), "_")
|
|
||||||
secretName := strings.Join(sname[:len(sname)-1], "_")
|
|
||||||
data := resources.Secrets[secretName]
|
|
||||||
if err := client.StoreSecret(cl2, s.Spec.Name, data); err != nil {
|
|
||||||
log.Infof("creating secret: %s", s.Spec.Name)
|
|
||||||
log.Errorf("failed to store secret on new server: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range resources.Volumes {
|
|
||||||
log.Infof("moving volume: %s", v.Name)
|
|
||||||
|
|
||||||
// Need to create the volume before copying the data, because when
|
|
||||||
// docker creates a new volume it set the folder permissions to
|
|
||||||
// root, which might be wrong. This ensures we always have the
|
|
||||||
// correct folder permissions inside the volume.
|
|
||||||
log.Debug("creating volume: %s", v.Name)
|
|
||||||
_, err := cl2.VolumeCreate(context.Background(), volume.CreateOptions{
|
|
||||||
Name: v.Name,
|
|
||||||
Driver: v.Driver,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to create volume: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fileName := fmt.Sprintf("%s.tar.gz", v.Name)
|
|
||||||
log.Debug("creating %s", fileName)
|
|
||||||
cmd := exec.Command("ssh", app.Server, "-tt", fmt.Sprintf("sudo tar --same-owner -czhpf %s -C /var/lib/docker/volumes %s", fileName, v.Name))
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
log.Errorf("failed to tar volume: %s", err)
|
|
||||||
fmt.Println(string(out))
|
|
||||||
}
|
|
||||||
log.Debug("copying %s to local machine", fileName)
|
|
||||||
cmd = exec.Command("scp", fmt.Sprintf("%s:%s", app.Server, fileName), fileName)
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
log.Errorf("failed to copy tar to local machine: %s", err)
|
|
||||||
fmt.Println(string(out))
|
|
||||||
}
|
|
||||||
log.Debug("copying %s to %s", fileName, newServer)
|
|
||||||
cmd = exec.Command("scp", fileName, fmt.Sprintf("%s:%s", newServer, fileName))
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
log.Errorf("failed to copy tar to new server: %s", err)
|
|
||||||
fmt.Println(string(out))
|
|
||||||
}
|
|
||||||
log.Debug("extracting %s on %s", fileName, newServer)
|
|
||||||
cmd = exec.Command("ssh", newServer, "-tt", fmt.Sprintf("sudo tar --same-owner -xzpf %s -C /var/lib/docker/volumes", fileName))
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
log.Errorf("failed to extract tar: %s", err)
|
|
||||||
fmt.Println(string(out))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove tar files
|
|
||||||
cmd = exec.Command("ssh", newServer, "-tt", fmt.Sprintf("sudo rm %s", fileName))
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
log.Errorf("failed to remove tar from new server: %s", err)
|
|
||||||
fmt.Println(string(out))
|
|
||||||
}
|
|
||||||
cmd = exec.Command("ssh", app.Server, "-tt", fmt.Sprintf("sudo rm %s", fileName))
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
log.Errorf("failed to remove tar from old server: %s", err)
|
|
||||||
fmt.Println(string(out))
|
|
||||||
}
|
|
||||||
cmd = exec.Command("rm", fileName)
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
log.Errorf("failed to remove tar on local machine: %s", err)
|
|
||||||
fmt.Println(string(out))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("moving app config to new server")
|
|
||||||
if err := copyFile(app.Path, strings.ReplaceAll(app.Path, app.Server, newServer)); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := os.Remove(app.Path); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
fmt.Println("% was succefully moved to %s", app.Name, newServer)
|
|
||||||
fmt.Println("Run the following command to deploy the app", app.Name, newServer)
|
|
||||||
fmt.Println(" abra app deploy --no-domain-checks", app.Domain)
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("And don't forget to update you DNS record. And don't panic, as it might take a bit for the dust to settle. Traefik for example might fail to obtain the lets encrypt certificate for a while.", app.Domain)
|
|
||||||
fmt.Println()
|
|
||||||
fmt.Println("If anything goes wrong, you can always move the app config file to the original server and deploy it there again. There was no data removed on the old server")
|
|
||||||
return
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
type AppResources struct {
|
|
||||||
Secrets map[string]string
|
|
||||||
SecretList []swarm.Secret
|
|
||||||
Volumes map[string]containertypes.MountPoint
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppResources) SecretNames() []string {
|
|
||||||
secrets := []string{}
|
|
||||||
for name := range a.Secrets {
|
|
||||||
secrets = append(secrets, name)
|
|
||||||
}
|
|
||||||
return secrets
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *AppResources) VolumeNames() []string {
|
|
||||||
volumes := []string{}
|
|
||||||
for name := range a.Volumes {
|
|
||||||
volumes = append(volumes, name)
|
|
||||||
}
|
|
||||||
return volumes
|
|
||||||
}
|
|
||||||
|
|
||||||
func getAppResources(cl *dockerclient.Client, app app.App) (*AppResources, error) {
|
|
||||||
filter, err := app.Filters(false, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
services, err := cl.ServiceList(context.Background(), types.ServiceListOptions{Filters: filter})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
secretList, err := cl.SecretList(context.Background(), types.SecretListOptions{Filters: filter})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
secretConfigs, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.StackName())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := stack.Deploy{Composefiles: composeFiles, Namespace: app.StackName()}
|
|
||||||
compose, err := appPkg.GetAppComposeConfig(app.Name, opts, app.Env)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
resources := &AppResources{
|
|
||||||
Secrets: make(map[string]string),
|
|
||||||
SecretList: secretList,
|
|
||||||
Volumes: make(map[string]containertypes.MountPoint),
|
|
||||||
}
|
|
||||||
for _, s := range services {
|
|
||||||
secretNames := map[string]string{}
|
|
||||||
for _, serviceCompose := range compose.Services {
|
|
||||||
if app.StackName()+"_"+serviceCompose.Name != s.Spec.Name {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, secret := range serviceCompose.Secrets {
|
|
||||||
for _, s := range secretList {
|
|
||||||
if s.Spec.Name == app.StackName()+"_"+secret.Source+"_"+secretConfigs[secret.Source].Version {
|
|
||||||
secretNames[secret.Source] = s.ID
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
f := filters.NewArgs()
|
|
||||||
f.Add("name", s.Spec.Name)
|
|
||||||
targetContainer, err := containerPkg.GetContainer(context.Background(), cl, f, true)
|
|
||||||
if err != nil {
|
|
||||||
log.Error(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
for _, m := range targetContainer.Mounts {
|
|
||||||
if m.Type == mount.TypeVolume {
|
|
||||||
resources.Volumes[m.Name] = m
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for secretName, secretID := range secretNames {
|
|
||||||
if _, ok := resources.Secrets[secretName]; ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
log.Debugf("extracting secret %s", secretName)
|
|
||||||
|
|
||||||
out, err := exec.Command("ssh", app.Server, "-tt", fmt.Sprintf("sudo cat /var/lib/docker/containers/%s/mounts/secrets/%s", targetContainer.ID, secretID)).Output()
|
|
||||||
if err != nil {
|
|
||||||
fmt.Println(string(out))
|
|
||||||
fmt.Println(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
resources.Secrets[secretName] = string(out)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return resources, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func copyFile(src string, dst string) error {
|
|
||||||
// Read all content of src to data, may cause OOM for a large file.
|
|
||||||
data, err := os.ReadFile(src)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
// Write data to dst
|
|
||||||
err = os.WriteFile(dst, data, 0o644)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
AppMoveCommand.Flags().BoolVarP(
|
|
||||||
&internal.Dry,
|
|
||||||
"dry-run",
|
|
||||||
"r",
|
|
||||||
false,
|
|
||||||
"report changes that would be made",
|
|
||||||
)
|
|
||||||
}
|
|
@ -25,7 +25,7 @@ This new app configuration is stored in your $ABRA_DIR directory under the
|
|||||||
appropriate server.
|
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 <app>" to do so.
|
||||||
|
|
||||||
You can see what recipes are available (i.e. values for the [recipe] argument)
|
You can see what recipes are available (i.e. values for the [recipe] argument)
|
||||||
by running "abra recipe ls".
|
by running "abra recipe ls".
|
||||||
@ -75,49 +75,42 @@ var AppNewCommand = &cobra.Command{
|
|||||||
|
|
||||||
chaosVersion := config.CHAOS_DEFAULT
|
chaosVersion := config.CHAOS_DEFAULT
|
||||||
if internal.Chaos {
|
if internal.Chaos {
|
||||||
var err error
|
|
||||||
chaosVersion, err = recipe.ChaosVersion()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
recipeVersion = chaosVersion
|
recipeVersion = chaosVersion
|
||||||
} else {
|
|
||||||
|
if !internal.Offline {
|
||||||
|
if err := recipe.EnsureUpToDate(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !internal.Chaos {
|
||||||
if err := recipe.EnsureIsClean(); err != nil {
|
if err := recipe.EnsureIsClean(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var recipeVersions recipePkg.RecipeVersions
|
var recipeVersions recipePkg.RecipeVersions
|
||||||
if recipeVersion == "" {
|
if recipeVersion == "" {
|
||||||
var err error
|
var err error
|
||||||
recipeVersions, _, err = recipe.GetRecipeVersions()
|
recipeVersions, err = recipe.GetRecipeVersions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(recipeVersions) > 0 {
|
||||||
|
latest := recipeVersions[len(recipeVersions)-1]
|
||||||
|
for tag := range latest {
|
||||||
|
recipeVersion = tag
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(recipeVersions) > 0 {
|
if _, err := recipe.EnsureVersion(recipeVersion); err != nil {
|
||||||
latest := recipeVersions[len(recipeVersions)-1]
|
log.Fatal(err)
|
||||||
for tag := range latest {
|
}
|
||||||
recipeVersion = tag
|
} else {
|
||||||
}
|
if err := recipe.EnsureLatest(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
if _, err := recipe.EnsureVersion(recipeVersion); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err := recipe.EnsureLatest(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if recipeVersion == "" {
|
|
||||||
head, err := recipe.Head()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("failed to retrieve latest commit for %s: %s", recipe.Name, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
recipeVersion = formatter.SmallSHA(head.String())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +187,7 @@ var AppNewCommand = &cobra.Command{
|
|||||||
newAppServer = "local"
|
newAppServer = "local"
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("%s created (version: %s)", appDomain, recipeVersion)
|
log.Infof("%s created successfully (version: %s, chaos: %s)", appDomain, recipeVersion, chaosVersion)
|
||||||
|
|
||||||
if len(appSecrets) > 0 {
|
if len(appSecrets) > 0 {
|
||||||
rows := [][]string{}
|
rows := [][]string{}
|
||||||
@ -208,8 +201,8 @@ var AppNewCommand = &cobra.Command{
|
|||||||
|
|
||||||
log.Warnf(
|
log.Warnf(
|
||||||
"secrets are %s shown again, please save them %s",
|
"secrets are %s shown again, please save them %s",
|
||||||
formatter.BoldUnderlineStyle.Render("NOT"),
|
formatter.BoldStyle.Render("NOT"),
|
||||||
formatter.BoldUnderlineStyle.Render("NOW"),
|
formatter.BoldStyle.Render("NOW"),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -218,8 +211,9 @@ var AppNewCommand = &cobra.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debugf("choosing %s as version to save to env file", recipeVersion)
|
||||||
if err := app.WriteRecipeVersion(recipeVersion, false); err != nil {
|
if err := app.WriteRecipeVersion(recipeVersion, false); err != nil {
|
||||||
log.Fatalf("writing recipe version failed: %s", err)
|
log.Fatalf("writing new recipe version in env file: %s", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -302,12 +296,6 @@ func ensureServerFlag() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(servers) == 1 {
|
|
||||||
newAppServer = servers[0]
|
|
||||||
log.Infof("single server detected, choosing %s automatically", newAppServer)
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if newAppServer == "" && !internal.NoInput {
|
if newAppServer == "" && !internal.NoInput {
|
||||||
prompt := &survey.Select{
|
prompt := &survey.Select{
|
||||||
Message: "Select app server:",
|
Message: "Select app server:",
|
||||||
|
@ -4,8 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
@ -24,9 +22,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var AppPsCommand = &cobra.Command{
|
var AppPsCommand = &cobra.Command{
|
||||||
Use: "ps <domain> [flags]",
|
Use: "ps <app> [flags]",
|
||||||
Aliases: []string{"p"},
|
Aliases: []string{"p"},
|
||||||
Short: "Check app deployment status",
|
Short: "Check app status",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -37,7 +35,7 @@ var AppPsCommand = &cobra.Command{
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,11 +57,9 @@ var AppPsCommand = &cobra.Command{
|
|||||||
statuses, err := appPkg.GetAppStatuses([]appPkg.App{app}, true)
|
statuses, err := appPkg.GetAppStatuses([]appPkg.App{app}, true)
|
||||||
if statusMeta, ok := statuses[app.StackName()]; ok {
|
if statusMeta, ok := statuses[app.StackName()]; ok {
|
||||||
if isChaos, exists := statusMeta["chaos"]; exists && isChaos == "true" {
|
if isChaos, exists := statusMeta["chaos"]; exists && isChaos == "true" {
|
||||||
if cVersion, exists := statusMeta["chaosVersion"]; exists {
|
chaosVersion, err = app.Recipe.ChaosVersion()
|
||||||
chaosVersion = cVersion
|
if err != nil {
|
||||||
if strings.HasSuffix(chaosVersion, config.DIRTY_DEFAULT) {
|
log.Fatal(err)
|
||||||
chaosVersion = formatter.BoldDirtyDefault(chaosVersion)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -92,14 +88,9 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
services := compose.Services
|
|
||||||
sort.Slice(services, func(i, j int) bool {
|
|
||||||
return services[i].Name < services[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
var rows [][]string
|
var rows [][]string
|
||||||
allContainerStats := make(map[string]map[string]string)
|
allContainerStats := make(map[string]map[string]string)
|
||||||
for _, service := range services {
|
for _, service := range compose.Services {
|
||||||
filters := filters.NewArgs()
|
filters := filters.NewArgs()
|
||||||
filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), service.Name))
|
filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), service.Name))
|
||||||
|
|
||||||
@ -149,10 +140,10 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
|
|||||||
|
|
||||||
row := []string{
|
row := []string{
|
||||||
containerStats["service"],
|
containerStats["service"],
|
||||||
containerStats["status"],
|
|
||||||
containerStats["image"],
|
containerStats["image"],
|
||||||
dVersion,
|
dVersion,
|
||||||
cVersion,
|
cVersion,
|
||||||
|
containerStats["status"],
|
||||||
}
|
}
|
||||||
|
|
||||||
rows = append(rows, row)
|
rows = append(rows, row)
|
||||||
@ -176,10 +167,10 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
|
|||||||
|
|
||||||
headers := []string{
|
headers := []string{
|
||||||
"SERVICE",
|
"SERVICE",
|
||||||
"STATUS",
|
|
||||||
"IMAGE",
|
"IMAGE",
|
||||||
"VERSION",
|
"VERSION",
|
||||||
"CHAOS",
|
"CHAOS",
|
||||||
|
"STATUS",
|
||||||
}
|
}
|
||||||
|
|
||||||
table.
|
table.
|
||||||
|
@ -9,14 +9,14 @@ 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"
|
||||||
"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/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var AppRemoveCommand = &cobra.Command{
|
var AppRemoveCommand = &cobra.Command{
|
||||||
Use: "remove <domain> [flags]",
|
Use: "remove <app> [flags]",
|
||||||
Aliases: []string{"rm"},
|
Aliases: []string{"rm"},
|
||||||
Short: "Remove all app data, locally and remotely",
|
Short: "Remove all app data, locally and remotely",
|
||||||
Long: `Remove everything related to an app which is already undeployed.
|
Long: `Remove everything related to an app which is already undeployed.
|
||||||
@ -78,22 +78,6 @@ flag.`,
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
configs, err := client.GetConfigs(cl, context.Background(), app.Server, fs)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
configNames := client.GetConfigNames(configs)
|
|
||||||
|
|
||||||
if len(configNames) > 0 {
|
|
||||||
if err := client.RemoveConfigs(cl, context.Background(), configNames, internal.Force); err != nil {
|
|
||||||
log.Fatalf("removing configs failed: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("%d config(s) removed successfully", len(configNames))
|
|
||||||
} else {
|
|
||||||
log.Info("no configs to remove")
|
|
||||||
}
|
|
||||||
|
|
||||||
secretList, err := cl.SecretList(context.Background(), types.SecretListOptions{Filters: fs})
|
secretList, err := cl.SecretList(context.Background(), types.SecretListOptions{Filters: fs})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -136,7 +120,7 @@ flag.`,
|
|||||||
log.Fatalf("removing volumes failed: %s", err)
|
log.Fatalf("removing volumes failed: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("%d volume(s) removed successfully", len(volumeNames))
|
log.Infof("%d volumes removed successfully", len(volumeNames))
|
||||||
} else {
|
} else {
|
||||||
log.Info("no volumes to remove")
|
log.Info("no volumes to remove")
|
||||||
}
|
}
|
||||||
|
@ -9,20 +9,18 @@ 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"
|
||||||
"coopcloud.tech/abra/pkg/ui"
|
|
||||||
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/docker/docker/api/types"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var AppRestartCommand = &cobra.Command{
|
var AppRestartCommand = &cobra.Command{
|
||||||
Use: "restart <domain> [[service] | --all-services] [flags]",
|
Use: "restart <app> [[service] | --all-services] [flags]",
|
||||||
Aliases: []string{"re"},
|
Aliases: []string{"re"},
|
||||||
Short: "Restart an app",
|
Short: "Restart an app",
|
||||||
Long: `This command restarts services within a deployed app.
|
Long: `This command restarts services within a deployed app.
|
||||||
|
|
||||||
Run "abra app ps <domain>" to see a list of service names.
|
Run "abra app ps <app>" to see a list of service names.
|
||||||
|
|
||||||
Pass "--all-services/-a" to restart all services.`,
|
Pass "--all-services/-a" to restart all services.`,
|
||||||
Example: ` # restart a single app service
|
Example: ` # restart a single app service
|
||||||
@ -50,7 +48,7 @@ Pass "--all-services/-a" to restart all services.`,
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
if err := app.Recipe.Ensure(false, false); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,36 +93,13 @@ Pass "--all-services/-a" to restart all services.`,
|
|||||||
for _, serviceName := range serviceNames {
|
for _, serviceName := range serviceNames {
|
||||||
stackServiceName := fmt.Sprintf("%s_%s", app.StackName(), serviceName)
|
stackServiceName := fmt.Sprintf("%s_%s", app.StackName(), serviceName)
|
||||||
|
|
||||||
service, _, err := cl.ServiceInspectWithRaw(
|
|
||||||
context.Background(),
|
|
||||||
stackServiceName,
|
|
||||||
types.ServiceInspectOptions{},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("attempting to scale %s to 0", stackServiceName)
|
log.Debugf("attempting to scale %s to 0", stackServiceName)
|
||||||
|
|
||||||
if err := upstream.RunServiceScale(context.Background(), cl, stackServiceName, 0); err != nil {
|
if err := upstream.RunServiceScale(context.Background(), cl, stackServiceName, 0); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
f, err := app.Filters(true, false, serviceName)
|
if err := stack.WaitOnService(context.Background(), cl, stackServiceName, app.Name); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
waitOpts := stack.WaitOpts{
|
|
||||||
Services: []ui.ServiceMeta{{Name: stackServiceName, ID: service.ID}},
|
|
||||||
AppName: app.Name,
|
|
||||||
ServerName: app.Server,
|
|
||||||
Filters: f,
|
|
||||||
NoLog: true,
|
|
||||||
Quiet: true,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := stack.WaitOnServices(cmd.Context(), cl, waitOpts); err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -135,7 +110,7 @@ Pass "--all-services/-a" to restart all services.`,
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := stack.WaitOnServices(cmd.Context(), cl, waitOpts); err != nil {
|
if err := stack.WaitOnService(context.Background(), cl, stackServiceName, app.Name); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,13 +123,6 @@ Pass "--all-services/-a" to restart all services.`,
|
|||||||
var allServices bool
|
var allServices bool
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
AppRestartCommand.Flags().BoolVarP(
|
|
||||||
&internal.Chaos,
|
|
||||||
"chaos",
|
|
||||||
"C",
|
|
||||||
false,
|
|
||||||
"ignore uncommitted recipes changes",
|
|
||||||
)
|
|
||||||
AppRestartCommand.Flags().BoolVarP(
|
AppRestartCommand.Flags().BoolVarP(
|
||||||
&allServices,
|
&allServices,
|
||||||
"all-services",
|
"all-services",
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var AppRestoreCommand = &cobra.Command{
|
var AppRestoreCommand = &cobra.Command{
|
||||||
Use: "restore <domain> [flags]",
|
Use: "restore <app> [flags]",
|
||||||
Aliases: []string{"rs"},
|
Aliases: []string{"rs"},
|
||||||
Short: "Restore a snapshot",
|
Short: "Restore a snapshot",
|
||||||
Long: `Snapshots are restored while apps are deployed.
|
Long: `Snapshots are restored while apps are deployed.
|
||||||
@ -28,7 +28,7 @@ Some restore scenarios may require service / app restarts.`,
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,14 +1,13 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/app"
|
|
||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/envfile"
|
"coopcloud.tech/abra/pkg/envfile"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
|
||||||
"coopcloud.tech/abra/pkg/lint"
|
"coopcloud.tech/abra/pkg/lint"
|
||||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
"coopcloud.tech/tagcmp"
|
"coopcloud.tech/tagcmp"
|
||||||
@ -21,23 +20,16 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var AppRollbackCommand = &cobra.Command{
|
var AppRollbackCommand = &cobra.Command{
|
||||||
Use: "rollback <domain> [version] [flags]",
|
Use: "rollback <app> [version] [flags]",
|
||||||
Aliases: []string{"rl"},
|
Aliases: []string{"rl"},
|
||||||
Short: "Roll an app back to a previous version",
|
Short: "Roll an app back to a previous version",
|
||||||
Long: `This command rolls an app back to a previous version.
|
Long: `This command rolls an app back to a previous version.
|
||||||
|
|
||||||
Unlike "abra app deploy", chaos operations are not supported here. Only recipe
|
Unlike "deploy", chaos operations are not supported here. Only recipe versions
|
||||||
versions are supported values for "[version]".
|
are supported values for "[<version>]".
|
||||||
|
|
||||||
It is possible to "--force/-f" an downgrade if you want to re-deploy a specific
|
A rollback can be destructive, please ensure you have a copy of your app data
|
||||||
version.
|
beforehand.`,
|
||||||
|
|
||||||
Only the deployed version is consulted when trying to determine what downgrades
|
|
||||||
are available. The live deployment version is the "source of truth" in this
|
|
||||||
case. The stored .env version is not consulted.
|
|
||||||
|
|
||||||
A downgrade can be destructive, please ensure you have a copy of your app data
|
|
||||||
beforehand. See "abra app backup" for more.`,
|
|
||||||
Example: ` # standard rollback
|
Example: ` # standard rollback
|
||||||
abra app rollback 1312.net
|
abra app rollback 1312.net
|
||||||
|
|
||||||
@ -63,15 +55,26 @@ beforehand. See "abra app backup" for more.`,
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
var (
|
var warnMessages []string
|
||||||
downgradeWarnMessages []string
|
|
||||||
chosenDowngrade string
|
|
||||||
availableDowngrades []string
|
|
||||||
)
|
|
||||||
|
|
||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
stackName := app.StackName()
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
var specificVersion string
|
||||||
|
if len(args) == 2 {
|
||||||
|
specificVersion = args[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
if specificVersion != "" {
|
||||||
|
log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion)
|
||||||
|
app.Recipe.Version = specificVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := lint.LintForErrors(app.Recipe); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,13 +83,15 @@ beforehand. See "abra app backup" for more.`,
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
deployMeta, err := ensureDeployed(cl, app)
|
log.Debugf("checking whether %s is already deployed", stackName)
|
||||||
|
|
||||||
|
deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := lint.LintForErrors(app.Recipe); err != nil {
|
if !deployMeta.IsDeployed {
|
||||||
log.Fatal(err)
|
log.Fatalf("%s is not deployed?", app.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
versions, err := app.Recipe.Tags()
|
versions, err := app.Recipe.Tags()
|
||||||
@ -94,56 +99,84 @@ beforehand. See "abra app backup" for more.`,
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE(d1): we've no idea what the live deployment version is, so every
|
var availableDowngrades []string
|
||||||
// possible downgrade can be shown. it's up to the user to make the choice
|
if deployMeta.Version == "unknown" {
|
||||||
if deployMeta.Version == config.UNKNOWN_DEFAULT {
|
|
||||||
availableDowngrades = versions
|
availableDowngrades = versions
|
||||||
|
warnMessages = append(warnMessages, fmt.Sprintf("failed to determine deployed version of %s", app.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) == 2 && args[1] != "" {
|
if specificVersion != "" {
|
||||||
chosenDowngrade = args[1]
|
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
||||||
|
|
||||||
if err := validateDowngradeVersionArg(chosenDowngrade, app, deployMeta); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
availableDowngrades = append(availableDowngrades, chosenDowngrade)
|
|
||||||
}
|
|
||||||
|
|
||||||
if deployMeta.Version != config.UNKNOWN_DEFAULT && chosenDowngrade == "" {
|
|
||||||
downgradeAvailable, err := ensureDowngradesAvailable(versions, &availableDowngrades, deployMeta)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatalf("'%s' is not a known version for %s", deployMeta.Version, app.Recipe.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !downgradeAvailable {
|
parsedSpecificVersion, err := tagcmp.Parse(specificVersion)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("'%s' is not a known version for %s", specificVersion, app.Recipe.Name)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedSpecificVersion.IsGreaterThan(parsedDeployedVersion) && !parsedSpecificVersion.Equals(parsedDeployedVersion) {
|
||||||
|
log.Fatalf("%s is not a downgrade for %s?", deployMeta.Version, specificVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedSpecificVersion.Equals(parsedDeployedVersion) && !internal.Force {
|
||||||
|
log.Fatalf("%s is not a downgrade for %s?", deployMeta.Version, specificVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
availableDowngrades = append(availableDowngrades, specificVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if deployMeta.Version != "unknown" && specificVersion == "" {
|
||||||
|
if deployMeta.IsChaos {
|
||||||
|
warnMessages = append(warnMessages, fmt.Sprintf("attempting to rollback a chaos deployment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, version := range versions {
|
||||||
|
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedVersion, err := tagcmp.Parse(version)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedVersion.IsLessThan(parsedDeployedVersion) && !(parsedVersion.Equals(parsedDeployedVersion)) {
|
||||||
|
availableDowngrades = append(availableDowngrades, version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(availableDowngrades) == 0 && !internal.Force {
|
||||||
log.Info("no available downgrades")
|
log.Info("no available downgrades")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.Force || internal.NoInput || chosenDowngrade != "" {
|
var chosenDowngrade string
|
||||||
if len(availableDowngrades) > 0 {
|
if len(availableDowngrades) > 0 {
|
||||||
|
if internal.Force || internal.NoInput || specificVersion != "" {
|
||||||
chosenDowngrade = availableDowngrades[len(availableDowngrades)-1]
|
chosenDowngrade = availableDowngrades[len(availableDowngrades)-1]
|
||||||
}
|
log.Debugf("choosing %s as version to downgrade to (--force/--no-input)", chosenDowngrade)
|
||||||
} else {
|
} else {
|
||||||
if err := chooseDowngrade(availableDowngrades, deployMeta, &chosenDowngrade); err != nil {
|
msg := fmt.Sprintf("please select a downgrade (version: %s):", deployMeta.Version)
|
||||||
log.Fatal(err)
|
if deployMeta.IsChaos {
|
||||||
}
|
msg = fmt.Sprintf("please select a downgrade (version: %s, chaosVersion: %s):", deployMeta.Version, deployMeta.ChaosVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.Force &&
|
prompt := &survey.Select{
|
||||||
chosenDowngrade == "" &&
|
Message: msg,
|
||||||
deployMeta.Version != config.UNKNOWN_DEFAULT {
|
Options: internal.SortVersionsDesc(availableDowngrades),
|
||||||
chosenDowngrade = deployMeta.Version
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if chosenDowngrade == "" {
|
if err := survey.AskOne(prompt, &chosenDowngrade); err != nil {
|
||||||
log.Fatal("unknown deployed version, unable to downgrade")
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("choosing %s as version to rollback", chosenDowngrade)
|
log.Debugf("choosing %s as version to rollback", chosenDowngrade)
|
||||||
|
|
||||||
if _, err := app.Recipe.EnsureVersion(chosenDowngrade); err != nil {
|
if _, err := app.Recipe.EnsureVersion(chosenDowngrade); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -161,7 +194,6 @@ beforehand. See "abra app backup" for more.`,
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stackName := app.StackName()
|
|
||||||
deployOpts := stack.Deploy{
|
deployOpts := stack.Deploy{
|
||||||
Composefiles: composeFiles,
|
Composefiles: composeFiles,
|
||||||
Namespace: stackName,
|
Namespace: stackName,
|
||||||
@ -178,143 +210,36 @@ beforehand. See "abra app backup" for more.`,
|
|||||||
appPkg.ExposeAllEnv(stackName, compose, app.Env)
|
appPkg.ExposeAllEnv(stackName, compose, app.Env)
|
||||||
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
|
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
|
||||||
appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
|
appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
|
||||||
if internal.Chaos {
|
appPkg.SetChaosVersionLabel(compose, stackName, chosenDowngrade)
|
||||||
appPkg.SetChaosVersionLabel(compose, stackName, chosenDowngrade)
|
|
||||||
}
|
|
||||||
appPkg.SetUpdateLabel(compose, stackName, app.Env)
|
appPkg.SetUpdateLabel(compose, stackName, app.Env)
|
||||||
|
|
||||||
|
chaosVersion := config.CHAOS_DEFAULT
|
||||||
|
if deployMeta.IsChaos {
|
||||||
|
chaosVersion = deployMeta.ChaosVersion
|
||||||
|
}
|
||||||
|
|
||||||
// NOTE(d1): no release notes implemeneted for rolling back
|
// NOTE(d1): no release notes implemeneted for rolling back
|
||||||
if err := internal.DeployOverview(
|
if err := internal.NewVersionOverview(
|
||||||
app,
|
app,
|
||||||
deployMeta.Version,
|
warnMessages,
|
||||||
chosenDowngrade,
|
"rollback",
|
||||||
"",
|
|
||||||
downgradeWarnMessages,
|
|
||||||
); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
stack.WaitTimeout, err = appPkg.GetTimeoutFromLabel(compose, stackName)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("set waiting timeout to %d second(s)", stack.WaitTimeout)
|
|
||||||
|
|
||||||
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := app.Filters(true, false, serviceNames...)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := stack.RunDeploy(
|
|
||||||
cl,
|
|
||||||
deployOpts,
|
|
||||||
compose,
|
|
||||||
stackName,
|
|
||||||
app.Server,
|
|
||||||
internal.DontWaitConverge,
|
|
||||||
f,
|
|
||||||
); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := app.WriteRecipeVersion(chosenDowngrade, false); err != nil {
|
|
||||||
log.Fatalf("writing recipe version failed: %s", err)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// chooseDowngrade prompts the user to choose an downgrade interactively.
|
|
||||||
func chooseDowngrade(
|
|
||||||
availableDowngrades []string,
|
|
||||||
deployMeta stack.DeployMeta,
|
|
||||||
chosenDowngrade *string,
|
|
||||||
) error {
|
|
||||||
msg := fmt.Sprintf("please select a downgrade (version: %s):", deployMeta.Version)
|
|
||||||
|
|
||||||
if deployMeta.IsChaos {
|
|
||||||
chaosVersion := formatter.BoldDirtyDefault(deployMeta.ChaosVersion)
|
|
||||||
|
|
||||||
msg = fmt.Sprintf(
|
|
||||||
"please select a downgrade (version: %s, chaos: %s):",
|
|
||||||
deployMeta.Version,
|
deployMeta.Version,
|
||||||
chaosVersion,
|
chaosVersion,
|
||||||
)
|
chosenDowngrade,
|
||||||
}
|
""); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
prompt := &survey.Select{
|
|
||||||
Message: msg,
|
|
||||||
Options: internal.SortVersionsDesc(availableDowngrades),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := survey.AskOne(prompt, chosenDowngrade); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateDownpgradeVersionArg validates the specific version.
|
|
||||||
func validateDowngradeVersionArg(
|
|
||||||
specificVersion string,
|
|
||||||
app app.App,
|
|
||||||
deployMeta stack.DeployMeta,
|
|
||||||
) error {
|
|
||||||
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("current deployment '%s' is not a known version for %s", deployMeta.Version, app.Recipe.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedSpecificVersion, err := tagcmp.Parse(specificVersion)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("'%s' is not a known version for %s", specificVersion, app.Recipe.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if parsedSpecificVersion.IsGreaterThan(parsedDeployedVersion) &&
|
|
||||||
!parsedSpecificVersion.Equals(parsedDeployedVersion) {
|
|
||||||
return fmt.Errorf("%s is not a downgrade for %s?", deployMeta.Version, specificVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
if parsedSpecificVersion.Equals(parsedDeployedVersion) && !internal.Force {
|
|
||||||
return fmt.Errorf("%s is not a downgrade for %s?", deployMeta.Version, specificVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensureDowngradesAvailable ensures that there are available downgrades.
|
|
||||||
func ensureDowngradesAvailable(
|
|
||||||
versions []string,
|
|
||||||
availableDowngrades *[]string,
|
|
||||||
deployMeta stack.DeployMeta,
|
|
||||||
) (bool, error) {
|
|
||||||
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, version := range versions {
|
|
||||||
parsedVersion, err := tagcmp.Parse(version)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if parsedVersion.IsLessThan(parsedDeployedVersion) &&
|
if err := stack.RunDeploy(cl, deployOpts, compose, stackName, internal.DontWaitConverge); err != nil {
|
||||||
!(parsedVersion.Equals(parsedDeployedVersion)) {
|
log.Fatal(err)
|
||||||
*availableDowngrades = append(*availableDowngrades, version)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if len(*availableDowngrades) == 0 && !internal.Force {
|
app.Recipe.Version = chosenDowngrade
|
||||||
return false, nil
|
log.Debugf("choosing %s as version to save to env file", app.Recipe.Version)
|
||||||
}
|
if err := app.WriteRecipeVersion(app.Recipe.Version, false); err != nil {
|
||||||
|
log.Fatalf("writing new recipe version in env file: %s", err)
|
||||||
return true, nil
|
}
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -338,6 +263,6 @@ func init() {
|
|||||||
&internal.DontWaitConverge, "no-converge-checks",
|
&internal.DontWaitConverge, "no-converge-checks",
|
||||||
"c",
|
"c",
|
||||||
false,
|
false,
|
||||||
"disable converge logic checks",
|
"do not wait for converge logic checks",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -11,13 +11,13 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/upstream/container"
|
"coopcloud.tech/abra/pkg/upstream/container"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
containertypes "github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var AppRunCommand = &cobra.Command{
|
var AppRunCommand = &cobra.Command{
|
||||||
Use: "run <domain> <service> <cmd> [[args] [flags] | [flags] -- [args]]",
|
Use: "run <app> <service> <cmd> [[args] [flags] | [flags] -- [args]]",
|
||||||
Aliases: []string{"r"},
|
Aliases: []string{"r"},
|
||||||
Short: "Run a command inside a service container",
|
Short: "Run a command inside a service container",
|
||||||
Example: ` # run <cmd> with args/flags
|
Example: ` # run <cmd> with args/flags
|
||||||
@ -64,7 +64,7 @@ var AppRunCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
userCmd := args[2:]
|
userCmd := args[2:]
|
||||||
execCreateOpts := containertypes.ExecOptions{
|
execCreateOpts := types.ExecConfig{
|
||||||
AttachStderr: true,
|
AttachStderr: true,
|
||||||
AttachStdin: true,
|
AttachStdin: true,
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
|
@ -20,7 +20,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var AppSecretGenerateCommand = &cobra.Command{
|
var AppSecretGenerateCommand = &cobra.Command{
|
||||||
Use: "generate <domain> [[secret] [version] | --all] [flags]",
|
Use: "generate <app> [[secret] [version] | --all] [flags]",
|
||||||
Aliases: []string{"g"},
|
Aliases: []string{"g"},
|
||||||
Short: "Generate secrets",
|
Short: "Generate secrets",
|
||||||
Args: cobra.RangeArgs(1, 3),
|
Args: cobra.RangeArgs(1, 3),
|
||||||
@ -45,15 +45,15 @@ var AppSecretGenerateCommand = &cobra.Command{
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) <= 2 && !generateAllSecrets {
|
if len(args) == 1 && !generateAllSecrets {
|
||||||
log.Fatal("missing arguments [secret]/[version] or '--all'")
|
log.Fatal("missing arguments [secret]/[version] or '--all'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) > 2 && generateAllSecrets {
|
if len(args) > 1 && generateAllSecrets {
|
||||||
log.Fatal("cannot use '[secret] [version]' and '--all' together")
|
log.Fatal("cannot use '[secret] [version]' and '--all' together")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,22 +140,14 @@ var AppSecretGenerateCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var AppSecretInsertCommand = &cobra.Command{
|
var AppSecretInsertCommand = &cobra.Command{
|
||||||
Use: "insert <domain> <secret> <version> <data> [flags]",
|
Use: "insert <app> <secret> <version> <data> [flags]",
|
||||||
Aliases: []string{"i"},
|
Aliases: []string{"i"},
|
||||||
Short: "Insert secret",
|
Short: "Insert secret",
|
||||||
Long: `This command inserts a secret into an app environment.
|
Long: `This command inserts a secret into an app environment.
|
||||||
|
|
||||||
Arbitrary secret insertion is not supported. Secrets that are inserted must
|
|
||||||
match those configured in the recipe beforehand.
|
|
||||||
|
|
||||||
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/-S" for more).`,
|
(see "abra app new --secrets/-S" for more).`,
|
||||||
Example: ` # insert regular secret
|
|
||||||
abra app secret insert 1312.net my_secret v1 mySuperSecret
|
|
||||||
|
|
||||||
# insert secret as file
|
|
||||||
abra app secret insert 1312.net my_secret v1 secret.txt -f`,
|
|
||||||
Args: cobra.MinimumNArgs(4),
|
Args: cobra.MinimumNArgs(4),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -178,7 +170,7 @@ environment. Typically, you can let Abra generate them for you on app creation
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -191,26 +183,6 @@ environment. Typically, you can let Abra generate them for you on app creation
|
|||||||
version := args[2]
|
version := args[2]
|
||||||
data := args[3]
|
data := args[3]
|
||||||
|
|
||||||
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
secrets, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.StackName())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var isRecipeSecret bool
|
|
||||||
for secretName := range secrets {
|
|
||||||
if secretName == name {
|
|
||||||
isRecipeSecret = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !isRecipeSecret {
|
|
||||||
log.Fatalf("no secret %s available for recipe %s?", name, app.Recipe.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if insertFromFile {
|
if insertFromFile {
|
||||||
raw, err := os.ReadFile(data)
|
raw, err := os.ReadFile(data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -224,7 +196,7 @@ environment. Typically, you can let Abra generate them for you on app creation
|
|||||||
}
|
}
|
||||||
|
|
||||||
secretName := fmt.Sprintf("%s_%s_%s", app.StackName(), name, version)
|
secretName := fmt.Sprintf("%s_%s_%s", app.StackName(), name, version)
|
||||||
if err := client.StoreSecret(cl, secretName, data); err != nil {
|
if err := client.StoreSecret(cl, secretName, data, app.Server); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -258,14 +230,9 @@ func secretRm(cl *dockerClient.Client, app appPkg.App, secretName, parsed string
|
|||||||
}
|
}
|
||||||
|
|
||||||
var AppSecretRmCommand = &cobra.Command{
|
var AppSecretRmCommand = &cobra.Command{
|
||||||
Use: "remove <domain> [[secret] | --all] [flags]",
|
Use: "remove <app> [[secret] | --all] [flags]",
|
||||||
Aliases: []string{"rm"},
|
Aliases: []string{"rm"},
|
||||||
Short: "Remove a secret",
|
Short: "Remove a secret",
|
||||||
Long: `This command removes a secret from an app environment.
|
|
||||||
|
|
||||||
Arbitrary secret removal is not supported. Secrets that are removed must
|
|
||||||
match those configured in the recipe beforehand.`,
|
|
||||||
Example: " abra app secret rm 1312.net oauth_key",
|
|
||||||
Args: cobra.RangeArgs(1, 2),
|
Args: cobra.RangeArgs(1, 2),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -291,7 +258,7 @@ match those configured in the recipe beforehand.`,
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -371,7 +338,7 @@ match those configured in the recipe beforehand.`,
|
|||||||
}
|
}
|
||||||
|
|
||||||
var AppSecretLsCommand = &cobra.Command{
|
var AppSecretLsCommand = &cobra.Command{
|
||||||
Use: "list <domain>",
|
Use: "list <app>",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Short: "List all secrets",
|
Short: "List all secrets",
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
@ -384,7 +351,7 @@ var AppSecretLsCommand = &cobra.Command{
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var AppServicesCommand = &cobra.Command{
|
var AppServicesCommand = &cobra.Command{
|
||||||
Use: "services <domain> [flags]",
|
Use: "services <app> [flags]",
|
||||||
Aliases: []string{"sr"},
|
Aliases: []string{"sr"},
|
||||||
Short: "Display all services of an app",
|
Short: "Display all services of an app",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
@ -30,7 +30,7 @@ var AppServicesCommand = &cobra.Command{
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var AppUndeployCommand = &cobra.Command{
|
var AppUndeployCommand = &cobra.Command{
|
||||||
Use: "undeploy <domain> [flags]",
|
Use: "undeploy <app> [flags]",
|
||||||
Aliases: []string{"un"},
|
Aliases: []string{"un"},
|
||||||
Short: "Undeploy an app",
|
Short: "Undeploy an app",
|
||||||
Long: `This does not destroy any application data.
|
Long: `This does not destroy any application data.
|
||||||
@ -38,10 +38,6 @@ Passing "--prune/-p" does not remove those volumes.`,
|
|||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
stackName := app.StackName()
|
stackName := app.StackName()
|
||||||
|
|
||||||
if err := app.Recipe.EnsureExists(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
cl, err := client.New(app.Server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -58,36 +54,20 @@ Passing "--prune/-p" does not remove those volumes.`,
|
|||||||
log.Fatalf("%s is not deployed?", app.Name)
|
log.Fatalf("%s is not deployed?", app.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := internal.DeployOverview(
|
chaosVersion := config.CHAOS_DEFAULT
|
||||||
|
if deployMeta.IsChaos {
|
||||||
|
chaosVersion = deployMeta.ChaosVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := internal.UndeployOverview(
|
||||||
app,
|
app,
|
||||||
deployMeta.Version,
|
deployMeta.Version,
|
||||||
config.NO_DOMAIN_DEFAULT,
|
chaosVersion); err != nil {
|
||||||
"",
|
|
||||||
nil,
|
|
||||||
); err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := stack.Deploy{Composefiles: composeFiles, Namespace: stackName}
|
|
||||||
compose, err := appPkg.GetAppComposeConfig(app.Name, opts, app.Env)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
stack.WaitTimeout, err = appPkg.GetTimeoutFromLabel(compose, stackName)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info("initialising undeploy")
|
|
||||||
|
|
||||||
rmOpts := stack.Remove{
|
rmOpts := stack.Remove{
|
||||||
Namespaces: []string{stackName},
|
Namespaces: []string{app.StackName()},
|
||||||
Detach: false,
|
Detach: false,
|
||||||
}
|
}
|
||||||
if err := stack.RunRemove(context.Background(), cl, rmOpts); err != nil {
|
if err := stack.RunRemove(context.Background(), cl, rmOpts); err != nil {
|
||||||
@ -100,10 +80,9 @@ Passing "--prune/-p" does not remove those volumes.`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info("undeploy succeeded 🟢")
|
log.Debugf("choosing %s as version to save to env file", deployMeta.Version)
|
||||||
|
|
||||||
if err := app.WriteRecipeVersion(deployMeta.Version, false); err != nil {
|
if err := app.WriteRecipeVersion(deployMeta.Version, false); err != nil {
|
||||||
log.Fatalf("writing recipe version failed: %s", err)
|
log.Fatalf("writing undeployed recipe version in env file: %s", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -3,50 +3,37 @@ package app
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/app"
|
|
||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/envfile"
|
"coopcloud.tech/abra/pkg/envfile"
|
||||||
"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"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
|
||||||
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"
|
||||||
dockerClient "github.com/docker/docker/client"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var AppUpgradeCommand = &cobra.Command{
|
var AppUpgradeCommand = &cobra.Command{
|
||||||
Use: "upgrade <domain> [version] [flags]",
|
Use: "upgrade <app> [version] [flags]",
|
||||||
Aliases: []string{"up"},
|
Aliases: []string{"up"},
|
||||||
Short: "Upgrade an app",
|
Short: "Upgrade an app",
|
||||||
Long: `Upgrade an app.
|
Long: `Upgrade an app.
|
||||||
|
|
||||||
Unlike "abra app deploy", chaos operations are not supported here. Only recipe
|
Unlike "deploy", chaos operations are not supported here. Only recipe versions
|
||||||
versions are supported values for "[version]".
|
are supported values for "[version]".
|
||||||
|
|
||||||
It is possible to "--force/-f" an upgrade if you want to re-deploy a specific
|
|
||||||
version.
|
|
||||||
|
|
||||||
Only the deployed version is consulted when trying to determine what upgrades
|
|
||||||
are available. The live deployment version is the "source of truth" in this
|
|
||||||
case. The stored .env version is not consulted.
|
|
||||||
|
|
||||||
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. See "abra app backup" for more.`,
|
beforehand.`,
|
||||||
Args: cobra.RangeArgs(1, 2),
|
Args: cobra.RangeArgs(1, 2),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
args []string,
|
args []string,
|
||||||
toComplete string,
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
) ([]string, cobra.ShellCompDirective) {
|
|
||||||
switch l := len(args); l {
|
switch l := len(args); l {
|
||||||
case 0:
|
case 0:
|
||||||
return autocomplete.AppNameComplete()
|
return autocomplete.AppNameComplete()
|
||||||
@ -62,32 +49,22 @@ beforehand. See "abra app backup" for more.`,
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
var (
|
var warnMessages []string
|
||||||
upgradeWarnMessages []string
|
|
||||||
chosenUpgrade string
|
|
||||||
availableUpgrades []string
|
|
||||||
upgradeReleaseNotes string
|
|
||||||
)
|
|
||||||
|
|
||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
stackName := app.StackName()
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(recipe.EnsureContext{
|
var specificVersion string
|
||||||
Chaos: internal.Chaos,
|
if len(args) == 2 {
|
||||||
Offline: internal.Offline,
|
specificVersion = args[1]
|
||||||
// Ignore the env version for now, to make sure we are at the latest commit.
|
|
||||||
// This enables us to get release notes, that were added after a release.
|
|
||||||
IgnoreEnvVersion: true,
|
|
||||||
}); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
if specificVersion != "" {
|
||||||
if err != nil {
|
log.Debugf("overriding env file version (%s) with %s", app.Recipe.Version, specificVersion)
|
||||||
log.Fatal(err)
|
app.Recipe.Version = specificVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
deployMeta, err := ensureDeployed(cl, app)
|
if err := app.Recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -95,68 +72,134 @@ beforehand. See "abra app backup" for more.`,
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debugf("checking whether %s is already deployed", stackName)
|
||||||
|
|
||||||
|
cl, err := client.New(app.Server)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !deployMeta.IsDeployed {
|
||||||
|
log.Fatalf("%s is not deployed?", app.Name)
|
||||||
|
}
|
||||||
|
|
||||||
versions, err := app.Recipe.Tags()
|
versions, err := app.Recipe.Tags()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE(d1): we've no idea what the live deployment version is, so every
|
var availableUpgrades []string
|
||||||
// possible upgrade can be shown. it's up to the user to make the choice
|
if deployMeta.Version == "unknown" {
|
||||||
if deployMeta.Version == config.UNKNOWN_DEFAULT {
|
|
||||||
availableUpgrades = versions
|
availableUpgrades = versions
|
||||||
|
warnMessages = append(warnMessages, fmt.Sprintf("failed to determine deployed version of %s", app.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) == 2 && args[1] != "" {
|
if specificVersion != "" {
|
||||||
chosenUpgrade = args[1]
|
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
||||||
|
|
||||||
if err := validateUpgradeVersionArg(chosenUpgrade, app, deployMeta); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
availableUpgrades = append(availableUpgrades, chosenUpgrade)
|
|
||||||
}
|
|
||||||
|
|
||||||
if deployMeta.Version != config.UNKNOWN_DEFAULT && chosenUpgrade == "" {
|
|
||||||
upgradeAvailable, err := ensureUpgradesAvailable(app, versions, &availableUpgrades, deployMeta)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatalf("'%s' is not a known version for %s", deployMeta.Version, app.Recipe.Name)
|
||||||
|
}
|
||||||
|
parsedSpecificVersion, err := tagcmp.Parse(specificVersion)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("'%s' is not a known version for %s", specificVersion, app.Recipe.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !upgradeAvailable {
|
if parsedSpecificVersion.IsLessThan(parsedDeployedVersion) && !parsedSpecificVersion.Equals(parsedDeployedVersion) {
|
||||||
|
log.Fatalf("%s is not an upgrade for %s?", deployMeta.Version, specificVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedSpecificVersion.Equals(parsedDeployedVersion) && !internal.Force {
|
||||||
|
log.Fatalf("%s is not an upgrade for %s?", deployMeta.Version, specificVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
availableUpgrades = append(availableUpgrades, specificVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if deployMeta.Version != "unknown" && specificVersion == "" {
|
||||||
|
if deployMeta.IsChaos {
|
||||||
|
warnMessages = append(warnMessages, fmt.Sprintf("attempting to upgrade a chaos deployment"))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, version := range versions {
|
||||||
|
parsedVersion, err := tagcmp.Parse(version)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if parsedVersion.IsGreaterThan(parsedDeployedVersion) && !(parsedVersion.Equals(parsedDeployedVersion)) {
|
||||||
|
availableUpgrades = append(availableUpgrades, version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(availableUpgrades) == 0 && !internal.Force {
|
||||||
log.Info("no available upgrades")
|
log.Info("no available upgrades")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.Force || internal.NoInput || chosenUpgrade != "" {
|
var chosenUpgrade string
|
||||||
if len(availableUpgrades) > 0 {
|
if len(availableUpgrades) > 0 {
|
||||||
|
if internal.Force || internal.NoInput || specificVersion != "" {
|
||||||
chosenUpgrade = availableUpgrades[len(availableUpgrades)-1]
|
chosenUpgrade = availableUpgrades[len(availableUpgrades)-1]
|
||||||
}
|
log.Debugf("choosing %s as version to upgrade to", chosenUpgrade)
|
||||||
} else {
|
} else {
|
||||||
if err := chooseUpgrade(availableUpgrades, deployMeta, &chosenUpgrade); err != nil {
|
msg := fmt.Sprintf("please select an upgrade (version: %s):", deployMeta.Version)
|
||||||
log.Fatal(err)
|
if deployMeta.IsChaos {
|
||||||
|
msg = fmt.Sprintf("please select an upgrade (version: %s, chaosVersion: %s):", deployMeta.Version, deployMeta.ChaosVersion)
|
||||||
|
}
|
||||||
|
|
||||||
|
prompt := &survey.Select{
|
||||||
|
Message: msg,
|
||||||
|
Options: internal.SortVersionsDesc(availableUpgrades),
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := survey.AskOne(prompt, &chosenUpgrade); err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.Force &&
|
if internal.Force && chosenUpgrade == "" {
|
||||||
chosenUpgrade == "" &&
|
warnMessages = append(warnMessages, fmt.Sprintf("%s is already upgraded to latest", app.Name))
|
||||||
deployMeta.Version != config.UNKNOWN_DEFAULT {
|
|
||||||
chosenUpgrade = deployMeta.Version
|
chosenUpgrade = deployMeta.Version
|
||||||
}
|
}
|
||||||
|
|
||||||
if chosenUpgrade == "" {
|
// if release notes written after git tag published, read them before we
|
||||||
log.Fatal("unknown deployed version, unable to upgrade")
|
// check out the tag and then they'll appear to be missing. this covers
|
||||||
|
// when we obviously will forget to write release notes before publishing
|
||||||
|
var releaseNotes string
|
||||||
|
if chosenUpgrade != "" {
|
||||||
|
parsedChosenUpgrade, err := tagcmp.Parse(chosenUpgrade)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
for _, version := range versions {
|
||||||
|
parsedVersion, err := tagcmp.Parse(version)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if parsedVersion.IsGreaterThan(parsedDeployedVersion) && parsedVersion.IsLessThan(parsedChosenUpgrade) {
|
||||||
|
note, err := app.Recipe.GetReleaseNotes(version)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
if note != "" {
|
||||||
|
releaseNotes += fmt.Sprintf("%s\n", note)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("choosing %s as version to upgrade", chosenUpgrade)
|
log.Debugf("choosing %s as version to upgrade", chosenUpgrade)
|
||||||
|
|
||||||
// Get the release notes before checking out the new version in the
|
|
||||||
// recipe. This enables us to get release notes, that were added after
|
|
||||||
// a release.
|
|
||||||
if err := getReleaseNotes(app, versions, chosenUpgrade, deployMeta, &upgradeReleaseNotes); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := app.Recipe.EnsureVersion(chosenUpgrade); err != nil {
|
if _, err := app.Recipe.EnsureVersion(chosenUpgrade); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -174,7 +217,6 @@ beforehand. See "abra app backup" for more.`,
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
stackName := app.StackName()
|
|
||||||
deployOpts := stack.Deploy{
|
deployOpts := stack.Deploy{
|
||||||
Composefiles: composeFiles,
|
Composefiles: composeFiles,
|
||||||
Namespace: stackName,
|
Namespace: stackName,
|
||||||
@ -191,9 +233,7 @@ beforehand. See "abra app backup" for more.`,
|
|||||||
appPkg.ExposeAllEnv(stackName, compose, app.Env)
|
appPkg.ExposeAllEnv(stackName, compose, app.Env)
|
||||||
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
|
appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name)
|
||||||
appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
|
appPkg.SetChaosLabel(compose, stackName, internal.Chaos)
|
||||||
if internal.Chaos {
|
appPkg.SetChaosVersionLabel(compose, stackName, chosenUpgrade)
|
||||||
appPkg.SetChaosVersionLabel(compose, stackName, chosenUpgrade)
|
|
||||||
}
|
|
||||||
appPkg.SetUpdateLabel(compose, stackName, app.Env)
|
appPkg.SetUpdateLabel(compose, stackName, app.Env)
|
||||||
|
|
||||||
envVars, err := appPkg.CheckEnv(app)
|
envVars, err := appPkg.CheckEnv(app)
|
||||||
@ -203,31 +243,30 @@ beforehand. See "abra app backup" for more.`,
|
|||||||
|
|
||||||
for _, envVar := range envVars {
|
for _, envVar := range envVars {
|
||||||
if !envVar.Present {
|
if !envVar.Present {
|
||||||
upgradeWarnMessages = append(upgradeWarnMessages,
|
warnMessages = append(warnMessages,
|
||||||
fmt.Sprintf("%s missing from %s.env", envVar.Name, app.Domain),
|
fmt.Sprintf("env var %s missing from %s.env, present in recipe .env.sample", envVar.Name, app.Domain),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if showReleaseNotes {
|
if showReleaseNotes {
|
||||||
fmt.Print(upgradeReleaseNotes)
|
fmt.Print(releaseNotes)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if upgradeReleaseNotes == "" {
|
chaosVersion := config.CHAOS_DEFAULT
|
||||||
upgradeWarnMessages = append(
|
if deployMeta.IsChaos {
|
||||||
upgradeWarnMessages,
|
chaosVersion = deployMeta.ChaosVersion
|
||||||
fmt.Sprintf("no release notes available for %s", chosenUpgrade),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := internal.DeployOverview(
|
if err := internal.NewVersionOverview(
|
||||||
app,
|
app,
|
||||||
|
warnMessages,
|
||||||
|
"upgrade",
|
||||||
deployMeta.Version,
|
deployMeta.Version,
|
||||||
|
chaosVersion,
|
||||||
chosenUpgrade,
|
chosenUpgrade,
|
||||||
upgradeReleaseNotes,
|
releaseNotes); err != nil {
|
||||||
upgradeWarnMessages,
|
|
||||||
); err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -235,197 +274,31 @@ beforehand. See "abra app backup" for more.`,
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
log.Debugf("set waiting timeout to %d s", stack.WaitTimeout)
|
||||||
|
|
||||||
log.Debugf("set waiting timeout to %d second(s)", stack.WaitTimeout)
|
if err := stack.RunDeploy(cl, deployOpts, compose, stackName, internal.DontWaitConverge); err != nil {
|
||||||
|
|
||||||
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := app.Filters(true, false, serviceNames...)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := stack.RunDeploy(
|
|
||||||
cl,
|
|
||||||
deployOpts,
|
|
||||||
compose,
|
|
||||||
stackName,
|
|
||||||
app.Server,
|
|
||||||
internal.DontWaitConverge,
|
|
||||||
f,
|
|
||||||
); err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
postDeployCmds, ok := app.Env["POST_UPGRADE_CMDS"]
|
postDeployCmds, ok := app.Env["POST_UPGRADE_CMDS"]
|
||||||
if ok && !internal.DontWaitConverge {
|
if ok && !internal.DontWaitConverge {
|
||||||
log.Debugf("run the following post-deploy commands: %s", postDeployCmds)
|
log.Debugf("run the following post-deploy commands: %s", postDeployCmds)
|
||||||
|
|
||||||
if err := internal.PostCmds(cl, app, postDeployCmds); err != nil {
|
if err := internal.PostCmds(cl, app, postDeployCmds); err != nil {
|
||||||
log.Fatalf("attempting to run post deploy commands, saw: %s", err)
|
log.Fatalf("attempting to run post deploy commands, saw: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.WriteRecipeVersion(chosenUpgrade, false); err != nil {
|
app.Recipe.Version = chosenUpgrade
|
||||||
log.Fatalf("writing recipe version failed: %s", err)
|
log.Debugf("choosing %s as version to save to env file", app.Recipe.Version)
|
||||||
|
if err := app.WriteRecipeVersion(app.Recipe.Version, false); err != nil {
|
||||||
|
log.Fatalf("writing new recipe version in env file: %s", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// chooseUpgrade prompts the user to choose an upgrade interactively.
|
var (
|
||||||
func chooseUpgrade(
|
showReleaseNotes bool
|
||||||
availableUpgrades []string,
|
)
|
||||||
deployMeta stack.DeployMeta,
|
|
||||||
chosenUpgrade *string,
|
|
||||||
) error {
|
|
||||||
msg := fmt.Sprintf("please select an upgrade (version: %s):", deployMeta.Version)
|
|
||||||
|
|
||||||
if deployMeta.IsChaos {
|
|
||||||
chaosVersion := formatter.BoldDirtyDefault(deployMeta.ChaosVersion)
|
|
||||||
|
|
||||||
msg = fmt.Sprintf(
|
|
||||||
"please select an upgrade (version: %s, chaos: %s):",
|
|
||||||
deployMeta.Version,
|
|
||||||
chaosVersion,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
prompt := &survey.Select{
|
|
||||||
Message: msg,
|
|
||||||
Options: internal.SortVersionsDesc(availableUpgrades),
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := survey.AskOne(prompt, chosenUpgrade); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getReleaseNotes(
|
|
||||||
app app.App,
|
|
||||||
versions []string,
|
|
||||||
chosenUpgrade string,
|
|
||||||
deployMeta stack.DeployMeta,
|
|
||||||
upgradeReleaseNotes *string,
|
|
||||||
) error {
|
|
||||||
parsedChosenUpgrade, err := tagcmp.Parse(chosenUpgrade)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parsing chosen upgrade version failed: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parsing deployment version failed: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, version := range internal.SortVersionsDesc(versions) {
|
|
||||||
parsedVersion, err := tagcmp.Parse(version)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("parsing recipe version failed: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if parsedVersion.IsGreaterThan(parsedDeployedVersion) &&
|
|
||||||
parsedVersion.IsLessThan(parsedChosenUpgrade) {
|
|
||||||
note, err := app.Recipe.GetReleaseNotes(version)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if note != "" {
|
|
||||||
// NOTE(d1): trim any final newline on the end of the note itself before
|
|
||||||
// we manually handle newlines (for multiple release notes and
|
|
||||||
// ensuring space between the warning messages)
|
|
||||||
note = strings.TrimSuffix(note, "\n")
|
|
||||||
|
|
||||||
*upgradeReleaseNotes += fmt.Sprintf("%s\n", note)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensureUpgradesAvailable ensures that there are available upgrades.
|
|
||||||
func ensureUpgradesAvailable(
|
|
||||||
app app.App,
|
|
||||||
versions []string,
|
|
||||||
availableUpgrades *[]string,
|
|
||||||
deployMeta stack.DeployMeta,
|
|
||||||
) (bool, error) {
|
|
||||||
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("parsing deployed version failed: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, version := range versions {
|
|
||||||
parsedVersion, err := tagcmp.Parse(version)
|
|
||||||
if err != nil {
|
|
||||||
return false, fmt.Errorf("parsing recipe version failed: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if parsedVersion.IsGreaterThan(parsedDeployedVersion) &&
|
|
||||||
!(parsedVersion.Equals(parsedDeployedVersion)) {
|
|
||||||
*availableUpgrades = append(*availableUpgrades, version)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(*availableUpgrades) == 0 && !internal.Force {
|
|
||||||
return false, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// validateUpgradeVersionArg validates the specific version.
|
|
||||||
func validateUpgradeVersionArg(
|
|
||||||
specificVersion string,
|
|
||||||
app app.App,
|
|
||||||
deployMeta stack.DeployMeta,
|
|
||||||
) error {
|
|
||||||
parsedSpecificVersion, err := tagcmp.Parse(specificVersion)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("'%s' is not a known version for %s", specificVersion, app.Recipe.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("'%s' is not a known version", deployMeta.Version)
|
|
||||||
}
|
|
||||||
|
|
||||||
if parsedSpecificVersion.IsLessThan(parsedDeployedVersion) &&
|
|
||||||
!parsedSpecificVersion.Equals(parsedDeployedVersion) {
|
|
||||||
return fmt.Errorf("%s is not an upgrade for %s?", deployMeta.Version, specificVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
if parsedSpecificVersion.Equals(parsedDeployedVersion) && !internal.Force {
|
|
||||||
return fmt.Errorf("%s is not an upgrade for %s?", deployMeta.Version, specificVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ensureDeployed ensures the app is deployed and if so, returns deployment
|
|
||||||
// meta info.
|
|
||||||
func ensureDeployed(cl *dockerClient.Client, app app.App) (stack.DeployMeta, error) {
|
|
||||||
log.Debugf("checking whether %s is already deployed", app.StackName())
|
|
||||||
|
|
||||||
deployMeta, err := stack.IsDeployed(context.Background(), cl, app.StackName())
|
|
||||||
if err != nil {
|
|
||||||
return stack.DeployMeta{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !deployMeta.IsDeployed {
|
|
||||||
return stack.DeployMeta{}, fmt.Errorf("%s is not deployed?", app.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return deployMeta, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
var showReleaseNotes bool
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
AppUpgradeCommand.Flags().BoolVarP(
|
AppUpgradeCommand.Flags().BoolVarP(
|
||||||
@ -448,7 +321,7 @@ func init() {
|
|||||||
&internal.DontWaitConverge, "no-converge-checks",
|
&internal.DontWaitConverge, "no-converge-checks",
|
||||||
"c",
|
"c",
|
||||||
false,
|
false,
|
||||||
"disable converge logic checks",
|
"do not wait for converge logic checks",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppUpgradeCommand.Flags().BoolVarP(
|
AppUpgradeCommand.Flags().BoolVarP(
|
||||||
|
@ -2,7 +2,6 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
@ -15,7 +14,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var AppVolumeListCommand = &cobra.Command{
|
var AppVolumeListCommand = &cobra.Command{
|
||||||
Use: "list <domain> [flags]",
|
Use: "list <app> [flags]",
|
||||||
Aliases: []string{"ls"},
|
Aliases: []string{"ls"},
|
||||||
Short: "List volumes associated with an app",
|
Short: "List volumes associated with an app",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
@ -72,23 +71,18 @@ var AppVolumeListCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
var AppVolumeRemoveCommand = &cobra.Command{
|
var AppVolumeRemoveCommand = &cobra.Command{
|
||||||
Use: "remove <domain> [volume] [flags]",
|
Use: "remove <app> [flags]",
|
||||||
Short: "Remove volume(s) associated with an app",
|
Short: "Remove volume(s) associated with an app",
|
||||||
Long: `Remove volumes associated with an app.
|
Long: `Remove volumes associated with an app.
|
||||||
|
|
||||||
The app in question must be undeployed before you try to remove volumes. See
|
The app in question must be undeployed before you try to remove volumes. See
|
||||||
"abra app undeploy <domain>" for more.
|
"abra app undeploy <app>" 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.`,
|
||||||
Example: ` # delete volumes interactively
|
|
||||||
abra app volume rm 1312.net
|
|
||||||
|
|
||||||
# delete specific volume
|
|
||||||
abra app volume rm 1312.net my_volume`,
|
|
||||||
Aliases: []string{"rm"},
|
Aliases: []string{"rm"},
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
@ -100,11 +94,6 @@ Passing "--force/-f" will select all volumes for removal. Be careful.`,
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
var volumeToDelete string
|
|
||||||
if len(args) == 2 {
|
|
||||||
volumeToDelete = args[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
cl, err := client.New(app.Server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -130,30 +119,6 @@ Passing "--force/-f" will select all volumes for removal. Be careful.`,
|
|||||||
}
|
}
|
||||||
volumeNames := client.GetVolumeNames(volumeList)
|
volumeNames := client.GetVolumeNames(volumeList)
|
||||||
|
|
||||||
if volumeToDelete != "" {
|
|
||||||
var exactMatch bool
|
|
||||||
|
|
||||||
fullVolumeToDeleteName := fmt.Sprintf("%s_%s", app.StackName(), volumeToDelete)
|
|
||||||
for _, volName := range volumeNames {
|
|
||||||
if volName == fullVolumeToDeleteName {
|
|
||||||
exactMatch = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if !exactMatch {
|
|
||||||
log.Fatalf("unable to remove volume: no volume with name '%s'?", volumeToDelete)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := client.RemoveVolumes(cl, context.Background(), []string{fullVolumeToDeleteName}, internal.Force, 5)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("removing volume %s failed: %s", volumeToDelete, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Infof("volume %s removed successfully", volumeToDelete)
|
|
||||||
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var volumesToRemove []string
|
var volumesToRemove []string
|
||||||
if !internal.Force && !internal.NoInput {
|
if !internal.Force && !internal.NoInput {
|
||||||
volumesPrompt := &survey.MultiSelect{
|
volumesPrompt := &survey.MultiSelect{
|
||||||
|
@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"path"
|
"path"
|
||||||
"slices"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
@ -25,17 +24,12 @@ var CatalogueGenerateCommand = &cobra.Command{
|
|||||||
Short: "Generate the recipe catalogue",
|
Short: "Generate the recipe catalogue",
|
||||||
Long: `Generate a new copy of the recipe catalogue.
|
Long: `Generate a new copy of the recipe catalogue.
|
||||||
|
|
||||||
N.B. this command **will** wipe local unstaged changes from your local recipes
|
|
||||||
if present. "--chaos/-C" on this command refers to the catalogue repository
|
|
||||||
("$ABRA_DIR/catalogue") and not the recipes. Please take care not to lose your
|
|
||||||
changes.
|
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
It is quite easy to get rate limited by Docker Hub when running this command.
|
It is quite easy to get rate limited by Docker Hub when running this command.
|
||||||
If you have a Hub account you can "docker login" and Abra will automatically
|
If you have a Hub account you can have Abra log you in to avoid this. Pass
|
||||||
use those details.
|
"--user" and "--pass".
|
||||||
|
|
||||||
Push your new release to git.coopcloud.tech with "--publish/-p". This requires
|
Push your new release to git.coopcloud.tech with "--publish/-p". 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
|
||||||
@ -53,62 +47,56 @@ keys configured on your account.`,
|
|||||||
recipeName = args[0]
|
recipeName = args[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
r := recipe.Get(recipeName)
|
||||||
|
|
||||||
if recipeName != "" {
|
if recipeName != "" {
|
||||||
internal.ValidateRecipe(args, cmd.Name())
|
internal.ValidateRecipe(args, cmd.Name())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := catalogue.EnsureCatalogue(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !internal.Chaos {
|
if !internal.Chaos {
|
||||||
if err := catalogue.EnsureIsClean(); err != nil {
|
if err := catalogue.EnsureIsClean(); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
repos, err := recipe.ReadReposMetadata(internal.Debug)
|
repos, err := recipe.ReadReposMetadata()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
barLength := len(repos)
|
var barLength int
|
||||||
|
var logMsg string
|
||||||
if recipeName != "" {
|
if recipeName != "" {
|
||||||
barLength = 1
|
barLength = 1
|
||||||
|
logMsg = fmt.Sprintf("ensuring %v recipe is cloned & up-to-date", barLength)
|
||||||
|
} else {
|
||||||
|
barLength = len(repos)
|
||||||
|
logMsg = fmt.Sprintf("ensuring %v recipes are cloned & up-to-date, this could take some time...", barLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !skipUpdates {
|
if !skipUpdates {
|
||||||
if err := recipe.UpdateRepositories(repos, recipeName, internal.Debug); err != nil {
|
log.Warn(logMsg)
|
||||||
|
if err := recipe.UpdateRepositories(repos, recipeName); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var warnings []string
|
|
||||||
catl := make(recipe.RecipeCatalogue)
|
catl := make(recipe.RecipeCatalogue)
|
||||||
catlBar := formatter.CreateProgressbar(barLength, "collecting catalogue metadata")
|
catlBar := formatter.CreateProgressbar(barLength, "generating catalogue metadata...")
|
||||||
for _, recipeMeta := range repos {
|
for _, recipeMeta := range repos {
|
||||||
if recipeName != "" && recipeName != recipeMeta.Name {
|
if recipeName != "" && recipeName != recipeMeta.Name {
|
||||||
if !internal.Debug {
|
catlBar.Add(1)
|
||||||
catlBar.Add(1)
|
|
||||||
}
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
r := recipe.Get(recipeMeta.Name)
|
versions, err := r.GetRecipeVersions()
|
||||||
versions, warnMsgs, err := r.GetRecipeVersions()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
warnings = append(warnings, err.Error())
|
log.Warn(err)
|
||||||
}
|
|
||||||
if len(warnMsgs) > 0 {
|
|
||||||
warnings = append(warnings, warnMsgs...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
features, category, warnMsgs, err := recipe.GetRecipeFeaturesAndCategory(r)
|
features, category, err := recipe.GetRecipeFeaturesAndCategory(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
warnings = append(warnings, err.Error())
|
log.Warn(err)
|
||||||
}
|
|
||||||
if len(warnMsgs) > 0 {
|
|
||||||
warnings = append(warnings, warnMsgs...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
catl[recipeMeta.Name] = recipe.RecipeMeta{
|
catl[recipeMeta.Name] = recipe.RecipeMeta{
|
||||||
@ -124,24 +112,7 @@ keys configured on your account.`,
|
|||||||
Features: features,
|
Features: features,
|
||||||
}
|
}
|
||||||
|
|
||||||
if !internal.Debug {
|
catlBar.Add(1)
|
||||||
catlBar.Add(1)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := catlBar.Close(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var uniqueWarnings []string
|
|
||||||
for _, w := range warnings {
|
|
||||||
if !slices.Contains(uniqueWarnings, w) {
|
|
||||||
uniqueWarnings = append(uniqueWarnings, w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, warnMsg := range uniqueWarnings {
|
|
||||||
log.Warn(warnMsg)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
recipesJSON, err := json.MarshalIndent(catl, "", " ")
|
recipesJSON, err := json.MarshalIndent(catl, "", " ")
|
||||||
@ -171,7 +142,7 @@ keys configured on your account.`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Infof("generated recipe catalogue: %s", config.RECIPES_JSON)
|
log.Infof("generated new recipe catalogue in %s", config.RECIPES_JSON)
|
||||||
|
|
||||||
cataloguePath := path.Join(config.ABRA_DIR, "catalogue")
|
cataloguePath := path.Join(config.ABRA_DIR, "catalogue")
|
||||||
if publishChanges {
|
if publishChanges {
|
||||||
@ -197,7 +168,7 @@ keys configured on your account.`,
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
sshURL := fmt.Sprintf(config.TOOLSHED_SSH_URL_TEMPLATE, config.CATALOGUE_JSON_REPO_NAME)
|
sshURL := fmt.Sprintf(config.SSH_URL_TEMPLATE, config.CATALOGUE_JSON_REPO_NAME)
|
||||||
if err := gitPkg.CreateRemote(repo, "origin-ssh", sshURL, internal.Dry); err != nil {
|
if err := gitPkg.CreateRemote(repo, "origin-ssh", sshURL, internal.Dry); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
@ -12,16 +12,17 @@ var AutocompleteCommand = &cobra.Command{
|
|||||||
Long: `To load completions:
|
Long: `To load completions:
|
||||||
|
|
||||||
Bash:
|
Bash:
|
||||||
# Load autocompletion for the current Bash session
|
|
||||||
$ source <(abra autocomplete bash)
|
$ source <(abra autocomplete bash)
|
||||||
|
|
||||||
# To load autocompletion for each session, execute once:
|
# To load autocompletion for each session, execute once:
|
||||||
# Linux:
|
# Linux:
|
||||||
$ abra autocomplete bash | sudo tee /etc/bash_completion.d/abra
|
$ abra autocomplete bash > /etc/bash_completion.d/abra
|
||||||
# macOS:
|
# macOS:
|
||||||
$ abra autocomplete bash | sudo tee $(brew --prefix)/etc/bash_completion.d/abra
|
$ abra autocomplete bash > $(brew --prefix)/etc/bash_completion.d/abra
|
||||||
|
|
||||||
Zsh:
|
Zsh:
|
||||||
|
|
||||||
# If shell autocompletion is not already enabled in your environment,
|
# If shell autocompletion is not already enabled in your environment,
|
||||||
# you will need to enable it. You can execute the following once:
|
# you will need to enable it. You can execute the following once:
|
||||||
|
|
||||||
@ -33,12 +34,14 @@ Zsh:
|
|||||||
# You will need to start a new shell for this setup to take effect.
|
# You will need to start a new shell for this setup to take effect.
|
||||||
|
|
||||||
fish:
|
fish:
|
||||||
|
|
||||||
$ abra autocomplete fish | source
|
$ abra autocomplete fish | source
|
||||||
|
|
||||||
# To load autocompletions for each session, execute once:
|
# To load autocompletions for each session, execute once:
|
||||||
$ abra autocomplete fish > ~/.config/fish/completions/abra.fish
|
$ abra autocomplete fish > ~/.config/fish/completions/abra.fish
|
||||||
|
|
||||||
PowerShell:
|
PowerShell:
|
||||||
|
|
||||||
PS> abra autocomplete powershell | Out-String | Invoke-Expression
|
PS> abra autocomplete powershell | Out-String | Invoke-Expression
|
||||||
|
|
||||||
# To load autocompletions for every new session, run:
|
# To load autocompletions for every new session, run:
|
||||||
|
@ -12,7 +12,6 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/upstream/container"
|
"coopcloud.tech/abra/pkg/upstream/container"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
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"
|
||||||
)
|
)
|
||||||
@ -48,7 +47,7 @@ func RunBackupCmdRemote(
|
|||||||
backupCmd string,
|
backupCmd string,
|
||||||
containerID string,
|
containerID string,
|
||||||
execEnv []string) (io.Writer, error) {
|
execEnv []string) (io.Writer, error) {
|
||||||
execBackupListOpts := containertypes.ExecOptions{
|
execBackupListOpts := types.ExecConfig{
|
||||||
AttachStderr: true,
|
AttachStderr: true,
|
||||||
AttachStdin: true,
|
AttachStdin: true,
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
|
@ -2,10 +2,9 @@ package internal
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
// NOTE(d1): global
|
// NOTE(d1): global
|
||||||
Debug bool
|
Debug bool
|
||||||
NoInput bool
|
NoInput bool
|
||||||
Offline bool
|
Offline bool
|
||||||
IgnoreEnvVersion bool
|
|
||||||
|
|
||||||
// NOTE(d1): sub-command specific
|
// NOTE(d1): sub-command specific
|
||||||
Chaos bool
|
Chaos bool
|
||||||
|
@ -14,7 +14,7 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/upstream/container"
|
"coopcloud.tech/abra/pkg/upstream/container"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
containertypes "github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types"
|
||||||
"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/docker/docker/pkg/archive"
|
"github.com/docker/docker/pkg/archive"
|
||||||
@ -24,7 +24,7 @@ import (
|
|||||||
func RunCmdRemote(
|
func RunCmdRemote(
|
||||||
cl *dockerClient.Client,
|
cl *dockerClient.Client,
|
||||||
app appPkg.App,
|
app appPkg.App,
|
||||||
disableTTY bool,
|
requestTTY bool,
|
||||||
abraSh, serviceName, cmdName, cmdArgs, remoteUser string) error {
|
abraSh, serviceName, cmdName, cmdArgs, remoteUser string) error {
|
||||||
filters := filters.NewArgs()
|
filters := filters.NewArgs()
|
||||||
filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), serviceName))
|
filters.Add("name", fmt.Sprintf("^%s_%s", app.StackName(), serviceName))
|
||||||
@ -42,7 +42,7 @@ func RunCmdRemote(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
copyOpts := containertypes.CopyToContainerOptions{AllowOverwriteDirWithFile: false, CopyUIDGID: false}
|
copyOpts := types.CopyToContainerOptions{AllowOverwriteDirWithFile: false, CopyUIDGID: false}
|
||||||
if err := cl.CopyToContainer(context.Background(), targetContainer.ID, "/tmp", content, copyOpts); err != nil {
|
if err := cl.CopyToContainer(context.Background(), targetContainer.ID, "/tmp", content, copyOpts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -55,7 +55,7 @@ func RunCmdRemote(
|
|||||||
|
|
||||||
shell := "/bin/bash"
|
shell := "/bin/bash"
|
||||||
findShell := []string{"test", "-e", shell}
|
findShell := []string{"test", "-e", shell}
|
||||||
execCreateOpts := containertypes.ExecOptions{
|
execCreateOpts := types.ExecConfig{
|
||||||
AttachStderr: true,
|
AttachStderr: true,
|
||||||
AttachStdin: true,
|
AttachStdin: true,
|
||||||
AttachStdout: true,
|
AttachStdout: true,
|
||||||
@ -84,10 +84,8 @@ func RunCmdRemote(
|
|||||||
}
|
}
|
||||||
|
|
||||||
execCreateOpts.Cmd = cmd
|
execCreateOpts.Cmd = cmd
|
||||||
|
execCreateOpts.Tty = requestTTY
|
||||||
execCreateOpts.Tty = true
|
if !requestTTY {
|
||||||
if disableTTY {
|
|
||||||
execCreateOpts.Tty = false
|
|
||||||
log.Debugf("not requesting a remote TTY")
|
log.Debugf("not requesting a remote TTY")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
@ -38,21 +37,18 @@ func horizontal(left, mid, right string) string {
|
|||||||
return lipgloss.JoinHorizontal(lipgloss.Left, left, mid, right)
|
return lipgloss.JoinHorizontal(lipgloss.Left, left, mid, right)
|
||||||
}
|
}
|
||||||
|
|
||||||
func formatComposeFiles(composeFiles string) string {
|
// NewVersionOverview shows an upgrade or downgrade overview
|
||||||
return strings.ReplaceAll(composeFiles, ":", "\n")
|
func NewVersionOverview(
|
||||||
}
|
|
||||||
|
|
||||||
// DeployOverview shows a deployment overview
|
|
||||||
func DeployOverview(
|
|
||||||
app appPkg.App,
|
app appPkg.App,
|
||||||
deployedVersion string,
|
|
||||||
toDeployVersion string,
|
|
||||||
releaseNotes string,
|
|
||||||
warnMessages []string,
|
warnMessages []string,
|
||||||
) error {
|
kind,
|
||||||
|
deployedVersion,
|
||||||
|
deployedChaosVersion,
|
||||||
|
toDeployVersion,
|
||||||
|
releaseNotes string) error {
|
||||||
deployConfig := "compose.yml"
|
deployConfig := "compose.yml"
|
||||||
if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
|
if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
|
||||||
deployConfig = formatComposeFiles(composeFiles)
|
deployConfig = composeFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
server := app.Server
|
server := app.Server
|
||||||
@ -65,29 +61,30 @@ func DeployOverview(
|
|||||||
domain = config.NO_DOMAIN_DEFAULT
|
domain = config.NO_DOMAIN_DEFAULT
|
||||||
}
|
}
|
||||||
|
|
||||||
envVersion := app.Recipe.EnvVersionRaw
|
|
||||||
if envVersion == "" {
|
|
||||||
envVersion = config.NO_VERSION_DEFAULT
|
|
||||||
}
|
|
||||||
|
|
||||||
rows := [][]string{
|
rows := [][]string{
|
||||||
{"DOMAIN", domain},
|
[]string{"APP", domain},
|
||||||
{"RECIPE", app.Recipe.Name},
|
[]string{"RECIPE", app.Recipe.Name},
|
||||||
{"SERVER", server},
|
[]string{"SERVER", server},
|
||||||
{"CONFIG", deployConfig},
|
[]string{"DEPLOYED", deployedVersion},
|
||||||
{"", ""},
|
[]string{"CURRENT CHAOS ", deployedChaosVersion},
|
||||||
{"CURRENT DEPLOYMENT", formatter.BoldDirtyDefault(deployedVersion)},
|
[]string{fmt.Sprintf("TO %s", strings.ToUpper(kind)), toDeployVersion},
|
||||||
{"ENV VERSION", formatter.BoldDirtyDefault(envVersion)},
|
[]string{"CONFIG", deployConfig},
|
||||||
{"NEW DEPLOYMENT", formatter.BoldDirtyDefault(toDeployVersion)},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deployType := getDeployType(deployedVersion, toDeployVersion)
|
overview := formatter.CreateOverview(
|
||||||
overview := formatter.CreateOverview(fmt.Sprintf("%s OVERVIEW", deployType), rows)
|
fmt.Sprintf("%s OVERVIEW", strings.ToUpper(kind)),
|
||||||
|
rows,
|
||||||
|
)
|
||||||
|
|
||||||
fmt.Println(overview)
|
fmt.Println(overview)
|
||||||
|
|
||||||
if releaseNotes != "" {
|
if releaseNotes != "" && toDeployVersion != "" {
|
||||||
fmt.Print(releaseNotes)
|
fmt.Print(releaseNotes)
|
||||||
|
} else {
|
||||||
|
warnMessages = append(
|
||||||
|
warnMessages,
|
||||||
|
fmt.Sprintf("no release notes available for %s", toDeployVersion),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, msg := range warnMessages {
|
for _, msg := range warnMessages {
|
||||||
@ -111,43 +108,19 @@ func DeployOverview(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDeployType(currentVersion, newVersion string) string {
|
// DeployOverview shows a deployment overview
|
||||||
if newVersion == config.NO_DOMAIN_DEFAULT {
|
func DeployOverview(
|
||||||
return "UNDEPLOY"
|
|
||||||
}
|
|
||||||
if strings.Contains(newVersion, "+U") {
|
|
||||||
return "CHAOS DEPLOY"
|
|
||||||
}
|
|
||||||
if strings.Contains(currentVersion, "+U") {
|
|
||||||
return "UNCHAOS DEPLOY"
|
|
||||||
}
|
|
||||||
if currentVersion == newVersion {
|
|
||||||
return "REDEPLOY"
|
|
||||||
}
|
|
||||||
if currentVersion == config.NO_VERSION_DEFAULT {
|
|
||||||
return "NEW DEPLOY"
|
|
||||||
}
|
|
||||||
currentParsed, err := tagcmp.Parse(currentVersion)
|
|
||||||
if err != nil {
|
|
||||||
return "DEPLOY"
|
|
||||||
}
|
|
||||||
newParsed, err := tagcmp.Parse(newVersion)
|
|
||||||
if err != nil {
|
|
||||||
return "DEPLOY"
|
|
||||||
}
|
|
||||||
if currentParsed.IsLessThan(newParsed) {
|
|
||||||
return "UPGRADE"
|
|
||||||
}
|
|
||||||
return "DOWNGRADE"
|
|
||||||
}
|
|
||||||
|
|
||||||
// MoveOverview shows a overview before moving an app to a different server
|
|
||||||
func MoveOverview(
|
|
||||||
app appPkg.App,
|
app appPkg.App,
|
||||||
newServer string,
|
warnMessages []string,
|
||||||
secrets []string,
|
deployedVersion string,
|
||||||
volumes []string,
|
deployedChaosVersion string,
|
||||||
) {
|
toDeployVersion,
|
||||||
|
toDeployChaosVersion string) error {
|
||||||
|
deployConfig := "compose.yml"
|
||||||
|
if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
|
||||||
|
deployConfig = composeFiles
|
||||||
|
}
|
||||||
|
|
||||||
server := app.Server
|
server := app.Server
|
||||||
if app.Server == "default" {
|
if app.Server == "default" {
|
||||||
server = "local"
|
server = "local"
|
||||||
@ -159,26 +132,26 @@ func MoveOverview(
|
|||||||
}
|
}
|
||||||
|
|
||||||
rows := [][]string{
|
rows := [][]string{
|
||||||
{"DOMAIN", domain},
|
[]string{"APP", domain},
|
||||||
{"RECIPE", app.Recipe.Name},
|
[]string{"RECIPE", app.Recipe.Name},
|
||||||
{"OLD SERVER", server},
|
[]string{"SERVER", server},
|
||||||
{"New SERVER", newServer},
|
[]string{"DEPLOYED", deployedVersion},
|
||||||
{"SECRETS", strings.Join(secrets, "\n")},
|
[]string{"CURRENT CHAOS ", deployedChaosVersion},
|
||||||
{"VOLUMES", strings.Join(volumes, "\n")},
|
[]string{"TO DEPLOY", toDeployVersion},
|
||||||
|
[]string{"NEW CHAOS", toDeployChaosVersion},
|
||||||
|
[]string{"CONFIG", deployConfig},
|
||||||
}
|
}
|
||||||
|
|
||||||
overview := formatter.CreateOverview("MOVE OVERVIEW", rows)
|
overview := formatter.CreateOverview("DEPLOY OVERVIEW", rows)
|
||||||
|
|
||||||
fmt.Println(overview)
|
fmt.Println(overview)
|
||||||
}
|
|
||||||
|
|
||||||
func PromptProcced() error {
|
for _, msg := range warnMessages {
|
||||||
if NoInput {
|
log.Warn(msg)
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if Dry {
|
if NoInput {
|
||||||
return fmt.Errorf("dry run")
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
response := false
|
response := false
|
||||||
@ -188,7 +161,57 @@ func PromptProcced() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !response {
|
if !response {
|
||||||
return errors.New("cancelled")
|
log.Fatal("deployment cancelled")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UndeployOverview shows an undeployment overview
|
||||||
|
func UndeployOverview(
|
||||||
|
app appPkg.App,
|
||||||
|
version,
|
||||||
|
chaosVersion string) error {
|
||||||
|
deployConfig := "compose.yml"
|
||||||
|
if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
|
||||||
|
deployConfig = composeFiles
|
||||||
|
}
|
||||||
|
|
||||||
|
server := app.Server
|
||||||
|
if app.Server == "default" {
|
||||||
|
server = "local"
|
||||||
|
}
|
||||||
|
|
||||||
|
domain := app.Domain
|
||||||
|
if domain == "" {
|
||||||
|
domain = config.NO_DOMAIN_DEFAULT
|
||||||
|
}
|
||||||
|
|
||||||
|
rows := [][]string{
|
||||||
|
[]string{"APP", domain},
|
||||||
|
[]string{"RECIPE", app.Recipe.Name},
|
||||||
|
[]string{"SERVER", server},
|
||||||
|
[]string{"DEPLOYED", version},
|
||||||
|
[]string{"CHAOS", chaosVersion},
|
||||||
|
[]string{"CONFIG", deployConfig},
|
||||||
|
}
|
||||||
|
|
||||||
|
overview := formatter.CreateOverview("UNDEPLOY OVERVIEW", rows)
|
||||||
|
|
||||||
|
fmt.Println(overview)
|
||||||
|
|
||||||
|
if NoInput {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
response := false
|
||||||
|
prompt := &survey.Confirm{Message: "proceed?"}
|
||||||
|
if err := survey.AskOne(prompt, &response); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !response {
|
||||||
|
log.Fatal("undeploy cancelled")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -200,7 +223,7 @@ func PromptProcced() error {
|
|||||||
func PostCmds(cl *dockerClient.Client, app appPkg.App, commands string) error {
|
func PostCmds(cl *dockerClient.Client, app appPkg.App, commands string) error {
|
||||||
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) {
|
||||||
return fmt.Errorf("%s does not exist for %s?", app.Recipe.AbraShPath, app.Name)
|
return fmt.Errorf(fmt.Sprintf("%s does not exist for %s?", app.Recipe.AbraShPath, app.Name))
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -208,7 +231,7 @@ func PostCmds(cl *dockerClient.Client, app appPkg.App, commands string) error {
|
|||||||
for _, command := range strings.Split(commands, "|") {
|
for _, command := range strings.Split(commands, "|") {
|
||||||
commandParts := strings.Split(command, " ")
|
commandParts := strings.Split(command, " ")
|
||||||
if len(commandParts) < 2 {
|
if len(commandParts) < 2 {
|
||||||
return fmt.Errorf("not enough arguments: %s", command)
|
return fmt.Errorf(fmt.Sprintf("not enough arguments: %s", command))
|
||||||
}
|
}
|
||||||
targetServiceName := commandParts[0]
|
targetServiceName := commandParts[0]
|
||||||
cmdName := commandParts[1]
|
cmdName := commandParts[1]
|
||||||
@ -235,7 +258,7 @@ func PostCmds(cl *dockerClient.Client, app appPkg.App, commands string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !matchingServiceName {
|
if !matchingServiceName {
|
||||||
return fmt.Errorf("no service %s for %s?", targetServiceName, app.Name)
|
return fmt.Errorf(fmt.Sprintf("no service %s for %s?", targetServiceName, app.Name))
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("running command %s %s within the context of %s_%s", cmdName, parsedCmdArgs, app.StackName(), targetServiceName)
|
log.Debugf("running command %s %s within the context of %s_%s", cmdName, parsedCmdArgs, app.StackName(), targetServiceName)
|
||||||
|
@ -1,17 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestSortVersionsDesc(t *testing.T) {
|
|
||||||
versions := SortVersionsDesc([]string{
|
|
||||||
"0.2.3+1.2.2",
|
|
||||||
"1.0.0+2.2.2",
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.Equal(t, "1.0.0+2.2.2", versions[0])
|
|
||||||
assert.Equal(t, "0.2.3+1.2.2", versions[1])
|
|
||||||
}
|
|
@ -1,11 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import "coopcloud.tech/abra/pkg/recipe"
|
|
||||||
|
|
||||||
func GetEnsureContext() recipe.EnsureContext {
|
|
||||||
return recipe.EnsureContext{
|
|
||||||
Chaos,
|
|
||||||
Offline,
|
|
||||||
IgnoreEnvVersion,
|
|
||||||
}
|
|
||||||
}
|
|
18
cli/internal/errors.go
Normal file
18
cli/internal/errors.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package internal
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"coopcloud.tech/abra/pkg/log"
|
||||||
|
"github.com/urfave/cli/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ShowSubcommandHelpAndError exits the program on error, logs the error to the
|
||||||
|
// terminal, and shows the help command.
|
||||||
|
func ShowSubcommandHelpAndError(cmd *cli.Command, err interface{}) {
|
||||||
|
if err2 := cli.ShowSubcommandHelp(cmd); err2 != nil {
|
||||||
|
log.Error(err2)
|
||||||
|
}
|
||||||
|
log.Error(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
@ -17,34 +17,34 @@ func ValidateRecipe(args []string, cmdName string) recipe.Recipe {
|
|||||||
recipeName = args[0]
|
recipeName = args[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
var recipes []string
|
if recipeName == "" && !NoInput {
|
||||||
|
var recipes []string
|
||||||
|
|
||||||
catl, err := recipe.ReadRecipeCatalogue(Offline)
|
catl, err := recipe.ReadRecipeCatalogue(Offline)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
knownRecipes := make(map[string]bool)
|
knownRecipes := make(map[string]bool)
|
||||||
for name := range catl {
|
for name := range catl {
|
||||||
knownRecipes[name] = true
|
knownRecipes[name] = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
localRecipes, err := recipe.GetRecipesLocal()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
localRecipes, err := recipe.GetRecipesLocal()
|
|
||||||
if err != nil {
|
|
||||||
log.Debugf("can't read local recipes: %s", err)
|
|
||||||
} else {
|
|
||||||
for _, recipeLocal := range localRecipes {
|
for _, recipeLocal := range localRecipes {
|
||||||
if _, ok := knownRecipes[recipeLocal]; !ok {
|
if _, ok := knownRecipes[recipeLocal]; !ok {
|
||||||
knownRecipes[recipeLocal] = true
|
knownRecipes[recipeLocal] = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for recipeName := range knownRecipes {
|
for recipeName := range knownRecipes {
|
||||||
recipes = append(recipes, recipeName)
|
recipes = append(recipes, recipeName)
|
||||||
}
|
}
|
||||||
|
|
||||||
if recipeName == "" && !NoInput {
|
|
||||||
prompt := &survey.Select{
|
prompt := &survey.Select{
|
||||||
Message: "Select recipe",
|
Message: "Select recipe",
|
||||||
Options: recipes,
|
Options: recipes,
|
||||||
@ -58,17 +58,11 @@ func ValidateRecipe(args []string, cmdName string) recipe.Recipe {
|
|||||||
log.Fatal("no recipe name provided")
|
log.Fatal("no recipe name provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := knownRecipes[recipeName]; !ok {
|
|
||||||
if !strings.Contains(recipeName, "/") {
|
|
||||||
log.Fatalf("no recipe '%s' exists?", recipeName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
chosenRecipe := recipe.Get(recipeName)
|
chosenRecipe := recipe.Get(recipeName)
|
||||||
if err := chosenRecipe.EnsureExists(); err != nil {
|
err := chosenRecipe.EnsureExists()
|
||||||
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = chosenRecipe.GetComposeConfig(nil)
|
_, err = chosenRecipe.GetComposeConfig(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if cmdName == "generate" {
|
if cmdName == "generate" {
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
package recipe
|
package recipe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
|
||||||
|
|
||||||
"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/go-git/go-git/v5"
|
|
||||||
gitCfg "github.com/go-git/go-git/v5/config"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -17,16 +13,7 @@ var RecipeFetchCommand = &cobra.Command{
|
|||||||
Use: "fetch [recipe | --all] [flags]",
|
Use: "fetch [recipe | --all] [flags]",
|
||||||
Aliases: []string{"f"},
|
Aliases: []string{"f"},
|
||||||
Short: "Clone recipe(s) locally",
|
Short: "Clone recipe(s) locally",
|
||||||
Long: `Using "--force/-f" Git syncs an existing recipe. It does not erase unstaged changes.`,
|
|
||||||
Args: cobra.RangeArgs(0, 1),
|
Args: cobra.RangeArgs(0, 1),
|
||||||
Example: ` # fetch from recipe catalogue
|
|
||||||
abra recipe fetch gitea
|
|
||||||
|
|
||||||
# fetch from remote recipe
|
|
||||||
abra recipe fetch git.foo.org/recipes/myrecipe
|
|
||||||
|
|
||||||
# fetch with ssh remote for hacking
|
|
||||||
abra recipe fetch gitea --ssh`,
|
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
args []string,
|
args []string,
|
||||||
@ -48,39 +35,10 @@ var RecipeFetchCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if recipeName != "" {
|
if recipeName != "" {
|
||||||
r := recipe.Get(recipeName)
|
r := internal.ValidateRecipe(args, cmd.Name())
|
||||||
if _, err := os.Stat(r.Dir); !os.IsNotExist(err) {
|
if err := r.Ensure(false, false); err != nil {
|
||||||
if !force {
|
log.Fatal(err)
|
||||||
log.Warnf("%s is already fetched", r.Name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
r = internal.ValidateRecipe(args, cmd.Name())
|
|
||||||
|
|
||||||
if sshRemote {
|
|
||||||
if r.SSHURL == "" {
|
|
||||||
log.Warnf("unable to discover SSH remote for %s", r.Name)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
repo, err := git.PlainOpen(r.Dir)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("unable to open %s: %s", r.Dir, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err = repo.DeleteRemote("origin"); err != nil {
|
|
||||||
log.Fatalf("unable to remove default remote in %s: %s", r.Dir, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := repo.CreateRemote(&gitCfg.RemoteConfig{
|
|
||||||
Name: "origin",
|
|
||||||
URLs: []string{r.SSHURL},
|
|
||||||
}); err != nil {
|
|
||||||
log.Fatalf("unable to set SSH remote in %s: %s", r.Dir, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,10 +48,9 @@ var RecipeFetchCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
catlBar := formatter.CreateProgressbar(len(catalogue), "fetching latest recipes...")
|
catlBar := formatter.CreateProgressbar(len(catalogue), "fetching latest recipes...")
|
||||||
ensureCtx := internal.GetEnsureContext()
|
|
||||||
for recipeName := range catalogue {
|
for recipeName := range catalogue {
|
||||||
r := recipe.Get(recipeName)
|
r := recipe.Get(recipeName)
|
||||||
if err := r.Ensure(ensureCtx); err != nil {
|
if err := r.Ensure(false, false); err != nil {
|
||||||
log.Error(err)
|
log.Error(err)
|
||||||
}
|
}
|
||||||
catlBar.Add(1)
|
catlBar.Add(1)
|
||||||
@ -103,8 +60,6 @@ var RecipeFetchCommand = &cobra.Command{
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
fetchAllRecipes bool
|
fetchAllRecipes bool
|
||||||
sshRemote bool
|
|
||||||
force bool
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@ -115,20 +70,4 @@ func init() {
|
|||||||
false,
|
false,
|
||||||
"fetch all recipes",
|
"fetch all recipes",
|
||||||
)
|
)
|
||||||
|
|
||||||
RecipeFetchCommand.Flags().BoolVarP(
|
|
||||||
&sshRemote,
|
|
||||||
"ssh",
|
|
||||||
"s",
|
|
||||||
false,
|
|
||||||
"automatically set ssh remote",
|
|
||||||
)
|
|
||||||
|
|
||||||
RecipeFetchCommand.Flags().BoolVarP(
|
|
||||||
&force,
|
|
||||||
"force",
|
|
||||||
"f",
|
|
||||||
false,
|
|
||||||
"force re-fetch",
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -23,7 +23,7 @@ var RecipeLintCommand = &cobra.Command{
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
recipe := internal.ValidateRecipe(args, cmd.Name())
|
recipe := internal.ValidateRecipe(args, cmd.Name())
|
||||||
|
|
||||||
if err := recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
if err := recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ var RecipeNewCommand = &cobra.Command{
|
|||||||
if err := os.RemoveAll(gitRepo); err != nil {
|
if err := os.RemoveAll(gitRepo); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
log.Debugf("removed .git repo in %s", gitRepo)
|
log.Debugf("removed example git repo in %s", gitRepo)
|
||||||
|
|
||||||
meta := newRecipeMeta(recipeName)
|
meta := newRecipeMeta(recipeName)
|
||||||
|
|
||||||
@ -76,6 +76,7 @@ var RecipeNewCommand = &cobra.Command{
|
|||||||
if err := os.WriteFile(path, templated.Bytes(), 0o644); err != nil {
|
if err := os.WriteFile(path, templated.Bytes(), 0o644); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := git.Init(r.Dir, true, gitName, gitEmail); err != nil {
|
if err := git.Init(r.Dir, true, gitName, gitEmail); err != nil {
|
||||||
|
@ -267,8 +267,6 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var addNextAsReleaseNotes bool
|
|
||||||
|
|
||||||
nextReleaseNotePath := path.Join(releaseDir, "next")
|
nextReleaseNotePath := path.Join(releaseDir, "next")
|
||||||
if _, err := os.Stat(nextReleaseNotePath); err == nil {
|
if _, err := os.Stat(nextReleaseNotePath); err == nil {
|
||||||
// release/next note exists. Move it to release/<tag>
|
// release/next note exists. Move it to release/<tag>
|
||||||
@ -278,37 +276,38 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !internal.NoInput {
|
if !internal.NoInput {
|
||||||
prompt := &survey.Confirm{
|
prompt := &survey.Input{
|
||||||
Message: "Use release note in release/next?",
|
Message: "Use release note in release/next?",
|
||||||
}
|
}
|
||||||
|
var addReleaseNote bool
|
||||||
if err := survey.AskOne(prompt, &addNextAsReleaseNotes); err != nil {
|
if err := survey.AskOne(prompt, &addReleaseNote); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if !addReleaseNote {
|
||||||
if !addNextAsReleaseNotes {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.Rename(nextReleaseNotePath, tagReleaseNotePath); err != nil {
|
err := os.Rename(nextReleaseNotePath, tagReleaseNotePath)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gitPkg.Add(recipe.Dir, path.Join("release", "next"), internal.Dry); err != nil {
|
err = gitPkg.Add(recipe.Dir, path.Join("release", "next"), internal.Dry)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gitPkg.Add(recipe.Dir, path.Join("release", tag), internal.Dry); err != nil {
|
err = gitPkg.Add(recipe.Dir, path.Join("release", tag), internal.Dry)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
} else if !errors.Is(err, os.ErrNotExist) {
|
} else if !errors.Is(err, os.ErrNotExist) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE(d1): No release note exists for the current release. Or, we've
|
// No release note exists for the current release.
|
||||||
// already used release/next as the release note
|
if internal.NoInput {
|
||||||
if internal.NoInput || addNextAsReleaseNotes {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,7 @@ You may invoke this command in "wizard" mode and be prompted for input.`,
|
|||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
recipe := internal.ValidateRecipe(args, cmd.Name())
|
recipe := internal.ValidateRecipe(args, cmd.Name())
|
||||||
|
|
||||||
if err := recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
if err := recipe.Ensure(internal.Chaos, internal.Offline); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,13 +37,10 @@ var RecipeVersionCommand = &cobra.Command{
|
|||||||
if !ok {
|
if !ok {
|
||||||
warnMessages = append(warnMessages, "retrieved versions from local recipe repository")
|
warnMessages = append(warnMessages, "retrieved versions from local recipe repository")
|
||||||
|
|
||||||
recipeVersions, warnMsg, err := recipe.GetRecipeVersions()
|
recipeVersions, err := recipe.GetRecipeVersions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
warnMessages = append(warnMessages, err.Error())
|
warnMessages = append(warnMessages, err.Error())
|
||||||
}
|
}
|
||||||
if len(warnMsg) > 0 {
|
|
||||||
warnMessages = append(warnMessages, warnMsg...)
|
|
||||||
}
|
|
||||||
|
|
||||||
recipeMeta = recipePkg.RecipeMeta{Versions: recipeVersions}
|
recipeMeta = recipePkg.RecipeMeta{Versions: recipeVersions}
|
||||||
}
|
}
|
||||||
|
60
cli/run.go
60
cli/run.go
@ -31,31 +31,26 @@ func Run(version, commit string) {
|
|||||||
"upgrade",
|
"upgrade",
|
||||||
},
|
},
|
||||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||||
dirs := []map[string]os.FileMode{
|
paths := []string{
|
||||||
{config.ABRA_DIR: 0764},
|
config.ABRA_DIR,
|
||||||
{config.SERVERS_DIR: 0700},
|
config.SERVERS_DIR,
|
||||||
{config.RECIPES_DIR: 0764},
|
config.RECIPES_DIR,
|
||||||
{config.LOGS_DIR: 0764},
|
config.VENDOR_DIR, // TODO(d1): remove > 0.9.x
|
||||||
|
config.BACKUP_DIR, // TODO(d1): remove > 0.9.x
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, dir := range dirs {
|
for _, path := range paths {
|
||||||
for path, perm := range dir {
|
if err := os.Mkdir(path, 0764); err != nil {
|
||||||
if err := os.Mkdir(path, perm); err != nil {
|
if !os.IsExist(err) {
|
||||||
if !os.IsExist(err) {
|
log.Fatal(err)
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Logger.SetStyles(charmLog.DefaultStyles())
|
log.Logger.SetStyles(log.Styles())
|
||||||
charmLog.SetDefault(log.Logger)
|
charmLog.SetDefault(log.Logger)
|
||||||
|
|
||||||
if internal.MachineReadable {
|
|
||||||
log.SetOutput(os.Stderr)
|
|
||||||
}
|
|
||||||
|
|
||||||
if internal.Debug {
|
if internal.Debug {
|
||||||
log.SetLevel(log.DebugLevel)
|
log.SetLevel(log.DebugLevel)
|
||||||
log.SetOutput(os.Stderr)
|
log.SetOutput(os.Stderr)
|
||||||
@ -73,8 +68,7 @@ func Run(version, commit string) {
|
|||||||
Aliases: []string{"m"},
|
Aliases: []string{"m"},
|
||||||
Short: "Generate manpage",
|
Short: "Generate manpage",
|
||||||
Example: ` # generate the man pages into /usr/local/share/man/man1
|
Example: ` # generate the man pages into /usr/local/share/man/man1
|
||||||
abra_path=$(which abra) # pass abra absolute path to sudo below
|
sudo abra man
|
||||||
sudo $abra_path man
|
|
||||||
sudo mandb
|
sudo mandb
|
||||||
|
|
||||||
# read the man pages
|
# read the man pages
|
||||||
@ -101,37 +95,20 @@ func Run(version, commit string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(
|
rootCmd.PersistentFlags().BoolVarP(
|
||||||
&internal.Debug,
|
&internal.Debug, "debug", "d", false,
|
||||||
"debug",
|
|
||||||
"d",
|
|
||||||
false,
|
|
||||||
"show debug messages",
|
"show debug messages",
|
||||||
)
|
)
|
||||||
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(
|
rootCmd.PersistentFlags().BoolVarP(
|
||||||
&internal.NoInput,
|
&internal.NoInput, "no-input", "n", false,
|
||||||
"no-input",
|
|
||||||
"n",
|
|
||||||
false,
|
|
||||||
"toggle non-interactive mode",
|
"toggle non-interactive mode",
|
||||||
)
|
)
|
||||||
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(
|
rootCmd.PersistentFlags().BoolVarP(
|
||||||
&internal.Offline,
|
&internal.Offline, "offline", "o", false,
|
||||||
"offline",
|
|
||||||
"o",
|
|
||||||
false,
|
|
||||||
"prefer offline & filesystem access",
|
"prefer offline & filesystem access",
|
||||||
)
|
)
|
||||||
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(
|
|
||||||
&internal.IgnoreEnvVersion,
|
|
||||||
"ignore-env-version",
|
|
||||||
"i",
|
|
||||||
false,
|
|
||||||
"ignore .env version checkout",
|
|
||||||
)
|
|
||||||
|
|
||||||
catalogue.CatalogueCommand.AddCommand(
|
catalogue.CatalogueCommand.AddCommand(
|
||||||
catalogue.CatalogueGenerateCommand,
|
catalogue.CatalogueGenerateCommand,
|
||||||
)
|
)
|
||||||
@ -204,18 +181,15 @@ func Run(version, commit string) {
|
|||||||
app.AppRestartCommand,
|
app.AppRestartCommand,
|
||||||
app.AppRestoreCommand,
|
app.AppRestoreCommand,
|
||||||
app.AppRollbackCommand,
|
app.AppRollbackCommand,
|
||||||
app.AppMoveCommand,
|
|
||||||
app.AppRunCommand,
|
app.AppRunCommand,
|
||||||
app.AppSecretCommand,
|
app.AppSecretCommand,
|
||||||
app.AppServicesCommand,
|
app.AppServicesCommand,
|
||||||
app.AppUndeployCommand,
|
app.AppUndeployCommand,
|
||||||
app.AppUpgradeCommand,
|
app.AppUpgradeCommand,
|
||||||
app.AppVolumeCommand,
|
app.AppVolumeCommand,
|
||||||
app.AppLabelsCommand,
|
|
||||||
app.AppEnvCommand,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
if err := rootCmd.Execute(); err != nil {
|
if err := rootCmd.Execute(); err != nil {
|
||||||
os.Exit(1)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,7 +103,8 @@ developer machine. The domain is then set to "default".`,
|
|||||||
|
|
||||||
if _, err := client.New(name, timeout); err != nil {
|
if _, err := client.New(name, timeout); err != nil {
|
||||||
cleanUp(name)
|
cleanUp(name)
|
||||||
log.Fatalf("ssh %s error: %s", name, sshPkg.Fatal(name, err))
|
log.Debugf("ssh %s error: %s", name, sshPkg.Fatal(name, err))
|
||||||
|
log.Fatalf("can't ssh to %s, make sure \"ssh %s\" works", name, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if created {
|
if created {
|
||||||
|
@ -441,25 +441,7 @@ func upgrade(cl *dockerclient.Client, stackName, recipeName, upgradeVersion stri
|
|||||||
|
|
||||||
log.Infof("upgrade %s (%s) to version %s", stackName, recipeName, upgradeVersion)
|
log.Infof("upgrade %s (%s) to version %s", stackName, recipeName, upgradeVersion)
|
||||||
|
|
||||||
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
|
err = stack.RunDeploy(cl, deployOpts, compose, stackName, true)
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
f, err := app.Filters(true, false, serviceNames...)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
err = stack.RunDeploy(
|
|
||||||
cl,
|
|
||||||
deployOpts,
|
|
||||||
compose,
|
|
||||||
stackName,
|
|
||||||
app.Server,
|
|
||||||
true,
|
|
||||||
f,
|
|
||||||
)
|
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -470,7 +452,7 @@ func newKadabraApp(version, commit string) *cobra.Command {
|
|||||||
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
|
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
|
||||||
Short: "The Co-op Cloud auto-updater 🤖 🚀",
|
Short: "The Co-op Cloud auto-updater 🤖 🚀",
|
||||||
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||||
log.Logger.SetStyles(charmLog.DefaultStyles())
|
log.Logger.SetStyles(log.Styles())
|
||||||
charmLog.SetDefault(log.Logger)
|
charmLog.SetDefault(log.Logger)
|
||||||
|
|
||||||
if internal.Debug {
|
if internal.Debug {
|
||||||
|
151
go.mod
151
go.mod
@ -1,142 +1,129 @@
|
|||||||
module coopcloud.tech/abra
|
module coopcloud.tech/abra
|
||||||
|
|
||||||
go 1.24.0
|
go 1.22.7
|
||||||
|
|
||||||
toolchain go1.24.1
|
toolchain go1.23.1
|
||||||
|
|
||||||
|
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-20250818180036-0ec1b205b5ca
|
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb
|
||||||
git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c
|
git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100509-01bff8284355
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||||
github.com/charmbracelet/bubbletea v1.3.6
|
github.com/charmbracelet/lipgloss v1.0.0
|
||||||
github.com/charmbracelet/lipgloss v1.1.0
|
github.com/charmbracelet/log v0.4.0
|
||||||
github.com/charmbracelet/log v0.4.2
|
|
||||||
github.com/distribution/reference v0.6.0
|
github.com/distribution/reference v0.6.0
|
||||||
github.com/docker/cli v28.3.3+incompatible
|
github.com/docker/cli v27.4.1+incompatible
|
||||||
github.com/docker/docker v28.3.3+incompatible
|
github.com/docker/docker v27.4.1+incompatible
|
||||||
github.com/docker/go-units v0.5.0
|
github.com/docker/go-units v0.5.0
|
||||||
github.com/go-git/go-git/v5 v5.16.2
|
github.com/go-git/go-git/v5 v5.12.0
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.6.0
|
||||||
github.com/leonelquinteros/gotext v1.7.2
|
|
||||||
github.com/moby/sys/signal v0.7.1
|
github.com/moby/sys/signal v0.7.1
|
||||||
github.com/moby/term v0.5.2
|
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.18.0
|
github.com/schollz/progressbar/v3 v3.17.1
|
||||||
golang.org/x/term v0.34.0
|
github.com/urfave/cli/v3 v3.0.0-alpha9
|
||||||
|
golang.org/x/term v0.27.0
|
||||||
gopkg.in/yaml.v3 v3.0.1
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
gotest.tools/v3 v3.5.2
|
gotest.tools/v3 v3.5.1
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
dario.cat/mergo v1.0.2 // indirect
|
dario.cat/mergo v1.0.1 // indirect
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 // indirect
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect
|
||||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
github.com/BurntSushi/toml v1.4.0 // indirect
|
||||||
github.com/Microsoft/go-winio v0.6.2 // indirect
|
github.com/Microsoft/go-winio v0.6.2 // indirect
|
||||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
github.com/ProtonMail/go-crypto v1.1.3 // indirect
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
github.com/beorn7/perks v1.0.1 // indirect
|
github.com/beorn7/perks v1.0.1 // indirect
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3 // indirect
|
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
github.com/charmbracelet/colorprofile v0.3.2 // indirect
|
github.com/charmbracelet/x/ansi v0.6.0 // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.10.1 // indirect
|
github.com/cloudflare/circl v1.5.0 // indirect
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
|
||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
|
||||||
github.com/cloudflare/circl v1.6.1 // indirect
|
|
||||||
github.com/containerd/errdefs v1.0.0 // indirect
|
|
||||||
github.com/containerd/errdefs/pkg v0.3.0 // indirect
|
|
||||||
github.com/containerd/log v0.1.0 // indirect
|
github.com/containerd/log v0.1.0 // indirect
|
||||||
github.com/containerd/platforms v0.2.1 // indirect
|
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
github.com/cyphar/filepath-securejoin v0.3.6 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1 // 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
|
||||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // indirect
|
||||||
github.com/docker/go-connections v0.6.0 // indirect
|
github.com/docker/go-connections v0.5.0 // indirect
|
||||||
github.com/docker/go-metrics v0.0.1 // indirect
|
github.com/docker/go-metrics v0.0.1 // indirect
|
||||||
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
|
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
|
||||||
github.com/emirpasic/gods v1.18.1 // indirect
|
github.com/emirpasic/gods v1.18.1 // indirect
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect
|
|
||||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||||
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
github.com/fsnotify/fsnotify v1.6.0 // indirect
|
||||||
github.com/ghodss/yaml v1.0.0 // indirect
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
|
||||||
github.com/go-git/go-billy/v5 v5.6.2 // indirect
|
github.com/go-git/go-billy/v5 v5.6.0 // indirect
|
||||||
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||||
github.com/go-logr/logr v1.4.3 // indirect
|
github.com/go-logr/logr v1.4.2 // indirect
|
||||||
github.com/go-logr/stdr v1.2.2 // indirect
|
github.com/go-logr/stdr v1.2.2 // indirect
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
github.com/go-viper/mapstructure/v2 v2.2.1 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect
|
||||||
github.com/google/uuid v1.6.0 // indirect
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 // indirect
|
||||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||||
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.17.11 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
github.com/mattn/go-localereader v0.0.1 // indirect
|
|
||||||
github.com/mattn/go-runewidth v0.0.16 // indirect
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||||
github.com/miekg/pkcs11 v1.1.1 // indirect
|
github.com/miekg/pkcs11 v1.1.1 // indirect
|
||||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
github.com/moby/docker-image-spec v1.3.1 // indirect
|
github.com/moby/docker-image-spec v1.3.1 // indirect
|
||||||
github.com/moby/go-archive v0.1.0 // indirect
|
github.com/moby/sys/mountinfo v0.6.2 // indirect
|
||||||
github.com/moby/sys/atomicwriter v0.1.0 // indirect
|
github.com/moby/sys/user v0.3.0 // indirect
|
||||||
github.com/moby/sys/mountinfo v0.7.2 // indirect
|
|
||||||
github.com/moby/sys/user v0.4.0 // indirect
|
|
||||||
github.com/moby/sys/userns v0.1.0 // indirect
|
github.com/moby/sys/userns v0.1.0 // indirect
|
||||||
github.com/morikuni/aec v1.0.0 // indirect
|
github.com/morikuni/aec v1.0.0 // indirect
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
github.com/muesli/termenv v0.15.2 // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
|
||||||
github.com/muesli/termenv v0.16.0 // indirect
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||||
github.com/opencontainers/go-digest v1.0.0 // indirect
|
github.com/opencontainers/go-digest v1.0.0 // indirect
|
||||||
github.com/opencontainers/runc v1.1.13 // indirect
|
github.com/opencontainers/runc v1.1.13 // indirect
|
||||||
github.com/opencontainers/runtime-spec v1.1.0 // indirect
|
github.com/opencontainers/runtime-spec v1.1.0 // indirect
|
||||||
github.com/pelletier/go-toml v1.9.5 // indirect
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
github.com/pjbgf/sha1cd v0.4.0 // indirect
|
github.com/pjbgf/sha1cd v0.3.0 // indirect
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
github.com/prometheus/client_model v0.6.2 // indirect
|
github.com/prometheus/client_model v0.6.1 // indirect
|
||||||
github.com/prometheus/common v0.65.0 // indirect
|
github.com/prometheus/common v0.61.0 // indirect
|
||||||
github.com/prometheus/procfs v0.17.0 // 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/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.3.1 // indirect
|
github.com/skeema/knownhosts v1.3.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.7 // 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/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.37.0 // indirect
|
go.opentelemetry.io/otel v1.33.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.33.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect
|
||||||
go.opentelemetry.io/otel/metric v1.37.0 // indirect
|
go.opentelemetry.io/otel/metric v1.33.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.37.0 // indirect
|
go.opentelemetry.io/otel/sdk v1.33.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect
|
go.opentelemetry.io/otel/sdk/metric v1.33.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.37.0 // indirect
|
go.opentelemetry.io/otel/trace v1.33.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.7.1 // indirect
|
go.opentelemetry.io/proto/otlp v1.4.0 // indirect
|
||||||
golang.org/x/crypto v0.41.0 // indirect
|
golang.org/x/crypto v0.31.0 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect
|
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 // indirect
|
||||||
golang.org/x/net v0.43.0 // indirect
|
golang.org/x/net v0.33.0 // indirect
|
||||||
golang.org/x/sync v0.16.0 // indirect
|
golang.org/x/sync v0.10.0 // indirect
|
||||||
golang.org/x/text v0.28.0 // indirect
|
golang.org/x/text v0.21.0 // indirect
|
||||||
golang.org/x/time v0.12.0 // indirect
|
golang.org/x/time v0.8.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
|
||||||
google.golang.org/grpc v1.74.2 // indirect
|
google.golang.org/grpc v1.69.2 // indirect
|
||||||
google.golang.org/protobuf v1.36.7 // indirect
|
google.golang.org/protobuf v1.36.1 // indirect
|
||||||
gopkg.in/warnings.v0 v0.1.2 // indirect
|
gopkg.in/warnings.v0 v0.1.2 // indirect
|
||||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
)
|
)
|
||||||
@ -145,19 +132,19 @@ require (
|
|||||||
github.com/containers/image v3.0.2+incompatible
|
github.com/containers/image v3.0.2+incompatible
|
||||||
github.com/containers/storage v1.38.2 // indirect
|
github.com/containers/storage v1.38.2 // indirect
|
||||||
github.com/decentral1se/passgen v1.0.1
|
github.com/decentral1se/passgen v1.0.1
|
||||||
github.com/docker/docker-credential-helpers v0.9.3 // indirect
|
github.com/docker/docker-credential-helpers v0.8.2 // indirect
|
||||||
github.com/fvbommel/sortorder v1.1.0 // indirect
|
github.com/fvbommel/sortorder v1.1.0 // indirect
|
||||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||||
github.com/gorilla/mux v1.8.1 // indirect
|
github.com/gorilla/mux v1.8.1 // indirect
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.8
|
github.com/hashicorp/go-retryablehttp v0.7.7
|
||||||
github.com/moby/patternmatcher v0.6.0 // indirect
|
github.com/moby/patternmatcher v0.6.0 // indirect
|
||||||
github.com/moby/sys/sequential v0.6.0 // indirect
|
github.com/moby/sys/sequential v0.6.0 // indirect
|
||||||
github.com/opencontainers/image-spec v1.1.1 // indirect
|
github.com/opencontainers/image-spec v1.1.0 // indirect
|
||||||
github.com/prometheus/client_golang v1.23.0 // indirect
|
github.com/prometheus/client_golang v1.20.5 // indirect
|
||||||
github.com/sergi/go-diff v1.4.0 // indirect
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 // indirect
|
||||||
github.com/spf13/cobra v1.9.1
|
github.com/spf13/cobra v1.8.1
|
||||||
github.com/stretchr/testify v1.10.0
|
github.com/stretchr/testify v1.10.0
|
||||||
github.com/theupdateframework/notary v0.7.0 // indirect
|
github.com/theupdateframework/notary v0.7.0 // indirect
|
||||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||||
golang.org/x/sys v0.35.0
|
golang.org/x/sys v0.28.0
|
||||||
)
|
)
|
||||||
|
410
go.sum
410
go.sum
@ -24,27 +24,19 @@ cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0Zeo
|
|||||||
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
|
||||||
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb h1:Ws6WEwKXeaYEkfdkX6AqX1XLPuaCeyStEtxbmEJPllk=
|
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb h1:Ws6WEwKXeaYEkfdkX6AqX1XLPuaCeyStEtxbmEJPllk=
|
||||||
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
|
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
|
||||||
coopcloud.tech/tagcmp v0.0.0-20250427094623-9ea3bbbde8e5 h1:tphJCjFJw9fdjyKnbU0f7f3z5KtYE8VbUcAfu+oHKg8=
|
|
||||||
coopcloud.tech/tagcmp v0.0.0-20250427094623-9ea3bbbde8e5/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
|
|
||||||
coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca h1:gSD53tBAsbIGq4SnFfq+mEep6foekQ2a5ea7b38qkm0=
|
|
||||||
coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
|
|
||||||
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||||
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
|
||||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
|
||||||
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
|
||||||
git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c h1:oeKnUB79PKYD8D0/unYuu7MRcWryQQWOns8+JL+acrs=
|
git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100509-01bff8284355 h1:tCv2B4qoN6RMheKDnCzIafOkWS5BB1h7hwhmo+9bVeE=
|
||||||
git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c/go.mod h1:fQuhwrpg6qb9NlFXKYi/LysWu1wxjraS8sxyW12CUF0=
|
git.coopcloud.tech/coop-cloud/godotenv v1.5.2-0.20231130100509-01bff8284355/go.mod h1:Q8V1zbtPAlzYSr/Dvky3wS6x58IQAl3rot2me1oSO2Q=
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
github.com/AlecAivazis/survey/v2 v2.3.7 h1:6I/u8FvytdGsgonrYsVn2t8t4QiRnh6QSTqkkhIiSjQ=
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
github.com/AlecAivazis/survey/v2 v2.3.7/go.mod h1:xUTIdE4KCOIjsBAE1JYsUPoCqYdZ1reCfTwbto0Fduo=
|
||||||
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg=
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0=
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
|
||||||
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||||
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24=
|
||||||
github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
|
github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw=
|
||||||
@ -59,8 +51,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
|
|||||||
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
github.com/BurntSushi/toml v1.0.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ=
|
||||||
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||||
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.4.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||||
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
|
||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
|
||||||
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
|
||||||
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA=
|
||||||
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
|
||||||
@ -89,10 +79,8 @@ github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb0
|
|||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63nhn5WAunQHLTznkw5W8b1Xc0dNjp83s=
|
||||||
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w=
|
||||||
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
|
||||||
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
|
github.com/ProtonMail/go-crypto v1.1.3 h1:nRBOetoydLeUb4nHajyO2bKqMLfWQ/ZPwkXqXxPxCFk=
|
||||||
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
github.com/ProtonMail/go-crypto v1.1.3/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||||
github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
|
||||||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
|
||||||
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
@ -142,40 +130,22 @@ github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3k
|
|||||||
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw=
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM=
|
|
||||||
github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw=
|
|
||||||
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
|
||||||
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko=
|
||||||
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
|
||||||
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
github.com/charmbracelet/bubbletea v1.3.4 h1:kCg7B+jSCFPLYRA52SDZjr51kG/fMUEoPoZrkaDHyoI=
|
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg=
|
||||||
github.com/charmbracelet/bubbletea v1.3.4/go.mod h1:dtcUCyCGEX3g9tosuYiut3MXgY/Jsv9nKVdibKKRRXo=
|
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo=
|
||||||
github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU=
|
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
|
||||||
github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc=
|
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
github.com/charmbracelet/x/ansi v0.5.2 h1:dEa1x2qdOZXD/6439s+wF7xjV+kZLu/iN00GuXXrU9E=
|
||||||
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
github.com/charmbracelet/x/ansi v0.5.2/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q=
|
||||||
github.com/charmbracelet/colorprofile v0.3.1 h1:k8dTHMd7fgw4bnFd7jXTLZrSU/CQrKnL3m+AxCzDz40=
|
github.com/charmbracelet/x/ansi v0.6.0 h1:qOznutrb93gx9oMiGf7caF7bqqubh6YIM0SWKyA08pA=
|
||||||
github.com/charmbracelet/colorprofile v0.3.1/go.mod h1:/GkGusxNs8VB/RSOh3fu0TJmQ4ICMMPApIIVn0KszZ0=
|
github.com/charmbracelet/x/ansi v0.6.0/go.mod h1:KBUFw1la39nl0dLl10l5ORDAqGXaeurTQmwyyVKse/Q=
|
||||||
github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI=
|
|
||||||
github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI=
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
|
||||||
github.com/charmbracelet/log v0.4.1 h1:6AYnoHKADkghm/vt4neaNEXkxcXLSV2g1rdyFDOpTyk=
|
|
||||||
github.com/charmbracelet/log v0.4.1/go.mod h1:pXgyTsqsVu4N9hGdHmQ0xEA4RsXof402LX9ZgiITn2I=
|
|
||||||
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
|
||||||
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
|
||||||
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
|
|
||||||
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
|
|
||||||
github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ=
|
|
||||||
github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE=
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
|
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U=
|
||||||
github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
|
||||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
|
||||||
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
|
github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw=
|
||||||
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
|
github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M=
|
||||||
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
github.com/checkpoint-restore/go-criu/v5 v5.3.0/go.mod h1:E/eQpaFtUKGOOSEBZgmKAcn+zUUwWxqcaKZlF54wK8E=
|
||||||
@ -193,10 +163,8 @@ github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2u
|
|||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ=
|
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ=
|
||||||
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
|
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
|
||||||
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
github.com/cloudflare/circl v1.5.0 h1:hxIWksrX6XN5a1L2TI/h53AGPhNHoUBo+TD1ms9+pys=
|
||||||
github.com/cloudflare/circl v1.6.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
github.com/cloudflare/circl v1.5.0/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
||||||
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
|
||||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
|
||||||
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
|
||||||
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
|
||||||
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
|
||||||
@ -242,10 +210,6 @@ github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cE
|
|||||||
github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y=
|
github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y=
|
||||||
github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ=
|
github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ=
|
||||||
github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM=
|
github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM=
|
||||||
github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI=
|
|
||||||
github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M=
|
|
||||||
github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE=
|
|
||||||
github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk=
|
|
||||||
github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
|
github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
|
||||||
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
|
github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI=
|
||||||
github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
|
github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0=
|
||||||
@ -268,8 +232,6 @@ github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3
|
|||||||
github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c=
|
github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c=
|
||||||
github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=
|
github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=
|
||||||
github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=
|
github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY=
|
||||||
github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A=
|
|
||||||
github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw=
|
|
||||||
github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM=
|
github.com/containerd/stargz-snapshotter/estargz v0.4.1/go.mod h1:x7Q9dg9QYb4+ELgxmo4gBUeJB0tl5dqH1Sdz0nJU1QM=
|
||||||
github.com/containerd/stargz-snapshotter/estargz v0.11.0/go.mod h1:/KsZXsJRllMbTKFfG0miFQWViQKdI9+9aSXs+HN0+ac=
|
github.com/containerd/stargz-snapshotter/estargz v0.11.0/go.mod h1:/KsZXsJRllMbTKFfG0miFQWViQKdI9+9aSXs+HN0+ac=
|
||||||
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
|
github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o=
|
||||||
@ -316,10 +278,10 @@ 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.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6 h1:XJtiaUW6dEEqVuZiMTn1ldk455QWwEIsMIJlo5vtkx0=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
|
|
||||||
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
|
|
||||||
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=
|
||||||
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
github.com/creack/pty v1.1.17/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
@ -327,8 +289,10 @@ github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY=
|
|||||||
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4=
|
||||||
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
|
github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4=
|
||||||
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
github.com/cyphar/filepath-securejoin v0.3.4 h1:VBWugsJh2ZxJmLFSM06/0qzQyiQX2Qs0ViKrUAcqdZ8=
|
||||||
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
github.com/cyphar/filepath-securejoin v0.3.4/go.mod h1:8s/MCNJREmFK0H02MF6Ihv1nakJe4L/w3WZLHNkvlYM=
|
||||||
|
github.com/cyphar/filepath-securejoin v0.3.6 h1:4d9N5ykBnSp5Xn2JkhocYDkOpURL/18CYMpo6xB9uWM=
|
||||||
|
github.com/cyphar/filepath-securejoin v0.3.6/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
||||||
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
|
github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ=
|
||||||
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
|
github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s=
|
||||||
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
|
github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8=
|
||||||
@ -347,30 +311,28 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr
|
|||||||
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E=
|
||||||
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E=
|
||||||
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/cli v28.0.1+incompatible h1:g0h5NQNda3/CxIsaZfH4Tyf6vpxFth7PYl3hgCPOKzs=
|
github.com/docker/cli v27.3.1+incompatible h1:qEGdFBF3Xu6SCvCYhc7CzaQTlBmqDuzxPDpigSyeKQQ=
|
||||||
github.com/docker/cli v28.0.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v27.3.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/cli v28.3.3+incompatible h1:fp9ZHAr1WWPGdIWBM1b3zLtgCF+83gRdVMTJsUeiyAo=
|
github.com/docker/cli v27.4.1+incompatible h1:VzPiUlRJ/xh+otB75gva3r05isHMo5wXDfPRi5/b4hI=
|
||||||
github.com/docker/cli v28.3.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v27.4.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
||||||
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY=
|
||||||
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk=
|
||||||
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
|
||||||
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0=
|
github.com/docker/docker v27.3.1+incompatible h1:KttF0XoteNTicmUtBO0L2tP+J7FGRFTjaEF4k6WdhfI=
|
||||||
github.com/docker/docker v28.0.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v27.3.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI=
|
github.com/docker/docker v27.4.1+incompatible h1:ZJvcY7gfwHn1JF48PfbyXg7Jyt9ZCWDW+GGXOIxEwp4=
|
||||||
github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v27.4.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
||||||
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y=
|
||||||
github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
|
github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo=
|
||||||
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
|
github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M=
|
||||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0=
|
||||||
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q=
|
||||||
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec=
|
||||||
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
|
||||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
|
||||||
github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||||
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA=
|
||||||
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
|
github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI=
|
||||||
@ -388,8 +350,8 @@ github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:Htrtb
|
|||||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||||
github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
|
github.com/dvsekhvalnov/jose2go v0.0.0-20170216131308-f21a8cedbbae/go.mod h1:7BvyPhdbLxMXIYTFPLsyJRFMsKmOZnQmzh6Gb+uquuM=
|
||||||
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc=
|
||||||
github.com/elazarl/goproxy v1.7.2 h1:Y2o6urb7Eule09PjlhQRGNsqRfPmYI3KKQLFpCAV3+o=
|
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
|
||||||
github.com/elazarl/goproxy v1.7.2/go.mod h1:82vkLNir0ALaW14Rc399OTTjyNREgmdL2cVoIbS6XaE=
|
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
|
||||||
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||||
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
|
||||||
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
|
||||||
@ -400,8 +362,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
|
|||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
|
||||||
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
|
||||||
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6baUTXGLOoWe4PQhGxaX0KpnayAqC48p4=
|
|
||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
|
||||||
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0=
|
||||||
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
@ -422,18 +382,16 @@ github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYis
|
|||||||
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||||
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c=
|
github.com/gliderlabs/ssh v0.3.7 h1:iV3Bqi942d9huXnzEF2Mt+CY9gLu8DNM4Obd+8bODRE=
|
||||||
github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU=
|
github.com/gliderlabs/ssh v0.3.7/go.mod h1:zpHEXBstFnQYtGnB8k8kQLol82umzn/2/snG7alWVD8=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
|
||||||
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
|
||||||
github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UNbRM=
|
github.com/go-git/go-billy/v5 v5.6.0 h1:w2hPNtoehvJIxR00Vb4xX94qHQi/ApZfX+nBE2Cjio8=
|
||||||
github.com/go-git/go-billy/v5 v5.6.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
github.com/go-git/go-billy/v5 v5.6.0/go.mod h1:sFDq7xD3fn3E0GOwUSZqHo9lrkmx8xJhA0ZrfvjBRGM=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
|
||||||
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
|
||||||
github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60=
|
github.com/go-git/go-git/v5 v5.12.0 h1:7Md+ndsjrzZxbddRDZjF14qK+NN56sy6wkqaVrjZtys=
|
||||||
github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k=
|
github.com/go-git/go-git/v5 v5.12.0/go.mod h1:FTM9VKtnI2m65hNI/TenDDDnUf2Q9FHnXYjuz9i5OEY=
|
||||||
github.com/go-git/go-git/v5 v5.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
|
|
||||||
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
|
||||||
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
@ -449,8 +407,6 @@ github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTg
|
|||||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
|
||||||
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
|
||||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0=
|
||||||
@ -469,8 +425,6 @@ github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG
|
|||||||
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||||
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
|
||||||
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||||
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw=
|
||||||
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4=
|
||||||
@ -534,8 +488,8 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
|
|||||||
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0=
|
github.com/google/go-containerregistry v0.5.1/go.mod h1:Ct15B4yir3PLOP5jsy0GNeYVaIZs/MK/Jz5any1wFW0=
|
||||||
github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y=
|
github.com/google/go-intervals v0.0.2/go.mod h1:MkaR3LNRfeKLPmqgJYs4E66z5InYjmCjbbr4TQlcT6Y=
|
||||||
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
|
||||||
@ -577,10 +531,10 @@ github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t
|
|||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo=
|
||||||
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0 h1:TmHmbvxPmaegwhDubVz0lICL0J5Ka2vwTzhoePEXsGE=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.24.0/go.mod h1:qztMSjm835F2bXf+5HKAPIS5qsmQDqZna/PgVt4rWtI=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1 h1:VNqngBF40hVlDloBruUehVYC3ArSgIyScOAyMRqBxRg=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.25.1/go.mod h1:RBRO7fro65R6tjKzYgLAFo0t1QEXY1Dp+i/bvpRiqiQ=
|
||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed h1:5upAirOpQc1Q53c0bnx2ufif5kANL7bfZWcc6VJWJd8=
|
||||||
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
|
||||||
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
@ -594,8 +548,6 @@ github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHh
|
|||||||
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
|
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
|
|
||||||
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
|
||||||
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
@ -644,8 +596,8 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
|
|||||||
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs=
|
||||||
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk=
|
||||||
github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
github.com/klauspost/compress v1.17.11 h1:In6xLpyWOi1+C7tXUUWv2ot1QvBjxevKAaI6IXrJmUc=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.17.11/go.mod h1:pMDklpSncoRMuLFrf1W9Ss9KT+0rH90U12bZKk7uwG0=
|
||||||
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
||||||
@ -663,8 +615,6 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
|||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
|
||||||
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
|
||||||
github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+YP7G1Mc=
|
|
||||||
github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8=
|
|
||||||
github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
|
||||||
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
|
github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo=
|
||||||
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
@ -679,14 +629,13 @@ github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7
|
|||||||
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
|
github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho=
|
||||||
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
|
||||||
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
|
||||||
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
|
||||||
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
|
||||||
github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88=
|
|
||||||
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||||
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
@ -715,20 +664,14 @@ github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RR
|
|||||||
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
|
github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A=
|
||||||
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0=
|
||||||
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo=
|
||||||
github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ=
|
|
||||||
github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo=
|
|
||||||
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc=
|
||||||
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk=
|
||||||
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc=
|
||||||
github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw=
|
|
||||||
github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs=
|
|
||||||
github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
|
github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
|
||||||
github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
|
github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A=
|
||||||
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
|
github.com/moby/sys/mountinfo v0.5.0/go.mod h1:3bMD3Rg+zkqx8MRYPi7Pyb0Ie97QEBmdxbhnCLlSvSU=
|
||||||
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
||||||
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
||||||
github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
|
|
||||||
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
|
|
||||||
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU=
|
||||||
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko=
|
||||||
github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0=
|
github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0=
|
||||||
@ -736,13 +679,11 @@ github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5Xt
|
|||||||
github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
|
github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ=
|
||||||
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
|
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
|
||||||
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
github.com/moby/sys/user v0.3.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
||||||
github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
|
||||||
github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs=
|
|
||||||
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g=
|
||||||
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28=
|
||||||
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
|
github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo=
|
||||||
github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ=
|
github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0=
|
||||||
github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc=
|
github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
|
||||||
@ -751,12 +692,8 @@ github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjY
|
|||||||
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A=
|
||||||
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc=
|
||||||
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ=
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D264iyp3TiX5OmNcI5cIARiQI=
|
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
|
||||||
github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
|
||||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
|
||||||
github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk=
|
|
||||||
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
|
||||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
|
||||||
@ -793,8 +730,8 @@ github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8
|
|||||||
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
|
||||||
github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
|
||||||
github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040=
|
github.com/opencontainers/image-spec v1.1.0 h1:8SG7/vwALn54lVB/0yZ/MMwhFrPYtpEHQb2IpWsCzug=
|
||||||
github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M=
|
github.com/opencontainers/image-spec v1.1.0/go.mod h1:W4s4sFTMaBeK1BQLXbG4AdM2szdn85PY75RI83NrTrM=
|
||||||
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||||
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||||
github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U=
|
||||||
@ -824,10 +761,8 @@ github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrap
|
|||||||
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8=
|
||||||
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
|
||||||
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
|
||||||
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
|
||||||
github.com/pjbgf/sha1cd v0.3.2/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
|
||||||
github.com/pjbgf/sha1cd v0.4.0 h1:NXzbL1RvjTUi6kgYZCX3fPwwl27Q1LJndxtUDVfJGRY=
|
|
||||||
github.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A=
|
|
||||||
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
@ -843,10 +778,8 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf
|
|||||||
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
|
||||||
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
|
||||||
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
|
||||||
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
|
github.com/prometheus/client_golang v1.20.5 h1:cxppBPuYhUnsO6yo/aoRol4L7q7UFfdm+bR9r+8l63Y=
|
||||||
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
github.com/prometheus/client_golang v1.20.5/go.mod h1:PIEt8X02hGcP8JWbeHyeZ53Y/jReSnHgO035n//V5WE=
|
||||||
github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc=
|
|
||||||
github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE=
|
|
||||||
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
|
||||||
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
@ -854,18 +787,16 @@ github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:
|
|||||||
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||||
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||||
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
|
||||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
|
||||||
github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
|
||||||
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
|
||||||
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
|
||||||
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo=
|
||||||
github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
|
github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc=
|
||||||
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
|
github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw=
|
||||||
github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE=
|
github.com/prometheus/common v0.61.0 h1:3gv/GThfX0cV2lpO7gkTUwZru38mxevy90Bj8YFSRQQ=
|
||||||
github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8=
|
github.com/prometheus/common v0.61.0/go.mod h1:zr29OCN/2BsJRaFwG8QOBr41D6kkchKbpeNH7pAjb/s=
|
||||||
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
|
||||||
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
|
||||||
@ -879,8 +810,6 @@ github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4O
|
|||||||
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
|
||||||
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
|
||||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
|
||||||
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
|
||||||
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
@ -888,23 +817,21 @@ github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUc
|
|||||||
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
|
||||||
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
|
||||||
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
|
||||||
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
|
||||||
github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc=
|
github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
|
||||||
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
|
github.com/russross/blackfriday v1.6.0 h1:KqfZb0pUVN2lYqZUYRddxF4OR8ZMURnJIG5Y3VRLtww=
|
||||||
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 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=
|
||||||
github.com/schollz/progressbar/v3 v3.18.0 h1:uXdoHABRFmNIjUfte/Ex7WtuyVslrw2wVPQmCN62HpA=
|
github.com/schollz/progressbar/v3 v3.17.1 h1:bI1MTaoQO+v5kzklBjYNRQLoVpe0zbyRZNK6DFkVC5U=
|
||||||
github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8GjO0Y9S69eFvNsec=
|
github.com/schollz/progressbar/v3 v3.17.1/go.mod h1:RzqpnsPQNjUyIgdglUjRLgD7sVnxN1wpmBMV+UiEbL4=
|
||||||
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
|
github.com/sclevine/spec v1.2.0/go.mod h1:W4J29eT/Kzv7/b9IWLB055Z+qvVC9vt0Arko24q7p+U=
|
||||||
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo=
|
||||||
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
github.com/seccomp/libseccomp-golang v0.9.2-0.20210429002308-3879420cc921/go.mod h1:JA8cRccbGaA1s33RQf7Y1+q9gHmZX1yB/z9WDN1C6fg=
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||||
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
||||||
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
|
||||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
|
||||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||||
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||||
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc=
|
||||||
@ -916,8 +843,8 @@ github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic
|
|||||||
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
|
||||||
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ=
|
||||||
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
|
github.com/skeema/knownhosts v1.3.0 h1:AM+y0rI04VksttfwjkSTNQorvGqmwATnvnAHpSgc0LY=
|
||||||
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
|
github.com/skeema/knownhosts v1.3.0/go.mod h1:sPINvnADmT/qYH1kfv+ePMmOBTH6Tbl7b5LvTDjFK7M=
|
||||||
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
@ -932,8 +859,8 @@ github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3
|
|||||||
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE=
|
||||||
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||||
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||||
github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
|
||||||
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
@ -942,11 +869,8 @@ github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnIn
|
|||||||
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
|
||||||
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M=
|
|
||||||
github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
|
||||||
github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
|
github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
|
||||||
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
|
github.com/spf13/viper v1.4.0 h1:yXHLWeravcrgGyFSyCgdYpXQ9dR9c/WED3pg1RhxqEU=
|
||||||
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||||
@ -981,6 +905,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/v3 v3.0.0-alpha9.1.0.20241019193437-5053ec708a44 h1:BeSTAZEDkDVNv9EOrycIGCkEg+6EhRRgSsbdc93Q3OM=
|
||||||
|
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=
|
||||||
@ -1001,8 +927,6 @@ github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf
|
|||||||
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74=
|
||||||
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/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
|
|
||||||
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
|
|
||||||
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/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=
|
||||||
@ -1023,49 +947,49 @@ go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
|||||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0 h1:DheMAlT6POBP+gh8RUH19EOTnQIor5QE0uSRPtzCpSw=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.57.0/go.mod h1:wZcGmeVO9nzP67aYSLDqXNWK87EZWhi7JWj1v7ZXf94=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 h1:yd02MEjBdJkG3uabWP9apV+OuWRIXGDuJEUJbOHmCFU=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0/go.mod h1:umTcuxiv1n/s/S6/c2AT/g2CQ7u5C59sHDNmfSwgz7Q=
|
||||||
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
go.opentelemetry.io/otel v1.32.0 h1:WnBN+Xjcteh0zdk01SVqV55d/m62NJLJdIyb4y/WO5U=
|
||||||
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
go.opentelemetry.io/otel v1.32.0/go.mod h1:00DCVSB0RQcnzlwyTfqtxSm+DRr9hpYrHjNGiBHVQIg=
|
||||||
go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ=
|
go.opentelemetry.io/otel v1.33.0 h1:/FerN9bax5LoK51X/sI0SVYrjSE0/yUL7DpxW4K3FWw=
|
||||||
go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I=
|
go.opentelemetry.io/otel v1.33.0/go.mod h1:SUUkR6csvUQl+yjReHu5uM3EtVV7MBm5FHKRlNx4I8I=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc=
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0 h1:j7ZSD+5yn+lo3sGV69nW04rRR0jhYnBwjuX3r0HvnK0=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA=
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.32.0/go.mod h1:WXbYJTUaZXAbYd8lbgGuvih0yuCfOFC5RJoYnoLcGz8=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 h1:zG8GlgXCJQd5BU98C0hZnBbElszTmUgCNCfYneaDL0A=
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.33.0 h1:7F29RDmnlqk6B5d+sUqemt8TBfDqxryYW5gX6L74RFA=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0/go.mod h1:hOfBCz8kv/wuq73Mx2H2QnWokh/kHZxkh6SNF2bdKtw=
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.33.0/go.mod h1:ZiGDq7xwDMKmWDrN1XsXAj0iC7hns+2DhxBFSncNHSE=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0 h1:IJFEoHiytixx8cMiVAO+GmHR6Frwu+u5Ur8njpFO6Ac=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.32.0/go.mod h1:3rHrKNtLIoS0oZwkY2vxi+oJcwFRWdtUyRII+so45p8=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0 h1:Vh5HayB/0HHfOQA7Ctx69E/Y/DcQSMPpKANYVMQ7fBA=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.33.0/go.mod h1:cpgtDBaqD/6ok/UG0jT15/uKjAY8mRA53diogHBg3UI=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0 h1:9kV11HXBHZAvuPUZxmMWrH8hZn/6UnHX4K0mu36vNsU=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.32.0/go.mod h1:JyA0FHXe22E1NeNiHmVp7kFHglnexDQ7uRWDiiJ1hKQ=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0 h1:5pojmb1U1AogINhN3SurB+zm/nIcusopeBNp42f45QM=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.33.0/go.mod h1:57gTHJSE5S1tqg+EKsLPlTWhpHMsWlVmer+LA926XiA=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU=
|
||||||
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
go.opentelemetry.io/otel/metric v1.32.0 h1:xV2umtmNcThh2/a/aCP+h64Xx5wsj8qqnkYZktzNa0M=
|
||||||
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
go.opentelemetry.io/otel/metric v1.32.0/go.mod h1:jH7CIbbK6SH2V2wE16W05BHCtIDzauciCRLoc/SyMv8=
|
||||||
go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE=
|
go.opentelemetry.io/otel/metric v1.33.0 h1:r+JOocAyeRVXD8lZpjdQjzMadVZp2M4WmQ+5WtEnklQ=
|
||||||
go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E=
|
go.opentelemetry.io/otel/metric v1.33.0/go.mod h1:L9+Fyctbp6HFTddIxClbQkjtubW6O9QS3Ann/M82u6M=
|
||||||
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
go.opentelemetry.io/otel/sdk v1.32.0 h1:RNxepc9vK59A8XsgZQouW8ue8Gkb4jpWtJm9ge5lEG4=
|
||||||
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
go.opentelemetry.io/otel/sdk v1.32.0/go.mod h1:LqgegDBjKMmb2GC6/PrTnteJG39I8/vJCAP9LlJXEjU=
|
||||||
go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI=
|
go.opentelemetry.io/otel/sdk v1.33.0 h1:iax7M131HuAm9QkZotNHEfstof92xM+N8sr3uHXc2IM=
|
||||||
go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg=
|
go.opentelemetry.io/otel/sdk v1.33.0/go.mod h1:A1Q5oi7/9XaMlIWzPSxLRWOI8nG3FnzHJNbiENQuihM=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
go.opentelemetry.io/otel/sdk/metric v1.32.0 h1:rZvFnvmvawYb0alrYkjraqJq0Z4ZUJAiyYCU9snn1CU=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
go.opentelemetry.io/otel/sdk/metric v1.32.0/go.mod h1:PWeZlq0zt9YkYAp3gjKZ0eicRYvOh1Gd+X99x6GHpCQ=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc=
|
go.opentelemetry.io/otel/sdk/metric v1.33.0 h1:Gs5VK9/WUJhNXZgn8MR6ITatvAmKeIuCtNbsP3JkNqU=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps=
|
go.opentelemetry.io/otel/sdk/metric v1.33.0/go.mod h1:dL5ykHZmm1B1nVRk9dDjChwDmt81MjVp3gLkQRwKf/Q=
|
||||||
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
go.opentelemetry.io/otel/trace v1.32.0 h1:WIC9mYrXf8TmY/EXuULKc8hR17vE+Hjv2cssQDe03fM=
|
||||||
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
go.opentelemetry.io/otel/trace v1.32.0/go.mod h1:+i4rkvCraA+tG6AzwloGaCtkx53Fa+L+V8e9a7YvhT8=
|
||||||
go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4=
|
go.opentelemetry.io/otel/trace v1.33.0 h1:cCJuF7LRjUFso9LPnEAHJDB2pqzp+hbO8eu1qqW2d/s=
|
||||||
go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0=
|
go.opentelemetry.io/otel/trace v1.33.0/go.mod h1:uIcdVUZMpTAmz0tI1z04GoVSezK37CbGV4fr1f2nBck=
|
||||||
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
|
||||||
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
go.opentelemetry.io/proto/otlp v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0=
|
||||||
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8=
|
||||||
go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4=
|
go.opentelemetry.io/proto/otlp v1.4.0 h1:TA9WRvW6zMwP+Ssb6fLoUIuirti1gGbP28GcKG1jgeg=
|
||||||
go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE=
|
go.opentelemetry.io/proto/otlp v1.4.0/go.mod h1:PPBWZIP98o2ElSqI35IHfu7hIhSwvc5N38Jw8pXuGFY=
|
||||||
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
|
||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
@ -1090,10 +1014,10 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP
|
|||||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
|
||||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
|
||||||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
golang.org/x/crypto v0.29.0 h1:L5SG1JTTXupVV3n6sUqMTeWbjAyfPwoda2DLX8J8FrQ=
|
||||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
golang.org/x/crypto v0.29.0/go.mod h1:+F4F4N5hv6v38hfeYwTdx20oUvLLc+QfrE9Ax9HtgRg=
|
||||||
golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4=
|
golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U=
|
||||||
golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc=
|
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
|
||||||
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
|
||||||
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
|
||||||
@ -1104,12 +1028,10 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0
|
|||||||
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4=
|
||||||
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM=
|
||||||
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU=
|
||||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f h1:XdNn9LlyWAhLVp6P/i8QYBW+hlyhrhei9uErw2B5GJo=
|
||||||
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f/go.mod h1:D5SMRVC3C2/4+F/DB1wZsLRnSNimn2Sp/NPsCrsv8ak=
|
||||||
golang.org/x/exp v0.0.0-20250811191247-51f88131bc50 h1:3yiSh9fhy5/RhCSntf4Sy0Tnx50DmMpQ4MQdKKk4yg4=
|
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
|
||||||
golang.org/x/exp v0.0.0-20250811191247-51f88131bc50/go.mod h1:rT6SFzZ7oxADUDx58pcaKFTcZ+inxAa9fTrYx/uVYwg=
|
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
|
||||||
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE=
|
|
||||||
golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4=
|
|
||||||
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
|
||||||
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
|
||||||
@ -1173,10 +1095,10 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b
|
|||||||
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
|
||||||
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
golang.org/x/net v0.31.0 h1:68CPQngjLL0r2AlUKiSxtQFKvzRVbnzLwMUn5SzcLHo=
|
||||||
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
golang.org/x/net v0.31.0/go.mod h1:P4fl1q7dY2hnZFxEk4pPSkDHF+QqjitcnDjUQyMM+pM=
|
||||||
golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE=
|
golang.org/x/net v0.33.0 h1:74SYHlV8BIgHIFC/LrYkOGIwL19eTYXQ5wc6TBuO36I=
|
||||||
golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg=
|
golang.org/x/net v0.33.0/go.mod h1:HXLR5J+9DxmrqMwG9qjGCxZ+zKXxBru04zlTvWlWuN4=
|
||||||
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
|
||||||
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
|
||||||
@ -1194,10 +1116,10 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ
|
|||||||
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw=
|
golang.org/x/sync v0.9.0 h1:fEo0HyrW1GIgZdpbhCRO0PkJajUS5H9IFUztCgEo2jQ=
|
||||||
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.9.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
|
||||||
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
|
||||||
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
@ -1267,7 +1189,6 @@ golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7w
|
|||||||
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
|
||||||
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20210906170528-6f6e22806c34/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211025201205-69cdffdb9359/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20211116061358-0a5406a5449c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
@ -1275,19 +1196,20 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
|
||||||
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
|
golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
|
||||||
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
|
||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
|
||||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
|
||||||
golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4=
|
golang.org/x/term v0.27.0 h1:WP60Sv1nlK1T6SupCHbXzSaN0b9wUmsPoRS9b61A23Q=
|
||||||
golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw=
|
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
|
||||||
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
@ -1297,20 +1219,18 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
|||||||
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
|
||||||
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
|
||||||
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
golang.org/x/text v0.20.0 h1:gK/Kv2otX8gz+wn7Rmb3vT96ZwuoxnQlY+HlJVj7Qug=
|
||||||
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
golang.org/x/text v0.20.0/go.mod h1:D4IsuqiFMhST5bX19pQ9ikHC2GsaKyk/oF+pn3ducp4=
|
||||||
golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng=
|
golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
|
||||||
golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU=
|
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
|
||||||
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
|
||||||
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
golang.org/x/time v0.8.0 h1:9i3RxcPv3PZnitoVGMPDKZSq1xW1gK1Xy3ArNOGZfEg=
|
||||||
golang.org/x/time v0.11.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
golang.org/x/time v0.8.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM=
|
||||||
golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE=
|
|
||||||
golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg=
|
|
||||||
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
@ -1404,14 +1324,14 @@ google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfG
|
|||||||
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
|
||||||
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA=
|
||||||
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4 h1:IFnXJq3UPB3oBREOodn1v1aGQeZYQclEmvWRMN0PSsY=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697 h1:pgr/4QbFyktUv9CtQ/Fq4gzEE6/Xs7iCXbktaGzLHbQ=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:c8q6Z6OCqnfVIqUFJkCzKcrj8eCvUrz+K4KRzSTuANg=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241118233622-e639e219e697/go.mod h1:+D9ySVjN8nY8YCVjc5O7PZDIdZporIDY3KaGfJunh88=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a h1:DMCgtIAIQGZqJXMVzJF4MV8BlWoJh2ZuFiRdAleyr58=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8 h1:st3LcW/BPi75W4q1jJTEor/QWwbNlPlDG0JTn6XhZu0=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a/go.mod h1:y2yVLIE/CSMCPXaHnSKXxu1spLPnglFLegmgdY23uuE=
|
google.golang.org/genproto/googleapis/api v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:klhJGKFyG8Tn50enBn7gizg4nXGXJ+jqEREdCWaPcV4=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 h1:TqExAhdPaB60Ux47Cn0oLV07rGnxZzIsaRhQaqS666A=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8/go.mod h1:lcTa1sDdWEIHMWlITnIczmw5w60CF9ffkb8Z+DVmmjA=
|
||||||
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw=
|
||||||
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
|
||||||
@ -1431,10 +1351,10 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp
|
|||||||
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc=
|
||||||
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
|
||||||
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
|
||||||
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0=
|
||||||
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA=
|
||||||
google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4=
|
google.golang.org/grpc v1.69.2 h1:U3S9QEtbXC0bYNvRtcoklF3xGtLViumSYxWykJS+7AU=
|
||||||
google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM=
|
google.golang.org/grpc v1.69.2/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
|
||||||
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
|
||||||
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
|
||||||
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
|
||||||
@ -1448,10 +1368,10 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba
|
|||||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io=
|
||||||
google.golang.org/protobuf v1.36.5/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
google.golang.org/protobuf v1.35.2/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A=
|
google.golang.org/protobuf v1.36.1 h1:yBPeRvTftaleIgM3PZ/WBIZ7XM/eEYAaEyCwvyjq/gk=
|
||||||
google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
|
google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||||
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U=
|
||||||
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
|
||||||
gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII=
|
gopkg.in/cenkalti/backoff.v2 v2.2.1 h1:eJ9UAg01/HIHG987TwxvnzK2MgxXq97YY6rYDpY9aII=
|
||||||
@ -1495,8 +1415,8 @@ gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
|
|||||||
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
|
||||||
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
|
||||||
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8=
|
||||||
gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q=
|
gotest.tools/v3 v3.5.1 h1:EENdUnS3pdur5nybKYIh2Vfgc8IUNBjxDPSjtiJcOzU=
|
||||||
gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA=
|
gotest.tools/v3 v3.5.1/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU=
|
||||||
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Language: \n"
|
|
||||||
"X-Generator: xgotext\n"
|
|
||||||
|
|
||||||
#: app.go:11
|
|
||||||
msgid "Manage apps"
|
|
||||||
msgstr ""
|
|
@ -1,20 +0,0 @@
|
|||||||
#, fuzzy
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2025-08-04 14:15+0000\n"
|
|
||||||
"PO-Revision-Date: 2025-08-04 14:15+0000\n"
|
|
||||||
"Last-Translator: 3wordchant <3wc.coopcloud@doesthisthing.work>\n"
|
|
||||||
"Language-Team: Spanish <https://translate.coopcloud.tech/projects/"
|
|
||||||
"co-op-cloud/abra/es/>\n"
|
|
||||||
"Language: es\n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: ENCODING\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=n != 1;\n"
|
|
||||||
"X-Generator: Weblate 5.12.2\n"
|
|
||||||
|
|
||||||
#: app.go:11
|
|
||||||
msgid "Manage apps"
|
|
||||||
msgstr "Gestionar aplicaciones"
|
|
124
pkg/app/app.go
124
pkg/app/app.go
@ -36,7 +36,7 @@ func Get(appName string) (App, error) {
|
|||||||
return App{}, err
|
return App{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("loaded app %s: %s", appName, app)
|
log.Debugf("retrieved %s for %s", app, appName)
|
||||||
|
|
||||||
return app, nil
|
return app, nil
|
||||||
}
|
}
|
||||||
@ -89,17 +89,8 @@ type App struct {
|
|||||||
Env envfile.AppEnv
|
Env envfile.AppEnv
|
||||||
Server string
|
Server string
|
||||||
Path string
|
Path string
|
||||||
}
|
|
||||||
|
|
||||||
// String outputs a human-friendly string representation.
|
status *Status
|
||||||
func (a App) String() string {
|
|
||||||
out := fmt.Sprintf("{name: %s, ", a.Name)
|
|
||||||
out += fmt.Sprintf("recipe: %s, ", a.Recipe)
|
|
||||||
out += fmt.Sprintf("domain: %s, ", a.Domain)
|
|
||||||
out += fmt.Sprintf("env %s, ", a.Env)
|
|
||||||
out += fmt.Sprintf("server %s, ", a.Server)
|
|
||||||
out += fmt.Sprintf("path %s}", a.Path)
|
|
||||||
return out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type aliases to make code hints easier to understand
|
// Type aliases to make code hints easier to understand
|
||||||
@ -143,6 +134,19 @@ func StackName(appName string) string {
|
|||||||
return stackName
|
return stackName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a App) Status() (Status, error) {
|
||||||
|
if a.status != nil {
|
||||||
|
return *a.status, nil
|
||||||
|
}
|
||||||
|
appStatuses, err := GetAppStatuses([]App{a}, true)
|
||||||
|
if err != nil {
|
||||||
|
return Status{}, err
|
||||||
|
}
|
||||||
|
appStatus := appStatuses[a.StackName()]
|
||||||
|
a.status = &appStatus
|
||||||
|
return appStatus, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Filters retrieves app filters for querying the container runtime. By default
|
// Filters retrieves app filters for querying the container runtime. By default
|
||||||
// it filters on all services in the app. It is also possible to pass an
|
// it filters on all services in the app. It is also possible to pass an
|
||||||
// otional list of service names, which get filtered instead.
|
// otional list of service names, which get filtered instead.
|
||||||
@ -246,6 +250,8 @@ func ReadAppEnvFile(appFile AppFile, name AppName) (App, error) {
|
|||||||
return App{}, fmt.Errorf("env file for %s couldn't be read: %s", name, err.Error())
|
return App{}, fmt.Errorf("env file for %s couldn't be read: %s", name, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debugf("read env %s from %s", env, appFile.Path)
|
||||||
|
|
||||||
app, err := NewApp(env, name, appFile)
|
app, err := NewApp(env, name, appFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return App{}, fmt.Errorf("env file for %s has issues: %s", name, err.Error())
|
return App{}, fmt.Errorf("env file for %s has issues: %s", name, err.Error())
|
||||||
@ -406,9 +412,17 @@ func SanitiseAppName(name string) string {
|
|||||||
return strings.ReplaceAll(name, ".", "_")
|
return strings.ReplaceAll(name, ".", "_")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Status struct {
|
||||||
|
Status string
|
||||||
|
Version string
|
||||||
|
Chaos bool
|
||||||
|
ChaosVersion string
|
||||||
|
AutoUpdate bool
|
||||||
|
}
|
||||||
|
|
||||||
// GetAppStatuses queries servers to check the deployment status of given apps.
|
// GetAppStatuses queries servers to check the deployment status of given apps.
|
||||||
func GetAppStatuses(apps []App, MachineReadable bool) (map[string]map[string]string, error) {
|
func GetAppStatuses(apps []App, MachineReadable bool) (map[string]Status, error) {
|
||||||
statuses := make(map[string]map[string]string)
|
statuses := make(map[string]Status)
|
||||||
|
|
||||||
servers := make(map[string]struct{})
|
servers := make(map[string]struct{})
|
||||||
for _, app := range apps {
|
for _, app := range apps {
|
||||||
@ -426,9 +440,7 @@ func GetAppStatuses(apps []App, MachineReadable bool) (map[string]map[string]str
|
|||||||
for server := range servers {
|
for server := range servers {
|
||||||
cl, err := client.New(server)
|
cl, err := client.New(server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(err)
|
return statuses, err
|
||||||
ch <- stack.StackStatus{}
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
go func(s string) {
|
go func(s string) {
|
||||||
@ -446,36 +458,32 @@ func GetAppStatuses(apps []App, MachineReadable bool) (map[string]map[string]str
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, service := range status.Services {
|
for _, service := range status.Services {
|
||||||
result := make(map[string]string)
|
result := Status{}
|
||||||
name := service.Spec.Labels[convert.LabelNamespace]
|
name := service.Spec.Labels[convert.LabelNamespace]
|
||||||
|
|
||||||
if _, ok := statuses[name]; !ok {
|
if _, ok := statuses[name]; !ok {
|
||||||
result["status"] = "deployed"
|
result.Status = "deployed"
|
||||||
}
|
}
|
||||||
|
|
||||||
labelKey := fmt.Sprintf("coop-cloud.%s.chaos", name)
|
labelKey := fmt.Sprintf("coop-cloud.%s.chaos", name)
|
||||||
chaos, ok := service.Spec.Labels[labelKey]
|
chaos, ok := service.Spec.Labels[labelKey]
|
||||||
if ok {
|
if ok {
|
||||||
result["chaos"] = chaos
|
result.Chaos = chaos == "true"
|
||||||
}
|
}
|
||||||
|
|
||||||
labelKey = fmt.Sprintf("coop-cloud.%s.chaos-version", name)
|
labelKey = fmt.Sprintf("coop-cloud.%s.chaos-version", name)
|
||||||
if chaosVersion, ok := service.Spec.Labels[labelKey]; ok {
|
if chaosVersion, ok := service.Spec.Labels[labelKey]; ok {
|
||||||
result["chaosVersion"] = chaosVersion
|
result.ChaosVersion = chaosVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
labelKey = fmt.Sprintf("coop-cloud.%s.autoupdate", name)
|
labelKey = fmt.Sprintf("coop-cloud.%s.autoupdate", name)
|
||||||
if autoUpdate, ok := service.Spec.Labels[labelKey]; ok {
|
if autoUpdate, ok := service.Spec.Labels[labelKey]; ok {
|
||||||
result["autoUpdate"] = autoUpdate
|
result.AutoUpdate = autoUpdate == "true"
|
||||||
} else {
|
|
||||||
result["autoUpdate"] = "false"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
labelKey = fmt.Sprintf("coop-cloud.%s.version", name)
|
labelKey = fmt.Sprintf("coop-cloud.%s.version", name)
|
||||||
if version, ok := service.Spec.Labels[labelKey]; ok {
|
if version, ok := service.Spec.Labels[labelKey]; ok {
|
||||||
result["version"] = version
|
result.Version = version
|
||||||
} else {
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
statuses[name] = result
|
statuses[name] = result
|
||||||
@ -505,13 +513,13 @@ func GetAppComposeConfig(recipe string, opts stack.Deploy, appEnv envfile.AppEnv
|
|||||||
func ExposeAllEnv(stackName string, compose *composetypes.Config, appEnv envfile.AppEnv) {
|
func ExposeAllEnv(stackName string, compose *composetypes.Config, appEnv envfile.AppEnv) {
|
||||||
for _, service := range compose.Services {
|
for _, service := range compose.Services {
|
||||||
if service.Name == "app" {
|
if service.Name == "app" {
|
||||||
log.Debugf("adding env vars to %s service config", stackName)
|
log.Debugf("add the following environment to the app service config of %s:", stackName)
|
||||||
for k, v := range appEnv {
|
for k, v := range appEnv {
|
||||||
_, exists := service.Environment[k]
|
_, exists := service.Environment[k]
|
||||||
if !exists {
|
if !exists {
|
||||||
value := v
|
value := v
|
||||||
service.Environment[k] = &value
|
service.Environment[k] = &value
|
||||||
log.Debugf("%s: %s: %s", stackName, k, value)
|
log.Debugf("add env var: %s value: %s to %s", k, value, stackName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -580,49 +588,6 @@ func ReadAbraShCmdNames(abraSh string) ([]string, error) {
|
|||||||
return cmdNames, nil
|
return cmdNames, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wipe removes the version from the app .env file.
|
|
||||||
func (a App) WipeRecipeVersion() error {
|
|
||||||
file, err := os.Open(a.Path)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
var (
|
|
||||||
lines []string
|
|
||||||
scanner = bufio.NewScanner(file)
|
|
||||||
)
|
|
||||||
|
|
||||||
for scanner.Scan() {
|
|
||||||
line := scanner.Text()
|
|
||||||
if !strings.HasPrefix(line, "RECIPE=") && !strings.HasPrefix(line, "TYPE=") {
|
|
||||||
lines = append(lines, line)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.HasPrefix(line, "#") {
|
|
||||||
lines = append(lines, line)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
splitted := strings.Split(line, ":")
|
|
||||||
lines = append(lines, splitted[0])
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := scanner.Err(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.WriteFile(a.Path, []byte(strings.Join(lines, "\n")), os.ModePerm); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("version wiped from %s.env", a.Domain)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// WriteRecipeVersion writes the recipe version to the app .env file.
|
|
||||||
func (a App) WriteRecipeVersion(version string, dryRun bool) error {
|
func (a App) WriteRecipeVersion(version string, dryRun bool) error {
|
||||||
file, err := os.Open(a.Path)
|
file, err := os.Open(a.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -630,13 +595,9 @@ func (a App) WriteRecipeVersion(version string, dryRun bool) error {
|
|||||||
}
|
}
|
||||||
defer file.Close()
|
defer file.Close()
|
||||||
|
|
||||||
var (
|
skipped := false
|
||||||
dirtyVersion string
|
scanner := bufio.NewScanner(file)
|
||||||
skipped bool
|
lines := []string{}
|
||||||
lines []string
|
|
||||||
scanner = bufio.NewScanner(file)
|
|
||||||
)
|
|
||||||
|
|
||||||
for scanner.Scan() {
|
for scanner.Scan() {
|
||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
if !strings.HasPrefix(line, "RECIPE=") && !strings.HasPrefix(line, "TYPE=") {
|
if !strings.HasPrefix(line, "RECIPE=") && !strings.HasPrefix(line, "TYPE=") {
|
||||||
@ -649,14 +610,13 @@ func (a App) WriteRecipeVersion(version string, dryRun bool) error {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(line, version) && !a.Recipe.Dirty && !strings.HasSuffix(line, config.DIRTY_DEFAULT) {
|
if strings.Contains(line, version) {
|
||||||
skipped = true
|
skipped = true
|
||||||
lines = append(lines, line)
|
lines = append(lines, line)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
splitted := strings.Split(line, ":")
|
splitted := strings.Split(line, ":")
|
||||||
|
|
||||||
line = fmt.Sprintf("%s:%s", splitted[0], version)
|
line = fmt.Sprintf("%s:%s", splitted[0], version)
|
||||||
lines = append(lines, line)
|
lines = append(lines, line)
|
||||||
}
|
}
|
||||||
@ -665,10 +625,6 @@ func (a App) WriteRecipeVersion(version string, dryRun bool) error {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if a.Recipe.Dirty && dirtyVersion != "" {
|
|
||||||
version = dirtyVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
if !dryRun {
|
if !dryRun {
|
||||||
if err := os.WriteFile(a.Path, []byte(strings.Join(lines, "\n")), os.ModePerm); err != nil {
|
if err := os.WriteFile(a.Path, []byte(strings.Join(lines, "\n")), os.ModePerm); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
|
@ -198,29 +198,3 @@ func compareFilter(t *testing.T, f1 filters.Args, f2 map[string]map[string]bool)
|
|||||||
t.Errorf("filters mismatch (-want +got):\n%s", diff)
|
t.Errorf("filters mismatch (-want +got):\n%s", diff)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestWriteRecipeVersionOverwrite(t *testing.T) {
|
|
||||||
app, err := appPkg.GetApp(testPkg.ExpectedAppFiles, testPkg.AppName)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer t.Cleanup(func() {
|
|
||||||
if err := app.WipeRecipeVersion(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
assert.Equal(t, "", app.Recipe.EnvVersion)
|
|
||||||
|
|
||||||
if err := app.WriteRecipeVersion("foo", false); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
app, err = appPkg.GetApp(testPkg.ExpectedAppFiles, testPkg.AppName)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, "foo", app.Recipe.EnvVersion)
|
|
||||||
}
|
|
||||||
|
@ -44,16 +44,6 @@ func SetChaosVersionLabel(compose *composetypes.Config, stackName string, chaosV
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func SetVersionLabel(compose *composetypes.Config, stackName string, version string) {
|
|
||||||
for _, service := range compose.Services {
|
|
||||||
if service.Name == "app" {
|
|
||||||
log.Debugf("set label 'coop-cloud.%s.version' to %v for %s", stackName, version, stackName)
|
|
||||||
labelKey := fmt.Sprintf("coop-cloud.%s.version", stackName)
|
|
||||||
service.Deploy.Labels[labelKey] = version
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// SetUpdateLabel adds env ENABLE_AUTO_UPDATE as label to enable/disable the
|
// SetUpdateLabel adds env ENABLE_AUTO_UPDATE as label to enable/disable the
|
||||||
// auto update process for this app. The default if this variable is not set is to disable
|
// auto update process for this app. The default if this variable is not set is to disable
|
||||||
// the auto update process.
|
// the auto update process.
|
||||||
|
@ -54,7 +54,7 @@ func RecipeNameComplete() ([]string, cobra.ShellCompDirective) {
|
|||||||
|
|
||||||
// RecipeVersionComplete completes versions for the recipe.
|
// RecipeVersionComplete completes versions for the recipe.
|
||||||
func RecipeVersionComplete(recipeName string) ([]string, cobra.ShellCompDirective) {
|
func RecipeVersionComplete(recipeName string) ([]string, cobra.ShellCompDirective) {
|
||||||
catl, err := recipe.ReadRecipeCatalogue(true)
|
catl, err := recipe.ReadRecipeCatalogue(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := fmt.Sprintf("autocomplete failed: %s", err)
|
err := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
return []string{err}, cobra.ShellCompDirectiveError
|
return []string{err}, cobra.ShellCompDirectiveError
|
||||||
|
@ -16,12 +16,13 @@ import (
|
|||||||
func EnsureCatalogue() error {
|
func EnsureCatalogue() error {
|
||||||
catalogueDir := path.Join(config.ABRA_DIR, "catalogue")
|
catalogueDir := path.Join(config.ABRA_DIR, "catalogue")
|
||||||
if _, err := os.Stat(catalogueDir); err != nil && os.IsNotExist(err) {
|
if _, err := os.Stat(catalogueDir); err != nil && os.IsNotExist(err) {
|
||||||
log.Debugf("catalogue is missing, retrieving now")
|
log.Warnf("local recipe catalogue is missing, retrieving now")
|
||||||
|
|
||||||
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, config.CATALOGUE_JSON_REPO_NAME)
|
url := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, config.CATALOGUE_JSON_REPO_NAME)
|
||||||
if err := gitPkg.Clone(catalogueDir, url); err != nil {
|
if err := gitPkg.Clone(catalogueDir, url); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debugf("cloned catalogue repository to %s", catalogueDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
"github.com/docker/docker/api/types/swarm"
|
|
||||||
"github.com/docker/docker/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetConfigs(cl *client.Client, ctx context.Context, server string, fs filters.Args) ([]swarm.Config, error) {
|
|
||||||
configList, err := cl.ConfigList(ctx, swarm.ConfigListOptions{Filters: fs})
|
|
||||||
if err != nil {
|
|
||||||
return configList, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return configList, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetConfigNames(configs []swarm.Config) []string {
|
|
||||||
var confNames []string
|
|
||||||
|
|
||||||
for _, conf := range configs {
|
|
||||||
confNames = append(confNames, conf.Spec.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
return confNames
|
|
||||||
}
|
|
||||||
|
|
||||||
func RemoveConfigs(cl *client.Client, ctx context.Context, configNames []string, force bool) error {
|
|
||||||
for _, confName := range configNames {
|
|
||||||
if err := cl.ConfigRemove(context.Background(), confName); err != nil {
|
|
||||||
return fmt.Errorf("conf %s: %s", confName, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
func StoreSecret(cl *client.Client, secretName, secretValue string) error {
|
func StoreSecret(cl *client.Client, secretName, secretValue, server string) error {
|
||||||
ann := swarm.Annotations{Name: secretName}
|
ann := swarm.Annotations{Name: secretName}
|
||||||
spec := swarm.SecretSpec{Annotations: ann, Data: []byte(secretValue)}
|
spec := swarm.SecretSpec{Annotations: ann, Data: []byte(secretValue)}
|
||||||
|
|
||||||
|
@ -90,7 +90,6 @@ func (a Abra) GetAbraDir() string {
|
|||||||
|
|
||||||
func (a Abra) GetServersDir() string { return path.Join(a.GetAbraDir(), "servers") }
|
func (a Abra) GetServersDir() string { return path.Join(a.GetAbraDir(), "servers") }
|
||||||
func (a Abra) GetRecipesDir() string { return path.Join(a.GetAbraDir(), "recipes") }
|
func (a Abra) GetRecipesDir() string { return path.Join(a.GetAbraDir(), "recipes") }
|
||||||
func (a Abra) GetLogsDir() string { return path.Join(a.GetAbraDir(), "logs") }
|
|
||||||
func (a Abra) GetVendorDir() string { return path.Join(a.GetAbraDir(), "vendor") }
|
func (a Abra) GetVendorDir() string { return path.Join(a.GetAbraDir(), "vendor") }
|
||||||
func (a Abra) GetBackupDir() string { return path.Join(a.GetAbraDir(), "backups") }
|
func (a Abra) GetBackupDir() string { return path.Join(a.GetAbraDir(), "backups") }
|
||||||
func (a Abra) GetCatalogueDir() string { return path.Join(a.GetAbraDir(), "catalogue") }
|
func (a Abra) GetCatalogueDir() string { return path.Join(a.GetAbraDir(), "catalogue") }
|
||||||
@ -98,18 +97,16 @@ func (a Abra) GetCatalogueDir() string { return path.Join(a.GetAbraDir(), "catal
|
|||||||
var config = LoadAbraConfig()
|
var config = LoadAbraConfig()
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ABRA_DIR = config.GetAbraDir()
|
ABRA_DIR = config.GetAbraDir()
|
||||||
SERVERS_DIR = config.GetServersDir()
|
SERVERS_DIR = config.GetServersDir()
|
||||||
RECIPES_DIR = config.GetRecipesDir()
|
RECIPES_DIR = config.GetRecipesDir()
|
||||||
LOGS_DIR = config.GetLogsDir()
|
VENDOR_DIR = config.GetVendorDir()
|
||||||
VENDOR_DIR = config.GetVendorDir()
|
BACKUP_DIR = config.GetBackupDir()
|
||||||
BACKUP_DIR = config.GetBackupDir()
|
CATALOGUE_DIR = config.GetCatalogueDir()
|
||||||
CATALOGUE_DIR = config.GetCatalogueDir()
|
RECIPES_JSON = path.Join(config.GetCatalogueDir(), "recipes.json")
|
||||||
RECIPES_JSON = path.Join(config.GetCatalogueDir(), "recipes.json")
|
REPOS_BASE_URL = "https://git.coopcloud.tech/coop-cloud"
|
||||||
REPOS_BASE_URL = "https://git.coopcloud.tech/coop-cloud"
|
CATALOGUE_JSON_REPO_NAME = "recipes-catalogue-json"
|
||||||
CATALOGUE_JSON_REPO_NAME = "recipes-catalogue-json"
|
SSH_URL_TEMPLATE = "ssh://git@git.coopcloud.tech:2222/coop-cloud/%s.git"
|
||||||
TOOLSHED_SSH_URL_TEMPLATE = "ssh://git@git.coopcloud.tech:2222/toolshed/%s.git"
|
|
||||||
RECIPES_SSH_URL_TEMPLATE = "ssh://git@git.coopcloud.tech:2222/coop-cloud/%s.git"
|
|
||||||
|
|
||||||
// NOTE(d1): please note, this was done purely out of laziness on our part
|
// NOTE(d1): please note, this was done purely out of laziness on our part
|
||||||
// AFAICR. it's easy to punt the value into the label because that is what is
|
// AFAICR. it's easy to punt the value into the label because that is what is
|
||||||
@ -117,10 +114,6 @@ var (
|
|||||||
// complained yet!
|
// complained yet!
|
||||||
CHAOS_DEFAULT = "false"
|
CHAOS_DEFAULT = "false"
|
||||||
|
|
||||||
DIRTY_DEFAULT = "+U"
|
|
||||||
|
|
||||||
NO_DOMAIN_DEFAULT = "N/A"
|
NO_DOMAIN_DEFAULT = "N/A"
|
||||||
NO_VERSION_DEFAULT = "N/A"
|
NO_VERSION_DEFAULT = "N/A"
|
||||||
|
|
||||||
UNKNOWN_DEFAULT = "unknown"
|
|
||||||
)
|
)
|
||||||
|
@ -26,16 +26,9 @@ func GetServers() ([]string, error) {
|
|||||||
return servers, err
|
return servers, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var filtered []string
|
log.Debugf("retrieved %v servers: %s", len(servers), servers)
|
||||||
for _, s := range servers {
|
|
||||||
if !strings.HasPrefix(s, ".") {
|
|
||||||
filtered = append(filtered, s)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("retrieved %v servers: %s", len(filtered), filtered)
|
return servers, nil
|
||||||
|
|
||||||
return filtered, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadServerNames retrieves all server names.
|
// ReadServerNames retrieves all server names.
|
||||||
|
@ -15,7 +15,7 @@ func TestEnsureDomainsResolveSameIPv4(t *testing.T) {
|
|||||||
}{
|
}{
|
||||||
// NOTE(d1): DNS records get checked, so use something that is maintained
|
// NOTE(d1): DNS records get checked, so use something that is maintained
|
||||||
// within the federation. if you're here because of a failing test, try
|
// within the federation. if you're here because of a failing test, try
|
||||||
// `dig +short <domain>` to ensure stuff matches first! If flakyness
|
// `dig +short <app>` to ensure stuff matches first! If flakyness
|
||||||
// becomes an issue we can look into mocking
|
// becomes an issue we can look into mocking
|
||||||
{"docs.coopcloud.tech", "swarm-0.coopcloud.tech", true},
|
{"docs.coopcloud.tech", "swarm-0.coopcloud.tech", true},
|
||||||
{"docs.coopcloud.tech", "coopcloud.tech", true},
|
{"docs.coopcloud.tech", "coopcloud.tech", true},
|
||||||
@ -43,7 +43,7 @@ func TestEnsureDomainsResolveSameIPv4(t *testing.T) {
|
|||||||
func TestEnsureIpv4(t *testing.T) {
|
func TestEnsureIpv4(t *testing.T) {
|
||||||
// NOTE(d1): DNS records get checked, so use something that is maintained
|
// NOTE(d1): DNS records get checked, so use something that is maintained
|
||||||
// within the federation. if you're here because of a failing test, try `dig
|
// within the federation. if you're here because of a failing test, try `dig
|
||||||
// +short <domain>` to ensure stuff matches first! If flakyness becomes an
|
// +short <app>` to ensure stuff matches first! If flakyness becomes an
|
||||||
// issue we can look into mocking
|
// issue we can look into mocking
|
||||||
domainName := "collabora.ostrom.collective.tools"
|
domainName := "collabora.ostrom.collective.tools"
|
||||||
serverName := "ostrom.collective.tools"
|
serverName := "ostrom.collective.tools"
|
||||||
|
@ -8,9 +8,14 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"git.coopcloud.tech/toolshed/godotenv"
|
"git.coopcloud.tech/coop-cloud/godotenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// envVarModifiers is a list of env var modifier strings. These are added to
|
||||||
|
// env vars as comments and modify their processing by Abra, e.g. determining
|
||||||
|
// how long secrets should be.
|
||||||
|
var envVarModifiers = []string{"length"}
|
||||||
|
|
||||||
// AppEnv is a map of the values in an apps env config
|
// AppEnv is a map of the values in an apps env config
|
||||||
type AppEnv = map[string]string
|
type AppEnv = map[string]string
|
||||||
|
|
||||||
@ -26,6 +31,8 @@ func ReadEnv(filePath string) (AppEnv, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debugf("read %s from %s", envVars, filePath)
|
||||||
|
|
||||||
return envVars, nil
|
return envVars, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -192,7 +192,7 @@ func TestEnvVarCommentsRemoved(t *testing.T) {
|
|||||||
|
|
||||||
envVar, exists = envSample["SECRET_TEST_PASS_TWO_VERSION"]
|
envVar, exists = envSample["SECRET_TEST_PASS_TWO_VERSION"]
|
||||||
if !exists {
|
if !exists {
|
||||||
t.Fatal("SECRET_TEST_PASS_TWO_VERSION env var should be present in .env.sample")
|
t.Fatal("WITH_COMMENT env var should be present in .env.sample")
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(envVar, "length") {
|
if strings.Contains(envVar, "length") {
|
||||||
|
@ -13,15 +13,11 @@ import (
|
|||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/config"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/schollz/progressbar/v3"
|
"github.com/schollz/progressbar/v3"
|
||||||
)
|
)
|
||||||
|
|
||||||
var BoldStyle = lipgloss.NewStyle().
|
var BoldStyle = lipgloss.NewStyle().
|
||||||
Bold(true)
|
|
||||||
|
|
||||||
var BoldUnderlineStyle = lipgloss.NewStyle().
|
|
||||||
Bold(true).
|
Bold(true).
|
||||||
Underline(true)
|
Underline(true)
|
||||||
|
|
||||||
@ -106,6 +102,7 @@ func CreateOverview(header string, rows [][]string) string {
|
|||||||
var borderStyle = lipgloss.NewStyle().
|
var borderStyle = lipgloss.NewStyle().
|
||||||
BorderStyle(lipgloss.ThickBorder()).
|
BorderStyle(lipgloss.ThickBorder()).
|
||||||
Padding(0, 1, 0, 1).
|
Padding(0, 1, 0, 1).
|
||||||
|
MaxWidth(79).
|
||||||
BorderForeground(lipgloss.Color("63"))
|
BorderForeground(lipgloss.Color("63"))
|
||||||
|
|
||||||
var headerStyle = lipgloss.NewStyle().
|
var headerStyle = lipgloss.NewStyle().
|
||||||
@ -113,7 +110,9 @@ func CreateOverview(header string, rows [][]string) string {
|
|||||||
Bold(true).
|
Bold(true).
|
||||||
PaddingBottom(1)
|
PaddingBottom(1)
|
||||||
|
|
||||||
var leftStyle = lipgloss.NewStyle()
|
var leftStyle = lipgloss.NewStyle().
|
||||||
|
Bold(true)
|
||||||
|
|
||||||
var rightStyle = lipgloss.NewStyle()
|
var rightStyle = lipgloss.NewStyle()
|
||||||
|
|
||||||
var longest int
|
var longest int
|
||||||
@ -125,10 +124,6 @@ func CreateOverview(header string, rows [][]string) string {
|
|||||||
|
|
||||||
var renderedRows []string
|
var renderedRows []string
|
||||||
for _, row := range rows {
|
for _, row := range rows {
|
||||||
if len(row) < 2 {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(row) > 2 {
|
if len(row) > 2 {
|
||||||
panic("CreateOverview: only accepts rows of len == 2")
|
panic("CreateOverview: only accepts rows of len == 2")
|
||||||
}
|
}
|
||||||
@ -143,21 +138,10 @@ func CreateOverview(header string, rows [][]string) string {
|
|||||||
offset = offset + " "
|
offset = offset + " "
|
||||||
}
|
}
|
||||||
|
|
||||||
rendered := horizontal(leftStyle.Render(row[0]), offset, rightStyle.Render(row[1]))
|
renderedRows = append(
|
||||||
|
renderedRows,
|
||||||
if row[1] == "---" {
|
horizontal(leftStyle.Render(row[0]), offset, rightStyle.Render(row[1])),
|
||||||
rendered = horizontal(
|
)
|
||||||
leftStyle.
|
|
||||||
Bold(true).
|
|
||||||
Underline(true).
|
|
||||||
PaddingTop(1).
|
|
||||||
Render(row[0]),
|
|
||||||
offset,
|
|
||||||
rightStyle.Render(""),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
renderedRows = append(renderedRows, rendered)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body := strings.Builder{}
|
body := strings.Builder{}
|
||||||
@ -217,6 +201,7 @@ func CreateProgressbar(length int, title string) *progressbar.ProgressBar {
|
|||||||
progressbar.OptionClearOnFinish(),
|
progressbar.OptionClearOnFinish(),
|
||||||
progressbar.OptionSetPredictTime(false),
|
progressbar.OptionSetPredictTime(false),
|
||||||
progressbar.OptionShowCount(),
|
progressbar.OptionShowCount(),
|
||||||
|
progressbar.OptionFullWidth(),
|
||||||
progressbar.OptionSetDescription(title),
|
progressbar.OptionSetDescription(title),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -257,18 +242,3 @@ func ByteCountSI(b uint64) string {
|
|||||||
|
|
||||||
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
|
return fmt.Sprintf("%.1f %cB", float64(b)/float64(div), "kMGTPE"[exp])
|
||||||
}
|
}
|
||||||
|
|
||||||
// BoldDirtyDefault ensures a dirty modifier is rendered in bold.
|
|
||||||
func BoldDirtyDefault(v string) string {
|
|
||||||
if strings.HasSuffix(v, config.DIRTY_DEFAULT) {
|
|
||||||
vBold := BoldStyle.Render(config.DIRTY_DEFAULT)
|
|
||||||
v = strings.Replace(v, config.DIRTY_DEFAULT, vBold, 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddDirtyMarker adds the dirty marker to a version string.
|
|
||||||
func AddDirtyMarker(v string) string {
|
|
||||||
return fmt.Sprintf("%s%s", v, config.DIRTY_DEFAULT)
|
|
||||||
}
|
|
||||||
|
@ -1,11 +0,0 @@
|
|||||||
package formatter
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestBoldDirtyDefault(t *testing.T) {
|
|
||||||
assert.Equal(t, "foo", BoldDirtyDefault("foo"))
|
|
||||||
}
|
|
@ -1,10 +1,9 @@
|
|||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
@ -12,94 +11,39 @@ import (
|
|||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// gitCloneIgnoreErr checks whether we can ignore a git clone error or not.
|
// Clone runs a git clone which accounts for different default branches.
|
||||||
func gitCloneIgnoreErr(err error) bool {
|
|
||||||
if strings.Contains(err.Error(), "authentication required") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if strings.Contains(err.Error(), "remote repository is empty") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// Clone runs a git clone which accounts for different default branches. This
|
|
||||||
// function respects Ctrl+C (SIGINT) calls from the user, cancelling the
|
|
||||||
// context and deleting the (typically) half-baked clone of the repository.
|
|
||||||
// This avoids broken state for future clone / recipe ops.
|
|
||||||
func Clone(dir, url string) error {
|
func Clone(dir, url string) error {
|
||||||
ctx := context.Background()
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||||
ctx, cancelCtx := context.WithCancel(ctx)
|
log.Debugf("%s does not exist, attempting to git clone from %s", dir, url)
|
||||||
|
|
||||||
sigIntCh := make(chan os.Signal, 1)
|
_, err := git.PlainClone(dir, false, &git.CloneOptions{
|
||||||
signal.Notify(sigIntCh, os.Interrupt)
|
URL: url,
|
||||||
defer func() {
|
Tags: git.AllTags,
|
||||||
signal.Stop(sigIntCh)
|
ReferenceName: plumbing.ReferenceName("refs/heads/master"),
|
||||||
cancelCtx()
|
SingleBranch: true,
|
||||||
}()
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Debugf("cloning %s default branch failed, attempting from main branch", url)
|
||||||
|
|
||||||
errCh := make(chan error)
|
_, err := git.PlainClone(dir, false, &git.CloneOptions{
|
||||||
|
|
||||||
go func() {
|
|
||||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
|
||||||
log.Debugf("git clone: %s", url)
|
|
||||||
|
|
||||||
_, err := git.PlainCloneContext(ctx, dir, false, &git.CloneOptions{
|
|
||||||
URL: url,
|
URL: url,
|
||||||
Tags: git.AllTags,
|
Tags: git.AllTags,
|
||||||
ReferenceName: plumbing.ReferenceName("refs/heads/main"),
|
ReferenceName: plumbing.ReferenceName("refs/heads/main"),
|
||||||
SingleBranch: true,
|
SingleBranch: true,
|
||||||
})
|
})
|
||||||
|
|
||||||
if err != nil && gitCloneIgnoreErr(err) {
|
|
||||||
log.Debugf("git clone: %s cloned successfully", dir)
|
|
||||||
errCh <- nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := ctx.Err(); err != nil {
|
|
||||||
errCh <- fmt.Errorf("git clone %s: cancelled due to interrupt", dir)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug("git clone: main branch failed, attempting master branch")
|
if strings.Contains(err.Error(), "authentication required") {
|
||||||
|
name := filepath.Base(dir)
|
||||||
_, err := git.PlainCloneContext(ctx, dir, false, &git.CloneOptions{
|
return fmt.Errorf("unable to clone %s, does %s exist?", name, url)
|
||||||
URL: url,
|
|
||||||
Tags: git.AllTags,
|
|
||||||
ReferenceName: plumbing.ReferenceName("refs/heads/master"),
|
|
||||||
SingleBranch: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err != nil && gitCloneIgnoreErr(err) {
|
|
||||||
log.Debugf("git clone: %s cloned successfully", dir)
|
|
||||||
errCh <- nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
return err
|
||||||
errCh <- err
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("git clone: %s cloned successfully", dir)
|
|
||||||
} else {
|
|
||||||
log.Debugf("git clone: %s already exists", dir)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
errCh <- nil
|
log.Debugf("%s has been git cloned successfully", dir)
|
||||||
}()
|
} else {
|
||||||
|
log.Debugf("%s already exists", dir)
|
||||||
select {
|
|
||||||
case <-sigIntCh:
|
|
||||||
cancelCtx()
|
|
||||||
fmt.Println() // NOTE(d1): newline after ^C
|
|
||||||
if err := os.RemoveAll(dir); err != nil {
|
|
||||||
return fmt.Errorf("unable to clean up git clone of %s: %s", dir, err)
|
|
||||||
}
|
|
||||||
return fmt.Errorf("git clone %s: cancelled due to interrupt", dir)
|
|
||||||
case err := <-errCh:
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
@ -1,48 +0,0 @@
|
|||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path"
|
|
||||||
"syscall"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/config"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestClone(t *testing.T) {
|
|
||||||
dir := path.Join(config.RECIPES_DIR, "gitea")
|
|
||||||
os.RemoveAll(dir)
|
|
||||||
|
|
||||||
gitURL := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, "gitea")
|
|
||||||
if err := Clone(dir, gitURL); err != nil {
|
|
||||||
t.Fatalf("unable to git clone gitea: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) {
|
|
||||||
t.Fatal("gitea repo was not cloned successfully")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCancelGitClone(t *testing.T) {
|
|
||||||
dir := path.Join(config.RECIPES_DIR, "gitea")
|
|
||||||
os.RemoveAll(dir)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
p, err := os.FindProcess(os.Getpid())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unable to find current process: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
p.Signal(syscall.SIGINT)
|
|
||||||
}()
|
|
||||||
|
|
||||||
gitURL := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, "gitea")
|
|
||||||
if err := Clone(dir, gitURL); err == nil {
|
|
||||||
t.Fatal("cloning should have been interrupted")
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := os.Stat(dir); err != nil && !os.IsNotExist(err) {
|
|
||||||
t.Fatal("recipe repo was not deleted")
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,11 +15,9 @@ func Init(repoPath string, commit bool, gitName, gitEmail string) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("git init: %s", err)
|
return fmt.Errorf("git init: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = SwitchToMain(repo); err != nil {
|
if err = SwitchToMain(repo); err != nil {
|
||||||
return fmt.Errorf("git branch rename: %s", err)
|
return fmt.Errorf("git branch rename: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("initialised new git repo in %s", repoPath)
|
log.Debugf("initialised new git repo in %s", repoPath)
|
||||||
|
|
||||||
if commit {
|
if commit {
|
||||||
@ -41,11 +39,9 @@ func Init(repoPath string, commit bool, gitName, gitEmail string) error {
|
|||||||
if gitName != "" && gitEmail != "" {
|
if gitName != "" && gitEmail != "" {
|
||||||
author = &object.Signature{Name: gitName, Email: gitEmail}
|
author = &object.Signature{Name: gitName, Email: gitEmail}
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err = commitWorktree.Commit("init", &git.CommitOptions{Author: author}); err != nil {
|
if _, err = commitWorktree.Commit("init", &git.CommitOptions{Author: author}); err != nil {
|
||||||
return fmt.Errorf("git commit: %s", err)
|
return fmt.Errorf("git commit: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("init committed all files for new git repo in %s", repoPath)
|
log.Debugf("init committed all files for new git repo in %s", repoPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,18 +54,14 @@ func SwitchToMain(repo *git.Repository) error {
|
|||||||
if err := repo.Storer.SetReference(ref); err != nil {
|
if err := repo.Storer.SetReference(ref); err != nil {
|
||||||
return fmt.Errorf("set reference: %s", err)
|
return fmt.Errorf("set reference: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := repo.Config()
|
cfg, err := repo.Config()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("repo config: %s", err)
|
return fmt.Errorf("repo config: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg.Init.DefaultBranch = "main"
|
cfg.Init.DefaultBranch = "main"
|
||||||
if err := repo.SetConfig(cfg); err != nil {
|
if err := repo.SetConfig(cfg); err != nil {
|
||||||
return fmt.Errorf("repo set config: %s", err)
|
return fmt.Errorf("repo set config: %s", err)
|
||||||
}
|
}
|
||||||
|
log.Debug("set 'main' as the default branch.")
|
||||||
log.Debug("set 'main' as the default branch")
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
@ -19,16 +17,12 @@ import (
|
|||||||
func IsClean(repoPath string) (bool, error) {
|
func IsClean(repoPath string) (bool, error) {
|
||||||
repo, err := git.PlainOpen(repoPath)
|
repo, err := git.PlainOpen(repoPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, git.ErrRepositoryNotExists) {
|
return false, err
|
||||||
return false, git.ErrRepositoryNotExists
|
|
||||||
}
|
|
||||||
|
|
||||||
return false, fmt.Errorf("unable to open %s: %s", repoPath, err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
worktree, err := repo.Worktree()
|
worktree, err := repo.Worktree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("unable to open worktree of %s: %s", repoPath, err)
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
patterns, err := GetExcludesFiles()
|
patterns, err := GetExcludesFiles()
|
||||||
@ -42,14 +36,13 @@ func IsClean(repoPath string) (bool, error) {
|
|||||||
|
|
||||||
status, err := worktree.Status()
|
status, err := worktree.Status()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("unable to query status of %s: %s", repoPath, err)
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if status.String() != "" {
|
if status.String() != "" {
|
||||||
noNewline := strings.TrimSuffix(status.String(), "\n")
|
log.Debugf("discovered git status in %s: %s", repoPath, status.String())
|
||||||
log.Debugf("git status: %s: %s", repoPath, noNewline)
|
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("git status: %s: clean", repoPath)
|
log.Debugf("discovered clean git status in %s", repoPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return status.IsClean(), nil
|
return status.IsClean(), nil
|
||||||
|
@ -1,15 +0,0 @@
|
|||||||
package git
|
|
||||||
|
|
||||||
import (
|
|
||||||
"errors"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIsClean(t *testing.T) {
|
|
||||||
isClean, err := IsClean("/tmp")
|
|
||||||
assert.Equal(t, isClean, false)
|
|
||||||
assert.True(t, errors.Is(err, git.ErrRepositoryNotExists))
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
package lang
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
func GetLocale() string {
|
|
||||||
if loc := os.Getenv("LC_MESSAGES"); loc != "" {
|
|
||||||
return NormalizeLocale(loc)
|
|
||||||
}
|
|
||||||
|
|
||||||
if loc := os.Getenv("LANG"); loc != "" {
|
|
||||||
return NormalizeLocale(loc)
|
|
||||||
}
|
|
||||||
|
|
||||||
return "C.UTF-8"
|
|
||||||
}
|
|
||||||
|
|
||||||
func NormalizeLocale(loc string) string {
|
|
||||||
if idx := strings.Index(loc, "."); idx != -1 {
|
|
||||||
return loc[:idx]
|
|
||||||
}
|
|
||||||
|
|
||||||
if idx := strings.Index(loc, "@"); idx != -1 {
|
|
||||||
return loc[:idx]
|
|
||||||
}
|
|
||||||
|
|
||||||
return loc
|
|
||||||
}
|
|
@ -15,10 +15,8 @@ import (
|
|||||||
"github.com/go-git/go-git/v5/plumbing"
|
"github.com/go-git/go-git/v5/plumbing"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var Warn = "warn"
|
||||||
Warn = "warn"
|
var Critical = "critical"
|
||||||
Critical = "critical"
|
|
||||||
)
|
|
||||||
|
|
||||||
type LintFunction func(recipe.Recipe) (bool, error)
|
type LintFunction func(recipe.Recipe) (bool, error)
|
||||||
|
|
||||||
@ -184,8 +182,6 @@ var LintRules = map[string][]LintRule{
|
|||||||
func LintForErrors(recipe recipe.Recipe) error {
|
func LintForErrors(recipe recipe.Recipe) error {
|
||||||
log.Debugf("linting for critical errors in %s configs", recipe.Name)
|
log.Debugf("linting for critical errors in %s configs", recipe.Name)
|
||||||
|
|
||||||
var errors string
|
|
||||||
|
|
||||||
for level := range LintRules {
|
for level := range LintRules {
|
||||||
if level != "error" {
|
if level != "error" {
|
||||||
continue
|
continue
|
||||||
@ -198,18 +194,14 @@ func LintForErrors(recipe recipe.Recipe) error {
|
|||||||
|
|
||||||
ok, err := rule.Function(recipe)
|
ok, err := rule.Function(recipe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors += fmt.Sprintf("\nlint %s: %s", rule.Ref, err)
|
return err
|
||||||
}
|
}
|
||||||
if !ok {
|
if !ok {
|
||||||
errors += fmt.Sprintf("\n * %s (%s)", rule.Description, rule.Ref)
|
return fmt.Errorf("lint error in %s configs: \"%s\" failed lint checks (%s)", recipe.Name, rule.Description, rule.Ref)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(errors) > 0 {
|
|
||||||
return fmt.Errorf("recipe '%s' failed lint checks:\n"+errors[1:], recipe.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("linting successful, %s is well configured", recipe.Name)
|
log.Debugf("linting successful, %s is well configured", recipe.Name)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -417,7 +409,7 @@ func LintHasPublishedVersion(recipe recipe.Recipe) (bool, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func LintMetadataFilledIn(r recipe.Recipe) (bool, error) {
|
func LintMetadataFilledIn(r recipe.Recipe) (bool, error) {
|
||||||
features, category, _, err := recipe.GetRecipeFeaturesAndCategory(r)
|
features, category, err := recipe.GetRecipeFeaturesAndCategory(r)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -2,10 +2,10 @@
|
|||||||
package log
|
package log
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math"
|
|
||||||
"os"
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
"github.com/charmbracelet/lipgloss"
|
||||||
charmLog "github.com/charmbracelet/log"
|
charmLog "github.com/charmbracelet/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -35,12 +35,41 @@ var DebugLevel = charmLog.DebugLevel
|
|||||||
var SetOutput = charmLog.SetOutput
|
var SetOutput = charmLog.SetOutput
|
||||||
var SetReportCaller = charmLog.SetReportCaller
|
var SetReportCaller = charmLog.SetReportCaller
|
||||||
|
|
||||||
type f func() (tea.Model, error)
|
func Styles() *charmLog.Styles {
|
||||||
|
styles := charmLog.DefaultStyles()
|
||||||
|
|
||||||
func Without(fn f) (tea.Model, error) {
|
styles.Levels = map[charmLog.Level]lipgloss.Style{
|
||||||
l := Logger.GetLevel()
|
charmLog.DebugLevel: lipgloss.NewStyle().
|
||||||
Logger.SetLevel(math.MaxInt)
|
SetString(strings.ToUpper(DebugLevel.String())).
|
||||||
m, err := fn()
|
Bold(true).
|
||||||
Logger.SetLevel(l)
|
Padding(0, 1, 0, 1).
|
||||||
return m, err
|
Background(lipgloss.Color("63")).
|
||||||
|
Foreground(lipgloss.Color("15")),
|
||||||
|
charmLog.InfoLevel: lipgloss.NewStyle().
|
||||||
|
SetString(strings.ToUpper(charmLog.InfoLevel.String())).
|
||||||
|
Bold(true).
|
||||||
|
Padding(0, 1, 0, 1).
|
||||||
|
Background(lipgloss.Color("86")).
|
||||||
|
Foreground(lipgloss.Color("16")),
|
||||||
|
charmLog.WarnLevel: lipgloss.NewStyle().
|
||||||
|
SetString(strings.ToUpper(charmLog.WarnLevel.String())).
|
||||||
|
Bold(true).
|
||||||
|
Padding(0, 1, 0, 1).
|
||||||
|
Background(lipgloss.Color("192")).
|
||||||
|
Foreground(lipgloss.Color("16")),
|
||||||
|
charmLog.ErrorLevel: lipgloss.NewStyle().
|
||||||
|
SetString(strings.ToUpper(charmLog.ErrorLevel.String())).
|
||||||
|
Bold(true).
|
||||||
|
Padding(0, 1, 0, 1).
|
||||||
|
Background(lipgloss.Color("204")).
|
||||||
|
Foreground(lipgloss.Color("15")),
|
||||||
|
charmLog.FatalLevel: lipgloss.NewStyle().
|
||||||
|
SetString(strings.ToUpper(charmLog.FatalLevel.String())).
|
||||||
|
Bold(true).
|
||||||
|
Padding(0, 1, 0, 1).
|
||||||
|
Background(lipgloss.Color("134")).
|
||||||
|
Foreground(lipgloss.Color("15")),
|
||||||
|
}
|
||||||
|
|
||||||
|
return styles
|
||||||
}
|
}
|
||||||
|
104
pkg/logs/logs.go
104
pkg/logs/logs.go
@ -1,104 +0,0 @@
|
|||||||
package logs
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"context"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"sync"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types"
|
|
||||||
containerTypes "github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
dockerClient "github.com/docker/docker/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
type TailOpts struct {
|
|
||||||
AppName string
|
|
||||||
Services []string
|
|
||||||
StdErr bool
|
|
||||||
Since string
|
|
||||||
Buffer *[]string
|
|
||||||
ToBuffer bool
|
|
||||||
Filters filters.Args
|
|
||||||
}
|
|
||||||
|
|
||||||
// TailLogs gathers logs for the given app with optional service names to be
|
|
||||||
// filtered on. These logs can be printed to os.Stdout or gathered to a buffer.
|
|
||||||
func TailLogs(
|
|
||||||
cl *dockerClient.Client,
|
|
||||||
opts TailOpts,
|
|
||||||
) error {
|
|
||||||
sigIntCh := make(chan os.Signal, 1)
|
|
||||||
signal.Notify(sigIntCh, os.Interrupt)
|
|
||||||
defer signal.Stop(sigIntCh)
|
|
||||||
|
|
||||||
services, err := cl.ServiceList(
|
|
||||||
context.Background(),
|
|
||||||
types.ServiceListOptions{Filters: opts.Filters},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
errCh := make(chan error)
|
|
||||||
waitCh := make(chan struct{})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for _, service := range services {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(serviceID string) {
|
|
||||||
tail := "50"
|
|
||||||
if opts.ToBuffer {
|
|
||||||
// NOTE(d1): more logs from before deployment when analysing via file
|
|
||||||
tail = "150"
|
|
||||||
}
|
|
||||||
|
|
||||||
logs, err := cl.ServiceLogs(context.Background(), serviceID, containerTypes.LogsOptions{
|
|
||||||
ShowStderr: true,
|
|
||||||
ShowStdout: !opts.StdErr,
|
|
||||||
Since: opts.Since,
|
|
||||||
Until: "",
|
|
||||||
Timestamps: true,
|
|
||||||
Follow: true,
|
|
||||||
Tail: tail,
|
|
||||||
Details: false,
|
|
||||||
})
|
|
||||||
|
|
||||||
if err == nil {
|
|
||||||
defer logs.Close()
|
|
||||||
if opts.ToBuffer {
|
|
||||||
buf := bufio.NewScanner(logs)
|
|
||||||
for buf.Scan() {
|
|
||||||
line := fmt.Sprintf("%s: %s", service.Spec.Name, buf.Text())
|
|
||||||
*opts.Buffer = append(*opts.Buffer, line)
|
|
||||||
}
|
|
||||||
logs.Close()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err = io.Copy(os.Stdout, logs); err != nil && err != io.EOF {
|
|
||||||
errCh <- fmt.Errorf("tailLogs: unable to copy buffer: %s", err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(service.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
wg.Wait()
|
|
||||||
close(waitCh)
|
|
||||||
}()
|
|
||||||
|
|
||||||
select {
|
|
||||||
case <-waitCh:
|
|
||||||
return nil
|
|
||||||
case <-sigIntCh:
|
|
||||||
return nil
|
|
||||||
case err := <-errCh:
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
@ -3,34 +3,23 @@ package recipe
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"slices"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/config"
|
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
gitPkg "coopcloud.tech/abra/pkg/git"
|
gitPkg "coopcloud.tech/abra/pkg/git"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/tagcmp"
|
|
||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EnsureContext struct {
|
// Ensure makes sure the recipe exists, is up to date and has the latest version checked out.
|
||||||
Chaos bool
|
func (r Recipe) Ensure(chaos bool, offline bool) error {
|
||||||
Offline bool
|
|
||||||
IgnoreEnvVersion bool
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ensure makes sure the recipe exists, is up to date and has the specific
|
|
||||||
// version checked out.
|
|
||||||
func (r Recipe) Ensure(ctx EnsureContext) error {
|
|
||||||
if err := r.EnsureExists(); err != nil {
|
if err := r.EnsureExists(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctx.Chaos {
|
if chaos {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -38,19 +27,15 @@ func (r Recipe) Ensure(ctx EnsureContext) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ctx.Offline {
|
if !offline {
|
||||||
if err := r.EnsureUpToDate(); err != nil {
|
if err := r.EnsureUpToDate(); err != nil {
|
||||||
return err
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.EnvVersion != "" && !ctx.IgnoreEnvVersion {
|
if r.Version != "" {
|
||||||
log.Debugf("ensuring env version %s", r.EnvVersion)
|
log.Debugf("ensuring version %s", r.Version)
|
||||||
if strings.Contains(r.EnvVersion, "+U") {
|
if _, err := r.EnsureVersion(r.Version); err != nil {
|
||||||
return fmt.Errorf("can not redeploy chaos version (%s) without --chaos", r.EnvVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := r.EnsureVersion(r.EnvVersion); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,6 +52,7 @@ func (r Recipe) Ensure(ctx EnsureContext) error {
|
|||||||
// EnsureExists ensures that the recipe is locally cloned
|
// EnsureExists ensures that the recipe is locally cloned
|
||||||
func (r Recipe) EnsureExists() error {
|
func (r Recipe) EnsureExists() error {
|
||||||
if _, err := os.Stat(r.Dir); os.IsNotExist(err) {
|
if _, err := os.Stat(r.Dir); os.IsNotExist(err) {
|
||||||
|
log.Debugf("%s does not exist, attemmpting to clone", r.Dir)
|
||||||
if err := gitPkg.Clone(r.Dir, r.GitURL); err != nil {
|
if err := gitPkg.Clone(r.Dir, r.GitURL); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -79,41 +65,6 @@ func (r Recipe) EnsureExists() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsChaosCommit determines if a version sttring is a chaos commit or not.
|
|
||||||
func (r Recipe) IsChaosCommit(version string) (bool, error) {
|
|
||||||
isChaosCommit := false
|
|
||||||
|
|
||||||
if err := gitPkg.EnsureGitRepo(r.Dir); err != nil {
|
|
||||||
return isChaosCommit, err
|
|
||||||
}
|
|
||||||
|
|
||||||
repo, err := git.PlainOpen(r.Dir)
|
|
||||||
if err != nil {
|
|
||||||
return isChaosCommit, err
|
|
||||||
}
|
|
||||||
|
|
||||||
tags, err := repo.Tags()
|
|
||||||
if err != nil {
|
|
||||||
return isChaosCommit, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var tagRef plumbing.ReferenceName
|
|
||||||
if err := tags.ForEach(func(ref *plumbing.Reference) (err error) {
|
|
||||||
if ref.Name().Short() == version {
|
|
||||||
tagRef = ref.Name()
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}); err != nil {
|
|
||||||
return isChaosCommit, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if tagRef.String() == "" {
|
|
||||||
isChaosCommit = true
|
|
||||||
}
|
|
||||||
|
|
||||||
return isChaosCommit, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// EnsureVersion checks whether a specific version exists for a recipe.
|
// EnsureVersion checks whether a specific version exists for a recipe.
|
||||||
func (r Recipe) EnsureVersion(version string) (bool, error) {
|
func (r Recipe) EnsureVersion(version string) (bool, error) {
|
||||||
isChaosCommit := false
|
isChaosCommit := false
|
||||||
@ -186,7 +137,8 @@ func (r Recipe) EnsureIsClean() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !isClean {
|
if !isClean {
|
||||||
return fmt.Errorf("%s (%s) has locally unstaged changes?", r.Name, r.Dir)
|
msg := "%s (%s) has locally unstaged changes? please commit/remove your changes before proceeding"
|
||||||
|
return fmt.Errorf(msg, r.Name, r.Dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -278,18 +230,8 @@ func (r Recipe) EnsureUpToDate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsDirty checks whether a recipe is dirty or not.
|
|
||||||
func (r *Recipe) IsDirty() (bool, error) {
|
|
||||||
isClean, err := gitPkg.IsClean(r.Dir)
|
|
||||||
if err != nil {
|
|
||||||
return false, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return !isClean, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// ChaosVersion constructs a chaos mode recipe version.
|
// ChaosVersion constructs a chaos mode recipe version.
|
||||||
func (r *Recipe) ChaosVersion() (string, error) {
|
func (r Recipe) ChaosVersion() (string, error) {
|
||||||
var version string
|
var version string
|
||||||
|
|
||||||
head, err := r.Head()
|
head, err := r.Head()
|
||||||
@ -299,12 +241,13 @@ func (r *Recipe) ChaosVersion() (string, error) {
|
|||||||
|
|
||||||
version = formatter.SmallSHA(head.String())
|
version = formatter.SmallSHA(head.String())
|
||||||
|
|
||||||
dirty, err := r.IsDirty()
|
isClean, err := gitPkg.IsClean(r.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return version, err
|
||||||
}
|
}
|
||||||
if dirty {
|
|
||||||
return fmt.Sprintf("%s%s", version, config.DIRTY_DEFAULT), nil
|
if !isClean {
|
||||||
|
version = fmt.Sprintf("%s+U", version)
|
||||||
}
|
}
|
||||||
|
|
||||||
return version, nil
|
return version, nil
|
||||||
@ -350,44 +293,29 @@ func (r Recipe) Tags() ([]string, error) {
|
|||||||
return tags, err
|
return tags, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sort.Slice(tags, func(i, j int) bool {
|
|
||||||
version1, err := tagcmp.Parse(tags[i])
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
version2, err := tagcmp.Parse(tags[j])
|
|
||||||
if err != nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
return version1.IsLessThan(version2)
|
|
||||||
})
|
|
||||||
|
|
||||||
log.Debugf("detected %s as tags for recipe %s", strings.Join(tags, ", "), r.Name)
|
log.Debugf("detected %s as tags for recipe %s", strings.Join(tags, ", "), r.Name)
|
||||||
|
|
||||||
return tags, nil
|
return tags, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRecipeVersions retrieves all recipe versions.
|
// GetRecipeVersions retrieves all recipe versions.
|
||||||
func (r Recipe) GetRecipeVersions() (RecipeVersions, []string, error) {
|
func (r Recipe) GetRecipeVersions() (RecipeVersions, error) {
|
||||||
var warnMsg []string
|
|
||||||
|
|
||||||
versions := RecipeVersions{}
|
versions := RecipeVersions{}
|
||||||
|
log.Debugf("attempting to open git repository in %s", r.Dir)
|
||||||
log.Debugf("git: opening repository in %s", r.Dir)
|
|
||||||
|
|
||||||
repo, err := git.PlainOpen(r.Dir)
|
repo, err := git.PlainOpen(r.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return versions, warnMsg, nil
|
return versions, err
|
||||||
}
|
}
|
||||||
|
|
||||||
worktree, err := repo.Worktree()
|
worktree, err := repo.Worktree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return versions, warnMsg, nil
|
return versions, err
|
||||||
}
|
}
|
||||||
|
|
||||||
gitTags, err := repo.Tags()
|
gitTags, err := repo.Tags()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return versions, warnMsg, nil
|
return versions, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := gitTags.ForEach(func(ref *plumbing.Reference) (err error) {
|
if err := gitTags.ForEach(func(ref *plumbing.Reference) (err error) {
|
||||||
@ -405,7 +333,7 @@ func (r Recipe) GetRecipeVersions() (RecipeVersions, []string, error) {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("git checkout: %s in %s", ref.Name(), r.Dir)
|
log.Debugf("successfully checked out %s in %s", ref.Name(), r.Dir)
|
||||||
|
|
||||||
config, err := r.GetComposeConfig(nil)
|
config, err := r.GetComposeConfig(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -429,7 +357,7 @@ func (r Recipe) GetRecipeVersions() (RecipeVersions, []string, error) {
|
|||||||
case reference.NamedTagged:
|
case reference.NamedTagged:
|
||||||
tag = img.(reference.NamedTagged).Tag()
|
tag = img.(reference.NamedTagged).Tag()
|
||||||
case reference.Named:
|
case reference.Named:
|
||||||
warnMsg = append(warnMsg, fmt.Sprintf("%s service is missing image tag?", path))
|
log.Warnf("%s service is missing image tag?", path)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -443,26 +371,19 @@ func (r Recipe) GetRecipeVersions() (RecipeVersions, []string, error) {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return versions, warnMsg, nil
|
return versions, err
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = gitPkg.CheckoutDefaultBranch(repo, r.Dir)
|
_, err = gitPkg.CheckoutDefaultBranch(repo, r.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return versions, warnMsg, nil
|
return versions, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sortRecipeVersions(versions)
|
sortRecipeVersions(versions)
|
||||||
|
|
||||||
log.Debugf("collected %s for %s", versions, r.Dir)
|
log.Debugf("collected %s for %s", versions, r.Dir)
|
||||||
|
|
||||||
var uniqueWarnings []string
|
return versions, nil
|
||||||
for _, w := range warnMsg {
|
|
||||||
if !slices.Contains(uniqueWarnings, w) {
|
|
||||||
uniqueWarnings = append(uniqueWarnings, w)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return versions, uniqueWarnings, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Head retrieves latest HEAD metadata.
|
// Head retrieves latest HEAD metadata.
|
||||||
|
@ -1,36 +0,0 @@
|
|||||||
package recipe
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestIsDirty(t *testing.T) {
|
|
||||||
r := Get("abra-test-recipe")
|
|
||||||
|
|
||||||
if err := r.EnsureExists(); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.False(t, r.Dirty)
|
|
||||||
|
|
||||||
fpath := filepath.Join(r.Dir, "foo.txt")
|
|
||||||
f, err := os.Create(fpath)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
defer t.Cleanup(func() {
|
|
||||||
os.Remove(fpath)
|
|
||||||
})
|
|
||||||
|
|
||||||
dirty, err := r.IsDirty()
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.True(t, dirty)
|
|
||||||
}
|
|
@ -2,18 +2,16 @@ package recipe
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/url"
|
"net/url"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"slices"
|
||||||
"sort"
|
"sort"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/go-git/go-git/v5"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/catalogue"
|
"coopcloud.tech/abra/pkg/catalogue"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
@ -59,6 +57,11 @@ type RecipeMeta struct {
|
|||||||
Website string `json:"website"`
|
Website string `json:"website"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TopicMeta represents a list of topics for a repository.
|
||||||
|
type TopicMeta struct {
|
||||||
|
Topics []string `json:"topics"`
|
||||||
|
}
|
||||||
|
|
||||||
// LatestVersion returns the latest version of a recipe.
|
// LatestVersion returns the latest version of a recipe.
|
||||||
func (r RecipeMeta) LatestVersion() string {
|
func (r RecipeMeta) LatestVersion() string {
|
||||||
var version string
|
var version string
|
||||||
@ -122,24 +125,17 @@ type Features struct {
|
|||||||
|
|
||||||
func Get(name string) Recipe {
|
func Get(name string) Recipe {
|
||||||
version := ""
|
version := ""
|
||||||
versionRaw := ""
|
|
||||||
if strings.Contains(name, ":") {
|
if strings.Contains(name, ":") {
|
||||||
split := strings.Split(name, ":")
|
split := strings.Split(name, ":")
|
||||||
if len(split) > 2 {
|
if len(split) > 2 {
|
||||||
log.Fatalf("version seems invalid: %s", name)
|
log.Fatalf("version seems invalid: %s", name)
|
||||||
}
|
}
|
||||||
name = split[0]
|
name = split[0]
|
||||||
|
|
||||||
version = split[1]
|
version = split[1]
|
||||||
versionRaw = version
|
|
||||||
if strings.HasSuffix(version, config.DIRTY_DEFAULT) {
|
|
||||||
version = strings.Replace(split[1], config.DIRTY_DEFAULT, "", 1)
|
|
||||||
log.Debugf("removed dirty suffix from .env version: %s -> %s", split[1], version)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gitURL := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, name)
|
gitURL := fmt.Sprintf("%s/%s.git", config.REPOS_BASE_URL, name)
|
||||||
sshURL := fmt.Sprintf(config.RECIPES_SSH_URL_TEMPLATE, name)
|
sshURL := fmt.Sprintf(config.SSH_URL_TEMPLATE, name)
|
||||||
if strings.Contains(name, "/") {
|
if strings.Contains(name, "/") {
|
||||||
u, err := url.Parse(name)
|
u, err := url.Parse(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -155,37 +151,26 @@ func Get(name string) Recipe {
|
|||||||
|
|
||||||
dir := path.Join(config.RECIPES_DIR, escapeRecipeName(name))
|
dir := path.Join(config.RECIPES_DIR, escapeRecipeName(name))
|
||||||
|
|
||||||
r := Recipe{
|
return Recipe{
|
||||||
Name: name,
|
Name: name,
|
||||||
EnvVersion: version,
|
Version: version,
|
||||||
EnvVersionRaw: versionRaw,
|
Dir: dir,
|
||||||
Dir: dir,
|
GitURL: gitURL,
|
||||||
GitURL: gitURL,
|
SSHURL: sshURL,
|
||||||
SSHURL: sshURL,
|
|
||||||
|
|
||||||
ComposePath: path.Join(dir, "compose.yml"),
|
ComposePath: path.Join(dir, "compose.yml"),
|
||||||
ReadmePath: path.Join(dir, "README.md"),
|
ReadmePath: path.Join(dir, "README.md"),
|
||||||
SampleEnvPath: path.Join(dir, ".env.sample"),
|
SampleEnvPath: path.Join(dir, ".env.sample"),
|
||||||
AbraShPath: path.Join(dir, "abra.sh"),
|
AbraShPath: path.Join(dir, "abra.sh"),
|
||||||
}
|
}
|
||||||
|
|
||||||
dirty, err := r.IsDirty()
|
|
||||||
if err != nil && !errors.Is(err, git.ErrRepositoryNotExists) {
|
|
||||||
log.Fatalf("failed to check git status of %s: %s", r.Name, err)
|
|
||||||
}
|
|
||||||
r.Dirty = dirty
|
|
||||||
|
|
||||||
return r
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Recipe struct {
|
type Recipe struct {
|
||||||
Name string
|
Name string
|
||||||
EnvVersion string
|
Version string
|
||||||
EnvVersionRaw string
|
Dir string
|
||||||
Dirty bool // NOTE(d1): git terminology for unstaged changes
|
GitURL string
|
||||||
Dir string
|
SSHURL string
|
||||||
GitURL string
|
|
||||||
SSHURL string
|
|
||||||
|
|
||||||
ComposePath string
|
ComposePath string
|
||||||
ReadmePath string
|
ReadmePath string
|
||||||
@ -193,21 +178,6 @@ type Recipe struct {
|
|||||||
AbraShPath string
|
AbraShPath string
|
||||||
}
|
}
|
||||||
|
|
||||||
// String outputs a human-friendly string representation.
|
|
||||||
func (r Recipe) String() string {
|
|
||||||
out := fmt.Sprintf("{name: %s, ", r.Name)
|
|
||||||
out += fmt.Sprintf("version : %s, ", r.EnvVersion)
|
|
||||||
out += fmt.Sprintf("dirty: %v, ", r.Dirty)
|
|
||||||
out += fmt.Sprintf("dir: %s, ", r.Dir)
|
|
||||||
out += fmt.Sprintf("git url: %s, ", r.GitURL)
|
|
||||||
out += fmt.Sprintf("ssh url: %s, ", r.SSHURL)
|
|
||||||
out += fmt.Sprintf("compose: %s, ", r.ComposePath)
|
|
||||||
out += fmt.Sprintf("readme: %s, ", r.ReadmePath)
|
|
||||||
out += fmt.Sprintf("sample env: %s, ", r.SampleEnvPath)
|
|
||||||
out += fmt.Sprintf("abra.sh: %s}", r.AbraShPath)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func escapeRecipeName(recipeName string) string {
|
func escapeRecipeName(recipeName string) string {
|
||||||
recipeName = strings.ReplaceAll(recipeName, "/", "_")
|
recipeName = strings.ReplaceAll(recipeName, "/", "_")
|
||||||
recipeName = strings.ReplaceAll(recipeName, ".", "_")
|
recipeName = strings.ReplaceAll(recipeName, ".", "_")
|
||||||
@ -226,18 +196,16 @@ func GetRecipesLocal() ([]string, error) {
|
|||||||
return recipes, nil
|
return recipes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetRecipeFeaturesAndCategory(r Recipe) (Features, string, []string, error) {
|
func GetRecipeFeaturesAndCategory(r Recipe) (Features, string, error) {
|
||||||
var (
|
feat := Features{}
|
||||||
category string
|
|
||||||
warnMsgs []string
|
|
||||||
feat = Features{}
|
|
||||||
)
|
|
||||||
|
|
||||||
log.Debugf("%s: attempt recipe metadata parse", r.ReadmePath)
|
var category string
|
||||||
|
|
||||||
|
log.Debugf("attempting to open %s for recipe metadata parsing", r.ReadmePath)
|
||||||
|
|
||||||
readmeFS, err := ioutil.ReadFile(r.ReadmePath)
|
readmeFS, err := ioutil.ReadFile(r.ReadmePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return feat, category, warnMsgs, err
|
return feat, category, err
|
||||||
}
|
}
|
||||||
|
|
||||||
readmeMetadata, err := GetStringInBetween( // Find text between delimiters
|
readmeMetadata, err := GetStringInBetween( // Find text between delimiters
|
||||||
@ -246,7 +214,7 @@ func GetRecipeFeaturesAndCategory(r Recipe) (Features, string, []string, error)
|
|||||||
"<!-- metadata -->", "<!-- endmetadata -->",
|
"<!-- metadata -->", "<!-- endmetadata -->",
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return feat, category, warnMsgs, err
|
return feat, category, err
|
||||||
}
|
}
|
||||||
|
|
||||||
readmeLines := strings.Split( // Array item from lines
|
readmeLines := strings.Split( // Array item from lines
|
||||||
@ -290,25 +258,20 @@ func GetRecipeFeaturesAndCategory(r Recipe) (Features, string, []string, error)
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
if strings.Contains(val, "**Image**") {
|
if strings.Contains(val, "**Image**") {
|
||||||
imageMetadata, warnings, err := GetImageMetadata(strings.TrimSpace(
|
imageMetadata, err := GetImageMetadata(strings.TrimSpace(
|
||||||
strings.TrimPrefix(val, "* **Image**:"),
|
strings.TrimPrefix(val, "* **Image**:"),
|
||||||
), r.Name)
|
), r.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if len(warnings) > 0 {
|
|
||||||
warnMsgs = append(warnMsgs, warnings...)
|
|
||||||
}
|
|
||||||
feat.Image = imageMetadata
|
feat.Image = imageMetadata
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return feat, category, warnMsgs, nil
|
return feat, category, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetImageMetadata(imageRowString, recipeName string) (Image, []string, error) {
|
func GetImageMetadata(imageRowString, recipeName string) (Image, error) {
|
||||||
var warnMsgs []string
|
|
||||||
|
|
||||||
img := Image{}
|
img := Image{}
|
||||||
|
|
||||||
imgFields := strings.Split(imageRowString, ",")
|
imgFields := strings.Split(imageRowString, ",")
|
||||||
@ -319,18 +282,11 @@ func GetImageMetadata(imageRowString, recipeName string) (Image, []string, error
|
|||||||
|
|
||||||
if len(imgFields) < 3 {
|
if len(imgFields) < 3 {
|
||||||
if imageRowString != "" {
|
if imageRowString != "" {
|
||||||
warnMsgs = append(
|
log.Warnf("%s image meta has incorrect format: %s", recipeName, imageRowString)
|
||||||
warnMsgs,
|
|
||||||
fmt.Sprintf("%s: image meta has incorrect format: %s", recipeName, imageRowString),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
warnMsgs = append(
|
log.Warnf("%s image meta is empty?", recipeName)
|
||||||
warnMsgs,
|
|
||||||
fmt.Sprintf("%s: image meta is empty?", recipeName),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
return img, nil
|
||||||
return img, warnMsgs, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
img.Rating = imgFields[1]
|
img.Rating = imgFields[1]
|
||||||
@ -340,17 +296,17 @@ func GetImageMetadata(imageRowString, recipeName string) (Image, []string, error
|
|||||||
|
|
||||||
imageName, err := GetStringInBetween(recipeName, imgString, "[", "]")
|
imageName, err := GetStringInBetween(recipeName, imgString, "[", "]")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return img, warnMsgs, err
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
img.Image = strings.ReplaceAll(imageName, "`", "")
|
img.Image = strings.ReplaceAll(imageName, "`", "")
|
||||||
|
|
||||||
imageURL, err := GetStringInBetween(recipeName, imgString, "(", ")")
|
imageURL, err := GetStringInBetween(recipeName, imgString, "(", ")")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return img, warnMsgs, err
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
img.URL = imageURL
|
img.URL = imageURL
|
||||||
|
|
||||||
return img, warnMsgs, nil
|
return img, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStringInBetween returns empty string if no start or end string found
|
// GetStringInBetween returns empty string if no start or end string found
|
||||||
@ -541,11 +497,11 @@ type InternalTracker struct {
|
|||||||
type RepoCatalogue map[string]RepoMeta
|
type RepoCatalogue map[string]RepoMeta
|
||||||
|
|
||||||
// ReadReposMetadata retrieves coop-cloud/... repo metadata from Gitea.
|
// ReadReposMetadata retrieves coop-cloud/... repo metadata from Gitea.
|
||||||
func ReadReposMetadata(debug bool) (RepoCatalogue, error) {
|
func ReadReposMetadata() (RepoCatalogue, error) {
|
||||||
reposMeta := make(RepoCatalogue)
|
reposMeta := make(RepoCatalogue)
|
||||||
|
|
||||||
pageIdx := 1
|
pageIdx := 1
|
||||||
bar := formatter.CreateProgressbar(-1, "collecting recipe listing")
|
bar := formatter.CreateProgressbar(-1, "retrieving recipe repos list from git.coopcloud.tech...")
|
||||||
for {
|
for {
|
||||||
var reposList []RepoMeta
|
var reposList []RepoMeta
|
||||||
|
|
||||||
@ -558,32 +514,28 @@ func ReadReposMetadata(debug bool) (RepoCatalogue, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(reposList) == 0 {
|
if len(reposList) == 0 {
|
||||||
if !debug {
|
bar.Add(1)
|
||||||
bar.Add(1)
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
for idx, repo := range reposList {
|
for idx, repo := range reposList {
|
||||||
// NOTE(d1): the "example" recipe is a temporary special case
|
var topicMeta TopicMeta
|
||||||
// https://git.coopcloud.tech/toolshed/organising/issues/666
|
|
||||||
if repo.Name == "example" {
|
topicsURL := getReposTopicUrl(repo.Name)
|
||||||
continue
|
if err := web.ReadJSON(topicsURL, &topicMeta); err != nil {
|
||||||
|
return reposMeta, err
|
||||||
}
|
}
|
||||||
|
|
||||||
reposMeta[repo.Name] = reposList[idx]
|
if slices.Contains(topicMeta.Topics, "recipe") && repo.Name != "example" {
|
||||||
|
reposMeta[repo.Name] = reposList[idx]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pageIdx++
|
pageIdx++
|
||||||
|
bar.Add(1)
|
||||||
if !debug {
|
|
||||||
bar.Add(1)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := bar.Close(); err != nil {
|
fmt.Println() // newline for spinner
|
||||||
return reposMeta, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return reposMeta, nil
|
return reposMeta, nil
|
||||||
}
|
}
|
||||||
@ -645,7 +597,7 @@ func GetRecipeCatalogueVersions(recipeName string, catl RecipeCatalogue) ([]stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
// UpdateRepositories clones and updates all recipe repositories locally.
|
// UpdateRepositories clones and updates all recipe repositories locally.
|
||||||
func UpdateRepositories(repos RepoCatalogue, recipeName string, debug bool) error {
|
func UpdateRepositories(repos RepoCatalogue, recipeName string) error {
|
||||||
var barLength int
|
var barLength int
|
||||||
if recipeName != "" {
|
if recipeName != "" {
|
||||||
barLength = 1
|
barLength = 1
|
||||||
@ -653,9 +605,9 @@ func UpdateRepositories(repos RepoCatalogue, recipeName string, debug bool) erro
|
|||||||
barLength = len(repos)
|
barLength = len(repos)
|
||||||
}
|
}
|
||||||
|
|
||||||
cloneLimiter := limit.New(3)
|
cloneLimiter := limit.New(10)
|
||||||
|
|
||||||
retrieveBar := formatter.CreateProgressbar(barLength, "retrieving recipes")
|
retrieveBar := formatter.CreateProgressbar(barLength, "ensuring recipes are cloned & up-to-date...")
|
||||||
ch := make(chan string, barLength)
|
ch := make(chan string, barLength)
|
||||||
for _, repoMeta := range repos {
|
for _, repoMeta := range repos {
|
||||||
go func(rm RepoMeta) {
|
go func(rm RepoMeta) {
|
||||||
@ -664,9 +616,7 @@ func UpdateRepositories(repos RepoCatalogue, recipeName string, debug bool) erro
|
|||||||
|
|
||||||
if recipeName != "" && recipeName != rm.Name {
|
if recipeName != "" && recipeName != rm.Name {
|
||||||
ch <- rm.Name
|
ch <- rm.Name
|
||||||
if !debug {
|
retrieveBar.Add(1)
|
||||||
retrieveBar.Add(1)
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -675,9 +625,7 @@ func UpdateRepositories(repos RepoCatalogue, recipeName string, debug bool) erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
ch <- rm.Name
|
ch <- rm.Name
|
||||||
if !debug {
|
retrieveBar.Add(1)
|
||||||
retrieveBar.Add(1)
|
|
||||||
}
|
|
||||||
}(repoMeta)
|
}(repoMeta)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -685,13 +633,14 @@ func UpdateRepositories(repos RepoCatalogue, recipeName string, debug bool) erro
|
|||||||
<-ch // wait for everything
|
<-ch // wait for everything
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := retrieveBar.Close(); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getReposTopicUrl retrieves the repository specific topic listing.
|
||||||
|
func getReposTopicUrl(repoName string) string {
|
||||||
|
return fmt.Sprintf("https://git.coopcloud.tech/api/v1/repos/coop-cloud/%s/topics", repoName)
|
||||||
|
}
|
||||||
|
|
||||||
// ensurePathExists ensures that a path exists.
|
// ensurePathExists ensures that a path exists.
|
||||||
func ensurePathExists(path string) error {
|
func ensurePathExists(path string) error {
|
||||||
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
|
if _, err := os.Stat(path); err != nil && os.IsNotExist(err) {
|
||||||
|
@ -33,8 +33,7 @@ func TestGet(t *testing.T) {
|
|||||||
name: "foo:1.2.3",
|
name: "foo:1.2.3",
|
||||||
recipe: Recipe{
|
recipe: Recipe{
|
||||||
Name: "foo",
|
Name: "foo",
|
||||||
EnvVersion: "1.2.3",
|
Version: "1.2.3",
|
||||||
EnvVersionRaw: "1.2.3",
|
|
||||||
Dir: path.Join(cfg.GetAbraDir(), "/recipes/foo"),
|
Dir: path.Join(cfg.GetAbraDir(), "/recipes/foo"),
|
||||||
GitURL: "https://git.coopcloud.tech/coop-cloud/foo.git",
|
GitURL: "https://git.coopcloud.tech/coop-cloud/foo.git",
|
||||||
SSHURL: "ssh://git@git.coopcloud.tech:2222/coop-cloud/foo.git",
|
SSHURL: "ssh://git@git.coopcloud.tech:2222/coop-cloud/foo.git",
|
||||||
@ -61,23 +60,7 @@ func TestGet(t *testing.T) {
|
|||||||
name: "mygit.org/myorg/cool-recipe:1.2.4",
|
name: "mygit.org/myorg/cool-recipe:1.2.4",
|
||||||
recipe: Recipe{
|
recipe: Recipe{
|
||||||
Name: "mygit.org/myorg/cool-recipe",
|
Name: "mygit.org/myorg/cool-recipe",
|
||||||
EnvVersion: "1.2.4",
|
Version: "1.2.4",
|
||||||
EnvVersionRaw: "1.2.4",
|
|
||||||
Dir: path.Join(cfg.GetAbraDir(), "/recipes/mygit_org_myorg_cool-recipe"),
|
|
||||||
GitURL: "https://mygit.org/myorg/cool-recipe.git",
|
|
||||||
SSHURL: "ssh://git@mygit.org/myorg/cool-recipe.git",
|
|
||||||
ComposePath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/compose.yml"),
|
|
||||||
ReadmePath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/README.md"),
|
|
||||||
SampleEnvPath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/.env.sample"),
|
|
||||||
AbraShPath: path.Join(cfg.GetAbraDir(), "recipes/mygit_org_myorg_cool-recipe/abra.sh"),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "mygit.org/myorg/cool-recipe:1e83340e+U",
|
|
||||||
recipe: Recipe{
|
|
||||||
Name: "mygit.org/myorg/cool-recipe",
|
|
||||||
EnvVersion: "1e83340e",
|
|
||||||
EnvVersionRaw: "1e83340e+U",
|
|
||||||
Dir: path.Join(cfg.GetAbraDir(), "/recipes/mygit_org_myorg_cool-recipe"),
|
Dir: path.Join(cfg.GetAbraDir(), "/recipes/mygit_org_myorg_cool-recipe"),
|
||||||
GitURL: "https://mygit.org/myorg/cool-recipe.git",
|
GitURL: "https://mygit.org/myorg/cool-recipe.git",
|
||||||
SSHURL: "ssh://git@mygit.org/myorg/cool-recipe.git",
|
SSHURL: "ssh://git@mygit.org/myorg/cool-recipe.git",
|
||||||
|
@ -33,13 +33,6 @@ type Secret struct {
|
|||||||
// variable. For Example:
|
// variable. For Example:
|
||||||
// SECRET_FOO=v1 # length=12
|
// SECRET_FOO=v1 # length=12
|
||||||
Length int
|
Length int
|
||||||
// Charset comes from the charset modifier at the secret version environment
|
|
||||||
// variable. For Example:
|
|
||||||
// SECRET_FOO=v1 # charset=default,special
|
|
||||||
Charset string
|
|
||||||
// Whether or not to skip generation of the secret or not
|
|
||||||
// For example: SECRET_FOO=v1 # generate=false
|
|
||||||
SkipGenerate bool
|
|
||||||
// RemoteName is the name of the secret on the server. For example:
|
// RemoteName is the name of the secret on the server. For example:
|
||||||
// name: ${STACK_NAME}_test_pass_two_${SECRET_TEST_PASS_TWO_VERSION}
|
// name: ${STACK_NAME}_test_pass_two_${SECRET_TEST_PASS_TWO_VERSION}
|
||||||
// With the following:
|
// With the following:
|
||||||
@ -50,34 +43,38 @@ type Secret struct {
|
|||||||
RemoteName string
|
RemoteName string
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeneratePassword generates passwords.
|
// GeneratePasswords generates passwords.
|
||||||
func GeneratePassword(length uint, charset string) (string, error) {
|
func GeneratePasswords(count, length uint) ([]string, error) {
|
||||||
passwords, err := passgen.GeneratePasswords(1, length, charset)
|
passwords, err := passgen.GeneratePasswords(
|
||||||
|
count,
|
||||||
|
length,
|
||||||
|
passgen.AlphabetDefault,
|
||||||
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("generated %s", strings.Join(passwords, ", "))
|
log.Debugf("generated %s", strings.Join(passwords, ", "))
|
||||||
|
|
||||||
return passwords[0], nil
|
return passwords, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeneratePassphrase generates human readable and rememberable passphrases.
|
// GeneratePassphrases generates human readable and rememberable passphrases.
|
||||||
func GeneratePassphrase() (string, error) {
|
func GeneratePassphrases(count uint) ([]string, error) {
|
||||||
passphrases, err := passgen.GeneratePassphrases(
|
passphrases, err := passgen.GeneratePassphrases(
|
||||||
1,
|
count,
|
||||||
passgen.PassphraseWordCountDefault,
|
passgen.PassphraseWordCountDefault,
|
||||||
rune('-'),
|
rune('-'),
|
||||||
passgen.PassphraseCasingDefault,
|
passgen.PassphraseCasingDefault,
|
||||||
passgen.WordListDefault,
|
passgen.WordListDefault,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("generated %s", strings.Join(passphrases, ", "))
|
log.Debugf("generated %s", strings.Join(passphrases, ", "))
|
||||||
|
|
||||||
return passphrases[0], nil
|
return passphrases, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReadSecretsConfig reads secret names/versions from the recipe config. The
|
// ReadSecretsConfig reads secret names/versions from the recipe config. The
|
||||||
@ -90,7 +87,6 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the STACK_NAME to be able to generate the remote name correctly.
|
// Set the STACK_NAME to be able to generate the remote name correctly.
|
||||||
appEnv["STACK_NAME"] = stackName
|
appEnv["STACK_NAME"] = stackName
|
||||||
|
|
||||||
@ -99,7 +95,6 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read the compose files without injecting environment variables.
|
// Read the compose files without injecting environment variables.
|
||||||
configWithoutEnv, err := loader.LoadComposefile(opts, map[string]string{}, loader.SkipInterpolation)
|
configWithoutEnv, err := loader.LoadComposefile(opts, map[string]string{}, loader.SkipInterpolation)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -147,7 +142,6 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
|
|||||||
if !strings.Contains(configWithoutEnv.Secrets[secretId].Name, envName) {
|
if !strings.Contains(configWithoutEnv.Secrets[secretId].Name, envName) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
lengthRaw, ok := modifierValues["length"]
|
lengthRaw, ok := modifierValues["length"]
|
||||||
if ok {
|
if ok {
|
||||||
length, err := strconv.Atoi(lengthRaw)
|
length, err := strconv.Atoi(lengthRaw)
|
||||||
@ -156,15 +150,6 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
|
|||||||
}
|
}
|
||||||
value.Length = length
|
value.Length = length
|
||||||
}
|
}
|
||||||
|
|
||||||
generateRaw, ok := modifierValues["generate"]
|
|
||||||
if ok {
|
|
||||||
if generateRaw == "false" {
|
|
||||||
value.SkipGenerate = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
value.Charset = resolveCharset(modifierValues["charset"])
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
secretValues[secretId] = value
|
secretValues[secretId] = value
|
||||||
@ -173,22 +158,6 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin
|
|||||||
return secretValues, nil
|
return secretValues, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// resolveCharset sets the passgen Alphabet required for a secret
|
|
||||||
func resolveCharset(input string) string {
|
|
||||||
switch strings.ToLower(input) {
|
|
||||||
case "special":
|
|
||||||
return passgen.AlphabetSpecial
|
|
||||||
case "safespecial":
|
|
||||||
return "!@#%^&*_-+="
|
|
||||||
case "default,special", "special,default":
|
|
||||||
return passgen.AlphabetDefault + passgen.AlphabetSpecial
|
|
||||||
case "default,safespecial", "safespecial,default":
|
|
||||||
return passgen.AlphabetDefault + "!@#%^&*_-+="
|
|
||||||
default:
|
|
||||||
return passgen.AlphabetDefault // Fallback to default
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GenerateSecrets generates secrets locally and sends them to a remote server for storage.
|
// GenerateSecrets generates secrets locally and sends them to a remote server for storage.
|
||||||
func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server string) (map[string]string, error) {
|
func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server string) (map[string]string, error) {
|
||||||
secretsGenerated := map[string]string{}
|
secretsGenerated := map[string]string{}
|
||||||
@ -201,22 +170,16 @@ func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server
|
|||||||
go func(secretName string, secret Secret) {
|
go func(secretName string, secret Secret) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
if secret.SkipGenerate {
|
|
||||||
log.Debugf("skipping generation of %s (generate=false)", secretName)
|
|
||||||
ch <- nil
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("attempting to generate and store %s on %s", secret.RemoteName, server)
|
log.Debugf("attempting to generate and store %s on %s", secret.RemoteName, server)
|
||||||
|
|
||||||
if secret.Length > 0 {
|
if secret.Length > 0 {
|
||||||
password, err := GeneratePassword(uint(secret.Length), secret.Charset)
|
passwords, err := GeneratePasswords(1, uint(secret.Length))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ch <- err
|
ch <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := client.StoreSecret(cl, secret.RemoteName, password); err != nil {
|
if err := client.StoreSecret(cl, secret.RemoteName, passwords[0], server); err != nil {
|
||||||
if strings.Contains(err.Error(), "AlreadyExists") {
|
if strings.Contains(err.Error(), "AlreadyExists") {
|
||||||
log.Warnf("%s already exists", secret.RemoteName)
|
log.Warnf("%s already exists", secret.RemoteName)
|
||||||
ch <- nil
|
ch <- nil
|
||||||
@ -228,15 +191,15 @@ func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server
|
|||||||
|
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
secretsGenerated[secretName] = password
|
secretsGenerated[secretName] = passwords[0]
|
||||||
} else {
|
} else {
|
||||||
passphrase, err := GeneratePassphrase()
|
passphrases, err := GeneratePassphrases(1)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ch <- err
|
ch <- err
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := client.StoreSecret(cl, secret.RemoteName, passphrase); err != nil {
|
if err := client.StoreSecret(cl, secret.RemoteName, passphrases[0], server); err != nil {
|
||||||
if strings.Contains(err.Error(), "AlreadyExists") {
|
if strings.Contains(err.Error(), "AlreadyExists") {
|
||||||
log.Warnf("%s already exists", secret.RemoteName)
|
log.Warnf("%s already exists", secret.RemoteName)
|
||||||
ch <- nil
|
ch <- nil
|
||||||
@ -248,7 +211,7 @@ func GenerateSecrets(cl *dockerClient.Client, secrets map[string]Secret, server
|
|||||||
|
|
||||||
mutex.Lock()
|
mutex.Lock()
|
||||||
defer mutex.Unlock()
|
defer mutex.Unlock()
|
||||||
secretsGenerated[secretName] = passphrase
|
secretsGenerated[secretName] = passphrases[0]
|
||||||
}
|
}
|
||||||
ch <- nil
|
ch <- nil
|
||||||
}(n, v)
|
}(n, v)
|
||||||
|
@ -17,37 +17,16 @@ func TestReadSecretsConfig(t *testing.T) {
|
|||||||
assert.Equal(t, "test_example_com_test_pass_one_v2", secretsFromConfig["test_pass_one"].RemoteName)
|
assert.Equal(t, "test_example_com_test_pass_one_v2", secretsFromConfig["test_pass_one"].RemoteName)
|
||||||
assert.Equal(t, "v2", secretsFromConfig["test_pass_one"].Version)
|
assert.Equal(t, "v2", secretsFromConfig["test_pass_one"].Version)
|
||||||
assert.Equal(t, 0, secretsFromConfig["test_pass_one"].Length)
|
assert.Equal(t, 0, secretsFromConfig["test_pass_one"].Length)
|
||||||
assert.Equal(t, "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789", secretsFromConfig["test_pass_one"].Charset)
|
|
||||||
|
|
||||||
// Has a length modifier
|
// Has a length modifier
|
||||||
assert.Equal(t, "test_example_com_test_pass_two_v1", secretsFromConfig["test_pass_two"].RemoteName)
|
assert.Equal(t, "test_example_com_test_pass_two_v1", secretsFromConfig["test_pass_two"].RemoteName)
|
||||||
assert.Equal(t, "v1", secretsFromConfig["test_pass_two"].Version)
|
assert.Equal(t, "v1", secretsFromConfig["test_pass_two"].Version)
|
||||||
assert.Equal(t, 10, secretsFromConfig["test_pass_two"].Length)
|
assert.Equal(t, 10, secretsFromConfig["test_pass_two"].Length)
|
||||||
assert.Equal(t, "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789", secretsFromConfig["test_pass_two"].Charset)
|
|
||||||
|
|
||||||
// Secret name does not include the secret id
|
// Secret name does not include the secret id
|
||||||
assert.Equal(t, "test_example_com_pass_three_v2", secretsFromConfig["test_pass_three"].RemoteName)
|
assert.Equal(t, "test_example_com_pass_three_v2", secretsFromConfig["test_pass_three"].RemoteName)
|
||||||
assert.Equal(t, "v2", secretsFromConfig["test_pass_three"].Version)
|
assert.Equal(t, "v2", secretsFromConfig["test_pass_three"].Version)
|
||||||
assert.Equal(t, 0, secretsFromConfig["test_pass_three"].Length)
|
assert.Equal(t, 0, secretsFromConfig["test_pass_three"].Length)
|
||||||
assert.Equal(t, "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789", secretsFromConfig["test_pass_three"].Charset)
|
|
||||||
|
|
||||||
// Has a length modifier and a charset=default,safespecial modifier
|
|
||||||
assert.Equal(t, "test_example_com_test_pass_four_v1", secretsFromConfig["test_pass_four"].RemoteName)
|
|
||||||
assert.Equal(t, "v1", secretsFromConfig["test_pass_four"].Version)
|
|
||||||
assert.Equal(t, 12, secretsFromConfig["test_pass_four"].Length)
|
|
||||||
assert.Equal(t, "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#%^&*_-+=", secretsFromConfig["test_pass_four"].Charset)
|
|
||||||
|
|
||||||
// Has a length modifier and a charset=default,special modifier
|
|
||||||
assert.Equal(t, "test_example_com_test_pass_five_v1", secretsFromConfig["test_pass_five"].RemoteName)
|
|
||||||
assert.Equal(t, "v1", secretsFromConfig["test_pass_five"].Version)
|
|
||||||
assert.Equal(t, 12, secretsFromConfig["test_pass_five"].Length)
|
|
||||||
assert.Equal(t, "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%^&*_-+=", secretsFromConfig["test_pass_five"].Charset)
|
|
||||||
|
|
||||||
// Has only a charset=default,special modifier, which gets setted but ignored in the generation
|
|
||||||
assert.Equal(t, "test_example_com_test_pass_six_v1", secretsFromConfig["test_pass_six"].RemoteName)
|
|
||||||
assert.Equal(t, "v1", secretsFromConfig["test_pass_six"].Version)
|
|
||||||
assert.Equal(t, 0, secretsFromConfig["test_pass_six"].Length)
|
|
||||||
assert.Equal(t, "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%^&*_-+=", secretsFromConfig["test_pass_six"].Charset)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestReadSecretsConfigWithLongDomain(t *testing.T) {
|
func TestReadSecretsConfigWithLongDomain(t *testing.T) {
|
||||||
|
@ -1,6 +1,3 @@
|
|||||||
SECRET_TEST_PASS_ONE_VERSION=v2
|
SECRET_TEST_PASS_ONE_VERSION=v2
|
||||||
SECRET_TEST_PASS_TWO_VERSION=v1 # length=10
|
SECRET_TEST_PASS_TWO_VERSION=v1 # length=10
|
||||||
SECRET_TEST_PASS_THREE_VERSION=v2
|
SECRET_TEST_PASS_THREE_VERSION=v2
|
||||||
SECRET_TEST_PASS_FOUR_VERSION=v1 # length=12 charset=default,safespecial
|
|
||||||
SECRET_TEST_PASS_FIVE_VERSION=v1 # length=12 charset=default,special
|
|
||||||
SECRET_TEST_PASS_SIX_VERSION=v1 # charset=default,special
|
|
||||||
|
@ -8,9 +8,6 @@ services:
|
|||||||
- test_pass_one
|
- test_pass_one
|
||||||
- test_pass_two
|
- test_pass_two
|
||||||
- test_pass_three
|
- test_pass_three
|
||||||
- test_pass_four
|
|
||||||
- test_pass_five
|
|
||||||
- test_pass_six
|
|
||||||
|
|
||||||
secrets:
|
secrets:
|
||||||
test_pass_one:
|
test_pass_one:
|
||||||
@ -22,12 +19,3 @@ secrets:
|
|||||||
test_pass_three:
|
test_pass_three:
|
||||||
external: true
|
external: true
|
||||||
name: ${STACK_NAME}_pass_three_${SECRET_TEST_PASS_THREE_VERSION} # secretId and name don't match
|
name: ${STACK_NAME}_pass_three_${SECRET_TEST_PASS_THREE_VERSION} # secretId and name don't match
|
||||||
test_pass_four:
|
|
||||||
external: true
|
|
||||||
name: ${STACK_NAME}_test_pass_four_${SECRET_TEST_PASS_FOUR_VERSION}
|
|
||||||
test_pass_five:
|
|
||||||
external: true
|
|
||||||
name: ${STACK_NAME}_test_pass_five_${SECRET_TEST_PASS_FIVE_VERSION}
|
|
||||||
test_pass_six:
|
|
||||||
external: true
|
|
||||||
name: ${STACK_NAME}_test_pass_six_${SECRET_TEST_PASS_SIX_VERSION}
|
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
func CreateServerDir(serverName string) error {
|
func CreateServerDir(serverName string) error {
|
||||||
serverPath := path.Join(config.ABRA_DIR, "servers", serverName)
|
serverPath := path.Join(config.ABRA_DIR, "servers", serverName)
|
||||||
|
|
||||||
if err := os.Mkdir(serverPath, 0700); err != nil {
|
if err := os.Mkdir(serverPath, 0764); err != nil {
|
||||||
if !os.IsExist(err) {
|
if !os.IsExist(err) {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,6 @@ func Fatal(hostname string, err error) error {
|
|||||||
return fmt.Errorf("ssh auth: permission denied for %s", hostname)
|
return fmt.Errorf("ssh auth: permission denied for %s", hostname)
|
||||||
} else if strings.Contains(out, "Network is unreachable") {
|
} else if strings.Contains(out, "Network is unreachable") {
|
||||||
return fmt.Errorf("unable to connect to %s, please check your SSH config", hostname)
|
return fmt.Errorf("unable to connect to %s, please check your SSH config", hostname)
|
||||||
} else if strings.Contains(out, "Is the docker daemon running") {
|
|
||||||
return fmt.Errorf("docker: is the daemon running / your user has docker permissions?")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return err
|
return err
|
||||||
|
353
pkg/ui/deploy.go
353
pkg/ui/deploy.go
@ -1,353 +0,0 @@
|
|||||||
package ui
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
|
||||||
"coopcloud.tech/abra/pkg/logs"
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
|
||||||
"github.com/docker/cli/cli/command/service/progress"
|
|
||||||
containerTypes "github.com/docker/docker/api/types/container"
|
|
||||||
"github.com/docker/docker/api/types/filters"
|
|
||||||
dockerClient "github.com/docker/docker/client"
|
|
||||||
"github.com/docker/docker/pkg/jsonmessage"
|
|
||||||
)
|
|
||||||
|
|
||||||
var IsRunning bool
|
|
||||||
|
|
||||||
type statusMsg struct {
|
|
||||||
stream stream
|
|
||||||
jsonMsg jsonmessage.JSONMessage
|
|
||||||
}
|
|
||||||
|
|
||||||
type progressCompleteMsg struct {
|
|
||||||
stream stream
|
|
||||||
failed bool
|
|
||||||
}
|
|
||||||
|
|
||||||
type healthcheckMsg struct {
|
|
||||||
stream stream
|
|
||||||
health string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ServiceMeta struct {
|
|
||||||
Name string
|
|
||||||
ID string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Model struct {
|
|
||||||
appName string
|
|
||||||
cl *dockerClient.Client
|
|
||||||
count int
|
|
||||||
ctx context.Context
|
|
||||||
timeout time.Duration
|
|
||||||
width int
|
|
||||||
filters filters.Args
|
|
||||||
|
|
||||||
Streams *[]stream
|
|
||||||
Logs *[]string
|
|
||||||
Failed bool
|
|
||||||
TimedOut bool
|
|
||||||
Quit bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) complete() bool {
|
|
||||||
if m.count == len(*m.Streams) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
type stream struct {
|
|
||||||
Name string
|
|
||||||
Err error
|
|
||||||
|
|
||||||
decoder *json.Decoder
|
|
||||||
id string
|
|
||||||
reader *io.PipeReader
|
|
||||||
writer *io.PipeWriter
|
|
||||||
status string
|
|
||||||
retries int
|
|
||||||
health string
|
|
||||||
rollback bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s stream) String() string {
|
|
||||||
out := fmt.Sprintf("{decoder: %v, ", s.decoder)
|
|
||||||
out += fmt.Sprintf("err: %v, ", s.Err)
|
|
||||||
out += fmt.Sprintf("id: %s, ", s.id)
|
|
||||||
out += fmt.Sprintf("name: %s, ", s.Name)
|
|
||||||
out += fmt.Sprintf("reader: %v, ", s.reader)
|
|
||||||
out += fmt.Sprintf("writer: %v, ", s.writer)
|
|
||||||
out += fmt.Sprintf("status: %s, ", s.status)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s stream) progress(m Model) tea.Msg {
|
|
||||||
if err := progress.ServiceProgress(m.ctx, m.cl, s.id, s.writer); err != nil {
|
|
||||||
return progressCompleteMsg{
|
|
||||||
stream: s,
|
|
||||||
failed: true,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return progressCompleteMsg{stream: s}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s stream) process() tea.Msg {
|
|
||||||
var jsonMsg jsonmessage.JSONMessage
|
|
||||||
|
|
||||||
if err := s.decoder.Decode(&jsonMsg); err != nil {
|
|
||||||
if err == io.EOF {
|
|
||||||
// NOTE(d1): end processing messages
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return statusMsg{
|
|
||||||
stream: s,
|
|
||||||
jsonMsg: jsonMsg,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s stream) healthcheck(m Model) tea.Msg {
|
|
||||||
filters := filters.NewArgs()
|
|
||||||
filters.Add("name", fmt.Sprintf("^%s", s.Name))
|
|
||||||
|
|
||||||
containers, err := m.cl.ContainerList(m.ctx, containerTypes.ListOptions{Filters: filters})
|
|
||||||
if err != nil {
|
|
||||||
s.Err = err
|
|
||||||
return healthcheckMsg{stream: s}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(containers) == 0 {
|
|
||||||
return healthcheckMsg{stream: s}
|
|
||||||
}
|
|
||||||
|
|
||||||
container := containers[0]
|
|
||||||
containerState, err := m.cl.ContainerInspect(m.ctx, container.ID)
|
|
||||||
if err != nil {
|
|
||||||
s.Err = err
|
|
||||||
return healthcheckMsg{stream: s}
|
|
||||||
}
|
|
||||||
|
|
||||||
var health string
|
|
||||||
if containerState.State.Health != nil {
|
|
||||||
health = containerState.State.Health.Status
|
|
||||||
}
|
|
||||||
|
|
||||||
return healthcheckMsg{stream: s, health: health}
|
|
||||||
}
|
|
||||||
|
|
||||||
func DeployInitialModel(
|
|
||||||
ctx context.Context,
|
|
||||||
cl *dockerClient.Client,
|
|
||||||
services []ServiceMeta,
|
|
||||||
appName string,
|
|
||||||
timeout time.Duration,
|
|
||||||
filters filters.Args,
|
|
||||||
) Model {
|
|
||||||
var streams []stream
|
|
||||||
for _, service := range services {
|
|
||||||
r, w := io.Pipe()
|
|
||||||
d := json.NewDecoder(r)
|
|
||||||
streams = append(streams, stream{
|
|
||||||
Name: service.Name,
|
|
||||||
id: service.ID,
|
|
||||||
reader: r,
|
|
||||||
writer: w,
|
|
||||||
decoder: d,
|
|
||||||
retries: -1, // NOTE(d1): skip first attempt
|
|
||||||
health: "?",
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
sort.Slice(streams, func(i, j int) bool {
|
|
||||||
return streams[i].Name < streams[j].Name
|
|
||||||
})
|
|
||||||
|
|
||||||
return Model{
|
|
||||||
ctx: ctx,
|
|
||||||
cl: cl,
|
|
||||||
appName: appName,
|
|
||||||
timeout: timeout,
|
|
||||||
filters: filters,
|
|
||||||
Streams: &streams,
|
|
||||||
Logs: &[]string{},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) Init() tea.Cmd {
|
|
||||||
var cmds []tea.Cmd
|
|
||||||
|
|
||||||
for _, stream := range *m.Streams {
|
|
||||||
cmds = append(
|
|
||||||
cmds,
|
|
||||||
[]tea.Cmd{
|
|
||||||
func() tea.Msg { return stream.progress(m) },
|
|
||||||
func() tea.Msg { return stream.process() },
|
|
||||||
func() tea.Msg { return stream.healthcheck(m) },
|
|
||||||
}...,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
cmds = append(cmds, func() tea.Msg { return deployTimeout(m) })
|
|
||||||
cmds = append(cmds, func() tea.Msg { return m.gatherLogs() })
|
|
||||||
|
|
||||||
return tea.Batch(cmds...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) gatherLogs() tea.Msg {
|
|
||||||
var services []string
|
|
||||||
for _, s := range *m.Streams {
|
|
||||||
services = append(services, s.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := logs.TailOpts{
|
|
||||||
AppName: m.appName,
|
|
||||||
Services: services,
|
|
||||||
StdErr: true,
|
|
||||||
Buffer: m.Logs,
|
|
||||||
ToBuffer: true,
|
|
||||||
Filters: m.filters,
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE(d1): not interested in log polling errors. if we don't see logs it
|
|
||||||
// will hopefully be self-evident based on what happened in the deployment
|
|
||||||
logs.TailLogs(m.cl, opts)
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type timeoutMsg struct{}
|
|
||||||
|
|
||||||
func deployTimeout(m Model) tea.Msg {
|
|
||||||
<-time.After(m.timeout)
|
|
||||||
return timeoutMsg{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
|
|
||||||
var cmds []tea.Cmd
|
|
||||||
|
|
||||||
switch msg := msg.(type) {
|
|
||||||
case tea.KeyMsg:
|
|
||||||
switch msg.String() {
|
|
||||||
case "ctrl+c", "q":
|
|
||||||
m.Quit = true
|
|
||||||
return m, tea.Quit
|
|
||||||
}
|
|
||||||
|
|
||||||
case tea.WindowSizeMsg:
|
|
||||||
m.width = msg.Width
|
|
||||||
|
|
||||||
case progressCompleteMsg:
|
|
||||||
if msg.failed {
|
|
||||||
m.Failed = true
|
|
||||||
}
|
|
||||||
|
|
||||||
m.count += 1
|
|
||||||
|
|
||||||
if m.complete() {
|
|
||||||
return m, tea.Quit
|
|
||||||
}
|
|
||||||
|
|
||||||
case timeoutMsg:
|
|
||||||
m.TimedOut = true
|
|
||||||
return m, tea.Quit
|
|
||||||
|
|
||||||
case healthcheckMsg:
|
|
||||||
for idx, s := range *m.Streams {
|
|
||||||
if s.id == msg.stream.id {
|
|
||||||
h := "?"
|
|
||||||
if s.health != "" {
|
|
||||||
h = s.health
|
|
||||||
}
|
|
||||||
if msg.health != "" {
|
|
||||||
h = msg.health
|
|
||||||
}
|
|
||||||
(*m.Streams)[idx].health = h
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cmds = append(
|
|
||||||
cmds,
|
|
||||||
func() tea.Msg { return msg.stream.healthcheck(m) },
|
|
||||||
)
|
|
||||||
|
|
||||||
case statusMsg:
|
|
||||||
for idx, s := range *m.Streams {
|
|
||||||
if s.id == msg.stream.id {
|
|
||||||
|
|
||||||
if msg.jsonMsg.ID == "rollback" {
|
|
||||||
m.Failed = true
|
|
||||||
(*m.Streams)[idx].rollback = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if msg.jsonMsg.ID != "overall progress" {
|
|
||||||
newStatus := strings.ToLower(msg.jsonMsg.Status)
|
|
||||||
currentStatus := (*m.Streams)[idx].status
|
|
||||||
|
|
||||||
if !strings.Contains(currentStatus, "starting") &&
|
|
||||||
strings.Contains(newStatus, "starting") {
|
|
||||||
(*m.Streams)[idx].retries += 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (*m.Streams)[idx].rollback {
|
|
||||||
if msg.jsonMsg.ID == "rollback" {
|
|
||||||
(*m.Streams)[idx].status = newStatus
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
(*m.Streams)[idx].status = newStatus
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
cmds = append(
|
|
||||||
cmds,
|
|
||||||
func() tea.Msg { return msg.stream.process() },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return m, tea.Batch(cmds...)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m Model) View() string {
|
|
||||||
body := strings.Builder{}
|
|
||||||
|
|
||||||
for _, stream := range *m.Streams {
|
|
||||||
split := strings.Split(stream.Name, "_")
|
|
||||||
short := split[len(split)-1]
|
|
||||||
|
|
||||||
status := stream.status
|
|
||||||
if strings.Contains(stream.status, "converged") && !stream.rollback {
|
|
||||||
status = "succeeded"
|
|
||||||
}
|
|
||||||
if strings.Contains(stream.status, "rolled back") {
|
|
||||||
status = "rolled back"
|
|
||||||
}
|
|
||||||
|
|
||||||
retries := 0
|
|
||||||
if stream.retries > 0 {
|
|
||||||
retries = stream.retries
|
|
||||||
}
|
|
||||||
|
|
||||||
output := fmt.Sprintf("%s: %s (retries: %v, healthcheck: %s)",
|
|
||||||
formatter.BoldStyle.Render(short),
|
|
||||||
status,
|
|
||||||
retries,
|
|
||||||
stream.health,
|
|
||||||
)
|
|
||||||
|
|
||||||
body.WriteString(output)
|
|
||||||
body.WriteString("\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
return body.String()
|
|
||||||
}
|
|
@ -9,14 +9,14 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/docker/cli/cli"
|
"github.com/docker/cli/cli"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types"
|
||||||
apiclient "github.com/docker/docker/client"
|
apiclient "github.com/docker/docker/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RunExec runs a command on a remote container. io.Writer corresponds to the
|
// RunExec runs a command on a remote container. io.Writer corresponds to the
|
||||||
// command output.
|
// command output.
|
||||||
func RunExec(dockerCli command.Cli, client *apiclient.Client, containerID string,
|
func RunExec(dockerCli command.Cli, client *apiclient.Client, containerID string,
|
||||||
execOptions *container.ExecOptions) (io.Writer, error) {
|
execConfig *types.ExecConfig) (io.Writer, error) {
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|
||||||
// We need to check the tty _before_ we do the ContainerExecCreate, because
|
// We need to check the tty _before_ we do the ContainerExecCreate, because
|
||||||
@ -26,13 +26,13 @@ func RunExec(dockerCli command.Cli, client *apiclient.Client, containerID string
|
|||||||
if _, err := client.ContainerInspect(ctx, containerID); err != nil {
|
if _, err := client.ContainerInspect(ctx, containerID); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !execOptions.Detach {
|
if !execConfig.Detach {
|
||||||
if err := dockerCli.In().CheckTty(execOptions.AttachStdin, execOptions.Tty); err != nil {
|
if err := dockerCli.In().CheckTty(execConfig.AttachStdin, execConfig.Tty); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
response, err := client.ContainerExecCreate(ctx, containerID, *execOptions)
|
response, err := client.ContainerExecCreate(ctx, containerID, *execConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -42,40 +42,40 @@ func RunExec(dockerCli command.Cli, client *apiclient.Client, containerID string
|
|||||||
return nil, errors.New("exec ID empty")
|
return nil, errors.New("exec ID empty")
|
||||||
}
|
}
|
||||||
|
|
||||||
if execOptions.Detach {
|
if execConfig.Detach {
|
||||||
execStartCheck := container.ExecStartOptions{
|
execStartCheck := types.ExecStartCheck{
|
||||||
Detach: execOptions.Detach,
|
Detach: execConfig.Detach,
|
||||||
Tty: execOptions.Tty,
|
Tty: execConfig.Tty,
|
||||||
}
|
}
|
||||||
return nil, client.ContainerExecStart(ctx, execID, execStartCheck)
|
return nil, client.ContainerExecStart(ctx, execID, execStartCheck)
|
||||||
}
|
}
|
||||||
return interactiveExec(ctx, dockerCli, client, execOptions, execID)
|
return interactiveExec(ctx, dockerCli, client, execConfig, execID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func interactiveExec(ctx context.Context, dockerCli command.Cli, client *apiclient.Client,
|
func interactiveExec(ctx context.Context, dockerCli command.Cli, client *apiclient.Client,
|
||||||
execOpts *container.ExecOptions, execID string) (io.Writer, error) {
|
execConfig *types.ExecConfig, execID string) (io.Writer, error) {
|
||||||
// Interactive exec requested.
|
// Interactive exec requested.
|
||||||
var (
|
var (
|
||||||
out, stderr io.Writer
|
out, stderr io.Writer
|
||||||
in io.ReadCloser
|
in io.ReadCloser
|
||||||
)
|
)
|
||||||
|
|
||||||
if execOpts.AttachStdin {
|
if execConfig.AttachStdin {
|
||||||
in = dockerCli.In()
|
in = dockerCli.In()
|
||||||
}
|
}
|
||||||
if execOpts.AttachStdout {
|
if execConfig.AttachStdout {
|
||||||
out = dockerCli.Out()
|
out = dockerCli.Out()
|
||||||
}
|
}
|
||||||
if execOpts.AttachStderr {
|
if execConfig.AttachStderr {
|
||||||
if execOpts.Tty {
|
if execConfig.Tty {
|
||||||
stderr = dockerCli.Out()
|
stderr = dockerCli.Out()
|
||||||
} else {
|
} else {
|
||||||
stderr = dockerCli.Err()
|
stderr = dockerCli.Err()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
execStartCheck := container.ExecStartOptions{
|
execStartCheck := types.ExecStartCheck{
|
||||||
Tty: execOpts.Tty,
|
Tty: execConfig.Tty,
|
||||||
}
|
}
|
||||||
resp, err := client.ContainerExecAttach(ctx, execID, execStartCheck)
|
resp, err := client.ContainerExecAttach(ctx, execID, execStartCheck)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -94,15 +94,15 @@ func interactiveExec(ctx context.Context, dockerCli command.Cli, client *apiclie
|
|||||||
outputStream: out,
|
outputStream: out,
|
||||||
errorStream: stderr,
|
errorStream: stderr,
|
||||||
resp: resp,
|
resp: resp,
|
||||||
tty: execOpts.Tty,
|
tty: execConfig.Tty,
|
||||||
detachKeys: execOpts.DetachKeys,
|
detachKeys: execConfig.DetachKeys,
|
||||||
}
|
}
|
||||||
|
|
||||||
return streamer.stream(ctx)
|
return streamer.stream(ctx)
|
||||||
}()
|
}()
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if execOpts.Tty && dockerCli.In().IsTerminal() {
|
if execConfig.Tty && dockerCli.In().IsTerminal() {
|
||||||
if err := MonitorTtySize(ctx, client, dockerCli, execID, true); err != nil {
|
if err := MonitorTtySize(ctx, client, dockerCli, execID, true); err != nil {
|
||||||
fmt.Fprintln(dockerCli.Err(), "Error monitoring TTY size:", err)
|
fmt.Fprintln(dockerCli.Err(), "Error monitoring TTY size:", err)
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
composetypes "github.com/docker/cli/cli/compose/types"
|
composetypes "github.com/docker/cli/cli/compose/types"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
networktypes "github.com/docker/docker/api/types/network"
|
networktypes "github.com/docker/docker/api/types/network"
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
)
|
)
|
||||||
@ -51,13 +52,13 @@ func AddStackLabel(namespace Namespace, labels map[string]string) map[string]str
|
|||||||
type networkMap map[string]composetypes.NetworkConfig
|
type networkMap map[string]composetypes.NetworkConfig
|
||||||
|
|
||||||
// Networks from the compose-file type to the engine API type
|
// Networks from the compose-file type to the engine API type
|
||||||
func Networks(namespace Namespace, networks networkMap, servicesNetworks map[string]struct{}) (map[string]networktypes.CreateOptions, []string) {
|
func Networks(namespace Namespace, networks networkMap, servicesNetworks map[string]struct{}) (map[string]types.NetworkCreate, []string) {
|
||||||
if networks == nil {
|
if networks == nil {
|
||||||
networks = make(map[string]composetypes.NetworkConfig)
|
networks = make(map[string]composetypes.NetworkConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
externalNetworks := []string{}
|
externalNetworks := []string{}
|
||||||
result := make(map[string]networktypes.CreateOptions)
|
result := make(map[string]types.NetworkCreate)
|
||||||
for internalName := range servicesNetworks {
|
for internalName := range servicesNetworks {
|
||||||
network := networks[internalName]
|
network := networks[internalName]
|
||||||
if network.External.External {
|
if network.External.External {
|
||||||
@ -65,7 +66,7 @@ func Networks(namespace Namespace, networks networkMap, servicesNetworks map[str
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
createOpts := networktypes.CreateOptions{
|
createOpts := types.NetworkCreate{
|
||||||
Labels: AddStackLabel(namespace, network.Labels),
|
Labels: AddStackLabel(namespace, network.Labels),
|
||||||
Driver: network.Driver,
|
Driver: network.Driver,
|
||||||
Options: network.DriverOpts,
|
Options: network.DriverOpts,
|
||||||
|
@ -4,6 +4,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
composetypes "github.com/docker/cli/cli/compose/types"
|
composetypes "github.com/docker/cli/cli/compose/types"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/network"
|
"github.com/docker/docker/api/types/network"
|
||||||
"gotest.tools/v3/assert"
|
"gotest.tools/v3/assert"
|
||||||
is "gotest.tools/v3/assert/cmp"
|
is "gotest.tools/v3/assert/cmp"
|
||||||
@ -66,7 +67,7 @@ func TestNetworks(t *testing.T) {
|
|||||||
Name: "othername",
|
Name: "othername",
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
expected := map[string]network.CreateOptions{
|
expected := map[string]types.NetworkCreate{
|
||||||
"foo_default": {
|
"foo_default": {
|
||||||
Labels: map[string]string{
|
Labels: map[string]string{
|
||||||
LabelNamespace: "foo",
|
LabelNamespace: "foo",
|
||||||
|
@ -3,15 +3,11 @@ package stack // https://github.com/docker/cli/blob/master/cli/command/stack/rem
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"os/signal"
|
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/network"
|
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
@ -21,87 +17,57 @@ import (
|
|||||||
|
|
||||||
// RunRemove is the swarm implementation of docker stack remove
|
// RunRemove is the swarm implementation of docker stack remove
|
||||||
func RunRemove(ctx context.Context, client *apiclient.Client, opts Remove) error {
|
func RunRemove(ctx context.Context, client *apiclient.Client, opts Remove) error {
|
||||||
sigIntCh := make(chan os.Signal, 1)
|
var errs []string
|
||||||
signal.Notify(sigIntCh, os.Interrupt)
|
for _, namespace := range opts.Namespaces {
|
||||||
defer signal.Stop(sigIntCh)
|
services, err := GetStackServices(ctx, client, namespace)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
waitCh := make(chan struct{})
|
networks, err := getStackNetworks(ctx, client, namespace)
|
||||||
errCh := make(chan error)
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
go func() {
|
var secrets []swarm.Secret
|
||||||
var errs []string
|
if versions.GreaterThanOrEqualTo(client.ClientVersion(), "1.25") {
|
||||||
for _, namespace := range opts.Namespaces {
|
secrets, err = getStackSecrets(ctx, client, namespace)
|
||||||
services, err := GetStackServices(ctx, client, namespace)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errCh <- err
|
return err
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
networks, err := getStackNetworks(ctx, client, namespace)
|
|
||||||
if err != nil {
|
|
||||||
errCh <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
var secrets []swarm.Secret
|
|
||||||
if versions.GreaterThanOrEqualTo(client.ClientVersion(), "1.25") {
|
|
||||||
secrets, err = getStackSecrets(ctx, client, namespace)
|
|
||||||
if err != nil {
|
|
||||||
errCh <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var configs []swarm.Config
|
|
||||||
if versions.GreaterThanOrEqualTo(client.ClientVersion(), "1.30") {
|
|
||||||
configs, err = getStackConfigs(ctx, client, namespace)
|
|
||||||
if err != nil {
|
|
||||||
errCh <- err
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(services)+len(networks)+len(secrets)+len(configs) == 0 {
|
|
||||||
log.Warnf("nothing found in stack: %s", namespace)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
hasError := removeServices(ctx, client, services)
|
|
||||||
hasError = removeSecrets(ctx, client, secrets) || hasError
|
|
||||||
hasError = removeConfigs(ctx, client, configs) || hasError
|
|
||||||
hasError = removeNetworks(ctx, client, networks) || hasError
|
|
||||||
|
|
||||||
if hasError {
|
|
||||||
errs = append(errs, fmt.Sprintf("failed to remove some resources from stack: %s", namespace))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug("polling undeploy status")
|
|
||||||
timeout, err := waitOnTasks(ctx, client, namespace)
|
|
||||||
if timeout {
|
|
||||||
errs = append(errs, err.Error())
|
|
||||||
} else {
|
|
||||||
if err != nil {
|
|
||||||
errs = append(errs, fmt.Sprintf("failed to wait on tasks of stack: %s: %s", namespace, err))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(errs) > 0 {
|
var configs []swarm.Config
|
||||||
errCh <- errors.New(strings.Join(errs, "\n"))
|
if versions.GreaterThanOrEqualTo(client.ClientVersion(), "1.30") {
|
||||||
return
|
configs, err = getStackConfigs(ctx, client, namespace)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
close(waitCh)
|
if len(services)+len(networks)+len(secrets)+len(configs) == 0 {
|
||||||
}()
|
log.Warnf("nothing found in stack: %s", namespace)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
select {
|
hasError := removeServices(ctx, client, services)
|
||||||
case <-waitCh:
|
hasError = removeSecrets(ctx, client, secrets) || hasError
|
||||||
return nil
|
hasError = removeConfigs(ctx, client, configs) || hasError
|
||||||
case <-sigIntCh:
|
hasError = removeNetworks(ctx, client, networks) || hasError
|
||||||
return fmt.Errorf("skipping as requested, undeploy still in progress 🟠")
|
|
||||||
case err := <-errCh:
|
if hasError {
|
||||||
return err
|
errs = append(errs, fmt.Sprintf("failed to remove some resources from stack: %s", namespace))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
err = waitOnTasks(ctx, client, namespace)
|
||||||
|
if err != nil {
|
||||||
|
errs = append(errs, fmt.Sprintf("failed to wait on tasks of stack: %s: %s", namespace, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return errors.Errorf(strings.Join(errs, "\n"))
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -121,7 +87,7 @@ func removeServices(
|
|||||||
var hasError bool
|
var hasError bool
|
||||||
sort.Slice(services, sortServiceByName(services))
|
sort.Slice(services, sortServiceByName(services))
|
||||||
for _, service := range services {
|
for _, service := range services {
|
||||||
log.Debugf("removing service %s", service.Spec.Name)
|
log.Infof("removing service %s", service.Spec.Name)
|
||||||
if err := client.ServiceRemove(ctx, service.ID); err != nil {
|
if err := client.ServiceRemove(ctx, service.ID); err != nil {
|
||||||
hasError = true
|
hasError = true
|
||||||
log.Fatalf("failed to remove service %s: %s", service.ID, err)
|
log.Fatalf("failed to remove service %s: %s", service.ID, err)
|
||||||
@ -133,11 +99,11 @@ func removeServices(
|
|||||||
func removeNetworks(
|
func removeNetworks(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
client *apiclient.Client,
|
client *apiclient.Client,
|
||||||
networks []network.Inspect,
|
networks []types.NetworkResource,
|
||||||
) bool {
|
) bool {
|
||||||
var hasError bool
|
var hasError bool
|
||||||
for _, network := range networks {
|
for _, network := range networks {
|
||||||
log.Debugf("removing network %s", network.Name)
|
log.Infof("removing network %s", network.Name)
|
||||||
if err := client.NetworkRemove(ctx, network.ID); err != nil {
|
if err := client.NetworkRemove(ctx, network.ID); err != nil {
|
||||||
hasError = true
|
hasError = true
|
||||||
log.Fatalf("failed to remove network %s: %s", network.ID, err)
|
log.Fatalf("failed to remove network %s: %s", network.ID, err)
|
||||||
@ -153,7 +119,7 @@ func removeSecrets(
|
|||||||
) bool {
|
) bool {
|
||||||
var hasError bool
|
var hasError bool
|
||||||
for _, secret := range secrets {
|
for _, secret := range secrets {
|
||||||
log.Debugf("removing secret %s", secret.Spec.Name)
|
log.Infof("removing secret %s", secret.Spec.Name)
|
||||||
if err := client.SecretRemove(ctx, secret.ID); err != nil {
|
if err := client.SecretRemove(ctx, secret.ID); err != nil {
|
||||||
hasError = true
|
hasError = true
|
||||||
log.Fatalf("Failed to remove secret %s: %s", secret.ID, err)
|
log.Fatalf("Failed to remove secret %s: %s", secret.ID, err)
|
||||||
@ -169,7 +135,7 @@ func removeConfigs(
|
|||||||
) bool {
|
) bool {
|
||||||
var hasError bool
|
var hasError bool
|
||||||
for _, config := range configs {
|
for _, config := range configs {
|
||||||
log.Debugf("removing config %s", config.Spec.Name)
|
log.Infof("removing config %s", config.Spec.Name)
|
||||||
if err := client.ConfigRemove(ctx, config.ID); err != nil {
|
if err := client.ConfigRemove(ctx, config.ID); err != nil {
|
||||||
hasError = true
|
hasError = true
|
||||||
log.Fatalf("failed to remove config %s: %s", config.ID, err)
|
log.Fatalf("failed to remove config %s: %s", config.ID, err)
|
||||||
@ -203,23 +169,12 @@ func terminalState(state swarm.TaskState) bool {
|
|||||||
return numberedStates[state] > numberedStates[swarm.TaskStateRunning]
|
return numberedStates[state] > numberedStates[swarm.TaskStateRunning]
|
||||||
}
|
}
|
||||||
|
|
||||||
func waitOnTasks(ctx context.Context, client apiclient.APIClient, namespace string) (bool, error) {
|
func waitOnTasks(ctx context.Context, client apiclient.APIClient, namespace string) error {
|
||||||
var timedOut bool
|
|
||||||
|
|
||||||
log.Debugf("waiting on undeploy tasks (timeout=%v secs)", WaitTimeout)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
t := time.Duration(WaitTimeout) * time.Second
|
|
||||||
<-time.After(t)
|
|
||||||
log.Debug("timed out on undeploy")
|
|
||||||
timedOut = true
|
|
||||||
}()
|
|
||||||
|
|
||||||
terminalStatesReached := 0
|
terminalStatesReached := 0
|
||||||
for {
|
for {
|
||||||
tasks, err := getStackTasks(ctx, client, namespace)
|
tasks, err := getStackTasks(ctx, client, namespace)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, fmt.Errorf("failed to get tasks: %w", err)
|
return fmt.Errorf("failed to get tasks: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, task := range tasks {
|
for _, task := range tasks {
|
||||||
@ -232,11 +187,6 @@ func waitOnTasks(ctx context.Context, client apiclient.APIClient, namespace stri
|
|||||||
if terminalStatesReached == len(tasks) {
|
if terminalStatesReached == len(tasks) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
if timedOut {
|
|
||||||
return true, fmt.Errorf("deployment timed out 🟠")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
return false, nil
|
|
||||||
}
|
}
|
||||||
|
@ -3,27 +3,24 @@ package stack // https://github.com/docker/cli/blob/master/cli/command/stack/swa
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"os/signal"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
stdlibErr "errors"
|
stdlibErr "errors"
|
||||||
|
|
||||||
tea "github.com/charmbracelet/bubbletea"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/config"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/ui"
|
|
||||||
"coopcloud.tech/abra/pkg/upstream/convert"
|
"coopcloud.tech/abra/pkg/upstream/convert"
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command/service/progress"
|
||||||
"github.com/docker/cli/cli/command/stack/formatter"
|
"github.com/docker/cli/cli/command/stack/formatter"
|
||||||
composetypes "github.com/docker/cli/cli/compose/types"
|
composetypes "github.com/docker/cli/cli/compose/types"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/api/types/container"
|
"github.com/docker/docker/api/types/container"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
networktypes "github.com/docker/docker/api/types/network"
|
|
||||||
"github.com/docker/docker/api/types/swarm"
|
"github.com/docker/docker/api/types/swarm"
|
||||||
"github.com/docker/docker/api/types/versions"
|
"github.com/docker/docker/api/types/versions"
|
||||||
"github.com/docker/docker/client"
|
"github.com/docker/docker/client"
|
||||||
@ -109,22 +106,13 @@ type DeployMeta struct {
|
|||||||
ChaosVersion string // the --chaos deployment version
|
ChaosVersion string // the --chaos deployment version
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d DeployMeta) String() string {
|
|
||||||
var out string
|
|
||||||
out += fmt.Sprintf("{isDeployed: %v, ", d.IsDeployed)
|
|
||||||
out += fmt.Sprintf("version: %s, ", d.Version)
|
|
||||||
out += fmt.Sprintf("isChaos: %v, ", d.IsChaos)
|
|
||||||
out += fmt.Sprintf("chaosVersion: %s}", d.ChaosVersion)
|
|
||||||
return out
|
|
||||||
}
|
|
||||||
|
|
||||||
// IsDeployed gathers metadata about an app deployment.
|
// IsDeployed gathers metadata about an app deployment.
|
||||||
func IsDeployed(ctx context.Context, cl *dockerClient.Client, stackName string) (DeployMeta, error) {
|
func IsDeployed(ctx context.Context, cl *dockerClient.Client, stackName string) (DeployMeta, error) {
|
||||||
deployMeta := DeployMeta{
|
deployMeta := DeployMeta{
|
||||||
IsDeployed: false,
|
IsDeployed: false,
|
||||||
Version: "unknown",
|
Version: "unknown",
|
||||||
IsChaos: false,
|
IsChaos: false,
|
||||||
ChaosVersion: config.CHAOS_DEFAULT,
|
ChaosVersion: "false", // NOTE(d1): match string type used on label
|
||||||
}
|
}
|
||||||
|
|
||||||
filter := filters.NewArgs()
|
filter := filters.NewArgs()
|
||||||
@ -178,7 +166,7 @@ func IsDeployed(ctx context.Context, cl *dockerClient.Client, stackName string)
|
|||||||
func pruneServices(ctx context.Context, cl *dockerClient.Client, namespace convert.Namespace, services map[string]struct{}) {
|
func pruneServices(ctx context.Context, cl *dockerClient.Client, namespace convert.Namespace, services map[string]struct{}) {
|
||||||
oldServices, err := GetStackServices(ctx, cl, namespace.Name())
|
oldServices, err := GetStackServices(ctx, cl, namespace.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warnf("failed to list services: %s", err)
|
log.Infof("failed to list services: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
pruneServices := []swarm.Service{}
|
pruneServices := []swarm.Service{}
|
||||||
@ -192,17 +180,7 @@ func pruneServices(ctx context.Context, cl *dockerClient.Client, namespace conve
|
|||||||
}
|
}
|
||||||
|
|
||||||
// RunDeploy is the swarm implementation of docker stack deploy
|
// RunDeploy is the swarm implementation of docker stack deploy
|
||||||
func RunDeploy(
|
func RunDeploy(cl *dockerClient.Client, opts Deploy, cfg *composetypes.Config, appName string, dontWait bool) error {
|
||||||
cl *dockerClient.Client,
|
|
||||||
opts Deploy,
|
|
||||||
cfg *composetypes.Config,
|
|
||||||
appName string,
|
|
||||||
serverName string,
|
|
||||||
dontWait bool,
|
|
||||||
filters filters.Args,
|
|
||||||
) error {
|
|
||||||
log.Info("initialising deployment")
|
|
||||||
|
|
||||||
if err := validateResolveImageFlag(&opts); err != nil {
|
if err := validateResolveImageFlag(&opts); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -212,16 +190,7 @@ func RunDeploy(
|
|||||||
opts.ResolveImage = ResolveImageNever
|
opts.ResolveImage = ResolveImageNever
|
||||||
}
|
}
|
||||||
|
|
||||||
return deployCompose(
|
return deployCompose(context.Background(), cl, opts, cfg, appName, dontWait)
|
||||||
context.Background(),
|
|
||||||
cl,
|
|
||||||
opts,
|
|
||||||
cfg,
|
|
||||||
appName,
|
|
||||||
serverName,
|
|
||||||
dontWait,
|
|
||||||
filters,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// validateResolveImageFlag validates the opts.resolveImage command line option
|
// validateResolveImageFlag validates the opts.resolveImage command line option
|
||||||
@ -234,16 +203,7 @@ func validateResolveImageFlag(opts *Deploy) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func deployCompose(
|
func deployCompose(ctx context.Context, cl *dockerClient.Client, opts Deploy, config *composetypes.Config, appName string, dontWait bool) error {
|
||||||
ctx context.Context,
|
|
||||||
cl *dockerClient.Client,
|
|
||||||
opts Deploy,
|
|
||||||
config *composetypes.Config,
|
|
||||||
appName string,
|
|
||||||
serverName string,
|
|
||||||
dontWait bool,
|
|
||||||
filters filters.Args,
|
|
||||||
) error {
|
|
||||||
namespace := convert.NewNamespace(opts.Namespace)
|
namespace := convert.NewNamespace(opts.Namespace)
|
||||||
|
|
||||||
if opts.Prune {
|
if opts.Prune {
|
||||||
@ -284,14 +244,7 @@ func deployCompose(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
serviceIDs, err := deployServices(
|
serviceIDs, err := deployServices(ctx, cl, services, namespace, opts.SendRegistryAuth, opts.ResolveImage)
|
||||||
ctx,
|
|
||||||
cl,
|
|
||||||
services,
|
|
||||||
namespace,
|
|
||||||
opts.SendRegistryAuth,
|
|
||||||
opts.ResolveImage,
|
|
||||||
)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -301,17 +254,14 @@ func deployCompose(
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
waitOpts := WaitOpts{
|
log.Infof("waiting for %s to deploy... please hold 🤚", appName)
|
||||||
Services: serviceIDs,
|
|
||||||
AppName: appName,
|
|
||||||
ServerName: serverName,
|
|
||||||
Filters: filters,
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := WaitOnServices(ctx, cl, waitOpts); err != nil {
|
if err := waitOnServices(ctx, cl, serviceIDs, appName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infof("successfully deployed %s", appName)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,7 +286,7 @@ func validateExternalNetworks(ctx context.Context, client dockerClient.NetworkAP
|
|||||||
// local-scoped networks, so there's no need to inspect them.
|
// local-scoped networks, so there's no need to inspect them.
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
network, err := client.NetworkInspect(ctx, networkName, networktypes.InspectOptions{})
|
network, err := client.NetworkInspect(ctx, networkName, types.NetworkInspectOptions{})
|
||||||
switch {
|
switch {
|
||||||
case dockerClient.IsErrNotFound(err):
|
case dockerClient.IsErrNotFound(err):
|
||||||
return errors.Errorf("network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed, which you can do by running this on the server: docker network create -d overlay proxy", networkName)
|
return errors.Errorf("network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed, which you can do by running this on the server: docker network create -d overlay proxy", networkName)
|
||||||
@ -382,7 +332,7 @@ func createConfigs(ctx context.Context, cl *dockerClient.Client, configs []swarm
|
|||||||
}
|
}
|
||||||
case dockerClient.IsErrNotFound(err):
|
case dockerClient.IsErrNotFound(err):
|
||||||
// config does not exist, then we create a new one.
|
// config does not exist, then we create a new one.
|
||||||
log.Debugf("creating config %s", configSpec.Name)
|
log.Infof("creating config %s", configSpec.Name)
|
||||||
if _, err := cl.ConfigCreate(ctx, configSpec); err != nil {
|
if _, err := cl.ConfigCreate(ctx, configSpec); err != nil {
|
||||||
return errors.Wrapf(err, "failed to create config %s", configSpec.Name)
|
return errors.Wrapf(err, "failed to create config %s", configSpec.Name)
|
||||||
}
|
}
|
||||||
@ -393,13 +343,13 @@ func createConfigs(ctx context.Context, cl *dockerClient.Client, configs []swarm
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func createNetworks(ctx context.Context, cl *dockerClient.Client, namespace convert.Namespace, networks map[string]networktypes.CreateOptions) error {
|
func createNetworks(ctx context.Context, cl *dockerClient.Client, namespace convert.Namespace, networks map[string]types.NetworkCreate) error {
|
||||||
existingNetworks, err := getStackNetworks(ctx, cl, namespace.Name())
|
existingNetworks, err := getStackNetworks(ctx, cl, namespace.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
existingNetworkMap := make(map[string]networktypes.Inspect)
|
existingNetworkMap := make(map[string]types.NetworkResource)
|
||||||
for _, network := range existingNetworks {
|
for _, network := range existingNetworks {
|
||||||
existingNetworkMap[network.Name] = network
|
existingNetworkMap[network.Name] = network
|
||||||
}
|
}
|
||||||
@ -413,7 +363,7 @@ func createNetworks(ctx context.Context, cl *dockerClient.Client, namespace conv
|
|||||||
createOpts.Driver = defaultNetworkDriver
|
createOpts.Driver = defaultNetworkDriver
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("creating network %s", name)
|
log.Infof("creating network %s", name)
|
||||||
if _, err := cl.NetworkCreate(ctx, name, createOpts); err != nil {
|
if _, err := cl.NetworkCreate(ctx, name, createOpts); err != nil {
|
||||||
return errors.Wrapf(err, "failed to create network %s", name)
|
return errors.Wrapf(err, "failed to create network %s", name)
|
||||||
}
|
}
|
||||||
@ -427,13 +377,10 @@ func deployServices(
|
|||||||
services map[string]swarm.ServiceSpec,
|
services map[string]swarm.ServiceSpec,
|
||||||
namespace convert.Namespace,
|
namespace convert.Namespace,
|
||||||
sendAuth bool,
|
sendAuth bool,
|
||||||
resolveImage string,
|
resolveImage string) ([]string, error) {
|
||||||
) ([]ui.ServiceMeta, error) {
|
|
||||||
var servicesMeta []ui.ServiceMeta
|
|
||||||
|
|
||||||
existingServices, err := GetStackServices(ctx, cl, namespace.Name())
|
existingServices, err := GetStackServices(ctx, cl, namespace.Name())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return servicesMeta, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
existingServiceMap := make(map[string]swarm.Service)
|
existingServiceMap := make(map[string]swarm.Service)
|
||||||
@ -441,6 +388,8 @@ func deployServices(
|
|||||||
existingServiceMap[service.Spec.Name] = service
|
existingServiceMap[service.Spec.Name] = service
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var serviceIDs []string
|
||||||
|
|
||||||
for internalName, serviceSpec := range services {
|
for internalName, serviceSpec := range services {
|
||||||
var (
|
var (
|
||||||
name = namespace.Scope(internalName)
|
name = namespace.Scope(internalName)
|
||||||
@ -448,23 +397,8 @@ func deployServices(
|
|||||||
encodedAuth string
|
encodedAuth string
|
||||||
)
|
)
|
||||||
|
|
||||||
// When sendAuth is set, use the docker cli to retrieve the auth token
|
|
||||||
// for the image we are deploying.
|
|
||||||
// This enables using a private registry by running docker login on the
|
|
||||||
// machine, that abra is executed.
|
|
||||||
if sendAuth {
|
|
||||||
dockerCLI, err := command.NewDockerCli()
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("retrieving docker auth token: failed create docker cli: %s", err)
|
|
||||||
}
|
|
||||||
encodedAuth, err = command.RetrieveAuthTokenFromImage(dockerCLI.ConfigFile(), image)
|
|
||||||
if err != nil {
|
|
||||||
log.Errorf("failed to retrieve registry auth for image %s: %s", image, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if service, exists := existingServiceMap[name]; exists {
|
if service, exists := existingServiceMap[name]; exists {
|
||||||
log.Debugf("updating %s", name)
|
log.Infof("updating %s", name)
|
||||||
|
|
||||||
updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth}
|
updateOpts := types.ServiceUpdateOptions{EncodedRegistryAuth: encodedAuth}
|
||||||
|
|
||||||
@ -506,12 +440,9 @@ func deployServices(
|
|||||||
log.Warn(warning)
|
log.Warn(warning)
|
||||||
}
|
}
|
||||||
|
|
||||||
servicesMeta = append(servicesMeta, ui.ServiceMeta{
|
serviceIDs = append(serviceIDs, service.ID)
|
||||||
Name: name,
|
|
||||||
ID: service.ID,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
log.Debugf("creating %s", name)
|
log.Infof("creating %s", name)
|
||||||
|
|
||||||
createOpts := types.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth}
|
createOpts := types.ServiceCreateOptions{EncodedRegistryAuth: encodedAuth}
|
||||||
|
|
||||||
@ -525,18 +456,15 @@ func deployServices(
|
|||||||
return nil, errors.Wrapf(err, "failed to create %s", name)
|
return nil, errors.Wrapf(err, "failed to create %s", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
servicesMeta = append(servicesMeta, ui.ServiceMeta{
|
serviceIDs = append(serviceIDs, serviceCreateResponse.ID)
|
||||||
Name: name,
|
|
||||||
ID: serviceCreateResponse.ID,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return servicesMeta, nil
|
return serviceIDs, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStackNetworks(ctx context.Context, dockerclient client.APIClient, namespace string) ([]networktypes.Inspect, error) {
|
func getStackNetworks(ctx context.Context, dockerclient client.APIClient, namespace string) ([]types.NetworkResource, error) {
|
||||||
return dockerclient.NetworkList(ctx, networktypes.ListOptions{Filters: getStackFilter(namespace)})
|
return dockerclient.NetworkList(ctx, types.NetworkListOptions{Filters: getStackFilter(namespace)})
|
||||||
}
|
}
|
||||||
|
|
||||||
func getStackSecrets(ctx context.Context, dockerclient client.APIClient, namespace string) ([]swarm.Secret, error) {
|
func getStackSecrets(ctx context.Context, dockerclient client.APIClient, namespace string) ([]swarm.Secret, error) {
|
||||||
@ -547,91 +475,69 @@ func getStackConfigs(ctx context.Context, dockerclient client.APIClient, namespa
|
|||||||
return dockerclient.ConfigList(ctx, types.ConfigListOptions{Filters: getStackFilter(namespace)})
|
return dockerclient.ConfigList(ctx, types.ConfigListOptions{Filters: getStackFilter(namespace)})
|
||||||
}
|
}
|
||||||
|
|
||||||
func timestamp() string {
|
func waitOnServices(ctx context.Context, cl *dockerClient.Client, serviceIDs []string, appName string) error {
|
||||||
ts := time.Now().UTC().Format(time.RFC3339)
|
var errs []error
|
||||||
return strings.Replace(ts, ":", "", -1) // get rid of offensive colons
|
|
||||||
}
|
|
||||||
|
|
||||||
type WaitOpts struct {
|
for _, serviceID := range serviceIDs {
|
||||||
AppName string
|
if err := WaitOnService(ctx, cl, serviceID, appName); err != nil {
|
||||||
Filters filters.Args
|
errs = append(errs, fmt.Errorf("%s: %w", serviceID, err))
|
||||||
NoLog bool
|
}
|
||||||
Quiet bool
|
|
||||||
ServerName string
|
|
||||||
Services []ui.ServiceMeta
|
|
||||||
}
|
|
||||||
|
|
||||||
func WaitOnServices(ctx context.Context, cl *dockerClient.Client, opts WaitOpts) error {
|
|
||||||
timeout := time.Duration(WaitTimeout) * time.Second
|
|
||||||
model := ui.DeployInitialModel(ctx, cl, opts.Services, opts.AppName, timeout, opts.Filters)
|
|
||||||
tui := tea.NewProgram(model)
|
|
||||||
|
|
||||||
if !opts.Quiet {
|
|
||||||
log.Info("polling deployment status")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m, err := log.Without(
|
if len(errs) > 0 {
|
||||||
func() (tea.Model, error) {
|
|
||||||
return tui.Run()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("waitOnServices: error running TUI: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
deployModel := m.(ui.Model)
|
|
||||||
if deployModel.TimedOut || deployModel.Failed || deployModel.Quit {
|
|
||||||
var errs []error
|
|
||||||
|
|
||||||
if deployModel.Failed {
|
|
||||||
errs = append(errs, fmt.Errorf("deploy failed 🛑"))
|
|
||||||
} else if deployModel.TimedOut {
|
|
||||||
errs = append(errs, fmt.Errorf("deploy timed out 🟠"))
|
|
||||||
} else {
|
|
||||||
errs = append(errs, fmt.Errorf("deploy in progress 🟠"))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, s := range *deployModel.Streams {
|
|
||||||
if s.Err != nil {
|
|
||||||
errs = append(errs, fmt.Errorf("%s: %s", s.Name, s.Err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(*deployModel.Logs) > 0 && !opts.NoLog {
|
|
||||||
logsPath := filepath.Join(
|
|
||||||
config.LOGS_DIR,
|
|
||||||
opts.ServerName,
|
|
||||||
fmt.Sprintf("%s_%s", opts.AppName, timestamp()),
|
|
||||||
)
|
|
||||||
|
|
||||||
if err := os.MkdirAll(filepath.Join(config.LOGS_DIR, opts.ServerName), 0o764); err != nil {
|
|
||||||
return fmt.Errorf("waitOnServices: error creating log dir: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
file, err := os.Create(logsPath)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("waitOnServices: error opening file: %s", err)
|
|
||||||
}
|
|
||||||
defer file.Close()
|
|
||||||
|
|
||||||
s := strings.Join(*deployModel.Logs, "\n")
|
|
||||||
if _, err := file.WriteString(s); err != nil {
|
|
||||||
return fmt.Errorf("waitOnServices: writeFile: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
errs = append(errs, fmt.Errorf("logs: %s", logsPath))
|
|
||||||
}
|
|
||||||
|
|
||||||
return stdlibErr.Join(errs...)
|
return stdlibErr.Join(errs...)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !opts.Quiet {
|
|
||||||
log.Info("deploy succeeded 🟢")
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// https://github.com/docker/cli/blob/master/cli/command/service/helpers.go
|
||||||
|
// https://github.com/docker/cli/blob/master/cli/command/service/progress/progress.go
|
||||||
|
func WaitOnService(ctx context.Context, cl *dockerClient.Client, serviceID, appName string) error {
|
||||||
|
errChan := make(chan error, 1)
|
||||||
|
pipeReader, pipeWriter := io.Pipe()
|
||||||
|
|
||||||
|
sigintChannel := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigintChannel, os.Interrupt)
|
||||||
|
defer signal.Stop(sigintChannel)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
errChan <- progress.ServiceProgress(ctx, cl, serviceID, pipeWriter)
|
||||||
|
}()
|
||||||
|
|
||||||
|
go io.Copy(ioutil.Discard, pipeReader)
|
||||||
|
|
||||||
|
timeout := time.Duration(WaitTimeout) * time.Second
|
||||||
|
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
return err
|
||||||
|
case <-sigintChannel:
|
||||||
|
return fmt.Errorf(`
|
||||||
|
Not waiting for %s to deploy. The deployment is ongoing...
|
||||||
|
|
||||||
|
If you want to stop the deployment, try:
|
||||||
|
|
||||||
|
abra app undeploy %s`, appName, appName)
|
||||||
|
case <-time.After(timeout):
|
||||||
|
return fmt.Errorf(`
|
||||||
|
%s has not converged (%s second timeout reached).
|
||||||
|
|
||||||
|
This does not necessarily mean your deployment has failed, it may just be that
|
||||||
|
the app is taking longer to deploy based on your server resources or network
|
||||||
|
latency.
|
||||||
|
|
||||||
|
You can track latest deployment status with:
|
||||||
|
|
||||||
|
abra app ps %s
|
||||||
|
|
||||||
|
And inspect the logs with:
|
||||||
|
|
||||||
|
abra app logs %s
|
||||||
|
`, appName, timeout, appName, appName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Copypasta from https://github.com/docker/cli/blob/master/cli/command/stack/swarm/list.go
|
// Copypasta from https://github.com/docker/cli/blob/master/cli/command/stack/swarm/list.go
|
||||||
// GetStacks lists the swarm stacks.
|
// GetStacks lists the swarm stacks.
|
||||||
func GetStacks(cl *dockerClient.Client) ([]*formatter.Stack, error) {
|
func GetStacks(cl *dockerClient.Client) ([]*formatter.Stack, error) {
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
ABRA_VERSION="0.10.1-beta"
|
ABRA_VERSION="0.9.0-beta"
|
||||||
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/toolshed/abra/releases/tags/$ABRA_VERSION"
|
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$ABRA_VERSION"
|
||||||
RC_VERSION="0.10.1-beta"
|
RC_VERSION="0.8.0-rc1-beta"
|
||||||
RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/toolshed/abra/releases/tags/$RC_VERSION"
|
RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/coop-cloud/abra/releases/tags/$RC_VERSION"
|
||||||
|
|
||||||
for arg in "$@"; do
|
for arg in "$@"; do
|
||||||
if [ "$arg" == "--rc" ]; then
|
if [ "$arg" == "--rc" ]; then
|
||||||
@ -40,7 +40,7 @@ function install_abra_release {
|
|||||||
if ! type "wget" > /dev/null 2>&1; then
|
if ! type "wget" > /dev/null 2>&1; then
|
||||||
echo "'wget' is not installed, cannot proceed..."
|
echo "'wget' is not installed, cannot proceed..."
|
||||||
echo "perhaps try installing manually via the releases URL?"
|
echo "perhaps try installing manually via the releases URL?"
|
||||||
echo "https://git.coopcloud.tech/toolshed/abra/releases"
|
echo "https://git.coopcloud.tech/coop-cloud/abra/releases"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
@ -3,5 +3,5 @@ STACK := abra_installer_script
|
|||||||
default: deploy
|
default: deploy
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
@DOCKER_CONTEXT=swarm-0.coopcloud.tech docker stack rm $(STACK) && \
|
@DOCKER_CONTEXT=swarm.autonomic.zone docker stack rm $(STACK) && \
|
||||||
DOCKER_CONTEXT=swarm-0.coopcloud.tech docker stack deploy -c compose.yml $(STACK)
|
DOCKER_CONTEXT=swarm.autonomic.zone docker stack deploy -c compose.yml $(STACK)
|
||||||
|
@ -42,7 +42,7 @@ echo "========================================================================"
|
|||||||
echo "CLONING ABRA"
|
echo "CLONING ABRA"
|
||||||
echo "========================================================================"
|
echo "========================================================================"
|
||||||
rm -rf abra
|
rm -rf abra
|
||||||
git clone ssh://git@git.coopcloud.tech:2222/toolshed/abra.git
|
git clone ssh://git@git.coopcloud.tech:2222/coop-cloud/abra.git
|
||||||
cd abra
|
cd abra
|
||||||
git checkout main
|
git checkout main
|
||||||
echo "========================================================================"
|
echo "========================================================================"
|
||||||
|
@ -50,9 +50,6 @@ teardown(){
|
|||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'locally unstaged changes'
|
assert_output --partial 'locally unstaged changes'
|
||||||
|
|
||||||
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
|
||||||
assert_equal "$(_git_status)" "?? foo"
|
|
||||||
|
|
||||||
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
}
|
}
|
||||||
@ -65,9 +62,6 @@ teardown(){
|
|||||||
run $ABRA app check "$TEST_APP_DOMAIN" --chaos
|
run $ABRA app check "$TEST_APP_DOMAIN" --chaos
|
||||||
assert_success
|
assert_success
|
||||||
|
|
||||||
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
|
||||||
assert_equal "$(_git_status)" "?? foo"
|
|
||||||
|
|
||||||
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
}
|
}
|
||||||
|
@ -53,9 +53,6 @@ teardown(){
|
|||||||
assert_failure
|
assert_failure
|
||||||
assert_output --partial 'locally unstaged changes'
|
assert_output --partial 'locally unstaged changes'
|
||||||
|
|
||||||
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
|
||||||
assert_equal "$(_git_status)" "?? foo"
|
|
||||||
|
|
||||||
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
}
|
}
|
||||||
@ -69,9 +66,6 @@ teardown(){
|
|||||||
assert_success
|
assert_success
|
||||||
assert_output --partial 'baz'
|
assert_output --partial 'baz'
|
||||||
|
|
||||||
assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
|
||||||
assert_equal "$(_git_status)" "?? foo"
|
|
||||||
|
|
||||||
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo"
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user