Compare commits

..

1 Commits

Author SHA1 Message Date
4ef849767f WIP
All checks were successful
continuous-integration/drone/push Build is passing
2025-11-02 14:13:40 +01:00
24 changed files with 1361 additions and 529 deletions

View File

@ -260,7 +260,6 @@ checkout as-is. Recipe commit hashes are also supported as values for
app.Name, app.Name,
app.Server, app.Server,
internal.DontWaitConverge, internal.DontWaitConverge,
internal.NoInput,
f, f,
); err != nil { ); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -128,7 +128,6 @@ Pass "--all-services/-a" to restart all services.`),
AppName: app.Name, AppName: app.Name,
ServerName: app.Server, ServerName: app.Server,
Filters: f, Filters: f,
NoInput: internal.NoInput,
NoLog: true, NoLog: true,
Quiet: true, Quiet: true,
} }

View File

@ -246,7 +246,6 @@ beforehand. See "abra app backup" for more.`),
stackName, stackName,
app.Server, app.Server,
internal.DontWaitConverge, internal.DontWaitConverge,
internal.NoInput,
f, f,
); err != nil { ); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -282,7 +282,6 @@ beforehand. See "abra app backup" for more.`),
stackName, stackName,
app.Server, app.Server,
internal.DontWaitConverge, internal.DontWaitConverge,
internal.NoInput,
f, f,
); err != nil { ); err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -64,7 +64,7 @@ func DeployOverview(
server = "local" server = "local"
} }
domain := fmt.Sprintf("https://%s", app.Domain) domain := app.Domain
if domain == "" { if domain == "" {
domain = config.MISSING_DEFAULT domain = config.MISSING_DEFAULT
} }

2
go.mod
View File

@ -8,6 +8,7 @@ require (
coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca
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.10 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.2
@ -41,7 +42,6 @@ require (
github.com/charmbracelet/colorprofile v0.3.2 // indirect github.com/charmbracelet/colorprofile v0.3.2 // indirect
github.com/charmbracelet/x/ansi v0.10.2 // indirect github.com/charmbracelet/x/ansi v0.10.2 // indirect
github.com/charmbracelet/x/cellbuf v0.0.13 // indirect github.com/charmbracelet/x/cellbuf v0.0.13 // indirect
github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 // 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/clipperhouse/uax29/v2 v2.2.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect github.com/cloudflare/circl v1.6.1 // indirect

2
go.sum
View File

@ -133,6 +133,8 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf
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/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg=
github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw=
github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4=
github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI= github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI=

View File

@ -633,11 +633,6 @@ func (a App) WipeRecipeVersion() error {
// WriteRecipeVersion writes the recipe version to the app .env file. // WriteRecipeVersion writes the recipe version to the app .env file.
func (a App) WriteRecipeVersion(version string, dryRun bool) error { func (a App) WriteRecipeVersion(version string, dryRun bool) error {
if version == config.UNKNOWN_DEFAULT {
log.Debug(i18n.G("version is unknown, skipping env write"))
return nil
}
file, err := os.Open(a.Path) file, err := os.Open(a.Path)
if err != nil { if err != nil {
return err return err

View File

@ -224,16 +224,3 @@ func TestWriteRecipeVersionOverwrite(t *testing.T) {
assert.Equal(t, "foo", app.Recipe.EnvVersion) assert.Equal(t, "foo", app.Recipe.EnvVersion)
} }
func TestWriteRecipeVersionUnknown(t *testing.T) {
app, err := appPkg.GetApp(testPkg.ExpectedAppFiles, testPkg.AppName)
if err != nil {
t.Fatal(err)
}
if err := app.WriteRecipeVersion(config.UNKNOWN_DEFAULT, false); err != nil {
t.Fatal(err)
}
assert.NotEqual(t, config.UNKNOWN_DEFAULT, app.Recipe.EnvVersion)
}

View File

@ -37,27 +37,18 @@ func WithTimeout(timeout int) Opt {
// New initiates a new Docker client. New client connections are validated so // New initiates a new Docker client. New client connections are validated so
// that we ensure connections via SSH to the daemon can succeed. It takes into // that we ensure connections via SSH to the daemon can succeed. It takes into
// account that you may only want the local client and not communicate via SSH. // account that you may only want the local client and not communicate via SSH.
// For this use-case, please pass "default" as the serverName. // For this use-case, please pass "default" as the contextName.
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) ctx, err := GetContext(serverName)
if err != nil { if err != nil {
serverDir := path.Join(config.SERVERS_DIR, serverName) serverDir := path.Join(config.SERVERS_DIR, serverName)
if _, err := os.Stat(serverDir); err != nil { if _, err := os.Stat(serverDir); err == nil {
return nil, errors.New(i18n.G("server missing, run \"abra server add %s\"?", serverName))
}
// NOTE(p4u1): when the docker context does not exist but the server folder
// is there, let's create a new docker context.
if err = CreateContext(serverName); err != nil {
return nil, errors.New(i18n.G("server missing context, context creation failed: %s", err))
}
ctx, err = GetContext(serverName)
if err != nil {
return nil, errors.New(i18n.G("server missing context, run \"abra server add %s\"?", serverName)) 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(ctx) ctxEndpoint, err := contextPkg.GetContextEndpoint(ctx)

View File

@ -7,7 +7,7 @@
msgid "" msgid ""
msgstr "Project-Id-Version: \n" msgstr "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: EMAIL\n" "Report-Msgid-Bugs-To: EMAIL\n"
"POT-Creation-Date: 2025-11-04 15:34+0100\n" "POT-Creation-Date: 2025-11-02 14:13+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -274,12 +274,12 @@ msgstr ""
msgid "%s has been detected as not deployed" msgid "%s has been detected as not deployed"
msgstr "" msgstr ""
#: ./cli/app/restart.go:140 #: ./cli/app/restart.go:139
#, c-format #, c-format
msgid "%s has been scaled to 0" msgid "%s has been scaled to 0"
msgstr "" msgstr ""
#: ./cli/app/restart.go:151 #: ./cli/app/restart.go:150
#, c-format #, c-format
msgid "%s has been scaled to 1" msgid "%s has been scaled to 1"
msgstr "" msgstr ""
@ -339,17 +339,17 @@ msgstr ""
msgid "%s is missing the TYPE env var?" msgid "%s is missing the TYPE env var?"
msgstr "" msgstr ""
#: ./cli/app/rollback.go:309 ./cli/app/rollback.go:313 #: ./cli/app/rollback.go:308 ./cli/app/rollback.go:312
#, c-format #, c-format
msgid "%s is not a downgrade for %s?" msgid "%s is not a downgrade for %s?"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:429 ./cli/app/upgrade.go:433 #: ./cli/app/upgrade.go:428 ./cli/app/upgrade.go:432
#, c-format #, c-format
msgid "%s is not an upgrade for %s?" msgid "%s is not an upgrade for %s?"
msgstr "" msgstr ""
#: ./cli/app/env.go:146 ./cli/app/logs.go:65 ./cli/app/ps.go:62 ./cli/app/restart.go:100 ./cli/app/services.go:55 ./cli/app/undeploy.go:66 ./cli/app/upgrade.go:450 #: ./cli/app/env.go:146 ./cli/app/logs.go:65 ./cli/app/ps.go:62 ./cli/app/restart.go:100 ./cli/app/services.go:55 ./cli/app/undeploy.go:66 ./cli/app/upgrade.go:449
#, c-format #, c-format
msgid "%s is not deployed?" msgid "%s is not deployed?"
msgstr "" msgstr ""
@ -424,7 +424,7 @@ msgstr ""
msgid "%s service is missing image tag?" msgid "%s service is missing image tag?"
msgstr "" msgstr ""
#: ./cli/app/restart.go:152 #: ./cli/app/restart.go:151
#, c-format #, c-format
msgid "%s service successfully restarted" msgid "%s service successfully restarted"
msgstr "" msgstr ""
@ -459,7 +459,7 @@ msgstr ""
msgid "%s/example.git" msgid "%s/example.git"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:613 #: ./pkg/upstream/stack/stack.go:602
#, c-format #, c-format
msgid "%s: %s" msgid "%s: %s"
msgstr "" msgstr ""
@ -469,7 +469,7 @@ msgstr ""
msgid "%s: %s (new)" msgid "%s: %s (new)"
msgstr "" msgstr ""
#: ./pkg/ui/deploy.go:344 #: ./pkg/ui/deploy.go:406
#, c-format #, c-format
msgid "%s: %s (retries: %v, healthcheck: %s)" msgid "%s: %s (retries: %v, healthcheck: %s)"
msgstr "" msgstr ""
@ -549,12 +549,12 @@ msgstr ""
msgid "%s: waiting %d seconds before next retry" msgid "%s: waiting %d seconds before next retry"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:424 #: ./cli/app/upgrade.go:423
#, c-format #, c-format
msgid "'%s' is not a known version" msgid "'%s' is not a known version"
msgstr "" msgstr ""
#: ./cli/app/rollback.go:304 ./cli/app/upgrade.go:419 #: ./cli/app/rollback.go:303 ./cli/app/upgrade.go:418
#, c-format #, c-format
msgid "'%s' is not a known version for %s" msgid "'%s' is not a known version for %s"
msgstr "" msgstr ""
@ -621,7 +621,7 @@ msgstr ""
msgid "Both local recipe and live deployment labels are shown." msgid "Both local recipe and live deployment labels are shown."
msgstr "" msgstr ""
#: ./cli/app/backup.go:319 ./cli/app/backup.go:335 ./cli/app/check.go:95 ./cli/app/cmd.go:285 ./cli/app/cp.go:385 ./cli/app/deploy.go:396 ./cli/app/labels.go:143 ./cli/app/new.go:397 ./cli/app/ps.go:213 ./cli/app/restart.go:163 ./cli/app/restore.go:138 ./cli/app/secret.go:569 ./cli/app/secret.go:609 ./cli/app/secret.go:633 ./cli/app/secret.go:641 ./cli/catalogue/catalogue.go:318 ./cli/recipe/lint.go:137 #: ./cli/app/backup.go:319 ./cli/app/backup.go:335 ./cli/app/check.go:95 ./cli/app/cmd.go:285 ./cli/app/cp.go:385 ./cli/app/deploy.go:395 ./cli/app/labels.go:143 ./cli/app/new.go:397 ./cli/app/ps.go:213 ./cli/app/restart.go:162 ./cli/app/restore.go:138 ./cli/app/secret.go:569 ./cli/app/secret.go:609 ./cli/app/secret.go:633 ./cli/app/secret.go:641 ./cli/catalogue/catalogue.go:318 ./cli/recipe/lint.go:137
msgid "C" msgid "C"
msgstr "" msgstr ""
@ -762,7 +762,7 @@ msgid "Creates a new app from a default recipe.\n"
"on your $PATH." "on your $PATH."
msgstr "" msgstr ""
#: ./cli/app/deploy.go:412 ./cli/app/new.go:373 ./cli/app/rollback.go:361 ./cli/app/upgrade.go:470 #: ./cli/app/deploy.go:411 ./cli/app/new.go:373 ./cli/app/rollback.go:360 ./cli/app/upgrade.go:469
msgid "D" msgid "D"
msgstr "" msgstr ""
@ -1469,7 +1469,7 @@ msgid "To load completions:\n"
" # and source this file from your PowerShell profile." " # and source this file from your PowerShell profile."
msgstr "" msgstr ""
#: ./cli/app/deploy.go:436 ./cli/app/rollback.go:377 ./cli/app/upgrade.go:494 #: ./cli/app/deploy.go:435 ./cli/app/rollback.go:376 ./cli/app/upgrade.go:493
msgid "U" msgid "U"
msgstr "" msgstr ""
@ -1659,7 +1659,7 @@ msgid "\n"
"lint %s: %s" "lint %s: %s"
msgstr "" msgstr ""
#: ./pkg/ui/deploy.go:121 #: ./pkg/ui/deploy.go:132
#, c-format #, c-format
msgid "^%s" msgid "^%s"
msgstr "" msgstr ""
@ -1676,7 +1676,7 @@ msgctxt "app backup list"
msgid "a" msgid "a"
msgstr "" msgstr ""
#: ./cli/app/restart.go:170 #: ./cli/app/restart.go:169
msgctxt "app restart" msgctxt "app restart"
msgid "a" msgid "a"
msgstr "" msgstr ""
@ -1785,7 +1785,7 @@ msgstr ""
msgid "all tasks reached terminal state" msgid "all tasks reached terminal state"
msgstr "" msgstr ""
#: ./cli/app/restart.go:169 #: ./cli/app/restart.go:168
msgid "all-services" msgid "all-services"
msgstr "" msgstr ""
@ -1844,7 +1844,7 @@ msgstr ""
msgid "attempting to run %s" msgid "attempting to run %s"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:273 ./cli/app/upgrade.go:296 #: ./cli/app/deploy.go:272 ./cli/app/upgrade.go:295
#, c-format #, c-format
msgid "attempting to run post deploy commands, saw: %s" msgid "attempting to run post deploy commands, saw: %s"
msgstr "" msgstr ""
@ -1854,7 +1854,7 @@ msgstr ""
msgid "attempting to scale %s to 0" msgid "attempting to scale %s to 0"
msgstr "" msgstr ""
#: ./cli/app/restart.go:141 #: ./cli/app/restart.go:140
#, c-format #, c-format
msgid "attempting to scale %s to 1" msgid "attempting to scale %s to 1"
msgstr "" msgstr ""
@ -1924,7 +1924,7 @@ msgstr ""
#. no spaces in between #. no spaces in between
#. translators: `abra app cp` aliases. use a comma separated list of aliases with #. translators: `abra app cp` aliases. use a comma separated list of aliases with
#. no spaces in between #. no spaces in between
#: ./cli/app/backup.go:148 ./cli/app/cp.go:30 ./cli/app/deploy.go:420 ./cli/app/rollback.go:369 ./cli/app/upgrade.go:478 #: ./cli/app/backup.go:148 ./cli/app/cp.go:30 ./cli/app/deploy.go:419 ./cli/app/rollback.go:368 ./cli/app/upgrade.go:477
msgid "c" msgid "c"
msgstr "" msgstr ""
@ -1965,7 +1965,7 @@ msgstr ""
msgid "cannot find app with name %s" msgid "cannot find app with name %s"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:668 #: ./pkg/upstream/stack/stack.go:657
#, c-format #, c-format
msgid "cannot get label %s for %s" msgid "cannot get label %s for %s"
msgstr "" msgstr ""
@ -1980,7 +1980,7 @@ msgstr ""
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?" msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:370 #: ./cli/app/deploy.go:369
#, c-format #, c-format
msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?\n" msgid "cannot redeploy previous chaos version (%s), did you mean to use \"--chaos\"?\n"
" to return to a regular release, specify a release tag, commit SHA or use \"--latest\"" " to return to a regular release, specify a release tag, commit SHA or use \"--latest\""
@ -1999,7 +1999,7 @@ msgstr ""
msgid "cannot use '[secret] [version]' and '--all' together" msgid "cannot use '[secret] [version]' and '--all' together"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:312 #: ./cli/app/deploy.go:311
msgid "cannot use --chaos and --latest together" msgid "cannot use --chaos and --latest together"
msgstr "" msgstr ""
@ -2023,11 +2023,11 @@ msgstr ""
msgid "cannot use [service] and --all-services/-a together" msgid "cannot use [service] and --all-services/-a together"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:304 ./cli/app/new.go:76 #: ./cli/app/deploy.go:303 ./cli/app/new.go:76
msgid "cannot use [version] and --chaos together" msgid "cannot use [version] and --chaos together"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:308 #: ./cli/app/deploy.go:307
msgid "cannot use [version] and --latest together" msgid "cannot use [version] and --latest together"
msgstr "" msgstr ""
@ -2059,7 +2059,7 @@ msgstr ""
msgid "cfg" msgid "cfg"
msgstr "" msgstr ""
#: ./cli/app/backup.go:318 ./cli/app/backup.go:334 ./cli/app/check.go:94 ./cli/app/cmd.go:284 ./cli/app/cp.go:384 ./cli/app/deploy.go:395 ./cli/app/labels.go:142 ./cli/app/new.go:396 ./cli/app/ps.go:212 ./cli/app/restart.go:162 ./cli/app/restore.go:137 ./cli/app/secret.go:568 ./cli/app/secret.go:608 ./cli/app/secret.go:632 ./cli/app/secret.go:640 ./cli/catalogue/catalogue.go:317 ./cli/recipe/lint.go:136 #: ./cli/app/backup.go:318 ./cli/app/backup.go:334 ./cli/app/check.go:94 ./cli/app/cmd.go:284 ./cli/app/cp.go:384 ./cli/app/deploy.go:394 ./cli/app/labels.go:142 ./cli/app/new.go:396 ./cli/app/ps.go:212 ./cli/app/restart.go:161 ./cli/app/restore.go:137 ./cli/app/secret.go:568 ./cli/app/secret.go:608 ./cli/app/secret.go:632 ./cli/app/secret.go:640 ./cli/catalogue/catalogue.go:317 ./cli/recipe/lint.go:136
msgid "chaos" msgid "chaos"
msgstr "" msgstr ""
@ -2068,7 +2068,7 @@ msgstr ""
msgid "check <domain> [flags]" msgid "check <domain> [flags]"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:94 ./cli/app/undeploy.go:58 ./cli/app/upgrade.go:442 #: ./cli/app/deploy.go:94 ./cli/app/undeploy.go:58 ./cli/app/upgrade.go:441
#, c-format #, c-format
msgid "checking whether %s is already deployed" msgid "checking whether %s is already deployed"
msgstr "" msgstr ""
@ -2291,7 +2291,7 @@ msgstr ""
msgid "create remote directory: %s" msgid "create remote directory: %s"
msgstr "" msgstr ""
#: ./pkg/client/client.go:111 #: ./pkg/client/client.go:102
#, c-format #, c-format
msgid "created client for %s" msgid "created client for %s"
msgstr "" msgstr ""
@ -2311,7 +2311,7 @@ msgstr ""
msgid "created the %s context" msgid "created the %s context"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:524 #: ./pkg/upstream/stack/stack.go:520
#, c-format #, c-format
msgid "creating %s" msgid "creating %s"
msgstr "" msgstr ""
@ -2326,12 +2326,12 @@ msgstr ""
msgid "creating context with domain %s" msgid "creating context with domain %s"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:426 #: ./pkg/upstream/stack/stack.go:422
#, c-format #, c-format
msgid "creating network %s" msgid "creating network %s"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:373 #: ./pkg/upstream/stack/stack.go:369
#, c-format #, c-format
msgid "creating secret %s" msgid "creating secret %s"
msgstr "" msgstr ""
@ -2350,7 +2350,7 @@ msgstr ""
msgid "critical errors present in %s config" msgid "critical errors present in %s config"
msgstr "" msgstr ""
#: ./cli/app/rollback.go:299 #: ./cli/app/rollback.go:298
#, c-format #, c-format
msgid "current deployment '%s' is not a known version for %s" msgid "current deployment '%s' is not a known version for %s"
msgstr "" msgstr ""
@ -2390,11 +2390,11 @@ msgstr ""
msgid "deploy <domain> [version] [flags]" msgid "deploy <domain> [version] [flags]"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:604 #: ./pkg/upstream/stack/stack.go:593
msgid "deploy failed 🛑" msgid "deploy failed 🛑"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:608 #: ./pkg/upstream/stack/stack.go:597
msgid "deploy in progress 🟠" msgid "deploy in progress 🟠"
msgstr "" msgstr ""
@ -2402,15 +2402,15 @@ msgstr ""
msgid "deploy labels stanza present" msgid "deploy labels stanza present"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:430 #: ./cli/app/deploy.go:429
msgid "deploy latest recipe version" msgid "deploy latest recipe version"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:648 #: ./pkg/upstream/stack/stack.go:637
msgid "deploy succeeded 🟢" msgid "deploy succeeded 🟢"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:606 #: ./pkg/upstream/stack/stack.go:595
msgid "deploy timed out 🟠" msgid "deploy timed out 🟠"
msgstr "" msgstr ""
@ -2504,11 +2504,11 @@ msgstr ""
msgid "dirty: %v, " msgid "dirty: %v, "
msgstr "" msgstr ""
#: ./cli/app/deploy.go:422 ./cli/app/rollback.go:371 ./cli/app/upgrade.go:480 #: ./cli/app/deploy.go:421 ./cli/app/rollback.go:370 ./cli/app/upgrade.go:479
msgid "disable converge logic checks" msgid "disable converge logic checks"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:414 ./cli/app/rollback.go:363 ./cli/app/upgrade.go:472 #: ./cli/app/deploy.go:413 ./cli/app/rollback.go:362 ./cli/app/upgrade.go:471
msgid "disable public DNS checks" msgid "disable public DNS checks"
msgstr "" msgstr ""
@ -2662,7 +2662,7 @@ msgstr ""
msgid "env file for %s has issues: %s" msgid "env file for %s has issues: %s"
msgstr "" msgstr ""
#: ./pkg/ui/deploy.go:83 #: ./pkg/ui/deploy.go:94
#, c-format #, c-format
msgid "err: %v, " msgid "err: %v, "
msgstr "" msgstr ""
@ -2726,7 +2726,7 @@ msgstr ""
#. translators: `abra recipe fetch` aliases. use a comma separated list of aliases #. translators: `abra recipe fetch` aliases. use a comma separated list of aliases
#. with no spaces in between #. with no spaces in between
#: ./cli/app/deploy.go:404 ./cli/app/env.go:325 ./cli/app/remove.go:163 ./cli/app/rollback.go:353 ./cli/app/secret.go:593 ./cli/app/upgrade.go:462 ./cli/app/volume.go:217 ./cli/recipe/fetch.go:20 ./cli/recipe/fetch.go:138 #: ./cli/app/deploy.go:403 ./cli/app/env.go:325 ./cli/app/remove.go:163 ./cli/app/rollback.go:352 ./cli/app/secret.go:593 ./cli/app/upgrade.go:461 ./cli/app/volume.go:217 ./cli/recipe/fetch.go:20 ./cli/recipe/fetch.go:138
msgid "f" msgid "f"
msgstr "" msgstr ""
@ -2760,22 +2760,22 @@ msgstr ""
msgid "failed to copy %s from local machine to %s: output:%s err:%s" msgid "failed to copy %s from local machine to %s: output:%s err:%s"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:535 #: ./pkg/upstream/stack/stack.go:531
#, c-format #, c-format
msgid "failed to create %s" msgid "failed to create %s"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:397 #: ./pkg/upstream/stack/stack.go:393
#, c-format #, c-format
msgid "failed to create config %s" msgid "failed to create config %s"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:428 #: ./pkg/upstream/stack/stack.go:424
#, c-format #, c-format
msgid "failed to create network %s" msgid "failed to create network %s"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:375 #: ./pkg/upstream/stack/stack.go:371
#, c-format #, c-format
msgid "failed to create secret %s" msgid "failed to create secret %s"
msgstr "" msgstr ""
@ -2872,7 +2872,7 @@ msgstr ""
msgid "failed to retrieve latest commit for %s: %s" msgid "failed to retrieve latest commit for %s: %s"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:472 #: ./pkg/upstream/stack/stack.go:468
#, c-format #, c-format
msgid "failed to retrieve registry auth for image %s: %s" msgid "failed to retrieve registry auth for image %s: %s"
msgstr "" msgstr ""
@ -2892,17 +2892,17 @@ msgstr ""
msgid "failed to tag release: %s" msgid "failed to tag release: %s"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:512 #: ./pkg/upstream/stack/stack.go:508
#, c-format #, c-format
msgid "failed to update %s" msgid "failed to update %s"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:391 #: ./pkg/upstream/stack/stack.go:387
#, c-format #, c-format
msgid "failed to update config %s" msgid "failed to update config %s"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:369 #: ./pkg/upstream/stack/stack.go:365
#, c-format #, c-format
msgid "failed to update secret %s" msgid "failed to update secret %s"
msgstr "" msgstr ""
@ -2957,7 +2957,7 @@ msgstr ""
msgid "final merged env values for %s are: %s" msgid "final merged env values for %s are: %s"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:403 ./cli/app/env.go:324 ./cli/app/remove.go:162 ./cli/app/rollback.go:352 ./cli/app/upgrade.go:461 ./cli/app/volume.go:216 ./cli/recipe/fetch.go:137 #: ./cli/app/deploy.go:402 ./cli/app/env.go:324 ./cli/app/remove.go:162 ./cli/app/rollback.go:351 ./cli/app/upgrade.go:460 ./cli/app/volume.go:216 ./cli/recipe/fetch.go:137
msgid "force" msgid "force"
msgstr "" msgstr ""
@ -3166,12 +3166,12 @@ msgstr ""
msgid "i" msgid "i"
msgstr "" msgstr ""
#: ./pkg/ui/deploy.go:84 #: ./pkg/ui/deploy.go:95
#, c-format #, c-format
msgid "id: %s, " msgid "id: %s, "
msgstr "" msgstr ""
#: ./cli/app/backup.go:321 ./cli/app/backup.go:337 ./cli/app/check.go:97 ./cli/app/cmd.go:287 ./cli/app/cp.go:387 ./cli/app/deploy.go:398 ./cli/app/labels.go:145 ./cli/app/new.go:399 ./cli/app/ps.go:215 ./cli/app/restart.go:165 ./cli/app/restore.go:140 ./cli/app/secret.go:571 ./cli/app/secret.go:611 ./cli/app/secret.go:635 ./cli/app/secret.go:643 ./cli/catalogue/catalogue.go:320 ./cli/recipe/lint.go:139 #: ./cli/app/backup.go:321 ./cli/app/backup.go:337 ./cli/app/check.go:97 ./cli/app/cmd.go:287 ./cli/app/cp.go:387 ./cli/app/deploy.go:397 ./cli/app/labels.go:145 ./cli/app/new.go:399 ./cli/app/ps.go:215 ./cli/app/restart.go:164 ./cli/app/restore.go:140 ./cli/app/secret.go:571 ./cli/app/secret.go:611 ./cli/app/secret.go:635 ./cli/app/secret.go:643 ./cli/catalogue/catalogue.go:320 ./cli/recipe/lint.go:139
msgid "ignore uncommitted recipes changes" msgid "ignore uncommitted recipes changes"
msgstr "" msgstr ""
@ -3282,7 +3282,7 @@ msgstr ""
msgid "initialised new git repo in %s" msgid "initialised new git repo in %s"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:207 #: ./pkg/upstream/stack/stack.go:206
msgid "initialising deployment" msgid "initialising deployment"
msgstr "" msgstr ""
@ -3346,7 +3346,7 @@ msgstr ""
msgid "invalid npipe source, source cannot be empty" msgid "invalid npipe source, source cannot be empty"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:241 #: ./pkg/upstream/stack/stack.go:239
#, c-format #, c-format
msgid "invalid option %s for flag --resolve-image" msgid "invalid option %s for flag --resolve-image"
msgstr "" msgstr ""
@ -3369,7 +3369,7 @@ msgstr ""
#. no spaces in between #. no spaces in between
#. translators: `abra recipe lint` aliases. use a comma separated list of #. translators: `abra recipe lint` aliases. use a comma separated list of
#. aliases with no spaces in between #. aliases with no spaces in between
#: ./cli/app/cmd.go:261 ./cli/app/deploy.go:428 ./cli/app/logs.go:20 ./cli/recipe/lint.go:17 ./cli/server/add.go:207 #: ./cli/app/cmd.go:261 ./cli/app/deploy.go:427 ./cli/app/logs.go:20 ./cli/recipe/lint.go:17 ./cli/server/add.go:207
msgid "l" msgid "l"
msgstr "" msgstr ""
@ -3384,7 +3384,7 @@ msgstr ""
msgid "labels <domain> [flags]" msgid "labels <domain> [flags]"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:427 ./cli/app/list.go:182 #: ./cli/app/deploy.go:426 ./cli/app/list.go:182
msgid "latest" msgid "latest"
msgstr "" msgstr ""
@ -3473,12 +3473,12 @@ msgstr ""
msgid "logs <domain> [service] [flags]" msgid "logs <domain> [service] [flags]"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:639 #: ./pkg/upstream/stack/stack.go:628
#, c-format #, c-format
msgid "logs: %s" msgid "logs: %s"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:641 #: ./pkg/upstream/stack/stack.go:630
msgid "logs: no log output received from deployment" msgid "logs: no log output received from deployment"
msgstr "" msgstr ""
@ -3605,7 +3605,7 @@ msgstr ""
msgid "name a servce 'app'" msgid "name a servce 'app'"
msgstr "" msgstr ""
#: ./pkg/ui/deploy.go:85 #: ./pkg/ui/deploy.go:96
#, c-format #, c-format
msgid "name: %s, " msgid "name: %s, "
msgstr "" msgstr ""
@ -3614,12 +3614,12 @@ msgstr ""
msgid "need 3 or 4 arguments" msgid "need 3 or 4 arguments"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:352 #: ./pkg/upstream/stack/stack.go:348
#, c-format #, c-format
msgid "network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed, which you can do by running this on the server: docker network create -d overlay proxy" msgid "network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed, which you can do by running this on the server: docker network create -d overlay proxy"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:356 #: ./pkg/upstream/stack/stack.go:352
#, c-format #, c-format
msgid "network %q is declared as external, but it is not in the right scope: %q instead of \"swarm\"" msgid "network %q is declared as external, but it is not in the right scope: %q instead of \"swarm\""
msgstr "" msgstr ""
@ -3842,11 +3842,11 @@ msgstr ""
msgid "no volumes to remove" msgid "no volumes to remove"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:419 ./cli/app/rollback.go:368 ./cli/app/upgrade.go:477 #: ./cli/app/deploy.go:418 ./cli/app/rollback.go:367 ./cli/app/upgrade.go:476
msgid "no-converge-checks" msgid "no-converge-checks"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:411 ./cli/app/rollback.go:360 ./cli/app/upgrade.go:469 #: ./cli/app/deploy.go:410 ./cli/app/rollback.go:359 ./cli/app/upgrade.go:468
msgid "no-domain-checks" msgid "no-domain-checks"
msgstr "" msgstr ""
@ -3902,7 +3902,7 @@ msgstr ""
msgid "only show errors" msgid "only show errors"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:488 #: ./cli/app/upgrade.go:487
msgid "only show release notes" msgid "only show release notes"
msgstr "" msgstr ""
@ -3933,22 +3933,22 @@ msgstr ""
msgid "parsed following command arguments: %s" msgid "parsed following command arguments: %s"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:345 #: ./cli/app/upgrade.go:344
#, c-format #, c-format
msgid "parsing chosen upgrade version failed: %s" msgid "parsing chosen upgrade version failed: %s"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:389 #: ./cli/app/upgrade.go:388
#, c-format #, c-format
msgid "parsing deployed version failed: %s" msgid "parsing deployed version failed: %s"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:350 #: ./cli/app/upgrade.go:349
#, c-format #, c-format
msgid "parsing deployment version failed: %s" msgid "parsing deployment version failed: %s"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:356 ./cli/app/upgrade.go:395 #: ./cli/app/upgrade.go:355 ./cli/app/upgrade.go:394
#, c-format #, c-format
msgid "parsing recipe version failed: %s" msgid "parsing recipe version failed: %s"
msgstr "" msgstr ""
@ -3973,7 +3973,7 @@ msgstr ""
msgid "pattern" msgid "pattern"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:406 ./cli/app/env.go:327 ./cli/app/remove.go:165 ./cli/app/rollback.go:355 ./cli/app/upgrade.go:464 ./cli/app/volume.go:219 #: ./cli/app/deploy.go:405 ./cli/app/env.go:327 ./cli/app/remove.go:165 ./cli/app/rollback.go:354 ./cli/app/upgrade.go:463 ./cli/app/volume.go:219
msgid "perform action without further prompt" msgid "perform action without further prompt"
msgstr "" msgstr ""
@ -3988,27 +3988,27 @@ msgstr ""
msgid "please fix your synced label for %s and re-run this command" msgid "please fix your synced label for %s and re-run this command"
msgstr "" msgstr ""
#: ./cli/app/rollback.go:267 #: ./cli/app/rollback.go:266
#, c-format #, c-format
msgid "please select a downgrade (version: %s):" msgid "please select a downgrade (version: %s):"
msgstr "" msgstr ""
#: ./cli/app/rollback.go:272 #: ./cli/app/rollback.go:271
#, c-format #, c-format
msgid "please select a downgrade (version: %s, chaos: %s):" msgid "please select a downgrade (version: %s, chaos: %s):"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:312 #: ./cli/app/upgrade.go:311
#, c-format #, c-format
msgid "please select an upgrade (version: %s):" msgid "please select an upgrade (version: %s):"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:317 #: ./cli/app/upgrade.go:316
#, c-format #, c-format
msgid "please select an upgrade (version: %s, chaos: %s):" msgid "please select an upgrade (version: %s, chaos: %s):"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:587 #: ./pkg/upstream/stack/stack.go:576
msgid "polling deployment status" msgid "polling deployment status"
msgstr "" msgstr ""
@ -4094,7 +4094,7 @@ msgstr ""
#. with no spaces in between #. with no spaces in between
#. translators: `abra recipe` aliases. use a comma separated list of aliases #. translators: `abra recipe` aliases. use a comma separated list of aliases
#. with no spaces in between #. with no spaces in between
#: ./cli/app/backup.go:327 ./cli/app/list.go:300 ./cli/app/move.go:350 ./cli/app/run.go:23 ./cli/app/upgrade.go:486 ./cli/catalogue/catalogue.go:302 ./cli/recipe/recipe.go:12 ./cli/recipe/release.go:649 ./cli/recipe/sync.go:272 #: ./cli/app/backup.go:327 ./cli/app/list.go:300 ./cli/app/move.go:350 ./cli/app/run.go:23 ./cli/app/upgrade.go:485 ./cli/catalogue/catalogue.go:302 ./cli/recipe/recipe.go:12 ./cli/recipe/release.go:649 ./cli/recipe/sync.go:272
msgid "r" msgid "r"
msgstr "" msgstr ""
@ -4147,7 +4147,7 @@ msgstr ""
msgid "read v:%s k: %s" msgid "read v:%s k: %s"
msgstr "" msgstr ""
#: ./pkg/ui/deploy.go:86 #: ./pkg/ui/deploy.go:97
#, c-format #, c-format
msgid "reader: %v, " msgid "reader: %v, "
msgstr "" msgstr ""
@ -4210,7 +4210,7 @@ msgstr ""
msgid "release <recipe> [version] [flags]" msgid "release <recipe> [version] [flags]"
msgstr "" msgstr ""
#: ./cli/app/upgrade.go:485 #: ./cli/app/upgrade.go:484
msgid "releasenotes" msgid "releasenotes"
msgstr "" msgstr ""
@ -4376,7 +4376,7 @@ msgstr ""
msgid "restart <domain> [[service] | --all-services] [flags]" msgid "restart <domain> [[service] | --all-services] [flags]"
msgstr "" msgstr ""
#: ./cli/app/restart.go:172 #: ./cli/app/restart.go:171
msgid "restart all services" msgid "restart all services"
msgstr "" msgstr ""
@ -4441,7 +4441,7 @@ msgstr ""
msgid "retrieved versions from local recipe repository" msgid "retrieved versions from local recipe repository"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:468 #: ./pkg/upstream/stack/stack.go:464
#, c-format #, c-format
msgid "retrieving docker auth token: failed create docker cli: %s" msgid "retrieving docker auth token: failed create docker cli: %s"
msgstr "" msgstr ""
@ -4475,7 +4475,7 @@ msgstr ""
msgid "rollback <domain> [version] [flags]" msgid "rollback <domain> [version] [flags]"
msgstr "" msgstr ""
#: ./pkg/ui/deploy.go:336 #: ./pkg/ui/deploy.go:398
msgid "rolled back" msgid "rolled back"
msgstr "" msgstr ""
@ -4514,7 +4514,7 @@ msgstr ""
msgid "run command locally" msgid "run command locally"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:271 ./cli/app/upgrade.go:293 #: ./cli/app/deploy.go:270 ./cli/app/upgrade.go:292
#, c-format #, c-format
msgid "run the following post-deploy commands: %s" msgid "run the following post-deploy commands: %s"
msgstr "" msgstr ""
@ -4599,12 +4599,12 @@ msgstr ""
msgid "secret not found: %s" msgid "secret not found: %s"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:340 #: ./cli/app/deploy.go:339
#, c-format #, c-format
msgid "secret not generated: %s" msgid "secret not generated: %s"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:338 #: ./cli/app/deploy.go:337
#, c-format #, c-format
msgid "secret not inserted (#generate=false): %s" msgid "secret not inserted (#generate=false): %s"
msgstr "" msgstr ""
@ -4651,19 +4651,9 @@ msgstr ""
msgid "server doesn't exist?" msgid "server doesn't exist?"
msgstr "" msgstr ""
#: ./pkg/client/client.go:54
#, c-format
msgid "server missing context, context creation failed: %s"
msgstr ""
#: ./pkg/client/client.go:59
#, c-format
msgid "server missing context, run \"abra server add %s\"?"
msgstr ""
#: ./pkg/client/client.go:48 #: ./pkg/client/client.go:48
#, c-format #, c-format
msgid "server missing, run \"abra server add %s\"?" msgid "server missing context, run \"abra server add %s\"?"
msgstr "" msgstr ""
#: ./cli/server/add.go:148 #: ./cli/server/add.go:148
@ -4738,7 +4728,7 @@ msgstr ""
msgid "severity" msgid "severity"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:438 ./cli/app/rollback.go:379 ./cli/app/upgrade.go:496 #: ./cli/app/deploy.go:437 ./cli/app/rollback.go:378 ./cli/app/upgrade.go:495
msgid "show all configs & images, including unchanged ones" msgid "show all configs & images, including unchanged ones"
msgstr "" msgstr ""
@ -4762,7 +4752,7 @@ msgstr ""
msgid "show debug messages" msgid "show debug messages"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:435 ./cli/app/rollback.go:376 ./cli/app/upgrade.go:493 #: ./cli/app/deploy.go:434 ./cli/app/rollback.go:375 ./cli/app/upgrade.go:492
msgid "show-unchanged" msgid "show-unchanged"
msgstr "" msgstr ""
@ -4806,7 +4796,7 @@ msgstr ""
msgid "skipping as requested, undeploy still in progress 🟠" msgid "skipping as requested, undeploy still in progress 🟠"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:309 #: ./pkg/upstream/stack/stack.go:306
msgid "skipping converge logic checks" msgid "skipping converge logic checks"
msgstr "" msgstr ""
@ -4828,12 +4818,12 @@ msgstr ""
msgid "skipping secret (because it already exists) on %s: %s" msgid "skipping secret (because it already exists) on %s: %s"
msgstr "" msgstr ""
#: ./pkg/app/app.go:697 #: ./pkg/app/app.go:692
#, c-format #, c-format
msgid "skipping version %s write as already exists in %s.env" msgid "skipping version %s write as already exists in %s.env"
msgstr "" msgstr ""
#: ./pkg/app/app.go:691 #: ./pkg/app/app.go:686
#, c-format #, c-format
msgid "skipping writing version %s because dry run" msgid "skipping writing version %s because dry run"
msgstr "" msgstr ""
@ -4900,7 +4890,7 @@ msgstr ""
msgid "status" msgid "status"
msgstr "" msgstr ""
#: ./pkg/ui/deploy.go:88 #: ./pkg/ui/deploy.go:99
#, c-format #, c-format
msgid "status: %s}" msgid "status: %s}"
msgstr "" msgstr ""
@ -4922,7 +4912,7 @@ msgstr ""
msgid "stripped %s to %s for parsing" msgid "stripped %s to %s for parsing"
msgstr "" msgstr ""
#: ./pkg/ui/deploy.go:333 #: ./pkg/ui/deploy.go:395
msgid "succeeded" msgid "succeeded"
msgstr "" msgstr ""
@ -4941,12 +4931,12 @@ msgstr ""
msgid "successfully created %s" msgid "successfully created %s"
msgstr "" msgstr ""
#: ./pkg/client/client.go:120 #: ./pkg/client/client.go:111
#, c-format #, c-format
msgid "swarm mode not enabled on %s?" msgid "swarm mode not enabled on %s?"
msgstr "" msgstr ""
#: ./pkg/client/client.go:123 #: ./pkg/client/client.go:114
msgid "swarm mode not enabled on local server?" msgid "swarm mode not enabled on local server?"
msgstr "" msgstr ""
@ -5022,7 +5012,7 @@ msgstr ""
msgid "timeout label: %s" msgid "timeout label: %s"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/remove.go:29 ./pkg/upstream/stack/stack.go:210 #: ./pkg/upstream/stack/remove.go:29 ./pkg/upstream/stack/stack.go:209
#, c-format #, c-format
msgid "timeout: set to %d second(s)" msgid "timeout: set to %d second(s)"
msgstr "" msgstr ""
@ -5445,6 +5435,11 @@ msgstr ""
msgid "unknown server %s, run \"abra server add %s\"?" msgid "unknown server %s, run \"abra server add %s\"?"
msgstr "" msgstr ""
#: ./pkg/client/client.go:51
#, c-format
msgid "unknown server, run \"abra server add %s\"?"
msgstr ""
#: ./cli/app/cp.go:259 #: ./cli/app/cp.go:259
#, c-format #, c-format
msgid "untar: %s" msgid "untar: %s"
@ -5456,7 +5451,7 @@ msgstr ""
msgid "up" msgid "up"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:477 #: ./pkg/upstream/stack/stack.go:473
#, c-format #, c-format
msgid "updating %s" msgid "updating %s"
msgstr "" msgstr ""
@ -5568,7 +5563,7 @@ msgstr ""
msgid "version" msgid "version"
msgstr "" msgstr ""
#: ./pkg/app/app.go:695 #: ./pkg/app/app.go:690
#, c-format #, c-format
msgid "version %s saved to %s.env" msgid "version %s saved to %s.env"
msgstr "" msgstr ""
@ -5587,10 +5582,6 @@ msgstr ""
msgid "version for abra" msgid "version for abra"
msgstr "" msgstr ""
#: ./pkg/app/app.go:637
msgid "version is unknown, skipping env write"
msgstr ""
#: ./pkg/recipe/recipe.go:130 #: ./pkg/recipe/recipe.go:130
#, c-format #, c-format
msgid "version seems invalid: %s" msgid "version seems invalid: %s"
@ -5601,27 +5592,27 @@ msgstr ""
msgid "version wiped from %s.env" msgid "version wiped from %s.env"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:354 #: ./cli/app/deploy.go:353
#, c-format #, c-format
msgid "version: taking chaos version: %s" msgid "version: taking chaos version: %s"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:380 #: ./cli/app/deploy.go:379
#, c-format #, c-format
msgid "version: taking deployed version: %s" msgid "version: taking deployed version: %s"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:385 #: ./cli/app/deploy.go:384
#, c-format #, c-format
msgid "version: taking new recipe version: %s" msgid "version: taking new recipe version: %s"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:374 #: ./cli/app/deploy.go:373
#, c-format #, c-format
msgid "version: taking version from .env file: %s" msgid "version: taking version from .env file: %s"
msgstr "" msgstr ""
#: ./cli/app/deploy.go:360 #: ./cli/app/deploy.go:359
#, c-format #, c-format
msgid "version: taking version from cli arg: %s" msgid "version: taking version from cli arg: %s"
msgstr "" msgstr ""
@ -5681,22 +5672,22 @@ msgstr ""
msgid "volumes pruned: %d; space reclaimed: %s" msgid "volumes pruned: %d; space reclaimed: %s"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:625 #: ./pkg/upstream/stack/stack.go:614
#, c-format #, c-format
msgid "waitOnServices: error creating log dir: %s" msgid "waitOnServices: error creating log dir: %s"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:630 #: ./pkg/upstream/stack/stack.go:619
#, c-format #, c-format
msgid "waitOnServices: error opening file: %s" msgid "waitOnServices: error opening file: %s"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:596 #: ./pkg/upstream/stack/stack.go:585
#, c-format #, c-format
msgid "waitOnServices: error running TUI: %s" msgid "waitOnServices: error running TUI: %s"
msgstr "" msgstr ""
#: ./pkg/upstream/stack/stack.go:636 #: ./pkg/upstream/stack/stack.go:625
#, c-format #, c-format
msgid "waitOnServices: writeFile: %s" msgid "waitOnServices: writeFile: %s"
msgstr "" msgstr ""
@ -5739,12 +5730,12 @@ msgstr ""
msgid "wire up healthchecks" msgid "wire up healthchecks"
msgstr "" msgstr ""
#: ./pkg/ui/deploy.go:87 #: ./pkg/ui/deploy.go:98
#, c-format #, c-format
msgid "writer: %v, " msgid "writer: %v, "
msgstr "" msgstr ""
#: ./cli/app/deploy.go:278 ./cli/app/new.go:241 ./cli/app/rollback.go:256 ./cli/app/undeploy.go:120 ./cli/app/upgrade.go:301 #: ./cli/app/deploy.go:277 ./cli/app/new.go:241 ./cli/app/rollback.go:255 ./cli/app/undeploy.go:120 ./cli/app/upgrade.go:300
#, c-format #, c-format
msgid "writing recipe version failed: %s" msgid "writing recipe version failed: %s"
msgstr "" msgstr ""
@ -5773,7 +5764,7 @@ msgstr ""
msgid "z" msgid "z"
msgstr "" msgstr ""
#: ./pkg/ui/deploy.go:82 #: ./pkg/ui/deploy.go:93
#, c-format #, c-format
msgid "{decoder: %v, " msgid "{decoder: %v, "
msgstr "" msgstr ""

File diff suppressed because it is too large Load Diff

View File

@ -11,6 +11,7 @@ import (
"coopcloud.tech/abra/pkg/formatter" "coopcloud.tech/abra/pkg/formatter"
"coopcloud.tech/abra/pkg/i18n" "coopcloud.tech/abra/pkg/i18n"
"coopcloud.tech/abra/pkg/logs" "coopcloud.tech/abra/pkg/logs"
"github.com/charmbracelet/bubbles/viewport"
tea "github.com/charmbracelet/bubbletea" tea "github.com/charmbracelet/bubbletea"
"github.com/docker/cli/cli/command/service/progress" "github.com/docker/cli/cli/command/service/progress"
containerTypes "github.com/docker/docker/api/types/container" containerTypes "github.com/docker/docker/api/types/container"
@ -41,6 +42,12 @@ type ServiceMeta struct {
ID string ID string
} }
const (
statusMode = iota
logsMode = iota
errorsMode = iota
)
type Model struct { type Model struct {
appName string appName string
cl *dockerClient.Client cl *dockerClient.Client
@ -49,6 +56,10 @@ type Model struct {
timeout time.Duration timeout time.Duration
width int width int
filters filters.Args filters filters.Args
mode int
logsViewport viewport.Model
logsViewportReady bool
Streams *[]stream Streams *[]stream
Logs *[]string Logs *[]string
@ -236,7 +247,10 @@ func deployTimeout(m Model) tea.Msg {
} }
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
var cmds []tea.Cmd var (
cmd tea.Cmd
cmds []tea.Cmd
)
switch msg := msg.(type) { switch msg := msg.(type) {
case tea.KeyMsg: case tea.KeyMsg:
@ -244,11 +258,25 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
case "ctrl+c", "q": case "ctrl+c", "q":
m.Quit = true m.Quit = true
return m, tea.Quit return m, tea.Quit
case "s":
m.mode = statusMode
case "l":
m.mode = logsMode
case "e":
m.mode = errorsMode
} }
case tea.WindowSizeMsg: case tea.WindowSizeMsg:
m.width = msg.Width m.width = msg.Width
if !m.logsViewportReady {
m.logsViewport = viewport.New(msg.Width, 20)
m.logsViewportReady = true
} else {
m.logsViewport.Width = msg.Width
m.logsViewport.Height = 20
}
case progressCompleteMsg: case progressCompleteMsg:
if msg.failed { if msg.failed {
m.Failed = true m.Failed = true
@ -256,9 +284,9 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
m.count += 1 m.count += 1
if m.complete() { // if m.complete() {
return m, tea.Quit // return m, tea.Quit
} // }
case timeoutMsg: case timeoutMsg:
m.TimedOut = true m.TimedOut = true
@ -318,12 +346,46 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
) )
} }
m.logsViewport, cmd = m.logsViewport.Update(msg)
cmds = append(cmds, cmd)
return m, tea.Batch(cmds...) return m, tea.Batch(cmds...)
} }
func (m Model) View() string { func (m Model) View() string {
body := strings.Builder{} body := strings.Builder{}
body.WriteString("menu: [s]tatus [l]ogs [e]rrors\n")
var res string
switch {
case m.mode == statusMode:
res = statusView(m)
case m.mode == logsMode:
res = logsView(m)
}
return body.String() + res
}
func logsView(m Model) string {
body := strings.Builder{}
m.logsViewport.SetContent(strings.Join(*m.Logs, "\n"))
m.logsViewport.GotoBottom()
body.WriteString(m.logsViewport.View())
return body.String()
}
func errorsView(m Model) string {
body := strings.Builder{}
body.WriteString("ERRORS COMING SOON")
return body.String()
}
func statusView(m Model) string {
body := strings.Builder{}
for _, stream := range *m.Streams { for _, stream := range *m.Streams {
split := strings.Split(stream.Name, "_") split := strings.Split(stream.Name, "_")
short := split[len(split)-1] short := split[len(split)-1]

View File

@ -201,7 +201,6 @@ func RunDeploy(
appName string, appName string,
serverName string, serverName string,
dontWait bool, dontWait bool,
noInput bool,
filters filters.Args, filters filters.Args,
) error { ) error {
log.Info(i18n.G("initialising deployment")) log.Info(i18n.G("initialising deployment"))
@ -227,7 +226,6 @@ func RunDeploy(
appName, appName,
serverName, serverName,
dontWait, dontWait,
noInput,
filters, filters,
) )
} }
@ -250,7 +248,6 @@ func deployCompose(
appName string, appName string,
serverName string, serverName string,
dontWait bool, dontWait bool,
noInput bool,
filters filters.Args, filters filters.Args,
) error { ) error {
namespace := convert.NewNamespace(opts.Namespace) namespace := convert.NewNamespace(opts.Namespace)
@ -314,7 +311,6 @@ func deployCompose(
Services: serviceIDs, Services: serviceIDs,
AppName: appName, AppName: appName,
ServerName: serverName, ServerName: serverName,
NoInput: noInput,
Filters: filters, Filters: filters,
} }
@ -565,7 +561,6 @@ func timestamp() string {
type WaitOpts struct { type WaitOpts struct {
AppName string AppName string
Filters filters.Args Filters filters.Args
NoInput bool
NoLog bool NoLog bool
Quiet bool Quiet bool
ServerName string ServerName string
@ -575,13 +570,7 @@ type WaitOpts struct {
func WaitOnServices(ctx context.Context, cl *dockerClient.Client, opts WaitOpts) error { func WaitOnServices(ctx context.Context, cl *dockerClient.Client, opts WaitOpts) error {
timeout := time.Duration(WaitTimeout) * time.Second timeout := time.Duration(WaitTimeout) * time.Second
model := ui.DeployInitialModel(ctx, cl, opts.Services, opts.AppName, timeout, opts.Filters) model := ui.DeployInitialModel(ctx, cl, opts.Services, opts.AppName, timeout, opts.Filters)
tui := tea.NewProgram(model)
var tui *tea.Program
if opts.NoInput {
tui = tea.NewProgram(model, tea.WithoutRenderer(), tea.WithInput(nil))
} else {
tui = tea.NewProgram(model)
}
if !opts.Quiet { if !opts.Quiet {
log.Info(i18n.G("polling deployment status")) log.Info(i18n.G("polling deployment status"))

View File

@ -1,8 +1,8 @@
#!/usr/bin/env bash #!/usr/bin/env bash
ABRA_VERSION="0.12.0-beta" ABRA_VERSION="0.11.0-beta"
ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/toolshed/abra/releases/tags/$ABRA_VERSION" ABRA_RELEASE_URL="https://git.coopcloud.tech/api/v1/repos/toolshed/abra/releases/tags/$ABRA_VERSION"
RC_VERSION="0.12.0-beta" RC_VERSION="0.11.0-beta"
RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/toolshed/abra/releases/tags/$RC_VERSION" RC_VERSION_URL="https://git.coopcloud.tech/api/v1/repos/toolshed/abra/releases/tags/$RC_VERSION"
for arg in "$@"; do for arg in "$@"; do
@ -14,15 +14,15 @@ done
function show_banner { function show_banner {
echo "" echo ""
echo " ____ ____ _ _ " echo " ____ ____ _ _ "
echo " / ___|___ ___ _ __ / ___| | ___ _ _ __| |" echo " / ___|___ ___ _ __ / ___| | ___ _ _ __| |"
echo " | | / _ \ ___ / _ \| '_ \ | | | |/ _ \| | | |/ _' |" echo " | | / _ \ _____ / _ \| '_ \ | | | |/ _ \| | | |/ _' |"
echo " | |__| (_) |___| (_) | |_) | | |___| | (_) | |_| | (_| |" echo " | |__| (_) |_____| (_) | |_) | | |___| | (_) | |_| | (_| |"
echo " \____\___/ \___/| .__/ \____|_|\___/ \__,_|\__,_|" echo " \____\___/ \___/| .__/ \____|_|\___/ \__,_|\__,_|"
echo " |_|" echo " |_|"
echo "" echo ""
echo "" echo ""
echo " === Public interest infrastructure === " echo " === Public interest infrastructure === "
echo "" echo ""
echo "" echo ""
} }

View File

@ -543,7 +543,7 @@ teardown(){
# bats test_tags=slow # bats test_tags=slow
@test "ignore timeout when not present in env" { @test "ignore timeout when not present in env" {
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks --debug
assert_success assert_success
refute_output --partial "timeout: set to" refute_output --partial "timeout: set to"
} }
@ -554,7 +554,6 @@ teardown(){
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env"
assert_success assert_success
# NOTE(d1}: --debug required
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks --debug run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks --debug
assert_success assert_success
assert_output --partial "timeout: set to 120" assert_output --partial "timeout: set to 120"
@ -580,25 +579,16 @@ teardown(){
} }
# bats test_tags=slow # bats test_tags=slow
@test "re-deploy updates existing env vars" { @test "manually created server without context bails gracefully" {
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input run mkdir -p "$ABRA_DIR/servers/default2"
assert_success assert_success
assert_exists "$ABRA_DIR/servers/default2"
run docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' \ run cp "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" "$ABRA_DIR/servers/default2/$TEST_APP_DOMAIN_2.env"
$(docker ps -f name="$TEST_APP_DOMAIN_$TEST_SERVER" -q)
assert_success assert_success
assert_output --partial "WITH_COMMENT=foo" assert_exists "$ABRA_DIR/servers/default2/$TEST_APP_DOMAIN_2.env"
run sed -i 's/WITH_COMMENT=foo/WITH_COMMENT=bar/g' \ run $ABRA app deploy "$TEST_APP_DOMAIN_2" --no-input --no-converge-checks
"$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" assert_failure
assert_success assert_output --partial "server missing context"
run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --force
assert_success
run docker inspect --format='{{range .Config.Env}}{{println .}}{{end}}' \
$(docker ps -f name="$TEST_APP_DOMAIN_$TEST_SERVER" -q)
assert_success
refute_output --partial "WITH_COMMENT=foo"
assert_output --partial "WITH_COMMENT=bar"
} }

View File

@ -68,13 +68,6 @@ teardown(){
assert_success assert_success
} }
@test "domain shown with https" {
run $ABRA app deploy "$TEST_APP_DOMAIN" \
--no-input --no-converge-checks
assert_success
assert_output --partial "https://$TEST_DOMAIN"
}
# bats test_tags=slow # bats test_tags=slow
@test "show changed config version on re-deploy" { @test "show changed config version on re-deploy" {
run $ABRA app deploy "$TEST_APP_DOMAIN" \ run $ABRA app deploy "$TEST_APP_DOMAIN" \

View File

@ -160,6 +160,23 @@ teardown(){
assert_not_exists "$ABRA_DIR/servers/foo.com" assert_not_exists "$ABRA_DIR/servers/foo.com"
} }
@test "list with status skips unknown servers" {
if [[ ! -d "$ABRA_DIR/servers/foo" ]]; then
run mkdir -p "$ABRA_DIR/servers/foo"
assert_success
assert_exists "$ABRA_DIR/servers/foo"
run cp "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" \
"$ABRA_DIR/servers/foo/$TEST_APP_DOMAIN.env"
assert_success
assert_exists "$ABRA_DIR/servers/foo/$TEST_APP_DOMAIN.env"
fi
run $ABRA app ls --status
assert_success
assert_output --partial "server missing context"
}
# bats test_tags=slow # bats test_tags=slow
@test "list does not fail if missing .env" { @test "list does not fail if missing .env" {
_deploy_app _deploy_app

View File

@ -19,10 +19,6 @@ setup(){
teardown(){ teardown(){
_reset_recipe _reset_recipe
_reset_tags _reset_tags
if [[ -d "$ABRA_DIR/recipes/foobar" ]]; then
run rm -rf "$ABRA_DIR/recipes/foobar"
assert_success
fi
} }
@test "validate recipe argument" { @test "validate recipe argument" {
@ -130,71 +126,3 @@ teardown(){
assert_line --index 0 --partial 'synced label' assert_line --index 0 --partial 'synced label'
refute_line --index 1 --partial 'synced label' refute_line --index 1 --partial 'synced label'
} }
@test "sync with no tags or previous release" {
_remove_tags
run $ABRA recipe upgrade "$TEST_RECIPE" --no-input --patch
assert_success
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" diff
assert_success
assert_output --partial 'image: nginx:1.21.6'
# NOTE(d1): ensure the latest tag is the one we expect
_remove_tags
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag \
-a "0.3.0+1.21.0" -m "fake: 0.3.0+1.21.0"
assert_success
run $ABRA recipe sync "$TEST_RECIPE" --no-input --patch
assert_success
run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" diff
assert_success
assert_output --regexp 'coop-cloud\.\$\{STACK_NAME\}\.version=0\.3\.1\+1\.2.*'
}
@test "sync recipe without input fails with prompt" {
run $ABRA recipe new foobar
assert_success
assert_exists "$ABRA_DIR/recipes/foobar"
run $ABRA recipe sync foobar --no-input --patch
assert_failure
assert_output --partial "input required for initial version"
}
@test "sync new recipe: development release" {
run $ABRA recipe new foobar
assert_success
assert_exists "$ABRA_DIR/recipes/foobar"
run bash -c "echo 0.1.0 | $ABRA recipe sync foobar --patch"
assert_success
assert_output --regexp 'coop-cloud\.\$\{STACK_NAME\}\.version=0\.1\.0\+1\.2.*'
}
@test "sync new recipe: public release" {
run $ABRA recipe new foobar
assert_success
assert_exists "$ABRA_DIR/recipes/foobar"
run bash -c "echo 1.0.0 | $ABRA recipe sync foobar --patch"
assert_success
assert_output --regexp 'coop-cloud\.\$\{STACK_NAME\}\.version=1\.0\.0\+1\.2.*'
}
@test "sync newly created recipe with no version label" {
run $ABRA recipe new foobar
assert_success
assert_exists "$ABRA_DIR/recipes/foobar"
run sed -i 's/- "coop-cloud.${STACK_NAME}.version="/#- "coop-cloud.${STACK_NAME}.version="/g' \
"$ABRA_DIR/recipes/foobar/compose.yml"
assert_success
run bash -c "echo 0.1.0 | $ABRA recipe sync foobar --patch"
assert_failure
assert_output --partial "automagic insertion not supported yet"
}

21
vendor/github.com/charmbracelet/bubbles/LICENSE generated vendored Normal file
View File

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

140
vendor/github.com/charmbracelet/bubbles/key/key.go generated vendored Normal file
View File

@ -0,0 +1,140 @@
// Package key provides some types and functions for generating user-definable
// keymappings useful in Bubble Tea components. There are a few different ways
// you can define a keymapping with this package. Here's one example:
//
// type KeyMap struct {
// Up key.Binding
// Down key.Binding
// }
//
// var DefaultKeyMap = KeyMap{
// Up: key.NewBinding(
// key.WithKeys("k", "up"), // actual keybindings
// key.WithHelp("↑/k", "move up"), // corresponding help text
// ),
// Down: key.NewBinding(
// key.WithKeys("j", "down"),
// key.WithHelp("↓/j", "move down"),
// ),
// }
//
// func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
// switch msg := msg.(type) {
// case tea.KeyMsg:
// switch {
// case key.Matches(msg, DefaultKeyMap.Up):
// // The user pressed up
// case key.Matches(msg, DefaultKeyMap.Down):
// // The user pressed down
// }
// }
//
// // ...
// }
//
// The help information, which is not used in the example above, can be used
// to render help text for keystrokes in your views.
package key
import "fmt"
// Binding describes a set of keybindings and, optionally, their associated
// help text.
type Binding struct {
keys []string
help Help
disabled bool
}
// BindingOpt is an initialization option for a keybinding. It's used as an
// argument to NewBinding.
type BindingOpt func(*Binding)
// NewBinding returns a new keybinding from a set of BindingOpt options.
func NewBinding(opts ...BindingOpt) Binding {
b := &Binding{}
for _, opt := range opts {
opt(b)
}
return *b
}
// WithKeys initializes a keybinding with the given keystrokes.
func WithKeys(keys ...string) BindingOpt {
return func(b *Binding) {
b.keys = keys
}
}
// WithHelp initializes a keybinding with the given help text.
func WithHelp(key, desc string) BindingOpt {
return func(b *Binding) {
b.help = Help{Key: key, Desc: desc}
}
}
// WithDisabled initializes a disabled keybinding.
func WithDisabled() BindingOpt {
return func(b *Binding) {
b.disabled = true
}
}
// SetKeys sets the keys for the keybinding.
func (b *Binding) SetKeys(keys ...string) {
b.keys = keys
}
// Keys returns the keys for the keybinding.
func (b Binding) Keys() []string {
return b.keys
}
// SetHelp sets the help text for the keybinding.
func (b *Binding) SetHelp(key, desc string) {
b.help = Help{Key: key, Desc: desc}
}
// Help returns the Help information for the keybinding.
func (b Binding) Help() Help {
return b.help
}
// Enabled returns whether or not the keybinding is enabled. Disabled
// keybindings won't be activated and won't show up in help. Keybindings are
// enabled by default.
func (b Binding) Enabled() bool {
return !b.disabled && b.keys != nil
}
// SetEnabled enables or disables the keybinding.
func (b *Binding) SetEnabled(v bool) {
b.disabled = !v
}
// Unbind removes the keys and help from this binding, effectively nullifying
// it. This is a step beyond disabling it, since applications can enable
// or disable key bindings based on application state.
func (b *Binding) Unbind() {
b.keys = nil
b.help = Help{}
}
// Help is help information for a given keybinding.
type Help struct {
Key string
Desc string
}
// Matches checks if the given key matches the given bindings.
func Matches[Key fmt.Stringer](k Key, b ...Binding) bool {
keys := k.String()
for _, binding := range b {
for _, v := range binding.keys {
if keys == v && binding.Enabled() {
return true
}
}
}
return false
}

View File

@ -0,0 +1,60 @@
// Package viewport provides a component for rendering a viewport in a Bubble
// Tea.
package viewport
import "github.com/charmbracelet/bubbles/key"
const spacebar = " "
// KeyMap defines the keybindings for the viewport. Note that you don't
// necessary need to use keybindings at all; the viewport can be controlled
// programmatically with methods like Model.LineDown(1). See the GoDocs for
// details.
type KeyMap struct {
PageDown key.Binding
PageUp key.Binding
HalfPageUp key.Binding
HalfPageDown key.Binding
Down key.Binding
Up key.Binding
Left key.Binding
Right key.Binding
}
// DefaultKeyMap returns a set of pager-like default keybindings.
func DefaultKeyMap() KeyMap {
return KeyMap{
PageDown: key.NewBinding(
key.WithKeys("pgdown", spacebar, "f"),
key.WithHelp("f/pgdn", "page down"),
),
PageUp: key.NewBinding(
key.WithKeys("pgup", "b"),
key.WithHelp("b/pgup", "page up"),
),
HalfPageUp: key.NewBinding(
key.WithKeys("u", "ctrl+u"),
key.WithHelp("u", "½ page up"),
),
HalfPageDown: key.NewBinding(
key.WithKeys("d", "ctrl+d"),
key.WithHelp("d", "½ page down"),
),
Up: key.NewBinding(
key.WithKeys("up", "k"),
key.WithHelp("↑/k", "up"),
),
Down: key.NewBinding(
key.WithKeys("down", "j"),
key.WithHelp("↓/j", "down"),
),
Left: key.NewBinding(
key.WithKeys("left", "h"),
key.WithHelp("←/h", "move left"),
),
Right: key.NewBinding(
key.WithKeys("right", "l"),
key.WithHelp("→/l", "move right"),
),
}
}

View File

@ -0,0 +1,544 @@
package viewport
import (
"math"
"strings"
"github.com/charmbracelet/bubbles/key"
tea "github.com/charmbracelet/bubbletea"
"github.com/charmbracelet/lipgloss"
"github.com/charmbracelet/x/ansi"
)
// New returns a new model with the given width and height as well as default
// key mappings.
func New(width, height int) (m Model) {
m.Width = width
m.Height = height
m.setInitialValues()
return m
}
// Model is the Bubble Tea model for this viewport element.
type Model struct {
Width int
Height int
KeyMap KeyMap
// Whether or not to respond to the mouse. The mouse must be enabled in
// Bubble Tea for this to work. For details, see the Bubble Tea docs.
MouseWheelEnabled bool
// The number of lines the mouse wheel will scroll. By default, this is 3.
MouseWheelDelta int
// YOffset is the vertical scroll position.
YOffset int
// xOffset is the horizontal scroll position.
xOffset int
// horizontalStep is the number of columns we move left or right during a
// default horizontal scroll.
horizontalStep int
// YPosition is the position of the viewport in relation to the terminal
// window. It's used in high performance rendering only.
YPosition int
// Style applies a lipgloss style to the viewport. Realistically, it's most
// useful for setting borders, margins and padding.
Style lipgloss.Style
// HighPerformanceRendering bypasses the normal Bubble Tea renderer to
// provide higher performance rendering. Most of the time the normal Bubble
// Tea rendering methods will suffice, but if you're passing content with
// a lot of ANSI escape codes you may see improved rendering in certain
// terminals with this enabled.
//
// This should only be used in program occupying the entire terminal,
// which is usually via the alternate screen buffer.
//
// Deprecated: high performance rendering is now deprecated in Bubble Tea.
HighPerformanceRendering bool
initialized bool
lines []string
longestLineWidth int
}
func (m *Model) setInitialValues() {
m.KeyMap = DefaultKeyMap()
m.MouseWheelEnabled = true
m.MouseWheelDelta = 3
m.initialized = true
}
// Init exists to satisfy the tea.Model interface for composability purposes.
func (m Model) Init() tea.Cmd {
return nil
}
// AtTop returns whether or not the viewport is at the very top position.
func (m Model) AtTop() bool {
return m.YOffset <= 0
}
// AtBottom returns whether or not the viewport is at or past the very bottom
// position.
func (m Model) AtBottom() bool {
return m.YOffset >= m.maxYOffset()
}
// PastBottom returns whether or not the viewport is scrolled beyond the last
// line. This can happen when adjusting the viewport height.
func (m Model) PastBottom() bool {
return m.YOffset > m.maxYOffset()
}
// ScrollPercent returns the amount scrolled as a float between 0 and 1.
func (m Model) ScrollPercent() float64 {
if m.Height >= len(m.lines) {
return 1.0
}
y := float64(m.YOffset)
h := float64(m.Height)
t := float64(len(m.lines))
v := y / (t - h)
return math.Max(0.0, math.Min(1.0, v))
}
// HorizontalScrollPercent returns the amount horizontally scrolled as a float
// between 0 and 1.
func (m Model) HorizontalScrollPercent() float64 {
if m.xOffset >= m.longestLineWidth-m.Width {
return 1.0
}
y := float64(m.xOffset)
h := float64(m.Width)
t := float64(m.longestLineWidth)
v := y / (t - h)
return math.Max(0.0, math.Min(1.0, v))
}
// SetContent set the pager's text content.
func (m *Model) SetContent(s string) {
s = strings.ReplaceAll(s, "\r\n", "\n") // normalize line endings
m.lines = strings.Split(s, "\n")
m.longestLineWidth = findLongestLineWidth(m.lines)
if m.YOffset > len(m.lines)-1 {
m.GotoBottom()
}
}
// maxYOffset returns the maximum possible value of the y-offset based on the
// viewport's content and set height.
func (m Model) maxYOffset() int {
return max(0, len(m.lines)-m.Height+m.Style.GetVerticalFrameSize())
}
// visibleLines returns the lines that should currently be visible in the
// viewport.
func (m Model) visibleLines() (lines []string) {
h := m.Height - m.Style.GetVerticalFrameSize()
w := m.Width - m.Style.GetHorizontalFrameSize()
if len(m.lines) > 0 {
top := max(0, m.YOffset)
bottom := clamp(m.YOffset+h, top, len(m.lines))
lines = m.lines[top:bottom]
}
if (m.xOffset == 0 && m.longestLineWidth <= w) || w == 0 {
return lines
}
cutLines := make([]string, len(lines))
for i := range lines {
cutLines[i] = ansi.Cut(lines[i], m.xOffset, m.xOffset+w)
}
return cutLines
}
// scrollArea returns the scrollable boundaries for high performance rendering.
//
// Deprecated: high performance rendering is deprecated in Bubble Tea.
func (m Model) scrollArea() (top, bottom int) {
top = max(0, m.YPosition)
bottom = max(top, top+m.Height)
if top > 0 && bottom > top {
bottom--
}
return top, bottom
}
// SetYOffset sets the Y offset.
func (m *Model) SetYOffset(n int) {
m.YOffset = clamp(n, 0, m.maxYOffset())
}
// ViewDown moves the view down by the number of lines in the viewport.
// Basically, "page down".
//
// Deprecated: use [Model.PageDown] instead.
func (m *Model) ViewDown() []string {
return m.PageDown()
}
// PageDown moves the view down by the number of lines in the viewport.
func (m *Model) PageDown() []string {
if m.AtBottom() {
return nil
}
return m.ScrollDown(m.Height)
}
// ViewUp moves the view up by one height of the viewport.
// Basically, "page up".
//
// Deprecated: use [Model.PageUp] instead.
func (m *Model) ViewUp() []string {
return m.PageUp()
}
// PageUp moves the view up by one height of the viewport.
func (m *Model) PageUp() []string {
if m.AtTop() {
return nil
}
return m.ScrollUp(m.Height)
}
// HalfViewDown moves the view down by half the height of the viewport.
//
// Deprecated: use [Model.HalfPageDown] instead.
func (m *Model) HalfViewDown() (lines []string) {
return m.HalfPageDown()
}
// HalfPageDown moves the view down by half the height of the viewport.
func (m *Model) HalfPageDown() (lines []string) {
if m.AtBottom() {
return nil
}
return m.ScrollDown(m.Height / 2) //nolint:mnd
}
// HalfViewUp moves the view up by half the height of the viewport.
//
// Deprecated: use [Model.HalfPageUp] instead.
func (m *Model) HalfViewUp() (lines []string) {
return m.HalfPageUp()
}
// HalfPageUp moves the view up by half the height of the viewport.
func (m *Model) HalfPageUp() (lines []string) {
if m.AtTop() {
return nil
}
return m.ScrollUp(m.Height / 2) //nolint:mnd
}
// LineDown moves the view down by the given number of lines.
//
// Deprecated: use [Model.ScrollDown] instead.
func (m *Model) LineDown(n int) (lines []string) {
return m.ScrollDown(n)
}
// ScrollDown moves the view down by the given number of lines.
func (m *Model) ScrollDown(n int) (lines []string) {
if m.AtBottom() || n == 0 || len(m.lines) == 0 {
return nil
}
// Make sure the number of lines by which we're going to scroll isn't
// greater than the number of lines we actually have left before we reach
// the bottom.
m.SetYOffset(m.YOffset + n)
// Gather lines to send off for performance scrolling.
//
// XXX: high performance rendering is deprecated in Bubble Tea.
bottom := clamp(m.YOffset+m.Height, 0, len(m.lines))
top := clamp(m.YOffset+m.Height-n, 0, bottom)
return m.lines[top:bottom]
}
// LineUp moves the view down by the given number of lines. Returns the new
// lines to show.
//
// Deprecated: use [Model.ScrollUp] instead.
func (m *Model) LineUp(n int) (lines []string) {
return m.ScrollUp(n)
}
// ScrollUp moves the view down by the given number of lines. Returns the new
// lines to show.
func (m *Model) ScrollUp(n int) (lines []string) {
if m.AtTop() || n == 0 || len(m.lines) == 0 {
return nil
}
// Make sure the number of lines by which we're going to scroll isn't
// greater than the number of lines we are from the top.
m.SetYOffset(m.YOffset - n)
// Gather lines to send off for performance scrolling.
//
// XXX: high performance rendering is deprecated in Bubble Tea.
top := max(0, m.YOffset)
bottom := clamp(m.YOffset+n, 0, m.maxYOffset())
return m.lines[top:bottom]
}
// SetHorizontalStep sets the default amount of columns to scroll left or right
// with the default viewport key map.
//
// If set to 0 or less, horizontal scrolling is disabled.
//
// On v1, horizontal scrolling is disabled by default.
func (m *Model) SetHorizontalStep(n int) {
m.horizontalStep = max(n, 0)
}
// SetXOffset sets the X offset.
func (m *Model) SetXOffset(n int) {
m.xOffset = clamp(n, 0, m.longestLineWidth-m.Width)
}
// ScrollLeft moves the viewport to the left by the given number of columns.
func (m *Model) ScrollLeft(n int) {
m.SetXOffset(m.xOffset - n)
}
// ScrollRight moves viewport to the right by the given number of columns.
func (m *Model) ScrollRight(n int) {
m.SetXOffset(m.xOffset + n)
}
// TotalLineCount returns the total number of lines (both hidden and visible) within the viewport.
func (m Model) TotalLineCount() int {
return len(m.lines)
}
// VisibleLineCount returns the number of the visible lines within the viewport.
func (m Model) VisibleLineCount() int {
return len(m.visibleLines())
}
// GotoTop sets the viewport to the top position.
func (m *Model) GotoTop() (lines []string) {
if m.AtTop() {
return nil
}
m.SetYOffset(0)
return m.visibleLines()
}
// GotoBottom sets the viewport to the bottom position.
func (m *Model) GotoBottom() (lines []string) {
m.SetYOffset(m.maxYOffset())
return m.visibleLines()
}
// Sync tells the renderer where the viewport will be located and requests
// a render of the current state of the viewport. It should be called for the
// first render and after a window resize.
//
// For high performance rendering only.
//
// Deprecated: high performance rendering is deprecated in Bubble Tea.
func Sync(m Model) tea.Cmd {
if len(m.lines) == 0 {
return nil
}
top, bottom := m.scrollArea()
return tea.SyncScrollArea(m.visibleLines(), top, bottom)
}
// ViewDown is a high performance command that moves the viewport up by a given
// number of lines. Use Model.ViewDown to get the lines that should be rendered.
// For example:
//
// lines := model.ViewDown(1)
// cmd := ViewDown(m, lines)
//
// Deprecated: high performance rendering is deprecated in Bubble Tea.
func ViewDown(m Model, lines []string) tea.Cmd {
if len(lines) == 0 {
return nil
}
top, bottom := m.scrollArea()
// XXX: high performance rendering is deprecated in Bubble Tea. In a v2 we
// won't need to return a command here.
return tea.ScrollDown(lines, top, bottom)
}
// ViewUp is a high performance command the moves the viewport down by a given
// number of lines height. Use Model.ViewUp to get the lines that should be
// rendered.
//
// Deprecated: high performance rendering is deprecated in Bubble Tea.
func ViewUp(m Model, lines []string) tea.Cmd {
if len(lines) == 0 {
return nil
}
top, bottom := m.scrollArea()
// XXX: high performance rendering is deprecated in Bubble Tea. In a v2 we
// won't need to return a command here.
return tea.ScrollUp(lines, top, bottom)
}
// Update handles standard message-based viewport updates.
func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) {
var cmd tea.Cmd
m, cmd = m.updateAsModel(msg)
return m, cmd
}
// Author's note: this method has been broken out to make it easier to
// potentially transition Update to satisfy tea.Model.
func (m Model) updateAsModel(msg tea.Msg) (Model, tea.Cmd) {
if !m.initialized {
m.setInitialValues()
}
var cmd tea.Cmd
switch msg := msg.(type) {
case tea.KeyMsg:
switch {
case key.Matches(msg, m.KeyMap.PageDown):
lines := m.PageDown()
if m.HighPerformanceRendering {
cmd = ViewDown(m, lines)
}
case key.Matches(msg, m.KeyMap.PageUp):
lines := m.PageUp()
if m.HighPerformanceRendering {
cmd = ViewUp(m, lines)
}
case key.Matches(msg, m.KeyMap.HalfPageDown):
lines := m.HalfPageDown()
if m.HighPerformanceRendering {
cmd = ViewDown(m, lines)
}
case key.Matches(msg, m.KeyMap.HalfPageUp):
lines := m.HalfPageUp()
if m.HighPerformanceRendering {
cmd = ViewUp(m, lines)
}
case key.Matches(msg, m.KeyMap.Down):
lines := m.ScrollDown(1)
if m.HighPerformanceRendering {
cmd = ViewDown(m, lines)
}
case key.Matches(msg, m.KeyMap.Up):
lines := m.ScrollUp(1)
if m.HighPerformanceRendering {
cmd = ViewUp(m, lines)
}
case key.Matches(msg, m.KeyMap.Left):
m.ScrollLeft(m.horizontalStep)
case key.Matches(msg, m.KeyMap.Right):
m.ScrollRight(m.horizontalStep)
}
case tea.MouseMsg:
if !m.MouseWheelEnabled || msg.Action != tea.MouseActionPress {
break
}
switch msg.Button { //nolint:exhaustive
case tea.MouseButtonWheelUp:
if msg.Shift {
// Note that not every terminal emulator sends the shift event for mouse actions by default (looking at you Konsole)
m.ScrollLeft(m.horizontalStep)
} else {
lines := m.ScrollUp(m.MouseWheelDelta)
if m.HighPerformanceRendering {
cmd = ViewUp(m, lines)
}
}
case tea.MouseButtonWheelDown:
if msg.Shift {
m.ScrollRight(m.horizontalStep)
} else {
lines := m.ScrollDown(m.MouseWheelDelta)
if m.HighPerformanceRendering {
cmd = ViewDown(m, lines)
}
}
// Note that not every terminal emulator sends the horizontal wheel events by default (looking at you Konsole)
case tea.MouseButtonWheelLeft:
m.ScrollLeft(m.horizontalStep)
case tea.MouseButtonWheelRight:
m.ScrollRight(m.horizontalStep)
}
}
return m, cmd
}
// View renders the viewport into a string.
func (m Model) View() string {
if m.HighPerformanceRendering {
// Just send newlines since we're going to be rendering the actual
// content separately. We still need to send something that equals the
// height of this view so that the Bubble Tea standard renderer can
// position anything below this view properly.
return strings.Repeat("\n", max(0, m.Height-1))
}
w, h := m.Width, m.Height
if sw := m.Style.GetWidth(); sw != 0 {
w = min(w, sw)
}
if sh := m.Style.GetHeight(); sh != 0 {
h = min(h, sh)
}
contentWidth := w - m.Style.GetHorizontalFrameSize()
contentHeight := h - m.Style.GetVerticalFrameSize()
contents := lipgloss.NewStyle().
Width(contentWidth). // pad to width.
Height(contentHeight). // pad to height.
MaxHeight(contentHeight). // truncate height if taller.
MaxWidth(contentWidth). // truncate width if wider.
Render(strings.Join(m.visibleLines(), "\n"))
return m.Style.
UnsetWidth().UnsetHeight(). // Style size already applied in contents.
Render(contents)
}
func clamp(v, low, high int) int {
if high < low {
low, high = high, low
}
return min(high, max(low, v))
}
func findLongestLineWidth(lines []string) int {
w := 0
for _, l := range lines {
if ww := ansi.StringWidth(l); ww > w {
w = ww
}
}
return w
}

6
vendor/modules.txt vendored
View File

@ -65,6 +65,10 @@ github.com/cenkalti/backoff/v5
# github.com/cespare/xxhash/v2 v2.3.0 # github.com/cespare/xxhash/v2 v2.3.0
## explicit; go 1.11 ## explicit; go 1.11
github.com/cespare/xxhash/v2 github.com/cespare/xxhash/v2
# github.com/charmbracelet/bubbles v0.21.0
## explicit; go 1.23.0
github.com/charmbracelet/bubbles/key
github.com/charmbracelet/bubbles/viewport
# github.com/charmbracelet/bubbletea v1.3.10 # github.com/charmbracelet/bubbletea v1.3.10
## explicit; go 1.24.0 ## explicit; go 1.24.0
github.com/charmbracelet/bubbletea github.com/charmbracelet/bubbletea
@ -85,8 +89,6 @@ github.com/charmbracelet/x/ansi/parser
# github.com/charmbracelet/x/cellbuf v0.0.13 # github.com/charmbracelet/x/cellbuf v0.0.13
## explicit; go 1.18 ## explicit; go 1.18
github.com/charmbracelet/x/cellbuf github.com/charmbracelet/x/cellbuf
# github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91
## explicit; go 1.19
# github.com/charmbracelet/x/term v0.2.1 # github.com/charmbracelet/x/term v0.2.1
## explicit; go 1.18 ## explicit; go 1.18
github.com/charmbracelet/x/term github.com/charmbracelet/x/term