Merge pull request #116 from foomo/feature/default-build-args
Some checks failed
Test Branch / test (push) Has been cancelled

feat: default build args
This commit is contained in:
Kevin Franklin Kim 2025-10-10 14:29:09 +02:00 committed by GitHub
commit 470cb96905
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
51 changed files with 572 additions and 249 deletions

View File

@ -9,7 +9,6 @@ on:
permissions: permissions:
contents: read contents: read
pull-requests: write
concurrency: concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
@ -28,9 +27,13 @@ jobs:
check-latest: true check-latest: true
go-version-file: go.mod go-version-file: go.mod
- uses: golangci/golangci-lint-action@v8 - name: Setup golangci-lint cache
uses: actions/cache@v4
with: with:
install-mode: none path: ~/.cache/golangci-lint
key: ${{ runner.os }}-golangci-lint-${{ hashFiles('**/go.mod') }}
restore-keys: |
${{ runner.os }}-golangci-lint-
- name: Run lint - name: Run lint
run: make lint run: make lint

View File

@ -1,122 +1,59 @@
# https://golangci-lint.run/usage/configuration/
# yaml-language-server: $schema=https://golangci-lint.run/jsonschema/golangci.jsonschema.json
version: "2" version: "2"
run: run:
build-tags: [safe] build-tags: [ safe ]
modules-download-mode: readonly modules-download-mode: readonly
linters: linters:
default: none default: all
enable: disable:
## Default linters # Project specific linters
- errcheck # errcheck is a program for checking for unchecked errors in Go code. These unchecked errors can be critical bugs in some cases [fast: false, auto-fix: false] - forbidigo
- govet # (vet, vetshadow) Vet examines Go source code and reports suspicious constructs. It is roughly the same as 'go vet' and uses its passes. [fast: false, auto-fix: false] - recvcheck
- ineffassign # Detects when assignments to existing variables are not used [fast: true, auto-fix: false] - paralleltest
- staticcheck # (megacheck) It's a set of rules from staticcheck. It's not the same thing as the staticcheck binary. The author of staticcheck doesn't support or approve the use of staticcheck as a library inside golangci-lint. [fast: false, auto-fix: false] # Discouraged linters
- unused # (megacheck) Checks Go code for unused constants, variables, functions and types [fast: false, auto-fix: false] - noinlineerr # Disallows inline error handling (`if err := ...; err != nil {`).
- embeddedstructfieldcheck # Embedded types should be at the top of the field list of a struct, and there must be an empty line separating embedded fields from regular fields. [fast]
## Recommended linters - cyclop # checks function and package cyclomatic complexity [fast: false, auto-fix: false]
- asasalint # check for pass []any as any in variadic func(...any) [fast: false, auto-fix: false] - depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false]
- asciicheck # checks that all code identifiers does not have non-ASCII symbols in the name [fast: true, auto-fix: false] - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
- bidichk # Checks for dangerous unicode character sequences [fast: true, auto-fix: false] - dupl # Tool for code clone detection [fast: true, auto-fix: false]
- bodyclose # checks whether HTTP response body is closed successfully [fast: false, auto-fix: false] - dupword # checks for duplicate words in the source code [fast: true, auto-fix: false]
- canonicalheader # checks whether net/http.Header uses canonical header [fast: false, auto-fix: false] - dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
- containedctx # containedctx is a linter that detects struct contained context.Context field [fast: false, auto-fix: false] - err113 # Go linter to check the errors handling expressions [fast: false, auto-fix: false]
- contextcheck # check whether the function uses a non-inherited context [fast: false, auto-fix: false] - exhaustruct # Checks if all structure fields are initialized [fast: false, auto-fix: false]
- copyloopvar # (go >= 1.22) copyloopvar is a linter detects places where loop variables are copied [fast: true, auto-fix: false] - funlen # Tool for detection of long functions [fast: true, auto-fix: false]
- decorder # check declaration order and count of types, constants, variables and functions [fast: true, auto-fix: false] - ginkgolinter # enforces standards of using ginkgo and gomega [fast: false, auto-fix: false]
- durationcheck # check for two durations multiplied together [fast: false, auto-fix: false] - gochecknoglobals # Check that no global variables exist. [fast: false, auto-fix: false]
- errchkjson # Checks types passed to the json encoding functions. Reports unsupported types and reports occations, where the check for the returned error can be omitted. [fast: false, auto-fix: false] - gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false]
- errname # Checks that sentinel errors are prefixed with the `Err` and error types are suffixed with the `Error`. [fast: false, auto-fix: false] - gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false]
- errorlint # errorlint is a linter for that can be used to find code that will cause problems with the error wrapping scheme introduced in Go 1.13. [fast: false, auto-fix: false] - gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false]
- exhaustive # check exhaustiveness of enum switch statements [fast: false, auto-fix: false] - godot # Check if comments end in a period [fast: true, auto-fix: true]
- exptostd # Detects functions from golang.org/x/exp/ that can be replaced by std functions. [auto-fix] - godox # Tool for detection of comment keywords [fast: true, auto-fix: false]
- fatcontext # detects nested contexts in loops and function literals [fast: false, auto-fix: false] - interfacebloat # A linter that checks the number of methods inside an interface. [fast: true, auto-fix: false]
#- forbidigo # Forbids identifiers [fast: false, auto-fix: false]
- forcetypeassert # finds forced type assertions [fast: true, auto-fix: false]
- gocheckcompilerdirectives # Checks that go compiler directive comments (//go:) are valid. [fast: true, auto-fix: false]
- gochecksumtype # Run exhaustiveness checks on Go "sum types" [fast: false, auto-fix: false]
- goconst # Finds repeated strings that could be replaced by a constant [fast: true, auto-fix: false]
- gocritic # Provides diagnostics that check for bugs, performance and style issues. [fast: false, auto-fix: true]
- goheader # Checks is file header matches to pattern [fast: true, auto-fix: true]
- gomoddirectives # Manage the use of 'replace', 'retract', and 'excludes' directives in go.mod. [fast: true, auto-fix: false]
- gomodguard # Allow and block list linter for direct Go module dependencies. This is different from depguard where there are different block types for example version constraints and module recommendations. [fast: true, auto-fix: false]
- goprintffuncname # Checks that printf-like functions are named with `f` at the end. [fast: true, auto-fix: false]
- gosec # (gas) Inspects source code for security problems [fast: false, auto-fix: false]
- gosmopolitan # Report certain i18n/l10n anti-patterns in your Go codebase [fast: false, auto-fix: false]
- grouper # Analyze expression groups. [fast: true, auto-fix: false]
- iface # Detect the incorrect use of interfaces, helping developers avoid interface pollution. [fast: false, auto-fix: false]
- importas # Enforces consistent import aliases [fast: false, auto-fix: false]
- inamedparam # reports interfaces with unnamed method parameters [fast: true, auto-fix: false]
- intrange # (go >= 1.22) intrange is a linter to find places where for loops could make use of an integer range. [fast: true, auto-fix: false] - intrange # (go >= 1.22) intrange is a linter to find places where for loops could make use of an integer range. [fast: true, auto-fix: false]
- loggercheck # (logrlint) Checks key value pairs for common logger libraries (kitlog,klog,logr,zap). [fast: false, auto-fix: false] - ireturn # Accept Interfaces, Return Concrete Types [fast: false, auto-fix: false]
- makezero # Finds slice declarations with non-zero initial length [fast: false, auto-fix: false] - lll # Reports long lines [fast: true, auto-fix: false]
- mirror # reports wrong mirror patterns of bytes/strings usage [fast: false, auto-fix: true] - maintidx # maintidx measures the maintainability index of each function. [fast: true, auto-fix: false]
- misspell # Finds commonly misspelled English words [fast: true, auto-fix: true] - nestif # Reports deeply nested if statements [fast: true, auto-fix: false]
- musttag # enforce field tags in (un)marshaled structs [fast: false, auto-fix: false] - nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity [fast: true, auto-fix: false]
- nakedret # Checks that functions with naked returns are not longer than a maximum size (can be zero). [fast: true, auto-fix: false] - mnd # An analyzer to detect magic numbers. [fast: true, auto-fix: false]
- nilerr # Finds the code that returns nil even if it checks that the error is not nil. [fast: false, auto-fix: false] - perfsprint # Checks that fmt.Sprintf can be replaced with a faster alternative. [fast: false, auto-fix: false]
- nilnesserr # Reports constructs that checks for err != nil, but returns a different nil value error. - prealloc # Finds slice declarations that could potentially be pre-allocated [fast: true, auto-fix: false]
- nilnil # Checks that there is no simultaneous return of `nil` error and an invalid value. [fast: false, auto-fix: false] - protogetter # Reports direct reads from proto message fields when getters should be used [fast: false, auto-fix: true]
- noctx # Finds sending http request without context.Context [fast: false, auto-fix: false] - sloglint # ensure consistent code style when using log/slog [fast: false, auto-fix: false]
- nolintlint # Reports ill-formed or insufficient nolint directives [fast: true, auto-fix: true] - tagalign # check that struct tags are well aligned [fast: true, auto-fix: true]
- nonamedreturns # Reports all named returns [fast: false, auto-fix: false] - tagliatelle # Checks the struct tags. [fast: true, auto-fix: false]
- nosprintfhostport # Checks for misuse of Sprintf to construct a host with port in a URL. [fast: true, auto-fix: false] - unparam # Reports unused function parameters [fast: false, auto-fix: false]
#- paralleltest # Detects missing usage of t.Parallel() method in your Go test [fast: false, auto-fix: false] - varnamelen # checks that the length of a variable's name matches its scope [fast: false, auto-fix: false]
- predeclared # find code that shadows one of Go's predeclared identifiers [fast: true, auto-fix: false] - wrapcheck # Checks that errors returned from external packages are wrapped [fast: false, auto-fix: false]
- promlinter # Check Prometheus metrics naming via promlint [fast: true, auto-fix: false] - zerologlint # Detects the wrong usage of `zerolog` that a user forgets to dispatch with `Send` or `Msg` [fast: false, auto-fix: false]
- reassign # Checks that package variables are not reassigned [fast: false, auto-fix: false] # Deprected linters
#- recvcheck # checks for receiver type consistency [fast: false, auto-fix: false] - wsl
- revive # Fast, configurable, extensible, flexible, and beautiful linter for Go. Drop-in replacement of golint. [fast: false, auto-fix: false]
- rowserrcheck # checks whether Rows.Err of rows is checked successfully [fast: false, auto-fix: false]
- spancheck # Checks for mistakes with OpenTelemetry/Census spans. [fast: false, auto-fix: false]
- sqlclosecheck # Checks that sql.Rows, sql.Stmt, sqlx.NamedStmt, pgx.Query are closed. [fast: false, auto-fix: false]
- testableexamples # linter checks if examples are testable (have an expected output) [fast: true, auto-fix: false]
- testifylint # Checks usage of github.com/stretchr/testify. [fast: false, auto-fix: false]
- testpackage # linter that makes you use a separate _test package [fast: true, auto-fix: false]
- thelper # thelper detects tests helpers which is not start with t.Helper() method. [fast: false, auto-fix: false]
- tparallel # tparallel detects inappropriate usage of t.Parallel() method in your Go test codes. [fast: false, auto-fix: false]
- unconvert # Remove unnecessary type conversions [fast: false, auto-fix: false]
- usestdlibvars # A linter that detect the possibility to use variables/constants from the Go standard library. [fast: true, auto-fix: false]
- usetesting # Reports uses of functions with replacement inside the testing package. [auto-fix]
- wastedassign # Finds wasted assignment statements [fast: false, auto-fix: false]
- whitespace # Whitespace is a linter that checks for unnecessary newlines at the start and end of functions, if, for, etc. [fast: true, auto-fix: true]
## Discouraged linters # https://golangci-lint.run/docs/linters/
#- cyclop # checks function and package cyclomatic complexity [fast: false, auto-fix: false]
#- depguard # Go linter that checks if package imports are in a list of acceptable packages [fast: true, auto-fix: false]
#- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
#- dupl # Tool for code clone detection [fast: true, auto-fix: false]
#- dupword # checks for duplicate words in the source code [fast: true, auto-fix: false]
#- dogsled # Checks assignments with too many blank identifiers (e.g. x, _, _, _, := f()) [fast: true, auto-fix: false]
#- err113 # Go linter to check the errors handling expressions [fast: false, auto-fix: false]
#- exhaustruct # Checks if all structure fields are initialized [fast: false, auto-fix: false]
#- funlen # Tool for detection of long functions [fast: true, auto-fix: false]
#- ginkgolinter # enforces standards of using ginkgo and gomega [fast: false, auto-fix: false]
#- gochecknoglobals # Check that no global variables exist. [fast: false, auto-fix: false]
#- gochecknoinits # Checks that no init functions are present in Go code [fast: true, auto-fix: false]
#- gocognit # Computes and checks the cognitive complexity of functions [fast: true, auto-fix: false]
#- gocyclo # Computes and checks the cyclomatic complexity of functions [fast: true, auto-fix: false]
#- godot # Check if comments end in a period [fast: true, auto-fix: true]
#- godox # Tool for detection of comment keywords [fast: true, auto-fix: false]
#- interfacebloat # A linter that checks the number of methods inside an interface. [fast: true, auto-fix: false]
#- intrange # (go >= 1.22) intrange is a linter to find places where for loops could make use of an integer range. [fast: true, auto-fix: false]
#- ireturn # Accept Interfaces, Return Concrete Types [fast: false, auto-fix: false]
#- lll # Reports long lines [fast: true, auto-fix: false]
#- maintidx # maintidx measures the maintainability index of each function. [fast: true, auto-fix: false]
#- nestif # Reports deeply nested if statements [fast: true, auto-fix: false]
#- nlreturn # nlreturn checks for a new line before return and branch statements to increase code clarity [fast: true, auto-fix: false]
#- mnd # An analyzer to detect magic numbers. [fast: true, auto-fix: false]
#- perfsprint # Checks that fmt.Sprintf can be replaced with a faster alternative. [fast: false, auto-fix: false]
#- prealloc # Finds slice declarations that could potentially be pre-allocated [fast: true, auto-fix: false]
#- protogetter # Reports direct reads from proto message fields when getters should be used [fast: false, auto-fix: true]
#- sloglint # ensure consistent code style when using log/slog [fast: false, auto-fix: false]
#- tagalign # check that struct tags are well aligned [fast: true, auto-fix: true]
#- tagliatelle # Checks the struct tags. [fast: true, auto-fix: false]
#- unparam # Reports unused function parameters [fast: false, auto-fix: false]
#- varnamelen # checks that the length of a variable's name matches its scope [fast: false, auto-fix: false]
#- wrapcheck # Checks that errors returned from external packages are wrapped [fast: false, auto-fix: false]
#- wsl # add or remove empty lines [fast: true, auto-fix: false]
#- zerologlint # Detects the wrong usage of `zerolog` that a user forgets to dispatch with `Send` or `Msg` [fast: false, auto-fix: false]
settings: settings:
exhaustive: exhaustive:
default-signifies-exhaustive: true default-signifies-exhaustive: true
@ -125,75 +62,19 @@ linters:
- '!!.+' - '!!.+'
gocritic: gocritic:
disabled-checks: disabled-checks:
- assignOp
- ifElseChain - ifElseChain
- commentFormatting
gomoddirectives: gomoddirectives:
replace-local: true
replace-allow-list: replace-allow-list:
- github.com/miracl/conflate - github.com/miracl/conflate
gosec: gosec:
excludes: severity: medium
- G204
confidence: medium confidence: medium
importas:
no-unaliased: true
misspell:
mode: restricted
predeclared:
ignore:
- new
- error
revive: revive:
enable-all-rules: true
rules: rules:
- name: line-length-limit
disabled: true
- name: cognitive-complexity
disabled: true
- name: unused-parameter - name: unused-parameter
disabled: true disabled: true
- name: add-constant
disabled: true
- name: cyclomatic
disabled: true
- name: function-length
disabled: true
- name: function-result-limit
disabled: true
- name: flag-parameter
disabled: true
- name: unused-receiver
disabled: true
- name: argument-limit
disabled: true
- name: max-control-nesting
disabled: true
- name: comment-spacings
disabled: true
- name: struct-tag
arguments:
- json,inline
- yaml,squash
- name: unhandled-error
arguments:
- fmt.Printf
- fmt.Println
- name: deep-exit
disabled: true
- name: empty-block
disabled: true
- name: nested-structs
disabled: true
- name: unhandled-error
disabled: true
- name: indent-error-flow
disabled: true
- name: var-naming
disabled: true
- name: enforce-switch-style
disabled: true
testifylint:
disable:
- float-compare
exclusions: exclusions:
generated: lax generated: lax
presets: presets:
@ -202,12 +83,14 @@ linters:
- legacy - legacy
- std-error-handling - std-error-handling
rules: rules:
- linters: - path: _test\.go
- asasalint linters:
path: (.+)_test\.go - forbidigo
- unused
- gosec
paths: paths:
- bin - bin$
- tmp - tmp$
- third_party$ - third_party$
- builtin$ - builtin$
- examples$ - examples$
@ -215,11 +98,3 @@ formatters:
enable: enable:
- gofmt - gofmt
- goimports - goimports
exclusions:
generated: lax
paths:
- bin
- tmp
- third_party$
- builtin$
- examples$

