forked from toolshed/abra
Compare commits
1 Commits
fix-crash-
...
private-re
| Author | SHA1 | Date | |
|---|---|---|---|
| 7a24bf9cd5 |
@ -4,4 +4,5 @@
|
|||||||
Dockerfile
|
Dockerfile
|
||||||
abra
|
abra
|
||||||
dist
|
dist
|
||||||
|
kadabra
|
||||||
tags
|
tags
|
||||||
|
|||||||
31
.drone.yml
31
.drone.yml
@ -7,40 +7,9 @@ steps:
|
|||||||
commands:
|
commands:
|
||||||
- make check
|
- make check
|
||||||
|
|
||||||
- name: xgettext-go
|
|
||||||
image: git.coopcloud.tech/toolshed/drone-xgettext-go:latest
|
|
||||||
settings:
|
|
||||||
keyword: i18n.G
|
|
||||||
keyword_ctx: i18n.GC
|
|
||||||
out: pkg/i18n/locales/abra.pot
|
|
||||||
comments_tag: translators
|
|
||||||
depends_on:
|
|
||||||
- make check
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
exclude:
|
|
||||||
- tag
|
|
||||||
|
|
||||||
- name: xgettext-go status
|
|
||||||
image: golang:1.24-alpine3.22
|
|
||||||
commands:
|
|
||||||
- apk add patchutils git make
|
|
||||||
- cd /drone/src
|
|
||||||
- sed -i "s/charset=CHARSET/charset=UTF-8/g" pkg/i18n/locales/*.pot
|
|
||||||
- git diff pkg/i18n/locales/abra.pot | grepdiff --output-matching=hunk POT-Creation-Date | git apply --reverse --allow-empty
|
|
||||||
- git diff
|
|
||||||
- git diff-files --exit-code
|
|
||||||
depends_on:
|
|
||||||
- xgettext-go
|
|
||||||
when:
|
|
||||||
event:
|
|
||||||
exclude:
|
|
||||||
- tag
|
|
||||||
|
|
||||||
- name: make test
|
- name: make test
|
||||||
image: golang:1.24
|
image: golang:1.24
|
||||||
environment:
|
environment:
|
||||||
ABRA_DIR: $HOME/.abra
|
|
||||||
CATL_URL: https://git.coopcloud.tech/toolshed/recipes-catalogue-json.git
|
CATL_URL: https://git.coopcloud.tech/toolshed/recipes-catalogue-json.git
|
||||||
commands:
|
commands:
|
||||||
- mkdir -p $HOME/.abra
|
- mkdir -p $HOME/.abra
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,9 +1,8 @@
|
|||||||
*.tar.gz
|
|
||||||
*fmtcoverage.html
|
*fmtcoverage.html
|
||||||
.e2e.env
|
.e2e.env
|
||||||
.envrc
|
.envrc
|
||||||
.vscode/
|
.vscode/
|
||||||
/abra
|
/abra
|
||||||
/bin
|
/kadabra
|
||||||
dist/
|
dist/
|
||||||
tests/integration/.bats
|
tests/integration/.bats
|
||||||
|
|||||||
@ -32,6 +32,31 @@ builds:
|
|||||||
- "-s"
|
- "-s"
|
||||||
- "-w"
|
- "-w"
|
||||||
|
|
||||||
|
- id: kadabra
|
||||||
|
binary: kadabra
|
||||||
|
dir: cmd/kadabra
|
||||||
|
env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
goos:
|
||||||
|
- linux
|
||||||
|
- darwin
|
||||||
|
goarch:
|
||||||
|
- 386
|
||||||
|
- amd64
|
||||||
|
- arm
|
||||||
|
- arm64
|
||||||
|
goarm:
|
||||||
|
- 5
|
||||||
|
- 6
|
||||||
|
- 7
|
||||||
|
gcflags:
|
||||||
|
- "all=-l -B"
|
||||||
|
ldflags:
|
||||||
|
- "-X 'main.Commit={{ .Commit }}'"
|
||||||
|
- "-X 'main.Version={{ .Version }}'"
|
||||||
|
- "-s"
|
||||||
|
- "-w"
|
||||||
|
|
||||||
checksum:
|
checksum:
|
||||||
name_template: "checksums.txt"
|
name_template: "checksums.txt"
|
||||||
|
|
||||||
|
|||||||
@ -5,21 +5,18 @@
|
|||||||
|
|
||||||
- 3wordchant
|
- 3wordchant
|
||||||
- ammaratef45
|
- ammaratef45
|
||||||
- apfelwurm
|
|
||||||
- basebuilder
|
|
||||||
- cassowary
|
- cassowary
|
||||||
- chasqui
|
|
||||||
- codegod100
|
- codegod100
|
||||||
- decentral1se
|
- decentral1se
|
||||||
- fauno
|
- fauno
|
||||||
- frando
|
- frando
|
||||||
- iexos
|
|
||||||
- kawaiipunk
|
- kawaiipunk
|
||||||
- knoflook
|
- knoflook
|
||||||
- mayel
|
|
||||||
- moritz
|
- moritz
|
||||||
- p4u1
|
- p4u1
|
||||||
- rix
|
- rix
|
||||||
- roxxers
|
- roxxers
|
||||||
- vera
|
- vera
|
||||||
- yksflip
|
- yksflip
|
||||||
|
- basebuilder
|
||||||
|
- mayel
|
||||||
|
|||||||
72
Makefile
72
Makefile
@ -1,35 +1,48 @@
|
|||||||
ABRA := ./cmd/abra
|
ABRA := ./cmd/abra
|
||||||
XGETTEXT := ./bin/xgettext-go
|
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.24
|
||||||
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"
|
||||||
DOMAIN := abra
|
|
||||||
POFILES := $(wildcard pkg/i18n/locales/*.po)
|
|
||||||
MOFILES := $(patsubst %.po,%.mo,$(POFILES))
|
|
||||||
LINGUAS := $(basename $(POFILES))
|
|
||||||
|
|
||||||
export GOPRIVATE=coopcloud.tech
|
export GOPRIVATE=coopcloud.tech
|
||||||
|
|
||||||
all: format check build
|
# NOTE(d1): default `make` optimised for Abra hacking
|
||||||
|
all: format check build-abra test
|
||||||
|
|
||||||
run:
|
run-abra:
|
||||||
@go run -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(ABRA)
|
@go run -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(ABRA)
|
||||||
|
|
||||||
install:
|
run-kadabra:
|
||||||
|
@go run -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(KADABRA)
|
||||||
|
|
||||||
|
install-abra:
|
||||||
@go install -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(ABRA)
|
@go install -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(ABRA)
|
||||||
|
|
||||||
build:
|
install-kadabra:
|
||||||
|
@go install -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(KADABRA)
|
||||||
|
|
||||||
|
install: install-abra install-kadabra
|
||||||
|
|
||||||
|
build-abra:
|
||||||
@go build -v -gcflags=$(GCFLAGS) -ldflags=$(DIST_LDFLAGS) $(ABRA)
|
@go build -v -gcflags=$(GCFLAGS) -ldflags=$(DIST_LDFLAGS) $(ABRA)
|
||||||
|
|
||||||
build-docker:
|
build-kadabra:
|
||||||
|
@go build -v -gcflags=$(GCFLAGS) -ldflags=$(DIST_LDFLAGS) $(KADABRA)
|
||||||
|
|
||||||
|
build: build-abra build-kadabra
|
||||||
|
|
||||||
|
build-docker-abra:
|
||||||
@docker run -it -v $(PWD):/abra golang:$(GOVERSION) \
|
@docker run -it -v $(PWD):/abra golang:$(GOVERSION) \
|
||||||
bash -c 'cd /abra; ./scripts/docker/build.sh'
|
bash -c 'cd /abra; ./scripts/docker/build.sh'
|
||||||
|
|
||||||
|
build-docker: build-docker-abra
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@rm '$(GOPATH)/bin/abra'
|
@rm '$(GOPATH)/bin/abra'
|
||||||
|
@rm '$(GOPATH)/bin/kadabra'
|
||||||
|
|
||||||
format:
|
format:
|
||||||
@gofmt -s -w $$(find . -type f -name '*.go' | grep -v "/vendor/")
|
@gofmt -s -w $$(find . -type f -name '*.go' | grep -v "/vendor/")
|
||||||
@ -41,47 +54,8 @@ check:
|
|||||||
test:
|
test:
|
||||||
@go test ./... -cover -v
|
@go test ./... -cover -v
|
||||||
|
|
||||||
find-tests:
|
|
||||||
@find . -name "*_test.go"
|
|
||||||
|
|
||||||
loc:
|
loc:
|
||||||
@find . -name "*.go" | xargs wc -l
|
@find . -name "*.go" | xargs wc -l
|
||||||
|
|
||||||
deps:
|
deps:
|
||||||
@go get -t -u ./...
|
@go get -t -u ./...
|
||||||
|
|
||||||
.PHONY: i18n
|
|
||||||
i18n: update-pot update-pot-po-metadata update-po build-mo
|
|
||||||
|
|
||||||
.PHONY: update-po
|
|
||||||
update-po:
|
|
||||||
@set -eu; \
|
|
||||||
for lang in $(LINGUAS); do \
|
|
||||||
msgmerge --backup=none -U $$lang.po pkg/i18n/locales/$(DOMAIN).pot; \
|
|
||||||
done
|
|
||||||
|
|
||||||
.PHONY: update-pot
|
|
||||||
update-pot: $(XGETTEXT)
|
|
||||||
@${XGETTEXT} \
|
|
||||||
-o pkg/i18n/locales/$(DOMAIN).pot \
|
|
||||||
--keyword=i18n.G \
|
|
||||||
--keyword-ctx=i18n.GC \
|
|
||||||
--sort-output \
|
|
||||||
--add-comments-tag="translators" \
|
|
||||||
$$(find . -name "*.go" -not -path "*vendor*" | sort)
|
|
||||||
|
|
||||||
${XGETTEXT}:
|
|
||||||
@mkdir -p ./bin && \
|
|
||||||
wget -O ./bin/xgettext-go https://git.coopcloud.tech/toolshed/xgettext-go/raw/branch/main/xgettext-go && \
|
|
||||||
chmod +x ./bin/xgettext-go
|
|
||||||
|
|
||||||
.PHONY: update-pot-po-metadata
|
|
||||||
update-pot-po-metadata:
|
|
||||||
@sed -i "s/charset=CHARSET/charset=UTF-8/g" pkg/i18n/locales/*.po pkg/i18n/locales/*.pot
|
|
||||||
|
|
||||||
.PHONY: build-mo
|
|
||||||
build-mo:
|
|
||||||
@set -eu; \
|
|
||||||
for lang in $(POFILES); do \
|
|
||||||
msgfmt $$lang -o $$(echo $$lang | sed 's/.po/.mo/g') --statistics; \
|
|
||||||
done
|
|
||||||
|
|||||||
@ -3,7 +3,6 @@
|
|||||||
[](https://build.coopcloud.tech/toolshed/abra)
|
[](https://build.coopcloud.tech/toolshed/abra)
|
||||||
[](https://goreportcard.com/report/git.coopcloud.tech/toolshed/abra)
|
[](https://goreportcard.com/report/git.coopcloud.tech/toolshed/abra)
|
||||||
[](https://pkg.go.dev/coopcloud.tech/abra)
|
[](https://pkg.go.dev/coopcloud.tech/abra)
|
||||||
[](https://translate.coopcloud.tech/engage/co-op-cloud/)
|
|
||||||
|
|
||||||
The Co-op Cloud utility belt 🎩🐇
|
The Co-op Cloud utility belt 🎩🐇
|
||||||
|
|
||||||
|
|||||||
@ -1,20 +1,11 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appAliases = i18n.GC("a", "abra app")
|
|
||||||
|
|
||||||
var AppCommand = &cobra.Command{
|
var AppCommand = &cobra.Command{
|
||||||
// translators: `app` command group
|
Use: "app [cmd] [args] [flags]",
|
||||||
Use: i18n.G("app [cmd] [args] [flags]"),
|
Aliases: []string{"a"},
|
||||||
Aliases: strings.Split(appAliases, ","),
|
Short: "Manage apps",
|
||||||
// translators: Short description for `app` command group
|
|
||||||
Short: i18n.G("Manage apps"),
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,26 +2,18 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app backup list` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appBackupListAliases = i18n.G("ls")
|
|
||||||
|
|
||||||
var AppBackupListCommand = &cobra.Command{
|
var AppBackupListCommand = &cobra.Command{
|
||||||
// translators: `app backup list` command
|
Use: "list <domain> [flags]",
|
||||||
Use: i18n.G("list <domain> [flags]"),
|
Aliases: []string{"ls"},
|
||||||
Aliases: strings.Split(appBackupListAliases, ","),
|
Short: "List the contents of a snapshot",
|
||||||
// translators: Short description for `app backup list` command
|
|
||||||
Short: i18n.G("List the contents of a snapshot"),
|
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -48,17 +40,17 @@ var AppBackupListCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if snapshot != "" {
|
if snapshot != "" {
|
||||||
log.Debug(i18n.G("including SNAPSHOT=%s in backupbot exec invocation", snapshot))
|
log.Debugf("including SNAPSHOT=%s in backupbot exec invocation", snapshot)
|
||||||
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
|
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
|
||||||
}
|
}
|
||||||
|
|
||||||
if showAllPaths {
|
if showAllPaths {
|
||||||
log.Debug(i18n.G("including SHOW_ALL=%v in backupbot exec invocation", showAllPaths))
|
log.Debugf("including SHOW_ALL=%v in backupbot exec invocation", showAllPaths)
|
||||||
execEnv = append(execEnv, fmt.Sprintf("SHOW_ALL=%v", showAllPaths))
|
execEnv = append(execEnv, fmt.Sprintf("SHOW_ALL=%v", showAllPaths))
|
||||||
}
|
}
|
||||||
|
|
||||||
if timestamps {
|
if timestamps {
|
||||||
log.Debug(i18n.G("including TIMESTAMPS=%v in backupbot exec invocation", timestamps))
|
log.Debugf("including TIMESTAMPS=%v in backupbot exec invocation", timestamps)
|
||||||
execEnv = append(execEnv, fmt.Sprintf("TIMESTAMPS=%v", timestamps))
|
execEnv = append(execEnv, fmt.Sprintf("TIMESTAMPS=%v", timestamps))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,20 +60,14 @@ var AppBackupListCommand = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// translators: `abra app backup download` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appBackupDownloadAliases = i18n.G("d")
|
|
||||||
|
|
||||||
var AppBackupDownloadCommand = &cobra.Command{
|
var AppBackupDownloadCommand = &cobra.Command{
|
||||||
// translators: `app backup download` command
|
Use: "download <domain> [flags]",
|
||||||
Use: i18n.G("download <domain> [flags]"),
|
Aliases: []string{"d"},
|
||||||
Aliases: strings.Split(appBackupDownloadAliases, ","),
|
Short: "Download a snapshot",
|
||||||
// translators: Short description for `app backup download` command
|
Long: `Downloads a backup.tar.gz to the current working directory.
|
||||||
Short: i18n.G("Download a snapshot"),
|
|
||||||
Long: i18n.G(`Downloads a backup.tar.gz to the current working directory.
|
|
||||||
|
|
||||||
"--volumes/-v" includes data contained in volumes alongide paths specified in
|
"--volumes/-v" includes data contained in volumes alongide paths specified in
|
||||||
"backupbot.backup.path" labels.`),
|
"backupbot.backup.path" labels.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -112,22 +98,22 @@ var AppBackupDownloadCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if snapshot != "" {
|
if snapshot != "" {
|
||||||
log.Debug(i18n.G("including SNAPSHOT=%s in backupbot exec invocation", snapshot))
|
log.Debugf("including SNAPSHOT=%s in backupbot exec invocation", snapshot)
|
||||||
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
|
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
|
||||||
}
|
}
|
||||||
|
|
||||||
if includePath != "" {
|
if includePath != "" {
|
||||||
log.Debug(i18n.G("including INCLUDE_PATH=%s in backupbot exec invocation", includePath))
|
log.Debugf("including INCLUDE_PATH=%s in backupbot exec invocation", includePath)
|
||||||
execEnv = append(execEnv, fmt.Sprintf("INCLUDE_PATH=%s", includePath))
|
execEnv = append(execEnv, fmt.Sprintf("INCLUDE_PATH=%s", includePath))
|
||||||
}
|
}
|
||||||
|
|
||||||
if includeSecrets {
|
if includeSecrets {
|
||||||
log.Debug(i18n.G("including SECRETS=%v in backupbot exec invocation", includeSecrets))
|
log.Debugf("including SECRETS=%v in backupbot exec invocation", includeSecrets)
|
||||||
execEnv = append(execEnv, fmt.Sprintf("SECRETS=%v", includeSecrets))
|
execEnv = append(execEnv, fmt.Sprintf("SECRETS=%v", includeSecrets))
|
||||||
}
|
}
|
||||||
|
|
||||||
if includeVolumes {
|
if includeVolumes {
|
||||||
log.Debug(i18n.G("including VOLUMES=%v in backupbot exec invocation", includeVolumes))
|
log.Debugf("including VOLUMES=%v in backupbot exec invocation", includeVolumes)
|
||||||
execEnv = append(execEnv, fmt.Sprintf("VOLUMES=%v", includeVolumes))
|
execEnv = append(execEnv, fmt.Sprintf("VOLUMES=%v", includeVolumes))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,16 +129,10 @@ var AppBackupDownloadCommand = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// translators: `abra app backup create` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appBackupCreateAliases = i18n.G("c")
|
|
||||||
|
|
||||||
var AppBackupCreateCommand = &cobra.Command{
|
var AppBackupCreateCommand = &cobra.Command{
|
||||||
// translators: `app backup create` command
|
Use: "create <domain> [flags]",
|
||||||
Use: i18n.G("create <domain> [flags]"),
|
Aliases: []string{"c"},
|
||||||
Aliases: strings.Split(appBackupCreateAliases, ","),
|
Short: "Create a new snapshot",
|
||||||
// translators: Short description for `app backup create` command
|
|
||||||
Short: i18n.G("Create a new snapshot"),
|
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -183,7 +163,7 @@ var AppBackupCreateCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if retries != "" {
|
if retries != "" {
|
||||||
log.Debug(i18n.G("including RETRIES=%s in backupbot exec invocation", retries))
|
log.Debugf("including RETRIES=%s in backupbot exec invocation", retries)
|
||||||
execEnv = append(execEnv, fmt.Sprintf("RETRIES=%s", retries))
|
execEnv = append(execEnv, fmt.Sprintf("RETRIES=%s", retries))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -193,16 +173,10 @@ var AppBackupCreateCommand = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// translators: `abra app backup snapshots` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appBackupSnapshotsAliases = i18n.G("s")
|
|
||||||
|
|
||||||
var AppBackupSnapshotsCommand = &cobra.Command{
|
var AppBackupSnapshotsCommand = &cobra.Command{
|
||||||
// translators: `app backup snapshots` command
|
Use: "snapshots <domain> [flags]",
|
||||||
Use: i18n.G("snapshots <domain> [flags]"),
|
Aliases: []string{"s"},
|
||||||
Aliases: strings.Split(appBackupSnapshotsAliases, ","),
|
Short: "List all snapshots",
|
||||||
// translators: Short description for `app backup snapshots` command
|
|
||||||
Short: i18n.G("List all snapshots"),
|
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -234,16 +208,10 @@ var AppBackupSnapshotsCommand = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// translators: `abra app backup` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appBackupAliases = i18n.G("b")
|
|
||||||
|
|
||||||
var AppBackupCommand = &cobra.Command{
|
var AppBackupCommand = &cobra.Command{
|
||||||
// translators: `app backup` command group
|
Use: "backup [cmd] [args] [flags]",
|
||||||
Use: i18n.G("backup [cmd] [args] [flags]"),
|
Aliases: []string{"b"},
|
||||||
Aliases: strings.Split(appBackupAliases, ","),
|
Short: "Manage app backups",
|
||||||
// translators: Short description for `app backup` command group
|
|
||||||
Short: i18n.G("Manage app backups"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -259,81 +227,81 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
AppBackupListCommand.Flags().StringVarP(
|
AppBackupListCommand.Flags().StringVarP(
|
||||||
&snapshot,
|
&snapshot,
|
||||||
i18n.G("snapshot"),
|
"snapshot",
|
||||||
i18n.G("s"),
|
"s",
|
||||||
"",
|
"",
|
||||||
i18n.G("list specific snapshot"),
|
"list specific snapshot",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppBackupListCommand.Flags().BoolVarP(
|
AppBackupListCommand.Flags().BoolVarP(
|
||||||
&showAllPaths,
|
&showAllPaths,
|
||||||
i18n.G("all"),
|
"all",
|
||||||
i18n.GC("a", "app backup list"),
|
"a",
|
||||||
false,
|
false,
|
||||||
i18n.G("show all paths"),
|
"show all paths",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppBackupListCommand.Flags().BoolVarP(
|
AppBackupListCommand.Flags().BoolVarP(
|
||||||
×tamps,
|
×tamps,
|
||||||
i18n.G("timestamps"),
|
"timestamps",
|
||||||
i18n.G("t"),
|
"t",
|
||||||
false,
|
false,
|
||||||
i18n.G("include timestamps"),
|
"include timestamps",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppBackupDownloadCommand.Flags().StringVarP(
|
AppBackupDownloadCommand.Flags().StringVarP(
|
||||||
&snapshot,
|
&snapshot,
|
||||||
i18n.G("snapshot"),
|
"snapshot",
|
||||||
i18n.G("s"),
|
"s",
|
||||||
"",
|
"",
|
||||||
i18n.G("list specific snapshot"),
|
"list specific snapshot",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppBackupDownloadCommand.Flags().StringVarP(
|
AppBackupDownloadCommand.Flags().StringVarP(
|
||||||
&includePath,
|
&includePath,
|
||||||
i18n.G("path"),
|
"path",
|
||||||
i18n.G("p"),
|
"p",
|
||||||
"",
|
"",
|
||||||
i18n.G("volumes path"),
|
"volumes path",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppBackupDownloadCommand.Flags().BoolVarP(
|
AppBackupDownloadCommand.Flags().BoolVarP(
|
||||||
&includeSecrets,
|
&includeSecrets,
|
||||||
i18n.G("secrets"),
|
"secrets",
|
||||||
i18n.G("S"),
|
"S",
|
||||||
false,
|
false,
|
||||||
i18n.G("include secrets"),
|
"include secrets",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppBackupDownloadCommand.Flags().BoolVarP(
|
AppBackupDownloadCommand.Flags().BoolVarP(
|
||||||
&includeVolumes,
|
&includeVolumes,
|
||||||
i18n.G("volumes"),
|
"volumes",
|
||||||
i18n.G("v"),
|
"v",
|
||||||
false,
|
false,
|
||||||
i18n.G("include volumes"),
|
"include volumes",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppBackupDownloadCommand.Flags().BoolVarP(
|
AppBackupDownloadCommand.Flags().BoolVarP(
|
||||||
&internal.Chaos,
|
&internal.Chaos,
|
||||||
i18n.G("chaos"),
|
"chaos",
|
||||||
i18n.G("C"),
|
"C",
|
||||||
false,
|
false,
|
||||||
i18n.G("ignore uncommitted recipes changes"),
|
"ignore uncommitted recipes changes",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppBackupCreateCommand.Flags().StringVarP(
|
AppBackupCreateCommand.Flags().StringVarP(
|
||||||
&retries,
|
&retries,
|
||||||
i18n.G("retries"),
|
"retries",
|
||||||
i18n.G("r"),
|
"r",
|
||||||
"1",
|
"1",
|
||||||
i18n.G("number of retry attempts"),
|
"number of retry attempts",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppBackupCreateCommand.Flags().BoolVarP(
|
AppBackupCreateCommand.Flags().BoolVarP(
|
||||||
&internal.Chaos,
|
&internal.Chaos,
|
||||||
i18n.G("chaos"),
|
"chaos",
|
||||||
i18n.G("C"),
|
"C",
|
||||||
false,
|
false,
|
||||||
i18n.G("ignore uncommitted recipes changes"),
|
"ignore uncommitted recipes changes",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,29 +2,21 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"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/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app check` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appCheckAliases = i18n.G("chk")
|
|
||||||
|
|
||||||
var AppCheckCommand = &cobra.Command{
|
var AppCheckCommand = &cobra.Command{
|
||||||
// translators: `app check` command
|
Use: "check <domain> [flags]",
|
||||||
Use: i18n.G("check <domain> [flags]"),
|
Aliases: []string{"chk"},
|
||||||
Aliases: strings.Split(appCheckAliases, ","),
|
Short: "Ensure an app is well configured",
|
||||||
// translators: Short description for `app check` command
|
Long: `Compare env vars in both the app ".env" and recipe ".env.sample" file.
|
||||||
Short: i18n.G("Ensure an app is well configured"),
|
|
||||||
Long: i18n.G(`Compare env vars in both the app ".env" and recipe ".env.sample" file.
|
|
||||||
|
|
||||||
The goal is to ensure that recipe ".env.sample" env vars are defined in your
|
The goal is to ensure that recipe ".env.sample" env vars are defined in your
|
||||||
app ".env" file. Only env var definitions in the ".env.sample" which are
|
app ".env" file. Only env var definitions in the ".env.sample" which are
|
||||||
@ -33,7 +25,7 @@ these env vars, then "check" will complain.
|
|||||||
|
|
||||||
Recipe maintainers may or may not provide defaults for env vars within their
|
Recipe maintainers may or may not provide defaults for env vars within their
|
||||||
recipes regardless of commenting or not (e.g. through the use of
|
recipes regardless of commenting or not (e.g. through the use of
|
||||||
${FOO:<default>} syntax). "check" does not confirm or deny this for you.`),
|
${FOO:<default>} syntax). "check" does not confirm or deny this for you.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -91,9 +83,9 @@ ${FOO:<default>} syntax). "check" does not confirm or deny this for you.`),
|
|||||||
func init() {
|
func init() {
|
||||||
AppCheckCommand.Flags().BoolVarP(
|
AppCheckCommand.Flags().BoolVarP(
|
||||||
&internal.Chaos,
|
&internal.Chaos,
|
||||||
i18n.G("chaos"),
|
"chaos",
|
||||||
i18n.G("C"),
|
"C",
|
||||||
false,
|
false,
|
||||||
i18n.G("ignore uncommitted recipes changes"),
|
"ignore uncommitted recipes changes",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,22 +13,15 @@ import (
|
|||||||
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/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app cmd` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appCmdAliases = i18n.G("cmd")
|
|
||||||
|
|
||||||
var AppCmdCommand = &cobra.Command{
|
var AppCmdCommand = &cobra.Command{
|
||||||
// translators: `app command` command
|
Use: "command <domain> [service | --local] <cmd> [[args] [flags] | [flags] -- [args]]",
|
||||||
Use: i18n.G("command <domain> [service | --local] <cmd> [[args] [flags] | [flags] -- [args]]"),
|
Aliases: []string{"cmd"},
|
||||||
Aliases: strings.Split(appCmdAliases, ","),
|
Short: "Run app commands",
|
||||||
// translators: Short description for `app cmd` command
|
Long: `Run an app specific command.
|
||||||
Short: i18n.G("Run app commands"),
|
|
||||||
Long: i18n.G(`Run an app specific command.
|
|
||||||
|
|
||||||
These commands are bash functions, defined in the abra.sh of the recipe itself.
|
These commands are bash functions, defined in the abra.sh of the recipe itself.
|
||||||
They can be run within the context of a service (e.g. app) or locally on your
|
They can be run within the context of a service (e.g. app) or locally on your
|
||||||
@ -37,24 +30,24 @@ work station by passing "--local/-l".
|
|||||||
N.B. If using the "--" style to pass arguments, flags (e.g. "--local/-l") must
|
N.B. If using the "--" style to pass arguments, flags (e.g. "--local/-l") must
|
||||||
be passed *before* the "--". It is possible to pass arguments without the "--"
|
be passed *before* the "--". It is possible to pass arguments without the "--"
|
||||||
as long as no dashes are present (i.e. "foo" works without "--", "-foo"
|
as long as no dashes are present (i.e. "foo" works without "--", "-foo"
|
||||||
does not).`),
|
does not).`,
|
||||||
Example: i18n.G(` # pass <cmd> args/flags without "--"
|
Example: ` # pass <cmd> args/flags without "--"
|
||||||
abra app cmd 1312.net app my_cmd_arg foo --user bar
|
abra app cmd 1312.net app my_cmd_arg foo --user bar
|
||||||
|
|
||||||
# pass <cmd> args/flags with "--"
|
# pass <cmd> args/flags with "--"
|
||||||
abra app cmd 1312.net app my_cmd_args --user bar -- foo -vvv
|
abra app cmd 1312.net app my_cmd_args --user bar -- foo -vvv
|
||||||
|
|
||||||
# drop the [service] arg if using "--local/-l"
|
# drop the [service] arg if using "--local/-l"
|
||||||
abra app cmd 1312.net my_cmd --local`),
|
abra app cmd 1312.net my_cmd --local`,
|
||||||
Args: func(cmd *cobra.Command, args []string) error {
|
Args: func(cmd *cobra.Command, args []string) error {
|
||||||
if local {
|
if local {
|
||||||
if !(len(args) >= 2) {
|
if !(len(args) >= 2) {
|
||||||
return errors.New(i18n.G("requires at least 2 arguments with --local/-l"))
|
return errors.New("requires at least 2 arguments with --local/-l")
|
||||||
}
|
}
|
||||||
|
|
||||||
if slices.Contains(os.Args, "--") {
|
if slices.Contains(os.Args, "--") {
|
||||||
if cmd.ArgsLenAtDash() > 2 {
|
if cmd.ArgsLenAtDash() > 2 {
|
||||||
return errors.New(i18n.G("accepts at most 2 args with --local/-l"))
|
return errors.New("accepts at most 2 args with --local/-l")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,7 +63,7 @@ does not).`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !(len(args) >= 3) {
|
if !(len(args) >= 3) {
|
||||||
return errors.New(i18n.G("requires at least 3 arguments"))
|
return errors.New("requires at least 3 arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -104,14 +97,14 @@ does not).`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
if local && remoteUser != "" {
|
if local && remoteUser != "" {
|
||||||
log.Fatal(i18n.G("cannot use --local & --user together"))
|
log.Fatal("cannot use --local & --user together")
|
||||||
}
|
}
|
||||||
|
|
||||||
hasCmdArgs, parsedCmdArgs := parseCmdArgs(args, local)
|
hasCmdArgs, parsedCmdArgs := parseCmdArgs(args, local)
|
||||||
|
|
||||||
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) {
|
||||||
log.Fatal(i18n.G("%s does not exist for %s?", app.Recipe.AbraShPath, app.Name))
|
log.Fatalf("%s does not exist for %s?", app.Recipe.AbraShPath, app.Name)
|
||||||
}
|
}
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -122,7 +115,7 @@ does not).`),
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("--local detected, running %s on local work station", cmdName))
|
log.Debugf("--local detected, running %s on local work station", cmdName)
|
||||||
|
|
||||||
var exportEnv string
|
var exportEnv string
|
||||||
for k, v := range app.Env {
|
for k, v := range app.Env {
|
||||||
@ -131,16 +124,16 @@ does not).`),
|
|||||||
|
|
||||||
var sourceAndExec string
|
var sourceAndExec string
|
||||||
if hasCmdArgs {
|
if hasCmdArgs {
|
||||||
log.Debug(i18n.G("parsed following command arguments: %s", parsedCmdArgs))
|
log.Debugf("parsed following command arguments: %s", parsedCmdArgs)
|
||||||
sourceAndExec = fmt.Sprintf("TARGET=local; APP_NAME=%s; STACK_NAME=%s; %s . %s; %s %s", app.Name, app.StackName(), exportEnv, app.Recipe.AbraShPath, cmdName, parsedCmdArgs)
|
sourceAndExec = fmt.Sprintf("TARGET=local; APP_NAME=%s; STACK_NAME=%s; %s . %s; %s %s", app.Name, app.StackName(), exportEnv, app.Recipe.AbraShPath, cmdName, parsedCmdArgs)
|
||||||
} else {
|
} else {
|
||||||
log.Debug(i18n.G("did not detect any command arguments"))
|
log.Debug("did not detect any command arguments")
|
||||||
sourceAndExec = fmt.Sprintf("TARGET=local; APP_NAME=%s; STACK_NAME=%s; %s . %s; %s", app.Name, app.StackName(), exportEnv, app.Recipe.AbraShPath, cmdName)
|
sourceAndExec = fmt.Sprintf("TARGET=local; APP_NAME=%s; STACK_NAME=%s; %s . %s; %s", app.Name, app.StackName(), exportEnv, app.Recipe.AbraShPath, cmdName)
|
||||||
}
|
}
|
||||||
|
|
||||||
shell := "/bin/bash"
|
shell := "/bin/bash"
|
||||||
if _, err := os.Stat(shell); errors.Is(err, os.ErrNotExist) {
|
if _, err := os.Stat(shell); errors.Is(err, os.ErrNotExist) {
|
||||||
log.Debug(i18n.G("%s does not exist locally, use /bin/sh as fallback", shell))
|
log.Debugf("%s does not exist locally, use /bin/sh as fallback", shell)
|
||||||
shell = "/bin/sh"
|
shell = "/bin/sh"
|
||||||
}
|
}
|
||||||
cmd := exec.Command(shell, "-c", sourceAndExec)
|
cmd := exec.Command(shell, "-c", sourceAndExec)
|
||||||
@ -171,15 +164,15 @@ does not).`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !matchingServiceName {
|
if !matchingServiceName {
|
||||||
log.Fatal(i18n.G("no service %s for %s?", targetServiceName, app.Name))
|
log.Fatalf("no service %s for %s?", targetServiceName, app.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("running command %s within the context of %s_%s", cmdName, app.StackName(), targetServiceName))
|
log.Debugf("running command %s within the context of %s_%s", cmdName, app.StackName(), targetServiceName)
|
||||||
|
|
||||||
if hasCmdArgs {
|
if hasCmdArgs {
|
||||||
log.Debug(i18n.G("parsed following command arguments: %s", parsedCmdArgs))
|
log.Debugf("parsed following command arguments: %s", parsedCmdArgs)
|
||||||
} else {
|
} else {
|
||||||
log.Debug(i18n.G("did not detect any command arguments"))
|
log.Debug("did not detect any command arguments")
|
||||||
}
|
}
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
cl, err := client.New(app.Server)
|
||||||
@ -198,16 +191,10 @@ does not).`),
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// translators: `abra app command list` aliases. use a comma separated list of
|
|
||||||
// aliases with no spaces in between
|
|
||||||
var appCmdListAliases = i18n.G("ls")
|
|
||||||
|
|
||||||
var AppCmdListCommand = &cobra.Command{
|
var AppCmdListCommand = &cobra.Command{
|
||||||
// translators: `app cmd list` command
|
Use: "list <domain> [flags]",
|
||||||
Use: i18n.G("list <domain> [flags]"),
|
Aliases: []string{"ls"},
|
||||||
Aliases: strings.Split(appCmdListAliases, ","),
|
Short: "List all available commands",
|
||||||
// translators: Short description for `app cmd list` command
|
|
||||||
Short: i18n.G("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)
|
||||||
@ -257,33 +244,33 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
AppCmdCommand.Flags().BoolVarP(
|
AppCmdCommand.Flags().BoolVarP(
|
||||||
&local,
|
&local,
|
||||||
i18n.G("local"),
|
"local",
|
||||||
i18n.G("l"),
|
"l",
|
||||||
false,
|
false,
|
||||||
i18n.G("run command locally"),
|
"run command locally",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppCmdCommand.Flags().StringVarP(
|
AppCmdCommand.Flags().StringVarP(
|
||||||
&remoteUser,
|
&remoteUser,
|
||||||
i18n.G("user"),
|
"user",
|
||||||
i18n.G("u"),
|
"u",
|
||||||
"",
|
"",
|
||||||
i18n.G("request remote user"),
|
"request remote user",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppCmdCommand.Flags().BoolVarP(
|
AppCmdCommand.Flags().BoolVarP(
|
||||||
&disableTTY,
|
&disableTTY,
|
||||||
i18n.G("tty"),
|
"tty",
|
||||||
i18n.G("T"),
|
"T",
|
||||||
false,
|
false,
|
||||||
i18n.G("disable remote TTY"),
|
"disable remote TTY",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppCmdCommand.Flags().BoolVarP(
|
AppCmdCommand.Flags().BoolVarP(
|
||||||
&internal.Chaos,
|
&internal.Chaos,
|
||||||
i18n.G("chaos"),
|
"chaos",
|
||||||
i18n.G("C"),
|
"C",
|
||||||
false,
|
false,
|
||||||
i18n.G("ignore uncommitted recipes changes"),
|
"ignore uncommitted recipes changes",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,27 +3,19 @@ package app
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
|
||||||
|
|
||||||
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/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app config` aliases. use a comma separated list of
|
|
||||||
// aliases with no spaces in between
|
|
||||||
var appConfigAliases = i18n.G("cfg")
|
|
||||||
|
|
||||||
var AppConfigCommand = &cobra.Command{
|
var AppConfigCommand = &cobra.Command{
|
||||||
// translators: `app config` command
|
Use: "config <domain> [flags]",
|
||||||
Use: i18n.G("config <domain> [flags]"),
|
Aliases: []string{"cfg"},
|
||||||
Aliases: strings.Split(appConfigAliases, ","),
|
Short: "Edit app config",
|
||||||
// translators: Short description for `app config` command
|
Example: " abra config 1312.net",
|
||||||
Short: i18n.G("Edit app config"),
|
|
||||||
Example: i18n.G(" abra config 1312.net"),
|
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -40,13 +32,13 @@ var AppConfigCommand = &cobra.Command{
|
|||||||
appName := args[0]
|
appName := args[0]
|
||||||
appFile, exists := files[appName]
|
appFile, exists := files[appName]
|
||||||
if !exists {
|
if !exists {
|
||||||
log.Fatal(i18n.G("cannot find app with name %s", appName))
|
log.Fatalf("cannot find app with name %s", appName)
|
||||||
}
|
}
|
||||||
|
|
||||||
ed, ok := os.LookupEnv("EDITOR")
|
ed, ok := os.LookupEnv("EDITOR")
|
||||||
if !ok {
|
if !ok {
|
||||||
edPrompt := &survey.Select{
|
edPrompt := &survey.Select{
|
||||||
Message: i18n.G("which editor do you wish to use?"),
|
Message: "which editor do you wish to use?",
|
||||||
Options: []string{"vi", "vim", "nvim", "nano", "pico", "emacs"},
|
Options: []string{"vi", "vim", "nvim", "nano", "pico", "emacs"},
|
||||||
}
|
}
|
||||||
if err := survey.AskOne(edPrompt, &ed); err != nil {
|
if err := survey.AskOne(edPrompt, &ed); err != nil {
|
||||||
|
|||||||
@ -3,6 +3,7 @@ package app
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@ -14,7 +15,6 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
containerPkg "coopcloud.tech/abra/pkg/container"
|
containerPkg "coopcloud.tech/abra/pkg/container"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"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"
|
||||||
@ -25,21 +25,15 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app cp` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appCpAliases = i18n.G("c")
|
|
||||||
|
|
||||||
var AppCpCommand = &cobra.Command{
|
var AppCpCommand = &cobra.Command{
|
||||||
// translators: `app cp` command
|
Use: "cp <domain> <src> <dst> [flags]",
|
||||||
Use: i18n.G("cp <domain> <src> <dst> [flags]"),
|
Aliases: []string{"c"},
|
||||||
Aliases: strings.Split(appCpAliases, ","),
|
Short: "Copy files to/from a deployed app service",
|
||||||
// translators: Short description for `app cp` command
|
Example: ` # copy myfile.txt to the root of the app service
|
||||||
Short: i18n.G("Copy files to/from a deployed app service"),
|
|
||||||
Example: i18n.G(` # 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,
|
||||||
@ -75,7 +69,7 @@ var AppCpCommand = &cobra.Command{
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
log.Debug(i18n.G("retrieved %s as target container on %s", formatter.ShortenID(container.ID), app.Server))
|
log.Debugf("retrieved %s as target container on %s", formatter.ShortenID(container.ID), app.Server)
|
||||||
|
|
||||||
if toContainer {
|
if toContainer {
|
||||||
err = CopyToContainer(cl, container.ID, srcPath, dstPath)
|
err = CopyToContainer(cl, container.ID, srcPath, dstPath)
|
||||||
@ -88,7 +82,7 @@ var AppCpCommand = &cobra.Command{
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var errServiceMissing = errors.New(i18n.G("one of <src>/<dest> arguments must take $SERVICE:$PATH form"))
|
var errServiceMissing = errors.New("one of <src>/<dest> arguments must take $SERVICE:$PATH form")
|
||||||
|
|
||||||
// parseSrcAndDst parses src and dest string. One of src or dst must be of the form $SERVICE:$PATH
|
// parseSrcAndDst parses src and dest string. One of src or dst must be of the form $SERVICE:$PATH
|
||||||
func parseSrcAndDst(src, dst string) (srcPath string, dstPath string, service string, toContainer bool, err error) {
|
func parseSrcAndDst(src, dst string) (srcPath string, dstPath string, service string, toContainer bool, err error) {
|
||||||
@ -111,7 +105,7 @@ func parseSrcAndDst(src, dst string) (srcPath string, dstPath string, service st
|
|||||||
func CopyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath string) error {
|
func CopyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath string) error {
|
||||||
srcStat, err := os.Stat(srcPath)
|
srcStat, err := os.Stat(srcPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(i18n.G("local %s ", err))
|
return fmt.Errorf("local %s ", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
dstStat, err := cl.ContainerStatPath(context.Background(), containerID, dstPath)
|
dstStat, err := cl.ContainerStatPath(context.Background(), containerID, dstPath)
|
||||||
@ -120,7 +114,7 @@ func CopyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath stri
|
|||||||
if errdefs.IsNotFound(err) {
|
if errdefs.IsNotFound(err) {
|
||||||
dstExists = false
|
dstExists = false
|
||||||
} else {
|
} else {
|
||||||
return errors.New(i18n.G("remote path: %s", err))
|
return fmt.Errorf("remote path: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,7 +142,7 @@ func CopyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath stri
|
|||||||
Detach: false,
|
Detach: false,
|
||||||
Tty: true,
|
Tty: true,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return errors.New(i18n.G("create remote directory: %s", err))
|
return fmt.Errorf("create remote directory: %s", err)
|
||||||
}
|
}
|
||||||
case CopyModeFileToFile:
|
case CopyModeFileToFile:
|
||||||
// Remove the file component from the path, since docker can only copy
|
// Remove the file component from the path, since docker can only copy
|
||||||
@ -167,7 +161,7 @@ func CopyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath stri
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("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 := containertypes.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
|
||||||
@ -187,7 +181,7 @@ func CopyToContainer(cl *dockerClient.Client, containerID, srcPath, dstPath stri
|
|||||||
Detach: false,
|
Detach: false,
|
||||||
Tty: true,
|
Tty: true,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return errors.New(i18n.G("create remote directory: %s", err))
|
return fmt.Errorf("create remote directory: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,9 +194,9 @@ func CopyFromContainer(cl *dockerClient.Client, containerID, srcPath, dstPath st
|
|||||||
srcStat, err := cl.ContainerStatPath(context.Background(), containerID, srcPath)
|
srcStat, err := cl.ContainerStatPath(context.Background(), containerID, srcPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errdefs.IsNotFound(err) {
|
if errdefs.IsNotFound(err) {
|
||||||
return errors.New(i18n.G("remote: %s does not exist", srcPath))
|
return fmt.Errorf("remote: %s does not exist", srcPath)
|
||||||
} else {
|
} else {
|
||||||
return errors.New(i18n.G("remote path: %s", err))
|
return fmt.Errorf("remote path: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -213,7 +207,7 @@ func CopyFromContainer(cl *dockerClient.Client, containerID, srcPath, dstPath st
|
|||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
dstExists = false
|
dstExists = false
|
||||||
} else {
|
} else {
|
||||||
return errors.New(i18n.G("remote path: %s", err))
|
return fmt.Errorf("remote path: %s", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
dstMode = dstStat.Mode()
|
dstMode = dstStat.Mode()
|
||||||
@ -248,7 +242,7 @@ func CopyFromContainer(cl *dockerClient.Client, containerID, srcPath, dstPath st
|
|||||||
|
|
||||||
content, _, err := cl.CopyFromContainer(context.Background(), containerID, srcPath)
|
content, _, err := cl.CopyFromContainer(context.Background(), containerID, srcPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(i18n.G("copy: %s", err))
|
return fmt.Errorf("copy: %s", err)
|
||||||
}
|
}
|
||||||
defer content.Close()
|
defer content.Close()
|
||||||
if err := archive.Untar(content, dstPath, &archive.TarOptions{
|
if err := archive.Untar(content, dstPath, &archive.TarOptions{
|
||||||
@ -256,7 +250,7 @@ func CopyFromContainer(cl *dockerClient.Client, containerID, srcPath, dstPath st
|
|||||||
Compression: archive.Gzip,
|
Compression: archive.Gzip,
|
||||||
NoLchown: true,
|
NoLchown: true,
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
return errors.New(i18n.G("untar: %s", err))
|
return fmt.Errorf("untar: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if moveDstFile != "" {
|
if moveDstFile != "" {
|
||||||
@ -275,8 +269,8 @@ func CopyFromContainer(cl *dockerClient.Client, containerID, srcPath, dstPath st
|
|||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrCopyDirToFile = errors.New(i18n.G("can't copy dir to file"))
|
ErrCopyDirToFile = fmt.Errorf("can't copy dir to file")
|
||||||
ErrDstDirNotExist = errors.New(i18n.G("destination directory does not exist"))
|
ErrDstDirNotExist = fmt.Errorf("destination directory does not exist")
|
||||||
)
|
)
|
||||||
|
|
||||||
type CopyMode int
|
type CopyMode int
|
||||||
@ -381,9 +375,9 @@ func moveFile(sourcePath, destPath string) error {
|
|||||||
func init() {
|
func init() {
|
||||||
AppCpCommand.Flags().BoolVarP(
|
AppCpCommand.Flags().BoolVarP(
|
||||||
&internal.Chaos,
|
&internal.Chaos,
|
||||||
i18n.G("chaos"),
|
"chaos",
|
||||||
i18n.G("C"),
|
"C",
|
||||||
false,
|
false,
|
||||||
i18n.G("ignore uncommitted recipes changes"),
|
"ignore uncommitted recipes changes",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,21 +2,20 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"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/secret"
|
"coopcloud.tech/abra/pkg/secret"
|
||||||
|
|
||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/deploy"
|
|
||||||
"coopcloud.tech/abra/pkg/dns"
|
"coopcloud.tech/abra/pkg/dns"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"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"
|
||||||
@ -24,22 +23,16 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app deploy` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appDeployAliases = i18n.G("d")
|
|
||||||
|
|
||||||
var AppDeployCommand = &cobra.Command{
|
var AppDeployCommand = &cobra.Command{
|
||||||
// translators: `app deploy` command
|
Use: "deploy <domain> [version] [flags]",
|
||||||
Use: i18n.G("deploy <domain> [version] [flags]"),
|
Aliases: []string{"d"},
|
||||||
Aliases: strings.Split(appDeployAliases, ","),
|
Short: "Deploy an app",
|
||||||
// translators: Short description for `app deploy` command
|
Long: `Deploy an app.
|
||||||
Short: i18n.G("Deploy an app"),
|
|
||||||
Long: i18n.G(`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 as values for
|
||||||
"[version]". Please note, "upgrade"/"rollback" do not support chaos operations.`),
|
"[version]". Please note, "upgrade"/"rollback" do not support chaos operations.`,
|
||||||
Example: i18n.G(` # standard deployment
|
Example: ` # standard deployment
|
||||||
abra app deploy 1312.net
|
abra app deploy 1312.net
|
||||||
|
|
||||||
# chaos deployment
|
# chaos deployment
|
||||||
@ -49,7 +42,7 @@ checkout as-is. Recipe commit hashes are also supported as values for
|
|||||||
abra app deploy 1312.net 2.0.0+1.2.3
|
abra app deploy 1312.net 2.0.0+1.2.3
|
||||||
|
|
||||||
# deploy a specific git hash
|
# deploy a specific git hash
|
||||||
abra app deploy 1312.net 886db76d`),
|
abra app deploy 1312.net 886db76d`,
|
||||||
Args: cobra.RangeArgs(1, 2),
|
Args: cobra.RangeArgs(1, 2),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -62,7 +55,7 @@ checkout as-is. Recipe commit hashes are also supported as values for
|
|||||||
case 1:
|
case 1:
|
||||||
app, err := appPkg.Get(args[0])
|
app, err := appPkg.Get(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errMsg := i18n.G("autocomplete failed: %s", err)
|
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
return []string{errMsg}, cobra.ShellCompDirectiveError
|
return []string{errMsg}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
return autocomplete.RecipeVersionComplete(app.Recipe.Name)
|
return autocomplete.RecipeVersionComplete(app.Recipe.Name)
|
||||||
@ -91,7 +84,7 @@ checkout as-is. Recipe commit hashes are also supported as values for
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("checking whether %s is already deployed", app.StackName()))
|
log.Debugf("checking whether %s is already deployed", app.StackName())
|
||||||
|
|
||||||
deployMeta, err := stack.IsDeployed(context.Background(), cl, app.StackName())
|
deployMeta, err := stack.IsDeployed(context.Background(), cl, app.StackName())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -99,40 +92,36 @@ checkout as-is. Recipe commit hashes are also supported as values for
|
|||||||
}
|
}
|
||||||
|
|
||||||
if deployMeta.IsDeployed && !(internal.Force || internal.Chaos) {
|
if deployMeta.IsDeployed && !(internal.Force || internal.Chaos) {
|
||||||
log.Fatal(i18n.G("%s is already deployed", app.Name))
|
log.Fatalf("%s is already deployed", app.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
toDeployVersion, err = getDeployVersion(args, deployMeta, app)
|
toDeployVersion, err = getDeployVersion(args, deployMeta, app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(fmt.Errorf("get deploy version: %s", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if !internal.Chaos {
|
if !internal.Chaos {
|
||||||
isChaosCommit, err := app.Recipe.EnsureVersion(toDeployVersion)
|
_, err = app.Recipe.EnsureVersion(toDeployVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(i18n.G("ensure recipe: %s", err))
|
log.Fatalf("ensure recipe: %s", err)
|
||||||
}
|
|
||||||
if isChaosCommit {
|
|
||||||
log.Warnf(i18n.G("version '%s' appears to be a chaos commit, but --chaos/-C was not provided", toDeployVersion))
|
|
||||||
internal.Chaos = true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := lint.LintForErrors(app.Recipe); err != nil {
|
if err := lint.LintForErrors(app.Recipe); err != nil {
|
||||||
if internal.Chaos {
|
|
||||||
log.Warn(err)
|
|
||||||
} else {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if err := validateSecrets(cl, app); err != nil {
|
if err := validateSecrets(cl, app); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deploy.MergeAbraShEnv(app.Recipe, app.Env); err != nil {
|
abraShEnv, err := envfile.ReadAbraShEnvVars(app.Recipe.AbraShPath)
|
||||||
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
for k, v := range abraShEnv {
|
||||||
|
app.Env[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -158,18 +147,8 @@ checkout as-is. Recipe commit hashes are also supported as values for
|
|||||||
if internal.Chaos {
|
if internal.Chaos {
|
||||||
appPkg.SetChaosVersionLabel(compose, stackName, toDeployVersion)
|
appPkg.SetChaosVersionLabel(compose, stackName, toDeployVersion)
|
||||||
}
|
}
|
||||||
|
appPkg.SetUpdateLabel(compose, stackName, app.Env)
|
||||||
versionLabel := toDeployVersion
|
appPkg.SetVersionLabel(compose, stackName, toDeployVersion)
|
||||||
if internal.Chaos {
|
|
||||||
for _, service := range compose.Services {
|
|
||||||
if service.Name == "app" {
|
|
||||||
labelKey := fmt.Sprintf("coop-cloud.%s.version", stackName)
|
|
||||||
// NOTE(d1): keep non-chaos version labbeling when doing chaos ops
|
|
||||||
versionLabel = service.Deploy.Labels[labelKey]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
appPkg.SetVersionLabel(compose, stackName, versionLabel)
|
|
||||||
|
|
||||||
envVars, err := appPkg.CheckEnv(app)
|
envVars, err := appPkg.CheckEnv(app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -179,7 +158,7 @@ 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,
|
deployWarnMessages = append(deployWarnMessages,
|
||||||
i18n.G("%s missing from %s.env", envVar.Name, app.Domain),
|
fmt.Sprintf("%s missing from %s.env", envVar.Name, app.Domain),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -190,48 +169,23 @@ checkout as-is. Recipe commit hashes are also supported as values for
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Debug(i18n.G("skipping domain checks, no DOMAIN=... configured"))
|
log.Debug("skipping domain checks, no DOMAIN=... configured")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Debug(i18n.G("skipping domain checks"))
|
log.Debug("skipping domain checks")
|
||||||
}
|
}
|
||||||
|
|
||||||
deployedVersion := config.MISSING_DEFAULT
|
deployedVersion := config.NO_VERSION_DEFAULT
|
||||||
if deployMeta.IsDeployed {
|
if deployMeta.IsDeployed {
|
||||||
deployedVersion = deployMeta.Version
|
deployedVersion = deployMeta.Version
|
||||||
if deployMeta.IsChaos {
|
|
||||||
deployedVersion = deployMeta.ChaosVersion
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather secrets
|
|
||||||
secretInfo, err := deploy.GatherSecretsForDeploy(cl, app, internal.ShowUnchanged)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gather configs
|
|
||||||
configInfo, err := deploy.GatherConfigsForDeploy(cl, app, compose, app.Env, internal.ShowUnchanged)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gather images
|
|
||||||
imageInfo, err := deploy.GatherImagesForDeploy(cl, app, compose, internal.ShowUnchanged)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show deploy overview
|
|
||||||
if err := internal.DeployOverview(
|
if err := internal.DeployOverview(
|
||||||
app,
|
app,
|
||||||
deployedVersion,
|
deployedVersion,
|
||||||
toDeployVersion,
|
toDeployVersion,
|
||||||
"",
|
"",
|
||||||
deployWarnMessages,
|
deployWarnMessages,
|
||||||
secretInfo,
|
|
||||||
configInfo,
|
|
||||||
imageInfo,
|
|
||||||
); err != nil {
|
); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -241,6 +195,8 @@ checkout as-is. Recipe commit hashes are also supported as values for
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debugf("set waiting timeout to %d second(s)", stack.WaitTimeout)
|
||||||
|
|
||||||
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
|
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -265,19 +221,19 @@ checkout as-is. Recipe commit hashes are also supported as values for
|
|||||||
|
|
||||||
postDeployCmds, ok := app.Env["POST_DEPLOY_CMDS"]
|
postDeployCmds, ok := app.Env["POST_DEPLOY_CMDS"]
|
||||||
if ok && !internal.DontWaitConverge {
|
if ok && !internal.DontWaitConverge {
|
||||||
log.Debug(i18n.G("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.Fatal(i18n.G("attempting to run post deploy commands, saw: %s", err))
|
log.Fatalf("attempting to run post deploy commands, saw: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := app.WriteRecipeVersion(toDeployVersion, false); err != nil {
|
if err := app.WriteRecipeVersion(toDeployVersion, false); err != nil {
|
||||||
log.Fatal(i18n.G("writing recipe version failed: %s", err))
|
log.Fatalf("writing recipe version failed: %s", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func getLatestVersionOrCommit(app appPkg.App) (string, error) {
|
func getLatestVersionOrCommit(app app.App) (string, error) {
|
||||||
versions, err := app.Recipe.Tags()
|
versions, err := app.Recipe.Tags()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
@ -298,31 +254,13 @@ func getLatestVersionOrCommit(app appPkg.App) (string, error) {
|
|||||||
// validateArgsAndFlags ensures compatible args/flags.
|
// validateArgsAndFlags ensures compatible args/flags.
|
||||||
func validateArgsAndFlags(args []string) error {
|
func validateArgsAndFlags(args []string) error {
|
||||||
if len(args) == 2 && args[1] != "" && internal.Chaos {
|
if len(args) == 2 && args[1] != "" && internal.Chaos {
|
||||||
return errors.New(i18n.G("cannot use [version] and --chaos together"))
|
return fmt.Errorf("cannot use [version] and --chaos together")
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) == 2 && args[1] != "" && internal.DeployLatest {
|
|
||||||
return errors.New(i18n.G("cannot use [version] and --latest together"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if internal.DeployLatest && internal.Chaos {
|
|
||||||
return errors.New(i18n.G("cannot use --chaos and --latest together"))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func validateSecrets(cl *dockerClient.Client, app appPkg.App) error {
|
func validateSecrets(cl *dockerClient.Client, app app.App) error {
|
||||||
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
secretsConfig, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.StackName())
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
secStats, err := secret.PollSecretsStatus(cl, app)
|
secStats, err := secret.PollSecretsStatus(cl, app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -330,56 +268,47 @@ func validateSecrets(cl *dockerClient.Client, app appPkg.App) error {
|
|||||||
|
|
||||||
for _, secStat := range secStats {
|
for _, secStat := range secStats {
|
||||||
if !secStat.CreatedOnRemote {
|
if !secStat.CreatedOnRemote {
|
||||||
secretConfig := secretsConfig[secStat.LocalName]
|
return fmt.Errorf("secret not generated: %s", secStat.LocalName)
|
||||||
if secretConfig.SkipGenerate {
|
|
||||||
return errors.New(i18n.G("secret not inserted (#generate=false): %s", secStat.LocalName))
|
|
||||||
}
|
|
||||||
return errors.New(i18n.G("secret not generated: %s", secStat.LocalName))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDeployVersion(cliArgs []string, deployMeta stack.DeployMeta, app appPkg.App) (string, error) {
|
func getDeployVersion(cliArgs []string, deployMeta stack.DeployMeta, app app.App) (string, error) {
|
||||||
// Chaos mode overrides everything
|
// Chaos mode overrides everything
|
||||||
if internal.Chaos {
|
if internal.Chaos {
|
||||||
v, err := app.Recipe.ChaosVersion()
|
v, err := app.Recipe.ChaosVersion()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
log.Debug(i18n.G("version: taking chaos version: %s", v))
|
log.Debugf("version: taking chaos version: %s", v)
|
||||||
return v, nil
|
return v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the deploy version is set with a cli argument
|
// Check if the deploy version is set with a cli argument
|
||||||
if len(cliArgs) == 2 && cliArgs[1] != "" {
|
if len(cliArgs) == 2 && cliArgs[1] != "" {
|
||||||
log.Debug(i18n.G("version: taking version from cli arg: %s", cliArgs[1]))
|
log.Debugf("version: taking version from cli arg: %s", cliArgs[1])
|
||||||
return cliArgs[1], nil
|
return cliArgs[1], nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the recipe has a version in the .env file
|
// Check if the recipe has a version in the .env file
|
||||||
if app.Recipe.EnvVersion != "" && !internal.DeployLatest {
|
if app.Recipe.EnvVersion != "" && !internal.IgnoreEnvVersion {
|
||||||
if strings.HasSuffix(app.Recipe.EnvVersionRaw, "+U") {
|
if strings.HasSuffix(app.Recipe.EnvVersionRaw, "+U") {
|
||||||
// NOTE(d1): use double-line 5 spaces ("FATA ") trick to make a more
|
return "", fmt.Errorf("version: can not redeploy chaos version %s", app.Recipe.EnvVersionRaw)
|
||||||
// informative error message. it's ugly but that's our logging situation
|
|
||||||
// atm
|
|
||||||
return "", errors.New(i18n.G(`cannot redeploy previous chaos version (%s), did you mean to use "--chaos"?
|
|
||||||
to return to a regular release, specify a release tag, commit SHA or use "--latest"`,
|
|
||||||
formatter.BoldDirtyDefault(app.Recipe.EnvVersionRaw)))
|
|
||||||
}
|
}
|
||||||
log.Debug(i18n.G("version: taking version from .env file: %s", app.Recipe.EnvVersion))
|
log.Debugf("version: taking version from .env file: %s", app.Recipe.EnvVersion)
|
||||||
return app.Recipe.EnvVersion, nil
|
return app.Recipe.EnvVersion, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Take deployed version
|
// Take deployed version
|
||||||
if deployMeta.IsDeployed && !internal.DeployLatest {
|
if deployMeta.IsDeployed {
|
||||||
log.Debug(i18n.G("version: taking deployed version: %s", deployMeta.Version))
|
log.Debugf("version: taking deployed version: %s", deployMeta.Version)
|
||||||
return deployMeta.Version, nil
|
return deployMeta.Version, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
v, err := getLatestVersionOrCommit(app)
|
v, err := getLatestVersionOrCommit(app)
|
||||||
log.Debug(i18n.G("version: taking new recipe version: %s", v))
|
log.Debugf("version: taking new recipe version: %s", v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -389,49 +318,33 @@ func getDeployVersion(cliArgs []string, deployMeta stack.DeployMeta, app appPkg.
|
|||||||
func init() {
|
func init() {
|
||||||
AppDeployCommand.Flags().BoolVarP(
|
AppDeployCommand.Flags().BoolVarP(
|
||||||
&internal.Chaos,
|
&internal.Chaos,
|
||||||
i18n.G("chaos"),
|
"chaos",
|
||||||
i18n.G("C"),
|
"C",
|
||||||
false,
|
false,
|
||||||
i18n.G("ignore uncommitted recipes changes"),
|
"ignore uncommitted recipes changes",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppDeployCommand.Flags().BoolVarP(
|
AppDeployCommand.Flags().BoolVarP(
|
||||||
&internal.Force,
|
&internal.Force,
|
||||||
i18n.G("force"),
|
"force",
|
||||||
i18n.G("f"),
|
"f",
|
||||||
false,
|
false,
|
||||||
i18n.G("perform action without further prompt"),
|
"perform action without further prompt",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppDeployCommand.Flags().BoolVarP(
|
AppDeployCommand.Flags().BoolVarP(
|
||||||
&internal.NoDomainChecks,
|
&internal.NoDomainChecks,
|
||||||
i18n.G("no-domain-checks"),
|
"no-domain-checks",
|
||||||
i18n.G("D"),
|
"D",
|
||||||
false,
|
false,
|
||||||
i18n.G("disable public DNS checks"),
|
"disable public DNS checks",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppDeployCommand.Flags().BoolVarP(
|
AppDeployCommand.Flags().BoolVarP(
|
||||||
&internal.DontWaitConverge,
|
&internal.DontWaitConverge,
|
||||||
i18n.G("no-converge-checks"),
|
"no-converge-checks",
|
||||||
i18n.G("c"),
|
"c",
|
||||||
false,
|
false,
|
||||||
i18n.G("disable converge logic checks"),
|
"disable converge logic checks",
|
||||||
)
|
|
||||||
|
|
||||||
AppDeployCommand.PersistentFlags().BoolVarP(
|
|
||||||
&internal.DeployLatest,
|
|
||||||
i18n.G("latest"),
|
|
||||||
i18n.G("l"),
|
|
||||||
false,
|
|
||||||
i18n.G("deploy latest recipe version"),
|
|
||||||
)
|
|
||||||
|
|
||||||
AppDeployCommand.Flags().BoolVarP(
|
|
||||||
&internal.ShowUnchanged,
|
|
||||||
i18n.G("show-unchanged"),
|
|
||||||
i18n.G("U"),
|
|
||||||
false,
|
|
||||||
i18n.G("show all configs & images, including unchanged ones"),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,26 +3,18 @@ package app
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"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/i18n"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app env` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appEnvAliases = i18n.G("e")
|
|
||||||
|
|
||||||
var AppEnvCommand = &cobra.Command{
|
var AppEnvCommand = &cobra.Command{
|
||||||
// translators: `app env` command
|
Use: "env <domain> [flags]",
|
||||||
Use: i18n.G("env <domain> [flags]"),
|
Aliases: []string{"e"},
|
||||||
Aliases: strings.Split(appEnvAliases, ","),
|
Short: "Show app .env values",
|
||||||
// translators: Short description for `app env` command
|
Example: " abra app env 1312.net",
|
||||||
Short: i18n.G("Show app .env values"),
|
|
||||||
Example: i18n.G(" abra app env 1312.net"),
|
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -45,7 +37,7 @@ var AppEnvCommand = &cobra.Command{
|
|||||||
rows = append(rows, []string{k, app.Env[k]})
|
rows = append(rows, []string{k, app.Env[k]})
|
||||||
}
|
}
|
||||||
|
|
||||||
overview := formatter.CreateOverview(i18n.G("ENV OVERVIEW"), rows)
|
overview := formatter.CreateOverview("ENV OVERVIEW", rows)
|
||||||
fmt.Println(overview)
|
fmt.Println(overview)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,13 +4,11 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/upstream/convert"
|
"coopcloud.tech/abra/pkg/upstream/convert"
|
||||||
composetypes "github.com/docker/cli/cli/compose/types"
|
composetypes "github.com/docker/cli/cli/compose/types"
|
||||||
@ -20,18 +18,12 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app labels` aliases. use a comma separated list of
|
|
||||||
// aliases with no spaces in between
|
|
||||||
var appLabelsAliases = i18n.G("lb")
|
|
||||||
|
|
||||||
var AppLabelsCommand = &cobra.Command{
|
var AppLabelsCommand = &cobra.Command{
|
||||||
// translators: `app labels` command
|
Use: "labels <domain> [flags]",
|
||||||
Use: i18n.G("labels <domain> [flags]"),
|
Aliases: []string{"lb"},
|
||||||
Aliases: strings.Split(appLabelsAliases, ","),
|
Short: "Show deployment labels",
|
||||||
// translators: Short description for `app labels` command
|
Long: "Both local recipe and live deployment labels are shown.",
|
||||||
Short: i18n.G("Show deployment labels"),
|
Example: " abra app labels 1312.net",
|
||||||
Long: i18n.G("Both local recipe and live deployment labels are shown."),
|
|
||||||
Example: " " + i18n.G("abra app labels 1312.net"),
|
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -57,7 +49,7 @@ var AppLabelsCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
rows := [][]string{
|
rows := [][]string{
|
||||||
{i18n.G("DEPLOYED LABELS"), "---"},
|
{"DEPLOYED LABELS", "---"},
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteLabelKeys := make([]string, 0, len(remoteLabels))
|
remoteLabelKeys := make([]string, 0, len(remoteLabels))
|
||||||
@ -75,10 +67,10 @@ var AppLabelsCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(remoteLabelKeys) == 0 {
|
if len(remoteLabelKeys) == 0 {
|
||||||
rows = append(rows, []string{i18n.G("unknown")})
|
rows = append(rows, []string{"unknown"})
|
||||||
}
|
}
|
||||||
|
|
||||||
rows = append(rows, []string{i18n.G("RECIPE LABELS"), "---"})
|
rows = append(rows, []string{"RECIPE LABELS", "---"})
|
||||||
|
|
||||||
config, err := app.Recipe.GetComposeConfig(app.Env)
|
config, err := app.Recipe.GetComposeConfig(app.Env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -106,7 +98,7 @@ var AppLabelsCommand = &cobra.Command{
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
overview := formatter.CreateOverview(i18n.G("LABELS OVERVIEW"), rows)
|
overview := formatter.CreateOverview("LABELS OVERVIEW", rows)
|
||||||
fmt.Println(overview)
|
fmt.Println(overview)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -139,9 +131,9 @@ func getLabels(cl *dockerClient.Client, stackName string) (map[string]string, er
|
|||||||
func init() {
|
func init() {
|
||||||
AppLabelsCommand.Flags().BoolVarP(
|
AppLabelsCommand.Flags().BoolVarP(
|
||||||
&internal.Chaos,
|
&internal.Chaos,
|
||||||
i18n.G("chaos"),
|
"chaos",
|
||||||
i18n.G("C"),
|
"C",
|
||||||
false,
|
false,
|
||||||
i18n.G("ignore uncommitted recipes changes"),
|
"ignore uncommitted recipes changes",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import (
|
|||||||
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/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/tagcmp"
|
"coopcloud.tech/tagcmp"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -25,6 +24,7 @@ type appStatus struct {
|
|||||||
Status string `json:"status"`
|
Status string `json:"status"`
|
||||||
Chaos string `json:"chaos"`
|
Chaos string `json:"chaos"`
|
||||||
ChaosVersion string `json:"chaosVersion"`
|
ChaosVersion string `json:"chaosVersion"`
|
||||||
|
AutoUpdate string `json:"autoUpdate"`
|
||||||
Version string `json:"version"`
|
Version string `json:"version"`
|
||||||
Upgrade string `json:"upgrade"`
|
Upgrade string `json:"upgrade"`
|
||||||
}
|
}
|
||||||
@ -38,27 +38,21 @@ type serverStatus struct {
|
|||||||
UpgradeCount int `json:"upgradeCount"`
|
UpgradeCount int `json:"upgradeCount"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// translators: `abra app list` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appListAliases = i18n.G("ls")
|
|
||||||
|
|
||||||
var AppListCommand = &cobra.Command{
|
var AppListCommand = &cobra.Command{
|
||||||
// translators: `app list` command
|
Use: "list [flags]",
|
||||||
Use: i18n.G("list [flags]"),
|
Aliases: []string{"ls"},
|
||||||
Aliases: strings.Split(appListAliases, ","),
|
Short: "List all managed apps",
|
||||||
// translators: Short description for `app list` command
|
Long: `Generate a report of all managed apps.
|
||||||
Short: i18n.G("List all managed apps"),
|
|
||||||
Long: i18n.G(`Generate a report of all managed apps.
|
|
||||||
|
|
||||||
Use "--status/-S" flag to query all servers for the live deployment status.`),
|
Use "--status/-S" flag to query all servers for the live deployment status.`,
|
||||||
Example: i18n.G(` # list apps of all servers without live status
|
Example: ` # list apps of all servers without live status
|
||||||
abra app ls
|
abra app ls
|
||||||
|
|
||||||
# list apps of a specific server with live status
|
# list apps of a specific server with live status
|
||||||
abra app ls -s 1312.net -S
|
abra app ls -s 1312.net -S
|
||||||
|
|
||||||
# list apps of all servers which match a specific recipe
|
# list apps of all servers which match a specific recipe
|
||||||
abra app ls -r gitea`),
|
abra app ls -r gitea`,
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
appFiles, err := appPkg.LoadAppFiles(listAppServer)
|
appFiles, err := appPkg.LoadAppFiles(listAppServer)
|
||||||
@ -113,10 +107,11 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
|
|||||||
totalAppsCount++
|
totalAppsCount++
|
||||||
|
|
||||||
if status {
|
if status {
|
||||||
status := i18n.G("unknown")
|
status := "unknown"
|
||||||
version := i18n.G("unknown")
|
version := "unknown"
|
||||||
chaos := i18n.G("unknown")
|
chaos := "unknown"
|
||||||
chaosVersion := i18n.G("unknown")
|
chaosVersion := "unknown"
|
||||||
|
autoUpdate := "unknown"
|
||||||
if statusMeta, ok := statuses[app.StackName()]; ok {
|
if statusMeta, ok := statuses[app.StackName()]; ok {
|
||||||
if currentVersion, exists := statusMeta["version"]; exists {
|
if currentVersion, exists := statusMeta["version"]; exists {
|
||||||
if currentVersion != "" {
|
if currentVersion != "" {
|
||||||
@ -129,6 +124,9 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
|
|||||||
if chaosDeployVersion, exists := statusMeta["chaosVersion"]; exists {
|
if chaosDeployVersion, exists := statusMeta["chaosVersion"]; exists {
|
||||||
chaosVersion = chaosDeployVersion
|
chaosVersion = chaosDeployVersion
|
||||||
}
|
}
|
||||||
|
if autoUpdateState, exists := statusMeta["autoUpdate"]; exists {
|
||||||
|
autoUpdate = autoUpdateState
|
||||||
|
}
|
||||||
if statusMeta["status"] != "" {
|
if statusMeta["status"] != "" {
|
||||||
status = statusMeta["status"]
|
status = statusMeta["status"]
|
||||||
}
|
}
|
||||||
@ -141,16 +139,17 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
|
|||||||
appStats.Chaos = chaos
|
appStats.Chaos = chaos
|
||||||
appStats.ChaosVersion = chaosVersion
|
appStats.ChaosVersion = chaosVersion
|
||||||
appStats.Version = version
|
appStats.Version = version
|
||||||
|
appStats.AutoUpdate = autoUpdate
|
||||||
|
|
||||||
var newUpdates []string
|
var newUpdates []string
|
||||||
if version != "unknown" && chaos == "false" {
|
if version != "unknown" && chaosVersion == "unknown" {
|
||||||
if err := app.Recipe.EnsureExists(); err != nil {
|
if err := app.Recipe.EnsureExists(); err != nil {
|
||||||
log.Fatal(i18n.G("unable to clone %s: %s", app.Name, err))
|
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.Fatal(i18n.G("unable to retrieve tags for %s: %s", app.Name, err))
|
log.Fatalf("unable to retrieve tags for %s: %s", app.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedVersion, err := tagcmp.Parse(version)
|
parsedVersion, err := tagcmp.Parse(version)
|
||||||
@ -159,11 +158,6 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, update := range updates {
|
for _, update := range updates {
|
||||||
if ok := tagcmp.IsParsable(update); !ok {
|
|
||||||
log.Debug(i18n.G("unable to parse %s, skipping as upgrade option", update))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
parsedUpdate, err := tagcmp.Parse(update)
|
parsedUpdate, err := tagcmp.Parse(update)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -177,9 +171,9 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
|
|||||||
|
|
||||||
if len(newUpdates) == 0 {
|
if len(newUpdates) == 0 {
|
||||||
if version == "unknown" {
|
if version == "unknown" {
|
||||||
appStats.Upgrade = i18n.G("unknown")
|
appStats.Upgrade = "unknown"
|
||||||
} else {
|
} else {
|
||||||
appStats.Upgrade = i18n.G("latest")
|
appStats.Upgrade = "latest"
|
||||||
stats.LatestCount++
|
stats.LatestCount++
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -218,14 +212,14 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
|
|||||||
|
|
||||||
serverStat := allStats[app.Server]
|
serverStat := allStats[app.Server]
|
||||||
|
|
||||||
headers := []string{i18n.G("RECIPE"), i18n.G("DOMAIN"), i18n.G("SERVER")}
|
headers := []string{"RECIPE", "DOMAIN", "SERVER"}
|
||||||
if status {
|
if status {
|
||||||
headers = append(headers, []string{
|
headers = append(headers, []string{
|
||||||
i18n.G("STATUS"),
|
"STATUS",
|
||||||
i18n.G("CHAOS"),
|
"CHAOS",
|
||||||
i18n.G("VERSION"),
|
"VERSION",
|
||||||
i18n.G("UPGRADE"),
|
"UPGRADE",
|
||||||
}...,
|
"AUTOUPDATE"}...,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,7 +249,8 @@ Use "--status/-S" flag to query all servers for the live deployment status.`),
|
|||||||
appStat.Status,
|
appStat.Status,
|
||||||
chaosStatus,
|
chaosStatus,
|
||||||
appStat.Version,
|
appStat.Version,
|
||||||
appStat.Upgrade}...,
|
appStat.Upgrade,
|
||||||
|
appStat.AutoUpdate}...,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -288,22 +283,22 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
AppListCommand.Flags().BoolVarP(
|
AppListCommand.Flags().BoolVarP(
|
||||||
&status,
|
&status,
|
||||||
i18n.G("status"),
|
"status",
|
||||||
i18n.G("S"),
|
"S",
|
||||||
false,
|
false,
|
||||||
i18n.G("show app deployment status"),
|
"show app deployment status",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppListCommand.Flags().StringVarP(
|
AppListCommand.Flags().StringVarP(
|
||||||
&recipeFilter,
|
&recipeFilter,
|
||||||
i18n.G("recipe"),
|
"recipe",
|
||||||
i18n.G("r"),
|
"r",
|
||||||
"",
|
"",
|
||||||
i18n.G("show apps of a specific recipe"),
|
"show apps of a specific recipe",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppListCommand.RegisterFlagCompletionFunc(
|
AppListCommand.RegisterFlagCompletionFunc(
|
||||||
i18n.G("recipe"),
|
"recipe",
|
||||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
return autocomplete.RecipeNameComplete()
|
return autocomplete.RecipeNameComplete()
|
||||||
},
|
},
|
||||||
@ -311,22 +306,22 @@ func init() {
|
|||||||
|
|
||||||
AppListCommand.Flags().BoolVarP(
|
AppListCommand.Flags().BoolVarP(
|
||||||
&internal.MachineReadable,
|
&internal.MachineReadable,
|
||||||
i18n.G("machine"),
|
"machine",
|
||||||
i18n.G("m"),
|
"m",
|
||||||
false,
|
false,
|
||||||
i18n.G("print machine-readable output"),
|
"print machine-readable output",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppListCommand.Flags().StringVarP(
|
AppListCommand.Flags().StringVarP(
|
||||||
&listAppServer,
|
&listAppServer,
|
||||||
i18n.G("server"),
|
"server",
|
||||||
i18n.G("s"),
|
"s",
|
||||||
"",
|
"",
|
||||||
i18n.G("show apps of a specific server"),
|
"show apps of a specific server",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppListCommand.RegisterFlagCompletionFunc(
|
AppListCommand.RegisterFlagCompletionFunc(
|
||||||
i18n.G("server"),
|
"server",
|
||||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
return autocomplete.ServerNameComplete()
|
return autocomplete.ServerNameComplete()
|
||||||
},
|
},
|
||||||
|
|||||||
@ -2,29 +2,22 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"strings"
|
"fmt"
|
||||||
|
|
||||||
"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/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/logs"
|
"coopcloud.tech/abra/pkg/logs"
|
||||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app logs` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appLogsAliases = i18n.G("l")
|
|
||||||
|
|
||||||
var AppLogsCommand = &cobra.Command{
|
var AppLogsCommand = &cobra.Command{
|
||||||
// translators: `app logs` command
|
Use: "logs <domain> [service] [flags]",
|
||||||
Use: i18n.G("logs <domain> [service] [flags]"),
|
Aliases: []string{"l"},
|
||||||
Aliases: strings.Split(appLogsAliases, ","),
|
Short: "Tail app logs",
|
||||||
// translators: Short description for `app logs` command
|
|
||||||
Short: i18n.G("Tail app logs"),
|
|
||||||
Args: cobra.RangeArgs(1, 2),
|
Args: cobra.RangeArgs(1, 2),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -36,7 +29,8 @@ var AppLogsCommand = &cobra.Command{
|
|||||||
case 1:
|
case 1:
|
||||||
app, err := appPkg.Get(args[0])
|
app, err := appPkg.Get(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{i18n.G("autocomplete failed: %s", err)}, cobra.ShellCompDirectiveError
|
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
|
return []string{errMsg}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
return autocomplete.ServiceNameComplete(app.Name)
|
return autocomplete.ServiceNameComplete(app.Name)
|
||||||
default:
|
default:
|
||||||
@ -62,7 +56,7 @@ var AppLogsCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !deployMeta.IsDeployed {
|
if !deployMeta.IsDeployed {
|
||||||
log.Fatal(i18n.G("%s is not deployed?", app.Name))
|
log.Fatalf("%s is not deployed?", app.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
var serviceNames []string
|
var serviceNames []string
|
||||||
@ -97,17 +91,17 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
AppLogsCommand.Flags().BoolVarP(
|
AppLogsCommand.Flags().BoolVarP(
|
||||||
&stdErr,
|
&stdErr,
|
||||||
i18n.G("stderr"),
|
"stderr",
|
||||||
i18n.G("s"),
|
"s",
|
||||||
false,
|
false,
|
||||||
i18n.G("only tail stderr"),
|
"only tail stderr",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppLogsCommand.Flags().StringVarP(
|
AppLogsCommand.Flags().StringVarP(
|
||||||
&sinceLogs,
|
&sinceLogs,
|
||||||
i18n.G("since"),
|
"since",
|
||||||
i18n.G("S"),
|
"S",
|
||||||
"",
|
"",
|
||||||
i18n.G("tail logs since YYYY-MM-DDTHH:MM:SSZ"),
|
"tail logs since YYYY-MM-DDTHH:MM:SSZ",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
350
cli/app/move.go
350
cli/app/move.go
@ -1,350 +0,0 @@
|
|||||||
package app
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"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"
|
|
||||||
"coopcloud.tech/abra/pkg/config"
|
|
||||||
containerPkg "coopcloud.tech/abra/pkg/container"
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"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"
|
|
||||||
)
|
|
||||||
|
|
||||||
// translators: `abra app move` aliases. use a comma separated list of aliases
|
|
||||||
// with no spaces in between
|
|
||||||
var appMoveAliases = i18n.G("m")
|
|
||||||
|
|
||||||
var AppMoveCommand = &cobra.Command{
|
|
||||||
// translators: `app move` command
|
|
||||||
Use: i18n.G("move <domain> <server> [flags]"),
|
|
||||||
Aliases: strings.Split(appMoveAliases, ","),
|
|
||||||
// translators: Short description for `app move` command
|
|
||||||
Short: i18n.G("Moves an app to a different server"),
|
|
||||||
Long: i18n.G(`Move an app to a differnt server.
|
|
||||||
|
|
||||||
This command will migrate an app config and copy secrets and volumes from the
|
|
||||||
old server to the new one. The app MUST be deployed on the old server before
|
|
||||||
doing the move. The app will be undeployed from the current server but not
|
|
||||||
deployed on the new server.
|
|
||||||
|
|
||||||
The "tar" command is required on both the old and new server as well as "sudo"
|
|
||||||
permissions. The "rsync" command is required on your local machine for
|
|
||||||
transferring volumes.
|
|
||||||
|
|
||||||
Do not forget to update your DNS records. Don't panic, it might take a while
|
|
||||||
for the dust to settle after you move an app. If anything goes wrong, you can
|
|
||||||
always move the app config file to the original server and deploy it there
|
|
||||||
again. No data is removed from the old server.
|
|
||||||
|
|
||||||
Use "--dry-run/-r" to see which secrets and volumes will be moved.`),
|
|
||||||
Example: i18n.G(` # move an app
|
|
||||||
abra app move nextcloud.1312.net 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(i18n.G("no server provided?"))
|
|
||||||
}
|
|
||||||
newServer := internal.ValidateServer([]string{args[1]})
|
|
||||||
|
|
||||||
if err := app.Recipe.Ensure(internal.GetEnsureContext()); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
currentServerClient, err := client.New(app.Server)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
deployMeta, err := stack.IsDeployed(context.Background(), currentServerClient, app.StackName())
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if !deployMeta.IsDeployed {
|
|
||||||
log.Fatal(i18n.G("%s must first be deployed on %s before moving", app.Name, app.Server))
|
|
||||||
}
|
|
||||||
|
|
||||||
resources, err := getAppResources(currentServerClient, app)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(i18n.G("unable to retrieve %s resources on %s: %s", app.Name, app.Server, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
internal.MoveOverview(app, newServer, resources.SecretNames(), resources.VolumeNames())
|
|
||||||
if err := internal.PromptProcced(); err != nil {
|
|
||||||
log.Fatal(i18n.G("bailing out: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info(i18n.G("undeploying %s on %s", app.Name, app.Server))
|
|
||||||
rmOpts := stack.Remove{
|
|
||||||
Namespaces: []string{app.StackName()},
|
|
||||||
Detach: false,
|
|
||||||
}
|
|
||||||
if err := stack.RunRemove(context.Background(), currentServerClient, rmOpts); err != nil {
|
|
||||||
log.Fatal(i18n.G("failed to remove app from %s: %s", err, app.Server))
|
|
||||||
}
|
|
||||||
|
|
||||||
newServerClient, 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(newServerClient, s.Spec.Name, data); err != nil {
|
|
||||||
log.Fatal(i18n.G("failed to store secret on %s: %s", err, newServer))
|
|
||||||
}
|
|
||||||
log.Info(i18n.G("created secret on %s: %s", s.Spec.Name, newServer))
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range resources.Volumes {
|
|
||||||
log.Info(i18n.G("moving volume %s from %s to %s", v.Name, app.Server, newServer))
|
|
||||||
|
|
||||||
// NOTE(p4u1): 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(i18n.G("creating volume %s on %s", v.Name, newServer))
|
|
||||||
_, err := newServerClient.VolumeCreate(context.Background(), volume.CreateOptions{
|
|
||||||
Name: v.Name,
|
|
||||||
Driver: v.Driver,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(i18n.G("failed to create volume %s on %s: %s", v.Name, newServer, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
filename := fmt.Sprintf("%s_outgoing.tar.gz", v.Name)
|
|
||||||
log.Debug(i18n.G("creating %s on %s", filename, app.Server))
|
|
||||||
tarCmd := fmt.Sprintf("sudo tar --same-owner -czhpf %s -C /var/lib/docker/volumes %s", filename, v.Name)
|
|
||||||
cmd := exec.Command("ssh", app.Server, "-tt", tarCmd)
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
log.Fatal(i18n.G("%s failed on %s: output:%s err:%s", tarCmd, app.Server, string(out), err))
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug(i18n.G("rsyncing %s from %s to local machine", filename, app.Server))
|
|
||||||
cmd = exec.Command("rsync", "-a", "-v", fmt.Sprintf("%s:%s", app.Server, filename), filename)
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
log.Fatal(i18n.G("failed to copy %s from %s to local machine: output:%s err:%s", filename, app.Server, string(out), err))
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug(i18n.G("rsyncing %s to %s from local machine", filename, filename, newServer))
|
|
||||||
cmd = exec.Command("rsync", "-a", "-v", filename, fmt.Sprintf("%s:%s", newServer, filename))
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
log.Fatal(i18n.G("failed to copy %s from local machine to %s: output:%s err:%s", filename, newServer, string(out), err))
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug(i18n.G("extracting %s on %s", filename, newServer))
|
|
||||||
tarExtractCmd := fmt.Sprintf("sudo tar --same-owner -xzpf %s -C /var/lib/docker/volumes", filename)
|
|
||||||
cmd = exec.Command("ssh", newServer, "-tt", tarExtractCmd)
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
log.Fatal(i18n.G("%s failed to extract %s on %s: output:%s err:%s", tarExtractCmd, filename, newServer, string(out), err))
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove tar files
|
|
||||||
log.Debug(i18n.G("removing %s from %s", filename, newServer))
|
|
||||||
cmd = exec.Command("ssh", newServer, "-tt", fmt.Sprintf("sudo rm -rf %s", filename))
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
log.Fatal(i18n.G("failed to remove %s from %s: output:%s err:%s", filename, newServer, string(out), err))
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug(i18n.G("removing %s from %s", filename, app.Server))
|
|
||||||
cmd = exec.Command("ssh", app.Server, "-tt", fmt.Sprintf("sudo rm -rf %s", filename))
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
log.Fatal(i18n.G("failed to remove %s from %s: output:%s err:%s", filename, app.Server, string(out), err))
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug(i18n.G("removing %s from local machine", filename))
|
|
||||||
cmd = exec.Command("rm", "-r", "-f", filename)
|
|
||||||
if out, err := cmd.CombinedOutput(); err != nil {
|
|
||||||
log.Fatal(i18n.G("failed to remove %s on local machine: output:%s err:%s", filename, string(out), err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
newServerPath := fmt.Sprintf("%s/servers/%s/%s.env", config.ABRA_DIR, newServer, app.Name)
|
|
||||||
log.Info(i18n.G("migrating app config from %s to %s", app.Server, newServerPath))
|
|
||||||
if err := copyFile(app.Path, newServerPath); err != nil {
|
|
||||||
log.Fatal(i18n.G("failed to migrate app config: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := os.Remove(app.Path); err != nil {
|
|
||||||
log.Fatal(i18n.G("unable to remove %s: %s", app.Path, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info(i18n.G("%s was successfully moved from %s to %s 🎉", app.Name, app.Server, newServer))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
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 {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
secretConfigs, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.StackName())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := stack.Deploy{Composefiles: composeFiles, Namespace: app.StackName()}
|
|
||||||
compose, err := appPkg.GetAppComposeConfig(app.Name, opts, app.Env)
|
|
||||||
if err != nil {
|
|
||||||
return nil, 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 {
|
|
||||||
stackService := fmt.Sprintf("%s_%s", app.StackName(), serviceCompose.Name)
|
|
||||||
if stackService != s.Spec.Name {
|
|
||||||
log.Debug(i18n.G("skipping %s as it does not match %s", stackService, s.Spec.Name))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, secret := range serviceCompose.Secrets {
|
|
||||||
for _, s := range secretList {
|
|
||||||
stackSecret := fmt.Sprintf("%s_%s_%s", app.StackName(), secret.Source, secretConfigs[secret.Source].Version)
|
|
||||||
if s.Spec.Name == stackSecret {
|
|
||||||
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 {
|
|
||||||
return nil, errors.New(i18n.G("unable to get container matching %s: %s", s.Spec.Name, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
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.Debug(i18n.G("extracting secret %s on %s", secretName, app.Server))
|
|
||||||
|
|
||||||
cmd := fmt.Sprintf("sudo cat /var/lib/docker/containers/%s/mounts/secrets/%s", targetContainer.ID, secretID)
|
|
||||||
out, err := exec.Command("ssh", app.Server, "-tt", cmd).Output()
|
|
||||||
if err != nil {
|
|
||||||
return nil, errors.New(i18n.G("%s failed on %s: output:%s err:%s", cmd, app.Server, string(out), err))
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
i18n.G("dry-run"),
|
|
||||||
i18n.G("r"),
|
|
||||||
false,
|
|
||||||
i18n.G("report changes that would be made"),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
125
cli/app/new.go
125
cli/app/new.go
@ -1,9 +1,7 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/app"
|
"coopcloud.tech/abra/pkg/app"
|
||||||
@ -12,16 +10,16 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||||
"coopcloud.tech/abra/pkg/secret"
|
"coopcloud.tech/abra/pkg/secret"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
|
"github.com/charmbracelet/lipgloss/table"
|
||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var appNewDescription = i18n.G(`Creates a new app from a default recipe.
|
var appNewDescription = `Creates a new app from a default recipe.
|
||||||
|
|
||||||
This new app configuration is stored in your $ABRA_DIR directory under the
|
This new app configuration is stored in your $ABRA_DIR directory under the
|
||||||
appropriate server.
|
appropriate server.
|
||||||
@ -41,18 +39,12 @@ store them somewhere safe.
|
|||||||
|
|
||||||
You can use the "--pass/-P" to store these generated passwords locally in a
|
You can use the "--pass/-P" to store these generated passwords locally in a
|
||||||
pass store (see passwordstore.org for more). The pass command must be available
|
pass store (see passwordstore.org for more). The pass command must be available
|
||||||
on your $PATH.`)
|
on your $PATH.`
|
||||||
|
|
||||||
// translators: `abra app new` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appNewAliases = i18n.G("n")
|
|
||||||
|
|
||||||
var AppNewCommand = &cobra.Command{
|
var AppNewCommand = &cobra.Command{
|
||||||
// translators: `app new` command
|
Use: "new [recipe] [version] [flags]",
|
||||||
Use: i18n.G("new [recipe] [version] [flags]"),
|
Aliases: []string{"n"},
|
||||||
Aliases: strings.Split(appNewAliases, ","),
|
Short: "Create a new app",
|
||||||
// translators: Short description for `app new` command
|
|
||||||
Short: i18n.G("Create a new app"),
|
|
||||||
Long: appNewDescription,
|
Long: appNewDescription,
|
||||||
Args: cobra.RangeArgs(0, 2),
|
Args: cobra.RangeArgs(0, 2),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
@ -73,7 +65,7 @@ var AppNewCommand = &cobra.Command{
|
|||||||
recipe := internal.ValidateRecipe(args, cmd.Name())
|
recipe := internal.ValidateRecipe(args, cmd.Name())
|
||||||
|
|
||||||
if len(args) == 2 && internal.Chaos {
|
if len(args) == 2 && internal.Chaos {
|
||||||
log.Fatal(i18n.G("cannot use [version] and --chaos together"))
|
log.Fatal("cannot use [version] and --chaos together")
|
||||||
}
|
}
|
||||||
|
|
||||||
var recipeVersion string
|
var recipeVersion string
|
||||||
@ -121,7 +113,7 @@ var AppNewCommand = &cobra.Command{
|
|||||||
if recipeVersion == "" {
|
if recipeVersion == "" {
|
||||||
head, err := recipe.Head()
|
head, err := recipe.Head()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(i18n.G("failed to retrieve latest commit for %s: %s", recipe.Name, err))
|
log.Fatalf("failed to retrieve latest commit for %s: %s", recipe.Name, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
recipeVersion = formatter.SmallSHA(head.String())
|
recipeVersion = formatter.SmallSHA(head.String())
|
||||||
@ -138,7 +130,7 @@ var AppNewCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
sanitisedAppName := appPkg.SanitiseAppName(appDomain)
|
sanitisedAppName := appPkg.SanitiseAppName(appDomain)
|
||||||
log.Debug(i18n.G("%s sanitised as %s for new app", appDomain, sanitisedAppName))
|
log.Debugf("%s sanitised as %s for new app", appDomain, sanitisedAppName)
|
||||||
|
|
||||||
if err := appPkg.TemplateAppEnvSample(
|
if err := appPkg.TemplateAppEnvSample(
|
||||||
recipe,
|
recipe,
|
||||||
@ -149,6 +141,9 @@ var AppNewCommand = &cobra.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var appSecrets AppSecrets
|
||||||
|
var secretsTable *table.Table
|
||||||
|
if generateSecrets {
|
||||||
sampleEnv, err := recipe.SampleEnv()
|
sampleEnv, err := recipe.SampleEnv()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -168,8 +163,6 @@ var AppNewCommand = &cobra.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var appSecrets AppSecrets
|
|
||||||
if generateSecrets {
|
|
||||||
if err := promptForSecrets(recipe.Name, secretsConfig); err != nil {
|
if err := promptForSecrets(recipe.Name, secretsConfig); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -183,37 +176,25 @@ var AppNewCommand = &cobra.Command{
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
secretsTable, err = formatter.CreateTable()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
headers := []string{"NAME", "VALUE"}
|
||||||
|
secretsTable.Headers(headers...)
|
||||||
|
|
||||||
|
for name, val := range appSecrets {
|
||||||
|
secretsTable.Row(name, val)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if newAppServer == "default" {
|
if newAppServer == "default" {
|
||||||
newAppServer = "local"
|
newAppServer = "local"
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(i18n.G("%s created (version: %s)", appDomain, recipeVersion))
|
log.Infof("%s created (version: %s)", appDomain, recipeVersion)
|
||||||
|
|
||||||
if len(secretsConfig) > 0 {
|
|
||||||
var (
|
|
||||||
hasSecretToGenerate bool
|
|
||||||
hasSecretToSkip bool
|
|
||||||
)
|
|
||||||
|
|
||||||
for _, secretConfig := range secretsConfig {
|
|
||||||
if secretConfig.SkipGenerate {
|
|
||||||
hasSecretToSkip = true
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
hasSecretToGenerate = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasSecretToGenerate && !generateSecrets {
|
|
||||||
log.Warn(i18n.G("%s requires secret generation before deploy, run \"abra app secret generate %s --all\"", recipe.Name, appDomain))
|
|
||||||
}
|
|
||||||
|
|
||||||
if hasSecretToSkip {
|
|
||||||
log.Warn(i18n.G("%s requires secret insertion before deploy (#generate=false)", recipe.Name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(appSecrets) > 0 {
|
if len(appSecrets) > 0 {
|
||||||
rows := [][]string{}
|
rows := [][]string{}
|
||||||
@ -221,15 +202,15 @@ var AppNewCommand = &cobra.Command{
|
|||||||
rows = append(rows, []string{k, v})
|
rows = append(rows, []string{k, v})
|
||||||
}
|
}
|
||||||
|
|
||||||
overview := formatter.CreateOverview(i18n.G("SECRETS OVERVIEW"), rows)
|
overview := formatter.CreateOverview("SECRETS OVERVIEW", rows)
|
||||||
|
|
||||||
fmt.Println(overview)
|
fmt.Println(overview)
|
||||||
|
|
||||||
log.Warn(i18n.G(
|
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.BoldUnderlineStyle.Render("NOT"),
|
||||||
formatter.BoldUnderlineStyle.Render("NOW"),
|
formatter.BoldUnderlineStyle.Render("NOW"),
|
||||||
))
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
app, err := app.Get(appDomain)
|
app, err := app.Get(appDomain)
|
||||||
@ -238,7 +219,7 @@ var AppNewCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := app.WriteRecipeVersion(recipeVersion, false); err != nil {
|
if err := app.WriteRecipeVersion(recipeVersion, false); err != nil {
|
||||||
log.Fatal(i18n.G("writing recipe version failed: %s", err))
|
log.Fatalf("writing recipe version failed: %s", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -250,7 +231,7 @@ type AppSecrets map[string]string
|
|||||||
func createSecrets(cl *dockerClient.Client, secretsConfig map[string]secret.Secret, sanitisedAppName string) (AppSecrets, error) {
|
func createSecrets(cl *dockerClient.Client, secretsConfig map[string]secret.Secret, sanitisedAppName string) (AppSecrets, error) {
|
||||||
// NOTE(d1): trim to match app.StackName() implementation
|
// NOTE(d1): trim to match app.StackName() implementation
|
||||||
if len(sanitisedAppName) > config.MAX_SANITISED_APP_NAME_LENGTH {
|
if len(sanitisedAppName) > config.MAX_SANITISED_APP_NAME_LENGTH {
|
||||||
log.Debug(i18n.G("trimming %s to %s to avoid runtime limits", sanitisedAppName, sanitisedAppName[:config.MAX_SANITISED_APP_NAME_LENGTH]))
|
log.Debugf("trimming %s to %s to avoid runtime limits", sanitisedAppName, sanitisedAppName[:config.MAX_SANITISED_APP_NAME_LENGTH])
|
||||||
sanitisedAppName = sanitisedAppName[:config.MAX_SANITISED_APP_NAME_LENGTH]
|
sanitisedAppName = sanitisedAppName[:config.MAX_SANITISED_APP_NAME_LENGTH]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,7 +261,7 @@ func createSecrets(cl *dockerClient.Client, secretsConfig map[string]secret.Secr
|
|||||||
func ensureDomainFlag(recipe recipePkg.Recipe, server string) error {
|
func ensureDomainFlag(recipe recipePkg.Recipe, server string) error {
|
||||||
if appDomain == "" && !internal.NoInput {
|
if appDomain == "" && !internal.NoInput {
|
||||||
prompt := &survey.Input{
|
prompt := &survey.Input{
|
||||||
Message: i18n.G("Specify app domain"),
|
Message: "Specify app domain",
|
||||||
Default: fmt.Sprintf("%s.%s", recipe.Name, server),
|
Default: fmt.Sprintf("%s.%s", recipe.Name, server),
|
||||||
}
|
}
|
||||||
if err := survey.AskOne(prompt, &appDomain); err != nil {
|
if err := survey.AskOne(prompt, &appDomain); err != nil {
|
||||||
@ -289,7 +270,7 @@ func ensureDomainFlag(recipe recipePkg.Recipe, server string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if appDomain == "" {
|
if appDomain == "" {
|
||||||
return errors.New(i18n.G("no domain provided"))
|
return fmt.Errorf("no domain provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -298,13 +279,13 @@ func ensureDomainFlag(recipe recipePkg.Recipe, server string) error {
|
|||||||
// promptForSecrets asks if we should generate secrets for a new app.
|
// promptForSecrets asks if we should generate secrets for a new app.
|
||||||
func promptForSecrets(recipeName string, secretsConfig map[string]secret.Secret) error {
|
func promptForSecrets(recipeName string, secretsConfig map[string]secret.Secret) error {
|
||||||
if len(secretsConfig) == 0 {
|
if len(secretsConfig) == 0 {
|
||||||
log.Debug(i18n.G("%s has no secrets to generate, skipping...", recipeName))
|
log.Debugf("%s has no secrets to generate, skipping...", recipeName)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !generateSecrets && !internal.NoInput {
|
if !generateSecrets && !internal.NoInput {
|
||||||
prompt := &survey.Confirm{
|
prompt := &survey.Confirm{
|
||||||
Message: i18n.G("Generate app secrets?"),
|
Message: "Generate app secrets?",
|
||||||
}
|
}
|
||||||
if err := survey.AskOne(prompt, &generateSecrets); err != nil {
|
if err := survey.AskOne(prompt, &generateSecrets); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -323,13 +304,13 @@ func ensureServerFlag() error {
|
|||||||
|
|
||||||
if len(servers) == 1 {
|
if len(servers) == 1 {
|
||||||
newAppServer = servers[0]
|
newAppServer = servers[0]
|
||||||
log.Info(i18n.G("single server detected, choosing %s automatically", newAppServer))
|
log.Infof("single server detected, choosing %s automatically", newAppServer)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if newAppServer == "" && !internal.NoInput {
|
if newAppServer == "" && !internal.NoInput {
|
||||||
prompt := &survey.Select{
|
prompt := &survey.Select{
|
||||||
Message: i18n.G("Select app server:"),
|
Message: "Select app server:",
|
||||||
Options: servers,
|
Options: servers,
|
||||||
}
|
}
|
||||||
if err := survey.AskOne(prompt, &newAppServer); err != nil {
|
if err := survey.AskOne(prompt, &newAppServer); err != nil {
|
||||||
@ -338,7 +319,7 @@ func ensureServerFlag() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if newAppServer == "" {
|
if newAppServer == "" {
|
||||||
return errors.New(i18n.G("no server provided"))
|
return fmt.Errorf("no server provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -354,14 +335,14 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
AppNewCommand.Flags().StringVarP(
|
AppNewCommand.Flags().StringVarP(
|
||||||
&newAppServer,
|
&newAppServer,
|
||||||
i18n.G("server"),
|
"server",
|
||||||
i18n.G("s"),
|
"s",
|
||||||
"",
|
"",
|
||||||
i18n.G("specify server for new app"),
|
"specify server for new app",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppNewCommand.RegisterFlagCompletionFunc(
|
AppNewCommand.RegisterFlagCompletionFunc(
|
||||||
i18n.G("server"),
|
"server",
|
||||||
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
return autocomplete.ServerNameComplete()
|
return autocomplete.ServerNameComplete()
|
||||||
},
|
},
|
||||||
@ -369,34 +350,34 @@ func init() {
|
|||||||
|
|
||||||
AppNewCommand.Flags().StringVarP(
|
AppNewCommand.Flags().StringVarP(
|
||||||
&appDomain,
|
&appDomain,
|
||||||
i18n.G("domain"),
|
"domain",
|
||||||
i18n.G("D"),
|
"D",
|
||||||
"",
|
"",
|
||||||
i18n.G("domain name for app"),
|
"domain name for app",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppNewCommand.Flags().BoolVarP(
|
AppNewCommand.Flags().BoolVarP(
|
||||||
&saveInPass,
|
&saveInPass,
|
||||||
i18n.G("pass"),
|
"pass",
|
||||||
i18n.G("p"),
|
"p",
|
||||||
false,
|
false,
|
||||||
i18n.G("store secrets in a local pass store"),
|
"store secrets in a local pass store",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppNewCommand.Flags().BoolVarP(
|
AppNewCommand.Flags().BoolVarP(
|
||||||
&generateSecrets,
|
&generateSecrets,
|
||||||
i18n.G("secrets"),
|
"secrets",
|
||||||
i18n.G("S"),
|
"S",
|
||||||
false,
|
false,
|
||||||
i18n.G("automatically generate secrets"),
|
"automatically generate secrets",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppNewCommand.Flags().BoolVarP(
|
AppNewCommand.Flags().BoolVarP(
|
||||||
&internal.Chaos,
|
&internal.Chaos,
|
||||||
i18n.G("chaos"),
|
"chaos",
|
||||||
i18n.G("C"),
|
"C",
|
||||||
false,
|
false,
|
||||||
i18n.G("ignore uncommitted recipes changes"),
|
"ignore uncommitted recipes changes",
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,7 +13,6 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
abraService "coopcloud.tech/abra/pkg/service"
|
abraService "coopcloud.tech/abra/pkg/service"
|
||||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
@ -24,16 +23,10 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app ps` aliases. use a comma separated list of aliases
|
|
||||||
// with no spaces in between
|
|
||||||
var appPsAliases = i18n.G("p")
|
|
||||||
|
|
||||||
var AppPsCommand = &cobra.Command{
|
var AppPsCommand = &cobra.Command{
|
||||||
// translators: `app ps` command
|
Use: "ps <domain> [flags]",
|
||||||
Use: i18n.G("ps <domain> [flags]"),
|
Aliases: []string{"p"},
|
||||||
Aliases: strings.Split(appPsAliases, ","),
|
Short: "Check app deployment status",
|
||||||
// translators: Short description for `app ps` command
|
|
||||||
Short: i18n.G("Check app deployment status"),
|
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -59,7 +52,7 @@ var AppPsCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !deployMeta.IsDeployed {
|
if !deployMeta.IsDeployed {
|
||||||
log.Fatal(i18n.G("%s is not deployed?", app.Name))
|
log.Fatalf("%s is not deployed?", app.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
chaosVersion := config.CHAOS_DEFAULT
|
chaosVersion := config.CHAOS_DEFAULT
|
||||||
@ -122,11 +115,11 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
|
|||||||
"version": deployedVersion,
|
"version": deployedVersion,
|
||||||
"chaos": chaosVersion,
|
"chaos": chaosVersion,
|
||||||
"service": service.Name,
|
"service": service.Name,
|
||||||
"image": i18n.G("unknown"),
|
"image": "unknown",
|
||||||
"created": i18n.G("unknown"),
|
"created": "unknown",
|
||||||
"status": i18n.G("unknown"),
|
"status": "unknown",
|
||||||
"state": i18n.G("unknown"),
|
"state": "unknown",
|
||||||
"ports": i18n.G("unknown"),
|
"ports": "unknown",
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
container := containers[0]
|
container := containers[0]
|
||||||
@ -168,7 +161,7 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
|
|||||||
if internal.MachineReadable {
|
if internal.MachineReadable {
|
||||||
rendered, err := json.Marshal(allContainerStats)
|
rendered, err := json.Marshal(allContainerStats)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(i18n.G("unable to convert to JSON: %s", err))
|
log.Fatal("unable to convert to JSON: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(string(rendered))
|
fmt.Println(string(rendered))
|
||||||
@ -182,11 +175,11 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
|
|||||||
}
|
}
|
||||||
|
|
||||||
headers := []string{
|
headers := []string{
|
||||||
i18n.G("SERVICE"),
|
"SERVICE",
|
||||||
i18n.G("STATUS"),
|
"STATUS",
|
||||||
i18n.G("IMAGE"),
|
"IMAGE",
|
||||||
i18n.G("VERSION"),
|
"VERSION",
|
||||||
i18n.G("CHAOS"),
|
"CHAOS",
|
||||||
}
|
}
|
||||||
|
|
||||||
table.
|
table.
|
||||||
@ -201,17 +194,17 @@ func showPSOutput(app appPkg.App, cl *dockerClient.Client, deployedVersion, chao
|
|||||||
func init() {
|
func init() {
|
||||||
AppPsCommand.Flags().BoolVarP(
|
AppPsCommand.Flags().BoolVarP(
|
||||||
&internal.MachineReadable,
|
&internal.MachineReadable,
|
||||||
i18n.G("machine"),
|
"machine",
|
||||||
i18n.G("m"),
|
"m",
|
||||||
false,
|
false,
|
||||||
i18n.G("print machine-readable output"),
|
"print machine-readable output",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppPsCommand.Flags().BoolVarP(
|
AppPsCommand.Flags().BoolVarP(
|
||||||
&internal.Chaos,
|
&internal.Chaos,
|
||||||
i18n.G("chaos"),
|
"chaos",
|
||||||
i18n.G("C"),
|
"C",
|
||||||
false,
|
false,
|
||||||
i18n.G("ignore uncommitted recipes changes"),
|
"ignore uncommitted recipes changes",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,31 +2,24 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"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"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app remove` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appRemoveAliases = i18n.G("rm")
|
|
||||||
|
|
||||||
var AppRemoveCommand = &cobra.Command{
|
var AppRemoveCommand = &cobra.Command{
|
||||||
// translators: `app remove` command
|
Use: "remove <domain> [flags]",
|
||||||
Use: i18n.G("remove <domain> [flags]"),
|
Aliases: []string{"rm"},
|
||||||
Aliases: strings.Split(appRemoveAliases, ","),
|
Short: "Remove all app data, locally and remotely",
|
||||||
// translators: Short description for `app remove` command
|
Long: `Remove everything related to an app which is already undeployed.
|
||||||
Short: i18n.G("Remove all app data, locally and remotely"),
|
|
||||||
Long: i18n.G(`Remove everything related to an app which is already undeployed.
|
|
||||||
|
|
||||||
By default, it will prompt for confirmation before proceeding. All secrets,
|
By default, it will prompt for confirmation before proceeding. All secrets,
|
||||||
volumes and the local app env file will be deleted.
|
volumes and the local app env file will be deleted.
|
||||||
@ -41,8 +34,8 @@ Please note, if you delete the local app env file without removing volumes and
|
|||||||
secrets first, Abra will *not* be able to help you remove them afterwards.
|
secrets first, Abra will *not* be able to help you remove them afterwards.
|
||||||
|
|
||||||
To delete everything without prompt, use the "--force/-f" or the "--no-input/n"
|
To delete everything without prompt, use the "--force/-f" or the "--no-input/n"
|
||||||
flag.`),
|
flag.`,
|
||||||
Example: i18n.G(" abra app remove 1312.net"),
|
Example: " abra app remove 1312.net",
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -54,16 +47,16 @@ flag.`),
|
|||||||
app := internal.ValidateApp(args)
|
app := internal.ValidateApp(args)
|
||||||
|
|
||||||
if !internal.Force && !internal.NoInput {
|
if !internal.Force && !internal.NoInput {
|
||||||
log.Warn(i18n.G("ALERTA ALERTA: deleting %s data and config (local/remote)", app.Name))
|
log.Warnf("ALERTA ALERTA: deleting %s data and config (local/remote)", app.Name)
|
||||||
|
|
||||||
response := false
|
response := false
|
||||||
prompt := &survey.Confirm{Message: i18n.G("are you sure?")}
|
prompt := &survey.Confirm{Message: "are you sure?"}
|
||||||
if err := survey.AskOne(prompt, &response); err != nil {
|
if err := survey.AskOne(prompt, &response); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !response {
|
if !response {
|
||||||
log.Fatal(i18n.G("aborting as requested"))
|
log.Fatal("aborting as requested")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,7 +70,7 @@ flag.`),
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
if deployMeta.IsDeployed {
|
if deployMeta.IsDeployed {
|
||||||
log.Fatal(i18n.G("%s is still deployed. Run \"abra app undeploy %s\"", app.Name, app.Name))
|
log.Fatalf("%s is still deployed. Run \"abra app undeploy %s\"", app.Name, app.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
fs, err := app.Filters(false, false)
|
fs, err := app.Filters(false, false)
|
||||||
@ -85,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.Fatal(i18n.G("removing configs failed: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info(i18n.G("%d config(s) removed successfully", len(configNames)))
|
|
||||||
} else {
|
|
||||||
log.Info(i18n.G("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)
|
||||||
@ -120,10 +97,10 @@ flag.`),
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
log.Info(i18n.G("secret: %s removed", name))
|
log.Info(fmt.Sprintf("secret: %s removed", name))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Info(i18n.G("no secrets to remove"))
|
log.Info("no secrets to remove")
|
||||||
}
|
}
|
||||||
|
|
||||||
fs, err = app.Filters(false, true)
|
fs, err = app.Filters(false, true)
|
||||||
@ -140,28 +117,28 @@ flag.`),
|
|||||||
if len(volumeNames) > 0 {
|
if len(volumeNames) > 0 {
|
||||||
err := client.RemoveVolumes(cl, context.Background(), volumeNames, internal.Force, 5)
|
err := client.RemoveVolumes(cl, context.Background(), volumeNames, internal.Force, 5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(i18n.G("removing volumes failed: %s", err))
|
log.Fatalf("removing volumes failed: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(i18n.G("%d volume(s) removed successfully", len(volumeNames)))
|
log.Infof("%d volumes removed successfully", len(volumeNames))
|
||||||
} else {
|
} else {
|
||||||
log.Info(i18n.G("no volumes to remove"))
|
log.Info("no volumes to remove")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = os.Remove(app.Path); err != nil {
|
if err = os.Remove(app.Path); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(i18n.G("file: %s removed", app.Path))
|
log.Info(fmt.Sprintf("file: %s removed", app.Path))
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
AppRemoveCommand.Flags().BoolVarP(
|
AppRemoveCommand.Flags().BoolVarP(
|
||||||
&internal.Force,
|
&internal.Force,
|
||||||
i18n.G("force"),
|
"force",
|
||||||
i18n.G("f"),
|
"f",
|
||||||
false,
|
false,
|
||||||
i18n.G("perform action without further prompt"),
|
"perform action without further prompt",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,13 +3,11 @@ package app
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"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/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/ui"
|
"coopcloud.tech/abra/pkg/ui"
|
||||||
upstream "coopcloud.tech/abra/pkg/upstream/service"
|
upstream "coopcloud.tech/abra/pkg/upstream/service"
|
||||||
@ -18,26 +16,20 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app restart` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appRestartAliases = i18n.G("re")
|
|
||||||
|
|
||||||
var AppRestartCommand = &cobra.Command{
|
var AppRestartCommand = &cobra.Command{
|
||||||
// translators: `app restart` command
|
Use: "restart <domain> [[service] | --all-services] [flags]",
|
||||||
Use: i18n.G("restart <domain> [[service] | --all-services] [flags]"),
|
Aliases: []string{"re"},
|
||||||
Aliases: strings.Split(appRestartAliases, ","),
|
Short: "Restart an app",
|
||||||
// translators: Short description for `app restart` command
|
Long: `This command restarts services within a deployed app.
|
||||||
Short: i18n.G("Restart an app"),
|
|
||||||
Long: i18n.G(`This command restarts services within a deployed app.
|
|
||||||
|
|
||||||
Run "abra app ps <domain>" to see a list of service names.
|
Run "abra app ps <domain>" to see a list of service names.
|
||||||
|
|
||||||
Pass "--all-services/-a" to restart all services.`),
|
Pass "--all-services/-a" to restart all services.`,
|
||||||
Example: i18n.G(` # restart a single app service
|
Example: ` # restart a single app service
|
||||||
abra app restart 1312.net app
|
abra app restart 1312.net app
|
||||||
|
|
||||||
# restart all app services
|
# restart all app services
|
||||||
abra app restart 1312.net -a`),
|
abra app restart 1312.net -a`,
|
||||||
Args: cobra.RangeArgs(1, 2),
|
Args: cobra.RangeArgs(1, 2),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -68,11 +60,11 @@ Pass "--all-services/-a" to restart all services.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
if serviceName == "" && !allServices {
|
if serviceName == "" && !allServices {
|
||||||
log.Fatal(i18n.G("missing [service]"))
|
log.Fatal("missing [service]")
|
||||||
}
|
}
|
||||||
|
|
||||||
if serviceName != "" && allServices {
|
if serviceName != "" && allServices {
|
||||||
log.Fatal(i18n.G("cannot use [service] and --all-services/-a together"))
|
log.Fatal("cannot use [service] and --all-services/-a together")
|
||||||
}
|
}
|
||||||
|
|
||||||
var serviceNames []string
|
var serviceNames []string
|
||||||
@ -97,7 +89,7 @@ Pass "--all-services/-a" to restart all services.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !deployMeta.IsDeployed {
|
if !deployMeta.IsDeployed {
|
||||||
log.Fatal(i18n.G("%s is not deployed?", app.Name))
|
log.Fatalf("%s is not deployed?", app.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, serviceName := range serviceNames {
|
for _, serviceName := range serviceNames {
|
||||||
@ -112,7 +104,7 @@ Pass "--all-services/-a" to restart all services.`),
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("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)
|
||||||
@ -136,8 +128,8 @@ Pass "--all-services/-a" to restart all services.`),
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("%s has been scaled to 0", stackServiceName))
|
log.Debugf("%s has been scaled to 0", stackServiceName)
|
||||||
log.Debug(i18n.G("attempting to scale %s to 1", stackServiceName))
|
log.Debugf("attempting to scale %s to 1", stackServiceName)
|
||||||
|
|
||||||
if err := upstream.RunServiceScale(context.Background(), cl, stackServiceName, 1); err != nil {
|
if err := upstream.RunServiceScale(context.Background(), cl, stackServiceName, 1); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -147,8 +139,8 @@ Pass "--all-services/-a" to restart all services.`),
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("%s has been scaled to 1", stackServiceName))
|
log.Debugf("%s has been scaled to 1", stackServiceName)
|
||||||
log.Info(i18n.G("%s service successfully restarted", serviceName))
|
log.Infof("%s service successfully restarted", serviceName)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -158,16 +150,16 @@ var allServices bool
|
|||||||
func init() {
|
func init() {
|
||||||
AppRestartCommand.Flags().BoolVarP(
|
AppRestartCommand.Flags().BoolVarP(
|
||||||
&internal.Chaos,
|
&internal.Chaos,
|
||||||
i18n.G("chaos"),
|
"chaos",
|
||||||
i18n.G("C"),
|
"C",
|
||||||
false,
|
false,
|
||||||
i18n.G("ignore uncommitted recipes changes"),
|
"ignore uncommitted recipes changes",
|
||||||
)
|
)
|
||||||
AppRestartCommand.Flags().BoolVarP(
|
AppRestartCommand.Flags().BoolVarP(
|
||||||
&allServices,
|
&allServices,
|
||||||
i18n.G("all-services"),
|
"all-services",
|
||||||
i18n.GC("a", "app restart"),
|
"a",
|
||||||
false,
|
false,
|
||||||
i18n.G("restart all services"),
|
"restart all services",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -7,24 +7,17 @@ import (
|
|||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app restore` aliases. use a comma separated list of
|
|
||||||
// aliases with no spaces in between
|
|
||||||
var appRestoreAliases = i18n.G("rs")
|
|
||||||
|
|
||||||
var AppRestoreCommand = &cobra.Command{
|
var AppRestoreCommand = &cobra.Command{
|
||||||
// translators: `app restore` command
|
Use: "restore <domain> [flags]",
|
||||||
Use: i18n.G("restore <domain> [flags]"),
|
Aliases: []string{"rs"},
|
||||||
Aliases: strings.Split(appRestoreAliases, ","),
|
Short: "Restore a snapshot",
|
||||||
// translators: Short description for `app restore` command
|
Long: `Snapshots are restored while apps are deployed.
|
||||||
Short: i18n.G("Restore a snapshot"),
|
|
||||||
Long: i18n.G(`Snapshots are restored while apps are deployed.
|
|
||||||
|
|
||||||
Some restore scenarios may require service / app restarts.`),
|
Some restore scenarios may require service / app restarts.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -55,34 +48,34 @@ Some restore scenarios may require service / app restarts.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
if snapshot != "" {
|
if snapshot != "" {
|
||||||
log.Debug(i18n.G("including SNAPSHOT=%s in backupbot exec invocation", snapshot))
|
log.Debugf("including SNAPSHOT=%s in backupbot exec invocation", snapshot)
|
||||||
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
|
execEnv = append(execEnv, fmt.Sprintf("SNAPSHOT=%s", snapshot))
|
||||||
}
|
}
|
||||||
|
|
||||||
if targetPath != "" {
|
if targetPath != "" {
|
||||||
log.Debug(i18n.G("including TARGET=%s in backupbot exec invocation", targetPath))
|
log.Debugf("including TARGET=%s in backupbot exec invocation", targetPath)
|
||||||
execEnv = append(execEnv, fmt.Sprintf("TARGET=%s", targetPath))
|
execEnv = append(execEnv, fmt.Sprintf("TARGET=%s", targetPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.NoInput {
|
if internal.NoInput {
|
||||||
log.Debug(i18n.G("including NONINTERACTIVE=%v in backupbot exec invocation", internal.NoInput))
|
log.Debugf("including NONINTERACTIVE=%v in backupbot exec invocation", internal.NoInput)
|
||||||
execEnv = append(execEnv, fmt.Sprintf("NONINTERACTIVE=%v", internal.NoInput))
|
execEnv = append(execEnv, fmt.Sprintf("NONINTERACTIVE=%v", internal.NoInput))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(volumes) > 0 {
|
if len(volumes) > 0 {
|
||||||
allVolumes := strings.Join(volumes, ",")
|
allVolumes := strings.Join(volumes, ",")
|
||||||
log.Debug(i18n.G("including VOLUMES=%s in backupbot exec invocation", allVolumes))
|
log.Debugf("including VOLUMES=%s in backupbot exec invocation", allVolumes)
|
||||||
execEnv = append(execEnv, fmt.Sprintf("VOLUMES=%s", allVolumes))
|
execEnv = append(execEnv, fmt.Sprintf("VOLUMES=%s", allVolumes))
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(services) > 0 {
|
if len(services) > 0 {
|
||||||
allServices := strings.Join(services, ",")
|
allServices := strings.Join(services, ",")
|
||||||
log.Debug(i18n.G("including CONTAINER=%s in backupbot exec invocation", allServices))
|
log.Debugf("including CONTAINER=%s in backupbot exec invocation", allServices)
|
||||||
execEnv = append(execEnv, fmt.Sprintf("CONTAINER=%s", allServices))
|
execEnv = append(execEnv, fmt.Sprintf("CONTAINER=%s", allServices))
|
||||||
}
|
}
|
||||||
|
|
||||||
if hooks {
|
if hooks {
|
||||||
log.Debug(i18n.G("including NO_COMMANDS=%v in backupbot exec invocation", false))
|
log.Debugf("including NO_COMMANDS=%v in backupbot exec invocation", false)
|
||||||
execEnv = append(execEnv, fmt.Sprintf("NO_COMMANDS=%v", false))
|
execEnv = append(execEnv, fmt.Sprintf("NO_COMMANDS=%v", false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,41 +95,41 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
AppRestoreCommand.Flags().StringVarP(
|
AppRestoreCommand.Flags().StringVarP(
|
||||||
&targetPath,
|
&targetPath,
|
||||||
i18n.G("target"),
|
"target",
|
||||||
i18n.G("t"),
|
"t",
|
||||||
"/",
|
"/",
|
||||||
i18n.G("target path"),
|
"target path",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppRestoreCommand.Flags().StringArrayVarP(
|
AppRestoreCommand.Flags().StringArrayVarP(
|
||||||
&services,
|
&services,
|
||||||
i18n.G("services"),
|
"services",
|
||||||
i18n.G("s"),
|
"s",
|
||||||
[]string{},
|
[]string{},
|
||||||
i18n.G("restore specific services"),
|
"restore specific services",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppRestoreCommand.Flags().StringArrayVarP(
|
AppRestoreCommand.Flags().StringArrayVarP(
|
||||||
&volumes,
|
&volumes,
|
||||||
i18n.G("volumes"),
|
"volumes",
|
||||||
i18n.G("v"),
|
"v",
|
||||||
[]string{},
|
[]string{},
|
||||||
i18n.G("restore specific volumes"),
|
"restore specific volumes",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppRestoreCommand.Flags().BoolVarP(
|
AppRestoreCommand.Flags().BoolVarP(
|
||||||
&hooks,
|
&hooks,
|
||||||
i18n.G("hooks"),
|
"hooks",
|
||||||
i18n.G("H"),
|
"H",
|
||||||
false,
|
false,
|
||||||
i18n.G("enable pre/post-hook command execution"),
|
"enable pre/post-hook command execution",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppRestoreCommand.Flags().BoolVarP(
|
AppRestoreCommand.Flags().BoolVarP(
|
||||||
&internal.Chaos,
|
&internal.Chaos,
|
||||||
i18n.G("chaos"),
|
"chaos",
|
||||||
i18n.G("C"),
|
"C",
|
||||||
false,
|
false,
|
||||||
i18n.G("ignore uncommitted recipes changes"),
|
"ignore uncommitted recipes changes",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,15 +1,14 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
|
"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/deploy"
|
"coopcloud.tech/abra/pkg/envfile"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"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,17 +20,11 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app rollback` aliases. use a comma separated list of
|
|
||||||
// aliases with no spaces in between
|
|
||||||
var appRollbackAliases = i18n.G("rl")
|
|
||||||
|
|
||||||
var AppRollbackCommand = &cobra.Command{
|
var AppRollbackCommand = &cobra.Command{
|
||||||
// translators: `app rollback` command
|
Use: "rollback <domain> [version] [flags]",
|
||||||
Use: i18n.G("rollback <domain> [version] [flags]"),
|
Aliases: []string{"rl"},
|
||||||
Aliases: strings.Split(appRollbackAliases, ","),
|
Short: "Roll an app back to a previous version",
|
||||||
// translators: Short description for `app rollback` command
|
Long: `This command rolls an app back to a previous version.
|
||||||
Short: i18n.G("Roll an app back to a previous version"),
|
|
||||||
Long: i18n.G(`This command rolls an app back to a previous version.
|
|
||||||
|
|
||||||
Unlike "abra app deploy", chaos operations are not supported here. Only recipe
|
Unlike "abra app deploy", chaos operations are not supported here. Only recipe
|
||||||
versions are supported values for "[version]".
|
versions are supported values for "[version]".
|
||||||
@ -44,12 +37,12 @@ are available. The live deployment version is the "source of truth" in this
|
|||||||
case. The stored .env version is not consulted.
|
case. The stored .env version is not consulted.
|
||||||
|
|
||||||
A downgrade can be destructive, please ensure you have a copy of your app data
|
A downgrade can be destructive, please ensure you have a copy of your app data
|
||||||
beforehand. See "abra app backup" for more.`),
|
beforehand. See "abra app backup" for more.`,
|
||||||
Example: i18n.G(` # standard rollback
|
Example: ` # standard rollback
|
||||||
abra app rollback 1312.net
|
abra app rollback 1312.net
|
||||||
|
|
||||||
# rollback to specific version
|
# rollback to specific version
|
||||||
abra app rollback 1312.net 2.0.0+1.2.3`),
|
abra app rollback 1312.net 2.0.0+1.2.3`,
|
||||||
Args: cobra.RangeArgs(1, 2),
|
Args: cobra.RangeArgs(1, 2),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -61,7 +54,8 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
case 1:
|
case 1:
|
||||||
app, err := appPkg.Get(args[0])
|
app, err := appPkg.Get(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{i18n.G("autocomplete failed: %s", err)}, cobra.ShellCompDirectiveError
|
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
|
return []string{errMsg}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
return autocomplete.RecipeVersionComplete(app.Recipe.Name)
|
return autocomplete.RecipeVersionComplete(app.Recipe.Name)
|
||||||
default:
|
default:
|
||||||
@ -123,7 +117,7 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !downgradeAvailable {
|
if !downgradeAvailable {
|
||||||
log.Info(i18n.G("no available downgrades"))
|
log.Info("no available downgrades")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -145,18 +139,22 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
if chosenDowngrade == "" {
|
if chosenDowngrade == "" {
|
||||||
log.Fatal(i18n.G("unknown deployed version, unable to downgrade"))
|
log.Fatal("unknown deployed version, unable to downgrade")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("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)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deploy.MergeAbraShEnv(app.Recipe, app.Env); err != nil {
|
abraShEnv, err := envfile.ReadAbraShEnvVars(app.Recipe.AbraShPath)
|
||||||
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
for k, v := range abraShEnv {
|
||||||
|
app.Env[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -183,40 +181,15 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
if internal.Chaos {
|
if internal.Chaos {
|
||||||
appPkg.SetChaosVersionLabel(compose, stackName, chosenDowngrade)
|
appPkg.SetChaosVersionLabel(compose, stackName, chosenDowngrade)
|
||||||
}
|
}
|
||||||
|
appPkg.SetUpdateLabel(compose, stackName, app.Env)
|
||||||
// Gather secrets
|
|
||||||
secretInfo, err := deploy.GatherSecretsForDeploy(cl, app, internal.ShowUnchanged)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gather configs
|
|
||||||
configInfo, err := deploy.GatherConfigsForDeploy(cl, app, compose, app.Env, internal.ShowUnchanged)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gather images
|
|
||||||
imageInfo, err := deploy.GatherImagesForDeploy(cl, app, compose, internal.ShowUnchanged)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
deployedVersion := deployMeta.Version
|
|
||||||
if deployMeta.IsChaos {
|
|
||||||
deployedVersion = 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.DeployOverview(
|
||||||
app,
|
app,
|
||||||
deployedVersion,
|
deployMeta.Version,
|
||||||
chosenDowngrade,
|
chosenDowngrade,
|
||||||
"",
|
"",
|
||||||
downgradeWarnMessages,
|
downgradeWarnMessages,
|
||||||
secretInfo,
|
|
||||||
configInfo,
|
|
||||||
imageInfo,
|
|
||||||
); err != nil {
|
); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -226,6 +199,8 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debugf("set waiting timeout to %d second(s)", stack.WaitTimeout)
|
||||||
|
|
||||||
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
|
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -249,7 +224,7 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := app.WriteRecipeVersion(chosenDowngrade, false); err != nil {
|
if err := app.WriteRecipeVersion(chosenDowngrade, false); err != nil {
|
||||||
log.Fatal(i18n.G("writing recipe version failed: %s", err))
|
log.Fatalf("writing recipe version failed: %s", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -260,12 +235,12 @@ func chooseDowngrade(
|
|||||||
deployMeta stack.DeployMeta,
|
deployMeta stack.DeployMeta,
|
||||||
chosenDowngrade *string,
|
chosenDowngrade *string,
|
||||||
) error {
|
) error {
|
||||||
msg := i18n.G("please select a downgrade (version: %s):", deployMeta.Version)
|
msg := fmt.Sprintf("please select a downgrade (version: %s):", deployMeta.Version)
|
||||||
|
|
||||||
if deployMeta.IsChaos {
|
if deployMeta.IsChaos {
|
||||||
chaosVersion := formatter.BoldDirtyDefault(deployMeta.ChaosVersion)
|
chaosVersion := formatter.BoldDirtyDefault(deployMeta.ChaosVersion)
|
||||||
|
|
||||||
msg = i18n.G(
|
msg = fmt.Sprintf(
|
||||||
"please select a downgrade (version: %s, chaos: %s):",
|
"please select a downgrade (version: %s, chaos: %s):",
|
||||||
deployMeta.Version,
|
deployMeta.Version,
|
||||||
chaosVersion,
|
chaosVersion,
|
||||||
@ -287,26 +262,26 @@ func chooseDowngrade(
|
|||||||
// validateDownpgradeVersionArg validates the specific version.
|
// validateDownpgradeVersionArg validates the specific version.
|
||||||
func validateDowngradeVersionArg(
|
func validateDowngradeVersionArg(
|
||||||
specificVersion string,
|
specificVersion string,
|
||||||
app appPkg.App,
|
app app.App,
|
||||||
deployMeta stack.DeployMeta,
|
deployMeta stack.DeployMeta,
|
||||||
) error {
|
) error {
|
||||||
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(i18n.G("current deployment '%s' is not a known version for %s", deployMeta.Version, app.Recipe.Name))
|
return fmt.Errorf("current deployment '%s' is not a known version for %s", deployMeta.Version, app.Recipe.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedSpecificVersion, err := tagcmp.Parse(specificVersion)
|
parsedSpecificVersion, err := tagcmp.Parse(specificVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(i18n.G("'%s' is not a known version for %s", specificVersion, app.Recipe.Name))
|
return fmt.Errorf("'%s' is not a known version for %s", specificVersion, app.Recipe.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
if parsedSpecificVersion.IsGreaterThan(parsedDeployedVersion) &&
|
if parsedSpecificVersion.IsGreaterThan(parsedDeployedVersion) &&
|
||||||
!parsedSpecificVersion.Equals(parsedDeployedVersion) {
|
!parsedSpecificVersion.Equals(parsedDeployedVersion) {
|
||||||
return errors.New(i18n.G("%s is not a downgrade for %s?", deployMeta.Version, specificVersion))
|
return fmt.Errorf("%s is not a downgrade for %s?", deployMeta.Version, specificVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
if parsedSpecificVersion.Equals(parsedDeployedVersion) && !internal.Force {
|
if parsedSpecificVersion.Equals(parsedDeployedVersion) && !internal.Force {
|
||||||
return errors.New(i18n.G("%s is not a downgrade for %s?", deployMeta.Version, specificVersion))
|
return fmt.Errorf("%s is not a downgrade for %s?", deployMeta.Version, specificVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -345,33 +320,24 @@ func ensureDowngradesAvailable(
|
|||||||
func init() {
|
func init() {
|
||||||
AppRollbackCommand.Flags().BoolVarP(
|
AppRollbackCommand.Flags().BoolVarP(
|
||||||
&internal.Force,
|
&internal.Force,
|
||||||
i18n.G("force"),
|
"force",
|
||||||
i18n.G("f"),
|
"f",
|
||||||
false,
|
false,
|
||||||
i18n.G("perform action without further prompt"),
|
"perform action without further prompt",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppRollbackCommand.Flags().BoolVarP(
|
AppRollbackCommand.Flags().BoolVarP(
|
||||||
&internal.NoDomainChecks,
|
&internal.NoDomainChecks,
|
||||||
i18n.G("no-domain-checks"),
|
"no-domain-checks",
|
||||||
i18n.G("D"),
|
"D",
|
||||||
false,
|
false,
|
||||||
i18n.G("disable public DNS checks"),
|
"disable public DNS checks",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppRollbackCommand.Flags().BoolVarP(
|
AppRollbackCommand.Flags().BoolVarP(
|
||||||
&internal.DontWaitConverge,
|
&internal.DontWaitConverge, "no-converge-checks",
|
||||||
i18n.G("no-converge-checks"),
|
"c",
|
||||||
i18n.G("c"),
|
|
||||||
false,
|
false,
|
||||||
i18n.G("disable converge logic checks"),
|
"disable converge logic checks",
|
||||||
)
|
|
||||||
|
|
||||||
AppRollbackCommand.Flags().BoolVarP(
|
|
||||||
&internal.ShowUnchanged,
|
|
||||||
i18n.G("show-unchanged"),
|
|
||||||
i18n.G("U"),
|
|
||||||
false,
|
|
||||||
i18n.G("show all configs & images, including unchanged ones"),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,13 +3,11 @@ package app
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
containerPkg "coopcloud.tech/abra/pkg/container"
|
containerPkg "coopcloud.tech/abra/pkg/container"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"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"
|
||||||
@ -18,24 +16,18 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app run` aliases. use a comma separated list of aliases
|
|
||||||
// with no spaces in between
|
|
||||||
var appRunAliases = i18n.G("r")
|
|
||||||
|
|
||||||
var AppRunCommand = &cobra.Command{
|
var AppRunCommand = &cobra.Command{
|
||||||
// translators: `app run` command
|
Use: "run <domain> <service> <cmd> [[args] [flags] | [flags] -- [args]]",
|
||||||
Use: i18n.G("run <domain> <service> <cmd> [[args] [flags] | [flags] -- [args]]"),
|
Aliases: []string{"r"},
|
||||||
Aliases: strings.Split(appRunAliases, ","),
|
Short: "Run a command inside a service container",
|
||||||
// translators: Short description for `app run` command
|
Example: ` # run <cmd> with args/flags
|
||||||
Short: i18n.G("Run a command inside a service container"),
|
|
||||||
Example: i18n.G(` # run <cmd> with args/flags
|
|
||||||
abra app run 1312.net app -- ls -lha
|
abra app run 1312.net app -- ls -lha
|
||||||
|
|
||||||
# run <cmd> without args/flags
|
# run <cmd> without args/flags
|
||||||
abra app run 1312.net app bash --user nobody
|
abra app run 1312.net app bash --user nobody
|
||||||
|
|
||||||
# run <cmd> with both kinds of args/flags
|
# run <cmd> with both kinds of args/flags
|
||||||
abra app run 1312.net app --user nobody -- ls -lha`),
|
abra app run 1312.net app --user nobody -- ls -lha`,
|
||||||
Args: cobra.MinimumNArgs(3),
|
Args: cobra.MinimumNArgs(3),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -106,17 +98,17 @@ var (
|
|||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
AppRunCommand.Flags().BoolVarP(&noTTY,
|
AppRunCommand.Flags().BoolVarP(&noTTY,
|
||||||
i18n.G("no-tty"),
|
"no-tty",
|
||||||
i18n.G("t"),
|
"t",
|
||||||
false,
|
false,
|
||||||
i18n.G("do not request a TTY"),
|
"do not request a TTY",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppRunCommand.Flags().StringVarP(
|
AppRunCommand.Flags().StringVarP(
|
||||||
&runAsUser,
|
&runAsUser,
|
||||||
i18n.G("user"),
|
"user",
|
||||||
i18n.G("u"),
|
"u",
|
||||||
"",
|
"",
|
||||||
i18n.G("run command as user"),
|
"run command as user",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,11 +2,8 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@ -15,38 +12,30 @@ 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/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/secret"
|
"coopcloud.tech/abra/pkg/secret"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
dockerClient "github.com/docker/docker/client"
|
dockerClient "github.com/docker/docker/client"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app secret generate` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appSecretGenerateAliases = i18n.G("g")
|
|
||||||
|
|
||||||
var AppSecretGenerateCommand = &cobra.Command{
|
var AppSecretGenerateCommand = &cobra.Command{
|
||||||
// translators: `app secret generate` command
|
Use: "generate <domain> [[secret] [version] | --all] [flags]",
|
||||||
Use: i18n.G("generate <domain> [[secret] [version] | --all] [flags]"),
|
Aliases: []string{"g"},
|
||||||
Aliases: strings.Split(appSecretGenerateAliases, ","),
|
Short: "Generate secrets",
|
||||||
// translators: Short description for `app secret generate` command
|
|
||||||
Short: i18n.G("Generate secrets"),
|
|
||||||
Args: cobra.RangeArgs(1, 3),
|
Args: cobra.RangeArgs(1, 3),
|
||||||
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()
|
||||||
case 1:
|
case 1:
|
||||||
app, err := appPkg.Get(args[0])
|
app, err := appPkg.Get(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{i18n.G("autocomplete failed: %s", err)}, cobra.ShellCompDirectiveError
|
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
|
return []string{errMsg}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
return autocomplete.SecretComplete(app.Recipe.Name)
|
return autocomplete.SecretComplete(app.Recipe.Name)
|
||||||
default:
|
default:
|
||||||
@ -61,11 +50,11 @@ var AppSecretGenerateCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(args) <= 2 && !generateAllSecrets {
|
if len(args) <= 2 && !generateAllSecrets {
|
||||||
log.Fatal(i18n.G("missing arguments [secret]/[version] or '--all'"))
|
log.Fatal("missing arguments [secret]/[version] or '--all'")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) > 2 && generateAllSecrets {
|
if len(args) > 2 && generateAllSecrets {
|
||||||
log.Fatal(i18n.G("cannot use '[secret] [version]' and '--all' together"))
|
log.Fatal("cannot use '[secret] [version]' and '--all' together")
|
||||||
}
|
}
|
||||||
|
|
||||||
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
||||||
@ -83,7 +72,7 @@ var AppSecretGenerateCommand = &cobra.Command{
|
|||||||
secretVersion := args[2]
|
secretVersion := args[2]
|
||||||
s, ok := secrets[secretName]
|
s, ok := secrets[secretName]
|
||||||
if !ok {
|
if !ok {
|
||||||
log.Fatal(i18n.G("%s doesn't exist in the env config?", secretName))
|
log.Fatalf("%s doesn't exist in the env config?", secretName)
|
||||||
}
|
}
|
||||||
s.Version = secretVersion
|
s.Version = secretVersion
|
||||||
secrets = map[string]secret.Secret{
|
secrets = map[string]secret.Secret{
|
||||||
@ -110,11 +99,11 @@ var AppSecretGenerateCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(secretVals) == 0 {
|
if len(secretVals) == 0 {
|
||||||
log.Warn(i18n.G("no secrets generated"))
|
log.Warn("no secrets generated")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
headers := []string{i18n.G("NAME"), i18n.G("VALUE")}
|
headers := []string{"NAME", "VALUE"}
|
||||||
table, err := formatter.CreateTable()
|
table, err := formatter.CreateTable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -132,7 +121,7 @@ var AppSecretGenerateCommand = &cobra.Command{
|
|||||||
if internal.MachineReadable {
|
if internal.MachineReadable {
|
||||||
out, err := formatter.ToJSON(headers, rows)
|
out, err := formatter.ToJSON(headers, rows)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(i18n.G("unable to render to JSON: %s", err))
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
}
|
}
|
||||||
fmt.Println(out)
|
fmt.Println(out)
|
||||||
return
|
return
|
||||||
@ -142,53 +131,36 @@ var AppSecretGenerateCommand = &cobra.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Warn(i18n.G(
|
log.Warnf(
|
||||||
"generated secrets %s shown again, please take note of them %s",
|
"generated secrets %s shown again, please take note of them %s",
|
||||||
formatter.BoldStyle.Render(i18n.G("NOT")),
|
formatter.BoldStyle.Render("NOT"),
|
||||||
formatter.BoldStyle.Render(i18n.G("NOW")),
|
formatter.BoldStyle.Render("NOW"),
|
||||||
))
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// translators: `abra app secret insert` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appSecretInsertAliases = i18n.G("i")
|
|
||||||
|
|
||||||
var AppSecretInsertCommand = &cobra.Command{
|
var AppSecretInsertCommand = &cobra.Command{
|
||||||
// translators: `app secret insert` command
|
Use: "insert <domain> <secret> <version> <data> [flags]",
|
||||||
Use: i18n.G("insert <domain> <secret> <version> [<data>] [flags]"),
|
Aliases: []string{"i"},
|
||||||
Aliases: strings.Split(appSecretInsertAliases, ","),
|
Short: "Insert secret",
|
||||||
// translators: Short description for `app secret insert` command
|
Long: `This command inserts a secret into an app environment.
|
||||||
Short: i18n.G("Insert secret"),
|
|
||||||
Long: i18n.G(`This command inserts a secret into an app environment.
|
|
||||||
|
|
||||||
Arbitrary secret insertion is not supported. Secrets that are inserted must
|
This can be useful when you want to manually generate secrets for an app
|
||||||
match those configured in the recipe beforehand.
|
|
||||||
|
|
||||||
This command 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: i18n.G(` # insert regular secret
|
Args: cobra.MinimumNArgs(4),
|
||||||
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
|
|
||||||
|
|
||||||
# insert secret from stdin
|
|
||||||
echo "mmySuperSecret" | abra app secret insert 1312.net my_secret v1`),
|
|
||||||
Args: cobra.MinimumNArgs(3),
|
|
||||||
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()
|
||||||
case 1:
|
case 1:
|
||||||
app, err := appPkg.Get(args[0])
|
app, err := appPkg.Get(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{i18n.G("autocomplete failed: %s", err)}, cobra.ShellCompDirectiveError
|
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
|
return []string{errMsg}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
return autocomplete.SecretComplete(app.Recipe.Name)
|
return autocomplete.SecretComplete(app.Recipe.Name)
|
||||||
default:
|
default:
|
||||||
@ -209,35 +181,12 @@ environment. Typically, you can let Abra generate them for you on app creation
|
|||||||
|
|
||||||
name := args[1]
|
name := args[1]
|
||||||
version := args[2]
|
version := args[2]
|
||||||
data, err := readSecretData(args)
|
data := args[3]
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
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.Fatal(i18n.G("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 {
|
||||||
log.Fatal(i18n.G("reading secret from file: %s", err))
|
log.Fatalf("reading secret from file: %s", err)
|
||||||
}
|
}
|
||||||
data = string(raw)
|
data = string(raw)
|
||||||
}
|
}
|
||||||
@ -247,11 +196,11 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(i18n.G("%s successfully stored on server", secretName))
|
log.Infof("%s successfully stored on server", secretName)
|
||||||
|
|
||||||
if storeInPass {
|
if storeInPass {
|
||||||
if err := secret.PassInsertSecret(data, name, app.Name, app.Server); err != nil {
|
if err := secret.PassInsertSecret(data, name, app.Name, app.Server); err != nil {
|
||||||
@ -261,95 +210,34 @@ environment. Typically, you can let Abra generate them for you on app creation
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
func readSecretData(args []string) (string, error) {
|
|
||||||
if len(args) == 4 {
|
|
||||||
return args[3], nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(args) != 3 {
|
|
||||||
return "", errors.New(i18n.G("need 3 or 4 arguments"))
|
|
||||||
}
|
|
||||||
// First check if data is provided by stdin
|
|
||||||
fi, err := os.Stdin.Stat()
|
|
||||||
if err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
if fi.Mode()&os.ModeNamedPipe != 0 {
|
|
||||||
// Can't insert from stdin and read from file
|
|
||||||
if insertFromFile {
|
|
||||||
return "", errors.New(i18n.G("can not insert from file and read from stdin"))
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug(i18n.G("reading secret data from stdin"))
|
|
||||||
bytes, err := io.ReadAll(os.Stdin)
|
|
||||||
if err != nil {
|
|
||||||
return "", errors.New(i18n.G("reading data from stdin: %s", err))
|
|
||||||
}
|
|
||||||
|
|
||||||
return string(bytes), nil
|
|
||||||
}
|
|
||||||
if internal.NoInput {
|
|
||||||
return "", errors.New(i18n.G("must provide <data> argument if --no-input is passed"))
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug(i18n.G("secret data not provided on command-line or stdin, prompting"))
|
|
||||||
var prompt survey.Prompt
|
|
||||||
if !insertFromFile {
|
|
||||||
prompt = &survey.Password{
|
|
||||||
Message: i18n.G("specify secret value"),
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
prompt = &survey.Input{
|
|
||||||
Message: i18n.G("specify secret file"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var data string
|
|
||||||
if err := survey.AskOne(prompt, &data); err != nil {
|
|
||||||
return "", err
|
|
||||||
}
|
|
||||||
return data, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// secretRm removes a secret.
|
// secretRm removes a secret.
|
||||||
func secretRm(cl *dockerClient.Client, app appPkg.App, secretName, parsed string) error {
|
func secretRm(cl *dockerClient.Client, app appPkg.App, secretName, parsed string) error {
|
||||||
if err := cl.SecretRemove(context.Background(), secretName); err != nil {
|
if err := cl.SecretRemove(context.Background(), secretName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(i18n.G("deleted %s successfully from server", secretName))
|
log.Infof("deleted %s successfully from server", secretName)
|
||||||
|
|
||||||
if removeFromPass {
|
if removeFromPass {
|
||||||
if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil {
|
if err := secret.PassRmSecret(parsed, app.StackName(), app.Server); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(i18n.G("deleted %s successfully from local pass store", secretName))
|
log.Infof("deleted %s successfully from local pass store", secretName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// translators: `abra app secret remove` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appSecretRemoveAliases = i18n.G("rm")
|
|
||||||
|
|
||||||
var AppSecretRmCommand = &cobra.Command{
|
var AppSecretRmCommand = &cobra.Command{
|
||||||
// translators: `app secret remove` command
|
Use: "remove <domain> [[secret] | --all] [flags]",
|
||||||
Use: i18n.G("remove <domain> [[secret] | --all] [flags]"),
|
Aliases: []string{"rm"},
|
||||||
Aliases: strings.Split(appSecretRemoveAliases, ","),
|
Short: "Remove a secret",
|
||||||
// translators: Short description for `app secret remove` command
|
|
||||||
Short: i18n.G("Remove a secret"),
|
|
||||||
Long: i18n.G(`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: i18n.G(" 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,
|
||||||
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()
|
||||||
@ -357,7 +245,8 @@ match those configured in the recipe beforehand.`),
|
|||||||
if !rmAllSecrets {
|
if !rmAllSecrets {
|
||||||
app, err := appPkg.Get(args[0])
|
app, err := appPkg.Get(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{i18n.G("autocomplete failed: %s", err)}, cobra.ShellCompDirectiveError
|
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
|
return []string{errMsg}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
return autocomplete.SecretComplete(app.Recipe.Name)
|
return autocomplete.SecretComplete(app.Recipe.Name)
|
||||||
}
|
}
|
||||||
@ -384,11 +273,11 @@ match those configured in the recipe beforehand.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(args) == 2 && rmAllSecrets {
|
if len(args) == 2 && rmAllSecrets {
|
||||||
log.Fatal(i18n.G("cannot use [secret] and --all/-a together"))
|
log.Fatal("cannot use [secret] and --all/-a together")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) != 2 && !rmAllSecrets {
|
if len(args) != 2 && !rmAllSecrets {
|
||||||
log.Fatal(i18n.G("no secret(s) specified?"))
|
log.Fatal("no secret(s) specified?")
|
||||||
}
|
}
|
||||||
|
|
||||||
cl, err := client.New(app.Server)
|
cl, err := client.New(app.Server)
|
||||||
@ -439,31 +328,24 @@ match those configured in the recipe beforehand.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !match && secretToRm != "" {
|
if !match && secretToRm != "" {
|
||||||
log.Fatal(i18n.G("%s doesn't exist on server?", secretToRm))
|
log.Fatalf("%s doesn't exist on server?", secretToRm)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !match {
|
if !match {
|
||||||
log.Fatal(i18n.G("no secrets to remove?"))
|
log.Fatal("no secrets to remove?")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// translators: `abra app secret ls` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appSecretLsAliases = i18n.G("ls")
|
|
||||||
|
|
||||||
var AppSecretLsCommand = &cobra.Command{
|
var AppSecretLsCommand = &cobra.Command{
|
||||||
// translators: `app secret list` command
|
Use: "list <domain>",
|
||||||
Use: i18n.G("list <domain>"),
|
Aliases: []string{"ls"},
|
||||||
Aliases: strings.Split(appSecretLsAliases, ","),
|
Short: "List all secrets",
|
||||||
// translators: Short description for `app secret list` command
|
|
||||||
Short: i18n.G("List all secrets"),
|
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
args []string,
|
args []string,
|
||||||
toComplete string,
|
toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
) ([]string, cobra.ShellCompDirective) {
|
|
||||||
return autocomplete.AppNameComplete()
|
return autocomplete.AppNameComplete()
|
||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
@ -478,7 +360,7 @@ var AppSecretLsCommand = &cobra.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
headers := []string{i18n.G("NAME"), i18n.G("VERSION"), i18n.G("GENERATED NAME"), i18n.G("CREATED ON SERVER")}
|
headers := []string{"NAME", "VERSION", "GENERATED NAME", "CREATED ON SERVER"}
|
||||||
table, err := formatter.CreateTable()
|
table, err := formatter.CreateTable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -491,10 +373,6 @@ var AppSecretLsCommand = &cobra.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort secrets to ensure reproducible output
|
|
||||||
sort.Slice(secStats, func(i, j int) bool {
|
|
||||||
return secStats[i].LocalName < secStats[j].LocalName
|
|
||||||
})
|
|
||||||
var rows [][]string
|
var rows [][]string
|
||||||
for _, secStat := range secStats {
|
for _, secStat := range secStats {
|
||||||
row := []string{
|
row := []string{
|
||||||
@ -512,7 +390,7 @@ var AppSecretLsCommand = &cobra.Command{
|
|||||||
if internal.MachineReadable {
|
if internal.MachineReadable {
|
||||||
out, err := formatter.ToJSON(headers, rows)
|
out, err := formatter.ToJSON(headers, rows)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(i18n.G("unable to render to JSON: %s", err))
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
}
|
}
|
||||||
fmt.Println(out)
|
fmt.Println(out)
|
||||||
return
|
return
|
||||||
@ -525,16 +403,14 @@ var AppSecretLsCommand = &cobra.Command{
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Warn(i18n.G("no secrets stored for %s", app.Name))
|
log.Warnf("no secrets stored for %s", app.Name)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var AppSecretCommand = &cobra.Command{
|
var AppSecretCommand = &cobra.Command{
|
||||||
// translators: `app secret` command group
|
Use: "secret [cmd] [args] [flags]",
|
||||||
Use: i18n.G("secret [cmd] [args] [flags]"),
|
Aliases: []string{"s"},
|
||||||
Aliases: []string{i18n.G("s")},
|
Short: "Manage app secrets",
|
||||||
// translators: Short description for `app secret` command group
|
|
||||||
Short: i18n.G("Manage app secrets"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -549,105 +425,105 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
AppSecretGenerateCommand.Flags().BoolVarP(
|
AppSecretGenerateCommand.Flags().BoolVarP(
|
||||||
&internal.MachineReadable,
|
&internal.MachineReadable,
|
||||||
i18n.G("machine"),
|
"machine",
|
||||||
i18n.G("m"),
|
"m",
|
||||||
false,
|
false,
|
||||||
i18n.G("print machine-readable output"),
|
"print machine-readable output",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppSecretGenerateCommand.Flags().BoolVarP(
|
AppSecretGenerateCommand.Flags().BoolVarP(
|
||||||
&storeInPass,
|
&storeInPass,
|
||||||
i18n.G("pass"),
|
"pass",
|
||||||
i18n.G("p"),
|
"p",
|
||||||
false,
|
false,
|
||||||
i18n.G("store generated secrets in a local pass store"),
|
"store generated secrets in a local pass store",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppSecretGenerateCommand.Flags().BoolVarP(
|
AppSecretGenerateCommand.Flags().BoolVarP(
|
||||||
&internal.Chaos,
|
&internal.Chaos,
|
||||||
i18n.G("chaos"),
|
"chaos",
|
||||||
i18n.G("C"),
|
"C",
|
||||||
false,
|
false,
|
||||||
i18n.G("ignore uncommitted recipes changes"),
|
"ignore uncommitted recipes changes",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppSecretGenerateCommand.Flags().BoolVarP(
|
AppSecretGenerateCommand.Flags().BoolVarP(
|
||||||
&generateAllSecrets,
|
&generateAllSecrets,
|
||||||
i18n.G("all"),
|
"all",
|
||||||
i18n.GC("a", "app secret generate"),
|
"a",
|
||||||
false,
|
false,
|
||||||
i18n.G("generate all secrets"),
|
"generate all secrets",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppSecretInsertCommand.Flags().BoolVarP(
|
AppSecretInsertCommand.Flags().BoolVarP(
|
||||||
&storeInPass,
|
&storeInPass,
|
||||||
i18n.G("pass"),
|
"pass",
|
||||||
i18n.G("p"),
|
"p",
|
||||||
false,
|
false,
|
||||||
i18n.G("store generated secrets in a local pass store"),
|
"store generated secrets in a local pass store",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppSecretInsertCommand.Flags().BoolVarP(
|
AppSecretInsertCommand.Flags().BoolVarP(
|
||||||
&insertFromFile,
|
&insertFromFile,
|
||||||
i18n.G("file"),
|
"file",
|
||||||
i18n.G("f"),
|
"f",
|
||||||
false,
|
false,
|
||||||
i18n.G("treat input as a file"),
|
"treat input as a file",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppSecretInsertCommand.Flags().BoolVarP(
|
AppSecretInsertCommand.Flags().BoolVarP(
|
||||||
&trimInput,
|
&trimInput,
|
||||||
i18n.G("trim"),
|
"trim",
|
||||||
i18n.G("t"),
|
"t",
|
||||||
false,
|
false,
|
||||||
i18n.G("trim input"),
|
"trim input",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppSecretInsertCommand.Flags().BoolVarP(
|
AppSecretInsertCommand.Flags().BoolVarP(
|
||||||
&internal.Chaos,
|
&internal.Chaos,
|
||||||
i18n.G("chaos"),
|
"chaos",
|
||||||
i18n.G("C"),
|
"C",
|
||||||
false,
|
false,
|
||||||
i18n.G("ignore uncommitted recipes changes"),
|
"ignore uncommitted recipes changes",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppSecretRmCommand.Flags().BoolVarP(
|
AppSecretRmCommand.Flags().BoolVarP(
|
||||||
&rmAllSecrets,
|
&rmAllSecrets,
|
||||||
i18n.G("all"),
|
"all",
|
||||||
i18n.GC("a", "app secret rm"),
|
"a",
|
||||||
false,
|
false,
|
||||||
i18n.G("remove all secrets"),
|
"remove all secrets",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppSecretRmCommand.Flags().BoolVarP(
|
AppSecretRmCommand.Flags().BoolVarP(
|
||||||
&removeFromPass,
|
&removeFromPass,
|
||||||
i18n.G("pass"),
|
"pass",
|
||||||
i18n.G("p"),
|
"p",
|
||||||
false,
|
false,
|
||||||
i18n.G("remove generated secrets from a local pass store"),
|
"remove generated secrets from a local pass store",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppSecretRmCommand.Flags().BoolVarP(
|
AppSecretRmCommand.Flags().BoolVarP(
|
||||||
&internal.Chaos,
|
&internal.Chaos,
|
||||||
i18n.G("chaos"),
|
"chaos",
|
||||||
i18n.G("C"),
|
"C",
|
||||||
false,
|
false,
|
||||||
i18n.G("ignore uncommitted recipes changes"),
|
"ignore uncommitted recipes changes",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppSecretLsCommand.Flags().BoolVarP(
|
AppSecretLsCommand.Flags().BoolVarP(
|
||||||
&internal.Chaos,
|
&internal.Chaos,
|
||||||
i18n.G("chaos"),
|
"chaos",
|
||||||
i18n.G("C"),
|
"C",
|
||||||
false,
|
false,
|
||||||
i18n.G("ignore uncommitted recipes changes"),
|
"ignore uncommitted recipes changes",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppSecretLsCommand.Flags().BoolVarP(
|
AppSecretLsCommand.Flags().BoolVarP(
|
||||||
&internal.MachineReadable,
|
&internal.MachineReadable,
|
||||||
i18n.G("machine"),
|
"machine",
|
||||||
i18n.G("m"),
|
"m",
|
||||||
false,
|
false,
|
||||||
i18n.G("print machine-readable output"),
|
"print machine-readable output",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,7 +9,6 @@ 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/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/service"
|
"coopcloud.tech/abra/pkg/service"
|
||||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
@ -17,16 +16,10 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app services` aliases. use a comma separated list of
|
|
||||||
// aliases with no spaces in between
|
|
||||||
var appServicesAliases = i18n.G("sr")
|
|
||||||
|
|
||||||
var AppServicesCommand = &cobra.Command{
|
var AppServicesCommand = &cobra.Command{
|
||||||
// translators: `app services` command
|
Use: "services <domain> [flags]",
|
||||||
Use: i18n.G("services <domain> [flags]"),
|
Aliases: []string{"sr"},
|
||||||
Aliases: strings.Split(appServicesAliases, ","),
|
Short: "Display all services of an app",
|
||||||
// translators: Short description for `app services` command
|
|
||||||
Short: i18n.G("Display all services of an app"),
|
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -52,7 +45,7 @@ var AppServicesCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !deployMeta.IsDeployed {
|
if !deployMeta.IsDeployed {
|
||||||
log.Fatal(i18n.G("%s is not deployed?", app.Name))
|
log.Fatalf("%s is not deployed?", app.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
filters, err := app.Filters(true, true)
|
filters, err := app.Filters(true, true)
|
||||||
@ -70,7 +63,7 @@ var AppServicesCommand = &cobra.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
headers := []string{i18n.G("SERVICE (SHORT)"), i18n.G("SERVICE (LONG)")}
|
headers := []string{"SERVICE (SHORT)", "SERVICE (LONG)"}
|
||||||
table.Headers(headers...)
|
table.Headers(headers...)
|
||||||
|
|
||||||
var rows [][]string
|
var rows [][]string
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package app
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
@ -11,7 +10,6 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
@ -19,21 +17,16 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app undeploy` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appUndeployAliases = i18n.G("un")
|
|
||||||
|
|
||||||
var AppUndeployCommand = &cobra.Command{
|
var AppUndeployCommand = &cobra.Command{
|
||||||
// translators: `app undeploy` command
|
Use: "undeploy <domain> [flags]",
|
||||||
Use: i18n.G("undeploy <domain> [flags]"),
|
Aliases: []string{"un"},
|
||||||
// translators: Short description for `app undeploy` command
|
Short: "Undeploy an app",
|
||||||
Aliases: strings.Split(appUndeployAliases, ","),
|
Long: `This does not destroy any application data.
|
||||||
Long: i18n.G(`This does not destroy any application data.
|
|
||||||
|
|
||||||
However, you should remain vigilant, as your swarm installation will consider
|
However, you should remain vigilant, as your swarm installation will consider
|
||||||
any previously attached volumes as eligible for pruning once undeployed.
|
any previously attached volumes as eligible for pruning once undeployed.
|
||||||
|
|
||||||
Passing "--prune/-p" does not remove those volumes.`),
|
Passing "--prune/-p" does not remove those volumes.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -45,16 +38,12 @@ 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("checking whether %s is already deployed", stackName))
|
log.Debugf("checking whether %s is already deployed", stackName)
|
||||||
|
|
||||||
deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName)
|
deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -62,23 +51,15 @@ Passing "--prune/-p" does not remove those volumes.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !deployMeta.IsDeployed {
|
if !deployMeta.IsDeployed {
|
||||||
log.Fatal(i18n.G("%s is not deployed?", app.Name))
|
log.Fatalf("%s is not deployed?", app.Name)
|
||||||
}
|
|
||||||
|
|
||||||
version := deployMeta.Version
|
|
||||||
if deployMeta.IsChaos {
|
|
||||||
version = deployMeta.ChaosVersion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := internal.DeployOverview(
|
if err := internal.DeployOverview(
|
||||||
app,
|
app,
|
||||||
version,
|
deployMeta.Version,
|
||||||
config.MISSING_DEFAULT,
|
config.NO_DOMAIN_DEFAULT,
|
||||||
"",
|
"",
|
||||||
nil,
|
nil,
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
nil,
|
|
||||||
); err != nil {
|
); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -99,6 +80,8 @@ Passing "--prune/-p" does not remove those volumes.`),
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Info("initialising undeploy")
|
||||||
|
|
||||||
rmOpts := stack.Remove{
|
rmOpts := stack.Remove{
|
||||||
Namespaces: []string{stackName},
|
Namespaces: []string{stackName},
|
||||||
Detach: false,
|
Detach: false,
|
||||||
@ -113,10 +96,10 @@ Passing "--prune/-p" does not remove those volumes.`),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(i18n.G("undeploy succeeded 🟢"))
|
log.Info("undeploy succeeded 🟢")
|
||||||
|
|
||||||
if err := app.WriteRecipeVersion(version, false); err != nil {
|
if err := app.WriteRecipeVersion(deployMeta.Version, false); err != nil {
|
||||||
log.Fatal(i18n.G("writing recipe version failed: %s", err))
|
log.Fatalf("writing recipe version failed: %s", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -137,14 +120,14 @@ func pruneApp(cl *dockerClient.Client, app appPkg.App) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cntSpaceReclaimed := formatter.ByteCountSI(cr.SpaceReclaimed)
|
cntSpaceReclaimed := formatter.ByteCountSI(cr.SpaceReclaimed)
|
||||||
log.Info(i18n.G("containers pruned: %d; space reclaimed: %s", len(cr.ContainersDeleted), cntSpaceReclaimed))
|
log.Infof("containers pruned: %d; space reclaimed: %s", len(cr.ContainersDeleted), cntSpaceReclaimed)
|
||||||
|
|
||||||
nr, err := cl.NetworksPrune(ctx, pruneFilters)
|
nr, err := cl.NetworksPrune(ctx, pruneFilters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(i18n.G("networks pruned: %d", len(nr.NetworksDeleted)))
|
log.Infof("networks pruned: %d", len(nr.NetworksDeleted))
|
||||||
|
|
||||||
ir, err := cl.ImagesPrune(ctx, pruneFilters)
|
ir, err := cl.ImagesPrune(ctx, pruneFilters)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -152,7 +135,7 @@ func pruneApp(cl *dockerClient.Client, app appPkg.App) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
imgSpaceReclaimed := formatter.ByteCountSI(ir.SpaceReclaimed)
|
imgSpaceReclaimed := formatter.ByteCountSI(ir.SpaceReclaimed)
|
||||||
log.Info(i18n.G("images pruned: %d; space reclaimed: %s", len(ir.ImagesDeleted), imgSpaceReclaimed))
|
log.Infof("images pruned: %d; space reclaimed: %s", len(ir.ImagesDeleted), imgSpaceReclaimed)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -164,9 +147,9 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
AppUndeployCommand.Flags().BoolVarP(
|
AppUndeployCommand.Flags().BoolVarP(
|
||||||
&prune,
|
&prune,
|
||||||
i18n.G("prune"),
|
"prune",
|
||||||
i18n.G("p"),
|
"p",
|
||||||
false,
|
false,
|
||||||
i18n.G("prune unused containers, networks, and dangling images"),
|
"prune unused containers, networks, and dangling images",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,18 +2,17 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"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/deploy"
|
"coopcloud.tech/abra/pkg/envfile"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"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"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
@ -24,17 +23,11 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app upgrade` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appUpgradeAliases = i18n.G("up")
|
|
||||||
|
|
||||||
var AppUpgradeCommand = &cobra.Command{
|
var AppUpgradeCommand = &cobra.Command{
|
||||||
// translators: `app upgrade` command
|
Use: "upgrade <domain> [version] [flags]",
|
||||||
Use: i18n.G("upgrade <domain> [version] [flags]"),
|
Aliases: []string{"up"},
|
||||||
Aliases: strings.Split(appUpgradeAliases, ","),
|
Short: "Upgrade an app",
|
||||||
// translators: Short description for `app upgrade` command
|
Long: `Upgrade an app.
|
||||||
Short: i18n.G("Upgrade an app"),
|
|
||||||
Long: i18n.G(`Upgrade an app.
|
|
||||||
|
|
||||||
Unlike "abra app deploy", chaos operations are not supported here. Only recipe
|
Unlike "abra app deploy", chaos operations are not supported here. Only recipe
|
||||||
versions are supported values for "[version]".
|
versions are supported values for "[version]".
|
||||||
@ -47,7 +40,7 @@ are available. The live deployment version is the "source of truth" in this
|
|||||||
case. The stored .env version is not consulted.
|
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. See "abra app backup" for more.`,
|
||||||
Args: cobra.RangeArgs(1, 2),
|
Args: cobra.RangeArgs(1, 2),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -60,7 +53,8 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
case 1:
|
case 1:
|
||||||
app, err := appPkg.Get(args[0])
|
app, err := appPkg.Get(args[0])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return []string{i18n.G("autocomplete failed: %s", err)}, cobra.ShellCompDirectiveError
|
errMsg := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
|
return []string{errMsg}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
return autocomplete.RecipeVersionComplete(app.Recipe.Name)
|
return autocomplete.RecipeVersionComplete(app.Recipe.Name)
|
||||||
default:
|
default:
|
||||||
@ -123,13 +117,13 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
if deployMeta.Version != config.UNKNOWN_DEFAULT && chosenUpgrade == "" {
|
if deployMeta.Version != config.UNKNOWN_DEFAULT && chosenUpgrade == "" {
|
||||||
upgradeAvailable, err := ensureUpgradesAvailable(app, versions, &availableUpgrades, deployMeta)
|
upgradeAvailable, err := ensureUpgradesAvailable(versions, &availableUpgrades, deployMeta)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !upgradeAvailable {
|
if !upgradeAvailable {
|
||||||
log.Info(i18n.G("no available upgrades"))
|
log.Info("no available upgrades")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,10 +145,10 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
if chosenUpgrade == "" {
|
if chosenUpgrade == "" {
|
||||||
log.Fatal(i18n.G("unknown deployed version, unable to upgrade"))
|
log.Fatal("unknown deployed version, unable to upgrade")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("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
|
// Get the release notes before checking out the new version in the
|
||||||
// recipe. This enables us to get release notes, that were added after
|
// recipe. This enables us to get release notes, that were added after
|
||||||
@ -167,9 +161,13 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := deploy.MergeAbraShEnv(app.Recipe, app.Env); err != nil {
|
abraShEnv, err := envfile.ReadAbraShEnvVars(app.Recipe.AbraShPath)
|
||||||
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
for k, v := range abraShEnv {
|
||||||
|
app.Env[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -196,6 +194,7 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
if internal.Chaos {
|
if internal.Chaos {
|
||||||
appPkg.SetChaosVersionLabel(compose, stackName, chosenUpgrade)
|
appPkg.SetChaosVersionLabel(compose, stackName, chosenUpgrade)
|
||||||
}
|
}
|
||||||
|
appPkg.SetUpdateLabel(compose, stackName, app.Env)
|
||||||
|
|
||||||
envVars, err := appPkg.CheckEnv(app)
|
envVars, err := appPkg.CheckEnv(app)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -205,29 +204,11 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
for _, envVar := range envVars {
|
for _, envVar := range envVars {
|
||||||
if !envVar.Present {
|
if !envVar.Present {
|
||||||
upgradeWarnMessages = append(upgradeWarnMessages,
|
upgradeWarnMessages = append(upgradeWarnMessages,
|
||||||
i18n.G("%s missing from %s.env", envVar.Name, app.Domain),
|
fmt.Sprintf("%s missing from %s.env", envVar.Name, app.Domain),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Gather secrets
|
|
||||||
secretInfo, err := deploy.GatherSecretsForDeploy(cl, app, internal.ShowUnchanged)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gather configs
|
|
||||||
configInfo, err := deploy.GatherConfigsForDeploy(cl, app, compose, app.Env, internal.ShowUnchanged)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Gather images
|
|
||||||
imageInfo, err := deploy.GatherImagesForDeploy(cl, app, compose, internal.ShowUnchanged)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if showReleaseNotes {
|
if showReleaseNotes {
|
||||||
fmt.Print(upgradeReleaseNotes)
|
fmt.Print(upgradeReleaseNotes)
|
||||||
return
|
return
|
||||||
@ -240,20 +221,12 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
deployedVersion := deployMeta.Version
|
|
||||||
if deployMeta.IsChaos {
|
|
||||||
deployedVersion = deployMeta.ChaosVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := internal.DeployOverview(
|
if err := internal.DeployOverview(
|
||||||
app,
|
app,
|
||||||
deployedVersion,
|
deployMeta.Version,
|
||||||
chosenUpgrade,
|
chosenUpgrade,
|
||||||
upgradeReleaseNotes,
|
upgradeReleaseNotes,
|
||||||
upgradeWarnMessages,
|
upgradeWarnMessages,
|
||||||
secretInfo,
|
|
||||||
configInfo,
|
|
||||||
imageInfo,
|
|
||||||
); err != nil {
|
); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -263,6 +236,8 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Debugf("set waiting timeout to %d second(s)", stack.WaitTimeout)
|
||||||
|
|
||||||
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
|
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -287,15 +262,15 @@ beforehand. See "abra app backup" for more.`),
|
|||||||
|
|
||||||
postDeployCmds, ok := app.Env["POST_UPGRADE_CMDS"]
|
postDeployCmds, ok := app.Env["POST_UPGRADE_CMDS"]
|
||||||
if ok && !internal.DontWaitConverge {
|
if ok && !internal.DontWaitConverge {
|
||||||
log.Debug(i18n.G("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.Fatal(i18n.G("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 {
|
if err := app.WriteRecipeVersion(chosenUpgrade, false); err != nil {
|
||||||
log.Fatal(i18n.G("writing recipe version failed: %s", err))
|
log.Fatalf("writing recipe version failed: %s", err)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@ -306,12 +281,12 @@ func chooseUpgrade(
|
|||||||
deployMeta stack.DeployMeta,
|
deployMeta stack.DeployMeta,
|
||||||
chosenUpgrade *string,
|
chosenUpgrade *string,
|
||||||
) error {
|
) error {
|
||||||
msg := i18n.G("please select an upgrade (version: %s):", deployMeta.Version)
|
msg := fmt.Sprintf("please select an upgrade (version: %s):", deployMeta.Version)
|
||||||
|
|
||||||
if deployMeta.IsChaos {
|
if deployMeta.IsChaos {
|
||||||
chaosVersion := formatter.BoldDirtyDefault(deployMeta.ChaosVersion)
|
chaosVersion := formatter.BoldDirtyDefault(deployMeta.ChaosVersion)
|
||||||
|
|
||||||
msg = i18n.G(
|
msg = fmt.Sprintf(
|
||||||
"please select an upgrade (version: %s, chaos: %s):",
|
"please select an upgrade (version: %s, chaos: %s):",
|
||||||
deployMeta.Version,
|
deployMeta.Version,
|
||||||
chaosVersion,
|
chaosVersion,
|
||||||
@ -331,7 +306,7 @@ func chooseUpgrade(
|
|||||||
}
|
}
|
||||||
|
|
||||||
func getReleaseNotes(
|
func getReleaseNotes(
|
||||||
app appPkg.App,
|
app app.App,
|
||||||
versions []string,
|
versions []string,
|
||||||
chosenUpgrade string,
|
chosenUpgrade string,
|
||||||
deployMeta stack.DeployMeta,
|
deployMeta stack.DeployMeta,
|
||||||
@ -339,23 +314,23 @@ func getReleaseNotes(
|
|||||||
) error {
|
) error {
|
||||||
parsedChosenUpgrade, err := tagcmp.Parse(chosenUpgrade)
|
parsedChosenUpgrade, err := tagcmp.Parse(chosenUpgrade)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(i18n.G("parsing chosen upgrade version failed: %s", err))
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(i18n.G("parsing deployment version failed: %s", err))
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, version := range internal.SortVersionsDesc(versions) {
|
for _, version := range internal.SortVersionsDesc(versions) {
|
||||||
parsedVersion, err := tagcmp.Parse(version)
|
parsedVersion, err := tagcmp.Parse(version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(i18n.G("parsing recipe version failed: %s", err))
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if parsedVersion.IsGreaterThan(parsedDeployedVersion) &&
|
if parsedVersion.IsGreaterThan(parsedDeployedVersion) &&
|
||||||
parsedVersion.IsLessThan(parsedChosenUpgrade) {
|
parsedVersion.IsLessThan(parsedChosenUpgrade) {
|
||||||
note, err := app.Recipe.GetReleaseNotes(version, app.Domain)
|
note, err := app.Recipe.GetReleaseNotes(version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -376,20 +351,19 @@ func getReleaseNotes(
|
|||||||
|
|
||||||
// ensureUpgradesAvailable ensures that there are available upgrades.
|
// ensureUpgradesAvailable ensures that there are available upgrades.
|
||||||
func ensureUpgradesAvailable(
|
func ensureUpgradesAvailable(
|
||||||
app appPkg.App,
|
|
||||||
versions []string,
|
versions []string,
|
||||||
availableUpgrades *[]string,
|
availableUpgrades *[]string,
|
||||||
deployMeta stack.DeployMeta,
|
deployMeta stack.DeployMeta,
|
||||||
) (bool, error) {
|
) (bool, error) {
|
||||||
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.New(i18n.G("parsing deployed version failed: %s", err))
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, version := range versions {
|
for _, version := range versions {
|
||||||
parsedVersion, err := tagcmp.Parse(version)
|
parsedVersion, err := tagcmp.Parse(version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.New(i18n.G("parsing recipe version failed: %s", err))
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if parsedVersion.IsGreaterThan(parsedDeployedVersion) &&
|
if parsedVersion.IsGreaterThan(parsedDeployedVersion) &&
|
||||||
@ -408,26 +382,26 @@ func ensureUpgradesAvailable(
|
|||||||
// validateUpgradeVersionArg validates the specific version.
|
// validateUpgradeVersionArg validates the specific version.
|
||||||
func validateUpgradeVersionArg(
|
func validateUpgradeVersionArg(
|
||||||
specificVersion string,
|
specificVersion string,
|
||||||
app appPkg.App,
|
app app.App,
|
||||||
deployMeta stack.DeployMeta,
|
deployMeta stack.DeployMeta,
|
||||||
) error {
|
) error {
|
||||||
parsedSpecificVersion, err := tagcmp.Parse(specificVersion)
|
parsedSpecificVersion, err := tagcmp.Parse(specificVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(i18n.G("'%s' is not a known version for %s", specificVersion, app.Recipe.Name))
|
return fmt.Errorf("'%s' is not a known version for %s", specificVersion, app.Recipe.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
parsedDeployedVersion, err := tagcmp.Parse(deployMeta.Version)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(i18n.G("'%s' is not a known version", deployMeta.Version))
|
return fmt.Errorf("'%s' is not a known version", deployMeta.Version)
|
||||||
}
|
}
|
||||||
|
|
||||||
if parsedSpecificVersion.IsLessThan(parsedDeployedVersion) &&
|
if parsedSpecificVersion.IsLessThan(parsedDeployedVersion) &&
|
||||||
!parsedSpecificVersion.Equals(parsedDeployedVersion) {
|
!parsedSpecificVersion.Equals(parsedDeployedVersion) {
|
||||||
return errors.New(i18n.G("%s is not an upgrade for %s?", deployMeta.Version, specificVersion))
|
return fmt.Errorf("%s is not an upgrade for %s?", deployMeta.Version, specificVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
if parsedSpecificVersion.Equals(parsedDeployedVersion) && !internal.Force {
|
if parsedSpecificVersion.Equals(parsedDeployedVersion) && !internal.Force {
|
||||||
return errors.New(i18n.G("%s is not an upgrade for %s?", deployMeta.Version, specificVersion))
|
return fmt.Errorf("%s is not an upgrade for %s?", deployMeta.Version, specificVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -435,8 +409,8 @@ func validateUpgradeVersionArg(
|
|||||||
|
|
||||||
// ensureDeployed ensures the app is deployed and if so, returns deployment
|
// ensureDeployed ensures the app is deployed and if so, returns deployment
|
||||||
// meta info.
|
// meta info.
|
||||||
func ensureDeployed(cl *dockerClient.Client, app appPkg.App) (stack.DeployMeta, error) {
|
func ensureDeployed(cl *dockerClient.Client, app app.App) (stack.DeployMeta, error) {
|
||||||
log.Debug(i18n.G("checking whether %s is already deployed", app.StackName()))
|
log.Debugf("checking whether %s is already deployed", app.StackName())
|
||||||
|
|
||||||
deployMeta, err := stack.IsDeployed(context.Background(), cl, app.StackName())
|
deployMeta, err := stack.IsDeployed(context.Background(), cl, app.StackName())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -444,7 +418,7 @@ func ensureDeployed(cl *dockerClient.Client, app appPkg.App) (stack.DeployMeta,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !deployMeta.IsDeployed {
|
if !deployMeta.IsDeployed {
|
||||||
return stack.DeployMeta{}, errors.New(i18n.G("%s is not deployed?", app.Name))
|
return stack.DeployMeta{}, fmt.Errorf("%s is not deployed?", app.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return deployMeta, nil
|
return deployMeta, nil
|
||||||
@ -455,41 +429,32 @@ var showReleaseNotes bool
|
|||||||
func init() {
|
func init() {
|
||||||
AppUpgradeCommand.Flags().BoolVarP(
|
AppUpgradeCommand.Flags().BoolVarP(
|
||||||
&internal.Force,
|
&internal.Force,
|
||||||
i18n.G("force"),
|
"force",
|
||||||
i18n.G("f"),
|
"f",
|
||||||
false,
|
false,
|
||||||
i18n.G("perform action without further prompt"),
|
"perform action without further prompt",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppUpgradeCommand.Flags().BoolVarP(
|
AppUpgradeCommand.Flags().BoolVarP(
|
||||||
&internal.NoDomainChecks,
|
&internal.NoDomainChecks,
|
||||||
i18n.G("no-domain-checks"),
|
"no-domain-checks",
|
||||||
i18n.G("D"),
|
"D",
|
||||||
false,
|
false,
|
||||||
i18n.G("disable public DNS checks"),
|
"disable public DNS checks",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppUpgradeCommand.Flags().BoolVarP(
|
AppUpgradeCommand.Flags().BoolVarP(
|
||||||
&internal.DontWaitConverge,
|
&internal.DontWaitConverge, "no-converge-checks",
|
||||||
i18n.G("no-converge-checks"),
|
"c",
|
||||||
i18n.G("c"),
|
|
||||||
false,
|
false,
|
||||||
i18n.G("disable converge logic checks"),
|
"disable converge logic checks",
|
||||||
)
|
)
|
||||||
|
|
||||||
AppUpgradeCommand.Flags().BoolVarP(
|
AppUpgradeCommand.Flags().BoolVarP(
|
||||||
&showReleaseNotes,
|
&showReleaseNotes,
|
||||||
i18n.G("releasenotes"),
|
"releasenotes",
|
||||||
i18n.G("r"),
|
"r",
|
||||||
false,
|
false,
|
||||||
i18n.G("only show release notes"),
|
"only show release notes",
|
||||||
)
|
|
||||||
|
|
||||||
AppUpgradeCommand.Flags().BoolVarP(
|
|
||||||
&internal.ShowUnchanged,
|
|
||||||
i18n.G("show-unchanged"),
|
|
||||||
i18n.G("U"),
|
|
||||||
false,
|
|
||||||
i18n.G("show all configs & images, including unchanged ones"),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,30 +2,21 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra app volume list` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appVolumeListAliases = i18n.G("ls")
|
|
||||||
|
|
||||||
var AppVolumeListCommand = &cobra.Command{
|
var AppVolumeListCommand = &cobra.Command{
|
||||||
// translators: `app volume list` command
|
Use: "list <domain> [flags]",
|
||||||
Use: i18n.G("list <domain> [flags]"),
|
Aliases: []string{"ls"},
|
||||||
Aliases: strings.Split(appVolumeListAliases, ","),
|
Short: "List volumes associated with an app",
|
||||||
// translators: Short description for `app list` command
|
|
||||||
Short: i18n.G("List volumes associated with an app"),
|
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -51,7 +42,7 @@ var AppVolumeListCommand = &cobra.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
headers := []string{i18n.G("NAME"), i18n.G("ON SERVER")}
|
headers := []string{"NAME", "ON SERVER"}
|
||||||
|
|
||||||
table, err := formatter.CreateTable()
|
table, err := formatter.CreateTable()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -75,20 +66,14 @@ var AppVolumeListCommand = &cobra.Command{
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Warn(i18n.G("no volumes created for %s", app.Name))
|
log.Warnf("no volumes created for %s", app.Name)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// translators: `abra app volume remove` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appVolumeRemoveAliases = i18n.G("rm")
|
|
||||||
|
|
||||||
var AppVolumeRemoveCommand = &cobra.Command{
|
var AppVolumeRemoveCommand = &cobra.Command{
|
||||||
// translators: `app volume remove` command
|
Use: "remove <domain> [flags]",
|
||||||
Use: i18n.G("remove <domain> [volume] [flags]"),
|
Short: "Remove volume(s) associated with an app",
|
||||||
// translators: Short description for `app volume remove` command
|
Long: `Remove volumes associated with an app.
|
||||||
Short: i18n.G("Remove volume(s) associated with an app"),
|
|
||||||
Long: i18n.G(`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 <domain>" for more.
|
||||||
@ -97,13 +82,8 @@ 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: i18n.G(` # delete volumes interactively
|
Aliases: []string{"rm"},
|
||||||
abra app volume rm 1312.net
|
|
||||||
|
|
||||||
# delete specific volume
|
|
||||||
abra app volume rm 1312.net my_volume`),
|
|
||||||
Aliases: strings.Split(appVolumeRemoveAliases, ","),
|
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -114,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,7 +105,7 @@ Passing "--force/-f" will select all volumes for removal. Be careful.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
if deployMeta.IsDeployed {
|
if deployMeta.IsDeployed {
|
||||||
log.Fatal(i18n.G("%s is still deployed. Run \"abra app undeploy %s\"", app.Name, app.Name))
|
log.Fatalf("%s is still deployed. Run \"abra app undeploy %s\"", app.Name, app.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
filters, err := app.Filters(false, true)
|
filters, err := app.Filters(false, true)
|
||||||
@ -144,35 +119,11 @@ 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.Fatal(i18n.G("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.Fatal(i18n.G("removing volume %s failed: %s", volumeToDelete, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info(i18n.G("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{
|
||||||
Message: i18n.G("which volumes do you want to remove?"),
|
Message: "which volumes do you want to remove?",
|
||||||
Help: i18n.G("'x' indicates selected, enter / return to confirm, ctrl-c to exit, vim mode is enabled"),
|
Help: "'x' indicates selected, enter / return to confirm, ctrl-c to exit, vim mode is enabled",
|
||||||
VimMode: true,
|
VimMode: true,
|
||||||
Options: volumeNames,
|
Options: volumeNames,
|
||||||
Default: volumeNames,
|
Default: volumeNames,
|
||||||
@ -189,33 +140,28 @@ Passing "--force/-f" will select all volumes for removal. Be careful.`),
|
|||||||
if len(volumesToRemove) > 0 {
|
if len(volumesToRemove) > 0 {
|
||||||
err := client.RemoveVolumes(cl, context.Background(), volumesToRemove, internal.Force, 5)
|
err := client.RemoveVolumes(cl, context.Background(), volumesToRemove, internal.Force, 5)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(i18n.G("removing volumes failed: %s", err))
|
log.Fatalf("removing volumes failed: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(i18n.G("%d volumes removed successfully", len(volumesToRemove)))
|
log.Infof("%d volumes removed successfully", len(volumesToRemove))
|
||||||
} else {
|
} else {
|
||||||
log.Info(i18n.G("no volumes removed"))
|
log.Info("no volumes removed")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// translators: `abra app volume` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appVolumeAliases = i18n.G("vl")
|
|
||||||
|
|
||||||
var AppVolumeCommand = &cobra.Command{
|
var AppVolumeCommand = &cobra.Command{
|
||||||
// translators: `app volume` command group
|
Use: "volume [cmd] [args] [flags]",
|
||||||
Use: i18n.G("volume [cmd] [args] [flags]"),
|
Aliases: []string{"vl"},
|
||||||
Aliases: strings.Split(appVolumeAliases, ","),
|
Short: "Manage app volumes",
|
||||||
Short: i18n.G("Manage app volumes"),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
AppVolumeRemoveCommand.Flags().BoolVarP(
|
AppVolumeRemoveCommand.Flags().BoolVarP(
|
||||||
&internal.Force,
|
&internal.Force,
|
||||||
i18n.G("force"),
|
"force",
|
||||||
i18n.G("f"),
|
"f",
|
||||||
false,
|
false,
|
||||||
i18n.G("perform action without further prompt"),
|
"perform action without further prompt",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,10 +4,8 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
|
||||||
"path"
|
"path"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
@ -15,48 +13,17 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/config"
|
"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/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra catalogue sync` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appCatalogueSyncAliases = i18n.G("s")
|
|
||||||
|
|
||||||
var CatalogueSyncCommand = &cobra.Command{
|
|
||||||
// translators: `catalogue sync` command
|
|
||||||
Use: i18n.G("sync [flags]"),
|
|
||||||
Aliases: strings.Split(appCatalogueSyncAliases, ","),
|
|
||||||
// translators: Short description for `catalogue sync` command
|
|
||||||
Short: i18n.G("Sync recipe catalogue for latest changes"),
|
|
||||||
Args: cobra.NoArgs,
|
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
|
||||||
if err := catalogue.EnsureCatalogue(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := catalogue.EnsureUpToDate(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Info(i18n.G("catalogue successfully synced"))
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
// translators: `abra catalogue` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var appCatalogueAliases = i18n.G("g")
|
|
||||||
|
|
||||||
var CatalogueGenerateCommand = &cobra.Command{
|
var CatalogueGenerateCommand = &cobra.Command{
|
||||||
// translators: `catalogue generate` command
|
Use: "generate [recipe] [flags]",
|
||||||
Use: i18n.G("generate [recipe] [flags]"),
|
Aliases: []string{"g"},
|
||||||
Aliases: strings.Split(appCatalogueAliases, ","),
|
Short: "Generate the recipe catalogue",
|
||||||
// translators: Short description for `catalogue generate` command
|
Long: `Generate a new copy of the recipe catalogue.
|
||||||
Short: i18n.G("Generate the recipe catalogue"),
|
|
||||||
Long: i18n.G(`Generate a new copy of the recipe catalogue.
|
|
||||||
|
|
||||||
N.B. this command **will** wipe local unstaged changes from your local recipes
|
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
|
if present. "--chaos/-C" on this command refers to the catalogue repository
|
||||||
@ -70,17 +37,9 @@ 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 "docker login" and Abra will automatically
|
||||||
use those details.
|
use those details.
|
||||||
|
|
||||||
Publish your new release to git.coopcloud.tech with "--publish/-p". This
|
Push your new release to git.coopcloud.tech with "--publish/-p". This requires
|
||||||
requires that you have permission to git push to these repositories and have
|
that you have permission to git push to these repositories and have your SSH
|
||||||
your SSH keys configured on your account. Enable ssh-agent and make sure to add
|
keys configured on your account.`,
|
||||||
your private key and enter your passphrase beforehand.
|
|
||||||
|
|
||||||
eval ` + "`ssh-agent`" + `
|
|
||||||
ssh-add ~/.ssh/<my-ssh-private-key-for-git-coopcloud-tech>`),
|
|
||||||
Example: ` # publish catalogue
|
|
||||||
eval ` + "`ssh-agent`" + `
|
|
||||||
ssh-add ~/.ssh/id_ed25519
|
|
||||||
abra catalogue generate -p`,
|
|
||||||
Args: cobra.RangeArgs(0, 1),
|
Args: cobra.RangeArgs(0, 1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -94,10 +53,6 @@ your private key and enter your passphrase beforehand.
|
|||||||
recipeName = args[0]
|
recipeName = args[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.Getenv("SSH_AUTH_SOCK") == "" {
|
|
||||||
log.Warn(i18n.G("ssh: SSH_AUTH_SOCK missing, --publish/-p will fail. see \"abra catalogue generate --help\""))
|
|
||||||
}
|
|
||||||
|
|
||||||
if recipeName != "" {
|
if recipeName != "" {
|
||||||
internal.ValidateRecipe(args, cmd.Name())
|
internal.ValidateRecipe(args, cmd.Name())
|
||||||
}
|
}
|
||||||
@ -130,7 +85,7 @@ your private key and enter your passphrase beforehand.
|
|||||||
|
|
||||||
var warnings []string
|
var warnings []string
|
||||||
catl := make(recipe.RecipeCatalogue)
|
catl := make(recipe.RecipeCatalogue)
|
||||||
catlBar := formatter.CreateProgressbar(barLength, i18n.G("collecting catalogue metadata"))
|
catlBar := formatter.CreateProgressbar(barLength, "collecting catalogue metadata")
|
||||||
for _, recipeMeta := range repos {
|
for _, recipeMeta := range repos {
|
||||||
if recipeName != "" && recipeName != recipeMeta.Name {
|
if recipeName != "" && recipeName != recipeMeta.Name {
|
||||||
if !internal.Debug {
|
if !internal.Debug {
|
||||||
@ -216,7 +171,7 @@ your private key and enter your passphrase beforehand.
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(i18n.G("generated recipe catalogue: %s", config.RECIPES_JSON))
|
log.Infof("generated recipe catalogue: %s", config.RECIPES_JSON)
|
||||||
|
|
||||||
cataloguePath := path.Join(config.ABRA_DIR, "catalogue")
|
cataloguePath := path.Join(config.ABRA_DIR, "catalogue")
|
||||||
if publishChanges {
|
if publishChanges {
|
||||||
@ -228,11 +183,11 @@ your private key and enter your passphrase beforehand.
|
|||||||
|
|
||||||
if isClean {
|
if isClean {
|
||||||
if !internal.Dry {
|
if !internal.Dry {
|
||||||
log.Fatal(i18n.G("no changes discovered in %s, nothing to publish?", cataloguePath))
|
log.Fatalf("no changes discovered in %s, nothing to publish?", cataloguePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
msg := i18n.G("chore: publish new catalogue release changes")
|
msg := "chore: publish new catalogue release changes"
|
||||||
if err := gitPkg.Commit(cataloguePath, msg, internal.Dry); err != nil {
|
if err := gitPkg.Commit(cataloguePath, msg, internal.Dry); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -264,21 +219,19 @@ your private key and enter your passphrase beforehand.
|
|||||||
|
|
||||||
if !internal.Dry && publishChanges {
|
if !internal.Dry && publishChanges {
|
||||||
url := fmt.Sprintf("%s/%s/commit/%s", config.REPOS_BASE_URL, config.CATALOGUE_JSON_REPO_NAME, head.Hash())
|
url := fmt.Sprintf("%s/%s/commit/%s", config.REPOS_BASE_URL, config.CATALOGUE_JSON_REPO_NAME, head.Hash())
|
||||||
log.Info(i18n.G("new changes published: %s", url))
|
log.Infof("new changes published: %s", url)
|
||||||
}
|
}
|
||||||
|
|
||||||
if internal.Dry {
|
if internal.Dry {
|
||||||
log.Info(i18n.G("dry run: no changes published"))
|
log.Info("dry run: no changes published")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// CatalogueCommand defines the `abra catalogue` command and sub-commands.
|
// CatalogueCommand defines the `abra catalogue` command and sub-commands.
|
||||||
var CatalogueCommand = &cobra.Command{
|
var CatalogueCommand = &cobra.Command{
|
||||||
// translators: `catalogue` command group
|
Use: "catalogue [cmd] [args] [flags]",
|
||||||
Use: i18n.G("catalogue [cmd] [args] [flags]"),
|
Short: "Manage the recipe catalogue",
|
||||||
// translators: Short description for `catalogue` command group
|
|
||||||
Short: i18n.G("Manage the recipe catalogue"),
|
|
||||||
Aliases: []string{"c"},
|
Aliases: []string{"c"},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -290,33 +243,33 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
CatalogueGenerateCommand.Flags().BoolVarP(
|
CatalogueGenerateCommand.Flags().BoolVarP(
|
||||||
&publishChanges,
|
&publishChanges,
|
||||||
i18n.G("publish"),
|
"publish",
|
||||||
i18n.G("p"),
|
"p",
|
||||||
false,
|
false,
|
||||||
i18n.G("publish changes to git.coopcloud.tech"),
|
"publish changes to git.coopcloud.tech",
|
||||||
)
|
)
|
||||||
|
|
||||||
CatalogueGenerateCommand.Flags().BoolVarP(
|
CatalogueGenerateCommand.Flags().BoolVarP(
|
||||||
&internal.Dry,
|
&internal.Dry,
|
||||||
i18n.G("dry-run"),
|
"dry-run",
|
||||||
i18n.G("r"),
|
"r",
|
||||||
false,
|
false,
|
||||||
i18n.G("report changes that would be made"),
|
"report changes that would be made",
|
||||||
)
|
)
|
||||||
|
|
||||||
CatalogueGenerateCommand.Flags().BoolVarP(
|
CatalogueGenerateCommand.Flags().BoolVarP(
|
||||||
&skipUpdates,
|
&skipUpdates,
|
||||||
i18n.G("skip-updates"),
|
"skip-updates",
|
||||||
i18n.G("s"),
|
"s",
|
||||||
false,
|
false,
|
||||||
i18n.G("skip updating recipe repositories"),
|
"skip updating recipe repositories",
|
||||||
)
|
)
|
||||||
|
|
||||||
CatalogueGenerateCommand.Flags().BoolVarP(
|
CatalogueGenerateCommand.Flags().BoolVarP(
|
||||||
&internal.Chaos,
|
&internal.Chaos,
|
||||||
i18n.G("chaos"),
|
"chaos",
|
||||||
i18n.G("C"),
|
"C",
|
||||||
false,
|
false,
|
||||||
i18n.G("ignore uncommitted recipes changes"),
|
"ignore uncommitted recipes changes",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,23 +2,14 @@ package cli
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra autocomplete` aliases. use a comma separated list of
|
|
||||||
// aliases with no spaces in between
|
|
||||||
var autocompleteAliases = i18n.G("ac")
|
|
||||||
|
|
||||||
var AutocompleteCommand = &cobra.Command{
|
var AutocompleteCommand = &cobra.Command{
|
||||||
// translators: `autocomplete` command
|
Use: "autocomplete [bash|zsh|fish|powershell]",
|
||||||
Use: i18n.G("autocomplete [bash|zsh|fish|powershell]"),
|
Short: "Generate autocompletion script",
|
||||||
Aliases: strings.Split(autocompleteAliases, ","),
|
Long: `To load completions:
|
||||||
// translators: Short description for `autocomplete` command
|
|
||||||
Short: i18n.G("Generate autocompletion script"),
|
|
||||||
Long: i18n.G(`To load completions:
|
|
||||||
|
|
||||||
Bash:
|
Bash:
|
||||||
# Load autocompletion for the current Bash session
|
# Load autocompletion for the current Bash session
|
||||||
@ -52,7 +43,7 @@ PowerShell:
|
|||||||
|
|
||||||
# To load autocompletions for every new session, run:
|
# To load autocompletions for every new session, run:
|
||||||
PS> abra autocomplete powershell > abra.ps1
|
PS> abra autocomplete powershell > abra.ps1
|
||||||
# and source this file from your PowerShell profile.`),
|
# and source this file from your PowerShell profile.`,
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
ValidArgs: []string{"bash", "zsh", "fish", "powershell"},
|
||||||
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
Args: cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
|
||||||
|
|||||||
@ -2,12 +2,11 @@ package internal
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
containerPkg "coopcloud.tech/abra/pkg/container"
|
containerPkg "coopcloud.tech/abra/pkg/container"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/service"
|
"coopcloud.tech/abra/pkg/service"
|
||||||
"coopcloud.tech/abra/pkg/upstream/container"
|
"coopcloud.tech/abra/pkg/upstream/container"
|
||||||
@ -23,10 +22,10 @@ func RetrieveBackupBotContainer(cl *dockerClient.Client) (types.Container, error
|
|||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
chosenService, err := service.GetServiceByLabel(ctx, cl, config.BackupbotLabel, NoInput)
|
chosenService, err := service.GetServiceByLabel(ctx, cl, config.BackupbotLabel, NoInput)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return types.Container{}, errors.New(i18n.G("no backupbot discovered, is it deployed?"))
|
return types.Container{}, fmt.Errorf("no backupbot discovered, is it deployed?")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("retrieved %s as backup enabled service", chosenService.Spec.Name))
|
log.Debugf("retrieved %s as backup enabled service", chosenService.Spec.Name)
|
||||||
|
|
||||||
filters := filters.NewArgs()
|
filters := filters.NewArgs()
|
||||||
filters.Add("name", chosenService.Spec.Name)
|
filters.Add("name", chosenService.Spec.Name)
|
||||||
@ -59,7 +58,7 @@ func RunBackupCmdRemote(
|
|||||||
Tty: true,
|
Tty: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("running backup %s on %s with exec config %v", backupCmd, containerID, execBackupListOpts))
|
log.Debugf("running backup %s on %s with exec config %v", backupCmd, containerID, execBackupListOpts)
|
||||||
|
|
||||||
// FIXME: avoid instantiating a new CLI
|
// FIXME: avoid instantiating a new CLI
|
||||||
dcli, err := command.NewDockerCli()
|
dcli, err := command.NewDockerCli()
|
||||||
|
|||||||
@ -5,12 +5,10 @@ var (
|
|||||||
Debug bool
|
Debug bool
|
||||||
NoInput bool
|
NoInput bool
|
||||||
Offline bool
|
Offline bool
|
||||||
Help bool
|
IgnoreEnvVersion bool
|
||||||
Version bool
|
|
||||||
|
|
||||||
// NOTE(d1): sub-command specific
|
// NOTE(d1): sub-command specific
|
||||||
Chaos bool
|
Chaos bool
|
||||||
DeployLatest bool
|
|
||||||
DontWaitConverge bool
|
DontWaitConverge bool
|
||||||
Dry bool
|
Dry bool
|
||||||
Force bool
|
Force bool
|
||||||
@ -19,5 +17,4 @@ var (
|
|||||||
Minor bool
|
Minor bool
|
||||||
NoDomainChecks bool
|
NoDomainChecks bool
|
||||||
Patch bool
|
Patch bool
|
||||||
ShowUnchanged bool
|
|
||||||
)
|
)
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
@ -12,7 +11,6 @@ import (
|
|||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
containerPkg "coopcloud.tech/abra/pkg/container"
|
containerPkg "coopcloud.tech/abra/pkg/container"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"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"
|
||||||
@ -36,7 +34,7 @@ func RunCmdRemote(
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("retrieved %s as target container on %s", formatter.ShortenID(targetContainer.ID), app.Server))
|
log.Debugf("retrieved %s as target container on %s", formatter.ShortenID(targetContainer.ID), app.Server)
|
||||||
|
|
||||||
toTarOpts := &archive.TarOptions{NoOverwriteDirNonDir: true, Compression: archive.Gzip}
|
toTarOpts := &archive.TarOptions{NoOverwriteDirNonDir: true, Compression: archive.Gzip}
|
||||||
content, err := archive.TarWithOptions(abraSh, toTarOpts)
|
content, err := archive.TarWithOptions(abraSh, toTarOpts)
|
||||||
@ -67,7 +65,7 @@ func RunCmdRemote(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil {
|
if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil {
|
||||||
log.Info(i18n.G("%s does not exist for %s, use /bin/sh as fallback", shell, app.Name))
|
log.Infof("%s does not exist for %s, use /bin/sh as fallback", shell, app.Name)
|
||||||
shell = "/bin/sh"
|
shell = "/bin/sh"
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,10 +76,10 @@ func RunCmdRemote(
|
|||||||
cmd = []string{shell, "-c", fmt.Sprintf("TARGET=%s; APP_NAME=%s; STACK_NAME=%s; . /tmp/abra.sh; %s", serviceName, app.Name, app.StackName(), cmdName)}
|
cmd = []string{shell, "-c", fmt.Sprintf("TARGET=%s; APP_NAME=%s; STACK_NAME=%s; . /tmp/abra.sh; %s", serviceName, app.Name, app.StackName(), cmdName)}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("running command: %s", strings.Join(cmd, " ")))
|
log.Debugf("running command: %s", strings.Join(cmd, " "))
|
||||||
|
|
||||||
if remoteUser != "" {
|
if remoteUser != "" {
|
||||||
log.Debug(i18n.G("running command with user %s", remoteUser))
|
log.Debugf("running command with user %s", remoteUser)
|
||||||
execCreateOpts.User = remoteUser
|
execCreateOpts.User = remoteUser
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,7 +88,7 @@ func RunCmdRemote(
|
|||||||
execCreateOpts.Tty = true
|
execCreateOpts.Tty = true
|
||||||
if disableTTY {
|
if disableTTY {
|
||||||
execCreateOpts.Tty = false
|
execCreateOpts.Tty = false
|
||||||
log.Debug(i18n.G("not requesting a remote TTY"))
|
log.Debugf("not requesting a remote TTY")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil {
|
if _, err := container.RunExec(dcli, cl, targetContainer.ID, &execCreateOpts); err != nil {
|
||||||
@ -107,7 +105,7 @@ func EnsureCommand(abraSh, recipeName, execCmd string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !strings.Contains(string(bytes), execCmd) {
|
if !strings.Contains(string(bytes), execCmd) {
|
||||||
return errors.New(i18n.G("%s doesn't have a %s function", recipeName, execCmd))
|
return fmt.Errorf("%s doesn't have a %s function", recipeName, execCmd)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"sort"
|
"sort"
|
||||||
@ -10,7 +9,6 @@ import (
|
|||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/tagcmp"
|
"coopcloud.tech/tagcmp"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
@ -50,9 +48,6 @@ func DeployOverview(
|
|||||||
toDeployVersion string,
|
toDeployVersion string,
|
||||||
releaseNotes string,
|
releaseNotes string,
|
||||||
warnMessages []string,
|
warnMessages []string,
|
||||||
secrets []string,
|
|
||||||
configs []string,
|
|
||||||
images []string,
|
|
||||||
) error {
|
) error {
|
||||||
deployConfig := "compose.yml"
|
deployConfig := "compose.yml"
|
||||||
if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
|
if composeFiles, ok := app.Env["COMPOSE_FILE"]; ok {
|
||||||
@ -66,51 +61,27 @@ func DeployOverview(
|
|||||||
|
|
||||||
domain := app.Domain
|
domain := app.Domain
|
||||||
if domain == "" {
|
if domain == "" {
|
||||||
domain = config.MISSING_DEFAULT
|
domain = config.NO_DOMAIN_DEFAULT
|
||||||
}
|
}
|
||||||
|
|
||||||
envVersion := app.Recipe.EnvVersionRaw
|
envVersion := app.Recipe.EnvVersionRaw
|
||||||
if envVersion == "" {
|
if envVersion == "" {
|
||||||
envVersion = config.MISSING_DEFAULT
|
envVersion = config.NO_VERSION_DEFAULT
|
||||||
}
|
}
|
||||||
|
|
||||||
rows := [][]string{
|
rows := [][]string{
|
||||||
{i18n.G("DOMAIN"), domain},
|
{"DOMAIN", domain},
|
||||||
{i18n.G("RECIPE"), app.Recipe.Name},
|
{"RECIPE", app.Recipe.Name},
|
||||||
{i18n.G("SERVER"), server},
|
{"SERVER", server},
|
||||||
{i18n.G("CONFIG"), deployConfig},
|
{"CONFIG", deployConfig},
|
||||||
{"", ""},
|
{"", ""},
|
||||||
{i18n.G("CURRENT DEPLOYMENT"), formatter.BoldDirtyDefault(deployedVersion)},
|
{"CURRENT DEPLOYMENT", formatter.BoldDirtyDefault(deployedVersion)},
|
||||||
{i18n.G("ENV VERSION"), formatter.BoldDirtyDefault(envVersion)},
|
{"ENV VERSION", formatter.BoldDirtyDefault(envVersion)},
|
||||||
{i18n.G("NEW DEPLOYMENT"), formatter.BoldDirtyDefault(toDeployVersion)},
|
{"NEW DEPLOYMENT", formatter.BoldDirtyDefault(toDeployVersion)},
|
||||||
}
|
|
||||||
|
|
||||||
if len(images) > 0 {
|
|
||||||
imageRows := [][]string{
|
|
||||||
{"", ""},
|
|
||||||
{i18n.G("IMAGES"), strings.Join(images, "\n")},
|
|
||||||
}
|
|
||||||
rows = append(rows, imageRows...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(secrets) > 0 {
|
|
||||||
secretsRows := [][]string{
|
|
||||||
{"", ""},
|
|
||||||
{i18n.G("SECRETS"), strings.Join(secrets, "\n")},
|
|
||||||
}
|
|
||||||
rows = append(rows, secretsRows...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(configs) > 0 {
|
|
||||||
configsRows := [][]string{
|
|
||||||
{"", ""},
|
|
||||||
{i18n.G("CONFIGS"), strings.Join(configs, "\n")},
|
|
||||||
}
|
|
||||||
rows = append(rows, configsRows...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
deployType := getDeployType(deployedVersion, toDeployVersion)
|
deployType := getDeployType(deployedVersion, toDeployVersion)
|
||||||
overview := formatter.CreateOverview(i18n.G("%s OVERVIEW", deployType), rows)
|
overview := formatter.CreateOverview(fmt.Sprintf("%s OVERVIEW", deployType), rows)
|
||||||
|
|
||||||
fmt.Println(overview)
|
fmt.Println(overview)
|
||||||
|
|
||||||
@ -133,111 +104,40 @@ func DeployOverview(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !response {
|
if !response {
|
||||||
log.Fatal(i18n.G("deployment cancelled"))
|
log.Fatal("deployment cancelled")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getDeployType(currentVersion, newVersion string) string {
|
func getDeployType(currentVersion, newVersion string) string {
|
||||||
if newVersion == config.MISSING_DEFAULT {
|
if newVersion == config.NO_DOMAIN_DEFAULT {
|
||||||
return i18n.G("UNDEPLOY")
|
return "UNDEPLOY"
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(newVersion, "+U") {
|
if strings.Contains(newVersion, "+U") {
|
||||||
return i18n.G("CHAOS DEPLOY")
|
return "CHAOS DEPLOY"
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Contains(currentVersion, "+U") {
|
if strings.Contains(currentVersion, "+U") {
|
||||||
return i18n.G("UNCHAOS DEPLOY")
|
return "UNCHAOS DEPLOY"
|
||||||
}
|
}
|
||||||
|
|
||||||
if currentVersion == newVersion {
|
if currentVersion == newVersion {
|
||||||
return ("REDEPLOY")
|
return "REDEPLOY"
|
||||||
}
|
}
|
||||||
|
if currentVersion == config.NO_VERSION_DEFAULT {
|
||||||
if currentVersion == config.MISSING_DEFAULT {
|
return "NEW DEPLOY"
|
||||||
return i18n.G("NEW DEPLOY")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
currentParsed, err := tagcmp.Parse(currentVersion)
|
currentParsed, err := tagcmp.Parse(currentVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return i18n.G("DEPLOY")
|
return "DEPLOY"
|
||||||
}
|
}
|
||||||
|
|
||||||
newParsed, err := tagcmp.Parse(newVersion)
|
newParsed, err := tagcmp.Parse(newVersion)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return i18n.G("DEPLOY")
|
return "DEPLOY"
|
||||||
}
|
}
|
||||||
|
|
||||||
if currentParsed.IsLessThan(newParsed) {
|
if currentParsed.IsLessThan(newParsed) {
|
||||||
return i18n.G("UPGRADE")
|
return "UPGRADE"
|
||||||
}
|
}
|
||||||
|
return "DOWNGRADE"
|
||||||
return i18n.G("DOWNGRADE")
|
|
||||||
}
|
|
||||||
|
|
||||||
// MoveOverview shows a overview before moving an app to a different server
|
|
||||||
func MoveOverview(
|
|
||||||
app appPkg.App,
|
|
||||||
newServer string,
|
|
||||||
secrets []string,
|
|
||||||
volumes []string,
|
|
||||||
) {
|
|
||||||
server := app.Server
|
|
||||||
if app.Server == "default" {
|
|
||||||
server = "local"
|
|
||||||
}
|
|
||||||
|
|
||||||
domain := app.Domain
|
|
||||||
if domain == "" {
|
|
||||||
domain = config.MISSING_DEFAULT
|
|
||||||
}
|
|
||||||
|
|
||||||
secretsOverview := strings.Join(secrets, "\n")
|
|
||||||
if len(secrets) == 0 {
|
|
||||||
secretsOverview = config.MISSING_DEFAULT
|
|
||||||
}
|
|
||||||
|
|
||||||
volumesOverview := strings.Join(volumes, "\n")
|
|
||||||
if len(volumes) == 0 {
|
|
||||||
volumesOverview = config.MISSING_DEFAULT
|
|
||||||
}
|
|
||||||
|
|
||||||
rows := [][]string{
|
|
||||||
{i18n.G("DOMAIN"), domain},
|
|
||||||
{i18n.G("RECIPE"), app.Recipe.Name},
|
|
||||||
{i18n.G("OLD SERVER"), server},
|
|
||||||
{i18n.G("NEW SERVER"), newServer},
|
|
||||||
{i18n.G("SECRETS"), secretsOverview},
|
|
||||||
{i18n.G("VOLUMES"), volumesOverview},
|
|
||||||
}
|
|
||||||
|
|
||||||
overview := formatter.CreateOverview(i18n.G("MOVE OVERVIEW"), rows)
|
|
||||||
|
|
||||||
fmt.Println(overview)
|
|
||||||
}
|
|
||||||
|
|
||||||
func PromptProcced() error {
|
|
||||||
if NoInput {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if Dry {
|
|
||||||
return errors.New(i18n.G("dry run"))
|
|
||||||
}
|
|
||||||
|
|
||||||
response := false
|
|
||||||
prompt := &survey.Confirm{Message: i18n.G("proceed?")}
|
|
||||||
if err := survey.AskOne(prompt, &response); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if !response {
|
|
||||||
return errors.New(i18n.G("cancelled"))
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostCmds parses a string of commands and executes them inside of the respective services
|
// PostCmds parses a string of commands and executes them inside of the respective services
|
||||||
@ -246,7 +146,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 errors.New(i18n.G("%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
|
||||||
}
|
}
|
||||||
@ -254,7 +154,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 errors.New(i18n.G("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]
|
||||||
@ -262,7 +162,7 @@ func PostCmds(cl *dockerClient.Client, app appPkg.App, commands string) error {
|
|||||||
if len(commandParts) > 2 {
|
if len(commandParts) > 2 {
|
||||||
parsedCmdArgs = fmt.Sprintf("%s ", strings.Join(commandParts[2:], " "))
|
parsedCmdArgs = fmt.Sprintf("%s ", strings.Join(commandParts[2:], " "))
|
||||||
}
|
}
|
||||||
log.Info(i18n.G("running post-command '%s %s' in container %s", cmdName, parsedCmdArgs, targetServiceName))
|
log.Infof("running post-command '%s %s' in container %s", cmdName, parsedCmdArgs, targetServiceName)
|
||||||
|
|
||||||
if err := EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil {
|
if err := EnsureCommand(app.Recipe.AbraShPath, app.Recipe.Name, cmdName); err != nil {
|
||||||
return err
|
return err
|
||||||
@ -281,10 +181,10 @@ 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.Debug(i18n.G("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)
|
||||||
|
|
||||||
requestTTY := true
|
requestTTY := true
|
||||||
if err := RunCmdRemote(
|
if err := RunCmdRemote(
|
||||||
|
|||||||
@ -6,6 +6,6 @@ func GetEnsureContext() recipe.EnsureContext {
|
|||||||
return recipe.EnsureContext{
|
return recipe.EnsureContext{
|
||||||
Chaos,
|
Chaos,
|
||||||
Offline,
|
Offline,
|
||||||
DeployLatest,
|
IgnoreEnvVersion,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
@ -13,9 +11,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
// PromptBumpType prompts for version bump type
|
// PromptBumpType prompts for version bump type
|
||||||
func PromptBumpType(tagString, latestRelease, changeOverview string) error {
|
func PromptBumpType(tagString, latestRelease string) error {
|
||||||
if (!Major && !Minor && !Patch) && tagString == "" {
|
if (!Major && !Minor && !Patch) && tagString == "" {
|
||||||
fmt.Print(i18n.G(`
|
fmt.Printf(`
|
||||||
You need to make a decision about what kind of an update this new recipe
|
You need to make a decision about what kind of an update this new recipe
|
||||||
version is. If someone else performs this upgrade, do they have to do some
|
version is. If someone else performs this upgrade, do they have to do some
|
||||||
migration work or take care of some breaking changes? This can be signaled in
|
migration work or take care of some breaking changes? This can be signaled in
|
||||||
@ -24,8 +22,6 @@ version.
|
|||||||
|
|
||||||
The latest published version is %s.
|
The latest published version is %s.
|
||||||
|
|
||||||
%s
|
|
||||||
|
|
||||||
Here is a semver cheat sheet (more on https://semver.org):
|
Here is a semver cheat sheet (more on https://semver.org):
|
||||||
|
|
||||||
major: new features/bug fixes, backwards incompatible (e.g 1.0.0 -> 2.0.0).
|
major: new features/bug fixes, backwards incompatible (e.g 1.0.0 -> 2.0.0).
|
||||||
@ -40,12 +36,12 @@ Here is a semver cheat sheet (more on https://semver.org):
|
|||||||
should also Just Work and is mostly to do with minor bug fixes
|
should also Just Work and is mostly to do with minor bug fixes
|
||||||
and/or security patches. "nothing to worry about".
|
and/or security patches. "nothing to worry about".
|
||||||
|
|
||||||
`, latestRelease, changeOverview))
|
`, latestRelease)
|
||||||
|
|
||||||
var chosenBumpType string
|
var chosenBumpType string
|
||||||
prompt := &survey.Select{
|
prompt := &survey.Select{
|
||||||
Message: fmt.Sprintf("select recipe version increment type"),
|
Message: fmt.Sprintf("select recipe version increment type"),
|
||||||
Options: []string{i18n.G("major"), i18n.G("minor"), i18n.G("patch")},
|
Options: []string{"major", "minor", "patch"},
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := survey.AskOne(prompt, &chosenBumpType); err != nil {
|
if err := survey.AskOne(prompt, &chosenBumpType); err != nil {
|
||||||
@ -63,13 +59,13 @@ func GetBumpType() string {
|
|||||||
var bumpType string
|
var bumpType string
|
||||||
|
|
||||||
if Major {
|
if Major {
|
||||||
bumpType = i18n.G("major")
|
bumpType = "major"
|
||||||
} else if Minor {
|
} else if Minor {
|
||||||
bumpType = i18n.G("minor")
|
bumpType = "minor"
|
||||||
} else if Patch {
|
} else if Patch {
|
||||||
bumpType = i18n.G("patch")
|
bumpType = "patch"
|
||||||
} else {
|
} else {
|
||||||
log.Fatal(i18n.G("no version bump type specififed?"))
|
log.Fatal("no version bump type specififed?")
|
||||||
}
|
}
|
||||||
|
|
||||||
return bumpType
|
return bumpType
|
||||||
@ -77,14 +73,14 @@ func GetBumpType() string {
|
|||||||
|
|
||||||
// SetBumpType figures out which bump type is specified
|
// SetBumpType figures out which bump type is specified
|
||||||
func SetBumpType(bumpType string) {
|
func SetBumpType(bumpType string) {
|
||||||
if bumpType == i18n.G("major") {
|
if bumpType == "major" {
|
||||||
Major = true
|
Major = true
|
||||||
} else if bumpType == i18n.G("minor") {
|
} else if bumpType == "minor" {
|
||||||
Minor = true
|
Minor = true
|
||||||
} else if bumpType == i18n.G("patch") {
|
} else if bumpType == "patch" {
|
||||||
Patch = true
|
Patch = true
|
||||||
} else {
|
} else {
|
||||||
log.Fatal(i18n.G("no version bump type specififed?"))
|
log.Fatal("no version bump type specififed?")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +107,7 @@ func GetMainAppImage(recipe recipe.Recipe) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if path == "" {
|
if path == "" {
|
||||||
return path, errors.New(i18n.G("%s has no main 'app' service?", recipe.Name))
|
return path, fmt.Errorf("%s has no main 'app' service?", recipe.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return path, nil
|
return path, nil
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import (
|
|||||||
|
|
||||||
"coopcloud.tech/abra/pkg/app"
|
"coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
@ -18,6 +17,7 @@ func ValidateRecipe(args []string, cmdName string) recipe.Recipe {
|
|||||||
recipeName = args[0]
|
recipeName = args[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if recipeName == "" && !NoInput {
|
||||||
var recipes []string
|
var recipes []string
|
||||||
|
|
||||||
catl, err := recipe.ReadRecipeCatalogue(Offline)
|
catl, err := recipe.ReadRecipeCatalogue(Offline)
|
||||||
@ -32,22 +32,21 @@ func ValidateRecipe(args []string, cmdName string) recipe.Recipe {
|
|||||||
|
|
||||||
localRecipes, err := recipe.GetRecipesLocal()
|
localRecipes, err := recipe.GetRecipesLocal()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug(i18n.G("can't read local recipes: %s", err))
|
log.Fatal(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: i18n.G("Select recipe"),
|
Message: "Select recipe",
|
||||||
Options: recipes,
|
Options: recipes,
|
||||||
}
|
}
|
||||||
if err := survey.AskOne(prompt, &recipeName); err != nil {
|
if err := survey.AskOne(prompt, &recipeName); err != nil {
|
||||||
@ -56,36 +55,30 @@ func ValidateRecipe(args []string, cmdName string) recipe.Recipe {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if recipeName == "" {
|
if recipeName == "" {
|
||||||
log.Fatal(i18n.G("no recipe name provided"))
|
log.Fatal("no recipe name provided")
|
||||||
}
|
|
||||||
|
|
||||||
if _, ok := knownRecipes[recipeName]; !ok {
|
|
||||||
if !strings.Contains(recipeName, "/") {
|
|
||||||
log.Fatal(i18n.G("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 == i18n.G("generate") {
|
if cmdName == "generate" {
|
||||||
if strings.Contains(err.Error(), "missing a compose") {
|
if strings.Contains(err.Error(), "missing a compose") {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
log.Warn(err)
|
log.Warn(err)
|
||||||
} else {
|
} else {
|
||||||
if strings.Contains(err.Error(), "template_driver is not allowed") {
|
if strings.Contains(err.Error(), "template_driver is not allowed") {
|
||||||
log.Warn(i18n.G("ensure %s recipe compose.* files include \"version: '3.8'\"", recipeName))
|
log.Warnf("ensure %s recipe compose.* files include \"version: '3.8'\"", recipeName)
|
||||||
}
|
}
|
||||||
log.Fatal(i18n.G("unable to validate recipe: %s", err))
|
log.Fatalf("unable to validate recipe: %s", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("validated %s as recipe argument", recipeName))
|
log.Debugf("validated %s as recipe argument", recipeName)
|
||||||
|
|
||||||
return chosenRecipe
|
return chosenRecipe
|
||||||
}
|
}
|
||||||
@ -93,7 +86,7 @@ func ValidateRecipe(args []string, cmdName string) recipe.Recipe {
|
|||||||
// ValidateApp ensures the app name arg is valid.
|
// ValidateApp ensures the app name arg is valid.
|
||||||
func ValidateApp(args []string) app.App {
|
func ValidateApp(args []string) app.App {
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
log.Fatal(i18n.G("no app provided"))
|
log.Fatal("no app provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
appName := args[0]
|
appName := args[0]
|
||||||
@ -103,7 +96,7 @@ func ValidateApp(args []string) app.App {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("validated %s as app argument", appName))
|
log.Debugf("validated %s as app argument", appName)
|
||||||
|
|
||||||
return app
|
return app
|
||||||
}
|
}
|
||||||
@ -117,8 +110,8 @@ func ValidateDomain(args []string) string {
|
|||||||
|
|
||||||
if domainName == "" && !NoInput {
|
if domainName == "" && !NoInput {
|
||||||
prompt := &survey.Input{
|
prompt := &survey.Input{
|
||||||
Message: i18n.G("Specify a domain name"),
|
Message: "Specify a domain name",
|
||||||
Default: "1312.net",
|
Default: "example.com",
|
||||||
}
|
}
|
||||||
if err := survey.AskOne(prompt, &domainName); err != nil {
|
if err := survey.AskOne(prompt, &domainName); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -126,10 +119,10 @@ func ValidateDomain(args []string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if domainName == "" {
|
if domainName == "" {
|
||||||
log.Fatal(i18n.G("no domain provided"))
|
log.Fatal("no domain provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("validated %s as domain argument", domainName))
|
log.Debugf("validated %s as domain argument", domainName)
|
||||||
|
|
||||||
return domainName
|
return domainName
|
||||||
}
|
}
|
||||||
@ -148,7 +141,7 @@ func ValidateServer(args []string) string {
|
|||||||
|
|
||||||
if serverName == "" && !NoInput {
|
if serverName == "" && !NoInput {
|
||||||
prompt := &survey.Select{
|
prompt := &survey.Select{
|
||||||
Message: i18n.G("Specify a server name"),
|
Message: "Specify a server name",
|
||||||
Options: serverNames,
|
Options: serverNames,
|
||||||
}
|
}
|
||||||
if err := survey.AskOne(prompt, &serverName); err != nil {
|
if err := survey.AskOne(prompt, &serverName); err != nil {
|
||||||
@ -164,14 +157,14 @@ func ValidateServer(args []string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if serverName == "" {
|
if serverName == "" {
|
||||||
log.Fatal(i18n.G("no server provided"))
|
log.Fatal("no server provided")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !matched {
|
if !matched {
|
||||||
log.Fatal(i18n.G("server doesn't exist?"))
|
log.Fatal("server doesn't exist?")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("validated %s as server argument", serverName))
|
log.Debugf("validated %s as server argument", serverName)
|
||||||
|
|
||||||
return serverName
|
return serverName
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,27 +1,18 @@
|
|||||||
package recipe
|
package recipe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
gitPkg "coopcloud.tech/abra/pkg/git"
|
gitPkg "coopcloud.tech/abra/pkg/git"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra recipe diff` aliases. use a comma separated list of aliases
|
|
||||||
// with no spaces in between
|
|
||||||
var recipeDiffAliases = i18n.G("d")
|
|
||||||
|
|
||||||
var RecipeDiffCommand = &cobra.Command{
|
var RecipeDiffCommand = &cobra.Command{
|
||||||
// translators: `recipe diff` command
|
Use: "diff <recipe> [flags]",
|
||||||
Use: i18n.G("diff <recipe> [flags]"),
|
Aliases: []string{"d"},
|
||||||
Aliases: strings.Split(recipeDiffAliases, ","),
|
Short: "Show unstaged changes in recipe config",
|
||||||
// translators: Short description for `recipe diff` command
|
Long: "This command requires /usr/bin/git.",
|
||||||
Short: i18n.G("Show unstaged changes in recipe config"),
|
|
||||||
Long: i18n.G("This command requires /usr/bin/git."),
|
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
|
|||||||
@ -2,12 +2,10 @@ package recipe
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"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/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
@ -15,26 +13,20 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra recipe fetch` aliases. use a comma separated list of aliases
|
|
||||||
// with no spaces in between
|
|
||||||
var recipeFetchAliases = i18n.G("f")
|
|
||||||
|
|
||||||
var RecipeFetchCommand = &cobra.Command{
|
var RecipeFetchCommand = &cobra.Command{
|
||||||
// translators: `recipe fetch` command
|
Use: "fetch [recipe | --all] [flags]",
|
||||||
Use: i18n.G("fetch [recipe | --all] [flags]"),
|
Aliases: []string{"f"},
|
||||||
Aliases: strings.Split(recipeFetchAliases, ","),
|
Short: "Clone recipe(s) locally",
|
||||||
// translators: Short description for `recipe fetch` command
|
Long: `Using "--force/-f" Git syncs an existing recipe. It does not erase unstaged changes.`,
|
||||||
Short: i18n.G("Clone recipe(s) locally"),
|
|
||||||
Long: i18n.G(`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: i18n.G(` # fetch from recipe catalogue
|
Example: ` # fetch from recipe catalogue
|
||||||
abra recipe fetch gitea
|
abra recipe fetch gitea
|
||||||
|
|
||||||
# fetch from remote recipe
|
# fetch from remote recipe
|
||||||
abra recipe fetch git.foo.org/recipes/myrecipe
|
abra recipe fetch git.foo.org/recipes/myrecipe
|
||||||
|
|
||||||
# fetch with ssh remote for hacking
|
# fetch with ssh remote for hacking
|
||||||
abra recipe fetch gitea --ssh`),
|
abra recipe fetch gitea --ssh`,
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
args []string,
|
args []string,
|
||||||
@ -48,18 +40,19 @@ var RecipeFetchCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if recipeName == "" && !fetchAllRecipes {
|
if recipeName == "" && !fetchAllRecipes {
|
||||||
log.Fatal(i18n.G("missing [recipe] or --all/-a"))
|
log.Fatal("missing [recipe] or --all/-a")
|
||||||
}
|
}
|
||||||
|
|
||||||
if recipeName != "" && fetchAllRecipes {
|
if recipeName != "" && fetchAllRecipes {
|
||||||
log.Fatal(i18n.G("cannot use [recipe] and --all/-a together"))
|
log.Fatal("cannot use [recipe] and --all/-a together")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ensureCtx := internal.GetEnsureContext()
|
||||||
if recipeName != "" {
|
if recipeName != "" {
|
||||||
r := recipe.Get(recipeName)
|
r := recipe.Get(recipeName)
|
||||||
if _, err := os.Stat(r.Dir); !os.IsNotExist(err) {
|
if _, err := os.Stat(r.Dir); !os.IsNotExist(err) {
|
||||||
if !force {
|
if !force {
|
||||||
log.Warn(i18n.G("%s is already fetched", r.Name))
|
log.Warnf("%s is already fetched", r.Name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,24 +61,24 @@ var RecipeFetchCommand = &cobra.Command{
|
|||||||
|
|
||||||
if sshRemote {
|
if sshRemote {
|
||||||
if r.SSHURL == "" {
|
if r.SSHURL == "" {
|
||||||
log.Warn(i18n.G("unable to discover SSH remote for %s", r.Name))
|
log.Warnf("unable to discover SSH remote for %s", r.Name)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
repo, err := git.PlainOpen(r.Dir)
|
repo, err := git.PlainOpen(r.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(i18n.G("unable to open %s: %s", r.Dir, err))
|
log.Fatalf("unable to open %s: %s", r.Dir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = repo.DeleteRemote("origin"); err != nil {
|
if err = repo.DeleteRemote("origin"); err != nil {
|
||||||
log.Fatal(i18n.G("unable to remove default remote in %s: %s", r.Dir, err))
|
log.Fatalf("unable to remove default remote in %s: %s", r.Dir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := repo.CreateRemote(&gitCfg.RemoteConfig{
|
if _, err := repo.CreateRemote(&gitCfg.RemoteConfig{
|
||||||
Name: "origin",
|
Name: "origin",
|
||||||
URLs: []string{r.SSHURL},
|
URLs: []string{r.SSHURL},
|
||||||
}); err != nil {
|
}); err != nil {
|
||||||
log.Fatal(i18n.G("unable to set SSH remote in %s: %s", r.Dir, err))
|
log.Fatalf("unable to set SSH remote in %s: %s", r.Dir, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,8 +90,7 @@ var RecipeFetchCommand = &cobra.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
catlBar := formatter.CreateProgressbar(len(catalogue), i18n.G("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(ensureCtx); err != nil {
|
||||||
@ -118,25 +110,25 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
RecipeFetchCommand.Flags().BoolVarP(
|
RecipeFetchCommand.Flags().BoolVarP(
|
||||||
&fetchAllRecipes,
|
&fetchAllRecipes,
|
||||||
i18n.G("all"),
|
"all",
|
||||||
i18n.GC("a", "recipe fetch"),
|
"a",
|
||||||
false,
|
false,
|
||||||
i18n.G("fetch all recipes"),
|
"fetch all recipes",
|
||||||
)
|
)
|
||||||
|
|
||||||
RecipeFetchCommand.Flags().BoolVarP(
|
RecipeFetchCommand.Flags().BoolVarP(
|
||||||
&sshRemote,
|
&sshRemote,
|
||||||
i18n.G("ssh"),
|
"ssh",
|
||||||
i18n.G("s"),
|
"s",
|
||||||
false,
|
false,
|
||||||
i18n.G("automatically set ssh remote"),
|
"automatically set ssh remote",
|
||||||
)
|
)
|
||||||
|
|
||||||
RecipeFetchCommand.Flags().BoolVarP(
|
RecipeFetchCommand.Flags().BoolVarP(
|
||||||
&force,
|
&force,
|
||||||
i18n.G("force"),
|
"force",
|
||||||
i18n.G("f"),
|
"f",
|
||||||
false,
|
false,
|
||||||
i18n.G("force re-fetch"),
|
"force re-fetch",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,27 +1,18 @@
|
|||||||
package recipe
|
package recipe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"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/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/lint"
|
"coopcloud.tech/abra/pkg/lint"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra recipe lint` aliases. use a comma separated list of
|
|
||||||
// aliases with no spaces in between
|
|
||||||
var recipeLintAliases = i18n.G("l")
|
|
||||||
|
|
||||||
var RecipeLintCommand = &cobra.Command{
|
var RecipeLintCommand = &cobra.Command{
|
||||||
// translators: `recipe lint` command
|
Use: "lint <recipe> [flags]",
|
||||||
Use: i18n.G("lint <recipe> [flags]"),
|
Short: "Lint a recipe",
|
||||||
// translators: Short description for `recipe lint` command
|
Aliases: []string{"l"},
|
||||||
Short: i18n.G("Lint a recipe"),
|
|
||||||
Aliases: strings.Split(recipeLintAliases, ","),
|
|
||||||
Args: cobra.MinimumNArgs(1),
|
Args: cobra.MinimumNArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -37,12 +28,12 @@ var RecipeLintCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
headers := []string{
|
headers := []string{
|
||||||
i18n.G("ref"),
|
"ref",
|
||||||
i18n.G("rule"),
|
"rule",
|
||||||
i18n.G("severity"),
|
"severity",
|
||||||
i18n.G("satisfied"),
|
"satisfied",
|
||||||
i18n.G("skipped"),
|
"skipped",
|
||||||
i18n.G("resolve"),
|
"resolve",
|
||||||
}
|
}
|
||||||
|
|
||||||
table, err := formatter.CreateTable()
|
table, err := formatter.CreateTable()
|
||||||
@ -58,7 +49,7 @@ var RecipeLintCommand = &cobra.Command{
|
|||||||
for level := range lint.LintRules {
|
for level := range lint.LintRules {
|
||||||
for _, rule := range lint.LintRules[level] {
|
for _, rule := range lint.LintRules[level] {
|
||||||
if onlyError && rule.Level != "error" {
|
if onlyError && rule.Level != "error" {
|
||||||
log.Debug(i18n.G("skipping %s, does not have level \"error\"", rule.Ref))
|
log.Debugf("skipping %s, does not have level \"error\"", rule.Ref)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,7 +70,7 @@ var RecipeLintCommand = &cobra.Command{
|
|||||||
warnMessages = append(warnMessages, err.Error())
|
warnMessages = append(warnMessages, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok && rule.Level == i18n.G("error") {
|
if !ok && rule.Level == "error" {
|
||||||
hasError = true
|
hasError = true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -120,7 +111,7 @@ var RecipeLintCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if hasError {
|
if hasError {
|
||||||
log.Warn(i18n.G("critical errors present in %s config", recipe.Name))
|
log.Warnf("critical errors present in %s config", recipe.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -133,17 +124,17 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
RecipeLintCommand.Flags().BoolVarP(
|
RecipeLintCommand.Flags().BoolVarP(
|
||||||
&internal.Chaos,
|
&internal.Chaos,
|
||||||
i18n.G("chaos"),
|
"chaos",
|
||||||
i18n.G("C"),
|
"C",
|
||||||
false,
|
false,
|
||||||
i18n.G("ignore uncommitted recipes changes"),
|
"ignore uncommitted recipes changes",
|
||||||
)
|
)
|
||||||
|
|
||||||
RecipeLintCommand.Flags().BoolVarP(
|
RecipeLintCommand.Flags().BoolVarP(
|
||||||
&onlyError,
|
&onlyError,
|
||||||
i18n.G("error"),
|
"error",
|
||||||
i18n.G("e"),
|
"e",
|
||||||
false,
|
false,
|
||||||
i18n.G("only show errors"),
|
"only show errors",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,22 +8,15 @@ import (
|
|||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra recipe list` aliases. use a comma separated list of
|
|
||||||
// aliases with no spaces in between
|
|
||||||
var recipeListAliases = i18n.G("ls")
|
|
||||||
|
|
||||||
var RecipeListCommand = &cobra.Command{
|
var RecipeListCommand = &cobra.Command{
|
||||||
// translators: `recipe list` command
|
Use: "list",
|
||||||
Use: i18n.G("list"),
|
Short: "List recipes",
|
||||||
// translators: Short description for `recipe list` command
|
Aliases: []string{"ls"},
|
||||||
Short: i18n.G("List recipes"),
|
|
||||||
Aliases: strings.Split(recipeListAliases, ","),
|
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
catl, err := recipe.ReadRecipeCatalogue(internal.Offline)
|
catl, err := recipe.ReadRecipeCatalogue(internal.Offline)
|
||||||
@ -40,14 +33,14 @@ var RecipeListCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
headers := []string{
|
headers := []string{
|
||||||
i18n.G("name"),
|
"name",
|
||||||
i18n.G("category"),
|
"category",
|
||||||
i18n.G("status"),
|
"status",
|
||||||
i18n.G("healthcheck"),
|
"healthcheck",
|
||||||
i18n.G("backups"),
|
"backups",
|
||||||
i18n.G("email"),
|
"email",
|
||||||
i18n.G("tests"),
|
"tests",
|
||||||
i18n.G("SSO"),
|
"SSO",
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Headers(headers...)
|
table.Headers(headers...)
|
||||||
@ -80,7 +73,7 @@ var RecipeListCommand = &cobra.Command{
|
|||||||
if internal.MachineReadable {
|
if internal.MachineReadable {
|
||||||
out, err := formatter.ToJSON(headers, rows)
|
out, err := formatter.ToJSON(headers, rows)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(i18n.G("unable to render to JSON: %s", err))
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
}
|
}
|
||||||
fmt.Println(out)
|
fmt.Println(out)
|
||||||
return
|
return
|
||||||
@ -100,17 +93,17 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
RecipeListCommand.Flags().BoolVarP(
|
RecipeListCommand.Flags().BoolVarP(
|
||||||
&internal.MachineReadable,
|
&internal.MachineReadable,
|
||||||
i18n.G("machine"),
|
"machine",
|
||||||
i18n.G("m"),
|
"m",
|
||||||
false,
|
false,
|
||||||
i18n.G("print machine-readable output"),
|
"print machine-readable output",
|
||||||
)
|
)
|
||||||
|
|
||||||
RecipeListCommand.Flags().StringVarP(
|
RecipeListCommand.Flags().StringVarP(
|
||||||
&pattern,
|
&pattern,
|
||||||
i18n.G("pattern"),
|
"pattern",
|
||||||
i18n.G("p"),
|
"p",
|
||||||
"",
|
"",
|
||||||
i18n.G("filter by recipe"),
|
"filter by recipe",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,13 +5,11 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
|
||||||
"text/template"
|
"text/template"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/git"
|
"coopcloud.tech/abra/pkg/git"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
@ -31,17 +29,11 @@ type recipeMetadata struct {
|
|||||||
SSO string
|
SSO string
|
||||||
}
|
}
|
||||||
|
|
||||||
// translators: `abra recipe new` aliases. use a comma separated list of
|
|
||||||
// aliases with no spaces in between
|
|
||||||
var recipeNewAliases = i18n.G("n")
|
|
||||||
|
|
||||||
var RecipeNewCommand = &cobra.Command{
|
var RecipeNewCommand = &cobra.Command{
|
||||||
// translators: `recipe new` command
|
Use: "new <recipe> [flags]",
|
||||||
Use: i18n.G("new <recipe> [flags]"),
|
Aliases: []string{"n"},
|
||||||
Aliases: strings.Split(recipeNewAliases, ","),
|
Short: "Create a new recipe",
|
||||||
// translators: Short description for `abra recipe new` command
|
Long: `A community managed recipe template is used.`,
|
||||||
Short: i18n.G("Create a new recipe"),
|
|
||||||
Long: i18n.G(`A community managed recipe template is used.`),
|
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -54,10 +46,10 @@ var RecipeNewCommand = &cobra.Command{
|
|||||||
|
|
||||||
r := recipe.Get(recipeName)
|
r := recipe.Get(recipeName)
|
||||||
if _, err := os.Stat(r.Dir); !os.IsNotExist(err) {
|
if _, err := os.Stat(r.Dir); !os.IsNotExist(err) {
|
||||||
log.Fatal(i18n.G("%s recipe directory already exists?", r.Dir))
|
log.Fatalf("%s recipe directory already exists?", r.Dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
url := i18n.G("%s/example.git", config.REPOS_BASE_URL)
|
url := fmt.Sprintf("%s/example.git", config.REPOS_BASE_URL)
|
||||||
if err := git.Clone(r.Dir, url); err != nil {
|
if err := git.Clone(r.Dir, url); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -66,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.Debug(i18n.G("removed .git repo in %s", gitRepo))
|
log.Debugf("removed .git repo in %s", gitRepo)
|
||||||
|
|
||||||
meta := newRecipeMeta(recipeName)
|
meta := newRecipeMeta(recipeName)
|
||||||
|
|
||||||
@ -90,8 +82,8 @@ var RecipeNewCommand = &cobra.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(i18n.G("new recipe '%s' created: %s", recipeName, path.Join(r.Dir)))
|
log.Infof("new recipe '%s' created: %s", recipeName, path.Join(r.Dir))
|
||||||
log.Info(i18n.G("happy hacking 🎉"))
|
log.Info("happy hacking 🎉")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -119,17 +111,17 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
RecipeNewCommand.Flags().StringVarP(
|
RecipeNewCommand.Flags().StringVarP(
|
||||||
&gitName,
|
&gitName,
|
||||||
i18n.G("git-name"),
|
"git-name",
|
||||||
i18n.G("N"),
|
"N",
|
||||||
"",
|
"",
|
||||||
i18n.G("Git (user) name to do commits with"),
|
"Git (user) name to do commits with",
|
||||||
)
|
)
|
||||||
|
|
||||||
RecipeNewCommand.Flags().StringVarP(
|
RecipeNewCommand.Flags().StringVarP(
|
||||||
&gitEmail,
|
&gitEmail,
|
||||||
i18n.G("git-email"),
|
"git-email",
|
||||||
i18n.G("e"),
|
"e",
|
||||||
"",
|
"",
|
||||||
i18n.G("Git email name to do commits with"),
|
"Git email name to do commits with",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,24 +1,13 @@
|
|||||||
package recipe
|
package recipe
|
||||||
|
|
||||||
import (
|
import "github.com/spf13/cobra"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// translators: `abra recipe` aliases. use a comma separated list of aliases
|
|
||||||
// with no spaces in between
|
|
||||||
var recipeAliases = i18n.G("r")
|
|
||||||
|
|
||||||
// RecipeCommand defines all recipe related sub-commands.
|
// RecipeCommand defines all recipe related sub-commands.
|
||||||
var RecipeCommand = &cobra.Command{
|
var RecipeCommand = &cobra.Command{
|
||||||
// translators: `recipe` command group
|
Use: "recipe [cmd] [args] [flags]",
|
||||||
Use: i18n.G("recipe [cmd] [args] [flags]"),
|
Aliases: []string{"r"},
|
||||||
Aliases: strings.Split(recipeAliases, ","),
|
Short: "Manage recipes",
|
||||||
// translators: Short description for `recipe` command group
|
Long: `A recipe is a blueprint for an app.
|
||||||
Short: i18n.G("Manage recipes"),
|
|
||||||
Long: i18n.G(`A recipe is a blueprint for an app.
|
|
||||||
|
|
||||||
It is a bunch of config files which describe how to deploy and maintain an app.
|
It is a bunch of config files which describe how to deploy and maintain an app.
|
||||||
Recipes are maintained by the Co-op Cloud community and you can use Abra to
|
Recipes are maintained by the Co-op Cloud community and you can use Abra to
|
||||||
@ -26,5 +15,5 @@ read them, deploy them and create apps for you.
|
|||||||
|
|
||||||
Anyone who uses a recipe can become a maintainer. Maintainers typically make
|
Anyone who uses a recipe can become a maintainer. Maintainers typically make
|
||||||
sure the recipe is in good working order and the config upgraded in a timely
|
sure the recipe is in good working order and the config upgraded in a timely
|
||||||
manner.`),
|
manner.`,
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,28 +12,20 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"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/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"coopcloud.tech/tagcmp"
|
"coopcloud.tech/tagcmp"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/go-git/go-git/v5/plumbing"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra recipe release` aliases. use a comma separated list of
|
|
||||||
// aliases with no spaces in between
|
|
||||||
var recipeReleaseAliases = i18n.G("rl")
|
|
||||||
|
|
||||||
var RecipeReleaseCommand = &cobra.Command{
|
var RecipeReleaseCommand = &cobra.Command{
|
||||||
// translators: `recipe release` command
|
Use: "release <recipe> [version] [flags]",
|
||||||
Use: i18n.G("release <recipe> [version] [flags]"),
|
Aliases: []string{"rl"},
|
||||||
Aliases: strings.Split(recipeReleaseAliases, ","),
|
Short: "Release a new recipe version",
|
||||||
// translators: Short description for `recipe release` command
|
Long: `Create a new version of a recipe.
|
||||||
Short: i18n.G("Release a new recipe version"),
|
|
||||||
Long: i18n.G(`Create a new version of a recipe.
|
|
||||||
|
|
||||||
These versions are then published on the Co-op Cloud recipe catalogue. These
|
These versions are then published on the Co-op Cloud recipe catalogue. These
|
||||||
versions take the following form:
|
versions take the following form:
|
||||||
@ -52,15 +44,7 @@ major and therefore require intervention while doing the upgrade work.
|
|||||||
|
|
||||||
Publish your new release to git.coopcloud.tech with "--publish/-p". This
|
Publish your new release to git.coopcloud.tech with "--publish/-p". This
|
||||||
requires that you have permission to git push to these repositories and have
|
requires that you have permission to git push to these repositories and have
|
||||||
your SSH keys configured on your account. Enable ssh-agent and make sure to add
|
your SSH keys configured on your account.`,
|
||||||
your private key and enter your passphrase beforehand.
|
|
||||||
|
|
||||||
eval ` + "`ssh-agent`" + `
|
|
||||||
ssh-add ~/.ssh/<my-ssh-private-key-for-git-coopcloud-tech>`),
|
|
||||||
Example: ` # publish release
|
|
||||||
eval ` + "`ssh-agent`" + `
|
|
||||||
ssh-add ~/.ssh/id_ed25519
|
|
||||||
abra recipe release gitea -p`,
|
|
||||||
Args: cobra.RangeArgs(1, 2),
|
Args: cobra.RangeArgs(1, 2),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -78,7 +62,7 @@ your private key and enter your passphrase beforehand.
|
|||||||
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())
|
||||||
|
|
||||||
imagesTmp, err := GetImageVersions(recipe)
|
imagesTmp, err := getImageVersions(recipe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -90,7 +74,7 @@ your private key and enter your passphrase beforehand.
|
|||||||
|
|
||||||
mainAppVersion := imagesTmp[mainApp]
|
mainAppVersion := imagesTmp[mainApp]
|
||||||
if mainAppVersion == "" {
|
if mainAppVersion == "" {
|
||||||
log.Fatal(i18n.G("main app service version for %s is empty?", recipe.Name))
|
log.Fatalf("main app service version for %s is empty?", recipe.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
var tagString string
|
var tagString string
|
||||||
@ -100,32 +84,16 @@ your private key and enter your passphrase beforehand.
|
|||||||
|
|
||||||
if tagString != "" {
|
if tagString != "" {
|
||||||
if _, err := tagcmp.Parse(tagString); err != nil {
|
if _, err := tagcmp.Parse(tagString); err != nil {
|
||||||
log.Fatal(i18n.G("cannot parse %s, invalid tag specified?", tagString))
|
log.Fatalf("cannot parse %s, invalid tag specified?", tagString)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (internal.Major || internal.Minor || internal.Patch) && tagString != "" {
|
if (internal.Major || internal.Minor || internal.Patch) && tagString != "" {
|
||||||
log.Fatal(i18n.G("cannot specify tag and bump type at the same time"))
|
log.Fatal("cannot specify tag and bump type at the same time")
|
||||||
}
|
|
||||||
|
|
||||||
repo, err := git.PlainOpen(recipe.Dir)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
preCommitHead, err := repo.Head()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if tagString != "" {
|
if tagString != "" {
|
||||||
if err := createReleaseFromTag(recipe, tagString, mainAppVersion); err != nil {
|
if err := createReleaseFromTag(recipe, tagString, mainAppVersion); err != nil {
|
||||||
if cleanErr := cleanTag(recipe, tagString); cleanErr != nil {
|
|
||||||
log.Fatal(cleanErr)
|
|
||||||
}
|
|
||||||
if cleanErr := cleanCommit(recipe, preCommitHead); cleanErr != nil {
|
|
||||||
log.Fatal(cleanErr)
|
|
||||||
}
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -135,21 +103,12 @@ your private key and enter your passphrase beforehand.
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
labelVersion, err := getLabelVersion(recipe, false)
|
if tagString == "" && (!internal.Major && !internal.Minor && !internal.Patch) {
|
||||||
|
var err error
|
||||||
|
tagString, err = getLabelVersion(recipe, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tag := range tags {
|
|
||||||
previousTagLeftHand := strings.Split(tag, "+")[0]
|
|
||||||
newTagStringLeftHand := strings.Split(labelVersion, "+")[0]
|
|
||||||
if previousTagLeftHand == newTagStringLeftHand {
|
|
||||||
log.Fatal(i18n.G("%s+... conflicts with a previous release: %s", newTagStringLeftHand, tag))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if tagString == "" && (!internal.Major && !internal.Minor && !internal.Patch) {
|
|
||||||
tagString = labelVersion
|
|
||||||
}
|
}
|
||||||
|
|
||||||
isClean, err := gitPkg.IsClean(recipe.Dir)
|
isClean, err := gitPkg.IsClean(recipe.Dir)
|
||||||
@ -158,33 +117,23 @@ your private key and enter your passphrase beforehand.
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !isClean {
|
if !isClean {
|
||||||
log.Info(i18n.G("%s currently has these unstaged changes 👇", recipe.Name))
|
log.Infof("%s currently has these unstaged changes 👇", recipe.Name)
|
||||||
if err := gitPkg.DiffUnstaged(recipe.Dir); err != nil {
|
if err := gitPkg.DiffUnstaged(recipe.Dir); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(tags) > 0 {
|
if len(tags) > 0 {
|
||||||
log.Warn(i18n.G("previous git tags detected, assuming new semver release"))
|
log.Warnf("previous git tags detected, assuming this is a new semver release")
|
||||||
|
|
||||||
if err := createReleaseFromPreviousTag(tagString, mainAppVersion, recipe, tags); err != nil {
|
if err := createReleaseFromPreviousTag(tagString, mainAppVersion, recipe, tags); err != nil {
|
||||||
if cleanErr := cleanTag(recipe, tagString); cleanErr != nil {
|
|
||||||
log.Fatal(cleanErr)
|
|
||||||
}
|
|
||||||
if cleanErr := cleanCommit(recipe, preCommitHead); cleanErr != nil {
|
|
||||||
log.Fatal(cleanErr)
|
|
||||||
}
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Warn(i18n.G("no tag specified and no previous tag available for %s, assuming initial release", recipe.Name))
|
log.Warnf("no tag specified and no previous tag available for %s, assuming this is the initial release", recipe.Name)
|
||||||
|
|
||||||
if err := createReleaseFromTag(recipe, tagString, mainAppVersion); err != nil {
|
if err := createReleaseFromTag(recipe, tagString, mainAppVersion); err != nil {
|
||||||
if cleanErr := cleanTag(recipe, tagString); cleanErr != nil {
|
if cleanUpErr := cleanUpTag(recipe, tagString); err != nil {
|
||||||
log.Fatal(cleanErr)
|
log.Fatal(cleanUpErr)
|
||||||
}
|
|
||||||
if cleanErr := cleanCommit(recipe, preCommitHead); cleanErr != nil {
|
|
||||||
log.Fatal(cleanErr)
|
|
||||||
}
|
}
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -194,8 +143,8 @@ your private key and enter your passphrase beforehand.
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetImageVersions retrieves image versions for a recipe
|
// getImageVersions retrieves image versions for a recipe
|
||||||
func GetImageVersions(recipe recipe.Recipe) (map[string]string, error) {
|
func getImageVersions(recipe recipe.Recipe) (map[string]string, error) {
|
||||||
services := make(map[string]string)
|
services := make(map[string]string)
|
||||||
|
|
||||||
config, err := recipe.GetComposeConfig(nil)
|
config, err := recipe.GetComposeConfig(nil)
|
||||||
@ -232,7 +181,7 @@ func GetImageVersions(recipe recipe.Recipe) (map[string]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if missingTag {
|
if missingTag {
|
||||||
return services, errors.New(i18n.G("app service is missing image tag?"))
|
return services, fmt.Errorf("app service is missing image tag?")
|
||||||
}
|
}
|
||||||
|
|
||||||
return services, nil
|
return services, nil
|
||||||
@ -267,19 +216,19 @@ func createReleaseFromTag(recipe recipe.Recipe, tagString, mainAppVersion string
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := addReleaseNotes(recipe, tagString); err != nil {
|
if err := addReleaseNotes(recipe, tagString); err != nil {
|
||||||
return errors.New(i18n.G("failed to add release notes: %s", err.Error()))
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := commitRelease(recipe, tagString); err != nil {
|
if err := commitRelease(recipe, tagString); err != nil {
|
||||||
return errors.New(i18n.G("failed to commit changes: %s", err.Error()))
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tagRelease(tagString, repo); err != nil {
|
if err := tagRelease(tagString, repo); err != nil {
|
||||||
return errors.New(i18n.G("failed to tag release: %s", err.Error()))
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pushRelease(recipe, tagString); err != nil {
|
if err := pushRelease(recipe, tagString); err != nil {
|
||||||
return errors.New(i18n.G("failed to publish new release: %s", err.Error()))
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -296,7 +245,7 @@ func btoi(b bool) int {
|
|||||||
|
|
||||||
// getTagCreateOptions constructs git tag create options
|
// getTagCreateOptions constructs git tag create options
|
||||||
func getTagCreateOptions(tag string) (git.CreateTagOptions, error) {
|
func getTagCreateOptions(tag string) (git.CreateTagOptions, error) {
|
||||||
msg := i18n.G("chore: publish %s release", tag)
|
msg := fmt.Sprintf("chore: publish %s release", tag)
|
||||||
return git.CreateTagOptions{Message: msg}, nil
|
return git.CreateTagOptions{Message: msg}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,13 +273,13 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
|
|||||||
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>
|
||||||
if internal.Dry {
|
if internal.Dry {
|
||||||
log.Debug(i18n.G("dry run: move release note from 'next' to %s", tag))
|
log.Debugf("dry run: move release note from 'next' to %s", tag)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !internal.NoInput {
|
if !internal.NoInput {
|
||||||
prompt := &survey.Confirm{
|
prompt := &survey.Confirm{
|
||||||
Message: i18n.G("use release note in release/next?"),
|
Message: "Use release note in release/next?",
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := survey.AskOne(prompt, &addNextAsReleaseNotes); err != nil {
|
if err := survey.AskOne(prompt, &addNextAsReleaseNotes); err != nil {
|
||||||
@ -364,7 +313,7 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
prompt := &survey.Input{
|
prompt := &survey.Input{
|
||||||
Message: i18n.G("add release note? (leave empty to skip)"),
|
Message: "Release Note (leave empty for no release note)",
|
||||||
}
|
}
|
||||||
|
|
||||||
var releaseNote string
|
var releaseNote string
|
||||||
@ -389,7 +338,7 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error {
|
|||||||
|
|
||||||
func commitRelease(recipe recipe.Recipe, tag string) error {
|
func commitRelease(recipe recipe.Recipe, tag string) error {
|
||||||
if internal.Dry {
|
if internal.Dry {
|
||||||
log.Debug(i18n.G("dry run: no changes committed"))
|
log.Debugf("dry run: no changes committed")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,7 +349,7 @@ func commitRelease(recipe recipe.Recipe, tag string) error {
|
|||||||
|
|
||||||
if isClean {
|
if isClean {
|
||||||
if !internal.Dry {
|
if !internal.Dry {
|
||||||
return errors.New(i18n.G("no changes discovered in %s, nothing to publish?", recipe.Dir))
|
return fmt.Errorf("no changes discovered in %s, nothing to publish?", recipe.Dir)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -414,7 +363,7 @@ func commitRelease(recipe recipe.Recipe, tag string) error {
|
|||||||
|
|
||||||
func tagRelease(tagString string, repo *git.Repository) error {
|
func tagRelease(tagString string, repo *git.Repository) error {
|
||||||
if internal.Dry {
|
if internal.Dry {
|
||||||
log.Debug(i18n.G("dry run: no git tag created (%s)", tagString))
|
log.Debugf("dry run: no git tag created (%s)", tagString)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -434,20 +383,20 @@ func tagRelease(tagString string, repo *git.Repository) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
hash := formatter.SmallSHA(head.Hash().String())
|
hash := formatter.SmallSHA(head.Hash().String())
|
||||||
log.Debug(i18n.G("created tag %s at %s", tagString, hash))
|
log.Debugf(fmt.Sprintf("created tag %s at %s", tagString, hash))
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func pushRelease(recipe recipe.Recipe, tagString string) error {
|
func pushRelease(recipe recipe.Recipe, tagString string) error {
|
||||||
if internal.Dry {
|
if internal.Dry {
|
||||||
log.Info(i18n.G("dry run: no changes published"))
|
log.Info("dry run: no changes published")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if !publish && !internal.NoInput {
|
if !publish && !internal.NoInput {
|
||||||
prompt := &survey.Confirm{
|
prompt := &survey.Confirm{
|
||||||
Message: i18n.G("publish new release?"),
|
Message: "publish new release?",
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := survey.AskOne(prompt, &publish); err != nil {
|
if err := survey.AskOne(prompt, &publish); err != nil {
|
||||||
@ -456,18 +405,13 @@ func pushRelease(recipe recipe.Recipe, tagString string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if publish {
|
if publish {
|
||||||
if os.Getenv("SSH_AUTH_SOCK") == "" {
|
|
||||||
return errors.New(i18n.G("ssh-agent not found. see \"abra recipe release --help\" and try again"))
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := recipe.Push(internal.Dry); err != nil {
|
if err := recipe.Push(internal.Dry); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
url := fmt.Sprintf("%s/src/tag/%s", recipe.GitURL, tagString)
|
url := fmt.Sprintf("%s/src/tag/%s", recipe.GitURL, tagString)
|
||||||
log.Info(i18n.G("new release published: %s", url))
|
log.Infof("new release published: %s", url)
|
||||||
} else {
|
} else {
|
||||||
log.Info(i18n.G("no -p/--publish passed, not publishing"))
|
log.Info("no -p/--publish passed, not publishing")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -482,7 +426,7 @@ func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recip
|
|||||||
bumpType := btoi(internal.Major)*4 + btoi(internal.Minor)*2 + btoi(internal.Patch)
|
bumpType := btoi(internal.Major)*4 + btoi(internal.Minor)*2 + btoi(internal.Patch)
|
||||||
if bumpType != 0 {
|
if bumpType != 0 {
|
||||||
if (bumpType & (bumpType - 1)) != 0 {
|
if (bumpType & (bumpType - 1)) != 0 {
|
||||||
return errors.New(i18n.G("you can only use one of: --major, --minor, --patch"))
|
return fmt.Errorf("you can only use one of: --major, --minor, --patch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -527,86 +471,69 @@ func createReleaseFromPreviousTag(tagString, mainAppVersion string, recipe recip
|
|||||||
newTag.Major = strconv.Itoa(now + 1)
|
newTag.Major = strconv.Itoa(now + 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if tagString == "" {
|
||||||
|
if err := internal.PromptBumpType(tagString, lastGitTag.String()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if internal.Major || internal.Minor || internal.Patch {
|
if internal.Major || internal.Minor || internal.Patch {
|
||||||
newTag.Metadata = mainAppVersion
|
newTag.Metadata = mainAppVersion
|
||||||
tagString = newTag.String()
|
tagString = newTag.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
if lastGitTag.String() == tagString {
|
if lastGitTag.String() == tagString {
|
||||||
return errors.New(i18n.G("latest git tag (%s) and synced label (%s) are the same?", lastGitTag, tagString))
|
log.Fatalf("latest git tag (%s) and synced label (%s) are the same?", lastGitTag, tagString)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !internal.NoInput {
|
if !internal.NoInput {
|
||||||
prompt := &survey.Confirm{
|
prompt := &survey.Confirm{
|
||||||
Message: i18n.G("current: %s, new: %s, correct?", lastGitTag, tagString),
|
Message: fmt.Sprintf("current: %s, new: %s, correct?", lastGitTag, tagString),
|
||||||
}
|
}
|
||||||
|
|
||||||
var ok bool
|
var ok bool
|
||||||
if err := survey.AskOne(prompt, &ok); err != nil {
|
if err := survey.AskOne(prompt, &ok); err != nil {
|
||||||
return err
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return errors.New(i18n.G("exiting as requested"))
|
log.Fatal("exiting as requested")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := addReleaseNotes(recipe, tagString); err != nil {
|
if err := addReleaseNotes(recipe, tagString); err != nil {
|
||||||
return errors.New(i18n.G("failed to add release notes: %s", err.Error()))
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := commitRelease(recipe, tagString); err != nil {
|
if err := commitRelease(recipe, tagString); err != nil {
|
||||||
return errors.New(i18n.G("failed to commit changes: %s", err.Error()))
|
log.Fatalf("failed to commit changes: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := tagRelease(tagString, repo); err != nil {
|
if err := tagRelease(tagString, repo); err != nil {
|
||||||
return errors.New(i18n.G("failed to tag release: %s", err.Error()))
|
log.Fatalf("failed to tag release: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := pushRelease(recipe, tagString); err != nil {
|
if err := pushRelease(recipe, tagString); err != nil {
|
||||||
return errors.New(i18n.G("failed to publish new release: %s", err.Error()))
|
log.Fatalf("failed to publish new release: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanCommit soft removes the latest release commit. No change are lost the
|
// cleanUpTag removes a freshly created tag
|
||||||
// the commit itself is removed. This is the equivalent of `git reset HEAD~1`.
|
func cleanUpTag(recipe recipe.Recipe, tag string) error {
|
||||||
func cleanCommit(recipe recipe.Recipe, head *plumbing.Reference) error {
|
|
||||||
repo, err := git.PlainOpen(recipe.Dir)
|
repo, err := git.PlainOpen(recipe.Dir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(i18n.G("unable to open repo in %s: %s", recipe.Dir, err))
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
worktree, err := repo.Worktree()
|
|
||||||
if err != nil {
|
|
||||||
return errors.New(i18n.G("unable to open work tree in %s: %s", recipe.Dir, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
opts := &git.ResetOptions{Commit: head.Hash(), Mode: git.MixedReset}
|
|
||||||
if err := worktree.Reset(opts); err != nil {
|
|
||||||
return errors.New(i18n.G("unable to soft reset %s: %s", recipe.Dir, err))
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debug(i18n.G("removed freshly created commit"))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// cleanTag removes a freshly created tag
|
|
||||||
func cleanTag(recipe recipe.Recipe, tag string) error {
|
|
||||||
repo, err := git.PlainOpen(recipe.Dir)
|
|
||||||
if err != nil {
|
|
||||||
return errors.New(i18n.G("unable to open repo in %s: %s", recipe.Dir, err))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := repo.DeleteTag(tag); err != nil {
|
if err := repo.DeleteTag(tag); err != nil {
|
||||||
if !strings.Contains(err.Error(), "not found") {
|
if !strings.Contains(err.Error(), "not found") {
|
||||||
return errors.New(i18n.G("unable to delete tag %s: %s", tag, err))
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("removed freshly created tag %s", tag))
|
log.Debugf("removed freshly created tag %s", tag)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -618,20 +545,20 @@ func getLabelVersion(recipe recipe.Recipe, prompt bool) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if initTag == "" {
|
if initTag == "" {
|
||||||
return "", errors.New(i18n.G("unable to read version for %s from synced label. Did you try running \"abra recipe sync %s\" already?", recipe.Name, recipe.Name))
|
log.Fatalf("unable to read version for %s from synced label. Did you try running \"abra recipe sync %s\" already?", recipe.Name, recipe.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Warn(i18n.G("discovered %s as currently synced recipe label", initTag))
|
log.Warnf("discovered %s as currently synced recipe label", initTag)
|
||||||
|
|
||||||
if prompt && !internal.NoInput {
|
if prompt && !internal.NoInput {
|
||||||
var response bool
|
var response bool
|
||||||
prompt := &survey.Confirm{Message: i18n.G("use %s as the new version?", initTag)}
|
prompt := &survey.Confirm{Message: fmt.Sprintf("use %s as the new version?", initTag)}
|
||||||
if err := survey.AskOne(prompt, &response); err != nil {
|
if err := survey.AskOne(prompt, &response); err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
if !response {
|
if !response {
|
||||||
return "", errors.New(i18n.G("please fix your synced label for %s and re-run this command", recipe.Name))
|
return "", fmt.Errorf("please fix your synced label for %s and re-run this command", recipe.Name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -645,41 +572,42 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
RecipeReleaseCommand.Flags().BoolVarP(
|
RecipeReleaseCommand.Flags().BoolVarP(
|
||||||
&internal.Dry,
|
&internal.Dry,
|
||||||
i18n.G("dry-run"),
|
"dry-run",
|
||||||
i18n.G("r"),
|
"r",
|
||||||
false,
|
false,
|
||||||
i18n.G("report changes that would be made"),
|
"report changes that would be made",
|
||||||
)
|
)
|
||||||
|
|
||||||
RecipeReleaseCommand.Flags().BoolVarP(
|
RecipeReleaseCommand.Flags().BoolVarP(
|
||||||
&internal.Major,
|
&internal.Major,
|
||||||
i18n.G("major"),
|
"major",
|
||||||
i18n.G("x"),
|
"x",
|
||||||
false,
|
false,
|
||||||
i18n.G("increase the major part of the version"),
|
"increase the major part of the version",
|
||||||
)
|
)
|
||||||
|
|
||||||
RecipeReleaseCommand.Flags().BoolVarP(
|
RecipeReleaseCommand.Flags().BoolVarP(
|
||||||
&internal.Minor,
|
&internal.Minor,
|
||||||
i18n.G("minor"),
|
"minor",
|
||||||
i18n.G("y"),
|
"y",
|
||||||
false,
|
false,
|
||||||
i18n.G("increase the minor part of the version"),
|
"increase the minor part of the version",
|
||||||
)
|
)
|
||||||
|
|
||||||
RecipeReleaseCommand.Flags().BoolVarP(
|
RecipeReleaseCommand.Flags().BoolVarP(
|
||||||
&internal.Patch,
|
&internal.Patch,
|
||||||
i18n.G("patch"),
|
"patch",
|
||||||
i18n.G("z"),
|
"z",
|
||||||
false,
|
false,
|
||||||
i18n.G("increase the patch part of the version"),
|
"increase the patch part of the version",
|
||||||
)
|
)
|
||||||
|
|
||||||
RecipeReleaseCommand.Flags().BoolVarP(
|
RecipeReleaseCommand.Flags().BoolVarP(
|
||||||
&publish,
|
&publish,
|
||||||
i18n.G("publish"),
|
"publish",
|
||||||
i18n.G("p"),
|
"p",
|
||||||
false,
|
false,
|
||||||
i18n.G("publish changes to git.coopcloud.tech"),
|
"publish changes to git.coopcloud.tech",
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,27 +1,18 @@
|
|||||||
package recipe
|
package recipe
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra recipe reset` aliases. use a comma separated list of
|
|
||||||
// aliases with no spaces in between
|
|
||||||
var recipeResetAliases = i18n.G("rs")
|
|
||||||
|
|
||||||
var RecipeResetCommand = &cobra.Command{
|
var RecipeResetCommand = &cobra.Command{
|
||||||
// translators: `recipe reset` command
|
Use: "reset <recipe> [flags]",
|
||||||
Use: i18n.G("reset <recipe> [flags]"),
|
Aliases: []string{"rs"},
|
||||||
Aliases: strings.Split(recipeResetAliases, ","),
|
Short: "Remove all unstaged changes from recipe config",
|
||||||
// translators: Short description for `recipe reset` command
|
Long: "WARNING: this will delete your changes. Be Careful.",
|
||||||
Short: i18n.G("Remove all unstaged changes from recipe config"),
|
|
||||||
Long: i18n.G("WARNING: this will delete your changes. Be Careful."),
|
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
|
|||||||
@ -3,15 +3,11 @@ package recipe
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"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"
|
|
||||||
gitPkg "coopcloud.tech/abra/pkg/git"
|
gitPkg "coopcloud.tech/abra/pkg/git"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
|
||||||
"coopcloud.tech/tagcmp"
|
"coopcloud.tech/tagcmp"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
@ -19,17 +15,11 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra recipe reset` aliases. use a comma separated list of
|
|
||||||
// aliases with no spaces in between
|
|
||||||
var recipeSyncAliases = i18n.G("s")
|
|
||||||
|
|
||||||
var RecipeSyncCommand = &cobra.Command{
|
var RecipeSyncCommand = &cobra.Command{
|
||||||
// translators: `recipe sync` command
|
Use: "sync <recipe> [version] [flags]",
|
||||||
Use: i18n.G("sync <recipe> [version] [flags]"),
|
Aliases: []string{"s"},
|
||||||
Aliases: strings.Split(recipeSyncAliases, ","),
|
Short: "Sync recipe version label",
|
||||||
// translators: Short description for `recipe sync` command
|
Long: `Generate labels for the main recipe service.
|
||||||
Short: i18n.G("Sync recipe version label"),
|
|
||||||
Long: i18n.G(`Generate labels for the main recipe service.
|
|
||||||
|
|
||||||
By convention, the service named "app" using the following format:
|
By convention, the service named "app" using the following format:
|
||||||
|
|
||||||
@ -37,13 +27,12 @@ By convention, the service named "app" using the following format:
|
|||||||
|
|
||||||
Where [version] can be specifed on the command-line or Abra can attempt to
|
Where [version] can be specifed on the command-line or Abra can attempt to
|
||||||
auto-generate it for you. The <recipe> configuration will be updated on the
|
auto-generate it for you. The <recipe> configuration will be updated on the
|
||||||
local file system.`),
|
local file system.`,
|
||||||
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.RecipeNameComplete()
|
return autocomplete.RecipeNameComplete()
|
||||||
@ -61,7 +50,7 @@ local file system.`),
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
imagesTmp, err := GetImageVersions(recipe)
|
imagesTmp, err := getImageVersions(recipe)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -79,11 +68,11 @@ local file system.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(tags) == 0 && nextTag == "" {
|
if len(tags) == 0 && nextTag == "" {
|
||||||
log.Warn(i18n.G("no git tags found for %s", recipe.Name))
|
log.Warnf("no git tags found for %s", recipe.Name)
|
||||||
if internal.NoInput {
|
if internal.NoInput {
|
||||||
log.Fatal(i18n.G("unable to continue, input required for initial version"))
|
log.Fatalf("unable to continue, input required for initial version")
|
||||||
}
|
}
|
||||||
fmt.Println(i18n.G(`
|
fmt.Println(fmt.Sprintf(`
|
||||||
The following options are two types of initial semantic version that you can
|
The following options are two types of initial semantic version that you can
|
||||||
pick for %s that will be published in the recipe catalogue. This follows the
|
pick for %s that will be published in the recipe catalogue. This follows the
|
||||||
semver convention (more on https://semver.org), here is a short cheatsheet
|
semver convention (more on https://semver.org), here is a short cheatsheet
|
||||||
@ -103,7 +92,7 @@ likely to change.
|
|||||||
`, recipe.Name, recipe.Name))
|
`, recipe.Name, recipe.Name))
|
||||||
var chosenVersion string
|
var chosenVersion string
|
||||||
edPrompt := &survey.Select{
|
edPrompt := &survey.Select{
|
||||||
Message: i18n.G("which version do you want to begin with?"),
|
Message: "which version do you want to begin with?",
|
||||||
Options: []string{"0.1.0", "1.0.0"},
|
Options: []string{"0.1.0", "1.0.0"},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,54 +104,8 @@ likely to change.
|
|||||||
}
|
}
|
||||||
|
|
||||||
if nextTag == "" && (!internal.Major && !internal.Minor && !internal.Patch) {
|
if nextTag == "" && (!internal.Major && !internal.Minor && !internal.Patch) {
|
||||||
var changeOverview string
|
latestRelease := tags[len(tags)-1]
|
||||||
|
if err := internal.PromptBumpType("", latestRelease); err != nil {
|
||||||
catl, err := recipePkg.ReadRecipeCatalogue(false)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
versions, err := recipePkg.GetRecipeCatalogueVersions(recipe.Name, catl)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
changesTable, err := formatter.CreateTable()
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
latestRelease := "0.0.0+0.0.0"
|
|
||||||
if len(tags) > 0 {
|
|
||||||
latestRelease = tags[len(tags)-1]
|
|
||||||
}
|
|
||||||
changesTable.Headers(i18n.G("SERVICE"), latestRelease, i18n.G("PROPOSED CHANGES"))
|
|
||||||
|
|
||||||
latestRecipeVersion := latestRelease
|
|
||||||
if len(versions) > 0 {
|
|
||||||
latestRecipeVersion = versions[len(versions)-1]
|
|
||||||
}
|
|
||||||
allRecipeVersions := catl[recipe.Name].Versions
|
|
||||||
for _, recipeVersion := range allRecipeVersions {
|
|
||||||
if serviceVersions, ok := recipeVersion[latestRecipeVersion]; ok {
|
|
||||||
for serviceName := range serviceVersions {
|
|
||||||
serviceMeta := serviceVersions[serviceName]
|
|
||||||
|
|
||||||
existingImageTag := fmt.Sprintf("%s:%s", serviceMeta.Image, serviceMeta.Tag)
|
|
||||||
newImageTag := fmt.Sprintf("%s:%s", serviceMeta.Image, imagesTmp[serviceMeta.Image])
|
|
||||||
|
|
||||||
if existingImageTag == newImageTag {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
changesTable.Row([]string{serviceName, existingImageTag, newImageTag}...)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
changeOverview = changesTable.Render()
|
|
||||||
|
|
||||||
if err := internal.PromptBumpType("", latestRelease, changeOverview); err != nil {
|
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -182,7 +125,7 @@ likely to change.
|
|||||||
if err := iter.ForEach(func(ref *plumbing.Reference) error {
|
if err := iter.ForEach(func(ref *plumbing.Reference) error {
|
||||||
obj, err := repo.TagObject(ref.Hash())
|
obj, err := repo.TagObject(ref.Hash())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(i18n.G("tag at commit %s is unannotated or otherwise broken", ref.Hash()))
|
log.Fatal("Tag at commit ", ref.Hash(), " is unannotated or otherwise broken. Please fix it.")
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +150,7 @@ likely to change.
|
|||||||
if bumpType != 0 {
|
if bumpType != 0 {
|
||||||
// a bitwise check if the number is a power of 2
|
// a bitwise check if the number is a power of 2
|
||||||
if (bumpType & (bumpType - 1)) != 0 {
|
if (bumpType & (bumpType - 1)) != 0 {
|
||||||
log.Fatal(i18n.G("you can only use one version flag: --major, --minor or --patch"))
|
log.Fatal("you can only use one version flag: --major, --minor or --patch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -241,22 +184,22 @@ likely to change.
|
|||||||
}
|
}
|
||||||
|
|
||||||
newTag.Metadata = mainAppVersion
|
newTag.Metadata = mainAppVersion
|
||||||
log.Debug(i18n.G("choosing %s as new version for %s", newTag.String(), recipe.Name))
|
log.Debugf("choosing %s as new version for %s", newTag.String(), recipe.Name)
|
||||||
nextTag = newTag.String()
|
nextTag = newTag.String()
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := tagcmp.Parse(nextTag); err != nil {
|
if _, err := tagcmp.Parse(nextTag); err != nil {
|
||||||
log.Fatal(i18n.G("invalid version %s specified", nextTag))
|
log.Fatalf("invalid version %s specified", nextTag)
|
||||||
}
|
}
|
||||||
|
|
||||||
mainService := "app"
|
mainService := "app"
|
||||||
label := i18n.G("coop-cloud.${STACK_NAME}.version=%s", nextTag)
|
label := fmt.Sprintf("coop-cloud.${STACK_NAME}.version=%s", nextTag)
|
||||||
if !internal.Dry {
|
if !internal.Dry {
|
||||||
if err := recipe.UpdateLabel("compose.y*ml", mainService, label); err != nil {
|
if err := recipe.UpdateLabel("compose.y*ml", mainService, label); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Info(i18n.G("dry run: not syncing label %s for recipe %s", nextTag, recipe.Name))
|
log.Infof("dry run: not syncing label %s for recipe %s", nextTag, recipe.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
isClean, err := gitPkg.IsClean(recipe.Dir)
|
isClean, err := gitPkg.IsClean(recipe.Dir)
|
||||||
@ -264,7 +207,7 @@ likely to change.
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
if !isClean {
|
if !isClean {
|
||||||
log.Info(i18n.G("%s currently has these unstaged changes 👇", recipe.Name))
|
log.Infof("%s currently has these unstaged changes 👇", recipe.Name)
|
||||||
if err := gitPkg.DiffUnstaged(recipe.Dir); err != nil {
|
if err := gitPkg.DiffUnstaged(recipe.Dir); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -275,33 +218,33 @@ likely to change.
|
|||||||
func init() {
|
func init() {
|
||||||
RecipeSyncCommand.Flags().BoolVarP(
|
RecipeSyncCommand.Flags().BoolVarP(
|
||||||
&internal.Dry,
|
&internal.Dry,
|
||||||
i18n.G("dry-run"),
|
"dry-run",
|
||||||
i18n.G("r"),
|
"r",
|
||||||
false,
|
false,
|
||||||
i18n.G("report changes that would be made"),
|
"report changes that would be made",
|
||||||
)
|
)
|
||||||
|
|
||||||
RecipeSyncCommand.Flags().BoolVarP(
|
RecipeSyncCommand.Flags().BoolVarP(
|
||||||
&internal.Major,
|
&internal.Major,
|
||||||
i18n.G("major"),
|
"major",
|
||||||
i18n.G("x"),
|
"x",
|
||||||
false,
|
false,
|
||||||
i18n.G("increase the major part of the version"),
|
"increase the major part of the version",
|
||||||
)
|
)
|
||||||
|
|
||||||
RecipeSyncCommand.Flags().BoolVarP(
|
RecipeSyncCommand.Flags().BoolVarP(
|
||||||
&internal.Minor,
|
&internal.Minor,
|
||||||
i18n.G("minor"),
|
"minor",
|
||||||
i18n.G("y"),
|
"y",
|
||||||
false,
|
false,
|
||||||
i18n.G("increase the minor part of the version"),
|
"increase the minor part of the version",
|
||||||
)
|
)
|
||||||
|
|
||||||
RecipeSyncCommand.Flags().BoolVarP(
|
RecipeSyncCommand.Flags().BoolVarP(
|
||||||
&internal.Patch,
|
&internal.Patch,
|
||||||
i18n.G("patch"),
|
"patch",
|
||||||
i18n.G("z"),
|
"z",
|
||||||
false,
|
false,
|
||||||
i18n.G("increase the patch part of the version"),
|
"increase the patch part of the version",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,7 +14,6 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"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/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||||
"coopcloud.tech/tagcmp"
|
"coopcloud.tech/tagcmp"
|
||||||
@ -37,17 +36,11 @@ type anUpgrade struct {
|
|||||||
UpgradeTags []string `json:"upgrades"`
|
UpgradeTags []string `json:"upgrades"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// translators: `abra recipe upgrade` aliases. use a comma separated list of
|
|
||||||
// aliases with no spaces in between
|
|
||||||
var recipeUpgradeAliases = i18n.G("u")
|
|
||||||
|
|
||||||
var RecipeUpgradeCommand = &cobra.Command{
|
var RecipeUpgradeCommand = &cobra.Command{
|
||||||
// translators: `recipe upgrade` command
|
Use: "upgrade <recipe> [flags]",
|
||||||
Use: i18n.G("upgrade <recipe> [flags]"),
|
Aliases: []string{"u"},
|
||||||
Aliases: strings.Split(recipeUpgradeAliases, ","),
|
Short: "Upgrade recipe image tags",
|
||||||
// translators: Short description for `recipe upgrade` command
|
Long: `Upgrade a given <recipe> configuration.
|
||||||
Short: i18n.G("Upgrade recipe image tags"),
|
|
||||||
Long: i18n.G(`Upgrade a given <recipe> configuration.
|
|
||||||
|
|
||||||
It will update the relevant compose file tags on the local file system.
|
It will update the relevant compose file tags on the local file system.
|
||||||
|
|
||||||
@ -59,7 +52,7 @@ The command is interactive and will show a select input which allows you to
|
|||||||
make a seclection. Use the "?" key to see more help on navigating this
|
make a seclection. Use the "?" key to see more help on navigating this
|
||||||
interface.
|
interface.
|
||||||
|
|
||||||
You may invoke this command in "wizard" mode and be prompted for input.`),
|
You may invoke this command in "wizard" mode and be prompted for input.`,
|
||||||
Args: cobra.RangeArgs(0, 1),
|
Args: cobra.RangeArgs(0, 1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -78,7 +71,7 @@ You may invoke this command in "wizard" mode and be prompted for input.`),
|
|||||||
if bumpType != 0 {
|
if bumpType != 0 {
|
||||||
// a bitwise check if the number is a power of 2
|
// a bitwise check if the number is a power of 2
|
||||||
if (bumpType & (bumpType - 1)) != 0 {
|
if (bumpType & (bumpType - 1)) != 0 {
|
||||||
log.Fatal(i18n.G("you can only use one of: --major, --minor, --patch."))
|
log.Fatal("you can only use one of: --major, --minor, --patch.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -94,7 +87,7 @@ You may invoke this command in "wizard" mode and be prompted for input.`),
|
|||||||
versionsPath := path.Join(recipe.Dir, "versions")
|
versionsPath := path.Join(recipe.Dir, "versions")
|
||||||
servicePins := make(map[string]imgPin)
|
servicePins := make(map[string]imgPin)
|
||||||
if _, err := os.Stat(versionsPath); err == nil {
|
if _, err := os.Stat(versionsPath); err == nil {
|
||||||
log.Debug(i18n.G("found versions file for %s", recipe.Name))
|
log.Debugf("found versions file for %s", recipe.Name)
|
||||||
file, err := os.Open(versionsPath)
|
file, err := os.Open(versionsPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -104,7 +97,7 @@ You may invoke this command in "wizard" mode and be prompted for input.`),
|
|||||||
line := scanner.Text()
|
line := scanner.Text()
|
||||||
splitLine := strings.Split(line, " ")
|
splitLine := strings.Split(line, " ")
|
||||||
if splitLine[0] != "pin" || len(splitLine) != 3 {
|
if splitLine[0] != "pin" || len(splitLine) != 3 {
|
||||||
log.Fatal(i18n.G("malformed version pin specification: %s", line))
|
log.Fatalf("malformed version pin specification: %s", line)
|
||||||
}
|
}
|
||||||
pinSlice := strings.Split(splitLine[2], ":")
|
pinSlice := strings.Split(splitLine[2], ":")
|
||||||
pinTag, err := tagcmp.Parse(pinSlice[1])
|
pinTag, err := tagcmp.Parse(pinSlice[1])
|
||||||
@ -122,7 +115,7 @@ You may invoke this command in "wizard" mode and be prompted for input.`),
|
|||||||
}
|
}
|
||||||
versionsPresent = true
|
versionsPresent = true
|
||||||
} else {
|
} else {
|
||||||
log.Debug(i18n.G("did not find versions file for %s", recipe.Name))
|
log.Debugf("did not find versions file for %s", recipe.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
config, err := recipe.GetComposeConfig(nil)
|
config, err := recipe.GetComposeConfig(nil)
|
||||||
@ -142,26 +135,26 @@ You may invoke this command in "wizard" mode and be prompted for input.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
image := reference.Path(img)
|
image := reference.Path(img)
|
||||||
log.Debug(i18n.G("retrieved %s from remote registry for %s", regVersions, image))
|
log.Debugf("retrieved %s from remote registry for %s", regVersions, image)
|
||||||
image = formatter.StripTagMeta(image)
|
image = formatter.StripTagMeta(image)
|
||||||
|
|
||||||
switch img.(type) {
|
switch img.(type) {
|
||||||
case reference.NamedTagged:
|
case reference.NamedTagged:
|
||||||
if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) {
|
if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) {
|
||||||
log.Debug(i18n.G("%s not considered semver-like", img.(reference.NamedTagged).Tag()))
|
log.Debugf("%s not considered semver-like", img.(reference.NamedTagged).Tag())
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
log.Warn(i18n.G("unable to read tag for image %s, is it missing? skipping upgrade for %s", image, service.Name))
|
log.Warnf("unable to read tag for image %s, is it missing? skipping upgrade for %s", image, service.Name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
tag, err := tagcmp.Parse(img.(reference.NamedTagged).Tag())
|
tag, err := tagcmp.Parse(img.(reference.NamedTagged).Tag())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(i18n.G("unable to parse %s, error was: %s, skipping upgrade for %s", image, err.Error(), service.Name))
|
log.Warnf("unable to parse %s, error was: %s, skipping upgrade for %s", image, err.Error(), service.Name)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("parsed %s for %s", tag, service.Name))
|
log.Debugf("parsed %s for %s", tag, service.Name)
|
||||||
|
|
||||||
var compatible []tagcmp.Tag
|
var compatible []tagcmp.Tag
|
||||||
for _, regVersion := range regVersions {
|
for _, regVersion := range regVersions {
|
||||||
@ -175,12 +168,12 @@ You may invoke this command in "wizard" mode and be prompted for input.`),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("detected potential upgradable tags %s for %s", compatible, service.Name))
|
log.Debugf("detected potential upgradable tags %s for %s", compatible, service.Name)
|
||||||
|
|
||||||
sort.Sort(tagcmp.ByTagDesc(compatible))
|
sort.Sort(tagcmp.ByTagDesc(compatible))
|
||||||
|
|
||||||
if len(compatible) == 0 && !allTags {
|
if len(compatible) == 0 && !allTags {
|
||||||
log.Info(i18n.G("no new versions available for %s, assuming %s is the latest (use -a/--all-tags to see all anyway)", image, tag))
|
log.Info(fmt.Sprintf("no new versions available for %s, assuming %s is the latest (use -a/--all-tags to see all anyway)", image, tag))
|
||||||
continue // skip on to the next tag and don't update any compose files
|
continue // skip on to the next tag and don't update any compose files
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -202,7 +195,7 @@ You may invoke this command in "wizard" mode and be prompted for input.`),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("detected compatible upgradable tags %s for %s", compatibleStrings, service.Name))
|
log.Debugf("detected compatible upgradable tags %s for %s", compatibleStrings, service.Name)
|
||||||
|
|
||||||
var upgradeTag string
|
var upgradeTag string
|
||||||
_, ok := servicePins[service.Name]
|
_, ok := servicePins[service.Name]
|
||||||
@ -219,13 +212,13 @@ You may invoke this command in "wizard" mode and be prompted for input.`),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if contains {
|
if contains {
|
||||||
log.Info(i18n.G("upgrading service %s from %s to %s (pinned tag: %s)", service.Name, tag.String(), upgradeTag, pinnedTagString))
|
log.Infof("upgrading service %s from %s to %s (pinned tag: %s)", service.Name, tag.String(), upgradeTag, pinnedTagString)
|
||||||
} else {
|
} else {
|
||||||
log.Info(i18n.G("service %s, image %s pinned to %s, no compatible upgrade found", service.Name, servicePins[service.Name].image, pinnedTagString))
|
log.Infof("service %s, image %s pinned to %s, no compatible upgrade found", service.Name, servicePins[service.Name].image, pinnedTagString)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Fatal(i18n.G("service %s is at version %s, but pinned to %s, please correct your compose.yml file manually!", service.Name, tag.String(), pinnedTag.String()))
|
log.Fatalf("service %s is at version %s, but pinned to %s, please correct your compose.yml file manually!", service.Name, tag.String(), pinnedTag.String())
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -242,17 +235,17 @@ You may invoke this command in "wizard" mode and be prompted for input.`),
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if upgradeTag == "" {
|
if upgradeTag == "" {
|
||||||
log.Warn(i18n.G("not upgrading from %s to %s for %s, because the upgrade type is more serious than what user wants", tag.String(), compatible[0].String(), image))
|
log.Warnf("not upgrading from %s to %s for %s, because the upgrade type is more serious than what user wants", tag.String(), compatible[0].String(), image)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
msg := i18n.G("upgrade to which tag? (service: %s, image: %s, tag: %s)", service.Name, image, tag)
|
msg := fmt.Sprintf("upgrade to which tag? (service: %s, image: %s, tag: %s)", service.Name, image, tag)
|
||||||
if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) || allTags {
|
if !tagcmp.IsParsable(img.(reference.NamedTagged).Tag()) || allTags {
|
||||||
tag := img.(reference.NamedTagged).Tag()
|
tag := img.(reference.NamedTagged).Tag()
|
||||||
if !allTags {
|
if !allTags {
|
||||||
log.Warn(i18n.G("unable to determine versioning semantics of %s, listing all tags", tag))
|
log.Warn(fmt.Sprintf("unable to determine versioning semantics of %s, listing all tags", tag))
|
||||||
}
|
}
|
||||||
msg = i18n.G("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag)
|
msg = fmt.Sprintf("upgrade to which tag? (service: %s, tag: %s)", service.Name, tag)
|
||||||
compatibleStrings = []string{"skip"}
|
compatibleStrings = []string{"skip"}
|
||||||
for _, regVersion := range regVersions {
|
for _, regVersion := range regVersions {
|
||||||
compatibleStrings = append(compatibleStrings, regVersion)
|
compatibleStrings = append(compatibleStrings, regVersion)
|
||||||
@ -283,7 +276,7 @@ You may invoke this command in "wizard" mode and be prompted for input.`),
|
|||||||
} else {
|
} else {
|
||||||
prompt := &survey.Select{
|
prompt := &survey.Select{
|
||||||
Message: msg,
|
Message: msg,
|
||||||
Help: i18n.G("enter / return to confirm, choose 'skip' to not upgrade this tag, vim mode is enabled"),
|
Help: "enter / return to confirm, choose 'skip' to not upgrade this tag, vim mode is enabled",
|
||||||
VimMode: true,
|
VimMode: true,
|
||||||
Options: compatibleStrings,
|
Options: compatibleStrings,
|
||||||
}
|
}
|
||||||
@ -299,11 +292,11 @@ You may invoke this command in "wizard" mode and be prompted for input.`),
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
if ok {
|
if ok {
|
||||||
log.Info(i18n.G("tag upgraded from %s to %s for %s", tag.String(), upgradeTag, image))
|
log.Infof("tag upgraded from %s to %s for %s", tag.String(), upgradeTag, image)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if !internal.NoInput {
|
if !internal.NoInput {
|
||||||
log.Warn(i18n.G("not upgrading %s, skipping as requested", image))
|
log.Warnf("not upgrading %s, skipping as requested", image)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -321,7 +314,7 @@ You may invoke this command in "wizard" mode and be prompted for input.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
for _, upgrade := range upgradeList {
|
for _, upgrade := range upgradeList {
|
||||||
log.Info(i18n.G("can upgrade service: %s, image: %s, tag: %s ::", upgrade.Service, upgrade.Image, upgrade.Tag))
|
log.Infof("can upgrade service: %s, image: %s, tag: %s ::", upgrade.Service, upgrade.Image, upgrade.Tag)
|
||||||
for _, utag := range upgrade.UpgradeTags {
|
for _, utag := range upgrade.UpgradeTags {
|
||||||
log.Infof(" %s", utag)
|
log.Infof(" %s", utag)
|
||||||
}
|
}
|
||||||
@ -333,7 +326,7 @@ You may invoke this command in "wizard" mode and be prompted for input.`),
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
if !isClean {
|
if !isClean {
|
||||||
log.Info(i18n.G("%s currently has these unstaged changes 👇", recipe.Name))
|
log.Infof("%s currently has these unstaged changes 👇", recipe.Name)
|
||||||
if err := gitPkg.DiffUnstaged(recipe.Dir); err != nil {
|
if err := gitPkg.DiffUnstaged(recipe.Dir); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -348,41 +341,41 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
RecipeUpgradeCommand.Flags().BoolVarP(
|
RecipeUpgradeCommand.Flags().BoolVarP(
|
||||||
&internal.Major,
|
&internal.Major,
|
||||||
i18n.G("major"),
|
"major",
|
||||||
i18n.G("x"),
|
"x",
|
||||||
false,
|
false,
|
||||||
i18n.G("increase the major part of the version"),
|
"increase the major part of the version",
|
||||||
)
|
)
|
||||||
|
|
||||||
RecipeUpgradeCommand.Flags().BoolVarP(
|
RecipeUpgradeCommand.Flags().BoolVarP(
|
||||||
&internal.Minor,
|
&internal.Minor,
|
||||||
i18n.G("minor"),
|
"minor",
|
||||||
i18n.G("y"),
|
"y",
|
||||||
false,
|
false,
|
||||||
i18n.G("increase the minor part of the version"),
|
"increase the minor part of the version",
|
||||||
)
|
)
|
||||||
|
|
||||||
RecipeUpgradeCommand.Flags().BoolVarP(
|
RecipeUpgradeCommand.Flags().BoolVarP(
|
||||||
&internal.Patch,
|
&internal.Patch,
|
||||||
i18n.G("patch"),
|
"patch",
|
||||||
i18n.G("z"),
|
"z",
|
||||||
false,
|
false,
|
||||||
i18n.G("increase the patch part of the version"),
|
"increase the patch part of the version",
|
||||||
)
|
)
|
||||||
|
|
||||||
RecipeUpgradeCommand.Flags().BoolVarP(
|
RecipeUpgradeCommand.Flags().BoolVarP(
|
||||||
&internal.MachineReadable,
|
&internal.MachineReadable,
|
||||||
i18n.G("machine"),
|
"machine",
|
||||||
i18n.G("m"),
|
"m",
|
||||||
false,
|
false,
|
||||||
i18n.G("print machine-readable output"),
|
"print machine-readable output",
|
||||||
)
|
)
|
||||||
|
|
||||||
RecipeUpgradeCommand.Flags().BoolVarP(
|
RecipeUpgradeCommand.Flags().BoolVarP(
|
||||||
&allTags,
|
&allTags,
|
||||||
i18n.G("all-tags"),
|
"all-tags",
|
||||||
i18n.GC("a", "recipe upgrade"),
|
"a",
|
||||||
false,
|
false,
|
||||||
i18n.G("list all tags, not just upgrades"),
|
"list all tags, not just upgrades",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,27 +3,19 @@ package recipe
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"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/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
recipePkg "coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra recipe versions` aliases. use a comma separated list of aliases
|
|
||||||
// with no spaces in between
|
|
||||||
var recipeVersionsAliases = i18n.G("v")
|
|
||||||
|
|
||||||
var RecipeVersionCommand = &cobra.Command{
|
var RecipeVersionCommand = &cobra.Command{
|
||||||
// translators: `recipe versions` command
|
Use: "versions <recipe> [flags]",
|
||||||
Use: i18n.G("versions <recipe> [flags]"),
|
Aliases: []string{"v"},
|
||||||
Aliases: strings.Split(recipeVersionsAliases, ","),
|
Short: "List recipe versions",
|
||||||
// translators: Short description for `recipe versions` command
|
|
||||||
Short: i18n.G("List recipe versions"),
|
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -43,7 +35,7 @@ var RecipeVersionCommand = &cobra.Command{
|
|||||||
|
|
||||||
recipeMeta, ok := catl[recipe.Name]
|
recipeMeta, ok := catl[recipe.Name]
|
||||||
if !ok {
|
if !ok {
|
||||||
warnMessages = append(warnMessages, i18n.G("retrieved versions from local recipe repository"))
|
warnMessages = append(warnMessages, "retrieved versions from local recipe repository")
|
||||||
|
|
||||||
recipeVersions, warnMsg, err := recipe.GetRecipeVersions()
|
recipeVersions, warnMsg, err := recipe.GetRecipeVersions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -57,7 +49,7 @@ var RecipeVersionCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(recipeMeta.Versions) == 0 {
|
if len(recipeMeta.Versions) == 0 {
|
||||||
log.Fatal(i18n.G("%s has no published versions?", recipe.Name))
|
log.Fatalf("%s has no published versions?", recipe.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := len(recipeMeta.Versions) - 1; i >= 0; i-- {
|
for i := len(recipeMeta.Versions) - 1; i >= 0; i-- {
|
||||||
@ -66,7 +58,7 @@ var RecipeVersionCommand = &cobra.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
table.Headers(i18n.G("SERVICE"), i18n.G("IMAGE"), i18n.G("TAG"), i18n.G("VERSION"))
|
table.Headers("SERVICE", "IMAGE", "TAG", "VERSION")
|
||||||
|
|
||||||
for version, meta := range recipeMeta.Versions[i] {
|
for version, meta := range recipeMeta.Versions[i] {
|
||||||
var allRows [][]string
|
var allRows [][]string
|
||||||
@ -107,10 +99,10 @@ var RecipeVersionCommand = &cobra.Command{
|
|||||||
|
|
||||||
if internal.MachineReadable {
|
if internal.MachineReadable {
|
||||||
sort.Slice(allRows, sortServiceByName(allRows))
|
sort.Slice(allRows, sortServiceByName(allRows))
|
||||||
headers := []string{i18n.G("VERSION"), i18n.G("SERVICE"), i18n.G("NAME"), i18n.G("TAG")}
|
headers := []string{"VERSION", "SERVICE", "NAME", "TAG"}
|
||||||
out, err := formatter.ToJSON(headers, allRows)
|
out, err := formatter.ToJSON(headers, allRows)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(i18n.G("unable to render to JSON: %s", err))
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
}
|
}
|
||||||
fmt.Println(out)
|
fmt.Println(out)
|
||||||
continue
|
continue
|
||||||
@ -135,9 +127,9 @@ func sortServiceByName(versions [][]string) func(i, j int) bool {
|
|||||||
func init() {
|
func init() {
|
||||||
RecipeVersionCommand.Flags().BoolVarP(
|
RecipeVersionCommand.Flags().BoolVarP(
|
||||||
&internal.MachineReadable,
|
&internal.MachineReadable,
|
||||||
i18n.G("machine"),
|
"machine",
|
||||||
i18n.G("m"),
|
"m",
|
||||||
false,
|
false,
|
||||||
i18n.G("print machine-readable output"),
|
"print machine-readable output",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
168
cli/run.go
168
cli/run.go
@ -1,10 +1,8 @@
|
|||||||
package cli
|
package cli
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/app"
|
"coopcloud.tech/abra/cli/app"
|
||||||
"coopcloud.tech/abra/cli/catalogue"
|
"coopcloud.tech/abra/cli/catalogue"
|
||||||
@ -12,114 +10,44 @@ import (
|
|||||||
"coopcloud.tech/abra/cli/recipe"
|
"coopcloud.tech/abra/cli/recipe"
|
||||||
"coopcloud.tech/abra/cli/server"
|
"coopcloud.tech/abra/cli/server"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
charmLog "github.com/charmbracelet/log"
|
charmLog "github.com/charmbracelet/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/cobra/doc"
|
"github.com/spf13/cobra/doc"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
// translators: `abra` usage template. please translate only words like
|
|
||||||
// "Aliases" and "Example" and nothing inside the {{ ... }}
|
|
||||||
usageTemplate = i18n.G(`Usage:{{if .Runnable}}
|
|
||||||
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
|
|
||||||
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
|
|
||||||
|
|
||||||
Aliases:
|
|
||||||
{{.NameAndAliases}}{{end}}{{if .HasExample}}
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}
|
|
||||||
|
|
||||||
Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
|
|
||||||
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
|
|
||||||
|
|
||||||
Flags:
|
|
||||||
{{.LocalFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
|
|
||||||
|
|
||||||
Global Flags:
|
|
||||||
{{.InheritedFlags.FlagUsages | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
|
|
||||||
|
|
||||||
Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
|
|
||||||
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
|
|
||||||
|
|
||||||
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
|
|
||||||
`)
|
|
||||||
|
|
||||||
helpCmd = &cobra.Command{
|
|
||||||
Use: i18n.G("help [command]"),
|
|
||||||
// translators: Short description for `help` command
|
|
||||||
Short: i18n.G("Help about any command"),
|
|
||||||
Long: i18n.G(`Help provides help for any command in the application.
|
|
||||||
Simply type abra help [path to command] for full details.`),
|
|
||||||
Run: func(c *cobra.Command, args []string) {
|
|
||||||
cmd, _, e := c.Root().Find(args)
|
|
||||||
if cmd == nil || e != nil {
|
|
||||||
c.Print(i18n.G("unknown help topic %#q\n", args))
|
|
||||||
if err := c.Root().Usage(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cmd.InitDefaultHelpFlag()
|
|
||||||
cmd.InitDefaultVersionFlag()
|
|
||||||
if err := cmd.Help(); err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func Run(version, commit string) {
|
func Run(version, commit string) {
|
||||||
rootCmd := &cobra.Command{
|
rootCmd := &cobra.Command{
|
||||||
// translators: `abra` binary name
|
Use: "abra [cmd] [args] [flags]",
|
||||||
Use: i18n.G("abra [cmd] [args] [flags]"),
|
Short: "The Co-op Cloud command-line utility belt 🎩🐇",
|
||||||
// translators: Short description for `abra` binary
|
|
||||||
Short: i18n.G("The Co-op Cloud command-line utility belt 🎩🐇"),
|
|
||||||
// translators: Long description for `abra` binary. This needs to be
|
|
||||||
// translated in the same way as the Short description so that everything
|
|
||||||
// matches up
|
|
||||||
Long: i18n.G(`The Co-op Cloud command-line utility belt 🎩🐇
|
|
||||||
|
|
||||||
Config:
|
|
||||||
$ABRA_DIR: %s`, config.ABRA_DIR),
|
|
||||||
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
|
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
|
||||||
ValidArgs: []string{
|
ValidArgs: []string{
|
||||||
// translators: `abra app` command for autocompletion
|
"app",
|
||||||
i18n.G("app"),
|
"autocomplete",
|
||||||
// translators: `abra autocomplete` command for autocompletion
|
"catalogue",
|
||||||
i18n.G("autocomplete"),
|
"man",
|
||||||
// translators: `abra catalogue` command for autocompletion
|
"recipe",
|
||||||
i18n.G("catalogue"),
|
"server",
|
||||||
// translators: `abra man` command for autocompletion
|
"upgrade",
|
||||||
i18n.G("man"),
|
|
||||||
// translators: `abra recipe` command for autocompletion
|
|
||||||
i18n.G("recipe"),
|
|
||||||
// translators: `abra server` command for autocompletion
|
|
||||||
i18n.G("server"),
|
|
||||||
// translators: `abra upgrade` command for autocompletion
|
|
||||||
i18n.G("upgrade"),
|
|
||||||
},
|
},
|
||||||
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
|
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.LOGS_DIR,
|
||||||
|
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) {
|
||||||
return errors.New(i18n.G("unable to create %s: %s", path, err))
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
log.Logger.SetStyles(charmLog.DefaultStyles())
|
log.Logger.SetStyles(charmLog.DefaultStyles())
|
||||||
charmLog.SetDefault(log.Logger)
|
charmLog.SetDefault(log.Logger)
|
||||||
@ -134,37 +62,23 @@ Config:
|
|||||||
log.SetReportCaller(true)
|
log.SetReportCaller(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G(
|
log.Debugf("abra version %s, commit %s", version, commit)
|
||||||
"abra version: %s, commit: %s, lang: %s",
|
|
||||||
version, formatter.SmallSHA(commit), i18n.Locale,
|
|
||||||
))
|
|
||||||
|
|
||||||
return nil
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
rootCmd.CompletionOptions.DisableDefaultCmd = true
|
rootCmd.CompletionOptions.DisableDefaultCmd = true
|
||||||
rootCmd.SetUsageTemplate(usageTemplate)
|
|
||||||
rootCmd.SetHelpCommand(helpCmd)
|
|
||||||
|
|
||||||
// translators: `abra man` aliases. use a comma separated list of aliases
|
|
||||||
// with no spaces in between
|
|
||||||
manAliases := i18n.G("m")
|
|
||||||
|
|
||||||
manCommand := &cobra.Command{
|
manCommand := &cobra.Command{
|
||||||
// translators: `man` command
|
Use: "man [flags]",
|
||||||
Use: i18n.G("man [flags]"),
|
Aliases: []string{"m"},
|
||||||
Aliases: strings.Split(manAliases, ","),
|
Short: "Generate manpage",
|
||||||
// translators: Short description for `man` command
|
Example: ` # generate the man pages into /usr/local/share/man/man1
|
||||||
Short: i18n.G("Generate manpage"),
|
sudo abra man
|
||||||
Example: i18n.G(` # generate the man pages into /usr/local/share/man/man1
|
|
||||||
abra_path=$(which abra) # pass abra absolute path to sudo below
|
|
||||||
sudo $abra_path man
|
|
||||||
sudo mandb
|
sudo mandb
|
||||||
|
|
||||||
# read the man pages
|
# read the man pages
|
||||||
man abra
|
man abra
|
||||||
man abra-app-deploy`),
|
man abra-app-deploy`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
header := &doc.GenManHeader{
|
header := &doc.GenManHeader{
|
||||||
Title: "ABRA",
|
Title: "ABRA",
|
||||||
@ -173,7 +87,7 @@ Config:
|
|||||||
|
|
||||||
manDir := "/usr/local/share/man/man1"
|
manDir := "/usr/local/share/man/man1"
|
||||||
if _, err := os.Stat(manDir); os.IsNotExist(err) {
|
if _, err := os.Stat(manDir); os.IsNotExist(err) {
|
||||||
log.Fatal(i18n.G("unable to proceed, %s does not exist?", manDir))
|
log.Fatalf("unable to proceed, '%s' does not exist?")
|
||||||
}
|
}
|
||||||
|
|
||||||
err := doc.GenManTree(rootCmd, header, manDir)
|
err := doc.GenManTree(rootCmd, header, manDir)
|
||||||
@ -181,7 +95,7 @@ Config:
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(i18n.G("don't forget to run 'sudo mandb'"))
|
log.Info("don't forget to run 'sudo mandb'")
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +104,7 @@ Config:
|
|||||||
"debug",
|
"debug",
|
||||||
"d",
|
"d",
|
||||||
false,
|
false,
|
||||||
i18n.G("show debug messages"),
|
"show debug messages",
|
||||||
)
|
)
|
||||||
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(
|
rootCmd.PersistentFlags().BoolVarP(
|
||||||
@ -198,7 +112,7 @@ Config:
|
|||||||
"no-input",
|
"no-input",
|
||||||
"n",
|
"n",
|
||||||
false,
|
false,
|
||||||
i18n.G("toggle non-interactive mode"),
|
"toggle non-interactive mode",
|
||||||
)
|
)
|
||||||
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(
|
rootCmd.PersistentFlags().BoolVarP(
|
||||||
@ -206,28 +120,19 @@ Config:
|
|||||||
"offline",
|
"offline",
|
||||||
"o",
|
"o",
|
||||||
false,
|
false,
|
||||||
i18n.G("prefer offline & filesystem access"),
|
"prefer offline & filesystem access",
|
||||||
)
|
)
|
||||||
|
|
||||||
rootCmd.PersistentFlags().BoolVarP(
|
rootCmd.PersistentFlags().BoolVarP(
|
||||||
&internal.Help,
|
&internal.IgnoreEnvVersion,
|
||||||
i18n.G("help"),
|
"ignore-env-version",
|
||||||
i18n.G("h"),
|
"i",
|
||||||
false,
|
false,
|
||||||
i18n.G("help for abra"),
|
"ignore .env version checkout",
|
||||||
)
|
|
||||||
|
|
||||||
rootCmd.Flags().BoolVarP(
|
|
||||||
&internal.Version,
|
|
||||||
i18n.G("version"),
|
|
||||||
i18n.G("v"),
|
|
||||||
false,
|
|
||||||
i18n.G("version for abra"),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
catalogue.CatalogueCommand.AddCommand(
|
catalogue.CatalogueCommand.AddCommand(
|
||||||
catalogue.CatalogueGenerateCommand,
|
catalogue.CatalogueGenerateCommand,
|
||||||
catalogue.CatalogueSyncCommand,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
server.ServerCommand.AddCommand(
|
server.ServerCommand.AddCommand(
|
||||||
@ -298,7 +203,6 @@ Config:
|
|||||||
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,
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package server
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
@ -11,24 +10,17 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
contextPkg "coopcloud.tech/abra/pkg/context"
|
contextPkg "coopcloud.tech/abra/pkg/context"
|
||||||
"coopcloud.tech/abra/pkg/dns"
|
"coopcloud.tech/abra/pkg/dns"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"coopcloud.tech/abra/pkg/server"
|
"coopcloud.tech/abra/pkg/server"
|
||||||
sshPkg "coopcloud.tech/abra/pkg/ssh"
|
sshPkg "coopcloud.tech/abra/pkg/ssh"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra server add` aliases. use a comma separated list of
|
|
||||||
// aliases with no spaces in between
|
|
||||||
var serverAddAliases = i18n.GC("a", "server add")
|
|
||||||
|
|
||||||
var ServerAddCommand = &cobra.Command{
|
var ServerAddCommand = &cobra.Command{
|
||||||
// translators: `server add` command
|
Use: "add [[server] | --local] [flags]",
|
||||||
Use: i18n.G("add [[server] | --local] [flags]"),
|
Aliases: []string{"a"},
|
||||||
Aliases: strings.Split(serverAddAliases, ","),
|
Short: "Add a new server",
|
||||||
// translators: Short description for `server add` command
|
Long: `Add a new server to your configuration so that it can be managed by Abra.
|
||||||
Short: i18n.G("Add a new server"),
|
|
||||||
Long: i18n.G(`Add a new server to your configuration so that it can be managed by Abra.
|
|
||||||
|
|
||||||
Abra relies on the standard SSH command-line and ~/.ssh/config for client
|
Abra relies on the standard SSH command-line and ~/.ssh/config for client
|
||||||
connection details. You must configure an entry per-host in your ~/.ssh/config
|
connection details. You must configure an entry per-host in your ~/.ssh/config
|
||||||
@ -43,8 +35,8 @@ for each server:
|
|||||||
If "--local" is passed, then Abra assumes that the current local server is
|
If "--local" is passed, then Abra assumes that the current local server is
|
||||||
intended as the target server. This is useful when you want to have your entire
|
intended as the target server. This is useful when you want to have your entire
|
||||||
Co-op Cloud config located on the server itself, and not on your local
|
Co-op Cloud config located on the server itself, and not on your local
|
||||||
developer machine. The domain is then set to "default".`),
|
developer machine. The domain is then set to "default".`,
|
||||||
Example: i18n.G(" abra server add 1312.net"),
|
Example: " abra server add 1312.net",
|
||||||
Args: cobra.RangeArgs(0, 1),
|
Args: cobra.RangeArgs(0, 1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -57,11 +49,11 @@ developer machine. The domain is then set to "default".`),
|
|||||||
},
|
},
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
if len(args) > 0 && local {
|
if len(args) > 0 && local {
|
||||||
log.Fatal(i18n.G("cannot use [server] and --local together"))
|
log.Fatal("cannot use [server] and --local together")
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(args) == 0 && !local {
|
if len(args) == 0 && !local {
|
||||||
log.Fatal(i18n.G("missing argument or --local/-l flag"))
|
log.Fatal("missing argument or --local/-l flag")
|
||||||
}
|
}
|
||||||
|
|
||||||
name := "default"
|
name := "default"
|
||||||
@ -80,7 +72,7 @@ developer machine. The domain is then set to "default".`),
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("attempting to create client for %s", name))
|
log.Debugf("attempting to create client for %s", name)
|
||||||
|
|
||||||
if _, err := client.New(name, timeout); err != nil {
|
if _, err := client.New(name, timeout); err != nil {
|
||||||
cleanUp(name)
|
cleanUp(name)
|
||||||
@ -88,9 +80,9 @@ developer machine. The domain is then set to "default".`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
if created {
|
if created {
|
||||||
log.Info(i18n.G("local server successfully added"))
|
log.Info("local server successfully added")
|
||||||
} else {
|
} else {
|
||||||
log.Warn(i18n.G("local server already exists"))
|
log.Warn("local server already exists")
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -104,27 +96,27 @@ developer machine. The domain is then set to "default".`),
|
|||||||
created, err := newContext(name)
|
created, err := newContext(name)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cleanUp(name)
|
cleanUp(name)
|
||||||
log.Fatal(i18n.G("unable to create local context: %s", err))
|
log.Fatalf("unable to create local context: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("attempting to create client for %s", name))
|
log.Debugf("attempting to create client for %s", name)
|
||||||
|
|
||||||
if _, err := client.New(name, timeout); err != nil {
|
if _, err := client.New(name, timeout); err != nil {
|
||||||
cleanUp(name)
|
cleanUp(name)
|
||||||
log.Fatal(i18n.G("ssh %s error: %s", name, sshPkg.Fatal(name, err)))
|
log.Fatalf("ssh %s error: %s", name, sshPkg.Fatal(name, err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if created {
|
if created {
|
||||||
log.Info(i18n.G("%s successfully added", name))
|
log.Infof("%s successfully added", name)
|
||||||
|
|
||||||
if _, err := dns.EnsureIPv4(name); err != nil {
|
if _, err := dns.EnsureIPv4(name); err != nil {
|
||||||
log.Warn(i18n.G("unable to resolve IPv4 for %s", name))
|
log.Warnf("unable to resolve IPv4 for %s", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Warn(i18n.G("%s already exists", name))
|
log.Warnf("%s already exists", name)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +124,7 @@ developer machine. The domain is then set to "default".`),
|
|||||||
// "server add" attempt.
|
// "server add" attempt.
|
||||||
func cleanUp(name string) {
|
func cleanUp(name string) {
|
||||||
if name != "default" {
|
if name != "default" {
|
||||||
log.Debug(i18n.G("serverAdd: cleanUp: cleaning up context for %s", name))
|
log.Debugf("serverAdd: cleanUp: cleaning up context for %s", name)
|
||||||
if err := client.DeleteContext(name); err != nil {
|
if err := client.DeleteContext(name); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -141,16 +133,16 @@ func cleanUp(name string) {
|
|||||||
serverDir := filepath.Join(config.SERVERS_DIR, name)
|
serverDir := filepath.Join(config.SERVERS_DIR, name)
|
||||||
files, err := config.GetAllFilesInDirectory(serverDir)
|
files, err := config.GetAllFilesInDirectory(serverDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(i18n.G("serverAdd: cleanUp: unable to list files in %s: %s", serverDir, err))
|
log.Fatalf("serverAdd: cleanUp: unable to list files in %s: %s", serverDir, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(files) > 0 {
|
if len(files) > 0 {
|
||||||
log.Debug(i18n.G("serverAdd: cleanUp: %s is not empty, aborting cleanup", serverDir))
|
log.Debugf("serverAdd: cleanUp: %s is not empty, aborting cleanup", serverDir)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := os.RemoveAll(serverDir); err != nil {
|
if err := os.RemoveAll(serverDir); err != nil {
|
||||||
log.Fatal(i18n.G("serverAdd: cleanUp: failed to remove %s: %s", serverDir, err))
|
log.Fatalf("serverAdd: cleanUp: failed to remove %s: %s", serverDir, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -167,12 +159,12 @@ func newContext(name string) (bool, error) {
|
|||||||
|
|
||||||
for _, context := range contexts {
|
for _, context := range contexts {
|
||||||
if context.Name == name {
|
if context.Name == name {
|
||||||
log.Debug(i18n.G("context for %s already exists", name))
|
log.Debugf("context for %s already exists", name)
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf(i18n.G("creating context with domain %s", name))
|
log.Debugf("creating context with domain %s", name)
|
||||||
|
|
||||||
if err := client.CreateContext(name); err != nil {
|
if err := client.CreateContext(name); err != nil {
|
||||||
return false, nil
|
return false, nil
|
||||||
@ -188,7 +180,7 @@ func createServerDir(name string) (bool, error) {
|
|||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("server dir for %s already created", name))
|
log.Debugf("server dir for %s already created", name)
|
||||||
|
|
||||||
return false, nil
|
return false, nil
|
||||||
}
|
}
|
||||||
@ -203,9 +195,9 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
ServerAddCommand.Flags().BoolVarP(
|
ServerAddCommand.Flags().BoolVarP(
|
||||||
&local,
|
&local,
|
||||||
i18n.G("local"),
|
"local",
|
||||||
i18n.G("l"),
|
"l",
|
||||||
false,
|
false,
|
||||||
i18n.G("use local server"),
|
"use local server",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,22 +8,15 @@ import (
|
|||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
contextPkg "coopcloud.tech/abra/pkg/context"
|
contextPkg "coopcloud.tech/abra/pkg/context"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/docker/cli/cli/connhelper/ssh"
|
"github.com/docker/cli/cli/connhelper/ssh"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra server list` aliases. use a comma separated list of
|
|
||||||
// aliases with no spaces in between
|
|
||||||
var serverListAliases = i18n.G("ls")
|
|
||||||
|
|
||||||
var ServerListCommand = &cobra.Command{
|
var ServerListCommand = &cobra.Command{
|
||||||
// translators: `server list` command
|
Use: "list [flags]",
|
||||||
Use: i18n.G("list [flags]"),
|
Aliases: []string{"ls"},
|
||||||
Aliases: strings.Split(serverListAliases, ","),
|
Short: "List managed servers",
|
||||||
// translators: Short description for `server list` command
|
|
||||||
Short: i18n.G("List managed servers"),
|
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
dockerContextStore := contextPkg.NewDefaultDockerContextStore()
|
dockerContextStore := contextPkg.NewDefaultDockerContextStore()
|
||||||
@ -37,7 +30,7 @@ var ServerListCommand = &cobra.Command{
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
headers := []string{i18n.G("NAME"), i18n.G("HOST")}
|
headers := []string{"NAME", "HOST"}
|
||||||
table.Headers(headers...)
|
table.Headers(headers...)
|
||||||
|
|
||||||
serverNames, err := config.ReadServerNames()
|
serverNames, err := config.ReadServerNames()
|
||||||
@ -62,7 +55,7 @@ var ServerListCommand = &cobra.Command{
|
|||||||
}
|
}
|
||||||
|
|
||||||
if sp.Host == "" {
|
if sp.Host == "" {
|
||||||
sp.Host = i18n.G("unknown")
|
sp.Host = "unknown"
|
||||||
}
|
}
|
||||||
|
|
||||||
row = []string{serverName, sp.Host}
|
row = []string{serverName, sp.Host}
|
||||||
@ -72,9 +65,9 @@ var ServerListCommand = &cobra.Command{
|
|||||||
|
|
||||||
if len(row) == 0 {
|
if len(row) == 0 {
|
||||||
if serverName == "default" {
|
if serverName == "default" {
|
||||||
row = []string{serverName, i18n.G("local")}
|
row = []string{serverName, "local"}
|
||||||
} else {
|
} else {
|
||||||
row = []string{serverName, i18n.G("unknown")}
|
row = []string{serverName, "unknown"}
|
||||||
}
|
}
|
||||||
rows = append(rows, row)
|
rows = append(rows, row)
|
||||||
}
|
}
|
||||||
@ -85,7 +78,7 @@ var ServerListCommand = &cobra.Command{
|
|||||||
if internal.MachineReadable {
|
if internal.MachineReadable {
|
||||||
out, err := formatter.ToJSON(headers, rows)
|
out, err := formatter.ToJSON(headers, rows)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(i18n.G("unable to render to JSON: %s", err))
|
log.Fatal("unable to render to JSON: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Println(out)
|
fmt.Println(out)
|
||||||
@ -102,9 +95,9 @@ var ServerListCommand = &cobra.Command{
|
|||||||
func init() {
|
func init() {
|
||||||
ServerListCommand.Flags().BoolVarP(
|
ServerListCommand.Flags().BoolVarP(
|
||||||
&internal.MachineReadable,
|
&internal.MachineReadable,
|
||||||
i18n.G("machine"),
|
"machine",
|
||||||
i18n.G("m"),
|
"m",
|
||||||
false,
|
false,
|
||||||
i18n.G("print machine-readable output"),
|
"print machine-readable output",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,32 +1,23 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra server prune` aliases. use a comma separated list of
|
|
||||||
// aliases with no spaces in between
|
|
||||||
var serverPruneliases = i18n.G("p")
|
|
||||||
|
|
||||||
var ServerPruneCommand = &cobra.Command{
|
var ServerPruneCommand = &cobra.Command{
|
||||||
// translators: `server prune` command
|
Use: "prune <server> [flags]",
|
||||||
Use: i18n.G("prune <server> [flags]"),
|
Aliases: []string{"p"},
|
||||||
Aliases: strings.Split(serverPruneliases, ","),
|
Short: "Prune resources on a server",
|
||||||
// translators: Short description for `server prune` command
|
Long: `Prunes unused containers, networks, and dangling images.
|
||||||
Short: i18n.G("Prune resources on a server"),
|
|
||||||
Long: i18n.G(`Prunes unused containers, networks, and dangling images.
|
|
||||||
|
|
||||||
Use "--volumes/-v" to remove volumes that are not associated with a deployed
|
Use "--volumes/-v" to remove volumes that are not associated with a deployed
|
||||||
app. This can result in unwanted data loss if not used carefully.`),
|
app. This can result in unwanted data loss if not used carefully.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -50,18 +41,18 @@ app. This can result in unwanted data loss if not used carefully.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
cntSpaceReclaimed := formatter.ByteCountSI(cr.SpaceReclaimed)
|
cntSpaceReclaimed := formatter.ByteCountSI(cr.SpaceReclaimed)
|
||||||
log.Info(i18n.G("containers pruned: %d; space reclaimed: %s", len(cr.ContainersDeleted), cntSpaceReclaimed))
|
log.Infof("containers pruned: %d; space reclaimed: %s", len(cr.ContainersDeleted), cntSpaceReclaimed)
|
||||||
|
|
||||||
nr, err := cl.NetworksPrune(cmd.Context(), filterArgs)
|
nr, err := cl.NetworksPrune(cmd.Context(), filterArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(i18n.G("networks pruned: %d", len(nr.NetworksDeleted)))
|
log.Infof("networks pruned: %d", len(nr.NetworksDeleted))
|
||||||
|
|
||||||
pruneFilters := filters.NewArgs()
|
pruneFilters := filters.NewArgs()
|
||||||
if allFilter {
|
if allFilter {
|
||||||
log.Debug(i18n.G("removing all images, not only dangling ones"))
|
log.Debugf("removing all images, not only dangling ones")
|
||||||
pruneFilters.Add("dangling", "false")
|
pruneFilters.Add("dangling", "false")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +62,7 @@ app. This can result in unwanted data loss if not used carefully.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
imgSpaceReclaimed := formatter.ByteCountSI(ir.SpaceReclaimed)
|
imgSpaceReclaimed := formatter.ByteCountSI(ir.SpaceReclaimed)
|
||||||
log.Info(i18n.G("images pruned: %d; space reclaimed: %s", len(ir.ImagesDeleted), imgSpaceReclaimed))
|
log.Infof("images pruned: %d; space reclaimed: %s", len(ir.ImagesDeleted), imgSpaceReclaimed)
|
||||||
|
|
||||||
if volumesFilter {
|
if volumesFilter {
|
||||||
vr, err := cl.VolumesPrune(cmd.Context(), filterArgs)
|
vr, err := cl.VolumesPrune(cmd.Context(), filterArgs)
|
||||||
@ -80,7 +71,7 @@ app. This can result in unwanted data loss if not used carefully.`),
|
|||||||
}
|
}
|
||||||
|
|
||||||
volSpaceReclaimed := formatter.ByteCountSI(vr.SpaceReclaimed)
|
volSpaceReclaimed := formatter.ByteCountSI(vr.SpaceReclaimed)
|
||||||
log.Info(i18n.G("volumes pruned: %d; space reclaimed: %s", len(vr.VolumesDeleted), volSpaceReclaimed))
|
log.Infof("volumes pruned: %d; space reclaimed: %s", len(vr.VolumesDeleted), volSpaceReclaimed)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
@ -95,17 +86,17 @@ var (
|
|||||||
func init() {
|
func init() {
|
||||||
ServerPruneCommand.Flags().BoolVarP(
|
ServerPruneCommand.Flags().BoolVarP(
|
||||||
&allFilter,
|
&allFilter,
|
||||||
i18n.G("all"),
|
"all",
|
||||||
i18n.GC("a", "server prune"),
|
"a",
|
||||||
false,
|
false,
|
||||||
i18n.G("remove all unused images"),
|
"remove all unused images",
|
||||||
)
|
)
|
||||||
|
|
||||||
ServerPruneCommand.Flags().BoolVarP(
|
ServerPruneCommand.Flags().BoolVarP(
|
||||||
&volumesFilter,
|
&volumesFilter,
|
||||||
i18n.G("volumes"),
|
"volumes",
|
||||||
i18n.G("v"),
|
"v",
|
||||||
false,
|
false,
|
||||||
i18n.G("remove volumes"),
|
"remove volumes",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,32 +3,24 @@ package server
|
|||||||
import (
|
import (
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/autocomplete"
|
"coopcloud.tech/abra/pkg/autocomplete"
|
||||||
"coopcloud.tech/abra/pkg/client"
|
"coopcloud.tech/abra/pkg/client"
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra server remove` aliases. use a comma separated list of
|
|
||||||
// aliases with no spaces in between
|
|
||||||
var serverRemoveAliases = i18n.G("rm")
|
|
||||||
|
|
||||||
var ServerRemoveCommand = &cobra.Command{
|
var ServerRemoveCommand = &cobra.Command{
|
||||||
// translators: `server remove` command
|
Use: "remove <server> [flags]",
|
||||||
Use: i18n.G("remove <server> [flags]"),
|
Aliases: []string{"rm"},
|
||||||
Aliases: strings.Split(serverRemoveAliases, ","),
|
Short: "Remove a managed server",
|
||||||
// translators: Short description for `server remove` command
|
Long: `Remove a managed server.
|
||||||
Short: i18n.G("Remove a managed server"),
|
|
||||||
Long: i18n.G(`Remove a managed server.
|
|
||||||
|
|
||||||
Abra will remove the internal bookkeeping ($ABRA_DIR/servers/...) and
|
Abra will remove the internal bookkeeping ($ABRA_DIR/servers/...) and
|
||||||
underlying client connection context. This server will then be lost in time,
|
underlying client connection context. This server will then be lost in time,
|
||||||
like tears in rain.`),
|
like tears in rain.`,
|
||||||
Args: cobra.ExactArgs(1),
|
Args: cobra.ExactArgs(1),
|
||||||
ValidArgsFunction: func(
|
ValidArgsFunction: func(
|
||||||
cmd *cobra.Command,
|
cmd *cobra.Command,
|
||||||
@ -47,7 +39,7 @@ like tears in rain.`),
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Info(i18n.G("%s is now lost in time, like tears in rain", serverName))
|
log.Infof("%s is now lost in time, like tears in rain", serverName)
|
||||||
|
|
||||||
return
|
return
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,21 +1,10 @@
|
|||||||
package server
|
package server
|
||||||
|
|
||||||
import (
|
import "github.com/spf13/cobra"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"github.com/spf13/cobra"
|
|
||||||
)
|
|
||||||
|
|
||||||
// translators: `abra server` aliases. use a comma separated list of aliases
|
|
||||||
// with no spaces in between
|
|
||||||
var serverAliases = i18n.G("s")
|
|
||||||
|
|
||||||
// ServerCommand defines the `abra server` command and its subcommands
|
// ServerCommand defines the `abra server` command and its subcommands
|
||||||
var ServerCommand = &cobra.Command{
|
var ServerCommand = &cobra.Command{
|
||||||
// translators: `server` command group
|
Use: "server [cmd] [args] [flags]",
|
||||||
Use: i18n.G("server [cmd] [args] [flags]"),
|
Aliases: []string{"s"},
|
||||||
Aliases: strings.Split(serverAliases, ","),
|
Short: "Manage servers",
|
||||||
// translators: Short description for `server` command group
|
|
||||||
Short: i18n.G("Manage servers"),
|
|
||||||
}
|
}
|
||||||
|
|||||||
550
cli/updater/updater.go
Normal file
550
cli/updater/updater.go
Normal file
@ -0,0 +1,550 @@
|
|||||||
|
package updater
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"coopcloud.tech/abra/cli/internal"
|
||||||
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
|
"coopcloud.tech/abra/pkg/client"
|
||||||
|
"coopcloud.tech/abra/pkg/envfile"
|
||||||
|
"coopcloud.tech/abra/pkg/lint"
|
||||||
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
|
"coopcloud.tech/abra/pkg/upstream/convert"
|
||||||
|
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
|
"coopcloud.tech/tagcmp"
|
||||||
|
charmLog "github.com/charmbracelet/log"
|
||||||
|
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"
|
||||||
|
|
||||||
|
"coopcloud.tech/abra/pkg/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
const SERVER = "localhost"
|
||||||
|
|
||||||
|
// NotifyCommand checks for available upgrades.
|
||||||
|
var NotifyCommand = &cobra.Command{
|
||||||
|
Use: "notify [flags]",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Short: "Check for available upgrades",
|
||||||
|
Long: `Notify on new versions for deployed apps.
|
||||||
|
|
||||||
|
If a new patch/minor version is available, a notification is printed.
|
||||||
|
|
||||||
|
Use "--major/-m" to include new major versions.`,
|
||||||
|
Args: cobra.NoArgs,
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
cl, err := client.New("default")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
stacks, err := stack.GetStacks(cl)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, stackInfo := range stacks {
|
||||||
|
stackName := stackInfo.Name
|
||||||
|
recipeName, err := getLabel(cl, stackName, "recipe")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if recipeName != "" {
|
||||||
|
_, err = getLatestUpgrade(cl, stackName, recipeName)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpgradeCommand upgrades apps.
|
||||||
|
var UpgradeCommand = &cobra.Command{
|
||||||
|
Use: "upgrade [[stack] [recipe] | --all] [flags]",
|
||||||
|
Aliases: []string{"u"},
|
||||||
|
Short: "Upgrade apps",
|
||||||
|
Long: `Upgrade an app by specifying stack name and recipe.
|
||||||
|
|
||||||
|
Use "--all" to upgrade every deployed app.
|
||||||
|
|
||||||
|
For each app with auto updates enabled, the deployed version is compared with
|
||||||
|
the current recipe catalogue version. If a new patch/minor version is
|
||||||
|
available, the app is upgraded.
|
||||||
|
|
||||||
|
To include major versions use the "--major/-m" flag. You probably don't want
|
||||||
|
that as it will break things. Only apps that are not deployed with "--chaos/-C"
|
||||||
|
are upgraded, to update chaos deployments use the "--chaos/-C" flag. Use it
|
||||||
|
with care.`,
|
||||||
|
Args: cobra.RangeArgs(0, 2),
|
||||||
|
// TODO(d1): complete stack/recipe
|
||||||
|
// ValidArgsFunction: func(
|
||||||
|
// cmd *cobra.Command,
|
||||||
|
// args []string,
|
||||||
|
// toComplete string) ([]string, cobra.ShellCompDirective) {
|
||||||
|
// },
|
||||||
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
cl, err := client.New("default")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !updateAll && len(args) != 2 {
|
||||||
|
log.Fatal("missing arguments or --all/-a flag")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !updateAll {
|
||||||
|
stackName := args[0]
|
||||||
|
recipeName := args[1]
|
||||||
|
|
||||||
|
err = tryUpgrade(cl, stackName, recipeName)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
stacks, err := stack.GetStacks(cl)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, stackInfo := range stacks {
|
||||||
|
stackName := stackInfo.Name
|
||||||
|
recipeName, err := getLabel(cl, stackName, "recipe")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = tryUpgrade(cl, stackName, recipeName)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLabel reads docker labels from running services in the format of "coop-cloud.${STACK_NAME}.${LABEL}".
|
||||||
|
func getLabel(cl *dockerclient.Client, stackName string, label string) (string, error) {
|
||||||
|
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 "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, service := range services {
|
||||||
|
labelKey := fmt.Sprintf("coop-cloud.%s.%s", stackName, label)
|
||||||
|
if labelValue, ok := service.Spec.Labels[labelKey]; ok {
|
||||||
|
return labelValue, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("no %s label found for %s", label, stackName)
|
||||||
|
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getBoolLabel reads a boolean docker label from running services
|
||||||
|
func getBoolLabel(cl *dockerclient.Client, stackName string, label string) (bool, error) {
|
||||||
|
lableValue, err := getLabel(cl, stackName, label)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if lableValue != "" {
|
||||||
|
value, err := strconv.ParseBool(lableValue)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return value, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("boolean label %s could not be found for %s, set default to false.", label, stackName)
|
||||||
|
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getEnv reads env variables from docker services.
|
||||||
|
func getEnv(cl *dockerclient.Client, stackName string) (envfile.AppEnv, error) {
|
||||||
|
envMap := 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 nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, service := range services {
|
||||||
|
envList := service.Spec.TaskTemplate.ContainerSpec.Env
|
||||||
|
for _, envString := range envList {
|
||||||
|
splitString := strings.SplitN(envString, "=", 2)
|
||||||
|
if len(splitString) != 2 {
|
||||||
|
log.Debugf("can't separate key from value: %s (this variable is probably unset)", envString)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
k := splitString[0]
|
||||||
|
v := splitString[1]
|
||||||
|
log.Debugf("for %s read env %s with value: %s from docker service", stackName, k, v)
|
||||||
|
envMap[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return envMap, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getLatestUpgrade returns the latest available version for an app respecting
|
||||||
|
// the "--major" flag if it is newer than the currently deployed version.
|
||||||
|
func getLatestUpgrade(cl *dockerclient.Client, stackName string, recipeName string) (string, error) {
|
||||||
|
deployedVersion, err := getDeployedVersion(cl, stackName, recipeName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
availableUpgrades, err := getAvailableUpgrades(cl, stackName, recipeName, deployedVersion)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(availableUpgrades) == 0 {
|
||||||
|
log.Debugf("no available upgrades for %s", stackName)
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var chosenUpgrade string
|
||||||
|
if len(availableUpgrades) > 0 {
|
||||||
|
chosenUpgrade = availableUpgrades[len(availableUpgrades)-1]
|
||||||
|
log.Infof("%s (%s) can be upgraded from version %s to %s", stackName, recipeName, deployedVersion, chosenUpgrade)
|
||||||
|
}
|
||||||
|
|
||||||
|
return chosenUpgrade, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getDeployedVersion returns the currently deployed version of an app.
|
||||||
|
func getDeployedVersion(cl *dockerclient.Client, stackName string, recipeName string) (string, error) {
|
||||||
|
log.Debugf("retrieve deployed version whether %s is already deployed", stackName)
|
||||||
|
|
||||||
|
deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !deployMeta.IsDeployed {
|
||||||
|
return "", fmt.Errorf("%s is not deployed?", stackName)
|
||||||
|
}
|
||||||
|
|
||||||
|
if deployMeta.Version == "unknown" {
|
||||||
|
return "", fmt.Errorf("failed to determine deployed version of %s", stackName)
|
||||||
|
}
|
||||||
|
|
||||||
|
return deployMeta.Version, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getAvailableUpgrades returns all available versions of an app that are newer
|
||||||
|
// than the deployed version. It only includes major upgrades if the "--major"
|
||||||
|
// flag is set.
|
||||||
|
func getAvailableUpgrades(cl *dockerclient.Client, stackName string, recipeName string,
|
||||||
|
deployedVersion string) ([]string, error) {
|
||||||
|
catl, err := recipe.ReadRecipeCatalogue(internal.Offline)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
versions, err := recipe.GetRecipeCatalogueVersions(recipeName, catl)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(versions) == 0 {
|
||||||
|
log.Warnf("no published releases for %s in the recipe catalogue?", recipeName)
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var availableUpgrades []string
|
||||||
|
for _, version := range versions {
|
||||||
|
parsedDeployedVersion, err := tagcmp.Parse(deployedVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedVersion, err := tagcmp.Parse(version)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
versionDelta, err := parsedDeployedVersion.UpgradeDelta(parsedVersion)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if 0 < versionDelta.UpgradeType() && (versionDelta.UpgradeType() < 4 || includeMajorUpdates) {
|
||||||
|
availableUpgrades = append(availableUpgrades, version)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("available updates for %s: %s", stackName, availableUpgrades)
|
||||||
|
|
||||||
|
return availableUpgrades, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// processRecipeRepoVersion clones, pulls, checks out the version and lints the
|
||||||
|
// recipe repository.
|
||||||
|
func processRecipeRepoVersion(r recipe.Recipe, version string) error {
|
||||||
|
if err := r.EnsureExists(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := r.EnsureUpToDate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := r.EnsureVersion(version); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := lint.LintForErrors(r); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// mergeAbraShEnv merges abra.sh env vars into the app env vars.
|
||||||
|
func mergeAbraShEnv(recipe recipe.Recipe, env envfile.AppEnv) error {
|
||||||
|
abraShEnv, err := envfile.ReadAbraShEnvVars(recipe.AbraShPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for k, v := range abraShEnv {
|
||||||
|
log.Debugf("read v:%s k: %s", v, k)
|
||||||
|
env[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// createDeployConfig merges and enriches the compose config for the deployment.
|
||||||
|
func createDeployConfig(r recipe.Recipe, stackName string, env envfile.AppEnv) (*composetypes.Config, stack.Deploy, error) {
|
||||||
|
env["STACK_NAME"] = stackName
|
||||||
|
|
||||||
|
deployOpts := stack.Deploy{
|
||||||
|
Namespace: stackName,
|
||||||
|
Prune: false,
|
||||||
|
ResolveImage: stack.ResolveImageAlways,
|
||||||
|
Detach: false,
|
||||||
|
}
|
||||||
|
|
||||||
|
composeFiles, err := r.GetComposeFiles(env)
|
||||||
|
if err != nil {
|
||||||
|
return nil, deployOpts, err
|
||||||
|
}
|
||||||
|
|
||||||
|
deployOpts.Composefiles = composeFiles
|
||||||
|
compose, err := appPkg.GetAppComposeConfig(stackName, deployOpts, env)
|
||||||
|
if err != nil {
|
||||||
|
return nil, deployOpts, err
|
||||||
|
}
|
||||||
|
|
||||||
|
appPkg.ExposeAllEnv(stackName, compose, env)
|
||||||
|
|
||||||
|
// after the upgrade the deployment won't be in chaos state anymore
|
||||||
|
appPkg.SetChaosLabel(compose, stackName, false)
|
||||||
|
appPkg.SetRecipeLabel(compose, stackName, r.Name)
|
||||||
|
appPkg.SetUpdateLabel(compose, stackName, env)
|
||||||
|
|
||||||
|
return compose, deployOpts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// tryUpgrade performs the upgrade if all the requirements are fulfilled.
|
||||||
|
func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string) error {
|
||||||
|
if recipeName == "" {
|
||||||
|
log.Debugf("don't update %s due to missing recipe name", stackName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
chaos, err := getBoolLabel(cl, stackName, "chaos")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if chaos && !internal.Chaos {
|
||||||
|
log.Debugf("don't update %s due to chaos deployment", stackName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
updatesEnabled, err := getBoolLabel(cl, stackName, "autoupdate")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if !updatesEnabled {
|
||||||
|
log.Debugf("don't update %s due to disabled auto updates or missing ENABLE_AUTO_UPDATE env", stackName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
upgradeVersion, err := getLatestUpgrade(cl, stackName, recipeName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if upgradeVersion == "" {
|
||||||
|
log.Debugf("don't update %s due to no new version", stackName)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
err = upgrade(cl, stackName, recipeName, upgradeVersion)
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// upgrade performs all necessary steps to upgrade an app.
|
||||||
|
func upgrade(cl *dockerclient.Client, stackName, recipeName, upgradeVersion string) error {
|
||||||
|
env, err := getEnv(cl, stackName)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
app := appPkg.App{
|
||||||
|
Name: stackName,
|
||||||
|
Recipe: recipe.Get(recipeName),
|
||||||
|
Server: SERVER,
|
||||||
|
Env: env,
|
||||||
|
}
|
||||||
|
|
||||||
|
r := recipe.Get(recipeName)
|
||||||
|
|
||||||
|
if err = processRecipeRepoVersion(r, upgradeVersion); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = mergeAbraShEnv(app.Recipe, app.Env); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
compose, deployOpts, err := createDeployConfig(r, stackName, app.Env)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Infof("upgrade %s (%s) to version %s", stackName, recipeName, upgradeVersion)
|
||||||
|
|
||||||
|
serviceNames, err := appPkg.GetAppServiceNames(app.Name)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
func newKadabraApp(version, commit string) *cobra.Command {
|
||||||
|
rootCmd := &cobra.Command{
|
||||||
|
Use: "kadabra [cmd] [flags]",
|
||||||
|
Version: fmt.Sprintf("%s-%s", version, commit[:7]),
|
||||||
|
Short: "The Co-op Cloud auto-updater 🤖 🚀",
|
||||||
|
PersistentPreRun: func(cmd *cobra.Command, args []string) {
|
||||||
|
log.Logger.SetStyles(charmLog.DefaultStyles())
|
||||||
|
charmLog.SetDefault(log.Logger)
|
||||||
|
|
||||||
|
if internal.Debug {
|
||||||
|
log.SetLevel(log.DebugLevel)
|
||||||
|
log.SetOutput(os.Stderr)
|
||||||
|
log.SetReportCaller(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debugf("kadabra version %s, commit %s", version, commit)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(
|
||||||
|
&internal.Debug, "debug", "d", false,
|
||||||
|
"show debug messages",
|
||||||
|
)
|
||||||
|
|
||||||
|
rootCmd.PersistentFlags().BoolVarP(
|
||||||
|
&internal.NoInput, "no-input", "n", false,
|
||||||
|
"toggle non-interactive mode",
|
||||||
|
)
|
||||||
|
|
||||||
|
rootCmd.AddCommand(
|
||||||
|
NotifyCommand,
|
||||||
|
UpgradeCommand,
|
||||||
|
)
|
||||||
|
|
||||||
|
return rootCmd
|
||||||
|
}
|
||||||
|
|
||||||
|
// RunApp runs CLI abra app.
|
||||||
|
func RunApp(version, commit string) {
|
||||||
|
app := newKadabraApp(version, commit)
|
||||||
|
|
||||||
|
if err := app.Execute(); err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
includeMajorUpdates bool
|
||||||
|
updateAll bool
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
NotifyCommand.Flags().BoolVarP(
|
||||||
|
&includeMajorUpdates,
|
||||||
|
"major",
|
||||||
|
"m",
|
||||||
|
false,
|
||||||
|
"check for major updates",
|
||||||
|
)
|
||||||
|
|
||||||
|
UpgradeCommand.Flags().BoolVarP(
|
||||||
|
&internal.Chaos,
|
||||||
|
"chaos",
|
||||||
|
"C",
|
||||||
|
false,
|
||||||
|
"ignore uncommitted recipes changes",
|
||||||
|
)
|
||||||
|
|
||||||
|
UpgradeCommand.Flags().BoolVarP(
|
||||||
|
&includeMajorUpdates,
|
||||||
|
"major",
|
||||||
|
"m",
|
||||||
|
false,
|
||||||
|
"check for major updates",
|
||||||
|
)
|
||||||
|
|
||||||
|
UpgradeCommand.Flags().BoolVarP(
|
||||||
|
&updateAll,
|
||||||
|
"all",
|
||||||
|
"a",
|
||||||
|
false,
|
||||||
|
"update all deployed apps",
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -4,33 +4,25 @@ package cli
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/cli/internal"
|
"coopcloud.tech/abra/cli/internal"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
// translators: `abra upgrade` aliases. use a comma separated list of aliases with
|
|
||||||
// no spaces in between
|
|
||||||
var upgradeAliases = i18n.G("u")
|
|
||||||
|
|
||||||
// UpgradeCommand upgrades abra in-place.
|
// UpgradeCommand upgrades abra in-place.
|
||||||
var UpgradeCommand = &cobra.Command{
|
var UpgradeCommand = &cobra.Command{
|
||||||
// translators: `upgrade` command
|
Use: "upgrade [flags]",
|
||||||
Use: i18n.G("upgrade [flags]"),
|
Aliases: []string{"u"},
|
||||||
Aliases: strings.Split(upgradeAliases, ","),
|
Short: "Upgrade abra",
|
||||||
// translators: Short description for `upgrade` command
|
Long: `Upgrade abra in-place with the latest stable or release candidate.
|
||||||
Short: i18n.G("Upgrade abra"),
|
|
||||||
Long: i18n.G(`Upgrade abra in-place with the latest stable or release candidate.
|
|
||||||
|
|
||||||
By default, the latest stable release is downloaded.
|
By default, the latest stable release is downloaded.
|
||||||
|
|
||||||
Use "--rc/-r" to install the latest release candidate. Please bear in mind that
|
Use "--rc/-r" to install the latest release candidate. Please bear in mind that
|
||||||
it may contain absolutely catastrophic deal-breaker bugs. Thank you very much
|
it may contain absolutely catastrophic deal-breaker bugs. Thank you very much
|
||||||
for the testing efforts 💗`),
|
for the testing efforts 💗`,
|
||||||
Example: i18n.G(" abra upgrade --rc"),
|
Example: " abra upgrade --rc",
|
||||||
Args: cobra.NoArgs,
|
Args: cobra.NoArgs,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
mainURL := "https://install.abra.coopcloud.tech"
|
mainURL := "https://install.abra.coopcloud.tech"
|
||||||
@ -41,7 +33,7 @@ for the testing efforts 💗`),
|
|||||||
c = exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash -s -- --rc", releaseCandidateURL))
|
c = exec.Command("bash", "-c", fmt.Sprintf("wget -q -O- %s | bash -s -- --rc", releaseCandidateURL))
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf(i18n.G("attempting to run %s", c))
|
log.Debugf("attempting to run %s", c)
|
||||||
|
|
||||||
if err := internal.RunCmd(c); err != nil {
|
if err := internal.RunCmd(c); err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
@ -59,6 +51,6 @@ func init() {
|
|||||||
"rc",
|
"rc",
|
||||||
"r",
|
"r",
|
||||||
false,
|
false,
|
||||||
i18n.G("install release candidate (may contain bugs)"),
|
"install release candidate (may contain bugs)",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
23
cmd/kadabra/main.go
Normal file
23
cmd/kadabra/main.go
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
// Package main provides the command-line entrypoint.
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"coopcloud.tech/abra/cli/updater"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Version is the current version of Kadabra.
|
||||||
|
var Version string
|
||||||
|
|
||||||
|
// Commit is the current git commit of Kadabra.
|
||||||
|
var Commit string
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
if Version == "" {
|
||||||
|
Version = "dev"
|
||||||
|
}
|
||||||
|
if Commit == "" {
|
||||||
|
Commit = " "
|
||||||
|
}
|
||||||
|
|
||||||
|
updater.RunApp(Version, Commit)
|
||||||
|
}
|
||||||
134
go.mod
134
go.mod
@ -1,141 +1,135 @@
|
|||||||
module coopcloud.tech/abra
|
module coopcloud.tech/abra
|
||||||
|
|
||||||
go 1.24.0
|
go 1.23.0
|
||||||
|
|
||||||
toolchain go1.24.1
|
toolchain go1.23.1
|
||||||
|
|
||||||
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/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c
|
||||||
github.com/AlecAivazis/survey/v2 v2.3.7
|
github.com/AlecAivazis/survey/v2 v2.3.7
|
||||||
github.com/charmbracelet/bubbles v0.21.0
|
github.com/charmbracelet/bubbletea v1.3.4
|
||||||
github.com/charmbracelet/bubbletea v1.3.10
|
|
||||||
github.com/charmbracelet/lipgloss v1.1.0
|
github.com/charmbracelet/lipgloss v1.1.0
|
||||||
github.com/charmbracelet/log v0.4.2
|
github.com/charmbracelet/log v0.4.1
|
||||||
github.com/distribution/reference v0.6.0
|
github.com/distribution/reference v0.6.0
|
||||||
github.com/docker/cli v28.4.0+incompatible
|
github.com/docker/cli v28.0.1+incompatible
|
||||||
github.com/docker/docker v28.4.0+incompatible
|
github.com/docker/docker v28.0.1+incompatible
|
||||||
github.com/docker/go-units v0.5.0
|
github.com/docker/go-units v0.5.0
|
||||||
github.com/evertras/bubble-table v0.19.2
|
github.com/go-git/go-git/v5 v5.14.0
|
||||||
github.com/go-git/go-git/v5 v5.16.2
|
|
||||||
github.com/google/go-cmp v0.7.0
|
github.com/google/go-cmp v0.7.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.2
|
||||||
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.18.0
|
||||||
golang.org/x/term v0.35.0
|
golang.org/x/term v0.30.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.2
|
||||||
)
|
)
|
||||||
|
|
||||||
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-20230811130428-ced1acdcaa24 // indirect
|
||||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // 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.6 // indirect
|
||||||
github.com/atotto/clipboard v0.1.4 // 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/colorprofile v0.2.3-0.20250311203215-f60798e515dc // indirect
|
||||||
github.com/charmbracelet/x/ansi v0.10.2 // indirect
|
github.com/charmbracelet/x/ansi v0.8.0 // indirect
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
|
||||||
github.com/charmbracelet/x/term v0.2.1 // indirect
|
github.com/charmbracelet/x/term v0.2.1 // indirect
|
||||||
github.com/clipperhouse/uax29/v2 v2.2.0 // indirect
|
github.com/cloudflare/circl v1.6.0 // 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.4.1 // indirect
|
||||||
github.com/cyphar/filepath-securejoin v0.5.0 // 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-connections v0.6.0 // indirect
|
github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c // 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/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/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.2 // 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/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.2 // indirect
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // 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.4.0 // indirect
|
github.com/kevinburke/ssh_config v1.2.0 // indirect
|
||||||
github.com/klauspost/compress v1.18.0 // indirect
|
github.com/klauspost/compress v1.18.0 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 // indirect
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
github.com/lucasb-eyer/go-colorful v1.3.0 // indirect
|
|
||||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
github.com/mattn/go-colorable v0.1.14 // 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-localereader v0.0.1 // indirect
|
||||||
github.com/mattn/go-runewidth v0.0.19 // 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/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/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/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect
|
||||||
github.com/muesli/cancelreader v0.2.2 // indirect
|
github.com/muesli/cancelreader v0.2.2 // indirect
|
||||||
github.com/muesli/reflow v0.3.0 // indirect
|
|
||||||
github.com/muesli/termenv v0.16.0 // 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/pjbgf/sha1cd v0.5.0 // indirect
|
github.com/pelletier/go-toml v1.9.5 // indirect
|
||||||
|
github.com/pjbgf/sha1cd v0.3.2 // 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.66.1 // indirect
|
github.com/prometheus/common v0.63.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.1 // indirect
|
||||||
github.com/spf13/pflag v1.0.10 // indirect
|
github.com/spf13/pflag v1.0.6 // 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
|
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 // indirect
|
||||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
go.opentelemetry.io/otel v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.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.38.0 // indirect
|
go.opentelemetry.io/otel/metric v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
|
go.opentelemetry.io/otel/sdk v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
|
go.opentelemetry.io/otel/sdk/metric v1.35.0 // indirect
|
||||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
go.opentelemetry.io/otel/trace v1.35.0 // indirect
|
||||||
go.opentelemetry.io/proto/otlp v1.8.0 // indirect
|
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||||
go.yaml.in/yaml/v2 v2.4.3 // indirect
|
golang.org/x/crypto v0.36.0 // indirect
|
||||||
golang.org/x/crypto v0.42.0 // indirect
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
|
||||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect
|
golang.org/x/net v0.37.0 // indirect
|
||||||
golang.org/x/net v0.44.0 // indirect
|
golang.org/x/sync v0.12.0 // indirect
|
||||||
golang.org/x/text v0.29.0 // indirect
|
golang.org/x/text v0.23.0 // indirect
|
||||||
golang.org/x/time v0.13.0 // indirect
|
golang.org/x/time v0.11.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect
|
google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 // indirect
|
||||||
google.golang.org/grpc v1.75.1 // indirect
|
google.golang.org/grpc v1.71.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.9 // indirect
|
google.golang.org/protobuf v1.36.5 // 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
|
||||||
)
|
)
|
||||||
@ -148,15 +142,15 @@ require (
|
|||||||
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.1 // indirect
|
||||||
github.com/prometheus/client_golang v1.23.2 // indirect
|
github.com/prometheus/client_golang v1.21.1 // 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.10.1
|
github.com/spf13/cobra v1.9.1
|
||||||
github.com/stretchr/testify v1.11.1
|
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.36.0
|
golang.org/x/sys v0.31.0
|
||||||
)
|
)
|
||||||
|
|||||||
289
go.sum
289
go.sum
@ -22,15 +22,15 @@ cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIA
|
|||||||
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
|
||||||
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
|
||||||
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-20250818180036-0ec1b205b5ca h1:gSD53tBAsbIGq4SnFfq+mEep6foekQ2a5ea7b38qkm0=
|
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb h1:Ws6WEwKXeaYEkfdkX6AqX1XLPuaCeyStEtxbmEJPllk=
|
||||||
coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
|
coopcloud.tech/tagcmp v0.0.0-20230809071031-eb3e7758d4eb/go.mod h1:ESVm0wQKcbcFi06jItF3rI7enf4Jt2PvbkWpDDHk1DQ=
|
||||||
dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8=
|
dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s=
|
||||||
dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA=
|
dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
|
||||||
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/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c h1:oeKnUB79PKYD8D0/unYuu7MRcWryQQWOns8+JL+acrs=
|
||||||
git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c/go.mod h1:fQuhwrpg6qb9NlFXKYi/LysWu1wxjraS8sxyW12CUF0=
|
git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c/go.mod h1:fQuhwrpg6qb9NlFXKYi/LysWu1wxjraS8sxyW12CUF0=
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk=
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU=
|
||||||
github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8=
|
github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/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=
|
||||||
@ -49,8 +49,8 @@ github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZ
|
|||||||
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU=
|
||||||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||||
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.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
github.com/BurntSushi/toml v1.4.0 h1:kuoIxZQy2WRRk1pttg9asf+WVv6tWQuBNVmK8+nqPr0=
|
||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
github.com/BurntSushi/toml v1.4.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=
|
||||||
@ -79,13 +79,14 @@ 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.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw=
|
github.com/ProtonMail/go-crypto v1.1.6 h1:ZcV+Ropw6Qn0AX9brlQLAUXfqLBc7Bl+f/DmNxpLfdw=
|
||||||
github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE=
|
github.com/ProtonMail/go-crypto v1.1.6/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
|
||||||
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=
|
||||||
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
|
||||||
github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
github.com/Shopify/logrus-bugsnag v0.0.0-20170309145241-6dbc35f2c30d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
||||||
|
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d h1:UrqY+r/OJnIp5u0s1SbQ8dVfLCZJsnvazdBP5hS4iRs=
|
||||||
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ=
|
||||||
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
|
||||||
@ -99,8 +100,6 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5
|
|||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
|
||||||
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
|
||||||
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
|
||||||
github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4=
|
|
||||||
github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI=
|
|
||||||
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
@ -122,35 +121,34 @@ github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dR
|
|||||||
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
|
github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk=
|
||||||
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s=
|
||||||
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
||||||
|
github.com/bugsnag/bugsnag-go v1.0.5-0.20150529004307-13fd6b8acda0 h1:s7+5BfS4WFJoVF9pnB8kBk03S7pZXRdKamnV0FOl5Sc=
|
||||||
github.com/bugsnag/bugsnag-go v1.0.5-0.20150529004307-13fd6b8acda0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
github.com/bugsnag/bugsnag-go v1.0.5-0.20150529004307-13fd6b8acda0/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8=
|
||||||
|
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b h1:otBG+dV+YK+Soembjv71DPz3uX/V/6MMlSyD9JBQ6kQ=
|
||||||
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
|
github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50=
|
||||||
|
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0 h1:nvj0OLI3YqYXer/kZD8Ri1aaunCxIEsOst1BVJswV0o=
|
||||||
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE=
|
||||||
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/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/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs=
|
github.com/charmbracelet/bubbletea v1.3.4 h1:kCg7B+jSCFPLYRA52SDZjr51kG/fMUEoPoZrkaDHyoI=
|
||||||
github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
|
github.com/charmbracelet/bubbletea v1.3.4/go.mod h1:dtcUCyCGEX3g9tosuYiut3MXgY/Jsv9nKVdibKKRRXo=
|
||||||
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc h1:4pZI35227imm7yK2bGPcfpFEmuY1gc2YSTShr4iJBfs=
|
||||||
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
|
github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc/go.mod h1:X4/0JoqgTIPSFcRA/P6INZzIuyqdFY5rm8tb41s9okk=
|
||||||
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 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY=
|
||||||
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30=
|
||||||
github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig=
|
github.com/charmbracelet/log v0.4.1 h1:6AYnoHKADkghm/vt4neaNEXkxcXLSV2g1rdyFDOpTyk=
|
||||||
github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw=
|
github.com/charmbracelet/log v0.4.1/go.mod h1:pXgyTsqsVu4N9hGdHmQ0xEA4RsXof402LX9ZgiITn2I=
|
||||||
github.com/charmbracelet/x/ansi v0.10.2 h1:ith2ArZS0CJG30cIUfID1LXN7ZFXRCww6RUvAPA+Pzw=
|
github.com/charmbracelet/x/ansi v0.8.0 h1:9GTq3xq9caJW8ZrBTe0LIe2fvfLR/bYXKTx2llXn7xE=
|
||||||
github.com/charmbracelet/x/ansi v0.10.2/go.mod h1:HbLdJjQH4UH4AqA2HpRWuWNluRE6zxJH/yteYEYCFa8=
|
github.com/charmbracelet/x/ansi v0.8.0/go.mod h1:wdYl/ONOLHLIVmQaxbIYEC/cRKOQyjTkowiI4blgS9Q=
|
||||||
github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k=
|
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/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs=
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ=
|
github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30=
|
||||||
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/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 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ=
|
||||||
github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg=
|
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=
|
||||||
@ -168,11 +166,10 @@ github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJ
|
|||||||
github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs=
|
||||||
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA=
|
||||||
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
|
||||||
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
|
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ=
|
||||||
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
|
|
||||||
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.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
|
github.com/cloudflare/circl v1.6.0 h1:cr5JKic4HI+LkINy2lg3W2jF8sHCVTBncJr5gIIq7qk=
|
||||||
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
|
github.com/cloudflare/circl v1.6.0/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=
|
||||||
@ -218,10 +215,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=
|
||||||
@ -244,8 +237,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=
|
||||||
@ -292,9 +283,8 @@ 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.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=
|
||||||
@ -302,8 +292,8 @@ 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.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw=
|
github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
|
||||||
github.com/cyphar/filepath-securejoin v0.5.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
|
github.com/cyphar/filepath-securejoin v0.4.1/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=
|
||||||
@ -322,24 +312,24 @@ 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.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY=
|
github.com/docker/cli v28.0.1+incompatible h1:g0h5NQNda3/CxIsaZfH4Tyf6vpxFth7PYl3hgCPOKzs=
|
||||||
github.com/docker/cli v28.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8=
|
github.com/docker/cli v28.0.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.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk=
|
github.com/docker/docker v28.0.1+incompatible h1:FCHjSRdXhNRFjlHMTv4jUNlIBbTeRjrWfeFuJp7jpo0=
|
||||||
github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
|
github.com/docker/docker v28.0.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.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8=
|
||||||
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
|
github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo=
|
||||||
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.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94=
|
github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c=
|
||||||
github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE=
|
github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc=
|
||||||
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=
|
||||||
@ -373,8 +363,6 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6
|
|||||||
github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM=
|
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/evertras/bubble-table v0.19.2 h1:u77oiM6JlRR+CvS5FZc3Hz+J6iEsvEDcR5kO8OFb1Yw=
|
|
||||||
github.com/evertras/bubble-table v0.19.2/go.mod h1:ifHujS1YxwnYSOgcR2+m3GnJ84f7CVU/4kUOxUCjEbQ=
|
|
||||||
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
|
||||||
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM=
|
||||||
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE=
|
||||||
@ -384,6 +372,8 @@ github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoD
|
|||||||
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
|
||||||
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
|
||||||
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY=
|
||||||
|
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||||
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
|
github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA=
|
||||||
github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw=
|
github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw=
|
||||||
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0=
|
||||||
@ -399,8 +389,8 @@ github.com/go-git/go-billy/v5 v5.6.2 h1:6Q86EsPXMa7c3YZ3aLAQsMA0VlWmy43r6FHqa/UN
|
|||||||
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.2/go.mod h1:rcFC2rAsp/erv7CMz9GczHcuD0D32fWzH+MJAU+jaUU=
|
||||||
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.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
|
github.com/go-git/go-git/v5 v5.14.0 h1:/MD3lCrGjCen5WfEAzKg00MJJffKhC8gzS80ycmCi60=
|
||||||
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
|
github.com/go-git/go-git/v5 v5.14.0/go.mod h1:Z5Xhoia5PcWA3NF8vRLURn9E5FRhSl7dGj9ItW3Wk5k=
|
||||||
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=
|
||||||
@ -414,8 +404,8 @@ github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KE
|
|||||||
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
github.com/go-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas=
|
||||||
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU=
|
||||||
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.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
|
||||||
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
github.com/go-logr/logr v1.4.2/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=
|
||||||
@ -429,10 +419,11 @@ github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8
|
|||||||
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I=
|
||||||
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
|
||||||
|
github.com/go-sql-driver/mysql v1.3.0 h1:pgwjLi/dvffoP9aabwkT3AKpXQM93QARkjFhDDqC1UE=
|
||||||
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
|
||||||
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.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
|
github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss=
|
||||||
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.2.1/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=
|
||||||
@ -447,6 +438,7 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV
|
|||||||
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||||
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||||
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||||
|
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
|
||||||
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
|
||||||
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
|
||||||
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
|
||||||
@ -483,6 +475,7 @@ github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek
|
|||||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
|
||||||
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
|
||||||
|
github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93 h1:jc2UWq7CbdszqeH6qu1ougXMIUBfSy8Pbh/anURYbGI=
|
||||||
github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
github.com/google/certificate-transparency-go v1.0.10-0.20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
|
||||||
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
|
||||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||||
@ -536,8 +529,9 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf
|
|||||||
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
github.com/grpc-ecosystem/grpc-gateway v1.9.0/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.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
|
||||||
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.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo=
|
||||||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs=
|
github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI=
|
||||||
|
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=
|
||||||
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
|
||||||
@ -548,10 +542,11 @@ github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVH
|
|||||||
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
|
github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I=
|
||||||
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
|
||||||
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.8 h1:ylXZWnqa7Lhqpk0L1P1LzDtGcCR0rPVUrx/c8Unxc48=
|
github.com/hashicorp/go-retryablehttp v0.7.7 h1:C8hUCYzor8PIfXHa4UrZkU4VvK8o9ISHxT2Q8+VepXU=
|
||||||
github.com/hashicorp/go-retryablehttp v0.7.8/go.mod h1:rjiScheydd+CxvumBsIrFKlx3iS0jrZ7LvzFGFmuKbw=
|
github.com/hashicorp/go-retryablehttp v0.7.7/go.mod h1:pkQpWZeYWskR+D1tR2O5OcBFOxfA7DoAO6xtkuQnHTk=
|
||||||
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/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog=
|
||||||
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68=
|
||||||
@ -568,7 +563,9 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf
|
|||||||
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
|
github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
|
||||||
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
|
||||||
|
github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8 h1:CZkYfurY6KGhVtlalI4QwQ6T0Cu6iuY3e0x5RLu96WE=
|
||||||
github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
|
github.com/jinzhu/gorm v0.0.0-20170222002820-5409931a1bb8/go.mod h1:Vla75njaFJ8clLU1W44h34PjIkijhjHIYnZxMqCdxqo=
|
||||||
|
github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d h1:jRQLvyVGL+iVtDElaEIDdKwpPqUIZJfzkNLV34htpEc=
|
||||||
github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
github.com/jinzhu/inflection v0.0.0-20170102125226-1c35d901db3d/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
|
||||||
@ -586,8 +583,8 @@ github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVE
|
|||||||
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
|
||||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||||
github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ=
|
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
|
||||||
github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M=
|
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
|
||||||
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
|
||||||
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||||
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
|
||||||
@ -597,8 +594,6 @@ github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdY
|
|||||||
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.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo=
|
||||||
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y=
|
|
||||||
github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
|
|
||||||
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=
|
||||||
@ -616,13 +611,12 @@ 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.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag=
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
|
github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
|
||||||
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
|
||||||
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc=
|
||||||
@ -640,9 +634,8 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D
|
|||||||
github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4=
|
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-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.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk=
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
|
|
||||||
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||||
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o=
|
||||||
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y=
|
||||||
@ -663,28 +656,26 @@ github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db/go.mod h1:l0
|
|||||||
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
|
||||||
github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v0.0.0-20150613213606-2caf8efc9366/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
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.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg=
|
github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78=
|
||||||
github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4=
|
github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI=
|
||||||
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=
|
||||||
github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8=
|
github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8=
|
||||||
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.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs=
|
github.com/moby/sys/user v0.3.0 h1:9ni5DlcW5an3SvRSx4MouotOygvzaXbaSrc/wGDFWPo=
|
||||||
github.com/moby/sys/user v0.4.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/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=
|
||||||
@ -702,8 +693,6 @@ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D
|
|||||||
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo=
|
||||||
github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA=
|
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/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo=
|
||||||
github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s=
|
|
||||||
github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8=
|
|
||||||
github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc=
|
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/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=
|
||||||
@ -766,12 +755,15 @@ github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqi
|
|||||||
github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
|
github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo=
|
||||||
github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
|
github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8=
|
||||||
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
|
github.com/opencontainers/selinux v1.10.0/go.mod h1:2i0OySw99QjzBBQByd1Gr9gSjvuho1lHsJxIJ3gGbJI=
|
||||||
|
github.com/opentracing/opentracing-go v1.1.0 h1:pWlfV3Bxv7k65HYwkikxat0+s3pV4bsqf19k25Ur8rU=
|
||||||
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o=
|
||||||
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
|
||||||
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
|
||||||
|
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/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.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0=
|
github.com/pjbgf/sha1cd v0.3.2 h1:a9wb0bp1oC2TGwStyn0Umc/IGKQnEgF0vVaZ8QF8eo4=
|
||||||
github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM=
|
github.com/pjbgf/sha1cd v0.3.2/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=
|
||||||
@ -787,23 +779,23 @@ 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.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o=
|
github.com/prometheus/client_golang v1.21.1 h1:DOvXXTqVzvkIewV/CDPFdejpMCGeMcbGCQ8YOmu+Ibk=
|
||||||
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
|
github.com/prometheus/client_golang v1.21.1/go.mod h1:U9NM32ykUErtVBxdvD3zfi+EuFkkaBvMb09mIfe0Zgg=
|
||||||
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=
|
||||||
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
|
||||||
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.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
|
github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E=
|
||||||
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
|
github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY=
|
||||||
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.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
|
github.com/prometheus/common v0.63.0 h1:YR/EIY1o3mEFP/kZCD7iDMnLPlGyuU2Gb3HIcXnA98k=
|
||||||
github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
|
github.com/prometheus/common v0.63.0/go.mod h1:VVFF/fBIoToEnWRVkYoXEkq3R3paCoxG9PXP74SnV18=
|
||||||
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=
|
||||||
@ -815,10 +807,9 @@ github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+Gx
|
|||||||
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
github.com/prometheus/procfs v0.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
|
||||||
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.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
|
github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc=
|
||||||
github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
|
github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk=
|
||||||
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.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
|
||||||
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=
|
||||||
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
@ -837,8 +828,8 @@ github.com/schollz/progressbar/v3 v3.18.0/go.mod h1:IsO3lpbaGuzh8zIMzgY3+J8l4C8G
|
|||||||
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.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3 h1:n661drycOFuPLCN3Uc8sB6B/s6Z4t2xvBgU1htSHuq8=
|
||||||
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
|
github.com/sergi/go-diff v1.3.2-0.20230802210424-5b0b94c5c0d3/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=
|
||||||
@ -857,16 +848,19 @@ github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:s
|
|||||||
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
|
||||||
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
|
||||||
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
|
||||||
|
github.com/spf13/afero v1.2.2 h1:5jhuqJyZCZf2JRofRvN/nIFgIWNzPa3/Vz8mYylgbWc=
|
||||||
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
|
||||||
github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
github.com/spf13/cast v0.0.0-20150508191742-4d07383ffe94/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
|
||||||
|
github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
|
||||||
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
|
||||||
github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
|
||||||
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.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s=
|
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
|
||||||
github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0=
|
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
|
||||||
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/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
|
||||||
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
github.com/spf13/pflag v1.0.0/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
|
||||||
@ -874,10 +868,10 @@ github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bd
|
|||||||
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/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
|
||||||
github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk=
|
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
github.com/spf13/pflag v1.0.10/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/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE=
|
||||||
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
|
github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8=
|
||||||
github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
@ -891,8 +885,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P
|
|||||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||||
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||||
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww=
|
||||||
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
|
github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI=
|
||||||
@ -950,39 +944,37 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
|
|||||||
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
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.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
|
||||||
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU=
|
||||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg=
|
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ=
|
||||||
go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8=
|
go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ=
|
||||||
go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM=
|
go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8=
|
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw=
|
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/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54=
|
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI=
|
||||||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk=
|
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/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.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA=
|
go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M=
|
||||||
go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI=
|
go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE=
|
||||||
go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E=
|
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
|
||||||
go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg=
|
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
|
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
|
||||||
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
|
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
|
||||||
go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE=
|
go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs=
|
||||||
go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs=
|
go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc=
|
||||||
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.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE=
|
go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4=
|
||||||
go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0=
|
go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4=
|
||||||
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=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
|
||||||
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
|
||||||
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0=
|
|
||||||
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8=
|
|
||||||
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||||
@ -1001,8 +993,8 @@ 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.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
|
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
|
||||||
golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
|
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
|
||||||
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=
|
||||||
@ -1013,8 +1005,8 @@ 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-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU=
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
|
||||||
golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk=
|
golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
|
||||||
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=
|
||||||
@ -1078,8 +1070,8 @@ 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.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
|
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c=
|
||||||
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
|
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
|
||||||
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=
|
||||||
@ -1097,6 +1089,8 @@ 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.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
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=
|
||||||
@ -1174,14 +1168,15 @@ 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-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.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
|
||||||
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||||
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.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
|
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
|
||||||
golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
|
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
|
||||||
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=
|
||||||
@ -1191,16 +1186,16 @@ 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.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
|
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY=
|
||||||
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
|
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4=
|
||||||
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.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI=
|
golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0=
|
||||||
golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
|
golang.org/x/time v0.11.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=
|
||||||
@ -1250,8 +1245,6 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T
|
|||||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
|
|
||||||
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
|
|
||||||
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0=
|
||||||
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
|
||||||
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
|
||||||
@ -1296,10 +1289,10 @@ 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-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4 h1:IFnXJq3UPB3oBREOodn1v1aGQeZYQclEmvWRMN0PSsY=
|
||||||
google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk=
|
google.golang.org/genproto/googleapis/api v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:c8q6Z6OCqnfVIqUFJkCzKcrj8eCvUrz+K4KRzSTuANg=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 h1:i8QOKZfYg6AbGVZzUAY3LrNWCKF8O6zFisU9Wl9RER4=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4 h1:iK2jbkWL86DXjEx0qiHcRE9dE4/Ahua5k6V8OWFb//c=
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ=
|
google.golang.org/genproto/googleapis/rpc v0.0.0-20250313205543-e70fdf4c4cb4/go.mod h1:LuRYeWDFV6WOn90g357N17oMCaxpgCnbi/44qJvDn2I=
|
||||||
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=
|
||||||
@ -1319,8 +1312,8 @@ 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.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI=
|
google.golang.org/grpc v1.71.0 h1:kF77BGdPTQ4/JZWMlb9VpJ5pa25aqvVqogsxNHHdeBg=
|
||||||
google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ=
|
google.golang.org/grpc v1.71.0/go.mod h1:H0GRtasmQOh9LkFoCPDu3ZrwUtD1YGE+b2vYBYd/8Ec=
|
||||||
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=
|
||||||
@ -1334,10 +1327,11 @@ 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.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
|
google.golang.org/protobuf v1.36.5 h1:tPhr+woSbjfYvY6/GPufUoYizxw1cF/yFoxJ2fmpwlM=
|
||||||
google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
|
google.golang.org/protobuf v1.36.5/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/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU=
|
gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v1.0.0-20141024133853-64131543e789/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
@ -1353,6 +1347,7 @@ gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKW
|
|||||||
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k=
|
||||||
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
|
||||||
|
gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1 h1:d4KQkxAaAiRY2h5Zqis161Pv91A37uZyJOx73duwUwM=
|
||||||
gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1/go.mod h1:WbjuEoo1oadwzQ4apSDU+JTvmllEHtsNHS6y7vFc7iw=
|
gopkg.in/rethinkdb/rethinkdb-go.v6 v6.2.1/go.mod h1:WbjuEoo1oadwzQ4apSDU+JTvmllEHtsNHS6y7vFc7iw=
|
||||||
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
|
||||||
|
|||||||
@ -2,7 +2,6 @@ package app
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@ -14,7 +13,6 @@ import (
|
|||||||
"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/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"coopcloud.tech/abra/pkg/upstream/convert"
|
"coopcloud.tech/abra/pkg/upstream/convert"
|
||||||
"coopcloud.tech/abra/pkg/upstream/stack"
|
"coopcloud.tech/abra/pkg/upstream/stack"
|
||||||
@ -38,7 +36,7 @@ func Get(appName string) (App, error) {
|
|||||||
return App{}, err
|
return App{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("loaded app %s: %s", appName, app))
|
log.Debugf("loaded app %s: %s", appName, app)
|
||||||
|
|
||||||
return app, nil
|
return app, nil
|
||||||
}
|
}
|
||||||
@ -49,7 +47,7 @@ func Get(appName string) (App, error) {
|
|||||||
func GetApp(apps AppFiles, name AppName) (App, error) {
|
func GetApp(apps AppFiles, name AppName) (App, error) {
|
||||||
appFile, exists := apps[name]
|
appFile, exists := apps[name]
|
||||||
if !exists {
|
if !exists {
|
||||||
return App{}, errors.New(i18n.G("cannot find app with name %s", name))
|
return App{}, fmt.Errorf("cannot find app with name %s", name)
|
||||||
}
|
}
|
||||||
|
|
||||||
app, err := ReadAppEnvFile(appFile, name)
|
app, err := ReadAppEnvFile(appFile, name)
|
||||||
@ -138,7 +136,7 @@ func StackName(appName string) string {
|
|||||||
stackName := SanitiseAppName(appName)
|
stackName := SanitiseAppName(appName)
|
||||||
|
|
||||||
if len(stackName) > config.MAX_SANITISED_APP_NAME_LENGTH {
|
if len(stackName) > config.MAX_SANITISED_APP_NAME_LENGTH {
|
||||||
log.Debug(i18n.G("trimming %s to %s to avoid runtime limits", stackName, stackName[:config.MAX_SANITISED_APP_NAME_LENGTH]))
|
log.Debugf("trimming %s to %s to avoid runtime limits", stackName, stackName[:config.MAX_SANITISED_APP_NAME_LENGTH])
|
||||||
stackName = stackName[:config.MAX_SANITISED_APP_NAME_LENGTH]
|
stackName = stackName[:config.MAX_SANITISED_APP_NAME_LENGTH]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,12 +243,12 @@ func (a ByName) Less(i, j int) bool {
|
|||||||
func ReadAppEnvFile(appFile AppFile, name AppName) (App, error) {
|
func ReadAppEnvFile(appFile AppFile, name AppName) (App, error) {
|
||||||
env, err := envfile.ReadEnv(appFile.Path)
|
env, err := envfile.ReadEnv(appFile.Path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return App{}, errors.New(i18n.G("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())
|
||||||
}
|
}
|
||||||
|
|
||||||
app, err := NewApp(env, name, appFile)
|
app, err := NewApp(env, name, appFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return App{}, errors.New(i18n.G("env file for %s has issues: %s", name, err.Error()))
|
return App{}, fmt.Errorf("env file for %s has issues: %s", name, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
return app, nil
|
return app, nil
|
||||||
@ -264,7 +262,7 @@ func NewApp(env envfile.AppEnv, name string, appFile AppFile) (App, error) {
|
|||||||
if !exists {
|
if !exists {
|
||||||
recipeName, exists = env["TYPE"]
|
recipeName, exists = env["TYPE"]
|
||||||
if !exists {
|
if !exists {
|
||||||
return App{}, errors.New(i18n.G("%s is missing the TYPE env var?", name))
|
return App{}, fmt.Errorf("%s is missing the TYPE env var?", name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,13 +290,13 @@ func LoadAppFiles(servers ...string) (AppFiles, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("collecting metadata from %v servers: %s", len(servers), strings.Join(servers, ", ")))
|
log.Debugf("collecting metadata from %v servers: %s", len(servers), strings.Join(servers, ", "))
|
||||||
|
|
||||||
for _, server := range servers {
|
for _, server := range servers {
|
||||||
serverDir := path.Join(config.SERVERS_DIR, server)
|
serverDir := path.Join(config.SERVERS_DIR, server)
|
||||||
files, err := config.GetAllFilesInDirectory(serverDir)
|
files, err := config.GetAllFilesInDirectory(serverDir)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return appFiles, errors.New(i18n.G("server %s doesn't exist? Run \"abra server ls\" to check", server))
|
return appFiles, fmt.Errorf("server %s doesn't exist? Run \"abra server ls\" to check", server)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
@ -377,7 +375,7 @@ func TemplateAppEnvSample(r recipe.Recipe, appName, server, domain string) error
|
|||||||
|
|
||||||
appEnvPath := path.Join(config.ABRA_DIR, "servers", server, fmt.Sprintf("%s.env", appName))
|
appEnvPath := path.Join(config.ABRA_DIR, "servers", server, fmt.Sprintf("%s.env", appName))
|
||||||
if _, err := os.Stat(appEnvPath); !os.IsNotExist(err) {
|
if _, err := os.Stat(appEnvPath); !os.IsNotExist(err) {
|
||||||
return errors.New(i18n.G("%s already exists?", appEnvPath))
|
return fmt.Errorf("%s already exists?", appEnvPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = os.WriteFile(appEnvPath, envSample, 0o664)
|
err = os.WriteFile(appEnvPath, envSample, 0o664)
|
||||||
@ -390,19 +388,14 @@ func TemplateAppEnvSample(r recipe.Recipe, appName, server, domain string) error
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
newContents := strings.Replace(
|
newContents := strings.Replace(string(read), r.Name+".example.com", domain, -1)
|
||||||
string(read),
|
|
||||||
fmt.Sprintf("%s.example.com", r.Name),
|
|
||||||
domain,
|
|
||||||
-1,
|
|
||||||
)
|
|
||||||
|
|
||||||
err = os.WriteFile(appEnvPath, []byte(newContents), 0)
|
err = os.WriteFile(appEnvPath, []byte(newContents), 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("copied & templated %s to %s", r.SampleEnvPath, appEnvPath))
|
log.Debugf("copied & templated %s to %s", r.SampleEnvPath, appEnvPath)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -426,16 +419,14 @@ func GetAppStatuses(apps []App, MachineReadable bool) (map[string]map[string]str
|
|||||||
|
|
||||||
var bar *progressbar.ProgressBar
|
var bar *progressbar.ProgressBar
|
||||||
if !MachineReadable {
|
if !MachineReadable {
|
||||||
bar = formatter.CreateProgressbar(len(servers), i18n.G("querying remote servers..."))
|
bar = formatter.CreateProgressbar(len(servers), "querying remote servers...")
|
||||||
}
|
}
|
||||||
|
|
||||||
ch := make(chan stack.StackStatus, len(servers))
|
ch := make(chan stack.StackStatus, len(servers))
|
||||||
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) {
|
||||||
@ -471,6 +462,13 @@ func GetAppStatuses(apps []App, MachineReadable bool) (map[string]map[string]str
|
|||||||
result["chaosVersion"] = chaosVersion
|
result["chaosVersion"] = chaosVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
labelKey = fmt.Sprintf("coop-cloud.%s.autoupdate", name)
|
||||||
|
if autoUpdate, ok := service.Spec.Labels[labelKey]; ok {
|
||||||
|
result["autoUpdate"] = autoUpdate
|
||||||
|
} 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
|
||||||
@ -482,7 +480,7 @@ func GetAppStatuses(apps []App, MachineReadable bool) (map[string]map[string]str
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("retrieved app statuses: %s", statuses))
|
log.Debugf("retrieved app statuses: %s", statuses)
|
||||||
|
|
||||||
return statuses, nil
|
return statuses, nil
|
||||||
}
|
}
|
||||||
@ -496,7 +494,7 @@ func GetAppComposeConfig(recipe string, opts stack.Deploy, appEnv envfile.AppEnv
|
|||||||
return &composetypes.Config{}, err
|
return &composetypes.Config{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("retrieved %s for %s", compose.Filename, recipe))
|
log.Debugf("retrieved %s for %s", compose.Filename, recipe)
|
||||||
|
|
||||||
return compose, nil
|
return compose, nil
|
||||||
}
|
}
|
||||||
@ -505,13 +503,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.Debug(i18n.G("adding env vars to %s service config", stackName))
|
log.Debugf("adding env vars to %s service config", 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.Debug(i18n.G("%s: %s: %s", stackName, k, value))
|
log.Debugf("%s: %s: %s", stackName, k, value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -572,9 +570,9 @@ func ReadAbraShCmdNames(abraSh string) ([]string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(cmdNames) > 0 {
|
if len(cmdNames) > 0 {
|
||||||
log.Debug(i18n.G("read %s from %s", strings.Join(cmdNames, " "), abraSh))
|
log.Debugf("read %s from %s", strings.Join(cmdNames, " "), abraSh)
|
||||||
} else {
|
} else {
|
||||||
log.Debug(i18n.G("read 0 command names from %s", abraSh))
|
log.Debugf("read 0 command names from %s", abraSh)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmdNames, nil
|
return cmdNames, nil
|
||||||
@ -617,7 +615,7 @@ func (a App) WipeRecipeVersion() error {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("version wiped from %s.env", a.Domain))
|
log.Debugf("version wiped from %s.env", a.Domain)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -674,13 +672,13 @@ func (a App) WriteRecipeVersion(version string, dryRun bool) error {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
log.Debug(i18n.G("skipping writing version %s because dry run", version))
|
log.Debugf("skipping writing version %s because dry run", version)
|
||||||
}
|
}
|
||||||
|
|
||||||
if !skipped {
|
if !skipped {
|
||||||
log.Debug(i18n.G("version %s saved to %s.env", version, a.Domain))
|
log.Debugf("version %s saved to %s.env", version, a.Domain)
|
||||||
} else {
|
} else {
|
||||||
log.Debug(i18n.G("skipping version %s write as already exists in %s.env", version, a.Domain))
|
log.Debugf("skipping version %s write as already exists in %s.env", version, a.Domain)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -1,11 +1,10 @@
|
|||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
"coopcloud.tech/abra/pkg/envfile"
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
composetypes "github.com/docker/cli/cli/compose/types"
|
composetypes "github.com/docker/cli/cli/compose/types"
|
||||||
)
|
)
|
||||||
@ -15,7 +14,7 @@ import (
|
|||||||
func SetRecipeLabel(compose *composetypes.Config, stackName string, recipe string) {
|
func SetRecipeLabel(compose *composetypes.Config, stackName string, recipe string) {
|
||||||
for _, service := range compose.Services {
|
for _, service := range compose.Services {
|
||||||
if service.Name == "app" {
|
if service.Name == "app" {
|
||||||
log.Debug(i18n.G("set recipe label 'coop-cloud.%s.recipe' to %s for %s", stackName, recipe, stackName))
|
log.Debugf("set recipe label 'coop-cloud.%s.recipe' to %s for %s", stackName, recipe, stackName)
|
||||||
labelKey := fmt.Sprintf("coop-cloud.%s.recipe", stackName)
|
labelKey := fmt.Sprintf("coop-cloud.%s.recipe", stackName)
|
||||||
service.Deploy.Labels[labelKey] = recipe
|
service.Deploy.Labels[labelKey] = recipe
|
||||||
}
|
}
|
||||||
@ -27,7 +26,7 @@ func SetRecipeLabel(compose *composetypes.Config, stackName string, recipe strin
|
|||||||
func SetChaosLabel(compose *composetypes.Config, stackName string, chaos bool) {
|
func SetChaosLabel(compose *composetypes.Config, stackName string, chaos bool) {
|
||||||
for _, service := range compose.Services {
|
for _, service := range compose.Services {
|
||||||
if service.Name == "app" {
|
if service.Name == "app" {
|
||||||
log.Debug(i18n.G("set label 'coop-cloud.%s.chaos' to %v for %s", stackName, chaos, stackName))
|
log.Debugf("set label 'coop-cloud.%s.chaos' to %v for %s", stackName, chaos, stackName)
|
||||||
labelKey := fmt.Sprintf("coop-cloud.%s.chaos", stackName)
|
labelKey := fmt.Sprintf("coop-cloud.%s.chaos", stackName)
|
||||||
service.Deploy.Labels[labelKey] = strconv.FormatBool(chaos)
|
service.Deploy.Labels[labelKey] = strconv.FormatBool(chaos)
|
||||||
}
|
}
|
||||||
@ -38,7 +37,7 @@ func SetChaosLabel(compose *composetypes.Config, stackName string, chaos bool) {
|
|||||||
func SetChaosVersionLabel(compose *composetypes.Config, stackName string, chaosVersion string) {
|
func SetChaosVersionLabel(compose *composetypes.Config, stackName string, chaosVersion string) {
|
||||||
for _, service := range compose.Services {
|
for _, service := range compose.Services {
|
||||||
if service.Name == "app" {
|
if service.Name == "app" {
|
||||||
log.Debug(i18n.G("set label 'coop-cloud.%s.chaos-version' to %v for %s", stackName, chaosVersion, stackName))
|
log.Debugf("set label 'coop-cloud.%s.chaos-version' to %v for %s", stackName, chaosVersion, stackName)
|
||||||
labelKey := fmt.Sprintf("coop-cloud.%s.chaos-version", stackName)
|
labelKey := fmt.Sprintf("coop-cloud.%s.chaos-version", stackName)
|
||||||
service.Deploy.Labels[labelKey] = chaosVersion
|
service.Deploy.Labels[labelKey] = chaosVersion
|
||||||
}
|
}
|
||||||
@ -48,43 +47,52 @@ func SetChaosVersionLabel(compose *composetypes.Config, stackName string, chaosV
|
|||||||
func SetVersionLabel(compose *composetypes.Config, stackName string, version string) {
|
func SetVersionLabel(compose *composetypes.Config, stackName string, version string) {
|
||||||
for _, service := range compose.Services {
|
for _, service := range compose.Services {
|
||||||
if service.Name == "app" {
|
if service.Name == "app" {
|
||||||
log.Debug(i18n.G("set label 'coop-cloud.%s.version' to %v for %s", stackName, version, stackName))
|
log.Debugf("set label 'coop-cloud.%s.version' to %v for %s", stackName, version, stackName)
|
||||||
labelKey := fmt.Sprintf("coop-cloud.%s.version", stackName)
|
labelKey := fmt.Sprintf("coop-cloud.%s.version", stackName)
|
||||||
service.Deploy.Labels[labelKey] = version
|
service.Deploy.Labels[labelKey] = version
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// the auto update process.
|
||||||
|
func SetUpdateLabel(compose *composetypes.Config, stackName string, appEnv envfile.AppEnv) {
|
||||||
|
for _, service := range compose.Services {
|
||||||
|
if service.Name == "app" {
|
||||||
|
enable_auto_update, exists := appEnv["ENABLE_AUTO_UPDATE"]
|
||||||
|
if !exists {
|
||||||
|
enable_auto_update = "false"
|
||||||
|
}
|
||||||
|
log.Debugf("set label 'coop-cloud.%s.autoupdate' to %s for %s", stackName, enable_auto_update, stackName)
|
||||||
|
labelKey := fmt.Sprintf("coop-cloud.%s.autoupdate", stackName)
|
||||||
|
service.Deploy.Labels[labelKey] = enable_auto_update
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// GetLabel reads docker labels in the format of "coop-cloud.${STACK_NAME}.${LABEL}" from the local compose files
|
// GetLabel reads docker labels in the format of "coop-cloud.${STACK_NAME}.${LABEL}" from the local compose files
|
||||||
func GetLabel(compose *composetypes.Config, stackName string, label string) string {
|
func GetLabel(compose *composetypes.Config, stackName string, label string) string {
|
||||||
for _, service := range compose.Services {
|
for _, service := range compose.Services {
|
||||||
if service.Name == "app" {
|
if service.Name == "app" {
|
||||||
labelKey := fmt.Sprintf("coop-cloud.%s.%s", stackName, label)
|
labelKey := fmt.Sprintf("coop-cloud.%s.%s", stackName, label)
|
||||||
log.Debug(i18n.G("get label '%s'", labelKey))
|
log.Debugf("get label '%s'", labelKey)
|
||||||
if labelValue, ok := service.Deploy.Labels[labelKey]; ok {
|
if labelValue, ok := service.Deploy.Labels[labelKey]; ok {
|
||||||
return labelValue
|
return labelValue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
log.Debug(i18n.G("no %s label found for %s", label, stackName))
|
log.Debugf("no %s label found for %s", label, stackName)
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTimeoutFromLabel reads the timeout value from docker label
|
// GetTimeoutFromLabel reads the timeout value from docker label "coop-cloud.${STACK_NAME}.TIMEOUT" and returns 50 as default value
|
||||||
// `coop-cloud.${STACK_NAME}.timeout=...` if present. A value is present if the
|
|
||||||
// operator uses a `TIMEOUT=...` in their app env.
|
|
||||||
func GetTimeoutFromLabel(compose *composetypes.Config, stackName string) (int, error) {
|
func GetTimeoutFromLabel(compose *composetypes.Config, stackName string) (int, error) {
|
||||||
var timeout int
|
timeout := 50 // Default Timeout
|
||||||
|
var err error = nil
|
||||||
if timeoutLabel := GetLabel(compose, stackName, "timeout"); timeoutLabel != "" {
|
if timeoutLabel := GetLabel(compose, stackName, "timeout"); timeoutLabel != "" {
|
||||||
log.Debug(i18n.G("timeout label: %s", timeoutLabel))
|
log.Debugf("timeout label: %s", timeoutLabel)
|
||||||
|
|
||||||
var err error
|
|
||||||
timeout, err = strconv.Atoi(timeoutLabel)
|
timeout, err = strconv.Atoi(timeoutLabel)
|
||||||
if err != nil {
|
|
||||||
return timeout, errors.New(i18n.G("unable to convert timeout label %s to int: %s", timeoutLabel, err))
|
|
||||||
}
|
}
|
||||||
}
|
return timeout, err
|
||||||
|
|
||||||
return timeout, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,62 +0,0 @@
|
|||||||
package app_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
|
||||||
testPkg "coopcloud.tech/abra/pkg/test"
|
|
||||||
stack "coopcloud.tech/abra/pkg/upstream/stack"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetTimeoutFromLabel(t *testing.T) {
|
|
||||||
testPkg.MkServerAppRecipe()
|
|
||||||
defer testPkg.RmServerAppRecipe()
|
|
||||||
|
|
||||||
tests := []struct {
|
|
||||||
configuredTimeout string
|
|
||||||
expectedTimeout int
|
|
||||||
}{
|
|
||||||
{"0", 0},
|
|
||||||
{"DOESNTEXIST", 0}, // NOTE(d1): test when missing from .env
|
|
||||||
{"80", 80},
|
|
||||||
{"120", 120},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, test := range tests {
|
|
||||||
app, err := appPkg.GetApp(testPkg.ExpectedAppFiles, testPkg.AppName)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
if test.configuredTimeout != "DOESNTEXIST" {
|
|
||||||
app.Env["TIMEOUT"] = test.configuredTimeout
|
|
||||||
}
|
|
||||||
|
|
||||||
composeFiles, err := app.Recipe.GetComposeFiles(app.Env)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
deployOpts := stack.Deploy{
|
|
||||||
Composefiles: composeFiles,
|
|
||||||
Namespace: app.StackName(),
|
|
||||||
Prune: false,
|
|
||||||
ResolveImage: stack.ResolveImageAlways,
|
|
||||||
Detach: false,
|
|
||||||
}
|
|
||||||
|
|
||||||
compose, err := appPkg.GetAppComposeConfig(app.Name, deployOpts, app.Env)
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout, err := appPkg.GetTimeoutFromLabel(compose, app.StackName())
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
assert.Equal(t, timeout, test.expectedTimeout)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,12 +1,11 @@
|
|||||||
package autocomplete
|
package autocomplete
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"sort"
|
"sort"
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/app"
|
"coopcloud.tech/abra/pkg/app"
|
||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
appPkg "coopcloud.tech/abra/pkg/app"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
"coopcloud.tech/abra/pkg/recipe"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
@ -15,7 +14,7 @@ import (
|
|||||||
func AppNameComplete() ([]string, cobra.ShellCompDirective) {
|
func AppNameComplete() ([]string, cobra.ShellCompDirective) {
|
||||||
appFiles, err := app.LoadAppFiles("")
|
appFiles, err := app.LoadAppFiles("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := i18n.G("autocomplete failed: %s", err)
|
err := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
return []string{err}, cobra.ShellCompDirectiveError
|
return []string{err}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +29,7 @@ func AppNameComplete() ([]string, cobra.ShellCompDirective) {
|
|||||||
func ServiceNameComplete(appName string) ([]string, cobra.ShellCompDirective) {
|
func ServiceNameComplete(appName string) ([]string, cobra.ShellCompDirective) {
|
||||||
serviceNames, err := app.GetAppServiceNames(appName)
|
serviceNames, err := app.GetAppServiceNames(appName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := i18n.G("autocomplete failed: %s", err)
|
err := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
return []string{err}, cobra.ShellCompDirectiveError
|
return []string{err}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -39,15 +38,9 @@ func ServiceNameComplete(appName string) ([]string, cobra.ShellCompDirective) {
|
|||||||
|
|
||||||
// RecipeNameComplete completes recipe names.
|
// RecipeNameComplete completes recipe names.
|
||||||
func RecipeNameComplete() ([]string, cobra.ShellCompDirective) {
|
func RecipeNameComplete() ([]string, cobra.ShellCompDirective) {
|
||||||
catl, err := recipe.ReadRecipeCatalogue(true)
|
catl, err := recipe.ReadRecipeCatalogue(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := i18n.G("autocomplete failed: %s", err)
|
err := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
return []string{err}, cobra.ShellCompDirectiveError
|
|
||||||
}
|
|
||||||
|
|
||||||
localRecipes, err := recipe.GetRecipesLocal()
|
|
||||||
if err != nil && !strings.Contains(err.Error(), "empty") {
|
|
||||||
err := i18n.G("autocomplete failed: %s", err)
|
|
||||||
return []string{err}, cobra.ShellCompDirectiveError
|
return []string{err}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,10 +49,6 @@ func RecipeNameComplete() ([]string, cobra.ShellCompDirective) {
|
|||||||
recipeNames = append(recipeNames, name)
|
recipeNames = append(recipeNames, name)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, recipeLocal := range localRecipes {
|
|
||||||
recipeNames = append(recipeNames, recipeLocal)
|
|
||||||
}
|
|
||||||
|
|
||||||
return recipeNames, cobra.ShellCompDirectiveDefault
|
return recipeNames, cobra.ShellCompDirectiveDefault
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,7 +56,7 @@ func RecipeNameComplete() ([]string, cobra.ShellCompDirective) {
|
|||||||
func RecipeVersionComplete(recipeName string) ([]string, cobra.ShellCompDirective) {
|
func RecipeVersionComplete(recipeName string) ([]string, cobra.ShellCompDirective) {
|
||||||
catl, err := recipe.ReadRecipeCatalogue(true)
|
catl, err := recipe.ReadRecipeCatalogue(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := i18n.G("autocomplete failed: %s", err)
|
err := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
return []string{err}, cobra.ShellCompDirectiveError
|
return []string{err}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -85,7 +74,7 @@ func RecipeVersionComplete(recipeName string) ([]string, cobra.ShellCompDirectiv
|
|||||||
func ServerNameComplete() ([]string, cobra.ShellCompDirective) {
|
func ServerNameComplete() ([]string, cobra.ShellCompDirective) {
|
||||||
files, err := app.LoadAppFiles("")
|
files, err := app.LoadAppFiles("")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := i18n.G("autocomplete failed: %s", err)
|
err := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
return []string{err}, cobra.ShellCompDirectiveError
|
return []string{err}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,13 +90,13 @@ func ServerNameComplete() ([]string, cobra.ShellCompDirective) {
|
|||||||
func CommandNameComplete(appName string) ([]string, cobra.ShellCompDirective) {
|
func CommandNameComplete(appName string) ([]string, cobra.ShellCompDirective) {
|
||||||
app, err := app.Get(appName)
|
app, err := app.Get(appName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := i18n.G("autocomplete failed: %s", err)
|
err := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
return []string{err}, cobra.ShellCompDirectiveError
|
return []string{err}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
|
||||||
cmdNames, err := appPkg.ReadAbraShCmdNames(app.Recipe.AbraShPath)
|
cmdNames, err := appPkg.ReadAbraShCmdNames(app.Recipe.AbraShPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := i18n.G("autocomplete failed: %s", err)
|
err := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
return []string{err}, cobra.ShellCompDirectiveError
|
return []string{err}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -122,7 +111,7 @@ func SecretComplete(recipeName string) ([]string, cobra.ShellCompDirective) {
|
|||||||
|
|
||||||
config, err := r.GetComposeConfig(nil)
|
config, err := r.GetComposeConfig(nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err := i18n.G("autocomplete failed: %s", err)
|
err := fmt.Sprintf("autocomplete failed: %s", err)
|
||||||
return []string{err}, cobra.ShellCompDirectiveError
|
return []string{err}, cobra.ShellCompDirectiveError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package catalogue
|
package catalogue
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
@ -9,7 +8,6 @@ import (
|
|||||||
|
|
||||||
"coopcloud.tech/abra/pkg/config"
|
"coopcloud.tech/abra/pkg/config"
|
||||||
gitPkg "coopcloud.tech/abra/pkg/git"
|
gitPkg "coopcloud.tech/abra/pkg/git"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
)
|
)
|
||||||
@ -18,7 +16,7 @@ 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.Debug(i18n.G("catalogue is missing, retrieving now"))
|
log.Debugf("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 {
|
||||||
@ -37,7 +35,8 @@ func EnsureIsClean() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !isClean {
|
if !isClean {
|
||||||
return errors.New(i18n.G("%s has locally unstaged changes? please commit/remove your changes before proceeding", config.CATALOGUE_DIR))
|
msg := "%s has locally unstaged changes? please commit/remove your changes before proceeding"
|
||||||
|
return fmt.Errorf(msg, config.CATALOGUE_DIR)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -56,7 +55,8 @@ func EnsureUpToDate() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(remotes) == 0 {
|
if len(remotes) == 0 {
|
||||||
log.Debug(i18n.G("cannot ensure %s is up-to-date, no git remotes configured", config.CATALOGUE_DIR))
|
msg := "cannot ensure %s is up-to-date, no git remotes configured"
|
||||||
|
log.Debugf(msg, config.CATALOGUE_DIR)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ func EnsureUpToDate() error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("fetched latest git changes for %s", config.CATALOGUE_DIR))
|
log.Debugf("fetched latest git changes for %s", config.CATALOGUE_DIR)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,15 +4,12 @@ package client
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
|
||||||
"strings"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/config"
|
|
||||||
contextPkg "coopcloud.tech/abra/pkg/context"
|
contextPkg "coopcloud.tech/abra/pkg/context"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
sshPkg "coopcloud.tech/abra/pkg/ssh"
|
sshPkg "coopcloud.tech/abra/pkg/ssh"
|
||||||
commandconnPkg "coopcloud.tech/abra/pkg/upstream/commandconn"
|
commandconnPkg "coopcloud.tech/abra/pkg/upstream/commandconn"
|
||||||
@ -41,29 +38,18 @@ func WithTimeout(timeout int) Opt {
|
|||||||
func New(serverName string, opts ...Opt) (*client.Client, error) {
|
func New(serverName string, opts ...Opt) (*client.Client, error) {
|
||||||
var clientOpts []client.Opt
|
var clientOpts []client.Opt
|
||||||
|
|
||||||
ctx, err := GetContext(serverName)
|
if serverName != "default" {
|
||||||
|
context, err := GetContext(serverName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
serverDir := path.Join(config.SERVERS_DIR, serverName)
|
return nil, fmt.Errorf("unknown server, run \"abra server add %s\"?", serverName)
|
||||||
if _, err := os.Stat(serverDir); err == nil {
|
|
||||||
return nil, errors.New(i18n.G("server missing context, run \"abra server add %s\"?", serverName))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil, errors.New(i18n.G("unknown server, run \"abra server add %s\"?", serverName))
|
ctxEndpoint, err := contextPkg.GetContextEndpoint(context)
|
||||||
}
|
|
||||||
|
|
||||||
ctxEndpoint, err := contextPkg.GetContextEndpoint(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
var isUnix bool
|
|
||||||
if strings.Contains(ctxEndpoint, "unix://") {
|
|
||||||
isUnix = true
|
|
||||||
}
|
|
||||||
|
|
||||||
if serverName != "default" && !isUnix {
|
|
||||||
conf := &Conf{}
|
conf := &Conf{}
|
||||||
|
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(conf)
|
opt(conf)
|
||||||
}
|
}
|
||||||
@ -99,7 +85,7 @@ func New(serverName string, opts ...Opt) (*client.Client, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("created client for %s", serverName))
|
log.Debugf("created client for %s", serverName)
|
||||||
|
|
||||||
info, err := cl.Info(context.Background())
|
info, err := cl.Info(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -107,11 +93,11 @@ func New(serverName string, opts ...Opt) (*client.Client, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if info.Swarm.LocalNodeState == "inactive" {
|
if info.Swarm.LocalNodeState == "inactive" {
|
||||||
if serverName != "default" && !isUnix {
|
if serverName != "default" {
|
||||||
return cl, errors.New(i18n.G("swarm mode not enabled on %s?", serverName))
|
return cl, fmt.Errorf("swarm mode not enabled on %s?", serverName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return cl, errors.New(i18n.G("swarm mode not enabled on local server?"))
|
return cl, errors.New("swarm mode not enabled on local server?")
|
||||||
}
|
}
|
||||||
|
|
||||||
return cl, nil
|
return cl, nil
|
||||||
|
|||||||
@ -1,39 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"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 errors.New(i18n.G("conf %s: %s", confName, err))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/context"
|
"coopcloud.tech/abra/pkg/context"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
commandconnPkg "coopcloud.tech/abra/pkg/upstream/commandconn"
|
commandconnPkg "coopcloud.tech/abra/pkg/upstream/commandconn"
|
||||||
dConfig "github.com/docker/cli/cli/config"
|
dConfig "github.com/docker/cli/cli/config"
|
||||||
@ -23,7 +22,7 @@ func CreateContext(contextName string) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("created the %s context", contextName))
|
log.Debugf("created the %s context", contextName)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -63,7 +62,7 @@ func createContext(name string, host string) error {
|
|||||||
|
|
||||||
func DeleteContext(name string) error {
|
func DeleteContext(name string) error {
|
||||||
if name == "default" {
|
if name == "default" {
|
||||||
return errors.New(i18n.G("context 'default' cannot be removed"))
|
return errors.New("context 'default' cannot be removed")
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := GetContext(name); err != nil {
|
if _, err := GetContext(name); err != nil {
|
||||||
|
|||||||
@ -2,10 +2,8 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"github.com/containers/image/docker"
|
"github.com/containers/image/docker"
|
||||||
"github.com/containers/image/types"
|
"github.com/containers/image/types"
|
||||||
"github.com/distribution/reference"
|
"github.com/distribution/reference"
|
||||||
@ -17,7 +15,7 @@ func GetRegistryTags(img reference.Named) ([]string, error) {
|
|||||||
|
|
||||||
ref, err := docker.ParseReference(fmt.Sprintf("//%s", img))
|
ref, err := docker.ParseReference(fmt.Sprintf("//%s", img))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return tags, errors.New(i18n.G("failed to parse image %s, saw: %s", img, err.Error()))
|
return tags, fmt.Errorf("failed to parse image %s, saw: %s", img, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx := context.Background()
|
ctx := context.Background()
|
||||||
|
|||||||
@ -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)}
|
||||||
|
|
||||||
@ -17,11 +17,3 @@ func StoreSecret(cl *client.Client, secretName, secretValue string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSecretNames(secrets []swarm.Secret) []string {
|
|
||||||
var secretNames []string
|
|
||||||
for _, secret := range secrets {
|
|
||||||
secretNames = append(secretNames, secret.Spec.Name)
|
|
||||||
}
|
|
||||||
return secretNames
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,58 +0,0 @@
|
|||||||
package client
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/docker/docker/api/types/swarm"
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetSecretNames(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
secrets []swarm.Secret
|
|
||||||
expected []string
|
|
||||||
description string
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "empty secrets list",
|
|
||||||
secrets: []swarm.Secret{},
|
|
||||||
expected: nil,
|
|
||||||
description: "should return nil for empty input",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "single secret",
|
|
||||||
secrets: []swarm.Secret{
|
|
||||||
{Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "database_password"}}},
|
|
||||||
},
|
|
||||||
expected: []string{"database_password"},
|
|
||||||
description: "should return single secret name",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "multiple secrets",
|
|
||||||
secrets: []swarm.Secret{
|
|
||||||
{Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "db_password"}}},
|
|
||||||
{Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "api_key"}}},
|
|
||||||
{Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "ssl_cert"}}},
|
|
||||||
},
|
|
||||||
expected: []string{"db_password", "api_key", "ssl_cert"},
|
|
||||||
description: "should return all secret names in order",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "secrets with empty names",
|
|
||||||
secrets: []swarm.Secret{
|
|
||||||
{Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: ""}}},
|
|
||||||
{Spec: swarm.SecretSpec{Annotations: swarm.Annotations{Name: "valid_name"}}},
|
|
||||||
},
|
|
||||||
expected: []string{"", "valid_name"},
|
|
||||||
description: "should include empty names if present",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
result := GetSecretNames(tt.secrets)
|
|
||||||
assert.Equal(t, tt.expected, result, tt.description)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -2,10 +2,9 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/docker/docker/api/types/filters"
|
"github.com/docker/docker/api/types/filters"
|
||||||
"github.com/docker/docker/api/types/volume"
|
"github.com/docker/docker/api/types/volume"
|
||||||
@ -38,7 +37,7 @@ func RemoveVolumes(cl *client.Client, ctx context.Context, volumeNames []string,
|
|||||||
return cl.VolumeRemove(context.Background(), volName, force)
|
return cl.VolumeRemove(context.Background(), volName, force)
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(i18n.G("volume %s: %s", volName, err))
|
return fmt.Errorf("volume %s: %s", volName, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
@ -55,9 +54,9 @@ func retryFunc(retries int, fn func() error) error {
|
|||||||
}
|
}
|
||||||
if i+1 < retries {
|
if i+1 < retries {
|
||||||
sleep := time.Duration(i+1) * time.Duration(i+1)
|
sleep := time.Duration(i+1) * time.Duration(i+1)
|
||||||
log.Info(i18n.G("%s: waiting %d seconds before next retry", err, sleep))
|
log.Infof("%s: waiting %d seconds before next retry", err, sleep)
|
||||||
time.Sleep(sleep * time.Second)
|
time.Sleep(sleep * time.Second)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errors.New(i18n.G("%d retries failed", retries))
|
return fmt.Errorf("%d retries failed", retries)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,6 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
)
|
)
|
||||||
@ -17,13 +16,13 @@ func LoadAbraConfig() Abra {
|
|||||||
wd, _ := os.Getwd()
|
wd, _ := os.Getwd()
|
||||||
configFile := findAbraConfig(wd)
|
configFile := findAbraConfig(wd)
|
||||||
if configFile == "" {
|
if configFile == "" {
|
||||||
log.Debug(i18n.G("no config file found"))
|
log.Debugf("no config file found")
|
||||||
return Abra{}
|
return Abra{}
|
||||||
}
|
}
|
||||||
data, err := os.ReadFile(configFile)
|
data, err := os.ReadFile(configFile)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Do nothing, when an error occurs
|
// Do nothing, when an error occurs
|
||||||
log.Debug(i18n.G("error reading config file: %s", err))
|
log.Debugf("error reading config file: %s", err)
|
||||||
return Abra{}
|
return Abra{}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,10 +30,10 @@ func LoadAbraConfig() Abra {
|
|||||||
err = yaml.Unmarshal(data, &config)
|
err = yaml.Unmarshal(data, &config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Do nothing, when an error occurs
|
// Do nothing, when an error occurs
|
||||||
log.Debug(i18n.G("error loading config file: %s", err))
|
log.Debugf("error loading config file: %s", err)
|
||||||
return Abra{}
|
return Abra{}
|
||||||
}
|
}
|
||||||
log.Debug(i18n.G("config file loaded from: %s", configFile))
|
log.Debugf("config file loaded from: %s", configFile)
|
||||||
config.configPath = filepath.Dir(configFile)
|
config.configPath = filepath.Dir(configFile)
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
@ -74,24 +73,26 @@ type Abra struct {
|
|||||||
// 3. use $HOME/.abra when above two options failed
|
// 3. use $HOME/.abra when above two options failed
|
||||||
func (a Abra) GetAbraDir() string {
|
func (a Abra) GetAbraDir() string {
|
||||||
if dir, exists := os.LookupEnv("ABRA_DIR"); exists && dir != "" {
|
if dir, exists := os.LookupEnv("ABRA_DIR"); exists && dir != "" {
|
||||||
log.Debug(i18n.G("read abra dir from $ABRA_DIR"))
|
log.Debug("read abra dir from $ABRA_DIR")
|
||||||
return dir
|
return dir
|
||||||
}
|
}
|
||||||
if a.AbraDir != "" {
|
if a.AbraDir != "" {
|
||||||
log.Debug(i18n.G("read abra dir from config file"))
|
log.Debug("read abra dir from config file")
|
||||||
if path.IsAbs(a.AbraDir) {
|
if path.IsAbs(a.AbraDir) {
|
||||||
return a.AbraDir
|
return a.AbraDir
|
||||||
}
|
}
|
||||||
// Make the path absolute
|
// Make the path absolute
|
||||||
return path.Join(a.configPath, a.AbraDir)
|
return path.Join(a.configPath, a.AbraDir)
|
||||||
}
|
}
|
||||||
log.Debug(i18n.G("using default abra dir"))
|
log.Debug("using default abra dir")
|
||||||
return os.ExpandEnv("$HOME/.abra")
|
return os.ExpandEnv("$HOME/.abra")
|
||||||
}
|
}
|
||||||
|
|
||||||
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) GetLogsDir() string { return path.Join(a.GetAbraDir(), "logs") }
|
||||||
|
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) GetCatalogueDir() string { return path.Join(a.GetAbraDir(), "catalogue") }
|
func (a Abra) GetCatalogueDir() string { return path.Join(a.GetAbraDir(), "catalogue") }
|
||||||
|
|
||||||
var config = LoadAbraConfig()
|
var config = LoadAbraConfig()
|
||||||
@ -101,6 +102,8 @@ var (
|
|||||||
SERVERS_DIR = config.GetServersDir()
|
SERVERS_DIR = config.GetServersDir()
|
||||||
RECIPES_DIR = config.GetRecipesDir()
|
RECIPES_DIR = config.GetRecipesDir()
|
||||||
LOGS_DIR = config.GetLogsDir()
|
LOGS_DIR = config.GetLogsDir()
|
||||||
|
VENDOR_DIR = config.GetVendorDir()
|
||||||
|
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"
|
||||||
@ -116,7 +119,8 @@ var (
|
|||||||
|
|
||||||
DIRTY_DEFAULT = "+U"
|
DIRTY_DEFAULT = "+U"
|
||||||
|
|
||||||
MISSING_DEFAULT = "-"
|
NO_DOMAIN_DEFAULT = "N/A"
|
||||||
|
NO_VERSION_DEFAULT = "N/A"
|
||||||
|
|
||||||
UNKNOWN_DEFAULT = "unknown"
|
UNKNOWN_DEFAULT = "unknown"
|
||||||
)
|
)
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
package config
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
"io/fs"
|
"io/fs"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
@ -9,7 +9,6 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -34,7 +33,7 @@ func GetServers() ([]string, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("retrieved %v servers: %s", len(filtered), filtered))
|
log.Debugf("retrieved %v servers: %s", len(filtered), filtered)
|
||||||
|
|
||||||
return filtered, nil
|
return filtered, nil
|
||||||
}
|
}
|
||||||
@ -47,7 +46,7 @@ func ReadServerNames() ([]string, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("read %s from %s", strings.Join(serverNames, ","), SERVERS_DIR))
|
log.Debugf("read %s from %s", strings.Join(serverNames, ","), SERVERS_DIR)
|
||||||
|
|
||||||
return serverNames, nil
|
return serverNames, nil
|
||||||
}
|
}
|
||||||
@ -71,7 +70,7 @@ func GetAllFilesInDirectory(directory string) ([]fs.FileInfo, error) {
|
|||||||
|
|
||||||
realPath, err := filepath.EvalSymlinks(filePath)
|
realPath, err := filepath.EvalSymlinks(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(i18n.G("broken symlink in your abra config folders: %s", filePath))
|
log.Warnf("broken symlink in your abra config folders: %s", filePath)
|
||||||
} else {
|
} else {
|
||||||
realFile, err := os.Stat(realPath)
|
realFile, err := os.Stat(realPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -95,7 +94,7 @@ func GetAllFoldersInDirectory(directory string) ([]string, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if len(files) == 0 {
|
if len(files) == 0 {
|
||||||
return nil, errors.New(i18n.G("directory is empty: %s", directory))
|
return nil, fmt.Errorf("directory is empty: %s", directory)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, file := range files {
|
for _, file := range files {
|
||||||
@ -104,7 +103,7 @@ func GetAllFoldersInDirectory(directory string) ([]string, error) {
|
|||||||
filePath := path.Join(directory, file.Name())
|
filePath := path.Join(directory, file.Name())
|
||||||
realDir, err := filepath.EvalSymlinks(filePath)
|
realDir, err := filepath.EvalSymlinks(filePath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Warn(i18n.G("broken symlink in your abra config folders: %s", filePath))
|
log.Warnf("broken symlink in your abra config folders: %s", filePath)
|
||||||
} else if stat, err := os.Stat(realDir); err == nil && stat.IsDir() {
|
} else if stat, err := os.Stat(realDir); err == nil && stat.IsDir() {
|
||||||
// path is a directory
|
// path is a directory
|
||||||
folders = append(folders, file.Name())
|
folders = append(folders, file.Name())
|
||||||
|
|||||||
@ -2,12 +2,10 @@ package container
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
"coopcloud.tech/abra/pkg/formatter"
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/AlecAivazis/survey/v2"
|
"github.com/AlecAivazis/survey/v2"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
@ -28,7 +26,7 @@ func GetContainer(c context.Context, cl *client.Client, filters filters.Args, no
|
|||||||
|
|
||||||
if len(containers) == 0 {
|
if len(containers) == 0 {
|
||||||
filter := filters.Get("name")[0]
|
filter := filters.Get("name")[0]
|
||||||
return types.Container{}, errors.New(i18n.G("no containers matching the %v filter found?", filter))
|
return types.Container{}, fmt.Errorf("no containers matching the %v filter found?", filter)
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(containers) > 1 {
|
if len(containers) > 1 {
|
||||||
@ -37,19 +35,19 @@ func GetContainer(c context.Context, cl *client.Client, filters filters.Args, no
|
|||||||
containerName := strings.Join(container.Names, " ")
|
containerName := strings.Join(container.Names, " ")
|
||||||
trimmed := strings.TrimPrefix(containerName, "/")
|
trimmed := strings.TrimPrefix(containerName, "/")
|
||||||
created := formatter.HumanDuration(container.Created)
|
created := formatter.HumanDuration(container.Created)
|
||||||
containersRaw = append(containersRaw, i18n.G("%s (created %v)", trimmed, created))
|
containersRaw = append(containersRaw, fmt.Sprintf("%s (created %v)", trimmed, created))
|
||||||
}
|
}
|
||||||
|
|
||||||
if noInput {
|
if noInput {
|
||||||
err := errors.New(i18n.G("expected 1 container but found %v: %s", len(containers), strings.Join(containersRaw, " ")))
|
err := fmt.Errorf("expected 1 container but found %v: %s", len(containers), strings.Join(containersRaw, " "))
|
||||||
return types.Container{}, err
|
return types.Container{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Warnf(i18n.G("ambiguous container list received, prompting for input"))
|
log.Warnf("ambiguous container list received, prompting for input")
|
||||||
|
|
||||||
var response string
|
var response string
|
||||||
prompt := &survey.Select{
|
prompt := &survey.Select{
|
||||||
Message: i18n.G("which container are you looking for?"),
|
Message: "which container are you looking for?",
|
||||||
Options: containersRaw,
|
Options: containersRaw,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -66,7 +64,7 @@ func GetContainer(c context.Context, cl *client.Client, filters filters.Args, no
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Fatal(i18n.G("failed to match chosen container"))
|
log.Fatal("failed to match chosen container")
|
||||||
}
|
}
|
||||||
|
|
||||||
return containers[0], nil
|
return containers[0], nil
|
||||||
@ -81,6 +79,5 @@ func GetContainerFromStackAndService(cl *client.Client, stack, service string) (
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return types.Container{}, err
|
return types.Container{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return container, nil
|
return container, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package context
|
|||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"github.com/docker/cli/cli/command"
|
"github.com/docker/cli/cli/command"
|
||||||
dConfig "github.com/docker/cli/cli/config"
|
dConfig "github.com/docker/cli/cli/config"
|
||||||
"github.com/docker/cli/cli/context"
|
"github.com/docker/cli/cli/context"
|
||||||
@ -31,7 +30,7 @@ func NewDefaultDockerContextStore() *command.ContextStoreWithDefault {
|
|||||||
func GetContextEndpoint(ctx contextStore.Metadata) (string, error) {
|
func GetContextEndpoint(ctx contextStore.Metadata) (string, error) {
|
||||||
endpointmeta, ok := ctx.Endpoints["docker"].(context.EndpointMetaBase)
|
endpointmeta, ok := ctx.Endpoints["docker"].(context.EndpointMetaBase)
|
||||||
if !ok {
|
if !ok {
|
||||||
err := errors.New(i18n.G("context lacks Docker endpoint"))
|
err := errors.New("context lacks Docker endpoint")
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
return endpointmeta.Host, nil
|
return endpointmeta.Host, nil
|
||||||
|
|||||||
@ -1,316 +0,0 @@
|
|||||||
package deploy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"errors"
|
|
||||||
"sort"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
appPkg "coopcloud.tech/abra/pkg/app"
|
|
||||||
"coopcloud.tech/abra/pkg/envfile"
|
|
||||||
"coopcloud.tech/abra/pkg/formatter"
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
|
||||||
"coopcloud.tech/abra/pkg/recipe"
|
|
||||||
"coopcloud.tech/abra/pkg/secret"
|
|
||||||
|
|
||||||
"github.com/distribution/reference"
|
|
||||||
composetypes "github.com/docker/cli/cli/compose/types"
|
|
||||||
"github.com/docker/docker/api/types/swarm"
|
|
||||||
dockerClient "github.com/docker/docker/client"
|
|
||||||
)
|
|
||||||
|
|
||||||
// MergeAbraShEnv merges abra.sh env vars into the app env vars.
|
|
||||||
func MergeAbraShEnv(recipe recipe.Recipe, env envfile.AppEnv) error {
|
|
||||||
abraShEnv, err := envfile.ReadAbraShEnvVars(recipe.AbraShPath)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
for k, v := range abraShEnv {
|
|
||||||
log.Debugf(i18n.G("read v:%s k: %s", v, k))
|
|
||||||
env[k] = v
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetEntityNameAndVersion parses a full name like `app_example_com_someconf_v1` to extract name and version, ("someconf", "v1")
|
|
||||||
func GetEntityNameAndVersion(fullName string, stackName string) (string, string, error) {
|
|
||||||
name := strings.TrimPrefix(fullName, stackName+"_")
|
|
||||||
if lastUnderscore := strings.LastIndex(name, "_"); lastUnderscore != -1 {
|
|
||||||
return name[0:lastUnderscore], name[lastUnderscore+1:], nil
|
|
||||||
}
|
|
||||||
return "", "", errors.New(i18n.G("can't parse version from '%s'", fullName))
|
|
||||||
}
|
|
||||||
|
|
||||||
func GetSecretsForStack(cl *dockerClient.Client, app appPkg.App) (map[string]string, error) {
|
|
||||||
filters, err := app.Filters(false, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// List all services in the stack
|
|
||||||
// NOTE: we could do cl.SecretList, but we want to know which secrets are actually attached
|
|
||||||
services, err := cl.ServiceList(context.Background(), swarm.ServiceListOptions{
|
|
||||||
Filters: filters,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
secrets := make(map[string]string)
|
|
||||||
|
|
||||||
for _, service := range services {
|
|
||||||
if service.Spec.TaskTemplate.ContainerSpec.Secrets != nil {
|
|
||||||
for _, secretRef := range service.Spec.TaskTemplate.ContainerSpec.Secrets {
|
|
||||||
secretName := secretRef.SecretName
|
|
||||||
if secretName == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
secretBaseName, secretVersion, err := GetEntityNameAndVersion(secretName, app.StackName())
|
|
||||||
if err != nil {
|
|
||||||
log.Warn(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
existingSecretVersion, exists := secrets[secretBaseName]
|
|
||||||
if !exists {
|
|
||||||
// First time seeing this, add to map
|
|
||||||
secrets[secretBaseName] = secretVersion
|
|
||||||
} else {
|
|
||||||
// Just make sure the versions are the same..
|
|
||||||
if existingSecretVersion != secretVersion {
|
|
||||||
log.Warnf(i18n.G("different versions for secret '%s', '%s' and %s'", secretBaseName, existingSecretVersion, secretVersion))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return secrets, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetConfigsForStack retrieves all Docker configs attached to services in a given stack.
|
|
||||||
func GetConfigsForStack(cl *dockerClient.Client, app appPkg.App) (map[string]string, error) {
|
|
||||||
filters, err := app.Filters(false, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// List all services in the stack
|
|
||||||
services, err := cl.ServiceList(context.Background(), swarm.ServiceListOptions{
|
|
||||||
Filters: filters,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect unique config names with versions
|
|
||||||
configs := make(map[string]string)
|
|
||||||
for _, service := range services {
|
|
||||||
if service.Spec.TaskTemplate.ContainerSpec != nil {
|
|
||||||
for _, configRef := range service.Spec.TaskTemplate.ContainerSpec.Configs {
|
|
||||||
configName := configRef.ConfigName
|
|
||||||
if configName == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
configBaseName, configVersion, err := GetEntityNameAndVersion(configName, app.StackName())
|
|
||||||
if err != nil {
|
|
||||||
log.Warn(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
existingConfigVersion, ok := configs[configBaseName]
|
|
||||||
if !ok {
|
|
||||||
// First time seeing this, add to map
|
|
||||||
configs[configBaseName] = configVersion
|
|
||||||
} else {
|
|
||||||
// Just make sure the versions are the same..
|
|
||||||
if existingConfigVersion != configVersion {
|
|
||||||
log.Warnf(i18n.G("different versions for config '%s', '%s' and %s'", configBaseName, existingConfigVersion, configVersion))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return configs, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetImagesForStack retrieves all Docker images for services in a given stack.
|
|
||||||
func GetImagesForStack(cl *dockerClient.Client, app appPkg.App) (map[string]string, error) {
|
|
||||||
filters, err := app.Filters(false, false)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// List all services in the stack
|
|
||||||
services, err := cl.ServiceList(context.Background(), swarm.ServiceListOptions{
|
|
||||||
Filters: filters,
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Collect unique image names with versions
|
|
||||||
images := make(map[string]string)
|
|
||||||
for _, service := range services {
|
|
||||||
if service.Spec.TaskTemplate.ContainerSpec != nil {
|
|
||||||
imageName := service.Spec.TaskTemplate.ContainerSpec.Image
|
|
||||||
|
|
||||||
imageParsed, err := reference.ParseNormalizedNamed(imageName)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
imageBaseName := reference.Path(imageParsed)
|
|
||||||
imageTag := imageParsed.(reference.NamedTagged).Tag()
|
|
||||||
|
|
||||||
existingImageVersion, ok := images[imageBaseName]
|
|
||||||
if !ok {
|
|
||||||
// First time seeing this, add to map
|
|
||||||
images[imageBaseName] = imageTag
|
|
||||||
} else {
|
|
||||||
// Just make sure the versions are the same..
|
|
||||||
if existingImageVersion != imageTag {
|
|
||||||
log.Warnf(i18n.G("different versions for image '%s', '%s' and %s'", imageBaseName, existingImageVersion, imageTag))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return images, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GatherSecretsForDeploy(cl *dockerClient.Client, app appPkg.App, showUnchanged bool) ([]string, error) {
|
|
||||||
// Get current secrets from existing deployment
|
|
||||||
currentSecrets, err := GetSecretsForStack(cl, app)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf(i18n.G("current secrets: %v", currentSecrets))
|
|
||||||
|
|
||||||
newSecrets, err := secret.PollSecretsStatus(cl, app)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf(i18n.G("new secrets: %v", newSecrets))
|
|
||||||
|
|
||||||
// Sort secrets to ensure reproducible output
|
|
||||||
sort.Slice(newSecrets, func(i, j int) bool {
|
|
||||||
return newSecrets[i].LocalName < newSecrets[j].LocalName
|
|
||||||
})
|
|
||||||
|
|
||||||
var secretInfo []string
|
|
||||||
|
|
||||||
for _, newSecret := range newSecrets {
|
|
||||||
if currentVersion, exists := currentSecrets[newSecret.LocalName]; exists {
|
|
||||||
if currentVersion == newSecret.Version {
|
|
||||||
if showUnchanged {
|
|
||||||
secretInfo = append(secretInfo, i18n.G("%s: %s (unchanged)", newSecret.LocalName, newSecret.Version))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
secretInfo = append(secretInfo, i18n.G("%s: %s → %s", newSecret.LocalName, currentVersion, newSecret.Version))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
secretInfo = append(secretInfo, i18n.G("%s: %s (new)", newSecret.LocalName, newSecret.Version))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return secretInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GatherConfigsForDeploy(cl *dockerClient.Client, app appPkg.App, compose *composetypes.Config, abraShEnv map[string]string, showUnchanged bool) ([]string, error) {
|
|
||||||
// Get current configs from existing deployment
|
|
||||||
currentConfigs, err := GetConfigsForStack(cl, app)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf(i18n.G("deployed config names: %v", currentConfigs))
|
|
||||||
|
|
||||||
// Get new configs from the compose specification
|
|
||||||
newConfigs := compose.Configs
|
|
||||||
|
|
||||||
var configInfo []string
|
|
||||||
for configName := range newConfigs {
|
|
||||||
log.Debugf(i18n.G("searching abra.sh for version for %s", configName))
|
|
||||||
versionKey := strings.ToUpper(configName) + "_VERSION"
|
|
||||||
newVersion, exists := abraShEnv[versionKey]
|
|
||||||
if !exists {
|
|
||||||
log.Warnf(i18n.G("no version found for config %s", configName))
|
|
||||||
configInfo = append(configInfo, i18n.G("%s: ? (missing version)", configName))
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
if currentVersion, exists := currentConfigs[configName]; exists {
|
|
||||||
if currentVersion == newVersion {
|
|
||||||
if showUnchanged {
|
|
||||||
configInfo = append(configInfo, i18n.G("%s: %s (unchanged)", configName, newVersion))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
configInfo = append(configInfo, i18n.G("%s: %s → %s", configName, currentVersion, newVersion))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
configInfo = append(configInfo, i18n.G("%s: %s (new)", configName, newVersion))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return configInfo, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func GatherImagesForDeploy(cl *dockerClient.Client, app appPkg.App, compose *composetypes.Config, showUnchanged bool) ([]string, error) {
|
|
||||||
// Get current images from existing deployment
|
|
||||||
currentImages, err := GetImagesForStack(cl, app)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf(i18n.G("deployed images: %v", currentImages))
|
|
||||||
|
|
||||||
// Proposed new images from the compose files
|
|
||||||
newImages := make(map[string]string)
|
|
||||||
|
|
||||||
for _, service := range compose.Services {
|
|
||||||
imageParsed, err := reference.ParseNormalizedNamed(service.Image)
|
|
||||||
if err != nil {
|
|
||||||
log.Warn(err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
imageBaseName := reference.Path(imageParsed)
|
|
||||||
imageTag := imageParsed.(reference.NamedTagged).Tag()
|
|
||||||
|
|
||||||
existingImageVersion, ok := newImages[imageBaseName]
|
|
||||||
if !ok {
|
|
||||||
// First time seeing this, add to map
|
|
||||||
newImages[imageBaseName] = imageTag
|
|
||||||
} else {
|
|
||||||
// Just make sure the versions are the same..
|
|
||||||
if existingImageVersion != imageTag {
|
|
||||||
log.Warnf(i18n.G("different versions for image '%s', '%s' and %s'", imageBaseName, existingImageVersion, imageTag))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Debugf(i18n.G("proposed images: %v", newImages))
|
|
||||||
|
|
||||||
var imageInfo []string
|
|
||||||
for newImageName, newImageVersion := range newImages {
|
|
||||||
if currentVersion, exists := currentImages[newImageName]; exists {
|
|
||||||
if currentVersion == newImageVersion {
|
|
||||||
if showUnchanged {
|
|
||||||
imageInfo = append(imageInfo, i18n.G("%s: %s (unchanged)", formatter.StripTagMeta(newImageName), newImageVersion))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
imageInfo = append(imageInfo, i18n.G("%s: %s → %s", formatter.StripTagMeta(newImageName), currentVersion, newImageVersion))
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
imageInfo = append(imageInfo, i18n.G("%s: %s (new)", formatter.StripTagMeta(newImageName), newImageVersion))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return imageInfo, nil
|
|
||||||
}
|
|
||||||
@ -1,89 +0,0 @@
|
|||||||
package deploy
|
|
||||||
|
|
||||||
import (
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestGetEntityNameAndVersion(t *testing.T) {
|
|
||||||
tests := []struct {
|
|
||||||
name string
|
|
||||||
fullName string
|
|
||||||
stackName string
|
|
||||||
expected string
|
|
||||||
expectedVer string
|
|
||||||
expectError bool
|
|
||||||
}{
|
|
||||||
{
|
|
||||||
name: "valid config with version",
|
|
||||||
fullName: "myapp_database_v2",
|
|
||||||
stackName: "myapp",
|
|
||||||
expected: "database",
|
|
||||||
expectedVer: "v2",
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "valid config with numeric version",
|
|
||||||
fullName: "myapp_redis_1",
|
|
||||||
stackName: "myapp",
|
|
||||||
expected: "redis",
|
|
||||||
expectedVer: "1",
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "config without underscore in name",
|
|
||||||
fullName: "myapp_db_v1",
|
|
||||||
stackName: "myapp",
|
|
||||||
expected: "db",
|
|
||||||
expectedVer: "v1",
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "config with multiple underscores",
|
|
||||||
fullName: "myapp_my_database_v3",
|
|
||||||
stackName: "myapp",
|
|
||||||
expected: "my_database",
|
|
||||||
expectedVer: "v3",
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "invalid config - no version",
|
|
||||||
fullName: "myapp_database",
|
|
||||||
stackName: "myapp",
|
|
||||||
expectError: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "empty config name",
|
|
||||||
fullName: "myapp__v1",
|
|
||||||
stackName: "myapp",
|
|
||||||
expected: "",
|
|
||||||
expectedVer: "v1",
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "wrong stack prefix",
|
|
||||||
fullName: "otherapp_database_v1",
|
|
||||||
stackName: "myapp",
|
|
||||||
expected: "otherapp_database",
|
|
||||||
expectedVer: "v1",
|
|
||||||
expectError: false,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, tt := range tests {
|
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
|
||||||
name, version, err := GetEntityNameAndVersion(tt.fullName, tt.stackName)
|
|
||||||
|
|
||||||
if tt.expectError {
|
|
||||||
assert.Error(t, err)
|
|
||||||
assert.Empty(t, name)
|
|
||||||
assert.Empty(t, version)
|
|
||||||
} else {
|
|
||||||
assert.NoError(t, err)
|
|
||||||
assert.Equal(t, tt.expected, name)
|
|
||||||
assert.Equal(t, tt.expectedVer, version)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,21 +1,19 @@
|
|||||||
package dns
|
package dns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// EnsureIPv4 ensures that an ipv4 address is set for a domain name
|
// EnsureIPv4 ensures that an ipv4 address is set for a domain name
|
||||||
func EnsureIPv4(domainName string) (string, error) {
|
func EnsureIPv4(domainName string) (string, error) {
|
||||||
ipv4, err := net.ResolveIPAddr("ip4", domainName)
|
ipv4, err := net.ResolveIPAddr("ip4", domainName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.New(i18n.G("%s: unable to resolve IPv4 address: %s", domainName, err))
|
return "", fmt.Errorf("%s: unable to resolve IPv4 address: %s", domainName, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if ipv4 == nil {
|
if ipv4 == nil {
|
||||||
return "", errors.New(i18n.G("%s: no IPv4 available", domainName))
|
return "", fmt.Errorf("%s: no IPv4 available", domainName)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ipv4.String(), nil
|
return ipv4.String(), nil
|
||||||
@ -35,7 +33,7 @@ func EnsureDomainsResolveSameIPv4(domainName, server string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if domainIPv4 == "" {
|
if domainIPv4 == "" {
|
||||||
return ipv4, errors.New(i18n.G("cannot resolve ipv4 for %s?", domainName))
|
return ipv4, fmt.Errorf("cannot resolve ipv4 for %s?", domainName)
|
||||||
}
|
}
|
||||||
|
|
||||||
serverIPv4, err := EnsureIPv4(server)
|
serverIPv4, err := EnsureIPv4(server)
|
||||||
@ -44,16 +42,12 @@ func EnsureDomainsResolveSameIPv4(domainName, server string) (string, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if serverIPv4 == "" {
|
if serverIPv4 == "" {
|
||||||
return ipv4, errors.New(i18n.G("cannot resolve ipv4 for %s?", server))
|
return ipv4, fmt.Errorf("cannot resolve ipv4 for %s?", server)
|
||||||
}
|
}
|
||||||
|
|
||||||
if domainIPv4 != serverIPv4 {
|
if domainIPv4 != serverIPv4 {
|
||||||
return ipv4, errors.New(
|
err := "app domain %s (%s) does not appear to resolve to app server %s (%s)?"
|
||||||
i18n.G(
|
return ipv4, fmt.Errorf(err, domainName, domainIPv4, server, serverIPv4)
|
||||||
"app domain %s (%s) does not appear to resolve to app server %s (%s)?",
|
|
||||||
domainName, domainIPv4, server, serverIPv4,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return ipv4, nil
|
return ipv4, nil
|
||||||
|
|||||||
@ -2,16 +2,20 @@ package envfile
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"errors"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"git.coopcloud.tech/toolshed/godotenv"
|
"git.coopcloud.tech/toolshed/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
|
||||||
|
|
||||||
@ -39,7 +43,7 @@ func ReadEnvWithModifiers(filePath string) (AppEnv, AppModifiers, error) {
|
|||||||
return nil, mods, err
|
return nil, mods, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("read %s from %s", envVars, filePath))
|
log.Debugf("read %s from %s", envVars, filePath)
|
||||||
|
|
||||||
return envVars, mods, nil
|
return envVars, mods, nil
|
||||||
}
|
}
|
||||||
@ -70,16 +74,16 @@ func ReadAbraShEnvVars(abraSh string) (map[string]string, error) {
|
|||||||
envVarDef := splitVals[len(splitVals)-1]
|
envVarDef := splitVals[len(splitVals)-1]
|
||||||
keyVal := strings.Split(envVarDef, "=")
|
keyVal := strings.Split(envVarDef, "=")
|
||||||
if len(keyVal) != 2 {
|
if len(keyVal) != 2 {
|
||||||
return envVars, errors.New(i18n.G("couldn't parse %s", txt))
|
return envVars, fmt.Errorf("couldn't parse %s", txt)
|
||||||
}
|
}
|
||||||
envVars[keyVal[0]] = keyVal[1]
|
envVars[keyVal[0]] = keyVal[1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(envVars) > 0 {
|
if len(envVars) > 0 {
|
||||||
log.Debug(i18n.G("read %s from %s", envVars, abraSh))
|
log.Debugf("read %s from %s", envVars, abraSh)
|
||||||
} else {
|
} else {
|
||||||
log.Debug(i18n.G("read 0 env var exports from %s", abraSh))
|
log.Debugf("read 0 env var exports from %s", abraSh)
|
||||||
}
|
}
|
||||||
|
|
||||||
return envVars, nil
|
return envVars, nil
|
||||||
|
|||||||
@ -15,7 +15,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func TestGetAllFoldersInDirectory(t *testing.T) {
|
func TestGetAllFoldersInDirectory(t *testing.T) {
|
||||||
folders, err := config.GetAllFoldersInDirectory(testPkg.TestDir)
|
folders, err := config.GetAllFoldersInDirectory(testPkg.TestFolder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@ -25,7 +25,7 @@ func TestGetAllFoldersInDirectory(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestGetAllFilesInDirectory(t *testing.T) {
|
func TestGetAllFilesInDirectory(t *testing.T) {
|
||||||
files, err := config.GetAllFilesInDirectory(testPkg.TestDir)
|
files, err := config.GetAllFilesInDirectory(testPkg.TestFolder)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,6 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"github.com/charmbracelet/lipgloss"
|
"github.com/charmbracelet/lipgloss"
|
||||||
"github.com/charmbracelet/lipgloss/table"
|
"github.com/charmbracelet/lipgloss/table"
|
||||||
"github.com/docker/go-units"
|
"github.com/docker/go-units"
|
||||||
@ -43,7 +42,7 @@ func RemoveSha(str string) string {
|
|||||||
func HumanDuration(timestamp int64) string {
|
func HumanDuration(timestamp int64) string {
|
||||||
date := time.Unix(timestamp, 0)
|
date := time.Unix(timestamp, 0)
|
||||||
now := time.Now().UTC()
|
now := time.Now().UTC()
|
||||||
return units.HumanDuration(now.Sub(date)) + i18n.G(" ago")
|
return units.HumanDuration(now.Sub(date)) + " ago"
|
||||||
}
|
}
|
||||||
|
|
||||||
// CreateTable prepares a table layout for output.
|
// CreateTable prepares a table layout for output.
|
||||||
@ -77,7 +76,7 @@ func CreateTable() (*table.Table, error) {
|
|||||||
func PrintTable(t *table.Table) error {
|
func PrintTable(t *table.Table) error {
|
||||||
if isAbraCI, ok := os.LookupEnv("ABRA_CI"); ok && isAbraCI == "1" {
|
if isAbraCI, ok := os.LookupEnv("ABRA_CI"); ok && isAbraCI == "1" {
|
||||||
// NOTE(d1): no width limits for CI testing since we test against outputs
|
// NOTE(d1): no width limits for CI testing since we test against outputs
|
||||||
log.Debug(i18n.G("detected ABRA_CI=1"))
|
log.Debug("detected ABRA_CI=1")
|
||||||
fmt.Println(t)
|
fmt.Println(t)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -100,7 +99,7 @@ func PrintTable(t *table.Table) error {
|
|||||||
|
|
||||||
// horizontal is a JoinHorizontal helper function.
|
// horizontal is a JoinHorizontal helper function.
|
||||||
func horizontal(left, mid, right string) string {
|
func horizontal(left, mid, right string) string {
|
||||||
return lipgloss.JoinHorizontal(lipgloss.Top, left, mid, right)
|
return lipgloss.JoinHorizontal(lipgloss.Right, left, mid, right)
|
||||||
}
|
}
|
||||||
|
|
||||||
func CreateOverview(header string, rows [][]string) string {
|
func CreateOverview(header string, rows [][]string) string {
|
||||||
@ -131,7 +130,7 @@ func CreateOverview(header string, rows [][]string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(row) > 2 {
|
if len(row) > 2 {
|
||||||
panic(i18n.G("CreateOverview: only accepts rows of len == 2"))
|
panic("CreateOverview: only accepts rows of len == 2")
|
||||||
}
|
}
|
||||||
|
|
||||||
lenOffset := 4
|
lenOffset := 4
|
||||||
@ -235,7 +234,7 @@ func StripTagMeta(image string) string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if originalImage != image {
|
if originalImage != image {
|
||||||
log.Debug(i18n.G("stripped %s to %s for parsing", originalImage, image))
|
log.Debugf("stripped %s to %s for parsing", originalImage, image)
|
||||||
}
|
}
|
||||||
|
|
||||||
return image
|
return image
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
)
|
)
|
||||||
@ -19,7 +18,7 @@ func Add(repoPath, path string, dryRun bool) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if dryRun {
|
if dryRun {
|
||||||
log.Debug(i18n.G("dry run: adding %s", path))
|
log.Debugf("dry run: adding %s", path)
|
||||||
} else {
|
} else {
|
||||||
worktree.Add(path)
|
worktree.Add(path)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,10 +1,8 @@
|
|||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"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"
|
||||||
@ -65,7 +63,7 @@ func GetDefaultBranch(repo *git.Repository, repoPath string) (plumbing.Reference
|
|||||||
|
|
||||||
if !HasBranch(repo, "master") {
|
if !HasBranch(repo, "master") {
|
||||||
if !HasBranch(repo, "main") {
|
if !HasBranch(repo, "main") {
|
||||||
return "", errors.New(i18n.G("failed to select default branch in %s", repoPath))
|
return "", fmt.Errorf("failed to select default branch in %s", repoPath)
|
||||||
}
|
}
|
||||||
branch = "main"
|
branch = "main"
|
||||||
}
|
}
|
||||||
@ -92,11 +90,11 @@ func CheckoutDefaultBranch(repo *git.Repository, repoPath string) (plumbing.Refe
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := worktree.Checkout(checkOutOpts); err != nil {
|
if err := worktree.Checkout(checkOutOpts); err != nil {
|
||||||
log.Debug(i18n.G("failed to check out %s in %s", branch, repoPath))
|
log.Debugf("failed to check out %s in %s", branch, repoPath)
|
||||||
return branch, err
|
return branch, err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("successfully checked out %v in %s", branch, repoPath))
|
log.Debugf("successfully checked out %v in %s", branch, repoPath)
|
||||||
|
|
||||||
return branch, nil
|
return branch, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,13 +2,11 @@ package git
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
"os/signal"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"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"
|
||||||
@ -46,7 +44,7 @@ func Clone(dir, url string) error {
|
|||||||
|
|
||||||
go func() {
|
go func() {
|
||||||
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||||
log.Debug(i18n.G("git clone: %s", url))
|
log.Debugf("git clone: %s", url)
|
||||||
|
|
||||||
_, err := git.PlainCloneContext(ctx, dir, false, &git.CloneOptions{
|
_, err := git.PlainCloneContext(ctx, dir, false, &git.CloneOptions{
|
||||||
URL: url,
|
URL: url,
|
||||||
@ -56,16 +54,16 @@ func Clone(dir, url string) error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil && gitCloneIgnoreErr(err) {
|
if err != nil && gitCloneIgnoreErr(err) {
|
||||||
log.Debug(i18n.G("git clone: %s cloned successfully", dir))
|
log.Debugf("git clone: %s cloned successfully", dir)
|
||||||
errCh <- nil
|
errCh <- nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := ctx.Err(); err != nil {
|
if err := ctx.Err(); err != nil {
|
||||||
errCh <- errors.New(i18n.G("git clone %s: cancelled due to interrupt", dir))
|
errCh <- fmt.Errorf("git clone %s: cancelled due to interrupt", dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debug(i18n.G("git clone: main branch failed, attempting master branch"))
|
log.Debug("git clone: main branch failed, attempting master branch")
|
||||||
|
|
||||||
_, err := git.PlainCloneContext(ctx, dir, false, &git.CloneOptions{
|
_, err := git.PlainCloneContext(ctx, dir, false, &git.CloneOptions{
|
||||||
URL: url,
|
URL: url,
|
||||||
@ -75,7 +73,7 @@ func Clone(dir, url string) error {
|
|||||||
})
|
})
|
||||||
|
|
||||||
if err != nil && gitCloneIgnoreErr(err) {
|
if err != nil && gitCloneIgnoreErr(err) {
|
||||||
log.Debug(i18n.G("git clone: %s cloned successfully", dir))
|
log.Debugf("git clone: %s cloned successfully", dir)
|
||||||
errCh <- nil
|
errCh <- nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,9 +82,9 @@ func Clone(dir, url string) error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("git clone: %s cloned successfully", dir))
|
log.Debugf("git clone: %s cloned successfully", dir)
|
||||||
} else {
|
} else {
|
||||||
log.Debug(i18n.G("git clone: %s already exists", dir))
|
log.Debugf("git clone: %s already exists", dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
errCh <- nil
|
errCh <- nil
|
||||||
@ -97,9 +95,9 @@ func Clone(dir, url string) error {
|
|||||||
cancelCtx()
|
cancelCtx()
|
||||||
fmt.Println() // NOTE(d1): newline after ^C
|
fmt.Println() // NOTE(d1): newline after ^C
|
||||||
if err := os.RemoveAll(dir); err != nil {
|
if err := os.RemoveAll(dir); err != nil {
|
||||||
return errors.New(i18n.G("unable to clean up git clone of %s: %s", dir, err))
|
return fmt.Errorf("unable to clean up git clone of %s: %s", dir, err)
|
||||||
}
|
}
|
||||||
return errors.New(i18n.G("git clone %s: cancelled due to interrupt", dir))
|
return fmt.Errorf("git clone %s: cancelled due to interrupt", dir)
|
||||||
case err := <-errCh:
|
case err := <-errCh:
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
)
|
)
|
||||||
@ -11,7 +10,7 @@ import (
|
|||||||
// Commit runs a git commit
|
// Commit runs a git commit
|
||||||
func Commit(repoPath, commitMessage string, dryRun bool) error {
|
func Commit(repoPath, commitMessage string, dryRun bool) error {
|
||||||
if commitMessage == "" {
|
if commitMessage == "" {
|
||||||
return errors.New(i18n.G("no commit message specified?"))
|
return fmt.Errorf("no commit message specified?")
|
||||||
}
|
}
|
||||||
|
|
||||||
commitRepo, err := git.PlainOpen(repoPath)
|
commitRepo, err := git.PlainOpen(repoPath)
|
||||||
@ -39,9 +38,9 @@ func Commit(repoPath, commitMessage string, dryRun bool) error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
log.Debug(i18n.G("git changes commited"))
|
log.Debug("git changes commited")
|
||||||
} else {
|
} else {
|
||||||
log.Debug(i18n.G("dry run: no changes commited"))
|
log.Debug("dry run: no changes commited")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -1,16 +1,14 @@
|
|||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// EnsureGitRepo ensures a git repo .git folder exists
|
// EnsureGitRepo ensures a git repo .git folder exists
|
||||||
func EnsureGitRepo(repoPath string) error {
|
func EnsureGitRepo(repoPath string) error {
|
||||||
if _, err := os.Stat(repoPath); os.IsNotExist(err) {
|
if _, err := os.Stat(repoPath); os.IsNotExist(err) {
|
||||||
return errors.New(i18n.G("no .git directory in %s?", repoPath))
|
return fmt.Errorf("no .git directory in %s?", repoPath)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,7 +4,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -27,7 +26,7 @@ func getGitDiffArgs(repoPath string) []string {
|
|||||||
// skips if it cannot find the command on the system.
|
// skips if it cannot find the command on the system.
|
||||||
func DiffUnstaged(path string) error {
|
func DiffUnstaged(path string) error {
|
||||||
if _, err := exec.LookPath("git"); err != nil {
|
if _, err := exec.LookPath("git"); err != nil {
|
||||||
log.Warnf(i18n.G("unable to locate git command, cannot output diff"))
|
log.Warnf("unable to locate git command, cannot output diff")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"fmt"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"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"
|
||||||
@ -14,28 +13,28 @@ import (
|
|||||||
func Init(repoPath string, commit bool, gitName, gitEmail string) error {
|
func Init(repoPath string, commit bool, gitName, gitEmail string) error {
|
||||||
repo, err := git.PlainInit(repoPath, false)
|
repo, err := git.PlainInit(repoPath, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(i18n.G("git init: %s", err))
|
return fmt.Errorf("git init: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err = SwitchToMain(repo); err != nil {
|
if err = SwitchToMain(repo); err != nil {
|
||||||
return errors.New(i18n.G("git branch rename: %s", err))
|
return fmt.Errorf("git branch rename: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("initialised new git repo in %s", repoPath))
|
log.Debugf("initialised new git repo in %s", repoPath)
|
||||||
|
|
||||||
if commit {
|
if commit {
|
||||||
commitRepo, err := git.PlainOpen(repoPath)
|
commitRepo, err := git.PlainOpen(repoPath)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(i18n.G("git open: %s", err))
|
return fmt.Errorf("git open: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
commitWorktree, err := commitRepo.Worktree()
|
commitWorktree, err := commitRepo.Worktree()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New(i18n.G("git worktree: %s", err))
|
return fmt.Errorf("git worktree: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := commitWorktree.AddWithOptions(&git.AddOptions{All: true}); err != nil {
|
if err := commitWorktree.AddWithOptions(&git.AddOptions{All: true}); err != nil {
|
||||||
return errors.New(i18n.G("git add: %s", err))
|
return fmt.Errorf("git add: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
var author *object.Signature
|
var author *object.Signature
|
||||||
@ -44,10 +43,10 @@ func Init(repoPath string, commit bool, gitName, gitEmail string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if _, err = commitWorktree.Commit("init", &git.CommitOptions{Author: author}); err != nil {
|
if _, err = commitWorktree.Commit("init", &git.CommitOptions{Author: author}); err != nil {
|
||||||
return errors.New(i18n.G("git commit: %s", err))
|
return fmt.Errorf("git commit: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("init committed all files for new git repo in %s", repoPath))
|
log.Debugf("init committed all files for new git repo in %s", repoPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@ -57,20 +56,20 @@ func Init(repoPath string, commit bool, gitName, gitEmail string) error {
|
|||||||
func SwitchToMain(repo *git.Repository) error {
|
func SwitchToMain(repo *git.Repository) error {
|
||||||
ref := plumbing.NewSymbolicReference(plumbing.HEAD, plumbing.ReferenceName("refs/heads/main"))
|
ref := plumbing.NewSymbolicReference(plumbing.HEAD, plumbing.ReferenceName("refs/heads/main"))
|
||||||
if err := repo.Storer.SetReference(ref); err != nil {
|
if err := repo.Storer.SetReference(ref); err != nil {
|
||||||
return errors.New(i18n.G("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 errors.New(i18n.G("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 errors.New(i18n.G("repo set config: %s", err))
|
return fmt.Errorf("repo set config: %s", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("set 'main' as the default branch"))
|
log.Debug("set 'main' as the default branch")
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
package git
|
package git
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/go-git/go-git/v5/config"
|
"github.com/go-git/go-git/v5/config"
|
||||||
@ -10,7 +9,7 @@ import (
|
|||||||
// Push pushes the latest changes & optionally tags to the default remote
|
// Push pushes the latest changes & optionally tags to the default remote
|
||||||
func Push(repoDir string, remote string, tags bool, dryRun bool) error {
|
func Push(repoDir string, remote string, tags bool, dryRun bool) error {
|
||||||
if dryRun {
|
if dryRun {
|
||||||
log.Debug(i18n.G("dry run: no git changes pushed in %s", repoDir))
|
log.Debugf("dry run: no git changes pushed in %s", repoDir)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,7 +27,7 @@ func Push(repoDir string, remote string, tags bool, dryRun bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("git changes pushed"))
|
log.Debugf("git changes pushed")
|
||||||
|
|
||||||
if tags {
|
if tags {
|
||||||
opts.RefSpecs = append(opts.RefSpecs, config.RefSpec("+refs/tags/*:refs/tags/*"))
|
opts.RefSpecs = append(opts.RefSpecs, config.RefSpec("+refs/tags/*:refs/tags/*"))
|
||||||
@ -37,7 +36,7 @@ func Push(repoDir string, remote string, tags bool, dryRun bool) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("git tags pushed"))
|
log.Debugf("git tags pushed")
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@ -2,13 +2,13 @@ package git
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"os/user"
|
"os/user"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
gitConfigPkg "github.com/go-git/go-git/v5/config"
|
gitConfigPkg "github.com/go-git/go-git/v5/config"
|
||||||
@ -23,12 +23,12 @@ func IsClean(repoPath string) (bool, error) {
|
|||||||
return false, git.ErrRepositoryNotExists
|
return false, git.ErrRepositoryNotExists
|
||||||
}
|
}
|
||||||
|
|
||||||
return false, errors.New(i18n.G("unable to open %s: %s", repoPath, err))
|
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, errors.New(i18n.G("unable to open worktree of %s: %s", repoPath, err))
|
return false, fmt.Errorf("unable to open worktree of %s: %s", repoPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
patterns, err := GetExcludesFiles()
|
patterns, err := GetExcludesFiles()
|
||||||
@ -42,14 +42,14 @@ func IsClean(repoPath string) (bool, error) {
|
|||||||
|
|
||||||
status, err := worktree.Status()
|
status, err := worktree.Status()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, errors.New(i18n.G("unable to query status of %s: %s", repoPath, err))
|
return false, fmt.Errorf("unable to query status of %s: %s", repoPath, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if status.String() != "" {
|
if status.String() != "" {
|
||||||
noNewline := strings.TrimSuffix(status.String(), "\n")
|
noNewline := strings.TrimSuffix(status.String(), "\n")
|
||||||
log.Debug(i18n.G("git status: %s: %s", repoPath, noNewline))
|
log.Debugf("git status: %s: %s", repoPath, noNewline)
|
||||||
} else {
|
} else {
|
||||||
log.Debug(i18n.G("git status: %s: clean", repoPath))
|
log.Debugf("git status: %s: clean", repoPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
return status.IsClean(), nil
|
return status.IsClean(), nil
|
||||||
@ -85,7 +85,7 @@ func parseGitConfig() (*gitConfigPkg.Config, error) {
|
|||||||
globalGitConfig := filepath.Join(usr.HomeDir, ".gitconfig")
|
globalGitConfig := filepath.Join(usr.HomeDir, ".gitconfig")
|
||||||
if _, err := os.Stat(globalGitConfig); err != nil {
|
if _, err := os.Stat(globalGitConfig); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
log.Debug(i18n.G("no %s exists, not reading any global gitignore config", globalGitConfig))
|
log.Debugf("no %s exists, not reading any global gitignore config", globalGitConfig)
|
||||||
return cfg, nil
|
return cfg, nil
|
||||||
}
|
}
|
||||||
return cfg, err
|
return cfg, err
|
||||||
@ -127,7 +127,7 @@ func parseExcludesFile(excludesfile string) ([]gitignore.Pattern, error) {
|
|||||||
|
|
||||||
if _, err := os.Stat(excludesfile); err != nil {
|
if _, err := os.Stat(excludesfile); err != nil {
|
||||||
if os.IsNotExist(err) {
|
if os.IsNotExist(err) {
|
||||||
log.Debug(i18n.G("no %s exists, skipping reading gitignore paths", excludesfile))
|
log.Debugf("no %s exists, skipping reading gitignore paths", excludesfile)
|
||||||
return ps, nil
|
return ps, nil
|
||||||
}
|
}
|
||||||
return ps, err
|
return ps, err
|
||||||
@ -146,7 +146,7 @@ func parseExcludesFile(excludesfile string) ([]gitignore.Pattern, error) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug(i18n.G("read global ignore paths: %s", strings.Join(pathsRaw, " ")))
|
log.Debugf("read global ignore paths: %s", strings.Join(pathsRaw, " "))
|
||||||
|
|
||||||
return ps, nil
|
return ps, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@ -3,7 +3,6 @@ package git
|
|||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
"coopcloud.tech/abra/pkg/log"
|
||||||
"github.com/go-git/go-git/v5"
|
"github.com/go-git/go-git/v5"
|
||||||
"github.com/go-git/go-git/v5/config"
|
"github.com/go-git/go-git/v5/config"
|
||||||
@ -12,7 +11,7 @@ import (
|
|||||||
// CreateRemote creates a new git remote in a repository
|
// CreateRemote creates a new git remote in a repository
|
||||||
func CreateRemote(repo *git.Repository, name, url string, dryRun bool) error {
|
func CreateRemote(repo *git.Repository, name, url string, dryRun bool) error {
|
||||||
if dryRun {
|
if dryRun {
|
||||||
log.Debug(i18n.G("dry run: remote %s (%s) not created", name, url))
|
log.Debugf("dry run: remote %s (%s) not created", name, url)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,67 +0,0 @@
|
|||||||
package i18n
|
|
||||||
|
|
||||||
import (
|
|
||||||
"embed"
|
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"slices"
|
|
||||||
"strings"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/log"
|
|
||||||
"github.com/leonelquinteros/gotext"
|
|
||||||
)
|
|
||||||
|
|
||||||
//go:embed locales/*.mo
|
|
||||||
var assetFS embed.FS
|
|
||||||
|
|
||||||
var (
|
|
||||||
DefaultLocale = "en"
|
|
||||||
Locale = DefaultLocale
|
|
||||||
_, Mo = LoadLocale()
|
|
||||||
G = Mo.Get
|
|
||||||
GC = Mo.GetC
|
|
||||||
)
|
|
||||||
|
|
||||||
func LoadLocale() (string, *gotext.Mo) {
|
|
||||||
entries, err := assetFS.ReadDir("locales")
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("i18n: unable to read embedded locales directory: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var linguas []string
|
|
||||||
for _, entry := range entries {
|
|
||||||
if filepath.Ext(entry.Name()) == ".mo" {
|
|
||||||
fname := entry.Name()
|
|
||||||
fnameWithoutExt := strings.TrimSuffix(fname, filepath.Ext(fname))
|
|
||||||
linguas = append(linguas, fnameWithoutExt)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
locale := os.Getenv("LANG")
|
|
||||||
if lastUnderscore := strings.LastIndex(locale, "_"); lastUnderscore != -1 {
|
|
||||||
locale = locale[0:lastUnderscore]
|
|
||||||
}
|
|
||||||
|
|
||||||
if locale != "" {
|
|
||||||
if slices.Contains(linguas, locale) {
|
|
||||||
Locale = locale
|
|
||||||
} else {
|
|
||||||
log.Debugf("unsupported language: %s (want: %s)", locale, strings.Join(linguas, " "))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if Locale == DefaultLocale {
|
|
||||||
return Locale, gotext.NewMo()
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := assetFS.ReadFile(fmt.Sprintf("locales/%s.mo", Locale))
|
|
||||||
if err != nil {
|
|
||||||
log.Fatalf("i18n: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
mo := gotext.NewMo()
|
|
||||||
mo.Parse(b)
|
|
||||||
|
|
||||||
return Locale, mo
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
package i18n_test
|
|
||||||
|
|
||||||
import (
|
|
||||||
"os"
|
|
||||||
"testing"
|
|
||||||
|
|
||||||
"coopcloud.tech/abra/pkg/i18n"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestLoadLocale(t *testing.T) {
|
|
||||||
originalLang := os.Getenv("LANG")
|
|
||||||
os.Setenv("LANG", "es_ES.UTF-8")
|
|
||||||
t.Cleanup(func() {
|
|
||||||
os.Setenv("LANG", originalLang)
|
|
||||||
})
|
|
||||||
|
|
||||||
locale, _ := i18n.LoadLocale()
|
|
||||||
if locale != "es" {
|
|
||||||
t.Fatalf("expected 'es', locale was '%s'", locale)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user