View File

@ -1,5 +1,5 @@
[tools] [tools]
# https://github.com/golangci/golangci-lint/releases # https://github.com/golangci/golangci-lint/releases
golangci-lint = "2.4.0" golangci-lint = "2.5.0"
# https://github.com/go-courier/husky/releases # https://github.com/go-courier/husky/releases
"github:go-courier/husky" = "1.8.1" "github:go-courier/husky" = "1.8.1"

View File

@ -1,6 +1,15 @@
.DEFAULT_GOAL:=help .DEFAULT_GOAL:=help
-include .makerc -include .makerc
# --- Config -----------------------------------------------------------------
GOMODS=$(shell find . -type f -name go.mod)
# Newline hack for error output
define br
endef
# --- Targets ----------------------------------------------------------------- # --- Targets -----------------------------------------------------------------
# This allows us to accept extra arguments # This allows us to accept extra arguments
@ -9,64 +18,77 @@
.PHONY: .mise .PHONY: .mise
# Install dependencies # Install dependencies
.mise: msg := $(br)$(br)Please ensure you have 'mise' installed and activated!$(br)$(br)$$ brew update$(br)$$ brew install mise$(br)$(br)See the documentation: https://mise.jdx.dev/getting-started.html$(br)$(br)
.mise: .mise:
@command -v mise >/dev/null 2>&1 || { echo >&2 "Error: 'mise' is not installed or not in PATH."; exit 1; } ifeq (, $(shell command -v mise))
@mise install -q $(error ${msg})
endif
@mise install
.PHONY: .husky
# Configure git hooks for husky
.husky:
@git config core.hooksPath .husky
### Tasks ### Tasks
.PHONY: check .PHONY: check
## Run tests and linters ## Run lint & tests
check: tidy lint test check: tidy lint test
.PHONY: doc .PHONY: tidy
## Run tests ## Run go mod tidy
doc: tidy:
@open "http://localhost:6060/pkg/github.com/foomo/squadron/" @echo "〉go mod tidy"
@godoc -http=localhost:6060 -play @go mod tidy
.PHONY: test
## Run tests
test:
@# see https://github.com/pterm/pterm/issues/482
@GO_TEST_TAGS=-skip go test -tags=safe -coverprofile=coverage.out
@#GO_TEST_TAGS=-skip go test -tags=safe -coverprofile=coverage.out -race
.PHONY: lint .PHONY: lint
## Run linter ## Run linter
lint: lint:
@echo "〉golangci-lint run"
@golangci-lint run @golangci-lint run
.PHONY: lint.fix .PHONY: lint.fix
## Fix lint violations ## Fix lint violations
lint.fix: lint.fix:
@echo "〉golangci-lint run fix"
@golangci-lint run --fix @golangci-lint run --fix
.PHONY: tidy .PHONY: test
## Run go mod tidy ## Run tests
tidy: test:
@go mod tidy @echo "〉go test"
@# see https://github.com/pterm/pterm/issues/482
@GO_TEST_TAGS=-skip go test -tags=safe -coverprofile=coverage.out
@#GO_TEST_TAGS=-skip go test -tags=safe -coverprofile=coverage.out -race
.PHONY: outdated .PHONY: outdated
## Show outdated direct dependencies ## Show outdated direct dependencies
outdated: outdated:
@echo "〉go mod outdated"
@go list -u -m -json all | go-mod-outdated -update -direct @go list -u -m -json all | go-mod-outdated -update -direct
.PHONY: install .PHONY: install
## Install binary ## Install binary
install: install:
@echo "installing ${GOPATH}/bin/squadron" @echo "installing ${GOPATH}/bin/squadron"
@go build -tags=safe -o ${GOPATH}/bin/squadron cmd/main.go @go build -tags=safe -o ${GOPATH}/bin/squadron cmd/main.go
.PHONY: build .PHONY: build
## Build binary ## Build binary
build: build:
@mkdir -p bin @mkdir -p bin
@echo "building bin/squadron" @echo "building bin/squadron"
@go build -tags=safe -o bin/squadron cmd/main.go @go build -tags=safe -o bin/squadron cmd/main.go
### Utils ### Utils
.PHONY: docs
## Open go docs
docs:
@echo "〉starting go docs"
@go doc -http
.PHONY: help .PHONY: help
## Show help text ## Show help text
help: help:

View File

@ -50,6 +50,7 @@ func NewBake(c *viper.Viper) *cobra.Command {
_ = x.BindPFlag("push", flags.Lookup("push")) _ = x.BindPFlag("push", flags.Lookup("push"))
cmd.Flags().Int("parallel", 1, "run command in parallel") cmd.Flags().Int("parallel", 1, "run command in parallel")
_ = x.BindPFlag("parallel", flags.Lookup("parallel")) _ = x.BindPFlag("parallel", flags.Lookup("parallel"))
flags.StringArray("bake-args", nil, "additional docker bake args") flags.StringArray("bake-args", nil, "additional docker bake args")

View File

@ -50,6 +50,7 @@ func NewBuild(c *viper.Viper) *cobra.Command {
_ = x.BindPFlag("push", flags.Lookup("push")) _ = x.BindPFlag("push", flags.Lookup("push"))
cmd.Flags().Int("parallel", 1, "run command in parallel") cmd.Flags().Int("parallel", 1, "run command in parallel")
_ = x.BindPFlag("parallel", flags.Lookup("parallel")) _ = x.BindPFlag("parallel", flags.Lookup("parallel"))
flags.StringArray("build-args", nil, "additional docker buildx build args") flags.StringArray("build-args", nil, "additional docker buildx build args")

View File

@ -64,6 +64,7 @@ PowerShell:
case "powershell": case "powershell":
return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) return cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout)
} }
return nil return nil
}, },
} }

View File

@ -26,6 +26,7 @@ func NewConfig(c *viper.Viper) *cobra.Command {
if err := sq.MergeConfigFiles(cmd.Context()); err != nil { if err := sq.MergeConfigFiles(cmd.Context()); err != nil {
return errors.Wrap(err, "failed to merge config files") return errors.Wrap(err, "failed to merge config files")
} }
pterm.Debug.Println(strings.Join(append([]string{"provided files"}, files...), "\n└ ")) pterm.Debug.Println(strings.Join(append([]string{"provided files"}, files...), "\n└ "))
squadronName, unitNames := parseSquadronAndUnitNames(args) squadronName, unitNames := parseSquadronAndUnitNames(args)
@ -43,6 +44,7 @@ func NewConfig(c *viper.Viper) *cobra.Command {
if !x.GetBool("raw") { if !x.GetBool("raw") {
out = util.Highlight(out) out = util.Highlight(out)
} }
pterm.Println(out) pterm.Println(out)
return nil return nil

View File

@ -46,6 +46,7 @@ func NewDiff(c *viper.Viper) *cobra.Command {
if !x.GetBool("raw") { if !x.GetBool("raw") {
out = util.Highlight(out) out = util.Highlight(out)
} }
pterm.Println(out) pterm.Println(out)
return nil return nil

View File

@ -38,17 +38,21 @@ func NewList(c *viper.Viper) *cobra.Command {
// List squadrons // List squadrons
_ = sq.Config().Squadrons.Iterate(cmd.Context(), func(ctx context.Context, key string, value config.Map[*config.Unit]) error { _ = sq.Config().Squadrons.Iterate(cmd.Context(), func(ctx context.Context, key string, value config.Map[*config.Unit]) error {
list = append(list, pterm.LeveledListItem{Level: 0, Text: key}) list = append(list, pterm.LeveledListItem{Level: 0, Text: key})
return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error { return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error {
list = append(list, pterm.LeveledListItem{Level: 1, Text: k}) list = append(list, pterm.LeveledListItem{Level: 1, Text: k})
if x.GetBool("with-tags") && len(v.Tags) > 0 { if x.GetBool("with-tags") && len(v.Tags) > 0 {
list = append(list, pterm.LeveledListItem{Level: 2, Text: "🏷️: " + v.Tags.SortedString()}) list = append(list, pterm.LeveledListItem{Level: 2, Text: "🏷️: " + v.Tags.SortedString()})
} }
if x.GetBool("with-charts") && len(v.Chart.String()) > 0 { if x.GetBool("with-charts") && len(v.Chart.String()) > 0 {
list = append(list, pterm.LeveledListItem{Level: 2, Text: "📑: " + v.Chart.String()}) list = append(list, pterm.LeveledListItem{Level: 2, Text: "📑: " + v.Chart.String()})
} }
if x.GetBool("with-priority") && len(v.Chart.String()) > 0 { if x.GetBool("with-priority") && len(v.Chart.String()) > 0 {
list = append(list, pterm.LeveledListItem{Level: 2, Text: fmt.Sprintf("☝️: %d", v.Priority)}) list = append(list, pterm.LeveledListItem{Level: 2, Text: fmt.Sprintf("☝️: %d", v.Priority)})
} }
if x.GetBool("with-bakes") && len(v.Bakes) > 0 { if x.GetBool("with-bakes") && len(v.Bakes) > 0 {
for name, build := range v.Bakes { for name, build := range v.Bakes {
list = append(list, pterm.LeveledListItem{Level: 2, Text: "📦: " + name}) list = append(list, pterm.LeveledListItem{Level: 2, Text: "📦: " + name})
@ -57,15 +61,18 @@ func NewList(c *viper.Viper) *cobra.Command {
} }
} }
} }
if x.GetBool("with-builds") && len(v.Builds) > 0 { if x.GetBool("with-builds") && len(v.Builds) > 0 {
for name, build := range v.Builds { for name, build := range v.Builds {
list = append(list, pterm.LeveledListItem{Level: 2, Text: "📦: " + name}) list = append(list, pterm.LeveledListItem{Level: 2, Text: "📦: " + name})
list = append(list, pterm.LeveledListItem{Level: 3, Text: build.Image + ":" + build.Tag}) list = append(list, pterm.LeveledListItem{Level: 3, Text: build.Image + ":" + build.Tag})
for _, dependency := range build.Dependencies { for _, dependency := range build.Dependencies {
list = append(list, pterm.LeveledListItem{Level: 3, Text: "🗃️: " + dependency}) list = append(list, pterm.LeveledListItem{Level: 3, Text: "🗃️: " + dependency})
} }
} }
} }
return nil return nil
}) })
}) })
@ -73,6 +80,7 @@ func NewList(c *viper.Viper) *cobra.Command {
if len(list) > 0 { if len(list) > 0 {
root := putils.TreeFromLeveledList(list) root := putils.TreeFromLeveledList(list)
root.Text = "Squadron" root.Text = "Squadron"
return pterm.DefaultTree.WithRoot(root).Render() return pterm.DefaultTree.WithRoot(root).Render()
} }

View File

@ -29,9 +29,10 @@ func NewPostRenderer(c *viper.Viper) *cobra.Command {
return err return err
} }
c := exec.CommandContext(cmd.Context(), "kustomize", "build", args[0]) c := exec.CommandContext(cmd.Context(), "kustomize", "build", args[0]) //nolint:gosec
c.Stdout = os.Stdout c.Stdout = os.Stdout
c.Stderr = os.Stderr c.Stderr = os.Stderr
return c.Run() return c.Run()
}, },
} }

View File

@ -51,15 +51,18 @@ func NewRoot() *cobra.Command {
if viper.GetBool("debug") { if viper.GetBool("debug") {
pterm.EnableDebugMessages() pterm.EnableDebugMessages()
} }
if cmd.Name() == "help" || cmd.Name() == "init" || cmd.Name() == "version" { if cmd.Name() == "help" || cmd.Name() == "init" || cmd.Name() == "version" {
return nil return nil
} }
return util.ValidatePath(".", &cwd) return util.ValidatePath(".", &cwd)
}, },
} }
flags := root.PersistentFlags() flags := root.PersistentFlags()
flags.BoolP("debug", "d", false, "show all output") flags.BoolP("debug", "d", false, "show all output")
_ = viper.BindPFlag("debug", root.PersistentFlags().Lookup("debug")) _ = viper.BindPFlag("debug", root.PersistentFlags().Lookup("debug"))
flags.StringSliceP("file", "f", []string{"squadron.yaml"}, "specify alternative squadron files") flags.StringSliceP("file", "f", []string{"squadron.yaml"}, "specify alternative squadron files")
@ -70,6 +73,7 @@ func NewRoot() *cobra.Command {
func NewViper(root *cobra.Command) *viper.Viper { func NewViper(root *cobra.Command) *viper.Viper {
c := viper.New() c := viper.New()
_ = c.BindPFlag("file", root.PersistentFlags().Lookup("file")) _ = c.BindPFlag("file", root.PersistentFlags().Lookup("file"))
return c return c
} }
@ -80,22 +84,27 @@ func Execute() {
if say, cerr := cowsay.Say(msg, cowsay.BallonWidth(80)); cerr == nil { if say, cerr := cowsay.Say(msg, cowsay.BallonWidth(80)); cerr == nil {
msg = say msg = say
} }
return msg return msg
} }
code := 0 code := 0
defer func() { defer func() {
if r := recover(); r != nil { if r := recover(); r != nil {
l.Error(say("It's time to panic")) l.Error(say("It's time to panic"))
l.Error(fmt.Sprintf("%v", r)) l.Error(fmt.Sprintf("%v", r))
l.Error(string(debug.Stack())) l.Error(string(debug.Stack()))
code = 1 code = 1
} }
os.Exit(code) os.Exit(code)
}() }()
if err := root.Execute(); err != nil { if err := root.Execute(); err != nil {
l.Error(util.SprintError(err)) l.Error(util.SprintError(err))
code = 1 code = 1
} }
} }
@ -109,6 +118,7 @@ func parseExtraArgs(args []string) (out []string, extraArgs []string) { //nolint
return nil, args return nil, args
} }
} }
return args, nil return args, nil
} }
@ -116,11 +126,14 @@ func parseSquadronAndUnitNames(args []string) (squadron string, units []string)
if len(args) == 0 { if len(args) == 0 {
return "", nil return "", nil
} }
if len(args) > 0 { if len(args) > 0 {
squadron = args[0] squadron = args[0]
} }
if len(args) > 1 { if len(args) > 1 {
units = args[1:] units = args[1:]
} }
return squadron, units return squadron, units
} }

View File

@ -37,15 +37,18 @@ func NewSchema(c *viper.Viper) *cobra.Command {
if output := x.GetString("output"); output != "" { if output := x.GetString("output"); output != "" {
pterm.Info.Printfln("Writing JSON schema to %s", output) pterm.Info.Printfln("Writing JSON schema to %s", output)
if err := os.WriteFile(output, []byte(out), 0600); err != nil { if err := os.WriteFile(output, []byte(out), 0600); err != nil {
return errors.Wrap(err, "failed to write schema") return errors.Wrap(err, "failed to write schema")
} }
return nil return nil
} }
if !x.GetBool("raw") { if !x.GetBool("raw") {
out = util.Highlight(out) out = util.Highlight(out)
} }
pterm.Println(out) pterm.Println(out)
return nil return nil

View File

@ -47,6 +47,7 @@ func NewTemplate(c *viper.Viper) *cobra.Command {
if !x.GetBool("raw") { if !x.GetBool("raw") {
out = util.Highlight(out) out = util.Highlight(out)
} }
pterm.Println(out) pterm.Println(out)
return nil return nil

View File

@ -62,23 +62,28 @@ func NewUp(c *viper.Viper) *cobra.Command {
Squadron: version, Squadron: version,
User: "unknown", User: "unknown",
} }
if wd, err := os.Getwd(); err == nil { if wd, err := os.Getwd(); err == nil {
if value := os.Getenv("GIT_DIR"); value != "" { if value := os.Getenv("GIT_DIR"); value != "" {
wd = value wd = value
} }
if repo, err := git.PlainOpen(wd); err == nil { if repo, err := git.PlainOpen(wd); err == nil {
if c, err := repo.Config(); err == nil { if c, err := repo.Config(); err == nil {
status.User = c.User.Name status.User = c.User.Name
} }
if ref, err := repo.Head(); err == nil { if ref, err := repo.Head(); err == nil {
status.Branch = ref.Name().Short() status.Branch = ref.Name().Short()
status.Commit = ref.Hash().String() status.Commit = ref.Hash().String()
if tags, err := repo.Tags(); err == nil { if tags, err := repo.Tags(); err == nil {
_ = tags.ForEach(func(r *plumbing.Reference) error { _ = tags.ForEach(func(r *plumbing.Reference) error {
if r.Hash() == ref.Hash() { if r.Hash() == ref.Hash() {
status.Branch = r.Name().Short() status.Branch = r.Name().Short()
return errors.New("found tag") return errors.New("found tag")
} }
return nil return nil
}) })
} }

View File

@ -16,5 +16,6 @@ func NewVersion(c *viper.Viper) *cobra.Command {
pterm.Println(version) pterm.Println(version)
}, },
} }
return cmd return cmd
} }

13
go.mod
View File

@ -13,7 +13,7 @@ require (
github.com/alecthomas/chroma v0.10.0 github.com/alecthomas/chroma v0.10.0
github.com/foomo/go v0.0.3 github.com/foomo/go v0.0.3
github.com/genelet/determined v1.12.3 github.com/genelet/determined v1.12.3
github.com/go-git/go-git/v5 v5.16.2 github.com/go-git/go-git/v5 v5.16.3
github.com/invopop/jsonschema v0.13.0 github.com/invopop/jsonschema v0.13.0
github.com/miracl/conflate v1.3.4 github.com/miracl/conflate v1.3.4
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
@ -44,9 +44,10 @@ require (
github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/bmatcuk/doublestar v1.3.4 // indirect github.com/bmatcuk/doublestar v1.3.4 // indirect
github.com/buger/jsonparser v1.1.1 // indirect github.com/buger/jsonparser v1.1.1 // 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
github.com/containerd/console v1.0.5 // indirect github.com/containerd/console v1.0.5 // 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.2-0.20180830191138-d8f796af33cc // indirect github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dlclark/regexp2 v1.11.5 // indirect github.com/dlclark/regexp2 v1.11.5 // indirect
github.com/emirpasic/gods v1.18.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect
@ -73,7 +74,7 @@ require (
github.com/klauspost/cpuid/v2 v2.3.0 // indirect github.com/klauspost/cpuid/v2 v2.3.0 // indirect
github.com/lithammer/fuzzysearch v1.1.8 // indirect github.com/lithammer/fuzzysearch v1.1.8 // indirect
github.com/mailru/easyjson v0.9.1 // indirect github.com/mailru/easyjson v0.9.1 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect
@ -85,10 +86,10 @@ require (
github.com/pjbgf/sha1cd v0.5.0 // indirect github.com/pjbgf/sha1cd v0.5.0 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect github.com/rivo/uniseg v0.4.7 // indirect
github.com/sagikazarmark/locafero v0.11.0 // indirect github.com/sagikazarmark/locafero v0.12.0 // indirect
github.com/sergi/go-diff v1.4.0 // indirect github.com/sergi/go-diff v1.4.0 // indirect
github.com/shopspring/decimal v1.4.0 // indirect github.com/shopspring/decimal v1.4.0 // indirect
github.com/skeema/knownhosts v1.3.1 // indirect github.com/skeema/knownhosts v1.3.2 // indirect
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 // indirect
github.com/spf13/afero v1.15.0 // indirect github.com/spf13/afero v1.15.0 // indirect
github.com/spf13/cast v1.10.0 // indirect github.com/spf13/cast v1.10.0 // indirect
@ -110,7 +111,7 @@ require (
golang.org/x/crypto v0.42.0 // indirect golang.org/x/crypto v0.42.0 // indirect
golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect golang.org/x/exp v0.0.0-20250408133849-7e4ce0ab07d0 // indirect
golang.org/x/mod v0.27.0 // indirect golang.org/x/mod v0.27.0 // indirect
golang.org/x/net v0.44.0 // indirect golang.org/x/net v0.45.0 // indirect
golang.org/x/sys v0.36.0 // indirect golang.org/x/sys v0.36.0 // indirect
golang.org/x/term v0.35.0 // indirect golang.org/x/term v0.35.0 // indirect
golang.org/x/text v0.29.0 // indirect golang.org/x/text v0.29.0 // indirect

14
go.sum
View File

@ -58,6 +58,8 @@ github.com/bmatcuk/doublestar v1.3.4 h1:gPypJ5xD31uhX6Tf54sDPUOBXTqKH4c9aPY66CyQ
github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE= github.com/bmatcuk/doublestar v1.3.4/go.mod h1:wiQtGV+rzVYxB7WIlirSN++5HPtPlXEo9MEoZQC/PmE=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
github.com/clipperhouse/uax29/v2 v2.2.0 h1:ChwIKnQN3kcZteTXMgb1wztSgaU+ZemkgWdohwgs8tY=
github.com/clipperhouse/uax29/v2 v2.2.0/go.mod h1:EFJ2TJMRUaplDxHKj1qAEhCtQPW2tJSwu5BF98AuoVM=
github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0=
github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs=
github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U=
@ -66,6 +68,8 @@ github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6
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/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s=
github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/cyphar/filepath-securejoin v0.5.0 h1:hIAhkRBMQ8nIeuVwcAoymp7MY4oherZdAxD+m0u9zaw=
github.com/cyphar/filepath-securejoin v0.5.0/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
@ -99,6 +103,8 @@ github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMj
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.16.2 h1:fT6ZIOjE5iEnkzKyxTHK1W4HGAsPhqEqiSAssSO77hM=
github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8= github.com/go-git/go-git/v5 v5.16.2/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-git/go-git/v5 v5.16.3 h1:Z8BtvxZ09bYm/yYNgPKCzgWtaRqDTgIKRgIRHBfU6Z8=
github.com/go-git/go-git/v5 v5.16.3/go.mod h1:4Ge4alE/5gPs30F2H1esi2gPd69R0C39lolkucHBOp8=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
@ -161,6 +167,8 @@ github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m
github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw=
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
@ -207,6 +215,8 @@ github.com/runz0rd/conflate v1.2.2-0.20210920145208-fa48576ef06d/go.mod h1:F85f+
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc= github.com/sagikazarmark/locafero v0.11.0 h1:1iurJgmM9G3PA/I+wWYIOw/5SyBtxapeHDcg+AAIFXc=
github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik= github.com/sagikazarmark/locafero v0.11.0/go.mod h1:nVIGvgyzw595SUSUE6tvCp3YYTeHs15MvlmU87WwIik=
github.com/sagikazarmark/locafero v0.12.0 h1:/NQhBAkUb4+fH1jivKHWusDYFjMOOKU88eegjfxfHb4=
github.com/sagikazarmark/locafero v0.12.0/go.mod h1:sZh36u/YSZ918v0Io+U9ogLYQJ9tLLBmM4eneO6WwsI=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw= github.com/sergi/go-diff v1.4.0 h1:n/SP9D5ad1fORl+llWyN+D6qoUETXNZARKjyY2/KVCw=
github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4= github.com/sergi/go-diff v1.4.0/go.mod h1:A0bzQcvG0E7Rwjx0REVgAGH58e96+X0MeOfepqsbeW4=
@ -215,6 +225,8 @@ github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+D
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8= github.com/skeema/knownhosts v1.3.1 h1:X2osQ+RAjK76shCbvhHHHVl3ZlgDm8apHEHFqRjnBY8=
github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY= github.com/skeema/knownhosts v1.3.1/go.mod h1:r7KTdC8l4uxWRyK2TpQZ/1o5HaSzh06ePQNxPwTcfiY=
github.com/skeema/knownhosts v1.3.2 h1:EDL9mgf4NzwMXCTfaxSD/o/a5fxDw/xL9nkU28JjdBg=
github.com/skeema/knownhosts v1.3.2/go.mod h1:bEg3iQAuw+jyiw+484wwFJoKSLwcfd7fqRy+N0QTiow=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8 h1:+jumHNA0Wrelhe64i8F6HNlS8pkoyMv5sreGx2Ry5Rw=
github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U= github.com/sourcegraph/conc v0.3.1-0.20240121214520-5f936abd7ae8/go.mod h1:3n1Cwaq1E1/1lhQhtRK2ts/ZwZEhjcQeJQ1RuC6Q/8U=
github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I= github.com/spf13/afero v1.15.0 h1:b/YBCLWAJdFWJTN9cLhiXXcD7mzKn9Dm86dNnfyQw1I=
@ -303,6 +315,8 @@ golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I= golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/net v0.45.0 h1:RLBg5JKixCy82FtLJpeNlVM0nrSqpCRYzVU1n8kj0tM=
golang.org/x/net v0.45.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=

View File

@ -12,6 +12,11 @@ type PTermSlogHandler struct {
attrs []slog.Attr attrs []slog.Attr
} }
// NewPTermSlogHandler returns a new logging handler that can be intrgrated with log/slog.
func NewPTermSlogHandler() *PTermSlogHandler {
return &PTermSlogHandler{}
}
// Enabled returns true if the given level is enabled. // Enabled returns true if the given level is enabled.
func (s *PTermSlogHandler) Enabled(ctx context.Context, level slog.Level) bool { func (s *PTermSlogHandler) Enabled(ctx context.Context, level slog.Level) bool {
switch level { switch level {
@ -70,6 +75,7 @@ func (s *PTermSlogHandler) Handle(ctx context.Context, record slog.Record) error
func (s *PTermSlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler { func (s *PTermSlogHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
newS := *s newS := *s
newS.attrs = attrs newS.attrs = attrs
return &newS return &newS
} }
@ -78,8 +84,3 @@ func (s *PTermSlogHandler) WithGroup(name string) slog.Handler {
// Grouping is not yet supported by pterm. // Grouping is not yet supported by pterm.
return s return s
} }
// NewPTermSlogHandler returns a new logging handler that can be intrgrated with log/slog.
func NewPTermSlogHandler() *PTermSlogHandler {
return &PTermSlogHandler{}
}

View File

@ -89,12 +89,14 @@ func (b *Build) Build(ctx context.Context, squadron, unit string, args []string)
cleanArgs = append(cleanArgs, strings.Split(value, " ")...) cleanArgs = append(cleanArgs, strings.Split(value, " ")...)
} }
} }
argOverride := func(name string, vs string, args []string) (string, string) { argOverride := func(name string, vs string, args []string) (string, string) {
if slices.ContainsFunc(args, func(s string) bool { if slices.ContainsFunc(args, func(s string) bool {
return strings.HasPrefix(s, name) return strings.HasPrefix(s, name)
}) { }) {
return "", "" return "", ""
} }
return name, vs return name, vs
} }
boolArgOverride := func(name string, vs bool, args []string) (string, bool) { boolArgOverride := func(name string, vs bool, args []string) (string, bool) {
@ -103,10 +105,12 @@ func (b *Build) Build(ctx context.Context, squadron, unit string, args []string)
}) { }) {
return "", false return "", false
} }
return name, vs return name, vs
} }
pterm.Debug.Printfln("running docker build for %q", b.Context) pterm.Debug.Printfln("running docker build for %q", b.Context)
return util.NewDockerCommand().Build(b.Context). return util.NewDockerCommand().Build(b.Context).
TemplateData(map[string]string{"image": b.Image, "tag": b.Tag}). TemplateData(map[string]string{"image": b.Image, "tag": b.Tag}).
ListArg("--add-host", b.AddHost). ListArg("--add-host", b.AddHost).
@ -114,7 +118,7 @@ func (b *Build) Build(ctx context.Context, squadron, unit string, args []string)
ListArg("--allow", b.Allow). ListArg("--allow", b.Allow).
ListArg("--attest", b.Attest). ListArg("--attest", b.Attest).
ListArg("--build-arg", b.BuildArg). ListArg("--build-arg", b.BuildArg).
ListArg("--build-contet", b.BuildContext). ListArg("--build-context", b.BuildContext).
Arg(argOverride("--builder", b.Builder, args)). Arg(argOverride("--builder", b.Builder, args)).
Arg(argOverride("--cache-from", b.CacheFrom, args)). Arg(argOverride("--cache-from", b.CacheFrom, args)).
Arg(argOverride("--cache-to", b.CacheTo, args)). Arg(argOverride("--cache-to", b.CacheTo, args)).
@ -152,7 +156,9 @@ func (b *Build) UnmarshalYAML(value *yaml.Node) error {
if err := value.Decode(&vString); err != nil { if err := value.Decode(&vString); err != nil {
return err return err
} }
b.Context = vString b.Context = vString
return nil return nil
default: default:
return fmt.Errorf("unsupported node tag type for %T: %q", b, value.Tag) return fmt.Errorf("unsupported node tag type for %T: %q", b, value.Tag)

View File

@ -63,6 +63,7 @@ func (d *Chart) UnmarshalYAML(value *yaml.Node) error {
if _, err := os.Stat(path.Join(schemaPath, "values.schema.json")); err == nil { if _, err := os.Stat(path.Join(schemaPath, "values.schema.json")); err == nil {
d.Schema = path.Join(schemaPath, "values.schema.json") d.Schema = path.Join(schemaPath, "values.schema.json")
} }
return nil return nil
default: default:
return fmt.Errorf("unsupported node tag type for %T: %q", d, value.Tag) return fmt.Errorf("unsupported node tag type for %T: %q", d, value.Tag)
@ -75,12 +76,15 @@ func (d *Chart) String() string {
func loadChart(name string) (*Chart, error) { func loadChart(name string) (*Chart, error) {
c := Chart{} c := Chart{}
file, err := os.ReadFile(name) file, err := os.ReadFile(name)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error while opening file") return nil, errors.Wrap(err, "error while opening file")
} }
if err := yaml.Unmarshal(file, &c); err != nil { if err := yaml.Unmarshal(file, &c); err != nil {
return nil, errors.Wrap(err, "error while unmarshalling template file") return nil, errors.Wrap(err, "error while unmarshalling template file")
} }
return &c, nil return &c, nil
} }

View File

@ -26,6 +26,7 @@ func (Config) JSONSchemaProperty(prop string) any {
if prop == "squadron" { if prop == "squadron" {
return map[string]map[string]*Unit{} return map[string]map[string]*Unit{}
} }
return nil return nil
} }
@ -40,15 +41,19 @@ func (c *Config) BuildDependencies(ctx context.Context) map[string]Build {
if !ok { if !ok {
return errors.Errorf("missing build dependency `%s`", dependency) return errors.Errorf("missing build dependency `%s`", dependency)
} }
ret[dependency] = b ret[dependency] = b
} }
} }
return nil return nil
}) })
}) })
if len(ret) > 0 { if len(ret) > 0 {
return ret return ret
} }
return nil return nil
} }

View File

@ -18,10 +18,12 @@ func (m Map[T]) Trim() {
if val.Kind() == reflect.Ptr { if val.Kind() == reflect.Ptr {
val = val.Elem() val = val.Elem()
} }
if !val.IsValid() { if !val.IsValid() {
delete(m, key) delete(m, key)
continue continue
} }
if val.IsZero() { if val.IsZero() {
delete(m, key) delete(m, key)
continue continue
@ -42,11 +44,14 @@ func (m Map[T]) Keys() []string {
if reflect.ValueOf(m).IsZero() { if reflect.ValueOf(m).IsZero() {
return nil return nil
} }
ret := make([]string, 0, len(m)) ret := make([]string, 0, len(m))
for key := range m { for key := range m {
ret = append(ret, key) ret = append(ret, key)
} }
sort.Strings(ret) sort.Strings(ret)
return ret return ret
} }
@ -55,11 +60,14 @@ func (m Map[T]) Values() []T {
if len(m) == 0 { if len(m) == 0 {
return nil return nil
} }
keys := m.Keys() keys := m.Keys()
ret := make([]T, 0, len(keys)) ret := make([]T, 0, len(keys))
for i, key := range keys { for i, key := range keys {
ret[i] = m[key] ret[i] = m[key]
} }
return ret return ret
} }
@ -67,17 +75,20 @@ func (m Map[T]) Filter(keys ...string) error {
if len(keys) == 0 { if len(keys) == 0 {
return nil return nil
} }
validKeys := m.Keys() validKeys := m.Keys()
for _, key := range keys { for _, key := range keys {
if !slices.Contains(validKeys, key) { if !slices.Contains(validKeys, key) {
return errors.Errorf("key not found: `%s`", key) return errors.Errorf("key not found: `%s`", key)
} }
} }
for key := range m { for key := range m {
if !slices.Contains(keys, key) { if !slices.Contains(keys, key) {
delete(m, key) delete(m, key)
} }
} }
return nil return nil
} }
@ -87,6 +98,7 @@ func (m Map[T]) FilterFn(handler func(key string, value T) bool) error {
delete(m, key) delete(m, key)
} }
} }
return nil return nil
} }
@ -94,13 +106,16 @@ func (m Map[T]) Iterate(ctx context.Context, handler func(ctx context.Context, k
if len(m) == 0 { if len(m) == 0 {
return nil return nil
} }
for _, key := range m.Keys() { for _, key := range m.Keys() {
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
return err return err
} }
if err := handler(ctx, key, m[key]); err != nil { if err := handler(ctx, key, m[key]); err != nil {
return err return err
} }
} }
return nil return nil
} }

View File

@ -20,11 +20,13 @@ func (t Tags) Strings() []string {
for i, tag := range t { for i, tag := range t {
ret[i] = tag.String() ret[i] = tag.String()
} }
return ret return ret
} }
func (t Tags) SortedStrings() []string { func (t Tags) SortedStrings() []string {
ret := t.Strings() ret := t.Strings()
slices.Sort(ret) slices.Sort(ret)
return ret return ret
} }

View File

@ -48,6 +48,7 @@ func (u *Unit) UnmarshalYAML(value *yaml.Node) error {
if err := value.Decode((*wrapper)(u)); err != nil { if err := value.Decode((*wrapper)(u)); err != nil {
return err return err
} }
if u.Extends != "" { if u.Extends != "" {
// render filename // render filename
filename, err := template.ExecuteFileTemplate(context.Background(), u.Extends, nil, true) filename, err := template.ExecuteFileTemplate(context.Background(), u.Extends, nil, true)
@ -65,6 +66,7 @@ func (u *Unit) UnmarshalYAML(value *yaml.Node) error {
if err := yaml.Unmarshal(defaults, &m); err != nil { if err := yaml.Unmarshal(defaults, &m); err != nil {
return errors.Wrap(err, "failed to unmarshal defaults") return errors.Wrap(err, "failed to unmarshal defaults")
} }
if err := mergo.Merge(&m, u.Values, mergo.WithAppendSlice, mergo.WithOverride, mergo.WithSliceDeepCopy); err != nil { if err := mergo.Merge(&m, u.Values, mergo.WithAppendSlice, mergo.WithOverride, mergo.WithSliceDeepCopy); err != nil {
return err return err
} }
@ -72,6 +74,7 @@ func (u *Unit) UnmarshalYAML(value *yaml.Node) error {
u.Extends = "" u.Extends = ""
u.Values = m u.Values = m
} }
return nil return nil
} }
@ -81,6 +84,7 @@ func (Unit) JSONSchemaProperty(prop string) any {
if prop == "chart" { if prop == "chart" {
return x return x
} }
return nil return nil
} }
@ -89,11 +93,13 @@ func (u *Unit) ValuesYAML(global map[string]any) ([]byte, error) {
if values == nil { if values == nil {
values = map[string]any{} values = map[string]any{}
} }
if global != nil { if global != nil {
if _, ok := values["global"]; !ok { if _, ok := values["global"]; !ok {
values["global"] = global values["global"] = global
} }
} }
return yamlv2.Marshal(values) return yamlv2.Marshal(values)
} }
@ -102,7 +108,9 @@ func (u *Unit) BakeNames() []string {
for name := range u.Bakes { for name := range u.Bakes {
ret = append(ret, name) ret = append(ret, name)
} }
sort.Strings(ret) sort.Strings(ret)
return ret return ret
} }
@ -111,12 +119,15 @@ func (u *Unit) BuildNames() []string {
for name := range u.Builds { for name := range u.Builds {
ret = append(ret, name) ret = append(ret, name)
} }
sort.Strings(ret) sort.Strings(ret)
return ret return ret
} }
func (u *Unit) Template(ctx context.Context, name, squadron, unit, namespace string, global map[string]any, helmArgs []string) ([]byte, error) { func (u *Unit) Template(ctx context.Context, name, squadron, unit, namespace string, global map[string]any, helmArgs []string) ([]byte, error) {
var ret bytes.Buffer var ret bytes.Buffer
valueBytes, err := u.ValuesYAML(global) valueBytes, err := u.ValuesYAML(global)
if err != nil { if err != nil {
return nil, err return nil, err
@ -138,13 +149,16 @@ func (u *Unit) Template(ctx context.Context, name, squadron, unit, namespace str
cmd.Args(path.Clean(strings.TrimPrefix(u.Chart.Repository, "file://"))) cmd.Args(path.Clean(strings.TrimPrefix(u.Chart.Repository, "file://")))
} else { } else {
cmd.Args(u.Chart.Name) cmd.Args(u.Chart.Name)
if u.Chart.Repository != "" { if u.Chart.Repository != "" {
cmd.Args("--repo", u.Chart.Repository) cmd.Args("--repo", u.Chart.Repository)
} }
if u.Chart.Version != "" { if u.Chart.Version != "" {
cmd.Args("--version", u.Chart.Version) cmd.Args("--version", u.Chart.Version)
} }
} }
if out, err := cmd.Run(ctx); err != nil { if out, err := cmd.Run(ctx); err != nil {
return nil, errors.Wrap(err, out) return nil, errors.Wrap(err, out)
} }
@ -161,5 +175,6 @@ func (u *Unit) PostRendererArgs() []string {
"--post-renderer-args", u.Kustomize, "--post-renderer-args", u.Kustomize,
) )
} }
return ret return ret
} }

View File

@ -27,17 +27,21 @@ func (d *Dependency) UnmarshalYAML(value *yaml.Node) error {
if err := value.Decode(&vString); err != nil { if err := value.Decode(&vString); err != nil {
return err return err
} }
vBytes, err := template.ExecuteFileTemplate(context.Background(), vString, nil, true) vBytes, err := template.ExecuteFileTemplate(context.Background(), vString, nil, true)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to render chart string") return errors.Wrap(err, "failed to render chart string")
} }
localChart, err := loadChart(path.Join(string(vBytes), chartFile)) localChart, err := loadChart(path.Join(string(vBytes), chartFile))
if err != nil { if err != nil {
return errors.New("failed to load local chart: " + vString) return errors.New("failed to load local chart: " + vString)
} }
d.Name = localChart.Name d.Name = localChart.Name
d.Repository = fmt.Sprintf("file://%v", vString) d.Repository = fmt.Sprintf("file://%v", vString)
d.Version = localChart.Version d.Version = localChart.Version
return nil return nil
default: default:
return fmt.Errorf("unsupported node tag type for %T: %q", d, value.Tag) return fmt.Errorf("unsupported node tag type for %T: %q", d, value.Tag)

View File

@ -16,12 +16,15 @@ const (
func loadChart(path string) (*Chart, error) { func loadChart(path string) (*Chart, error) {
c := Chart{} c := Chart{}
file, err := os.ReadFile(path) file, err := os.ReadFile(path)
if err != nil { if err != nil {
return nil, errors.Wrap(err, "error while opening file") return nil, errors.Wrap(err, "error while opening file")
} }
if err := yaml.Unmarshal(file, &c); err != nil { if err := yaml.Unmarshal(file, &c); err != nil {
return nil, errors.Wrap(err, "error while unmarshalling template file") return nil, errors.Wrap(err, "error while unmarshalling template file")
} }
return &c, nil return &c, nil
} }

View File

@ -24,12 +24,14 @@ func (js *JSONSchema) LoadBaseSchema(ctx context.Context, url string) error {
if err != nil { if err != nil {
return err return err
} }
js.baseSchema = baseSchema js.baseSchema = baseSchema
return nil return nil
} }
// SetSquadronUnitSchema overrides the base schema at the given path with another JSON schema from a URL // SetSquadronUnitSchema overrides the base schema at the given path with another JSON schema from a URL
func (js *JSONSchema) SetSquadronUnitSchema(ctx context.Context, squardon, unit, url string) error { func (js *JSONSchema) SetSquadronUnitSchema(ctx context.Context, squadron, unit, url string) error {
var ref string var ref string
if strings.HasPrefix(url, "http") { if strings.HasPrefix(url, "http") {
ref = strings.TrimPrefix(url, "https:") ref = strings.TrimPrefix(url, "https:")
@ -41,6 +43,7 @@ func (js *JSONSchema) SetSquadronUnitSchema(ctx context.Context, squardon, unit,
ref = strings.TrimPrefix(ref, ".") ref = strings.TrimPrefix(ref, ".")
ref = strings.TrimPrefix(ref, "/") ref = strings.TrimPrefix(ref, "/")
} }
ref = strings.TrimSuffix(ref, "/") ref = strings.TrimSuffix(ref, "/")
ref = strings.ReplaceAll(ref, "/", "-") ref = strings.ReplaceAll(ref, "/", "-")
ref = strings.ToLower(ref) ref = strings.ToLower(ref)
@ -54,6 +57,7 @@ func (js *JSONSchema) SetSquadronUnitSchema(ctx context.Context, squardon, unit,
if err != nil { if err != nil {
return errors.Wrap(err, "failed to load map: "+url) return errors.Wrap(err, "failed to load map: "+url)
} }
delete(valuesMap, "$schema") delete(valuesMap, "$schema")
js.ensure(defsMap, ref, valuesMap) js.ensure(defsMap, ref, valuesMap)
} }
@ -63,7 +67,7 @@ func (js *JSONSchema) SetSquadronUnitSchema(ctx context.Context, squardon, unit,
configPropertiesMap := js.ensure(configMap, "properties", map[string]any{}) configPropertiesMap := js.ensure(configMap, "properties", map[string]any{})
squadronsMap := js.ensure(configPropertiesMap, "squadron", map[string]any{}) squadronsMap := js.ensure(configPropertiesMap, "squadron", map[string]any{})
squadronsPropertiesMap := js.ensure(squadronsMap, "properties", map[string]any{}) squadronsPropertiesMap := js.ensure(squadronsMap, "properties", map[string]any{})
squadronMap := js.ensure(squadronsPropertiesMap, squardon, map[string]any{ squadronMap := js.ensure(squadronsPropertiesMap, squadron, map[string]any{
"additionalProperties": map[string]any{ "additionalProperties": map[string]any{
"$ref": "#/$defs/Unit", "$ref": "#/$defs/Unit",
}, },
@ -95,6 +99,7 @@ func (js *JSONSchema) String() (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
return string(output), nil return string(output), nil
} }
@ -104,6 +109,7 @@ func (js *JSONSchema) PrettyString() (string, error) {
if err != nil { if err != nil {
return "", err return "", err
} }
return string(output), nil return string(output), nil
} }
@ -113,5 +119,6 @@ func (js *JSONSchema) ensure(source map[string]any, name string, initial map[str
ret = initial ret = initial
source[name] = ret source[name] = ret
} }
return ret return ret
} }

View File

@ -13,11 +13,14 @@ import (
// LoadMap fetches the JSON schema from a given URL // LoadMap fetches the JSON schema from a given URL
func LoadMap(ctx context.Context, url string) (map[string]any, error) { func LoadMap(ctx context.Context, url string) (map[string]any, error) {
var err error var (
var body []byte err error
body []byte
)
if strings.HasPrefix(url, "http") { if strings.HasPrefix(url, "http") {
pterm.Debug.Printfln("Loading map from %s", url) pterm.Debug.Printfln("Loading map from %s", url)
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil) req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -12,15 +12,20 @@ type MultiPrinter interface {
} }
func MustNewMultiPrinter() MultiPrinter { func MustNewMultiPrinter() MultiPrinter {
var err error var (
var value MultiPrinter err error
value MultiPrinter
)
if _, ok := os.LookupEnv("CI"); ok { if _, ok := os.LookupEnv("CI"); ok {
value, err = NewNoopMultiPrinter() value, err = NewNoopMultiPrinter()
} else { } else {
value, err = NewStandardMultiPrinter() value, err = NewStandardMultiPrinter()
} }
if err != nil { if err != nil {
pterm.Fatal.Print(err) pterm.Fatal.Print(err)
} }
return value return value
} }

View File

@ -55,6 +55,7 @@ func (s *NoopSpinner) Write(p []byte) (int, error) {
lines = append(lines, line) lines = append(lines, line)
} }
} }
s.log = append(s.log, lines...) s.log = append(s.log, lines...)
// pterm.UpdateText.Println(s.message()) // pterm.UpdateText.Println(s.message())
return len(p), nil return len(p), nil
@ -65,11 +66,14 @@ func (s *NoopSpinner) message(message ...string) string {
if !s.start.IsZero() && s.stopped { if !s.start.IsZero() && s.stopped {
msg[0] += " ⏱ " + time.Since(s.start).Truncate(time.Second).String() msg[0] += " ⏱ " + time.Since(s.start).Truncate(time.Second).String()
} }
if value := strings.Join(message, " "); len(value) > 0 { if value := strings.Join(message, " "); len(value) > 0 {
msg = append(msg, value) msg = append(msg, value)
} }
if pterm.PrintDebugMessages { if pterm.PrintDebugMessages {
msg = append(msg, s.log...) msg = append(msg, s.log...)
} }
return strings.Join(msg, "\n ") return strings.Join(msg, "\n ")
} }

View File

@ -15,6 +15,7 @@ func NewStandardMultiPrinter() (*StandardMultiPrinter, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &StandardMultiPrinter{printer: printer}, nil return &StandardMultiPrinter{printer: printer}, nil
} }

View File

@ -65,7 +65,9 @@ func (s *StandardSpinner) Write(p []byte) (int, error) {
lines = append(lines, line) lines = append(lines, line)
} }
} }
s.log = append(s.log, lines...) s.log = append(s.log, lines...)
return len(p), nil return len(p), nil
} }
@ -74,17 +76,21 @@ func (s *StandardSpinner) message(message ...string) string {
if !s.start.IsZero() && s.stopped { if !s.start.IsZero() && s.stopped {
msg[0] += " ⏱ " + time.Since(s.start).Truncate(time.Second).String() msg[0] += " ⏱ " + time.Since(s.start).Truncate(time.Second).String()
} }
width := pterm.GetTerminalWidth() - 10 width := pterm.GetTerminalWidth() - 10
for i, line := range msg { for i, line := range msg {
if len(line) > width { if len(line) > width {
msg[i] = line[:width] + "…" msg[i] = line[:width] + "…"
} }
} }
if value := strings.Join(message, " "); len(value) > 0 { if value := strings.Join(message, " "); len(value) > 0 {
msg = append(msg, value) msg = append(msg, value)
} }
if pterm.PrintDebugMessages { if pterm.PrintDebugMessages {
msg = append(msg, s.log...) msg = append(msg, s.log...)
} }
return strings.Join(msg, "\n ") return strings.Join(msg, "\n ")
} }

View File

@ -5,5 +5,6 @@ func defaultIndexValue(v map[string]any, index string, def any) any {
if _, ok = v[index]; ok { if _, ok = v[index]; ok {
return v[index] return v[index]
} }
return def return def
} }

View File

@ -41,18 +41,21 @@ func toYAML(v any) string {
if err != nil { if err != nil {
return err.Error() return err.Error()
} }
return strings.TrimSuffix(string(data), "\n") return strings.TrimSuffix(string(data), "\n")
} }
func toYAMLPretty(v any) string { func toYAMLPretty(v any) string {
var data bytes.Buffer var data bytes.Buffer
encoder := yaml.NewEncoder(&data) encoder := yaml.NewEncoder(&data)
encoder.SetIndent(2) encoder.SetIndent(2)
err := encoder.Encode(v)
err := encoder.Encode(v)
if err != nil { if err != nil {
return err.Error() return err.Error()
} }
return strings.TrimSuffix(data.String(), "\n") return strings.TrimSuffix(data.String(), "\n")
} }
@ -68,6 +71,7 @@ func fromYAML(str string) map[string]any {
if err := yaml.Unmarshal([]byte(str), &m); err != nil { if err := yaml.Unmarshal([]byte(str), &m); err != nil {
m["Error"] = err.Error() m["Error"] = err.Error()
} }
return m return m
} }
@ -82,6 +86,7 @@ func fromYAMLArray(str string) []any {
if err := yaml.Unmarshal([]byte(str), &a); err != nil { if err := yaml.Unmarshal([]byte(str), &a); err != nil {
a = []any{err.Error()} a = []any{err.Error()}
} }
return a return a
} }
@ -92,10 +97,12 @@ func fromYAMLArray(str string) []any {
func toTOML(v any) string { func toTOML(v any) string {
b := bytes.NewBuffer(nil) b := bytes.NewBuffer(nil)
e := toml.NewEncoder(b) e := toml.NewEncoder(b)
err := e.Encode(v) err := e.Encode(v)
if err != nil { if err != nil {
return err.Error() return err.Error()
} }
return b.String() return b.String()
} }
@ -111,6 +118,7 @@ func fromTOML(str string) map[string]any {
if err := toml.Unmarshal([]byte(str), &m); err != nil { if err := toml.Unmarshal([]byte(str), &m); err != nil {
m["Error"] = err.Error() m["Error"] = err.Error()
} }
return m return m
} }
@ -123,6 +131,7 @@ func toJSON(v any) string {
if err != nil { if err != nil {
return err.Error() return err.Error()
} }
return string(data) return string(data)
} }
@ -138,6 +147,7 @@ func fromJSON(str string) map[string]any {
if err := json.Unmarshal([]byte(str), &m); err != nil { if err := json.Unmarshal([]byte(str), &m); err != nil {
m["Error"] = err.Error() m["Error"] = err.Error()
} }
return m return m
} }
@ -152,5 +162,6 @@ func fromJSONArray(str string) []any {
if err := json.Unmarshal([]byte(str), &a); err != nil { if err := json.Unmarshal([]byte(str), &a); err != nil {
a = []any{err.Error()} a = []any{err.Error()}
} }
return a return a
} }

View File

@ -12,6 +12,7 @@ func quote(str ...any) string {
out = append(out, fmt.Sprintf("%v", s)) out = append(out, fmt.Sprintf("%v", s))
} }
} }
return "'" + strings.Join(out, " ") + "'" return "'" + strings.Join(out, " ") + "'"
} }
@ -22,6 +23,7 @@ func quoteAll(str ...any) string {
out = append(out, fmt.Sprintf("'%v'", s)) out = append(out, fmt.Sprintf("'%v'", s))
} }
} }
return strings.Join(out, " ") return strings.Join(out, " ")
} }

View File

@ -18,6 +18,7 @@ func git(ctx context.Context) func(action string) (string, error) {
default: default:
cmd.Args = append(cmd.Args, "describe", "--tags", "--always") cmd.Args = append(cmd.Args, "describe", "--tags", "--always")
} }
res, err := cmd.Output() res, err := cmd.Output()
if err != nil { if err != nil {
return "", err return "", err

View File

@ -14,6 +14,7 @@ import (
func kubeseal(ctx context.Context) func(values ...string) (string, error) { func kubeseal(ctx context.Context) func(values ...string) (string, error) {
return func(values ...string) (string, error) { return func(values ...string) (string, error) {
var value string var value string
if len(values) == 0 { if len(values) == 0 {
return "", errors.Errorf("missing value") return "", errors.Errorf("missing value")
} else if len(values) == 1 { } else if len(values) == 1 {
@ -21,6 +22,7 @@ func kubeseal(ctx context.Context) func(values ...string) (string, error) {
} else { } else {
value, values = values[len(values)-1], values[:len(values)-1] value, values = values[len(values)-1], values[:len(values)-1]
} }
cmd := exec.CommandContext(ctx, "kubeseal", "--raw", "--from-file=/dev/stdin") cmd := exec.CommandContext(ctx, "kubeseal", "--raw", "--from-file=/dev/stdin")
cmd.Args = append(cmd.Args, values...) cmd.Args = append(cmd.Args, values...)
cmd.Env = os.Environ() cmd.Env = os.Environ()
@ -29,15 +31,19 @@ func kubeseal(ctx context.Context) func(values ...string) (string, error) {
if v := os.Getenv("SQUADRON_KUBESEAL_NAME"); v != "" { if v := os.Getenv("SQUADRON_KUBESEAL_NAME"); v != "" {
cmd.Args = append(cmd.Args, "--name", v) cmd.Args = append(cmd.Args, "--name", v)
} }
if v := os.Getenv("SQUADRON_KUBESEAL_NAMESPACE"); v != "" { if v := os.Getenv("SQUADRON_KUBESEAL_NAMESPACE"); v != "" {
cmd.Args = append(cmd.Args, "--namespace", v) cmd.Args = append(cmd.Args, "--namespace", v)
} }
if v := os.Getenv("SQUADRON_KUBESEAL_CONTROLLER_NAME"); v != "" { if v := os.Getenv("SQUADRON_KUBESEAL_CONTROLLER_NAME"); v != "" {
cmd.Args = append(cmd.Args, "--controller-name", v) cmd.Args = append(cmd.Args, "--controller-name", v)
} }
if v := os.Getenv("SQUADRON_KUBESEAL_CONTROLLER_NAMESPACE"); v != "" { if v := os.Getenv("SQUADRON_KUBESEAL_CONTROLLER_NAMESPACE"); v != "" {
cmd.Args = append(cmd.Args, "--controller-namespace", v) cmd.Args = append(cmd.Args, "--controller-namespace", v)
} }
if v := os.Getenv("SQUADRON_KUBESEAL_EXTRA_ARGS"); v != "" { if v := os.Getenv("SQUADRON_KUBESEAL_EXTRA_ARGS"); v != "" {
cmd.Args = append(cmd.Args, strings.Split(v, " ")...) cmd.Args = append(cmd.Args, strings.Split(v, " ")...)
} }
@ -46,6 +52,7 @@ func kubeseal(ctx context.Context) func(values ...string) (string, error) {
if err != nil { if err != nil {
pterm.Debug.Println(cmd.String()) pterm.Debug.Println(cmd.String())
pterm.Error.Println(string(res)) pterm.Error.Println(string(res))
return "", err return "", err
} }

View File

@ -28,6 +28,7 @@ var ErrOnePasswordNotSignedIn = errors.New("not signed in")
func onePasswordConnectGet(client connect.Client, vaultUUID, itemUUID string) (map[string]string, error) { func onePasswordConnectGet(client connect.Client, vaultUUID, itemUUID string) (map[string]string, error) {
var item *onepassword.Item var item *onepassword.Item
if onePasswordUUID.MatchString(itemUUID) { if onePasswordUUID.MatchString(itemUUID) {
if v, err := client.GetItem(itemUUID, vaultUUID); err != nil { if v, err := client.GetItem(itemUUID, vaultUUID); err != nil {
return nil, err return nil, err
@ -52,6 +53,7 @@ func onePasswordConnectGet(client connect.Client, vaultUUID, itemUUID string) (m
func onePasswordConnectGetDocument(client connect.Client, vaultUUID, itemUUID string) (string, error) { func onePasswordConnectGetDocument(client connect.Client, vaultUUID, itemUUID string) (string, error) {
var item *onepassword.Item var item *onepassword.Item
if onePasswordUUID.MatchString(itemUUID) { if onePasswordUUID.MatchString(itemUUID) {
if v, err := client.GetItem(itemUUID, vaultUUID); err != nil { if v, err := client.GetItem(itemUUID, vaultUUID); err != nil {
return "", err return "", err
@ -85,6 +87,7 @@ var onePasswordGetLock sync.Mutex
func onePasswordGet(ctx context.Context, account, vaultUUID, itemUUID string) (map[string]string, error) { func onePasswordGet(ctx context.Context, account, vaultUUID, itemUUID string) (map[string]string, error) {
onePasswordGetLock.Lock() onePasswordGetLock.Lock()
defer onePasswordGetLock.Unlock() defer onePasswordGetLock.Unlock()
var v struct { var v struct {
Vault struct { Vault struct {
ID string `json:"id"` ID string `json:"id"`
@ -106,6 +109,7 @@ func onePasswordGet(ctx context.Context, account, vaultUUID, itemUUID string) (m
return nil, errors.Errorf("wrong vault UUID %s for item %s", vaultUUID, itemUUID) return nil, errors.Errorf("wrong vault UUID %s for item %s", vaultUUID, itemUUID)
} else { } else {
ret := map[string]string{} ret := map[string]string{}
aliases := map[string]string{ aliases := map[string]string{
"notesPlain": "notes", "notesPlain": "notes",
} }
@ -116,6 +120,7 @@ func onePasswordGet(ctx context.Context, account, vaultUUID, itemUUID string) (m
ret[field.Label] = fmt.Sprintf("%v", field.Value) ret[field.Label] = fmt.Sprintf("%v", field.Value)
} }
} }
return ret, nil return ret, nil
} }
} }
@ -125,12 +130,14 @@ var onePasswordGetDocumentLock sync.Mutex
func onePasswordGetDocument(ctx context.Context, account, vaultUUID, itemUUID string) (string, error) { func onePasswordGetDocument(ctx context.Context, account, vaultUUID, itemUUID string) (string, error) {
onePasswordGetDocumentLock.Lock() onePasswordGetDocumentLock.Lock()
defer onePasswordGetDocumentLock.Unlock() defer onePasswordGetDocumentLock.Unlock()
res, err := exec.CommandContext(ctx, "op", "document", "get", itemUUID, "--vault", vaultUUID, "--account", account).CombinedOutput() res, err := exec.CommandContext(ctx, "op", "document", "get", itemUUID, "--vault", vaultUUID, "--account", account).CombinedOutput()
if err != nil && strings.Contains(string(res), "You are not currently signed in") { if err != nil && strings.Contains(string(res), "You are not currently signed in") {
return "", ErrOnePasswordNotSignedIn return "", ErrOnePasswordNotSignedIn
} else if err != nil { } else if err != nil {
return "", errors.Wrap(err, string(res)) return "", errors.Wrap(err, string(res))
} }
return strings.Trim(string(res), "\n"), nil return strings.Trim(string(res), "\n"), nil
} }
@ -142,6 +149,7 @@ func onePasswordSignIn(ctx context.Context, account string) error {
// use multi writer to handle password prompt // use multi writer to handle password prompt
var stdoutBuf bytes.Buffer var stdoutBuf bytes.Buffer
cmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf) cmd.Stdout = io.MultiWriter(os.Stdout, &stdoutBuf)
cmd.Stdin = os.Stdin cmd.Stdin = os.Stdin
@ -155,6 +163,7 @@ func onePasswordSignIn(ctx context.Context, account string) error {
if token := strings.TrimSuffix(stdoutBuf.String(), "\n"); token == "" { if token := strings.TrimSuffix(stdoutBuf.String(), "\n"); token == "" {
fmt.Printf("Failed to login into your '%s' account! Please refer to the manual:\n", account) fmt.Printf("Failed to login into your '%s' account! Please refer to the manual:\n", account)
fmt.Println("https://support.1password.com/command-line-getting-started/#set-up-the-command-line-tool") fmt.Println("https://support.1password.com/command-line-getting-started/#set-up-the-command-line-tool")
return errors.New("failed to retrieve 1password session token") return errors.New("failed to retrieve 1password session token")
} else if err := os.Setenv(fmt.Sprintf("OP_SESSION_%s", account), token); err != nil { } else if err := os.Setenv(fmt.Sprintf("OP_SESSION_%s", account), token); err != nil {
return err return err
@ -162,6 +171,7 @@ func onePasswordSignIn(ctx context.Context, account string) error {
fmt.Println("NOTE: If you want to skip this step, run:") fmt.Println("NOTE: If you want to skip this step, run:")
fmt.Printf("export OP_SESSION_%s=%s\n", account, token) fmt.Printf("export OP_SESSION_%s=%s\n", account, token)
} }
return nil return nil
} }
@ -195,6 +205,7 @@ func onePasswordInit(ctx context.Context, account string) error {
if _, err := exec.LookPath("op"); err != nil { if _, err := exec.LookPath("op"); err != nil {
pterm.Warning.Println("Your templates includes a call to 1Password, please install it:") pterm.Warning.Println("Your templates includes a call to 1Password, please install it:")
pterm.Warning.Println("https://support.1password.com/command-line-getting-started/#set-up-the-command-line-tool") pterm.Warning.Println("https://support.1password.com/command-line-getting-started/#set-up-the-command-line-tool")
return errors.Wrap(err, "failed to lookup op") return errors.Wrap(err, "failed to lookup op")
} }
@ -225,6 +236,7 @@ func onePassword(ctx context.Context, templateVars any, errorOnMissing bool) fun
} else { } else {
itemUUID = value itemUUID = value
} }
if value, err := onePasswordRender("op", field, templateVars, errorOnMissing); err != nil { if value, err := onePasswordRender("op", field, templateVars, errorOnMissing); err != nil {
return "", err return "", err
} else { } else {
@ -240,6 +252,7 @@ func onePassword(ctx context.Context, templateVars any, errorOnMissing bool) fun
if err != nil { if err != nil {
return "", err return "", err
} }
if res, err := onePasswordConnectGet(client, vaultUUID, itemUUID); err != nil { if res, err := onePasswordConnectGet(client, vaultUUID, itemUUID); err != nil {
return "", err return "", err
} else { } else {
@ -310,11 +323,13 @@ func onePasswordRender(name, text string, data any, errorOnMissing bool) (string
if !errorOnMissing { if !errorOnMissing {
opts = append(opts, "missingkey=error") opts = append(opts, "missingkey=error")
} }
out := bytes.NewBuffer([]byte{}) out := bytes.NewBuffer([]byte{})
if uuidTpl, err := template.New(name).Option(opts...).Parse(text); err != nil { if uuidTpl, err := template.New(name).Option(opts...).Parse(text); err != nil {
return "", err return "", err
} else if err := uuidTpl.Execute(out, data); err != nil { } else if err := uuidTpl.Execute(out, data); err != nil {
return "", err return "", err
} }
return out.String(), nil return out.String(), nil
} }

View File

@ -43,12 +43,16 @@ func ExecuteFileTemplate(ctx context.Context, text string, templateVars any, err
if err != nil { if err != nil {
return nil, err return nil, err
} }
out := bytes.NewBuffer([]byte{}) out := bytes.NewBuffer([]byte{})
if errorOnMissing { if errorOnMissing {
tpl = tpl.Option("missingkey=error") tpl = tpl.Option("missingkey=error")
} }
if err := tpl.Funcs(funcMap).Execute(out, templateVars); err != nil { if err := tpl.Funcs(funcMap).Execute(out, templateVars); err != nil {
return nil, err return nil, err
} }
return out.Bytes(), nil return out.Bytes(), nil
} }

View File

@ -12,10 +12,12 @@ import (
// Snapshot compares v with its snapshot file // Snapshot compares v with its snapshot file
func Snapshot(t *testing.T, name, yaml string) { func Snapshot(t *testing.T, name, yaml string) {
t.Helper() t.Helper()
snapshot := readSnapshot(t, name) snapshot := readSnapshot(t, name)
if *UpdateFlag || snapshot == "" { if *UpdateFlag || snapshot == "" {
writeSnapshot(t, name, yaml) writeSnapshot(t, name, yaml)
} }
assert.YAMLEq(t, snapshot, yaml) assert.YAMLEq(t, snapshot, yaml)
} }
@ -28,9 +30,11 @@ func writeSnapshot(t *testing.T, name string, content string) {
// readSnapshot reads the snapshot file for a given test t. // readSnapshot reads the snapshot file for a given test t.
func readSnapshot(t *testing.T, name string) string { func readSnapshot(t *testing.T, name string) string {
t.Helper() t.Helper()
g, err := os.ReadFile(name) g, err := os.ReadFile(name)
if !errors.Is(err, os.ErrNotExist) { if !errors.Is(err, os.ErrNotExist) {
require.NoError(t, err, "failed reading file", name) require.NoError(t, err, "failed reading file", name)
} }
return string(g) return string(g)
} }

View File

@ -42,8 +42,10 @@ func (c *Cmd) Args(args ...string) *Cmd {
if arg == "" { if arg == "" {
continue continue
} }
c.append(arg) c.append(arg)
} }
return c return c
} }
@ -56,7 +58,9 @@ func (c *Cmd) Arg(name, v string) *Cmd {
if name == "" || v == "" { if name == "" || v == "" {
return c return c
} }
c.append(name, v) c.append(name, v)
return c return c
} }
@ -64,7 +68,9 @@ func (c *Cmd) BoolArg(name string, v bool) *Cmd {
if name == "" || !v { if name == "" || !v {
return c return c
} }
c.append(name) c.append(name)
return c return c
} }
@ -72,12 +78,15 @@ func (c *Cmd) ListArg(name string, vs []string) *Cmd {
if name == "" { if name == "" {
return c return c
} }
for _, v := range vs { for _, v := range vs {
if v == "" { if v == "" {
continue continue
} }
c.append(name, v) c.append(name, v)
} }
return c return c
} }
@ -100,7 +109,9 @@ func (c *Cmd) Stdout(w io.Writer) *Cmd {
if w == nil { if w == nil {
w, _ = os.Open(os.DevNull) w, _ = os.Open(os.DevNull)
} }
c.stdoutWriters = append(c.stdoutWriters, w) c.stdoutWriters = append(c.stdoutWriters, w)
return c return c
} }
@ -108,28 +119,35 @@ func (c *Cmd) Stderr(w io.Writer) *Cmd {
if w == nil { if w == nil {
w, _ = os.Open(os.DevNull) w, _ = os.Open(os.DevNull)
} }
c.stderrWriters = append(c.stderrWriters, w) c.stderrWriters = append(c.stderrWriters, w)
return c return c
} }
func (c *Cmd) String() string { func (c *Cmd) String() string {
cmd := exec.Command(c.command[0], c.command[1:]...) //nolint:noctx cmd := exec.Command(c.command[0], c.command[1:]...) //nolint:noctx,gosec
cmd.Env = append(os.Environ(), c.env...) cmd.Env = append(os.Environ(), c.env...)
if c.cwd != "" { if c.cwd != "" {
cmd.Dir = c.cwd cmd.Dir = c.cwd
} }
return cmd.String() return cmd.String()
} }
func (c *Cmd) Run(ctx context.Context) (string, error) { func (c *Cmd) Run(ctx context.Context) (string, error) {
cmd := exec.CommandContext(ctx, c.command[0], c.command[1:]...) cmd := exec.CommandContext(ctx, c.command[0], c.command[1:]...) //nolint:gosec
cmd.Env = append(os.Environ(), c.env...) cmd.Env = append(os.Environ(), c.env...)
if c.cwd != "" { if c.cwd != "" {
cmd.Dir = c.cwd cmd.Dir = c.cwd
} }
var stdout bytes.Buffer var (
var stderr bytes.Buffer stdout bytes.Buffer
stderr bytes.Buffer
)
if c.stdin != nil { if c.stdin != nil {
cmd.Stdin = c.stdin cmd.Stdin = c.stdin
@ -144,10 +162,12 @@ func (c *Cmd) Run(ctx context.Context) (string, error) {
cmd.Stderr = io.MultiWriter(append(c.stderrWriters, &stderr)...) cmd.Stderr = io.MultiWriter(append(c.stderrWriters, &stderr)...)
pterm.Debug.Println(" " + cmd.String()) pterm.Debug.Println(" " + cmd.String())
err := cmd.Run() err := cmd.Run()
if err != nil { if err != nil {
err = errors.Wrap(err, "failed to execute: "+cmd.String()) err = errors.Wrap(err, "failed to execute: "+cmd.String())
} }
return stdout.String() + stderr.String(), err return stdout.String() + stderr.String(), err
} }
@ -165,5 +185,6 @@ func (c *Cmd) append(v ...string) {
} }
} }
} }
c.command = append(c.command, v...) c.command = append(c.command, v...)
} }

View File

@ -10,7 +10,9 @@ import (
func SprintError(err error) string { func SprintError(err error) string {
var ret string var ret string
prefix := "Error: " prefix := "Error: "
if pterm.PrintDebugMessages { if pterm.PrintDebugMessages {
return fmt.Sprintf("%+v", err) + "\n" return fmt.Sprintf("%+v", err) + "\n"
} }
@ -21,10 +23,12 @@ func SprintError(err error) string {
ret += prefix + err.Error() + "\n" ret += prefix + err.Error() + "\n"
break break
} }
if err.Error() != w.Error() { if err.Error() != w.Error() {
ret += prefix + strings.TrimSuffix(err.Error(), ": "+w.Error()) + "\n" ret += prefix + strings.TrimSuffix(err.Error(), ": "+w.Error()) + "\n"
prefix = "↪ " prefix = "↪ "
} }
err = w err = w
} }

View File

@ -21,9 +21,11 @@ func Highlight(source string) string {
if l == nil { if l == nil {
l = lexers.Analyse(source) l = lexers.Analyse(source)
} }
if l == nil { if l == nil {
l = lexers.Fallback l = lexers.Fallback
} }
l = chroma.Coalesce(l) l = chroma.Coalesce(l)
// Determine formatter. // Determine formatter.
@ -60,9 +62,11 @@ func HighlightHCL(source string) string {
if l == nil { if l == nil {
l = lexers.Analyse(source) l = lexers.Analyse(source)
} }
if l == nil { if l == nil {
l = lexers.Fallback l = lexers.Fallback
} }
l = chroma.Coalesce(l) l = chroma.Coalesce(l)
// Determine formatter. // Determine formatter.
@ -109,6 +113,7 @@ func (w *numberWriter) Write(p []byte) (int, error) {
) )
for i, c := range original { for i, c := range original {
tokenLen++ tokenLen++
if c != '\n' { if c != '\n' {
continue continue
} }
@ -121,11 +126,13 @@ func (w *numberWriter) Write(p []byte) (int, error) {
if w.currentLine > 9999 { if w.currentLine > 9999 {
format = "%d |\t%s%s" format = "%d |\t%s%s"
} }
format = "\033[34m" + format + "\033[0m" format = "\033[34m" + format + "\033[0m"
if _, err := fmt.Fprintf(w.w, format, w.currentLine, string(w.buf), string(token)); err != nil { if _, err := fmt.Fprintf(w.w, format, w.currentLine, string(w.buf), string(token)); err != nil {
return i + 1, err return i + 1, err
} }
w.buf = w.buf[:0] w.buf = w.buf[:0]
w.currentLine++ w.currentLine++
} }
@ -133,5 +140,6 @@ func (w *numberWriter) Write(p []byte) (int, error) {
if len(p) > 0 { if len(p) > 0 {
w.buf = append(w.buf, p...) w.buf = append(w.buf, p...)
} }
return len(original), nil return len(original), nil
} }

View File

@ -33,6 +33,7 @@ func (c KubeCmd) GetMostRecentPodBySelectors(ctx context.Context, selectors map[
for k, v := range selectors { for k, v := range selectors {
selector = append(selector, fmt.Sprintf("%v=%v", k, v)) selector = append(selector, fmt.Sprintf("%v=%v", k, v))
} }
out, err := c.Args("--selector", strings.Join(selector, ","), out, err := c.Args("--selector", strings.Join(selector, ","),
"get", "pods", "--sort-by=.status.startTime", "-o", "name").Run(ctx) "get", "pods", "--sort-by=.status.startTime", "-o", "name").Run(ctx)
if err != nil { if err != nil {
@ -43,9 +44,11 @@ func (c KubeCmd) GetMostRecentPodBySelectors(ctx context.Context, selectors map[
if err != nil { if err != nil {
return "", err return "", err
} }
if len(pods) > 0 { if len(pods) > 0 {
return pods[len(pods)-1], nil return pods[len(pods)-1], nil
} }
return "", errors.New("no pods found") return "", errors.New("no pods found")
} }
@ -78,6 +81,7 @@ func (c KubeCmd) ExposePod(pod string, host string, port int) *Cmd {
if host == "127.0.0.1" { if host == "127.0.0.1" {
host = "" host = ""
} }
return c.Args("expose", "pod", pod, "--type=LoadBalancer", return c.Args("expose", "pod", pod, "--type=LoadBalancer",
fmt.Sprintf("--port=%v", port), fmt.Sprintf("--external-ip=%v", host)) fmt.Sprintf("--port=%v", port), fmt.Sprintf("--external-ip=%v", host))
} }
@ -91,10 +95,12 @@ func (c KubeCmd) GetDeployment(ctx context.Context, deployment string) (*k8s.Dep
if err != nil { if err != nil {
return nil, err return nil, err
} }
var d k8s.Deployment var d k8s.Deployment
if err := json.Unmarshal([]byte(out), &d); err != nil { if err := json.Unmarshal([]byte(out), &d); err != nil {
return nil, err return nil, err
} }
return &d, nil return &d, nil
} }
@ -103,6 +109,7 @@ func (c KubeCmd) GetNamespaces(ctx context.Context) ([]string, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return parseResources(out, "namespace/") return parseResources(out, "namespace/")
} }
@ -111,6 +118,7 @@ func (c KubeCmd) GetDeployments(ctx context.Context) ([]string, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
return parseResources(out, "deployment.apps/") return parseResources(out, "deployment.apps/")
} }
@ -119,12 +127,14 @@ func (c KubeCmd) GetPods(ctx context.Context, selectors map[string]string) ([]st
for k, v := range selectors { for k, v := range selectors {
selector = append(selector, fmt.Sprintf("%v=%v", k, v)) selector = append(selector, fmt.Sprintf("%v=%v", k, v))
} }
out, err := c.Args("--selector", strings.Join(selector, ","), out, err := c.Args("--selector", strings.Join(selector, ","),
"get", "pods", "--sort-by=.status.startTime", "get", "pods", "--sort-by=.status.startTime",
"-o", "name").Run(ctx) "-o", "name").Run(ctx)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return parseResources(out, "pod/") return parseResources(out, "pod/")
} }
@ -133,6 +143,7 @@ func (c KubeCmd) GetContainers(deployment k8s.Deployment) []string {
for i, c := range deployment.Spec.Template.Spec.Containers { for i, c := range deployment.Spec.Template.Spec.Containers {
containers[i] = c.Name containers[i] = c.Name
} }
return containers return containers
} }
@ -141,6 +152,7 @@ func (c KubeCmd) GetPodsByLabels(ctx context.Context, labels []string) ([]string
if err != nil { if err != nil {
return nil, err return nil, err
} }
return parseResources(out, "pod/") return parseResources(out, "pod/")
} }
@ -154,9 +166,11 @@ func (c KubeCmd) CreateConfigMapFromFile(ctx context.Context, name, path string)
func (c KubeCmd) CreateConfigMap(ctx context.Context, name string, keyMap map[string]string) (string, error) { func (c KubeCmd) CreateConfigMap(ctx context.Context, name string, keyMap map[string]string) (string, error) {
c.Args("create", "configmap", name) c.Args("create", "configmap", name)
for key, value := range keyMap { for key, value := range keyMap {
c.Args(fmt.Sprintf("--from-literal=%v=%v", key, value)) c.Args(fmt.Sprintf("--from-literal=%v=%v", key, value))
} }
return c.Run(ctx) return c.Run(ctx)
} }
@ -172,9 +186,11 @@ func (c KubeCmd) GetConfigMapKey(ctx context.Context, name, key string) (string,
if err != nil { if err != nil {
return out, err return out, err
} }
if out == "" { if out == "" {
return out, fmt.Errorf("no key %q found in ConfigMap %q", key, name) return out, fmt.Errorf("no key %q found in ConfigMap %q", key, name)
} }
return out, nil return out, nil
} }
@ -183,19 +199,24 @@ func parseResources(out, prefix string) ([]string, error) {
if out == "" { if out == "" {
return res, nil return res, nil
} }
lines := strings.Split(out, "\n") lines := strings.Split(out, "\n")
if len(lines) == 1 && lines[0] == "" { if len(lines) == 1 && lines[0] == "" {
return nil, fmt.Errorf("delimiter %q not found in %q", "\n", out) return nil, fmt.Errorf("delimiter %q not found in %q", "\n", out)
} }
for _, line := range lines { for _, line := range lines {
if line == "" { if line == "" {
continue continue
} }
unprefixed := strings.TrimPrefix(line, prefix) unprefixed := strings.TrimPrefix(line, prefix)
if unprefixed == line { if unprefixed == line {
return nil, fmt.Errorf("prefix %q not found in %q", prefix, line) return nil, fmt.Errorf("prefix %q not found in %q", prefix, line)
} }
res = append(res, strings.TrimPrefix(line, prefix)) res = append(res, strings.TrimPrefix(line, prefix))
} }
return res, nil return res, nil
} }

View File

@ -10,14 +10,18 @@ func ValidatePath(wd string, p *string) error {
if !filepath.IsAbs(*p) { if !filepath.IsAbs(*p) {
*p = path.Join(wd, *p) *p = path.Join(wd, *p)
} }
absPath, err := filepath.Abs(*p) absPath, err := filepath.Abs(*p)
if err != nil { if err != nil {
return err return err
} }
_, err = os.Stat(absPath) _, err = os.Stat(absPath)
if err != nil { if err != nil {
return err return err
} }
*p = absPath *p = absPath
return nil return nil
} }

View File

@ -12,9 +12,11 @@ func RenderTemplateString(s string, data any) (string, error) {
if err != nil { if err != nil {
return "", errors.Wrap(err, "failed to parse template") return "", errors.Wrap(err, "failed to parse template")
} }
var out bytes.Buffer var out bytes.Buffer
if err := t.Execute(&out, data); err != nil { if err := t.Execute(&out, data); err != nil {
return "", errors.Wrap(err, "failed to execute template") return "", errors.Wrap(err, "failed to execute template")
} }
return out.String(), nil return out.String(), nil
} }

View File

@ -11,15 +11,19 @@ func GenerateYaml(path string, data any) (err error) {
if marshalErr != nil { if marshalErr != nil {
return marshalErr return marshalErr
} }
file, crateErr := os.Create(path) file, crateErr := os.Create(path)
if crateErr != nil { if crateErr != nil {
return crateErr return crateErr
} }
defer func() { defer func() {
if closeErr := file.Close(); err == nil { if closeErr := file.Close(); err == nil {
err = closeErr err = closeErr
} }
}() }()
_, err = file.Write(out) _, err = file.Write(out)
return err return err
} }

View File

@ -30,6 +30,7 @@ func TestSchema(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
filename := path.Join(cwd, "squadron.schema.json") filename := path.Join(cwd, "squadron.schema.json")
expected, err := os.ReadFile(filename) expected, err := os.ReadFile(filename)
if !errors.Is(err, os.ErrNotExist) { if !errors.Is(err, os.ErrNotExist) {
require.NoError(t, err) require.NoError(t, err)

View File

@ -5,6 +5,8 @@ import (
"context" "context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"maps"
"os"
"os/exec" "os/exec"
"path" "path"
"slices" "slices"
@ -19,6 +21,8 @@ import (
templatex "github.com/foomo/squadron/internal/template" templatex "github.com/foomo/squadron/internal/template"
"github.com/foomo/squadron/internal/util" "github.com/foomo/squadron/internal/util"
"github.com/genelet/determined/dethcl" "github.com/genelet/determined/dethcl"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/miracl/conflate" "github.com/miracl/conflate"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/pterm/pterm" "github.com/pterm/pterm"
@ -55,6 +59,7 @@ func New(basePath, namespace string, files []string) *Squadron {
func (sq *Squadron) Namespace(ctx context.Context, squadron, unit string, u *config.Unit) (string, error) { func (sq *Squadron) Namespace(ctx context.Context, squadron, unit string, u *config.Unit) (string, error) {
var tpl string var tpl string
switch { switch {
case u.Namespace != "": case u.Namespace != "":
tpl = u.Namespace tpl = u.Namespace
@ -63,6 +68,7 @@ func (sq *Squadron) Namespace(ctx context.Context, squadron, unit string, u *con
default: default:
return "default", nil return "default", nil
} }
return util.RenderTemplateString(tpl, map[string]string{"Squadron": squadron, "Unit": unit}) return util.RenderTemplateString(tpl, map[string]string{"Squadron": squadron, "Unit": unit})
} }
@ -80,12 +86,14 @@ func (sq *Squadron) ConfigYAML() string {
func (sq *Squadron) MergeConfigFiles(ctx context.Context) error { func (sq *Squadron) MergeConfigFiles(ctx context.Context) error {
start := time.Now() start := time.Now()
pterm.Info.Println("📚 | merging configs") pterm.Info.Println("📚 | merging configs")
mergedFiles, err := conflate.FromFiles(sq.files...) mergedFiles, err := conflate.FromFiles(sq.files...)
if err != nil { if err != nil {
return errors.Wrap(err, "failed to conflate files") return errors.Wrap(err, "failed to conflate files")
} }
fileBytes, err := mergedFiles.MarshalYAML() fileBytes, err := mergedFiles.MarshalYAML()
if err != nil { if err != nil {
return errors.Wrap(err, "failed to marshal yaml") return errors.Wrap(err, "failed to marshal yaml")
@ -95,6 +103,7 @@ func (sq *Squadron) MergeConfigFiles(ctx context.Context) error {
pterm.Error.Println(string(fileBytes)) pterm.Error.Println(string(fileBytes))
return errors.Wrap(err, "failed to unmarshal yaml") return errors.Wrap(err, "failed to unmarshal yaml")
} }
if sq.c.Version != config.Version { if sq.c.Version != config.Version {
pterm.Debug.Println(string(fileBytes)) pterm.Debug.Println(string(fileBytes))
return errors.New("Please upgrade your YAML definition to from '" + sq.c.Version + "' to '" + config.Version + "'") return errors.New("Please upgrade your YAML definition to from '" + sq.c.Version + "' to '" + config.Version + "'")
@ -111,6 +120,7 @@ func (sq *Squadron) MergeConfigFiles(ctx context.Context) error {
sq.config = string(value) sq.config = string(value)
pterm.Success.Println("📚 | merging configs ⏱ " + time.Since(start).Truncate(time.Second).String()) pterm.Success.Println("📚 | merging configs ⏱ " + time.Since(start).Truncate(time.Second).String())
return nil return nil
} }
@ -160,10 +170,14 @@ func (sq *Squadron) FilterConfig(ctx context.Context, squadron string, units, ta
func (sq *Squadron) RenderConfig(ctx context.Context) error { func (sq *Squadron) RenderConfig(ctx context.Context) error {
start := time.Now() start := time.Now()
pterm.Info.Println("📗 | rendering config") pterm.Info.Println("📗 | rendering config")
var tv templatex.Vars var (
var vars map[string]any tv templatex.Vars
vars map[string]any
)
if err := yaml.Unmarshal([]byte(sq.config), &vars); err != nil { if err := yaml.Unmarshal([]byte(sq.config), &vars); err != nil {
return errors.Wrap(err, "failed to render config") return errors.Wrap(err, "failed to render config")
} }
@ -173,9 +187,11 @@ func (sq *Squadron) RenderConfig(ctx context.Context) error {
if value, ok := vars["global"]; ok { if value, ok := vars["global"]; ok {
tv.Add("Global", value) tv.Add("Global", value)
} }
if value, ok := vars["vars"]; ok { if value, ok := vars["vars"]; ok {
tv.Add("Vars", value) tv.Add("Vars", value)
} }
if value, ok := vars["squadron"]; ok { if value, ok := vars["squadron"]; ok {
tv.Add("Squadron", value) tv.Add("Squadron", value)
} }
@ -202,9 +218,11 @@ func (sq *Squadron) RenderConfig(ctx context.Context) error {
if value, ok := vars["global"]; ok { if value, ok := vars["global"]; ok {
tv.Add("Global", value) tv.Add("Global", value)
} }
if value, ok := vars["vars"]; ok { if value, ok := vars["vars"]; ok {
tv.Add("Vars", value) tv.Add("Vars", value)
} }
if value, ok := vars["squadron"]; ok { if value, ok := vars["squadron"]; ok {
tv.Add("Squadron", value) tv.Add("Squadron", value)
} }
@ -241,6 +259,7 @@ func (sq *Squadron) Push(ctx context.Context, pushArgs []string, parallel int) e
item any item any
image string image string
} }
var all []one var all []one
_ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error { _ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error {
@ -257,6 +276,7 @@ func (sq *Squadron) Push(ctx context.Context, pushArgs []string, parallel int) e
}) })
spinner.Start() spinner.Start()
} }
for _, name := range v.BakeNames() { for _, name := range v.BakeNames() {
bake := v.Bakes[name] bake := v.Bakes[name]
for _, tag := range bake.Tags { for _, tag := range bake.Tags {
@ -271,6 +291,7 @@ func (sq *Squadron) Push(ctx context.Context, pushArgs []string, parallel int) e
spinner.Start() spinner.Start()
} }
} }
return nil return nil
}) })
}) })
@ -293,13 +314,16 @@ func (sq *Squadron) Push(ctx context.Context, pushArgs []string, parallel int) e
cleanArgs = append(cleanArgs, strings.Split(value, " ")...) cleanArgs = append(cleanArgs, strings.Split(value, " ")...)
} }
} }
pterm.Debug.Printfln("running docker push for %s", a.image) pterm.Debug.Printfln("running docker push for %s", a.image)
if out, err := util.NewDockerCommand().Push(a.image).Args(cleanArgs...).Run(ctx); err != nil { if out, err := util.NewDockerCommand().Push(a.image).Args(cleanArgs...).Run(ctx); err != nil {
a.spinner.Fail(out) a.spinner.Fail(out)
return err return err
} }
a.spinner.Success() a.spinner.Success()
return nil return nil
}) })
} }
@ -330,6 +354,7 @@ func (sq *Squadron) BuildDependencies(ctx context.Context, buildArgs []string, p
} }
spinner.Success() spinner.Success()
return nil return nil
} }
@ -382,19 +407,30 @@ func (sq *Squadron) Bake(ctx context.Context, buildArgs []string) error {
Name: "all", Name: "all",
} }
gitInfo, err := sq.getGitInfo(ctx)
if err != nil {
pterm.Debug.Println("failed to get git info:", err)
}
_ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error { _ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error {
_ = value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error { _ = value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error {
for _, name := range v.BakeNames() { for _, name := range v.BakeNames() {
item := v.Bakes[name] item := v.Bakes[name]
item.Name = strings.Join([]string{key, k, name}, "-") item.Name = strings.Join([]string{key, k, name}, "-")
item.Args["SQUADRON_NAME"] = key
item.Args["SQUADRON_UNIT_NAME"] = k
maps.Copy(item.Args, gitInfo)
pterm.Info.Printfln("📦 | %s/%s.%s (%s)", key, k, name, strings.Join(item.Tags, ",")) pterm.Info.Printfln("📦 | %s/%s.%s (%s)", key, k, name, strings.Join(item.Tags, ","))
g.Targets = append(g.Targets, item.Name) g.Targets = append(g.Targets, item.Name)
c.Targets = append(c.Targets, &item) c.Targets = append(c.Targets, &item)
} }
return nil return nil
}) })
return nil return nil
}) })
c.Groups = append(c.Groups, g) c.Groups = append(c.Groups, g)
b, err := dethcl.Marshal(c) b, err := dethcl.Marshal(c)
@ -402,10 +438,13 @@ func (sq *Squadron) Bake(ctx context.Context, buildArgs []string) error {
if err != nil { if err != nil {
return errors.Wrap(err, "failed to marshal bake config") return errors.Wrap(err, "failed to marshal bake config")
} }
pterm.Debug.Println("🔥 | bakefile:\n" + util.HighlightHCL(string(b))) pterm.Debug.Println("🔥 | bakefile:\n" + util.HighlightHCL(string(b)))
start := time.Now() start := time.Now()
pterm.Success.Println("🔥 | baking containers") pterm.Success.Println("🔥 | baking containers")
out, err := util.NewDockerCommand(). out, err := util.NewDockerCommand().
Bake(bytes.NewReader(b)). Bake(bytes.NewReader(b)).
Stderr(ptermx.NewWriter(pterm.Debug)). Stderr(ptermx.NewWriter(pterm.Debug)).
@ -414,6 +453,7 @@ func (sq *Squadron) Bake(ctx context.Context, buildArgs []string) error {
pterm.Println(util.HighlightHCL(string(b))) pterm.Println(util.HighlightHCL(string(b)))
return errors.Wrap(err, out) return errors.Wrap(err, out)
} }
pterm.Success.Println("🔥 | baking containers ⏱︎ " + time.Since(start).Truncate(time.Second).String()) pterm.Success.Println("🔥 | baking containers ⏱︎ " + time.Since(start).Truncate(time.Second).String())
return nil return nil
@ -436,12 +476,28 @@ func (sq *Squadron) Build(ctx context.Context, buildArgs []string, parallel int)
unit string unit string
item config.Build item config.Build
} }
var all []one var all []one
gitInfo, err := sq.getGitInfo(ctx)
if err != nil {
pterm.Debug.Println("failed to get git info:", err)
}
var gitInfoArgs []string
for s, s2 := range gitInfo {
gitInfoArgs = append(gitInfoArgs, fmt.Sprintf("%s=%s", s, s2))
}
_ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error { _ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error {
return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error { return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error {
for _, name := range v.BuildNames() { for _, name := range v.BuildNames() {
item := v.Builds[name] item := v.Builds[name]
item.BuildArg = append(item.BuildArg,
"SQUADRON_NAME="+key,
"SQUADRON_UNIT_NAME="+k,
)
item.BuildArg = append(item.BuildArg, gitInfoArgs...)
spinner := printer.NewSpinner(fmt.Sprintf("📦 | %s/%s.%s (%s:%s)", key, k, name, item.Image, item.Tag)) spinner := printer.NewSpinner(fmt.Sprintf("📦 | %s/%s.%s (%s:%s)", key, k, name, item.Image, item.Tag))
all = append(all, one{ all = append(all, one{
spinner: spinner, spinner: spinner,
@ -451,6 +507,7 @@ func (sq *Squadron) Build(ctx context.Context, buildArgs []string, parallel int)
}) })
spinner.Start() spinner.Start()
} }
return nil return nil
}) })
}) })
@ -462,17 +519,19 @@ func (sq *Squadron) Build(ctx context.Context, buildArgs []string, parallel int)
ctx := ptermx.ContextWithSpinner(ctx, a.spinner) ctx := ptermx.ContextWithSpinner(ctx, a.spinner)
if err := ctx.Err(); err != nil { if err := ctx.Err(); err != nil {
a.spinner.Warning(err.Error()) a.spinner.Warning(err.Error())
return err return nil
} }
if out, err := a.item.Build(ctx, a.squadron, a.unit, buildArgs); err != nil { if out, err := a.item.Build(ctx, a.squadron, a.unit, buildArgs); errors.Is(ctx.Err(), context.Canceled) {
if !errors.Is(err, context.Canceled) { a.spinner.Warning(ctx.Err().Error())
a.spinner.Fail(out) return nil
} } else if err != nil {
a.spinner.Fail(out)
return err return err
} }
a.spinner.Success() a.spinner.Success()
return nil return nil
}) })
} }
@ -501,6 +560,7 @@ func (sq *Squadron) Down(ctx context.Context, helmArgs []string, parallel int) e
} }
name := sq.getReleaseName(key, k, v) name := sq.getReleaseName(key, k, v)
namespace, err := sq.Namespace(ctx, key, k, v) namespace, err := sq.Namespace(ctx, key, k, v)
if err != nil { if err != nil {
return err return err
@ -519,8 +579,10 @@ func (sq *Squadron) Down(ctx context.Context, helmArgs []string, parallel int) e
} }
spinner.Success() spinner.Success()
return nil return nil
}) })
return nil return nil
}) })
}) })
@ -554,12 +616,17 @@ func (sq *Squadron) RenderSchema(ctx context.Context, baseSchema string) (string
} }
func (sq *Squadron) Diff(ctx context.Context, helmArgs []string, parallel int) (string, error) { func (sq *Squadron) Diff(ctx context.Context, helmArgs []string, parallel int) (string, error) {
var m sync.Mutex var (
var ret bytes.Buffer m sync.Mutex
ret bytes.Buffer
)
write := func(b []byte) error { write := func(b []byte) error {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
_, err := ret.Write(b) _, err := ret.Write(b)
return err return err
} }
@ -583,10 +650,12 @@ func (sq *Squadron) Diff(ctx context.Context, helmArgs []string, parallel int) (
} }
name := sq.getReleaseName(key, k, v) name := sq.getReleaseName(key, k, v)
namespace, err := sq.Namespace(ctx, key, k, v) namespace, err := sq.Namespace(ctx, key, k, v)
if err != nil { if err != nil {
return err return err
} }
valueBytes, err := v.ValuesYAML(sq.c.Global) valueBytes, err := v.ValuesYAML(sq.c.Global)
if err != nil { if err != nil {
return err return err
@ -597,6 +666,7 @@ func (sq *Squadron) Diff(ctx context.Context, helmArgs []string, parallel int) (
spinner.Fail(string(manifest)) spinner.Fail(string(manifest))
return err return err
} }
cmd := exec.CommandContext(ctx, "helm", "upgrade", name, cmd := exec.CommandContext(ctx, "helm", "upgrade", name,
"--install", "--install",
"--namespace", namespace, "--namespace", namespace,
@ -616,11 +686,14 @@ func (sq *Squadron) Diff(ctx context.Context, helmArgs []string, parallel int) (
if v.Chart.Repository != "" { if v.Chart.Repository != "" {
cmd.Args = append(cmd.Args, "--repo", v.Chart.Repository) cmd.Args = append(cmd.Args, "--repo", v.Chart.Repository)
} }
if v.Chart.Version != "" { if v.Chart.Version != "" {
cmd.Args = append(cmd.Args, "--version", v.Chart.Version) cmd.Args = append(cmd.Args, "--version", v.Chart.Version)
} }
} }
cmd.Args = append(cmd.Args, helmArgs...) cmd.Args = append(cmd.Args, helmArgs...)
out, err := cmd.CombinedOutput() out, err := cmd.CombinedOutput()
if err != nil { if err != nil {
spinner.Fail(string(out)) spinner.Fail(string(out))
@ -634,6 +707,7 @@ func (sq *Squadron) Diff(ctx context.Context, helmArgs []string, parallel int) (
} }
outStr := strings.Split(string(out), "\n") outStr := strings.Split(string(out), "\n")
yamls2, err := yamldiff.Load(strings.Join(outStr[10:], "\n")) yamls2, err := yamldiff.Load(strings.Join(outStr[10:], "\n"))
if err != nil { if err != nil {
spinner.Fail(string(out)) spinner.Fail(string(out))
@ -651,6 +725,7 @@ func (sq *Squadron) Diff(ctx context.Context, helmArgs []string, parallel int) (
} }
spinner.Success() spinner.Success()
return nil return nil
}) })
@ -667,14 +742,17 @@ func (sq *Squadron) Diff(ctx context.Context, helmArgs []string, parallel int) (
func (sq *Squadron) Status(ctx context.Context, helmArgs []string, parallel int) error { func (sq *Squadron) Status(ctx context.Context, helmArgs []string, parallel int) error {
var m sync.Mutex var m sync.Mutex
tbd := pterm.TableData{ tbd := pterm.TableData{
{"Name", "Revision", "Status", "User", "Branch", "Commit", "Squadron", "Last deployed", "Notes"}, {"Name", "Revision", "Status", "User", "Branch", "Commit", "Squadron", "Last deployed", "Notes"},
} }
write := func(b []string) { write := func(b []string) {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
tbd = append(tbd, b) tbd = append(tbd, b)
} }
type statusType struct { type statusType struct {
Name string `json:"name"` Name string `json:"name"`
Version int `json:"version"` Version int `json:"version"`
@ -686,10 +764,10 @@ func (sq *Squadron) Status(ctx context.Context, helmArgs []string, parallel int)
LastDeployed string `json:"last_deployed"` LastDeployed string `json:"last_deployed"`
Description string `json:"description"` Description string `json:"description"`
} `json:"info"` } `json:"info"`
user string `json:"-"` //nolint:revive user string `json:"-"`
branch string `json:"-"` //nolint:revive branch string `json:"-"`
commit string `json:"-"` //nolint:revive commit string `json:"-"`
squadron string `json:"-"` //nolint:revive squadron string `json:"-"`
} }
wg, ctx := errgroup.WithContext(ctx) wg, ctx := errgroup.WithContext(ctx)
@ -701,7 +779,9 @@ func (sq *Squadron) Status(ctx context.Context, helmArgs []string, parallel int)
_ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error { _ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error {
return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error { return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error {
var status statusType var status statusType
name := sq.getReleaseName(key, k, v) name := sq.getReleaseName(key, k, v)
namespace, err := sq.Namespace(ctx, key, k, v) namespace, err := sq.Namespace(ctx, key, k, v)
if err != nil { if err != nil {
return errors.Errorf("failed to retrieve namsspace: %s/%s", key, k) return errors.Errorf("failed to retrieve namsspace: %s/%s", key, k)
@ -739,8 +819,10 @@ func (sq *Squadron) Status(ctx context.Context, helmArgs []string, parallel int)
return errors.Errorf("failed to retrieve status: %s/%s", key, k) return errors.Errorf("failed to retrieve status: %s/%s", key, k)
} }
var notes []string var (
var statusDescription Status notes []string
statusDescription Status
)
if err := json.Unmarshal([]byte(status.Info.Description), &statusDescription); err == nil { if err := json.Unmarshal([]byte(status.Info.Description), &statusDescription); err == nil {
status.user = statusDescription.User status.user = statusDescription.User
@ -769,6 +851,7 @@ func (sq *Squadron) Status(ctx context.Context, helmArgs []string, parallel int)
}) })
spinner.Success() spinner.Success()
return nil return nil
}) })
@ -779,12 +862,14 @@ func (sq *Squadron) Status(ctx context.Context, helmArgs []string, parallel int)
if err := wg.Wait(); err != nil { if err := wg.Wait(); err != nil {
return err return err
} }
printer.Stop() printer.Stop()
out, err := pterm.DefaultTable.WithHasHeader().WithData(tbd).Srender() out, err := pterm.DefaultTable.WithHasHeader().WithData(tbd).Srender()
if err != nil { if err != nil {
return err return err
} }
pterm.Println(out) pterm.Println(out)
return nil return nil
@ -804,6 +889,7 @@ func (sq *Squadron) Rollback(ctx context.Context, revision string, helmArgs []st
_ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error { _ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error {
return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error { return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error {
name := sq.getReleaseName(key, k, v) name := sq.getReleaseName(key, k, v)
namespace, err := sq.Namespace(ctx, key, k, v) namespace, err := sq.Namespace(ctx, key, k, v)
if err != nil { if err != nil {
return err return err
@ -821,6 +907,7 @@ func (sq *Squadron) Rollback(ctx context.Context, revision string, helmArgs []st
} }
stdErr := bytes.NewBuffer([]byte{}) stdErr := bytes.NewBuffer([]byte{})
out, err := util.NewHelmCommand().Args("rollback", name). out, err := util.NewHelmCommand().Args("rollback", name).
Stderr(stdErr). Stderr(stdErr).
Args(helmArgs...). Args(helmArgs...).
@ -836,6 +923,7 @@ func (sq *Squadron) Rollback(ctx context.Context, revision string, helmArgs []st
} }
spinner.Success(out) spinner.Success(out)
return nil return nil
}) })
@ -851,11 +939,13 @@ func (sq *Squadron) Rollback(ctx context.Context, revision string, helmArgs []st
func (sq *Squadron) UpdateLocalDependencies(ctx context.Context, parallel int) error { func (sq *Squadron) UpdateLocalDependencies(ctx context.Context, parallel int) error {
// collect unique entrie // collect unique entrie
repositories := map[string]struct{}{} repositories := map[string]struct{}{}
err := sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error { err := sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error {
return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error { return value.Iterate(ctx, func(ctx context.Context, k string, v *config.Unit) error {
if strings.HasPrefix(v.Chart.Repository, "file:///") { if strings.HasPrefix(v.Chart.Repository, "file:///") {
repositories[v.Chart.Repository] = struct{}{} repositories[v.Chart.Repository] = struct{}{}
} }
return nil return nil
}) })
}) })
@ -869,6 +959,7 @@ func (sq *Squadron) UpdateLocalDependencies(ctx context.Context, parallel int) e
for repository := range repositories { for repository := range repositories {
wg.Go(func() error { wg.Go(func() error {
pterm.Debug.Printfln("running helm dependency update for %s", repository) pterm.Debug.Printfln("running helm dependency update for %s", repository)
if out, err := util.NewHelmCommand(). if out, err := util.NewHelmCommand().
Cwd(path.Clean(strings.TrimPrefix(repository, "file://"))). Cwd(path.Clean(strings.TrimPrefix(repository, "file://"))).
Args("dependency", "update", "--skip-refresh", "--debug"). Args("dependency", "update", "--skip-refresh", "--debug").
@ -877,6 +968,7 @@ func (sq *Squadron) UpdateLocalDependencies(ctx context.Context, parallel int) e
} else { } else {
pterm.Debug.Println(out) pterm.Debug.Println(out)
} }
return nil return nil
}) })
} }
@ -895,12 +987,14 @@ func (sq *Squadron) Up(ctx context.Context, helmArgs []string, status Status, pa
printer := ptermx.MustNewMultiPrinter() printer := ptermx.MustNewMultiPrinter()
defer printer.Stop() defer printer.Stop()
type one struct { type one struct {
spinner ptermx.Spinner spinner ptermx.Spinner
squadron string squadron string
unit string unit string
item *config.Unit item *config.Unit
} }
var all []one var all []one
_ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error { _ = sq.Config().Squadrons.Iterate(ctx, func(ctx context.Context, key string, value config.Map[*config.Unit]) error {
@ -909,6 +1003,7 @@ func (sq *Squadron) Up(ctx context.Context, helmArgs []string, status Status, pa
if v.Priority != 0 { if v.Priority != 0 {
priority = fmt.Sprintf(" ☝︎ %d", v.Priority) priority = fmt.Sprintf(" ☝︎ %d", v.Priority)
} }
spinner := printer.NewSpinner(fmt.Sprintf("🚀 | %s/%s", key, k) + priority) spinner := printer.NewSpinner(fmt.Sprintf("🚀 | %s/%s", key, k) + priority)
all = append(all, one{ all = append(all, one{
spinner: spinner, spinner: spinner,
@ -937,11 +1032,13 @@ func (sq *Squadron) Up(ctx context.Context, helmArgs []string, status Status, pa
} }
name := sq.getReleaseName(a.squadron, a.unit, a.item) name := sq.getReleaseName(a.squadron, a.unit, a.item)
namespace, err := sq.Namespace(ctx, a.squadron, a.unit, a.item) namespace, err := sq.Namespace(ctx, a.squadron, a.unit, a.item)
if err != nil { if err != nil {
a.spinner.Fail(err.Error()) a.spinner.Fail(err.Error())
return err return err
} }
valueBytes, err := a.item.ValuesYAML(sq.c.Global) valueBytes, err := a.item.ValuesYAML(sq.c.Global)
if err != nil { if err != nil {
a.spinner.Fail(err.Error()) a.spinner.Fail(err.Error())
@ -966,9 +1063,11 @@ func (sq *Squadron) Up(ctx context.Context, helmArgs []string, status Status, pa
cmd.Args(path.Clean(strings.TrimPrefix(a.item.Chart.Repository, "file://"))) cmd.Args(path.Clean(strings.TrimPrefix(a.item.Chart.Repository, "file://")))
} else { } else {
cmd.Args(a.item.Chart.Name) cmd.Args(a.item.Chart.Name)
if a.item.Chart.Repository != "" { if a.item.Chart.Repository != "" {
cmd.Args("--repo", a.item.Chart.Repository) cmd.Args("--repo", a.item.Chart.Repository)
} }
if a.item.Chart.Version != "" { if a.item.Chart.Version != "" {
cmd.Args("--version", a.item.Chart.Version) cmd.Args("--version", a.item.Chart.Version)
} }
@ -984,6 +1083,7 @@ func (sq *Squadron) Up(ctx context.Context, helmArgs []string, status Status, pa
} }
a.spinner.Success() a.spinner.Success()
return nil return nil
}) })
} }
@ -992,12 +1092,17 @@ func (sq *Squadron) Up(ctx context.Context, helmArgs []string, status Status, pa
} }
func (sq *Squadron) Template(ctx context.Context, helmArgs []string, parallel int) (string, error) { func (sq *Squadron) Template(ctx context.Context, helmArgs []string, parallel int) (string, error) {
var m sync.Mutex var (
var ret bytes.Buffer m sync.Mutex
ret bytes.Buffer
)
write := func(b []byte) error { write := func(b []byte) error {
m.Lock() m.Lock()
defer m.Unlock() defer m.Unlock()
_, err := ret.Write(b) _, err := ret.Write(b)
return err return err
} }
@ -1021,6 +1126,7 @@ func (sq *Squadron) Template(ctx context.Context, helmArgs []string, parallel in
} }
name := sq.getReleaseName(key, k, v) name := sq.getReleaseName(key, k, v)
namespace, err := sq.Namespace(ctx, key, k, v) namespace, err := sq.Namespace(ctx, key, k, v)
if err != nil { if err != nil {
spinner.Fail(err.Error()) spinner.Fail(err.Error())
@ -1039,6 +1145,7 @@ func (sq *Squadron) Template(ctx context.Context, helmArgs []string, parallel in
} }
spinner.Success() spinner.Success()
return nil return nil
}) })
@ -1057,5 +1164,74 @@ func (sq *Squadron) getReleaseName(squadron, unit string, u *config.Unit) string
if u.Name != "" { if u.Name != "" {
return u.Name return u.Name
} }
return squadron + "-" + unit return squadron + "-" + unit
} }
func (sq *Squadron) getGitInfo(ctx context.Context) (map[string]string, error) {
ret := map[string]string{}
dir := "."
for _, s := range []string{"GIT_DIR", "PROJECT_ROOT"} {
if v := os.Getenv(s); v != "" {
dir = v
break
}
}
repo, err := git.PlainOpen(dir)
if err != nil {
return nil, err
}
// Get remote "origin" URL
remote, err := repo.Remote("origin")
if err != nil {
return nil, err
}
remoteURLs := remote.Config().URLs
if len(remoteURLs) > 0 {
url := remoteURLs[0]
if strings.HasPrefix(url, "git@") {
// Example input: git@github.com:user/repo.git
parts := strings.SplitN(url, ":", 2)
if len(parts) == 2 {
// parts[1] = user/repo.git
hostParts := strings.Split(parts[0], "@")
if len(hostParts) == 2 {
host := hostParts[1]
url = "https://" + host + "/" + strings.TrimSuffix(parts[1], ".git")
}
}
}
ret["GIT_REPOSITORY_URL"] = url
}
// Get HEAD reference to find the current branch or commit
ref, err := repo.Head()
if err != nil {
return nil, err
}
ret["GIT_TYPE"] = "branch"
ret["GIT_BRANCH"] = ref.Name().Short()
ret["GIT_COMMIT_HASH"] = ref.Hash().String()
if t, err := repo.Tags(); err == nil {
_ = t.ForEach(func(reference *plumbing.Reference) error {
if ref.Hash() == reference.Hash() {
ret["GIT_TAG"] = reference.Name().Short()
ret["GIT_TYPE"] = "tag"
return errors.New("break")
}
return nil
})
}
return ret, nil
}

View File

@ -70,13 +70,16 @@ func TestConfigSimpleSnapshot(t *testing.T) {
func runTestConfig(t *testing.T, name string, files []string, squadronName string, unitNames, tags []string) { func runTestConfig(t *testing.T, name string, files []string, squadronName string, unitNames, tags []string) {
t.Helper() t.Helper()
var cwd string var cwd string
ctx := t.Context() ctx := t.Context()
require.NoError(t, util.ValidatePath(".", &cwd)) require.NoError(t, util.ValidatePath(".", &cwd))
for i, file := range files { for i, file := range files {
files[i] = path.Join("testdata", name, file) files[i] = path.Join("testdata", name, file)
} }
sq := squadron.New(cwd, "default", files) sq := squadron.New(cwd, "default", files)
{ {