Compare commits
	
		
			71 Commits
		
	
	
		
			0.11.0-bet
			...
			fix-crash-
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| dc0cc20141 | |||
| 11656c009d | |||
| e4e1b58501 | |||
| 3b8f12643c | |||
| e5f5154197 | |||
| 6c1c0a8a8a | |||
| 662f45008c | |||
| 708c5f5223 | |||
| d58552b748 | |||
| 51fe809851 | |||
| 3f6a22747f | |||
| 4e75b96914 | |||
| fd4ee75ab7 | |||
| 964ed834ee | |||
| fcb3167394 | |||
| 3845b40aa3 | |||
| 0dc5c307af | |||
| fc5855ff28 | |||
| 5b504a1550 | |||
| fc16a21f1c | |||
| 7b4d2d7230 | |||
| d0ccb805c6 | |||
| 2460dd9438 | |||
| 9c648a2566 | |||
| 22ecfb9c4c | |||
| 9f3cf718be | |||
| b737ce2107 | |||
| a3d0ece7cb | |||
| d63a1c28ea | |||
| 1c10e64c58 | |||
| 21826ec555 | |||
| 4b4c56d406 | |||
| 4314195dd7 | |||
| df4447b038 | |||
| 3fa660e579 | |||
| a430b1e4fd | |||
| 896c434f38 | |||
| 847b7238c5 | |||
| 89d5fc91b0 | |||
| 5af3c5f56e | |||
| beb3864b2d | |||
| 581e6ef538 | |||
| fd642ddb84 | |||
| 1ad8c127d9 | |||
| 40aab6a6c1 | |||
| 4d33a24a07 | |||
| ee59eb350b | |||
| 5da13ff15a | |||
| 491c594ad3 | |||
| c794d533be | |||
| a6daf7030e | |||
| fe3b7ffa9c | |||
| 4c066a92d8 | |||
| 7899b57781 | |||
| 6e0a901887 | |||
| 713fdebc90 | |||
| 6944d138c6 | |||
| fbb1f16470 | |||
| 2473cafdf5 | |||
| 0ccfbd253e | |||
| 6c4bee0ac7 | |||
| 4fa9f536eb | |||
| 033c9bfc13 | |||
| 0db1ee87fc | |||
| d180bb924f | |||
| d50d68d95a | |||
| f468bc7443 | |||
| dee2d9d104 | |||
| 5c892b1d6a | |||
| 81b96fc7b1 | |||
| c92a0d0703 | 
| @ -4,5 +4,4 @@ | ||||
| Dockerfile | ||||
| abra | ||||
| dist | ||||
| kadabra | ||||
| tags | ||||
|  | ||||
| @ -11,10 +11,15 @@ steps: | ||||
|     image: git.coopcloud.tech/toolshed/drone-xgettext-go:latest | ||||
|     settings: | ||||
|       keyword: i18n.G | ||||
|       keyword_ctx: i18n.GC | ||||
|       out: pkg/i18n/locales/abra.pot | ||||
|       comments_tag: translators | ||||
|     depends_on: | ||||
|       - make check | ||||
|     when: | ||||
|       event: | ||||
|         exclude: | ||||
|           - tag | ||||
|  | ||||
|   - name: xgettext-go status | ||||
|     image: golang:1.24-alpine3.22 | ||||
| @ -27,6 +32,10 @@ steps: | ||||
|       - git diff-files --exit-code | ||||
|     depends_on: | ||||
|       - xgettext-go | ||||
|     when: | ||||
|       event: | ||||
|         exclude: | ||||
|           - tag | ||||
|  | ||||
|   - name: make test | ||||
|     image: golang:1.24 | ||||
|  | ||||
							
								
								
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @ -4,6 +4,6 @@ | ||||
| .envrc | ||||
| .vscode/ | ||||
| /abra | ||||
| /kadabra | ||||
| /bin | ||||
| dist/ | ||||
| tests/integration/.bats | ||||
|  | ||||
| @ -32,31 +32,6 @@ builds: | ||||
|       - "-s" | ||||
|       - "-w" | ||||
|  | ||||
|   - id: kadabra | ||||
|     binary: kadabra | ||||
|     dir: cmd/kadabra | ||||
|     env: | ||||
|       - CGO_ENABLED=0 | ||||
|     goos: | ||||
|       - linux | ||||
|       - darwin | ||||
|     goarch: | ||||
|       - 386 | ||||
|       - amd64 | ||||
|       - arm | ||||
|       - arm64 | ||||
|     goarm: | ||||
|       - 5 | ||||
|       - 6 | ||||
|       - 7 | ||||
|     gcflags: | ||||
|       - "all=-l -B" | ||||
|     ldflags: | ||||
|       - "-X 'main.Commit={{ .Commit }}'" | ||||
|       - "-X 'main.Version={{ .Version }}'" | ||||
|       - "-s" | ||||
|       - "-w" | ||||
|  | ||||
| checksum: | ||||
|   name_template: "checksums.txt" | ||||
|  | ||||
|  | ||||
							
								
								
									
										39
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								Makefile
									
									
									
									
									
								
							| @ -1,5 +1,5 @@ | ||||
| ABRA         := ./cmd/abra | ||||
| KADABRA      := ./cmd/kadabra | ||||
| XGETTEXT     := ./bin/xgettext-go | ||||
| COMMIT       := $(shell git rev-list -1 HEAD) | ||||
| GOPATH       := $(shell go env GOPATH) | ||||
| GOVERSION    := 1.24 | ||||
| @ -13,40 +13,23 @@ LINGUAS      := $(basename $(POFILES)) | ||||
|  | ||||
| export GOPRIVATE=coopcloud.tech | ||||
|  | ||||
| # NOTE(d1): default `make` optimised for Abra hacking | ||||
| all: format check build-abra test | ||||
| all: format check build | ||||
|  | ||||
| run-abra: | ||||
| run: | ||||
| 	@go run -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(ABRA) | ||||
|  | ||||
| run-kadabra: | ||||
| 	@go run -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(KADABRA) | ||||
|  | ||||
| install-abra: | ||||
| install: | ||||
| 	@go install -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(ABRA) | ||||
|  | ||||
| install-kadabra: | ||||
| 	@go install -gcflags=$(GCFLAGS) -ldflags=$(LDFLAGS) $(KADABRA) | ||||
|  | ||||
| install: install-abra install-kadabra | ||||
|  | ||||
| build-abra: | ||||
| build: | ||||
| 	@go build -v -gcflags=$(GCFLAGS) -ldflags=$(DIST_LDFLAGS) $(ABRA) | ||||
|  | ||||
| build-kadabra: | ||||
| 	@go build -v -gcflags=$(GCFLAGS) -ldflags=$(DIST_LDFLAGS) $(KADABRA) | ||||
|  | ||||
| build: build-abra build-kadabra | ||||
|  | ||||
| build-docker-abra: | ||||
| build-docker: | ||||
| 	@docker run -it -v $(PWD):/abra golang:$(GOVERSION) \ | ||||
| 		bash -c 'cd /abra; ./scripts/docker/build.sh' | ||||
|  | ||||
| build-docker: build-docker-abra | ||||
|  | ||||
| clean: | ||||
| 	@rm '$(GOPATH)/bin/abra' | ||||
| 	@rm '$(GOPATH)/bin/kadabra' | ||||
|  | ||||
| format: | ||||
| 	@gofmt -s -w $$(find . -type f -name '*.go' | grep -v "/vendor/") | ||||
| @ -78,14 +61,20 @@ update-po: | ||||
| 	done | ||||
|  | ||||
| .PHONY: update-pot | ||||
| update-pot: | ||||
| 	@xgettext-go \ | ||||
| update-pot: $(XGETTEXT) | ||||
| 	@${XGETTEXT} \ | ||||
| 		-o pkg/i18n/locales/$(DOMAIN).pot \ | ||||
| 		--keyword=i18n.G \ | ||||
| 		--keyword-ctx=i18n.GC \ | ||||
| 		--sort-output \ | ||||
| 		--add-comments-tag="translators" \ | ||||
| 		$$(find . -name "*.go" -not -path "*vendor*" | sort) | ||||
|  | ||||
| ${XGETTEXT}: | ||||
| 	@mkdir -p ./bin && \ | ||||
| 	wget -O ./bin/xgettext-go https://git.coopcloud.tech/toolshed/xgettext-go/raw/branch/main/xgettext-go && \ | ||||
| 	chmod +x ./bin/xgettext-go | ||||
|  | ||||
| .PHONY: update-pot-po-metadata | ||||
| update-pot-po-metadata: | ||||
| 	@sed -i "s/charset=CHARSET/charset=UTF-8/g" pkg/i18n/locales/*.po pkg/i18n/locales/*.pot | ||||
|  | ||||
| @ -9,7 +9,7 @@ import ( | ||||
|  | ||||
| // translators: `abra app` aliases. use a comma separated list of aliases with | ||||
| // no spaces in between | ||||
| var appAliases = i18n.G("a") | ||||
| var appAliases = i18n.GC("a", "abra app") | ||||
|  | ||||
| var AppCommand = &cobra.Command{ | ||||
| 	// translators: `app` command group | ||||
|  | ||||
| @ -268,7 +268,7 @@ func init() { | ||||
| 	AppBackupListCommand.Flags().BoolVarP( | ||||
| 		&showAllPaths, | ||||
| 		i18n.G("all"), | ||||
| 		i18n.G("a"), | ||||
| 		i18n.GC("a", "app backup list"), | ||||
| 		false, | ||||
| 		i18n.G("show all paths"), | ||||
| 	) | ||||
|  | ||||
| @ -3,6 +3,7 @@ package app | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"strings" | ||||
|  | ||||
| 	"coopcloud.tech/abra/cli/internal" | ||||
| @ -103,18 +104,17 @@ checkout as-is. Recipe commit hashes are also supported as values for | ||||
|  | ||||
| 		toDeployVersion, err = getDeployVersion(args, deployMeta, app) | ||||
| 		if err != nil { | ||||
| 			log.Fatal(i18n.G("get deploy version: %s", err)) | ||||
| 			log.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		versionIsChaos := false | ||||
| 		if !internal.Chaos { | ||||
| 			var err error | ||||
| 			versionIsChaos, err = app.Recipe.EnsureVersion(toDeployVersion) | ||||
| 			isChaosCommit, err := app.Recipe.EnsureVersion(toDeployVersion) | ||||
| 			if err != nil { | ||||
| 				log.Fatal(i18n.G("ensure recipe: %s", err)) | ||||
| 			} | ||||
| 			if versionIsChaos { | ||||
| 			if isChaosCommit { | ||||
| 				log.Warnf(i18n.G("version '%s' appears to be a chaos commit, but --chaos/-C was not provided", toDeployVersion)) | ||||
| 				internal.Chaos = true | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| @ -154,12 +154,22 @@ checkout as-is. Recipe commit hashes are also supported as values for | ||||
|  | ||||
| 		appPkg.ExposeAllEnv(stackName, compose, app.Env) | ||||
| 		appPkg.SetRecipeLabel(compose, stackName, app.Recipe.Name) | ||||
| 		appPkg.SetChaosLabel(compose, stackName, internal.Chaos || versionIsChaos) | ||||
| 		appPkg.SetChaosLabel(compose, stackName, internal.Chaos) | ||||
| 		if internal.Chaos { | ||||
| 			appPkg.SetChaosVersionLabel(compose, stackName, toDeployVersion) | ||||
| 		} | ||||
| 		appPkg.SetUpdateLabel(compose, stackName, app.Env) | ||||
| 		appPkg.SetVersionLabel(compose, stackName, toDeployVersion) | ||||
|  | ||||
| 		versionLabel := toDeployVersion | ||||
| 		if internal.Chaos { | ||||
| 			for _, service := range compose.Services { | ||||
| 				if service.Name == "app" { | ||||
| 					labelKey := fmt.Sprintf("coop-cloud.%s.version", stackName) | ||||
| 					// NOTE(d1): keep non-chaos version labbeling when doing chaos ops | ||||
| 					versionLabel = service.Deploy.Labels[labelKey] | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 		appPkg.SetVersionLabel(compose, stackName, versionLabel) | ||||
|  | ||||
| 		envVars, err := appPkg.CheckEnv(app) | ||||
| 		if err != nil { | ||||
| @ -186,9 +196,12 @@ checkout as-is. Recipe commit hashes are also supported as values for | ||||
| 			log.Debug(i18n.G("skipping domain checks")) | ||||
| 		} | ||||
|  | ||||
| 		deployedVersion := config.NO_VERSION_DEFAULT | ||||
| 		deployedVersion := config.MISSING_DEFAULT | ||||
| 		if deployMeta.IsDeployed { | ||||
| 			deployedVersion = deployMeta.Version | ||||
| 			if deployMeta.IsChaos { | ||||
| 				deployedVersion = deployMeta.ChaosVersion | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// Gather secrets | ||||
| @ -300,6 +313,16 @@ func validateArgsAndFlags(args []string) error { | ||||
| } | ||||
|  | ||||
| func validateSecrets(cl *dockerClient.Client, app appPkg.App) error { | ||||
| 	composeFiles, err := app.Recipe.GetComposeFiles(app.Env) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	secretsConfig, err := secret.ReadSecretsConfig(app.Path, composeFiles, app.StackName()) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	secStats, err := secret.PollSecretsStatus(cl, app) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| @ -307,6 +330,10 @@ func validateSecrets(cl *dockerClient.Client, app appPkg.App) error { | ||||
|  | ||||
| 	for _, secStat := range secStats { | ||||
| 		if !secStat.CreatedOnRemote { | ||||
| 			secretConfig := secretsConfig[secStat.LocalName] | ||||
| 			if secretConfig.SkipGenerate { | ||||
| 				return errors.New(i18n.G("secret not inserted (#generate=false): %s", secStat.LocalName)) | ||||
| 			} | ||||
| 			return errors.New(i18n.G("secret not generated: %s", secStat.LocalName)) | ||||
| 		} | ||||
| 	} | ||||
| @ -334,7 +361,12 @@ func getDeployVersion(cliArgs []string, deployMeta stack.DeployMeta, app appPkg. | ||||
| 	// Check if the recipe has a version in the .env file | ||||
| 	if app.Recipe.EnvVersion != "" && !internal.DeployLatest { | ||||
| 		if strings.HasSuffix(app.Recipe.EnvVersionRaw, "+U") { | ||||
| 			return "", errors.New(i18n.G("version: can not redeploy chaos version %s", app.Recipe.EnvVersionRaw)) | ||||
| 			// NOTE(d1): use double-line 5 spaces ("FATA ") trick to make a more | ||||
| 			// informative error message. it's ugly but that's our logging situation | ||||
| 			// atm | ||||
| 			return "", errors.New(i18n.G(`cannot redeploy previous chaos version (%s), did you mean to use "--chaos"? | ||||
|      to return to a regular release, specify a release tag, commit SHA or use "--latest"`, | ||||
| 				formatter.BoldDirtyDefault(app.Recipe.EnvVersionRaw))) | ||||
| 		} | ||||
| 		log.Debug(i18n.G("version: taking version from .env file: %s", app.Recipe.EnvVersion)) | ||||
| 		return app.Recipe.EnvVersion, nil | ||||
|  | ||||
| @ -25,7 +25,6 @@ type appStatus struct { | ||||
| 	Status       string `json:"status"` | ||||
| 	Chaos        string `json:"chaos"` | ||||
| 	ChaosVersion string `json:"chaosVersion"` | ||||
| 	AutoUpdate   string `json:"autoUpdate"` | ||||
| 	Version      string `json:"version"` | ||||
| 	Upgrade      string `json:"upgrade"` | ||||
| } | ||||
| @ -118,7 +117,6 @@ Use "--status/-S" flag to query all servers for the live deployment status.`), | ||||
| 					version := i18n.G("unknown") | ||||
| 					chaos := i18n.G("unknown") | ||||
| 					chaosVersion := i18n.G("unknown") | ||||
| 					autoUpdate := i18n.G("unknown") | ||||
| 					if statusMeta, ok := statuses[app.StackName()]; ok { | ||||
| 						if currentVersion, exists := statusMeta["version"]; exists { | ||||
| 							if currentVersion != "" { | ||||
| @ -131,9 +129,6 @@ Use "--status/-S" flag to query all servers for the live deployment status.`), | ||||
| 						if chaosDeployVersion, exists := statusMeta["chaosVersion"]; exists { | ||||
| 							chaosVersion = chaosDeployVersion | ||||
| 						} | ||||
| 						if autoUpdateState, exists := statusMeta["autoUpdate"]; exists { | ||||
| 							autoUpdate = autoUpdateState | ||||
| 						} | ||||
| 						if statusMeta["status"] != "" { | ||||
| 							status = statusMeta["status"] | ||||
| 						} | ||||
| @ -146,7 +141,6 @@ Use "--status/-S" flag to query all servers for the live deployment status.`), | ||||
| 					appStats.Chaos = chaos | ||||
| 					appStats.ChaosVersion = chaosVersion | ||||
| 					appStats.Version = version | ||||
| 					appStats.AutoUpdate = autoUpdate | ||||
|  | ||||
| 					var newUpdates []string | ||||
| 					if version != "unknown" && chaos == "false" { | ||||
| @ -165,6 +159,11 @@ Use "--status/-S" flag to query all servers for the live deployment status.`), | ||||
| 						} | ||||
|  | ||||
| 						for _, update := range updates { | ||||
| 							if ok := tagcmp.IsParsable(update); !ok { | ||||
| 								log.Debug(i18n.G("unable to parse %s, skipping as upgrade option", update)) | ||||
| 								continue | ||||
| 							} | ||||
|  | ||||
| 							parsedUpdate, err := tagcmp.Parse(update) | ||||
| 							if err != nil { | ||||
| 								log.Fatal(err) | ||||
| @ -226,7 +225,6 @@ Use "--status/-S" flag to query all servers for the live deployment status.`), | ||||
| 					i18n.G("CHAOS"), | ||||
| 					i18n.G("VERSION"), | ||||
| 					i18n.G("UPGRADE"), | ||||
| 					i18n.G("AUTOUPDATE"), | ||||
| 				}..., | ||||
| 				) | ||||
| 			} | ||||
| @ -257,8 +255,7 @@ Use "--status/-S" flag to query all servers for the live deployment status.`), | ||||
| 						appStat.Status, | ||||
| 						chaosStatus, | ||||
| 						appStat.Version, | ||||
| 						appStat.Upgrade, | ||||
| 						appStat.AutoUpdate}..., | ||||
| 						appStat.Upgrade}..., | ||||
| 					) | ||||
| 				} | ||||
|  | ||||
|  | ||||
| @ -192,7 +192,27 @@ var AppNewCommand = &cobra.Command{ | ||||
| 		log.Info(i18n.G("%s created (version: %s)", appDomain, recipeVersion)) | ||||
|  | ||||
| 		if len(secretsConfig) > 0 { | ||||
| 			log.Warn(i18n.G("%s requires secret generation before deploying, run \"abra app secret generate %s --all\"", recipe.Name, appDomain)) | ||||
| 			var ( | ||||
| 				hasSecretToGenerate bool | ||||
| 				hasSecretToSkip     bool | ||||
| 			) | ||||
|  | ||||
| 			for _, secretConfig := range secretsConfig { | ||||
| 				if secretConfig.SkipGenerate { | ||||
| 					hasSecretToSkip = true | ||||
| 					continue | ||||
| 				} | ||||
|  | ||||
| 				hasSecretToGenerate = true | ||||
| 			} | ||||
|  | ||||
| 			if hasSecretToGenerate && !generateSecrets { | ||||
| 				log.Warn(i18n.G("%s requires secret generation before deploy, run \"abra app secret generate %s --all\"", recipe.Name, appDomain)) | ||||
| 			} | ||||
|  | ||||
| 			if hasSecretToSkip { | ||||
| 				log.Warn(i18n.G("%s requires secret insertion before deploy (#generate=false)", recipe.Name)) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if len(appSecrets) > 0 { | ||||
|  | ||||
| @ -166,7 +166,7 @@ func init() { | ||||
| 	AppRestartCommand.Flags().BoolVarP( | ||||
| 		&allServices, | ||||
| 		i18n.G("all-services"), | ||||
| 		i18n.G("a"), | ||||
| 		i18n.GC("a", "app restart"), | ||||
| 		false, | ||||
| 		i18n.G("restart all services"), | ||||
| 	) | ||||
|  | ||||
| @ -183,7 +183,6 @@ beforehand. See "abra app backup" for more.`), | ||||
| 		if internal.Chaos { | ||||
| 			appPkg.SetChaosVersionLabel(compose, stackName, chosenDowngrade) | ||||
| 		} | ||||
| 		appPkg.SetUpdateLabel(compose, stackName, app.Env) | ||||
|  | ||||
| 		// Gather secrets | ||||
| 		secretInfo, err := deploy.GatherSecretsForDeploy(cl, app, internal.ShowUnchanged) | ||||
| @ -203,10 +202,15 @@ beforehand. See "abra app backup" for more.`), | ||||
| 			log.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		deployedVersion := deployMeta.Version | ||||
| 		if deployMeta.IsChaos { | ||||
| 			deployedVersion = deployMeta.ChaosVersion | ||||
| 		} | ||||
|  | ||||
| 		// NOTE(d1): no release notes implemeneted for rolling back | ||||
| 		if err := internal.DeployOverview( | ||||
| 			app, | ||||
| 			deployMeta.Version, | ||||
| 			deployedVersion, | ||||
| 			chosenDowngrade, | ||||
| 			"", | ||||
| 			downgradeWarnMessages, | ||||
|  | ||||
| @ -165,7 +165,7 @@ var AppSecretInsertCommand = &cobra.Command{ | ||||
| Arbitrary secret insertion is not supported. Secrets that are inserted must | ||||
| match those configured in the recipe beforehand. | ||||
|  | ||||
| This can be useful when you want to manually generate secrets for an app | ||||
| This command can be useful when you want to manually generate secrets for an app | ||||
| environment. Typically, you can let Abra generate them for you on app creation | ||||
| (see "abra app new --secrets/-S" for more).`), | ||||
| 	Example: i18n.G(`  # insert regular secret | ||||
| @ -574,7 +574,7 @@ func init() { | ||||
| 	AppSecretGenerateCommand.Flags().BoolVarP( | ||||
| 		&generateAllSecrets, | ||||
| 		i18n.G("all"), | ||||
| 		i18n.G("a"), | ||||
| 		i18n.GC("a", "app secret generate"), | ||||
| 		false, | ||||
| 		i18n.G("generate all secrets"), | ||||
| 	) | ||||
| @ -614,7 +614,7 @@ func init() { | ||||
| 	AppSecretRmCommand.Flags().BoolVarP( | ||||
| 		&rmAllSecrets, | ||||
| 		i18n.G("all"), | ||||
| 		i18n.G("a"), | ||||
| 		i18n.GC("a", "app secret rm"), | ||||
| 		false, | ||||
| 		i18n.G("remove all secrets"), | ||||
| 	) | ||||
|  | ||||
| @ -65,10 +65,15 @@ Passing "--prune/-p" does not remove those volumes.`), | ||||
| 			log.Fatal(i18n.G("%s is not deployed?", app.Name)) | ||||
| 		} | ||||
|  | ||||
| 		version := deployMeta.Version | ||||
| 		if deployMeta.IsChaos { | ||||
| 			version = deployMeta.ChaosVersion | ||||
| 		} | ||||
|  | ||||
| 		if err := internal.DeployOverview( | ||||
| 			app, | ||||
| 			deployMeta.Version, | ||||
| 			config.NO_DOMAIN_DEFAULT, | ||||
| 			version, | ||||
| 			config.MISSING_DEFAULT, | ||||
| 			"", | ||||
| 			nil, | ||||
| 			nil, | ||||
| @ -110,7 +115,7 @@ Passing "--prune/-p" does not remove those volumes.`), | ||||
|  | ||||
| 		log.Info(i18n.G("undeploy succeeded 🟢")) | ||||
|  | ||||
| 		if err := app.WriteRecipeVersion(deployMeta.Version, false); err != nil { | ||||
| 		if err := app.WriteRecipeVersion(version, false); err != nil { | ||||
| 			log.Fatal(i18n.G("writing recipe version failed: %s", err)) | ||||
| 		} | ||||
| 	}, | ||||
|  | ||||
| @ -196,7 +196,6 @@ beforehand. See "abra app backup" for more.`), | ||||
| 		if internal.Chaos { | ||||
| 			appPkg.SetChaosVersionLabel(compose, stackName, chosenUpgrade) | ||||
| 		} | ||||
| 		appPkg.SetUpdateLabel(compose, stackName, app.Env) | ||||
|  | ||||
| 		envVars, err := appPkg.CheckEnv(app) | ||||
| 		if err != nil { | ||||
| @ -241,9 +240,14 @@ beforehand. See "abra app backup" for more.`), | ||||
| 			) | ||||
| 		} | ||||
|  | ||||
| 		deployedVersion := deployMeta.Version | ||||
| 		if deployMeta.IsChaos { | ||||
| 			deployedVersion = deployMeta.ChaosVersion | ||||
| 		} | ||||
|  | ||||
| 		if err := internal.DeployOverview( | ||||
| 			app, | ||||
| 			deployMeta.Version, | ||||
| 			deployedVersion, | ||||
| 			chosenUpgrade, | ||||
| 			upgradeReleaseNotes, | ||||
| 			upgradeWarnMessages, | ||||
|  | ||||
| @ -66,12 +66,12 @@ func DeployOverview( | ||||
|  | ||||
| 	domain := app.Domain | ||||
| 	if domain == "" { | ||||
| 		domain = config.NO_DOMAIN_DEFAULT | ||||
| 		domain = config.MISSING_DEFAULT | ||||
| 	} | ||||
|  | ||||
| 	envVersion := app.Recipe.EnvVersionRaw | ||||
| 	if envVersion == "" { | ||||
| 		envVersion = config.NO_VERSION_DEFAULT | ||||
| 		envVersion = config.MISSING_DEFAULT | ||||
| 	} | ||||
|  | ||||
| 	rows := [][]string{ | ||||
| @ -140,32 +140,40 @@ func DeployOverview( | ||||
| } | ||||
|  | ||||
| func getDeployType(currentVersion, newVersion string) string { | ||||
| 	if newVersion == config.NO_DOMAIN_DEFAULT { | ||||
| 	if newVersion == config.MISSING_DEFAULT { | ||||
| 		return i18n.G("UNDEPLOY") | ||||
| 	} | ||||
|  | ||||
| 	if strings.Contains(newVersion, "+U") { | ||||
| 		return i18n.G("CHAOS DEPLOY") | ||||
| 	} | ||||
|  | ||||
| 	if strings.Contains(currentVersion, "+U") { | ||||
| 		return i18n.G("UNCHAOS DEPLOY") | ||||
| 	} | ||||
|  | ||||
| 	if currentVersion == newVersion { | ||||
| 		return ("REDEPLOY") | ||||
| 	} | ||||
| 	if currentVersion == config.NO_VERSION_DEFAULT { | ||||
|  | ||||
| 	if currentVersion == config.MISSING_DEFAULT { | ||||
| 		return i18n.G("NEW DEPLOY") | ||||
| 	} | ||||
|  | ||||
| 	currentParsed, err := tagcmp.Parse(currentVersion) | ||||
| 	if err != nil { | ||||
| 		return i18n.G("DEPLOY") | ||||
| 	} | ||||
|  | ||||
| 	newParsed, err := tagcmp.Parse(newVersion) | ||||
| 	if err != nil { | ||||
| 		return i18n.G("DEPLOY") | ||||
| 	} | ||||
|  | ||||
| 	if currentParsed.IsLessThan(newParsed) { | ||||
| 		return i18n.G("UPGRADE") | ||||
| 	} | ||||
|  | ||||
| 	return i18n.G("DOWNGRADE") | ||||
| } | ||||
|  | ||||
| @ -183,17 +191,17 @@ func MoveOverview( | ||||
|  | ||||
| 	domain := app.Domain | ||||
| 	if domain == "" { | ||||
| 		domain = config.NO_DOMAIN_DEFAULT | ||||
| 		domain = config.MISSING_DEFAULT | ||||
| 	} | ||||
|  | ||||
| 	secretsOverview := strings.Join(secrets, "\n") | ||||
| 	if len(secrets) == 0 { | ||||
| 		secretsOverview = config.NO_SECRETS_DEFAULT | ||||
| 		secretsOverview = config.MISSING_DEFAULT | ||||
| 	} | ||||
|  | ||||
| 	volumesOverview := strings.Join(volumes, "\n") | ||||
| 	if len(volumes) == 0 { | ||||
| 		volumesOverview = config.NO_VOLUMES_DEFAULT | ||||
| 		volumesOverview = config.MISSING_DEFAULT | ||||
| 	} | ||||
|  | ||||
| 	rows := [][]string{ | ||||
|  | ||||
| @ -119,7 +119,7 @@ func init() { | ||||
| 	RecipeFetchCommand.Flags().BoolVarP( | ||||
| 		&fetchAllRecipes, | ||||
| 		i18n.G("all"), | ||||
| 		i18n.G("a"), | ||||
| 		i18n.GC("a", "recipe fetch"), | ||||
| 		false, | ||||
| 		i18n.G("fetch all recipes"), | ||||
| 	) | ||||
|  | ||||
| @ -135,14 +135,23 @@ your private key and enter your passphrase beforehand. | ||||
| 			log.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		if tagString == "" && (!internal.Major && !internal.Minor && !internal.Patch) { | ||||
| 			var err error | ||||
| 			tagString, err = getLabelVersion(recipe, false) | ||||
| 			if err != nil { | ||||
| 				log.Fatal(err) | ||||
| 		labelVersion, err := getLabelVersion(recipe, false) | ||||
| 		if err != nil { | ||||
| 			log.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		for _, tag := range tags { | ||||
| 			previousTagLeftHand := strings.Split(tag, "+")[0] | ||||
| 			newTagStringLeftHand := strings.Split(labelVersion, "+")[0] | ||||
| 			if previousTagLeftHand == newTagStringLeftHand { | ||||
| 				log.Fatal(i18n.G("%s+... conflicts with a previous release: %s", newTagStringLeftHand, tag)) | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if tagString == "" && (!internal.Major && !internal.Minor && !internal.Patch) { | ||||
| 			tagString = labelVersion | ||||
| 		} | ||||
|  | ||||
| 		isClean, err := gitPkg.IsClean(recipe.Dir) | ||||
| 		if err != nil { | ||||
| 			log.Fatal(err) | ||||
| @ -321,7 +330,7 @@ func addReleaseNotes(recipe recipe.Recipe, tag string) error { | ||||
|  | ||||
| 		if !internal.NoInput { | ||||
| 			prompt := &survey.Confirm{ | ||||
| 				Message: i18n.G("Use release note in release/next?"), | ||||
| 				Message: i18n.G("use release note in release/next?"), | ||||
| 			} | ||||
|  | ||||
| 			if err := survey.AskOne(prompt, &addNextAsReleaseNotes); err != nil { | ||||
|  | ||||
| @ -42,7 +42,8 @@ local file system.`), | ||||
| 	ValidArgsFunction: func( | ||||
| 		cmd *cobra.Command, | ||||
| 		args []string, | ||||
| 		toComplete string) ([]string, cobra.ShellCompDirective) { | ||||
| 		toComplete string, | ||||
| 	) ([]string, cobra.ShellCompDirective) { | ||||
| 		switch l := len(args); l { | ||||
| 		case 0: | ||||
| 			return autocomplete.RecipeNameComplete() | ||||
| @ -131,22 +132,30 @@ likely to change. | ||||
| 				log.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			latestRelease := tags[len(tags)-1] | ||||
| 			latestRelease := "0.0.0+0.0.0" | ||||
| 			if len(tags) > 0 { | ||||
| 				latestRelease = tags[len(tags)-1] | ||||
| 			} | ||||
| 			changesTable.Headers(i18n.G("SERVICE"), latestRelease, i18n.G("PROPOSED CHANGES")) | ||||
|  | ||||
| 			latestRecipeVersion := versions[len(versions)-1] | ||||
| 			latestRecipeVersion := latestRelease | ||||
| 			if len(versions) > 0 { | ||||
| 				latestRecipeVersion = versions[len(versions)-1] | ||||
| 			} | ||||
| 			allRecipeVersions := catl[recipe.Name].Versions | ||||
| 			for _, recipeVersion := range allRecipeVersions { | ||||
| 				if serviceVersions, ok := recipeVersion[latestRecipeVersion]; ok { | ||||
| 					for serviceName := range serviceVersions { | ||||
| 						serviceMeta := serviceVersions[serviceName] | ||||
| 						changesTable.Row( | ||||
| 							[]string{ | ||||
| 								serviceName, | ||||
| 								fmt.Sprintf("%s:%s", serviceMeta.Image, serviceMeta.Tag), | ||||
| 								fmt.Sprintf("%s:%s", serviceMeta.Image, imagesTmp[serviceMeta.Image]), | ||||
| 							}..., | ||||
| 						) | ||||
|  | ||||
| 						existingImageTag := fmt.Sprintf("%s:%s", serviceMeta.Image, serviceMeta.Tag) | ||||
| 						newImageTag := fmt.Sprintf("%s:%s", serviceMeta.Image, imagesTmp[serviceMeta.Image]) | ||||
|  | ||||
| 						if existingImageTag == newImageTag { | ||||
| 							continue | ||||
| 						} | ||||
|  | ||||
| 						changesTable.Row([]string{serviceName, existingImageTag, newImageTag}...) | ||||
| 					} | ||||
| 				} | ||||
| 			} | ||||
|  | ||||
| @ -381,7 +381,7 @@ func init() { | ||||
| 	RecipeUpgradeCommand.Flags().BoolVarP( | ||||
| 		&allTags, | ||||
| 		i18n.G("all-tags"), | ||||
| 		i18n.G("a"), | ||||
| 		i18n.GC("a", "recipe upgrade"), | ||||
| 		false, | ||||
| 		i18n.G("list all tags, not just upgrades"), | ||||
| 	) | ||||
|  | ||||
| @ -20,7 +20,7 @@ import ( | ||||
|  | ||||
| // translators: `abra server add` aliases. use a comma separated list of | ||||
| // aliases with no spaces in between | ||||
| var serverAddAliases = i18n.G("a") | ||||
| var serverAddAliases = i18n.GC("a", "server add") | ||||
|  | ||||
| var ServerAddCommand = &cobra.Command{ | ||||
| 	// translators: `server add` command | ||||
|  | ||||
| @ -96,7 +96,7 @@ func init() { | ||||
| 	ServerPruneCommand.Flags().BoolVarP( | ||||
| 		&allFilter, | ||||
| 		i18n.G("all"), | ||||
| 		i18n.G("a"), | ||||
| 		i18n.GC("a", "server prune"), | ||||
| 		false, | ||||
| 		i18n.G("remove all unused images"), | ||||
| 	) | ||||
|  | ||||
| @ -1,558 +0,0 @@ | ||||
| package updater | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"errors" | ||||
| 	"fmt" | ||||
| 	"os" | ||||
| 	"strconv" | ||||
| 	"strings" | ||||
|  | ||||
| 	"coopcloud.tech/abra/cli/internal" | ||||
| 	appPkg "coopcloud.tech/abra/pkg/app" | ||||
| 	"coopcloud.tech/abra/pkg/client" | ||||
| 	"coopcloud.tech/abra/pkg/deploy" | ||||
| 	"coopcloud.tech/abra/pkg/envfile" | ||||
| 	"coopcloud.tech/abra/pkg/i18n" | ||||
| 	"coopcloud.tech/abra/pkg/lint" | ||||
| 	"coopcloud.tech/abra/pkg/recipe" | ||||
| 	"coopcloud.tech/abra/pkg/upstream/convert" | ||||
| 	"coopcloud.tech/abra/pkg/upstream/stack" | ||||
| 	"coopcloud.tech/tagcmp" | ||||
| 	charmLog "github.com/charmbracelet/log" | ||||
| 	composetypes "github.com/docker/cli/cli/compose/types" | ||||
| 	"github.com/docker/docker/api/types" | ||||
| 	"github.com/docker/docker/api/types/filters" | ||||
| 	dockerclient "github.com/docker/docker/client" | ||||
| 	"github.com/spf13/cobra" | ||||
|  | ||||
| 	"coopcloud.tech/abra/pkg/log" | ||||
| ) | ||||
|  | ||||
| const SERVER = "localhost" | ||||
|  | ||||
| // translators: `kadabra notify` aliases. use a comma separated list of aliases | ||||
| // with no spaces in between | ||||
| var notifyAliases = i18n.G("n") | ||||
|  | ||||
| // NotifyCommand checks for available upgrades. | ||||
| var NotifyCommand = &cobra.Command{ | ||||
| 	// translators: `notify` command | ||||
| 	Use:     i18n.G("notify [flags]"), | ||||
| 	Aliases: strings.Split(notifyAliases, ","), | ||||
| 	// translators: Short description for `notify` command | ||||
| 	Short: i18n.G("Check for available upgrades"), | ||||
| 	Long: i18n.G(`Notify on new versions for deployed apps. | ||||
|  | ||||
| If a new patch/minor version is available, a notification is printed. | ||||
|  | ||||
| Use "--major/-m" to include new major versions.`), | ||||
| 	Args: cobra.NoArgs, | ||||
| 	Run: func(cmd *cobra.Command, args []string) { | ||||
| 		cl, err := client.New("default") | ||||
| 		if err != nil { | ||||
| 			log.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		stacks, err := stack.GetStacks(cl) | ||||
| 		if err != nil { | ||||
| 			log.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		for _, stackInfo := range stacks { | ||||
| 			stackName := stackInfo.Name | ||||
| 			recipeName, err := getLabel(cl, stackName, "recipe") | ||||
| 			if err != nil { | ||||
| 				log.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			if recipeName != "" { | ||||
| 				_, err = getLatestUpgrade(cl, stackName, recipeName) | ||||
| 				if err != nil { | ||||
| 					log.Fatal(err) | ||||
| 				} | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| // translators: `kadabra upgrade` aliases. use a comma separated list of aliases with | ||||
| // no spaces in between | ||||
| var upgradeAliases = i18n.G("u") | ||||
|  | ||||
| // UpgradeCommand upgrades apps. | ||||
| var UpgradeCommand = &cobra.Command{ | ||||
| 	// translators: `app upgrade` command | ||||
| 	Use:     i18n.G("upgrade [[stack] [recipe] | --all] [flags]"), | ||||
| 	Aliases: strings.Split(upgradeAliases, ","), | ||||
| 	// translators: Short description for `app upgrade` command | ||||
| 	Short: i18n.G("Upgrade apps"), | ||||
| 	Long: i18n.G(`Upgrade an app by specifying stack name and recipe.  | ||||
|  | ||||
| Use "--all" to upgrade every deployed app. | ||||
|  | ||||
| For each app with auto updates enabled, the deployed version is compared with | ||||
| the current recipe catalogue version. If a new patch/minor version is | ||||
| available, the app is upgraded. | ||||
|  | ||||
| To include major versions use the "--major/-m" flag. You probably don't want | ||||
| that as it will break things. Only apps that are not deployed with "--chaos/-C" | ||||
| are upgraded, to update chaos deployments use the "--chaos/-C" flag. Use it | ||||
| with care.`), | ||||
| 	Args: cobra.RangeArgs(0, 2), | ||||
| 	// TODO(d1): complete stack/recipe | ||||
| 	// ValidArgsFunction: func( | ||||
| 	// 	cmd *cobra.Command, | ||||
| 	// 	args []string, | ||||
| 	// 	toComplete string) ([]string, cobra.ShellCompDirective) { | ||||
| 	// }, | ||||
| 	Run: func(cmd *cobra.Command, args []string) { | ||||
| 		cl, err := client.New("default") | ||||
| 		if err != nil { | ||||
| 			log.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		if !updateAll && len(args) != 2 { | ||||
| 			log.Fatal(i18n.G("missing arguments or --all/-a flag")) | ||||
| 		} | ||||
|  | ||||
| 		if !updateAll { | ||||
| 			stackName := args[0] | ||||
| 			recipeName := args[1] | ||||
|  | ||||
| 			err = tryUpgrade(cl, stackName, recipeName) | ||||
| 			if err != nil { | ||||
| 				log.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		stacks, err := stack.GetStacks(cl) | ||||
| 		if err != nil { | ||||
| 			log.Fatal(err) | ||||
| 		} | ||||
|  | ||||
| 		for _, stackInfo := range stacks { | ||||
| 			stackName := stackInfo.Name | ||||
| 			recipeName, err := getLabel(cl, stackName, "recipe") | ||||
| 			if err != nil { | ||||
| 				log.Fatal(err) | ||||
| 			} | ||||
|  | ||||
| 			err = tryUpgrade(cl, stackName, recipeName) | ||||
| 			if err != nil { | ||||
| 				log.Fatal(err) | ||||
| 			} | ||||
| 		} | ||||
| 	}, | ||||
| } | ||||
|  | ||||
| // getLabel reads docker labels from running services in the format of "coop-cloud.${STACK_NAME}.${LABEL}". | ||||
| func getLabel(cl *dockerclient.Client, stackName string, label string) (string, error) { | ||||
| 	filter := filters.NewArgs() | ||||
| 	filter.Add("label", fmt.Sprintf("%s=%s", convert.LabelNamespace, stackName)) | ||||
|  | ||||
| 	services, err := cl.ServiceList(context.Background(), types.ServiceListOptions{Filters: filter}) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	for _, service := range services { | ||||
| 		labelKey := fmt.Sprintf("coop-cloud.%s.%s", stackName, label) | ||||
| 		if labelValue, ok := service.Spec.Labels[labelKey]; ok { | ||||
| 			return labelValue, nil | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	log.Debug(i18n.G("no %s label found for %s", label, stackName)) | ||||
|  | ||||
| 	return "", nil | ||||
| } | ||||
|  | ||||
| // getBoolLabel reads a boolean docker label from running services | ||||
| func getBoolLabel(cl *dockerclient.Client, stackName string, label string) (bool, error) { | ||||
| 	lableValue, err := getLabel(cl, stackName, label) | ||||
| 	if err != nil { | ||||
| 		return false, err | ||||
| 	} | ||||
|  | ||||
| 	if lableValue != "" { | ||||
| 		value, err := strconv.ParseBool(lableValue) | ||||
| 		if err != nil { | ||||
| 			return false, err | ||||
| 		} | ||||
|  | ||||
| 		return value, nil | ||||
| 	} | ||||
|  | ||||
| 	log.Debug(i18n.G("boolean label %s could not be found for %s, set default to false.", label, stackName)) | ||||
|  | ||||
| 	return false, nil | ||||
| } | ||||
|  | ||||
| // getEnv reads env variables from docker services. | ||||
| func getEnv(cl *dockerclient.Client, stackName string) (envfile.AppEnv, error) { | ||||
| 	envMap := make(map[string]string) | ||||
| 	filter := filters.NewArgs() | ||||
| 	filter.Add("label", fmt.Sprintf("%s=%s", convert.LabelNamespace, stackName)) | ||||
|  | ||||
| 	services, err := cl.ServiceList(context.Background(), types.ServiceListOptions{Filters: filter}) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	for _, service := range services { | ||||
| 		envList := service.Spec.TaskTemplate.ContainerSpec.Env | ||||
| 		for _, envString := range envList { | ||||
| 			splitString := strings.SplitN(envString, "=", 2) | ||||
| 			if len(splitString) != 2 { | ||||
| 				log.Debug(i18n.G("can't separate key from value: %s (this variable is probably unset)", envString)) | ||||
| 				continue | ||||
| 			} | ||||
| 			k := splitString[0] | ||||
| 			v := splitString[1] | ||||
| 			log.Debugf(i18n.G("for %s read env %s with value: %s from docker service", stackName, k, v)) | ||||
| 			envMap[k] = v | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	return envMap, nil | ||||
| } | ||||
|  | ||||
| // getLatestUpgrade returns the latest available version for an app respecting | ||||
| // the "--major" flag if it is newer than the currently deployed version. | ||||
| func getLatestUpgrade(cl *dockerclient.Client, stackName string, recipeName string) (string, error) { | ||||
| 	deployedVersion, err := getDeployedVersion(cl, stackName, recipeName) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	availableUpgrades, err := getAvailableUpgrades(cl, stackName, recipeName, deployedVersion) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	if len(availableUpgrades) == 0 { | ||||
| 		log.Debugf(i18n.G("no available upgrades for %s", stackName)) | ||||
| 		return "", nil | ||||
| 	} | ||||
|  | ||||
| 	var chosenUpgrade string | ||||
| 	if len(availableUpgrades) > 0 { | ||||
| 		chosenUpgrade = availableUpgrades[len(availableUpgrades)-1] | ||||
| 		log.Info(i18n.G("%s (%s) can be upgraded from version %s to %s", stackName, recipeName, deployedVersion, chosenUpgrade)) | ||||
| 	} | ||||
|  | ||||
| 	return chosenUpgrade, nil | ||||
| } | ||||
|  | ||||
| // getDeployedVersion returns the currently deployed version of an app. | ||||
| func getDeployedVersion(cl *dockerclient.Client, stackName string, recipeName string) (string, error) { | ||||
| 	log.Debug(i18n.G("retrieve deployed version whether %s is already deployed", stackName)) | ||||
|  | ||||
| 	deployMeta, err := stack.IsDeployed(context.Background(), cl, stackName) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	if !deployMeta.IsDeployed { | ||||
| 		return "", errors.New(i18n.G("%s is not deployed?", stackName)) | ||||
| 	} | ||||
|  | ||||
| 	if deployMeta.Version == "unknown" { | ||||
| 		return "", errors.New(i18n.G("failed to determine deployed version of %s", stackName)) | ||||
| 	} | ||||
|  | ||||
| 	return deployMeta.Version, nil | ||||
| } | ||||
|  | ||||
| // getAvailableUpgrades returns all available versions of an app that are newer | ||||
| // than the deployed version. It only includes major upgrades if the "--major" | ||||
| // flag is set. | ||||
| func getAvailableUpgrades(cl *dockerclient.Client, stackName string, recipeName string, | ||||
| 	deployedVersion string) ([]string, error) { | ||||
| 	catl, err := recipe.ReadRecipeCatalogue(internal.Offline) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	versions, err := recipe.GetRecipeCatalogueVersions(recipeName, catl) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	if len(versions) == 0 { | ||||
| 		log.Warn(i18n.G("no published releases for %s in the recipe catalogue?", recipeName)) | ||||
| 		return nil, nil | ||||
| 	} | ||||
|  | ||||
| 	var availableUpgrades []string | ||||
| 	for _, version := range versions { | ||||
| 		parsedDeployedVersion, err := tagcmp.Parse(deployedVersion) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		parsedVersion, err := tagcmp.Parse(version) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		versionDelta, err := parsedDeployedVersion.UpgradeDelta(parsedVersion) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 		} | ||||
|  | ||||
| 		if 0 < versionDelta.UpgradeType() && (versionDelta.UpgradeType() < 4 || includeMajorUpdates) { | ||||
| 			availableUpgrades = append(availableUpgrades, version) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	log.Debug(i18n.G("available updates for %s: %s", stackName, availableUpgrades)) | ||||
|  | ||||
| 	return availableUpgrades, nil | ||||
| } | ||||
|  | ||||
| // processRecipeRepoVersion clones, pulls, checks out the version and lints the | ||||
| // recipe repository. | ||||
| func processRecipeRepoVersion(r recipe.Recipe, version string) error { | ||||
| 	if err := r.EnsureExists(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := r.EnsureUpToDate(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if _, err := r.EnsureVersion(version); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := lint.LintForErrors(r); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // createDeployConfig merges and enriches the compose config for the deployment. | ||||
| func createDeployConfig(r recipe.Recipe, stackName string, env envfile.AppEnv) (*composetypes.Config, stack.Deploy, error) { | ||||
| 	env["STACK_NAME"] = stackName | ||||
|  | ||||
| 	deployOpts := stack.Deploy{ | ||||
| 		Namespace:    stackName, | ||||
| 		Prune:        false, | ||||
| 		ResolveImage: stack.ResolveImageAlways, | ||||
| 		Detach:       false, | ||||
| 	} | ||||
|  | ||||
| 	composeFiles, err := r.GetComposeFiles(env) | ||||
| 	if err != nil { | ||||
| 		return nil, deployOpts, err | ||||
| 	} | ||||
|  | ||||
| 	deployOpts.Composefiles = composeFiles | ||||
| 	compose, err := appPkg.GetAppComposeConfig(stackName, deployOpts, env) | ||||
| 	if err != nil { | ||||
| 		return nil, deployOpts, err | ||||
| 	} | ||||
|  | ||||
| 	appPkg.ExposeAllEnv(stackName, compose, env) | ||||
|  | ||||
| 	// after the upgrade the deployment won't be in chaos state anymore | ||||
| 	appPkg.SetChaosLabel(compose, stackName, false) | ||||
| 	appPkg.SetRecipeLabel(compose, stackName, r.Name) | ||||
| 	appPkg.SetUpdateLabel(compose, stackName, env) | ||||
|  | ||||
| 	return compose, deployOpts, nil | ||||
| } | ||||
|  | ||||
| // tryUpgrade performs the upgrade if all the requirements are fulfilled. | ||||
| func tryUpgrade(cl *dockerclient.Client, stackName, recipeName string) error { | ||||
| 	if recipeName == "" { | ||||
| 		log.Debug(i18n.G("don't update %s due to missing recipe name", stackName)) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	chaos, err := getBoolLabel(cl, stackName, "chaos") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if chaos && !internal.Chaos { | ||||
| 		log.Debug(i18n.G("don't update %s due to chaos deployment", stackName)) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	updatesEnabled, err := getBoolLabel(cl, stackName, "autoupdate") | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if !updatesEnabled { | ||||
| 		log.Debug(i18n.G("don't update %s due to disabled auto updates or missing ENABLE_AUTO_UPDATE env", stackName)) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	upgradeVersion, err := getLatestUpgrade(cl, stackName, recipeName) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if upgradeVersion == "" { | ||||
| 		log.Debug(i18n.G("don't update %s due to no new version", stackName)) | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	err = upgrade(cl, stackName, recipeName, upgradeVersion) | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| // upgrade performs all necessary steps to upgrade an app. | ||||
| func upgrade(cl *dockerclient.Client, stackName, recipeName, upgradeVersion string) error { | ||||
| 	env, err := getEnv(cl, stackName) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	app := appPkg.App{ | ||||
| 		Name:   stackName, | ||||
| 		Recipe: recipe.Get(recipeName), | ||||
| 		Server: SERVER, | ||||
| 		Env:    env, | ||||
| 	} | ||||
|  | ||||
| 	r := recipe.Get(recipeName) | ||||
|  | ||||
| 	if err = processRecipeRepoVersion(r, upgradeVersion); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err = deploy.MergeAbraShEnv(app.Recipe, app.Env); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	compose, deployOpts, err := createDeployConfig(r, stackName, app.Env) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	log.Info(i18n.G("upgrade %s (%s) to version %s", stackName, recipeName, upgradeVersion)) | ||||
|  | ||||
| 	serviceNames, err := appPkg.GetAppServiceNames(app.Name) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	f, err := app.Filters(true, false, serviceNames...) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	err = stack.RunDeploy( | ||||
| 		cl, | ||||
| 		deployOpts, | ||||
| 		compose, | ||||
| 		stackName, | ||||
| 		app.Server, | ||||
| 		true, | ||||
| 		f, | ||||
| 	) | ||||
|  | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func newKadabraApp(version, commit string) *cobra.Command { | ||||
| 	rootCmd := &cobra.Command{ | ||||
| 		// translators: `kadabra` binary name | ||||
| 		Use:     i18n.G("kadabra [cmd] [flags]"), | ||||
| 		Version: fmt.Sprintf("%s-%s", version, commit[:7]), | ||||
| 		// translators: Short description for `kababra` binary | ||||
| 		Short: i18n.G("The Co-op Cloud auto-updater 🤖 🚀"), | ||||
| 		PersistentPreRun: func(cmd *cobra.Command, args []string) { | ||||
| 			log.Logger.SetStyles(charmLog.DefaultStyles()) | ||||
| 			charmLog.SetDefault(log.Logger) | ||||
|  | ||||
| 			if internal.Debug { | ||||
| 				log.SetLevel(log.DebugLevel) | ||||
| 				log.SetOutput(os.Stderr) | ||||
| 				log.SetReportCaller(true) | ||||
| 			} | ||||
|  | ||||
| 			log.Debug(i18n.G("kadabra version %s, commit %s", version, commit)) | ||||
| 		}, | ||||
| 	} | ||||
|  | ||||
| 	rootCmd.PersistentFlags().BoolVarP( | ||||
| 		&internal.Debug, | ||||
| 		i18n.G("debug"), | ||||
| 		i18n.G("d"), | ||||
| 		false, | ||||
| 		i18n.G("show debug messages"), | ||||
| 	) | ||||
|  | ||||
| 	rootCmd.PersistentFlags().BoolVarP( | ||||
| 		&internal.NoInput, | ||||
| 		i18n.G("no-input"), | ||||
| 		i18n.G("n"), | ||||
| 		false, | ||||
| 		i18n.G("toggle non-interactive mode"), | ||||
| 	) | ||||
|  | ||||
| 	rootCmd.AddCommand( | ||||
| 		NotifyCommand, | ||||
| 		UpgradeCommand, | ||||
| 	) | ||||
|  | ||||
| 	return rootCmd | ||||
| } | ||||
|  | ||||
| // RunApp runs CLI abra app. | ||||
| func RunApp(version, commit string) { | ||||
| 	app := newKadabraApp(version, commit) | ||||
|  | ||||
| 	if err := app.Execute(); err != nil { | ||||
| 		log.Fatal(err) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	includeMajorUpdates bool | ||||
| 	updateAll           bool | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	NotifyCommand.Flags().BoolVarP( | ||||
| 		&includeMajorUpdates, | ||||
| 		"major", | ||||
| 		"m", | ||||
| 		false, | ||||
| 		"check for major updates", | ||||
| 	) | ||||
|  | ||||
| 	UpgradeCommand.Flags().BoolVarP( | ||||
| 		&internal.Chaos, | ||||
| 		i18n.G("chaos"), | ||||
| 		i18n.G("C"), | ||||
| 		false, | ||||
| 		i18n.G("ignore uncommitted recipes changes"), | ||||
| 	) | ||||
|  | ||||
| 	UpgradeCommand.Flags().BoolVarP( | ||||
| 		&includeMajorUpdates, | ||||
| 		i18n.G("major"), | ||||
| 		i18n.G("m"), | ||||
| 		false, | ||||
| 		i18n.G("check for major updates"), | ||||
| 	) | ||||
|  | ||||
| 	UpgradeCommand.Flags().BoolVarP( | ||||
| 		&updateAll, | ||||
| 		i18n.G("all"), | ||||
| 		i18n.G("a"), | ||||
| 		false, | ||||
| 		i18n.G("update all deployed apps"), | ||||
| 	) | ||||
| } | ||||
| @ -1,23 +0,0 @@ | ||||
| // Package main provides the command-line entrypoint. | ||||
| package main | ||||
|  | ||||
| import ( | ||||
| 	"coopcloud.tech/abra/cli/updater" | ||||
| ) | ||||
|  | ||||
| // Version is the current version of Kadabra. | ||||
| var Version string | ||||
|  | ||||
| // Commit is the current git commit of Kadabra. | ||||
| var Commit string | ||||
|  | ||||
| func main() { | ||||
| 	if Version == "" { | ||||
| 		Version = "dev" | ||||
| 	} | ||||
| 	if Commit == "" { | ||||
| 		Commit = "       " | ||||
| 	} | ||||
|  | ||||
| 	updater.RunApp(Version, Commit) | ||||
| } | ||||
							
								
								
									
										83
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										83
									
								
								go.mod
									
									
									
									
									
								
							| @ -8,13 +8,15 @@ require ( | ||||
| 	coopcloud.tech/tagcmp v0.0.0-20250818180036-0ec1b205b5ca | ||||
| 	git.coopcloud.tech/toolshed/godotenv v1.5.2-0.20250103171850-4d0ca41daa5c | ||||
| 	github.com/AlecAivazis/survey/v2 v2.3.7 | ||||
| 	github.com/charmbracelet/bubbletea v1.3.6 | ||||
| 	github.com/charmbracelet/bubbles v0.21.0 | ||||
| 	github.com/charmbracelet/bubbletea v1.3.10 | ||||
| 	github.com/charmbracelet/lipgloss v1.1.0 | ||||
| 	github.com/charmbracelet/log v0.4.2 | ||||
| 	github.com/distribution/reference v0.6.0 | ||||
| 	github.com/docker/cli v28.3.3+incompatible | ||||
| 	github.com/docker/docker v28.3.3+incompatible | ||||
| 	github.com/docker/cli v28.4.0+incompatible | ||||
| 	github.com/docker/docker v28.4.0+incompatible | ||||
| 	github.com/docker/go-units v0.5.0 | ||||
| 	github.com/evertras/bubble-table v0.19.2 | ||||
| 	github.com/go-git/go-git/v5 v5.16.2 | ||||
| 	github.com/google/go-cmp v0.7.0 | ||||
| 	github.com/leonelquinteros/gotext v1.7.2 | ||||
| @ -22,7 +24,7 @@ require ( | ||||
| 	github.com/moby/term v0.5.2 | ||||
| 	github.com/pkg/errors v0.9.1 | ||||
| 	github.com/schollz/progressbar/v3 v3.18.0 | ||||
| 	golang.org/x/term v0.34.0 | ||||
| 	golang.org/x/term v0.35.0 | ||||
| 	gopkg.in/yaml.v3 v3.0.1 | ||||
| 	gotest.tools/v3 v3.5.2 | ||||
| ) | ||||
| @ -33,22 +35,24 @@ require ( | ||||
| 	github.com/BurntSushi/toml v1.5.0 // indirect | ||||
| 	github.com/Microsoft/go-winio v0.6.2 // indirect | ||||
| 	github.com/ProtonMail/go-crypto v1.3.0 // indirect | ||||
| 	github.com/atotto/clipboard v0.1.4 // indirect | ||||
| 	github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect | ||||
| 	github.com/beorn7/perks v1.0.1 // indirect | ||||
| 	github.com/cenkalti/backoff/v4 v4.3.0 // indirect | ||||
| 	github.com/cenkalti/backoff/v5 v5.0.3 // indirect | ||||
| 	github.com/cespare/xxhash/v2 v2.3.0 // indirect | ||||
| 	github.com/charmbracelet/colorprofile v0.3.2 // indirect | ||||
| 	github.com/charmbracelet/x/ansi v0.10.1 // indirect | ||||
| 	github.com/charmbracelet/x/ansi v0.10.2 // indirect | ||||
| 	github.com/charmbracelet/x/cellbuf v0.0.13 // indirect | ||||
| 	github.com/charmbracelet/x/term v0.2.1 // indirect | ||||
| 	github.com/clipperhouse/uax29/v2 v2.2.0 // indirect | ||||
| 	github.com/cloudflare/circl v1.6.1 // indirect | ||||
| 	github.com/containerd/errdefs v1.0.0 // indirect | ||||
| 	github.com/containerd/errdefs/pkg v0.3.0 // indirect | ||||
| 	github.com/containerd/log v0.1.0 // indirect | ||||
| 	github.com/containerd/platforms v0.2.1 // indirect | ||||
| 	github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect | ||||
| 	github.com/cyphar/filepath-securejoin v0.4.1 // indirect | ||||
| 	github.com/cyphar/filepath-securejoin v0.5.0 // indirect | ||||
| 	github.com/davecgh/go-spew v1.1.1 // indirect | ||||
| 	github.com/docker/distribution v2.8.3+incompatible // indirect | ||||
| 	github.com/docker/go-connections v0.6.0 // indirect | ||||
| @ -64,21 +68,21 @@ require ( | ||||
| 	github.com/go-logr/logr v1.4.3 // indirect | ||||
| 	github.com/go-logr/stdr v1.2.2 // indirect | ||||
| 	github.com/go-viper/mapstructure/v2 v2.4.0 // indirect | ||||
| 	github.com/gogo/protobuf v1.3.2 // indirect | ||||
| 	github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 // indirect | ||||
| 	github.com/google/uuid v1.6.0 // indirect | ||||
| 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect | ||||
| 	github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 // indirect | ||||
| 	github.com/hashicorp/go-cleanhttp v0.5.2 // indirect | ||||
| 	github.com/inconshreveable/mousetrap v1.1.0 // indirect | ||||
| 	github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect | ||||
| 	github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect | ||||
| 	github.com/kevinburke/ssh_config v1.2.0 // indirect | ||||
| 	github.com/kevinburke/ssh_config v1.4.0 // indirect | ||||
| 	github.com/klauspost/compress v1.18.0 // indirect | ||||
| 	github.com/lucasb-eyer/go-colorful v1.2.0 // indirect | ||||
| 	github.com/klauspost/cpuid/v2 v2.3.0 // indirect | ||||
| 	github.com/lucasb-eyer/go-colorful v1.3.0 // indirect | ||||
| 	github.com/mattn/go-colorable v0.1.14 // indirect | ||||
| 	github.com/mattn/go-isatty v0.0.20 // indirect | ||||
| 	github.com/mattn/go-localereader v0.0.1 // indirect | ||||
| 	github.com/mattn/go-runewidth v0.0.16 // indirect | ||||
| 	github.com/mattn/go-runewidth v0.0.19 // indirect | ||||
| 	github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect | ||||
| 	github.com/miekg/pkcs11 v1.1.1 // indirect | ||||
| 	github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect | ||||
| @ -90,47 +94,48 @@ require ( | ||||
| 	github.com/morikuni/aec v1.0.0 // indirect | ||||
| 	github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect | ||||
| 	github.com/muesli/cancelreader v0.2.2 // indirect | ||||
| 	github.com/muesli/reflow v0.3.0 // indirect | ||||
| 	github.com/muesli/termenv v0.16.0 // indirect | ||||
| 	github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect | ||||
| 	github.com/opencontainers/go-digest v1.0.0 // indirect | ||||
| 	github.com/opencontainers/runc v1.1.13 // indirect | ||||
| 	github.com/opencontainers/runtime-spec v1.1.0 // indirect | ||||
| 	github.com/pjbgf/sha1cd v0.4.0 // indirect | ||||
| 	github.com/pjbgf/sha1cd v0.5.0 // indirect | ||||
| 	github.com/pmezard/go-difflib v1.0.0 // indirect | ||||
| 	github.com/prometheus/client_model v0.6.2 // indirect | ||||
| 	github.com/prometheus/common v0.65.0 // indirect | ||||
| 	github.com/prometheus/common v0.66.1 // indirect | ||||
| 	github.com/prometheus/procfs v0.17.0 // indirect | ||||
| 	github.com/rivo/uniseg v0.4.7 // indirect | ||||
| 	github.com/russross/blackfriday/v2 v2.1.0 // indirect | ||||
| 	github.com/sirupsen/logrus v1.9.3 // indirect | ||||
| 	github.com/skeema/knownhosts v1.3.1 // indirect | ||||
| 	github.com/spf13/pflag v1.0.7 // indirect | ||||
| 	github.com/spf13/pflag v1.0.10 // indirect | ||||
| 	github.com/xanzy/ssh-agent v0.3.3 // indirect | ||||
| 	github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect | ||||
| 	github.com/xeipuuv/gojsonschema v1.2.0 // indirect | ||||
| 	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect | ||||
| 	go.opentelemetry.io/auto/sdk v1.1.0 // indirect | ||||
| 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 // indirect | ||||
| 	go.opentelemetry.io/otel v1.37.0 // indirect | ||||
| 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 // indirect | ||||
| 	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 // indirect | ||||
| 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 // indirect | ||||
| 	go.opentelemetry.io/auto/sdk v1.2.1 // indirect | ||||
| 	go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 // indirect | ||||
| 	go.opentelemetry.io/otel v1.38.0 // indirect | ||||
| 	go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 // indirect | ||||
| 	go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 // indirect | ||||
| 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 // indirect | ||||
| 	go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 // indirect | ||||
| 	go.opentelemetry.io/otel/metric v1.37.0 // indirect | ||||
| 	go.opentelemetry.io/otel/sdk v1.37.0 // indirect | ||||
| 	go.opentelemetry.io/otel/sdk/metric v1.37.0 // indirect | ||||
| 	go.opentelemetry.io/otel/trace v1.37.0 // indirect | ||||
| 	go.opentelemetry.io/proto/otlp v1.7.1 // indirect | ||||
| 	golang.org/x/crypto v0.41.0 // indirect | ||||
| 	golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 // indirect | ||||
| 	golang.org/x/net v0.43.0 // indirect | ||||
| 	golang.org/x/sync v0.16.0 // indirect | ||||
| 	golang.org/x/text v0.28.0 // indirect | ||||
| 	golang.org/x/time v0.12.0 // indirect | ||||
| 	google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a // indirect | ||||
| 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a // indirect | ||||
| 	google.golang.org/grpc v1.74.2 // indirect | ||||
| 	google.golang.org/protobuf v1.36.7 // indirect | ||||
| 	go.opentelemetry.io/otel/metric v1.38.0 // indirect | ||||
| 	go.opentelemetry.io/otel/sdk v1.38.0 // indirect | ||||
| 	go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect | ||||
| 	go.opentelemetry.io/otel/trace v1.38.0 // indirect | ||||
| 	go.opentelemetry.io/proto/otlp v1.8.0 // indirect | ||||
| 	go.yaml.in/yaml/v2 v2.4.3 // indirect | ||||
| 	golang.org/x/crypto v0.42.0 // indirect | ||||
| 	golang.org/x/exp v0.0.0-20250911091902-df9299821621 // indirect | ||||
| 	golang.org/x/net v0.44.0 // indirect | ||||
| 	golang.org/x/text v0.29.0 // indirect | ||||
| 	golang.org/x/time v0.13.0 // indirect | ||||
| 	google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 // indirect | ||||
| 	google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 // indirect | ||||
| 	google.golang.org/grpc v1.75.1 // indirect | ||||
| 	google.golang.org/protobuf v1.36.9 // indirect | ||||
| 	gopkg.in/warnings.v0 v0.1.2 // indirect | ||||
| 	gopkg.in/yaml.v2 v2.4.0 // indirect | ||||
| ) | ||||
| @ -147,11 +152,11 @@ require ( | ||||
| 	github.com/moby/patternmatcher v0.6.0 // indirect | ||||
| 	github.com/moby/sys/sequential v0.6.0 // indirect | ||||
| 	github.com/opencontainers/image-spec v1.1.1 // indirect | ||||
| 	github.com/prometheus/client_golang v1.23.0 // indirect | ||||
| 	github.com/prometheus/client_golang v1.23.2 // indirect | ||||
| 	github.com/sergi/go-diff v1.4.0 // indirect | ||||
| 	github.com/spf13/cobra v1.9.1 | ||||
| 	github.com/stretchr/testify v1.10.0 | ||||
| 	github.com/spf13/cobra v1.10.1 | ||||
| 	github.com/stretchr/testify v1.11.1 | ||||
| 	github.com/theupdateframework/notary v0.7.0 // indirect | ||||
| 	github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect | ||||
| 	golang.org/x/sys v0.35.0 | ||||
| 	golang.org/x/sys v0.36.0 | ||||
| ) | ||||
|  | ||||
							
								
								
									
										175
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										175
									
								
								go.sum
									
									
									
									
									
								
							| @ -99,6 +99,8 @@ github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5 | ||||
| github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= | ||||
| github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs= | ||||
| github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= | ||||
| github.com/atotto/clipboard v0.1.4 h1:EH0zSVneZPSuFR11BlR9YppQTVDbh5+16AmcJi4g1z4= | ||||
| github.com/atotto/clipboard v0.1.4/go.mod h1:ZY9tmq7sm5xIbd9bOK4onWV4S6X0u6GY7Vn0Yu86PYI= | ||||
| github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= | ||||
| github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= | ||||
| github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= | ||||
| @ -133,20 +135,22 @@ github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghf | ||||
| github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | ||||
| github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= | ||||
| github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= | ||||
| github.com/charmbracelet/bubbletea v1.3.6 h1:VkHIxPJQeDt0aFJIsVxw8BQdh/F/L2KKZGsK6et5taU= | ||||
| github.com/charmbracelet/bubbletea v1.3.6/go.mod h1:oQD9VCRQFF8KplacJLo28/jofOI2ToOfGYeFgBBxHOc= | ||||
| github.com/charmbracelet/bubbles v0.21.0 h1:9TdC97SdRVg/1aaXNVWfFH3nnLAwOXr8Fn6u6mfQdFs= | ||||
| github.com/charmbracelet/bubbles v0.21.0/go.mod h1:HF+v6QUR4HkEpz62dx7ym2xc71/KBHg+zKwJtMw+qtg= | ||||
| github.com/charmbracelet/bubbletea v1.3.10 h1:otUDHWMMzQSB0Pkc87rm691KZ3SWa4KUlvF9nRvCICw= | ||||
| github.com/charmbracelet/bubbletea v1.3.10/go.mod h1:ORQfo0fk8U+po9VaNvnV95UPWA1BitP1E0N6xJPlHr4= | ||||
| github.com/charmbracelet/colorprofile v0.3.2 h1:9J27WdztfJQVAQKX2WOlSSRB+5gaKqqITmrvb1uTIiI= | ||||
| github.com/charmbracelet/colorprofile v0.3.2/go.mod h1:mTD5XzNeWHj8oqHb+S1bssQb7vIHbepiebQ2kPKVKbI= | ||||
| github.com/charmbracelet/lipgloss v1.1.0 h1:vYXsiLHVkK7fp74RkV7b2kq9+zDLoEU4MZoFqR/noCY= | ||||
| github.com/charmbracelet/lipgloss v1.1.0/go.mod h1:/6Q8FR2o+kj8rz4Dq0zQc3vYf7X+B0binUUBwA0aL30= | ||||
| github.com/charmbracelet/log v0.4.2 h1:hYt8Qj6a8yLnvR+h7MwsJv/XvmBJXiueUcI3cIxsyig= | ||||
| github.com/charmbracelet/log v0.4.2/go.mod h1:qifHGX/tc7eluv2R6pWIpyHDDrrb/AG71Pf2ysQu5nw= | ||||
| github.com/charmbracelet/x/ansi v0.10.1 h1:rL3Koar5XvX0pHGfovN03f5cxLbCF2YvLeyz7D2jVDQ= | ||||
| github.com/charmbracelet/x/ansi v0.10.1/go.mod h1:3RQDQ6lDnROptfpWuUVIUG64bD2g2BgntdxH0Ya5TeE= | ||||
| github.com/charmbracelet/x/ansi v0.10.2 h1:ith2ArZS0CJG30cIUfID1LXN7ZFXRCww6RUvAPA+Pzw= | ||||
| github.com/charmbracelet/x/ansi v0.10.2/go.mod h1:HbLdJjQH4UH4AqA2HpRWuWNluRE6zxJH/yteYEYCFa8= | ||||
| github.com/charmbracelet/x/cellbuf v0.0.13 h1:/KBBKHuVRbq1lYx5BzEHBAFBP8VcQzJejZ/IA3iR28k= | ||||
| github.com/charmbracelet/x/cellbuf v0.0.13/go.mod h1:xe0nKWGd3eJgtqZRaN9RjMtK7xUYchjzPr7q6kcvCCs= | ||||
| github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a h1:G99klV19u0QnhiizODirwVksQB91TJKV/UaTnACcG30= | ||||
| github.com/charmbracelet/x/exp/golden v0.0.0-20240806155701-69247e0abc2a/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= | ||||
| github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91 h1:payRxjMjKgx2PaCWLZ4p3ro9y97+TVLZNaRZgJwSVDQ= | ||||
| github.com/charmbracelet/x/exp/golden v0.0.0-20241011142426-46044092ad91/go.mod h1:wDlXFlCrmJ8J+swcL/MnGUuYnqgQdW9rhSD61oNMb6U= | ||||
| github.com/charmbracelet/x/term v0.2.1 h1:AQeHeLZ1OqSXhrAWpYUtZyX1T3zVxfpZuEQMIQaGIAQ= | ||||
| github.com/charmbracelet/x/term v0.2.1/go.mod h1:oQ4enTYFV7QN4m0i9mzHrViD7TQKvNEEkHUMCmsxdUg= | ||||
| github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= | ||||
| @ -164,6 +168,8 @@ github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJ | ||||
| github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= | ||||
| github.com/cilium/ebpf v0.7.0/go.mod h1:/oI2+1shJiTGAMgl6/RgJr36Eo1jzrRcAWbcXO2usCA= | ||||
| github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= | ||||
| 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/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= | ||||
| github.com/cloudflare/circl v1.6.1 h1:zqIqSPIndyBh1bjLVVDHMPpVKqp8Su/V+6MeDzzQBQ0= | ||||
| github.com/cloudflare/circl v1.6.1/go.mod h1:uddAzsPgqdMAYatqJ0lsjX1oECcQLIlRpzZh3pJrofs= | ||||
| @ -296,8 +302,8 @@ github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= | ||||
| github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= | ||||
| github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= | ||||
| github.com/cyphar/filepath-securejoin v0.2.3/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= | ||||
| github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= | ||||
| 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/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= | ||||
| github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= | ||||
| github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= | ||||
| @ -316,16 +322,16 @@ github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5Qvfr | ||||
| github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= | ||||
| github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= | ||||
| github.com/docker/cli v0.0.0-20191017083524-a8ff7f821017/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= | ||||
| github.com/docker/cli v28.3.3+incompatible h1:fp9ZHAr1WWPGdIWBM1b3zLtgCF+83gRdVMTJsUeiyAo= | ||||
| github.com/docker/cli v28.3.3+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= | ||||
| github.com/docker/cli v28.4.0+incompatible h1:RBcf3Kjw2pMtwui5V0DIMdyeab8glEw5QY0UUU4C9kY= | ||||
| github.com/docker/cli v28.4.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= | ||||
| github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= | ||||
| github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= | ||||
| github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= | ||||
| github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= | ||||
| github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= | ||||
| github.com/docker/docker v1.4.2-0.20190924003213-a8608b5b67c7/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= | ||||
| github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= | ||||
| github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= | ||||
| github.com/docker/docker v28.4.0+incompatible h1:KVC7bz5zJY/4AZe/78BIvCnPsLaC9T/zh72xnlrTTOk= | ||||
| github.com/docker/docker v28.4.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= | ||||
| github.com/docker/docker-credential-helpers v0.6.3/go.mod h1:WRaJzqw3CTB9bk10avuGsjVBZsD05qeibJ1/TYlvc0Y= | ||||
| github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= | ||||
| github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= | ||||
| @ -367,6 +373,8 @@ github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f h1:Y/CXytFA4m6 | ||||
| github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f/go.mod h1:vw97MGsxSvLiUE2X8qFplwetxpGLQrlU1Q9AUEIzCaM= | ||||
| github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= | ||||
| github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= | ||||
| github.com/evertras/bubble-table v0.19.2 h1:u77oiM6JlRR+CvS5FZc3Hz+J6iEsvEDcR5kO8OFb1Yw= | ||||
| github.com/evertras/bubble-table v0.19.2/go.mod h1:ifHujS1YxwnYSOgcR2+m3GnJ84f7CVU/4kUOxUCjEbQ= | ||||
| github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= | ||||
| github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= | ||||
| github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= | ||||
| @ -439,7 +447,6 @@ github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zV | ||||
| github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= | ||||
| github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= | ||||
| github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= | ||||
| github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= | ||||
| github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= | ||||
| github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= | ||||
| github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= | ||||
| @ -529,8 +536,8 @@ github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgf | ||||
| github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= | ||||
| github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= | ||||
| github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= | ||||
| github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= | ||||
| github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= | ||||
| github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= | ||||
| github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= | ||||
| github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4= | ||||
| github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | ||||
| github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= | ||||
| @ -579,8 +586,8 @@ github.com/juju/loggo v0.0.0-20190526231331-6e530bcce5d8/go.mod h1:vgyd7OREkbtVE | ||||
| github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= | ||||
| github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= | ||||
| github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= | ||||
| github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= | ||||
| github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= | ||||
| github.com/kevinburke/ssh_config v1.4.0 h1:6xxtP5bZ2E4NF5tuQulISpTO2z8XbtH8cg1PWkxoFkQ= | ||||
| github.com/kevinburke/ssh_config v1.4.0/go.mod h1:q2RIzfka+BXARoNexmF9gkxEX7DmvbW9P4hIVx2Kg4M= | ||||
| github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= | ||||
| github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= | ||||
| github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= | ||||
| @ -590,6 +597,8 @@ github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdY | ||||
| github.com/klauspost/compress v1.14.2/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= | ||||
| github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= | ||||
| github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= | ||||
| github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= | ||||
| github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= | ||||
| github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= | ||||
| github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | ||||
| github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= | ||||
| @ -611,8 +620,8 @@ github.com/leonelquinteros/gotext v1.7.2 h1:bDPndU8nt+/kRo1m4l/1OXiiy2v7Z7dfPQ9+ | ||||
| github.com/leonelquinteros/gotext v1.7.2/go.mod h1:9/haCkm5P7Jay1sxKDGJ5WIg4zkz8oZKw4ekNpALob8= | ||||
| github.com/lib/pq v0.0.0-20150723085316-0dad96c0b94f/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= | ||||
| github.com/linuxkit/virtsock v0.0.0-20201010232012-f8cee7dfc7a3/go.mod h1:3r6x7q95whyfWQpmGZTu3gk3v2YkMi05HEzl7Tf7YEo= | ||||
| github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= | ||||
| github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | ||||
| github.com/lucasb-eyer/go-colorful v1.3.0 h1:2/yBRLdWBZKrf7gB40FoiKfAWYQ0lqNcbuQwVHXptag= | ||||
| github.com/lucasb-eyer/go-colorful v1.3.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0= | ||||
| github.com/magiconair/properties v1.5.3/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= | ||||
| github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= | ||||
| github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= | ||||
| @ -631,8 +640,9 @@ github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D | ||||
| github.com/mattn/go-localereader v0.0.1 h1:ygSAOl7ZXTx4RdPYinUpg6W99U8jWvWi9Ye2JC/oIi4= | ||||
| github.com/mattn/go-localereader v0.0.1/go.mod h1:8fBrzywKY7BI3czFoHkuzRoWE9C+EiG4R1k4Cjx5p88= | ||||
| github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= | ||||
| github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= | ||||
| github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= | ||||
| github.com/mattn/go-runewidth v0.0.12/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= | ||||
| 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/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= | ||||
| github.com/mattn/go-shellwords v1.0.6/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= | ||||
| github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= | ||||
| @ -692,6 +702,8 @@ github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 h1:ZK8zHtRHOkbHy6Mmr5D | ||||
| github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6/go.mod h1:CJlz5H+gyd6CUWT45Oy4q24RdLyn7Md9Vj2/ldJBSIo= | ||||
| github.com/muesli/cancelreader v0.2.2 h1:3I4Kt4BQjOR54NavqnDogx/MIoWBFa0StPA8ELUXHmA= | ||||
| github.com/muesli/cancelreader v0.2.2/go.mod h1:3XuTXfFS2VjM+HTLZY9Ak0l6eUKfijIfMUZ4EgX0QYo= | ||||
| github.com/muesli/reflow v0.3.0 h1:IFsN6K9NfGtjeggFP+68I4chLZV2yIKsXJFNZ+eWh6s= | ||||
| github.com/muesli/reflow v0.3.0/go.mod h1:pbwTDkVPibjO2kyvBQRBxTWEEGDGq0FlB1BIKtnHY/8= | ||||
| github.com/muesli/termenv v0.16.0 h1:S5AlUN9dENB57rsbnkPyfdGuWIlkmzJjbFf0Tf5FWUc= | ||||
| github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3VfY/Cnk= | ||||
| github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= | ||||
| @ -758,8 +770,8 @@ github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFSt | ||||
| github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= | ||||
| github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= | ||||
| github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= | ||||
| github.com/pjbgf/sha1cd v0.4.0 h1:NXzbL1RvjTUi6kgYZCX3fPwwl27Q1LJndxtUDVfJGRY= | ||||
| github.com/pjbgf/sha1cd v0.4.0/go.mod h1:zQWigSxVmsHEZow5qaLtPYxpcKMMQpa09ixqBxuCS6A= | ||||
| github.com/pjbgf/sha1cd v0.5.0 h1:a+UkboSi1znleCDUNT3M5YxjOnN1fz2FhN48FlwCxs0= | ||||
| github.com/pjbgf/sha1cd v0.5.0/go.mod h1:lhpGlyHLpQZoxMv8HcgXvZEhcGs0PG/vsZnEJ7H0iCM= | ||||
| github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= | ||||
| @ -775,8 +787,8 @@ github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDf | ||||
| github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= | ||||
| github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= | ||||
| github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= | ||||
| github.com/prometheus/client_golang v1.23.0 h1:ust4zpdl9r4trLY/gSjlm07PuiBq2ynaXXlptpfy8Uc= | ||||
| github.com/prometheus/client_golang v1.23.0/go.mod h1:i/o0R9ByOnHX0McrTMTyhYvKE4haaf2mW08I+jGAjEE= | ||||
| github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= | ||||
| github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= | ||||
| github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= | ||||
| github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= | ||||
| github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= | ||||
| @ -790,8 +802,8 @@ github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y8 | ||||
| github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= | ||||
| github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= | ||||
| github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= | ||||
| github.com/prometheus/common v0.65.0 h1:QDwzd+G1twt//Kwj/Ww6E9FQq1iVMmODnILtW1t2VzE= | ||||
| github.com/prometheus/common v0.65.0/go.mod h1:0gZns+BLRQ3V6NdaerOhMbwwRbNh9hkGINtQAsP5GS8= | ||||
| github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= | ||||
| github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= | ||||
| github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= | ||||
| github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= | ||||
| github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= | ||||
| @ -806,6 +818,7 @@ github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1 | ||||
| github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0= | ||||
| github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw= | ||||
| github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= | ||||
| github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||||
| github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= | ||||
| github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= | ||||
| github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= | ||||
| @ -851,8 +864,8 @@ github.com/spf13/cobra v0.0.1/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3 | ||||
| github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= | ||||
| github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= | ||||
| github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= | ||||
| github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= | ||||
| github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= | ||||
| github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= | ||||
| github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= | ||||
| github.com/spf13/jwalterweatherman v0.0.0-20141219030609-3d60171a6431/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= | ||||
| github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= | ||||
| github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | ||||
| @ -861,9 +874,9 @@ github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bd | ||||
| github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | ||||
| github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= | ||||
| github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||
| github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||
| github.com/spf13/pflag v1.0.7 h1:vN6T9TfwStFPFM5XzjsvmzZkLuaLX+HS+0SeFLRgU6M= | ||||
| github.com/spf13/pflag v1.0.7/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||
| github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||
| github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= | ||||
| github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= | ||||
| github.com/spf13/viper v0.0.0-20150530192845-be5ff3e4840c/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= | ||||
| github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= | ||||
| github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= | ||||
| @ -878,8 +891,8 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P | ||||
| github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= | ||||
| github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= | ||||
| github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= | ||||
| github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= | ||||
| github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= | ||||
| github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= | ||||
| github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= | ||||
| github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= | ||||
| github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 h1:kdXcSzyDtseVEc4yCz2qF8ZrQvIDBJLl4S1c3GCXmoI= | ||||
| @ -937,37 +950,39 @@ go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= | ||||
| go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= | ||||
| go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | ||||
| go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= | ||||
| go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= | ||||
| go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= | ||||
| go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0 h1:Hf9xI/XLML9ElpiHVDNwvqI0hIFlzV8dgIr35kV1kRU= | ||||
| go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.62.0/go.mod h1:NfchwuyNoMcZ5MLHwPrODwUF1HWCXWrL31s8gSAdIKY= | ||||
| go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= | ||||
| go.opentelemetry.io/otel v1.37.0/go.mod h1:ehE/umFRLnuLa/vSccNq9oS1ErUlkkK71gMcN34UG8I= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0 h1:zG8GlgXCJQd5BU98C0hZnBbElszTmUgCNCfYneaDL0A= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.37.0/go.mod h1:hOfBCz8kv/wuq73Mx2H2QnWokh/kHZxkh6SNF2bdKtw= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0 h1:Ahq7pZmv87yiyn3jeFz/LekZmPLLdKejuO3NcK9MssM= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.37.0/go.mod h1:MJTqhM0im3mRLw1i8uGHnCvUEeS7VwRyxlLC78PA18M= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0 h1:EtFWSnwW9hGObjkIdmlnWSydO+Qs8OwzfzXLUPg4xOc= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.37.0/go.mod h1:QjUEoiGCPkvFZ/MjK6ZZfNOS6mfVEVKYE99dFhuN2LI= | ||||
| go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= | ||||
| go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= | ||||
| go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0 h1:RbKq8BG0FI8OiXhBfcRtqqHcZcka+gU3cskNuf05R18= | ||||
| go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.63.0/go.mod h1:h06DGIukJOevXaj/xrNjhi/2098RZzcLTbc0jDAUbsg= | ||||
| go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= | ||||
| go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0 h1:vl9obrcoWVKp/lwl8tRE33853I8Xru9HFbw/skNeLs8= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.38.0/go.mod h1:GAXRxmLJcVM3u22IjTg74zWBrRCKq8BnOqUVLodpcpw= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0 h1:GqRJVj7UmLjCVyVJ3ZFLdPRmhDUp2zFmQe3RHIOsw24= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.38.0/go.mod h1:ri3aaHSmCTVYu2AWv44YMauwAQc0aqI9gHKIcSbI1pU= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0 h1:lwI4Dc5leUqENgGuQImwLo4WnuXFPetmPpkLi2IrX54= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.38.0/go.mod h1:Kz/oCE7z5wuyhPxsXDuaPteSWqjSBD5YaSdbxZYGbGk= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0 h1:IeMeyr1aBvBiPVYihXIaeIZba6b8E1bYp7lbdxK8CQg= | ||||
| go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.19.0/go.mod h1:oVdCUtjq9MK9BlS7TtucsQwUcXcymNiEDjgDD2jMtZU= | ||||
| go.opentelemetry.io/otel/metric v1.37.0 h1:mvwbQS5m0tbmqML4NqK+e3aDiO02vsf/WgbsdpcPoZE= | ||||
| go.opentelemetry.io/otel/metric v1.37.0/go.mod h1:04wGrZurHYKOc+RKeye86GwKiTb9FKm1WHtO+4EVr2E= | ||||
| go.opentelemetry.io/otel/sdk v1.37.0 h1:ItB0QUqnjesGRvNcmAcU0LyvkVyGJ2xftD29bWdDvKI= | ||||
| go.opentelemetry.io/otel/sdk v1.37.0/go.mod h1:VredYzxUvuo2q3WRcDnKDjbdvmO0sCzOvVAiY+yUkAg= | ||||
| go.opentelemetry.io/otel/sdk/metric v1.37.0 h1:90lI228XrB9jCMuSdA0673aubgRobVZFhbjxHHspCPc= | ||||
| go.opentelemetry.io/otel/sdk/metric v1.37.0/go.mod h1:cNen4ZWfiD37l5NhS+Keb5RXVWZWpRE+9WyVCpbo5ps= | ||||
| go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mxVK7z4= | ||||
| go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= | ||||
| go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= | ||||
| go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= | ||||
| go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= | ||||
| go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= | ||||
| go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= | ||||
| go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= | ||||
| go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= | ||||
| go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= | ||||
| go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= | ||||
| go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= | ||||
| go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= | ||||
| go.opentelemetry.io/proto/otlp v1.8.0 h1:fRAZQDcAFHySxpJ1TwlA1cJ4tvcrw7nXl9xWWC8N5CE= | ||||
| go.opentelemetry.io/proto/otlp v1.8.0/go.mod h1:tIeYOeNBU4cvmPqpaji1P+KbB4Oloai8wN4rWzRrFF0= | ||||
| go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= | ||||
| go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= | ||||
| go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= | ||||
| go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= | ||||
| go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= | ||||
| go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= | ||||
| go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= | ||||
| go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= | ||||
| golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||
| golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||
| golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= | ||||
| @ -986,8 +1001,8 @@ golang.org/x/crypto v0.0.0-20201117144127-c1f2f97bffc9/go.mod h1:jdWPYTVW3xRLrWP | ||||
| golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= | ||||
| golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= | ||||
| golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= | ||||
| golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= | ||||
| golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= | ||||
| golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI= | ||||
| golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8= | ||||
| golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
| golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= | ||||
| golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= | ||||
| @ -998,8 +1013,8 @@ golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u0 | ||||
| golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= | ||||
| golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= | ||||
| golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= | ||||
| golang.org/x/exp v0.0.0-20250813145105-42675adae3e6 h1:SbTAbRFnd5kjQXbczszQ0hdk3ctwYf3qBNH9jIsGclE= | ||||
| golang.org/x/exp v0.0.0-20250813145105-42675adae3e6/go.mod h1:4QTo5u+SEIbbKW1RacMZq1YEfOBqeXa19JeshGi+zc4= | ||||
| golang.org/x/exp v0.0.0-20250911091902-df9299821621 h1:2id6c1/gto0kaHYyrixvknJ8tUK/Qs5IsmBtrc+FtgU= | ||||
| golang.org/x/exp v0.0.0-20250911091902-df9299821621/go.mod h1:TwQYMMnGpvZyc+JpB/UAuTNIsVJifOlSkrZkhcvpVUk= | ||||
| golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= | ||||
| golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= | ||||
| golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= | ||||
| @ -1063,8 +1078,8 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b | ||||
| golang.org/x/net v0.0.0-20210825183410-e898025ed96a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||||
| golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= | ||||
| golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= | ||||
| golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= | ||||
| golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= | ||||
| 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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= | ||||
| golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||
| golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= | ||||
| @ -1082,8 +1097,6 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ | ||||
| golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= | ||||
| golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= | ||||
| golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= | ||||
| golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= | ||||
| @ -1162,13 +1175,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc | ||||
| golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= | ||||
| golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= | ||||
| golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= | ||||
| golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k= | ||||
| golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= | ||||
| golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= | ||||
| golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= | ||||
| golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= | ||||
| golang.org/x/term v0.34.0 h1:O/2T7POpk0ZZ7MAzMeWFSg6S5IpWd/RXDlM9hgM3DR4= | ||||
| golang.org/x/term v0.34.0/go.mod h1:5jC53AEywhIVebHgPVeg0mj8OD3VO9OzclacVrqpaAw= | ||||
| golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ= | ||||
| golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA= | ||||
| golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= | ||||
| @ -1178,16 +1191,16 @@ golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= | ||||
| golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= | ||||
| golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= | ||||
| golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= | ||||
| golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= | ||||
| golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk= | ||||
| golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4= | ||||
| golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= | ||||
| golang.org/x/time v0.12.0 h1:ScB/8o8olJvc+CQPWrK3fPZNfh7qgwCrY0zJmoEQLSE= | ||||
| golang.org/x/time v0.12.0/go.mod h1:CDIdPxbZBQxdj6cxyCIdrNogrJKMJ7pr37NYpMcMDSg= | ||||
| golang.org/x/time v0.13.0 h1:eUlYslOIt32DgYD6utsuUeHs4d7AsEYLuIAdg7FlYgI= | ||||
| golang.org/x/time v0.13.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= | ||||
| golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= | ||||
| @ -1237,6 +1250,8 @@ golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8T | ||||
| golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= | ||||
| gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= | ||||
| gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= | ||||
| google.golang.org/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= | ||||
| google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= | ||||
| google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= | ||||
| @ -1281,10 +1296,10 @@ google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfG | ||||
| google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= | ||||
| google.golang.org/genproto v0.0.0-20200527145253-8367513e4ece/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= | ||||
| google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= | ||||
| google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a h1:DMCgtIAIQGZqJXMVzJF4MV8BlWoJh2ZuFiRdAleyr58= | ||||
| google.golang.org/genproto/googleapis/api v0.0.0-20250811230008-5f3141c8851a/go.mod h1:y2yVLIE/CSMCPXaHnSKXxu1spLPnglFLegmgdY23uuE= | ||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a h1:tPE/Kp+x9dMSwUm/uM0JKK0IfdiJkwAbSMSeZBXXJXc= | ||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20250811230008-5f3141c8851a/go.mod h1:gw1tLEfykwDz2ET4a12jcXt4couGAm7IwsVaTy0Sflo= | ||||
| google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4 h1:8XJ4pajGwOlasW+L13MnEGA8W4115jJySQtVfS2/IBU= | ||||
| google.golang.org/genproto/googleapis/api v0.0.0-20250929231259-57b25ae835d4/go.mod h1:NnuHhy+bxcg30o7FnVAZbXsPHUDQ9qKWAQKCD7VxFtk= | ||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4 h1:i8QOKZfYg6AbGVZzUAY3LrNWCKF8O6zFisU9Wl9RER4= | ||||
| google.golang.org/genproto/googleapis/rpc v0.0.0-20250929231259-57b25ae835d4/go.mod h1:HSkG/KdJWusxU1F6CNrwNDjBMgisKxGnc5dAZfT0mjQ= | ||||
| google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= | ||||
| google.golang.org/grpc v1.0.5/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= | ||||
| google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= | ||||
| @ -1304,8 +1319,8 @@ google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTp | ||||
| google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= | ||||
| google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= | ||||
| google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= | ||||
| google.golang.org/grpc v1.74.2 h1:WoosgB65DlWVC9FqI82dGsZhWFNBSLjQ84bjROOpMu4= | ||||
| google.golang.org/grpc v1.74.2/go.mod h1:CtQ+BGjaAIXHs/5YS3i473GqwBBa1zGQNevxdeBEXrM= | ||||
| google.golang.org/grpc v1.75.1 h1:/ODCNEuf9VghjgO3rqLcfg8fiOP0nSluljWFlDxELLI= | ||||
| google.golang.org/grpc v1.75.1/go.mod h1:JtPAzKiq4v1xcAB2hydNlWI2RnF85XXcV0mhKXr2ecQ= | ||||
| google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= | ||||
| google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= | ||||
| google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= | ||||
| @ -1319,8 +1334,8 @@ google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlba | ||||
| google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= | ||||
| google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||
| google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= | ||||
| google.golang.org/protobuf v1.36.7 h1:IgrO7UwFQGJdRNXH/sQux4R1Dj1WAKcLElzeeRaXV2A= | ||||
| google.golang.org/protobuf v1.36.7/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= | ||||
| google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw= | ||||
| google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= | ||||
| gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= | ||||
| gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= | ||||
| gopkg.in/cenkalti/backoff.v2 v2.2.1/go.mod h1:S0QdOvT2AlerfSBkp0O+dk+bbIMaNbEmVk876gPCthU= | ||||
|  | ||||
| @ -471,13 +471,6 @@ func GetAppStatuses(apps []App, MachineReadable bool) (map[string]map[string]str | ||||
| 				result["chaosVersion"] = chaosVersion | ||||
| 			} | ||||
|  | ||||
| 			labelKey = fmt.Sprintf("coop-cloud.%s.autoupdate", name) | ||||
| 			if autoUpdate, ok := service.Spec.Labels[labelKey]; ok { | ||||
| 				result["autoUpdate"] = autoUpdate | ||||
| 			} else { | ||||
| 				result["autoUpdate"] = "false" | ||||
| 			} | ||||
|  | ||||
| 			labelKey = fmt.Sprintf("coop-cloud.%s.version", name) | ||||
| 			if version, ok := service.Spec.Labels[labelKey]; ok { | ||||
| 				result["version"] = version | ||||
|  | ||||
| @ -5,7 +5,6 @@ import ( | ||||
| 	"fmt" | ||||
| 	"strconv" | ||||
|  | ||||
| 	"coopcloud.tech/abra/pkg/envfile" | ||||
| 	"coopcloud.tech/abra/pkg/i18n" | ||||
| 	"coopcloud.tech/abra/pkg/log" | ||||
| 	composetypes "github.com/docker/cli/cli/compose/types" | ||||
| @ -56,23 +55,6 @@ func SetVersionLabel(compose *composetypes.Config, stackName string, version str | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // SetUpdateLabel adds env ENABLE_AUTO_UPDATE as label to enable/disable the | ||||
| // auto update process for this app. The default if this variable is not set is to disable | ||||
| // the auto update process. | ||||
| func SetUpdateLabel(compose *composetypes.Config, stackName string, appEnv envfile.AppEnv) { | ||||
| 	for _, service := range compose.Services { | ||||
| 		if service.Name == "app" { | ||||
| 			enable_auto_update, exists := appEnv["ENABLE_AUTO_UPDATE"] | ||||
| 			if !exists { | ||||
| 				enable_auto_update = "false" | ||||
| 			} | ||||
| 			log.Debug(i18n.G("set label 'coop-cloud.%s.autoupdate' to %s for %s", stackName, enable_auto_update, stackName)) | ||||
| 			labelKey := fmt.Sprintf("coop-cloud.%s.autoupdate", stackName) | ||||
| 			service.Deploy.Labels[labelKey] = enable_auto_update | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // GetLabel reads docker labels in the format of "coop-cloud.${STACK_NAME}.${LABEL}" from the local compose files | ||||
| func GetLabel(compose *composetypes.Config, stackName string, label string) string { | ||||
| 	for _, service := range compose.Services { | ||||
|  | ||||
| @ -6,9 +6,11 @@ import ( | ||||
| 	"errors" | ||||
| 	"net/http" | ||||
| 	"os" | ||||
| 	"path" | ||||
| 	"strings" | ||||
| 	"time" | ||||
|  | ||||
| 	"coopcloud.tech/abra/pkg/config" | ||||
| 	contextPkg "coopcloud.tech/abra/pkg/context" | ||||
| 	"coopcloud.tech/abra/pkg/i18n" | ||||
| 	"coopcloud.tech/abra/pkg/log" | ||||
| @ -41,6 +43,11 @@ func New(serverName string, opts ...Opt) (*client.Client, error) { | ||||
|  | ||||
| 	ctx, err := GetContext(serverName) | ||||
| 	if err != nil { | ||||
| 		serverDir := path.Join(config.SERVERS_DIR, serverName) | ||||
| 		if _, err := os.Stat(serverDir); err == nil { | ||||
| 			return nil, errors.New(i18n.G("server missing context, run \"abra server add %s\"?", serverName)) | ||||
| 		} | ||||
|  | ||||
| 		return nil, errors.New(i18n.G("unknown server, run \"abra server add %s\"?", serverName)) | ||||
| 	} | ||||
|  | ||||
|  | ||||
| @ -116,10 +116,7 @@ var ( | ||||
|  | ||||
| 	DIRTY_DEFAULT = "+U" | ||||
|  | ||||
| 	NO_DOMAIN_DEFAULT  = "N/A" | ||||
| 	NO_VERSION_DEFAULT = "N/A" | ||||
| 	NO_SECRETS_DEFAULT = "N/A" | ||||
| 	NO_VOLUMES_DEFAULT = "N/A" | ||||
| 	MISSING_DEFAULT = "-" | ||||
|  | ||||
| 	UNKNOWN_DEFAULT = "unknown" | ||||
| ) | ||||
|  | ||||
| @ -20,6 +20,7 @@ var ( | ||||
| 	Locale        = DefaultLocale | ||||
| 	_, Mo         = LoadLocale() | ||||
| 	G             = Mo.Get | ||||
| 	GC            = Mo.GetC | ||||
| ) | ||||
|  | ||||
| func LoadLocale() (string, *gotext.Mo) { | ||||
|  | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
										
											Binary file not shown.
										
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -49,7 +49,7 @@ func (r Recipe) Ensure(ctx EnsureContext) error { | ||||
| 	if r.EnvVersion != "" && !ctx.IgnoreEnvVersion { | ||||
| 		log.Debug(i18n.G("ensuring env version %s", r.EnvVersion)) | ||||
| 		if strings.Contains(r.EnvVersion, "+U") { | ||||
| 			return errors.New(i18n.G("can not redeploy chaos version (%s) without --chaos", r.EnvVersion)) | ||||
| 			return errors.New(i18n.G(`cannot redeploy previous chaos version (%s), did you mean to use "--chaos"?`)) | ||||
| 		} | ||||
|  | ||||
| 		if _, err := r.EnsureVersion(r.EnvVersion); err != nil { | ||||
|  | ||||
| @ -379,7 +379,7 @@ func ReadRecipeCatalogue(offline bool) (RecipeCatalogue, error) { | ||||
|  | ||||
| 	if !offline { | ||||
| 		if err := catalogue.EnsureUpToDate(); err != nil { | ||||
| 			return nil, err | ||||
| 			return nil, fmt.Errorf("unable to update catalogue: %s", err) | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
|  | ||||
| @ -50,6 +50,11 @@ type Secret struct { | ||||
| 	// Will have this remote name: | ||||
| 	//   test_example_com_test_pass_two_v2 | ||||
| 	RemoteName string | ||||
|  | ||||
| 	// LocalName iis the name of the secret in the recipe config. This is also | ||||
| 	// the name that you pass to `abra app secret insert` and is shown on `abra | ||||
| 	// app secret list` | ||||
| 	LocalName string | ||||
| } | ||||
|  | ||||
| // GeneratePassword generates passwords. | ||||
| @ -133,7 +138,12 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin | ||||
|  | ||||
| 		lastIdx := strings.LastIndex(secretConfig.Name, "_") | ||||
| 		secretVersion := secretConfig.Name[lastIdx+1:] | ||||
| 		value := Secret{Version: secretVersion, RemoteName: secretConfig.Name} | ||||
|  | ||||
| 		value := Secret{ | ||||
| 			Version:    secretVersion, | ||||
| 			RemoteName: secretConfig.Name, | ||||
| 			LocalName:  secretId, | ||||
| 		} | ||||
|  | ||||
| 		if len(value.RemoteName) > config.MAX_DOCKER_SECRET_LENGTH { | ||||
| 			return nil, errors.New(i18n.G("secret %s is > %d chars when combined with %s", secretId, config.MAX_DOCKER_SECRET_LENGTH, stackName)) | ||||
| @ -178,6 +188,8 @@ func ReadSecretsConfig(appEnvPath string, composeFiles []string, stackName strin | ||||
| // resolveCharset sets the passgen Alphabet required for a secret | ||||
| func resolveCharset(input string) string { | ||||
| 	switch strings.ToLower(input) { | ||||
| 	case "hex": | ||||
| 		return passgen.AlphabetNumericAmbiguous + "abcdef" | ||||
| 	case "special": | ||||
| 		return passgen.AlphabetSpecial | ||||
| 	case "safespecial": | ||||
|  | ||||
| @ -48,6 +48,12 @@ func TestReadSecretsConfig(t *testing.T) { | ||||
| 	assert.Equal(t, "v1", secretsFromConfig["test_pass_six"].Version) | ||||
| 	assert.Equal(t, 0, secretsFromConfig["test_pass_six"].Length) | ||||
| 	assert.Equal(t, "abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789!@#$%^&*_-+=", secretsFromConfig["test_pass_six"].Charset) | ||||
|  | ||||
| 	// Has a length modifier and a charset=hex modifier | ||||
| 	assert.Equal(t, "test_example_com_test_pass_seven_v1", secretsFromConfig["test_pass_seven"].RemoteName) | ||||
| 	assert.Equal(t, "v1", secretsFromConfig["test_pass_seven"].Version) | ||||
| 	assert.Equal(t, 32, secretsFromConfig["test_pass_seven"].Length) | ||||
| 	assert.Equal(t, "0123456789abcdef", secretsFromConfig["test_pass_seven"].Charset) | ||||
| } | ||||
|  | ||||
| func TestReadSecretsConfigWithLongDomain(t *testing.T) { | ||||
|  | ||||
| @ -4,3 +4,4 @@ SECRET_TEST_PASS_THREE_VERSION=v2 | ||||
| SECRET_TEST_PASS_FOUR_VERSION=v1 # length=12 charset=default,safespecial | ||||
| SECRET_TEST_PASS_FIVE_VERSION=v1 # length=12 charset=default,special | ||||
| SECRET_TEST_PASS_SIX_VERSION=v1 # charset=default,special | ||||
| SECRET_TEST_PASS_SEVEN_VERSION=v1 # length=32 charset=hex | ||||
|  | ||||
| @ -11,6 +11,7 @@ services: | ||||
|       - test_pass_four | ||||
|       - test_pass_five | ||||
|       - test_pass_six | ||||
|       - test_pass_seven | ||||
|  | ||||
| secrets: | ||||
|   test_pass_one: | ||||
| @ -31,3 +32,6 @@ secrets: | ||||
|   test_pass_six: | ||||
|     external: true | ||||
|     name: ${STACK_NAME}_test_pass_six_${SECRET_TEST_PASS_SIX_VERSION} | ||||
|   test_pass_seven: | ||||
|     external: true | ||||
|     name: ${STACK_NAME}_test_pass_seven_${SECRET_TEST_PASS_SEVEN_VERSION} | ||||
|  | ||||
| @ -247,7 +247,7 @@ func waitOnTasks(ctx context.Context, client apiclient.APIClient, namespace stri | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		if terminalStatesReached == len(tasks) { | ||||
| 		if terminalStatesReached >= len(tasks) { | ||||
| 			log.Debug(i18n.G("all tasks reached terminal state")) | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
							
								
								
									
										5
									
								
								scripts/cloud-init/README.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								scripts/cloud-init/README.md
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,5 @@ | ||||
| # cloud-init | ||||
|  | ||||
| This folder contains cloud-init files for installing Abra and its dependencies. | ||||
|  | ||||
| For more information, see <https://cloudinit.readthedocs.io/en/latest/index.html> | ||||
							
								
								
									
										49
									
								
								scripts/cloud-init/cloud-init.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								scripts/cloud-init/cloud-init.yaml
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | ||||
| #cloud-config | ||||
|  | ||||
| package_update: true | ||||
| package_upgrade: true | ||||
| package_reboot_if_required: true | ||||
|  | ||||
| # https://packages.debian.org/bookworm/docker.io | ||||
| packages: | ||||
|   - ca-certificates | ||||
|   - curl | ||||
|   - docker.io | ||||
|   - docker-compose | ||||
|   # https://stackoverflow.com/a/74084180 | ||||
|   - apparmor | ||||
|  | ||||
| # https://docs.coopcloud.tech/operators/tutorial/#server-setup | ||||
| runcmd: | ||||
|   - curl -fsSL https://install.abra.coopcloud.tech | env HOME=/root bash | ||||
|   - docker swarm init | ||||
|   - docker network create -d overlay proxy | ||||
|  | ||||
| write_files: | ||||
|   # Add abra to PATH and set EDITOR | ||||
|   - path: /etc/profile.d/custom_path.sh | ||||
|     content: | | ||||
|       export PATH=$PATH:$HOME/.local/bin | ||||
|       export EDITOR=vim | ||||
|     owner: root:root | ||||
|     permissions: '0755' | ||||
|   # Send container log to journald: https://docs.coopcloud.tech/operators/handbook/#how-do-i-persist-container-logs-after-they-go-away | ||||
|   - path: /etc/docker/daemon.json | ||||
|     content: | | ||||
|       { | ||||
|           "log-driver": "journald", | ||||
|           "log-opts": { | ||||
|             "labels":"com.docker.swarm.service.name" | ||||
|           } | ||||
|       } | ||||
|     owner: root:root | ||||
|     permissions: '0644' | ||||
|   # Rotate logs | ||||
|   - path: /etc/systemd/journald.conf | ||||
|     content: | | ||||
|       [Journal] | ||||
|       Storage=persistent | ||||
|       SystemMaxUse=5G | ||||
|       MaxFileSec=1month | ||||
|     owner: root:root | ||||
|     permissions: '0644' | ||||
| @ -51,7 +51,7 @@ echo "========================================================================" | ||||
| echo "BUILDING ABRA" | ||||
| echo "========================================================================" | ||||
| export PATH="/usr/lib/go-1.21/bin:$PATH" | ||||
| make build-abra | ||||
| make build | ||||
| echo "========================================================================" | ||||
|  | ||||
| echo "========================================================================" | ||||
|  | ||||
| @ -175,7 +175,7 @@ teardown(){ | ||||
| } | ||||
|  | ||||
| # bats test_tags=slow | ||||
| @test "bail if env has a hash but no --chaos" { | ||||
| @test "do not bail if env version is a hash but no --chaos" { | ||||
|   wantHash=$(_get_n_hash 3) | ||||
|  | ||||
|   run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" reset --hard HEAD~3 | ||||
| @ -250,6 +250,7 @@ teardown(){ | ||||
|   run $ABRA app deploy "$TEST_APP_DOMAIN" \ | ||||
|     --no-input --no-converge-checks --chaos | ||||
|   assert_success | ||||
|   assert_output --regexp "NEW DEPLOYMENT.*${_get_head_hash:0:8}" | ||||
| } | ||||
|  | ||||
| # bats test_tags=slow | ||||
| @ -367,6 +368,21 @@ teardown(){ | ||||
|  | ||||
|   run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks | ||||
|   assert_failure | ||||
|   assert_output --partial "secret not generated" | ||||
| } | ||||
|  | ||||
| @test "error if secret not inserted" { | ||||
|   run sed -i 's/COMPOSE_FILE="compose.yml"/COMPOSE_FILE="compose.yml:compose.skip_pass.yml"/g' \ | ||||
|     "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" | ||||
|   assert_success | ||||
|  | ||||
|   run sed -i 's/#SECRET_TEST_SKIP_PASS_VERSION=v1/SECRET_TEST_SKIP_PASS_VERSION=v1/g' \ | ||||
|     "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" | ||||
|   assert_success | ||||
|  | ||||
|   run $ABRA app deploy "$TEST_APP_DOMAIN" --no-input --no-converge-checks | ||||
|   assert_failure | ||||
|   assert_output --partial "secret not inserted" | ||||
| } | ||||
|  | ||||
| # bats test_tags=slow | ||||
| @ -561,3 +577,18 @@ teardown(){ | ||||
|   assert_success | ||||
|   refute_output --partial "IMAGES" | ||||
| } | ||||
|  | ||||
| # bats test_tags=slow | ||||
| @test "manually created server without context bails gracefully" { | ||||
|   run mkdir -p "$ABRA_DIR/servers/default2" | ||||
|   assert_success | ||||
|   assert_exists "$ABRA_DIR/servers/default2" | ||||
|  | ||||
|   run cp "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" "$ABRA_DIR/servers/default2/$TEST_APP_DOMAIN_2.env" | ||||
|   assert_success | ||||
|   assert_exists "$ABRA_DIR/servers/default2/$TEST_APP_DOMAIN_2.env" | ||||
|  | ||||
|   run $ABRA app deploy "$TEST_APP_DOMAIN_2" --no-input --no-converge-checks | ||||
|   assert_failure | ||||
|   assert_output --partial "server missing context" | ||||
| } | ||||
|  | ||||
| @ -38,8 +38,8 @@ teardown(){ | ||||
|   assert_success | ||||
|  | ||||
|   assert_output --partial 'NEW DEPLOY OVERVIEW' | ||||
|   assert_output --partial 'CURRENT DEPLOYMENT    N/A' | ||||
|   assert_output --partial 'ENV VERSION           N/A' | ||||
|   assert_output --partial 'CURRENT DEPLOYMENT    -' | ||||
|   assert_output --partial 'ENV VERSION           -' | ||||
|   assert_output --partial "NEW DEPLOYMENT        ${latestRelease}" | ||||
|   assert_output --partial "IMAGES                nginx: ${latestRelease##*+} (new)" | ||||
|   assert_output --partial "CONFIGS               test_conf: v1 (new)" | ||||
| @ -57,7 +57,7 @@ teardown(){ | ||||
|   assert_success | ||||
|  | ||||
|   assert_output --partial 'NEW DEPLOY OVERVIEW' | ||||
|   assert_output --partial "CURRENT DEPLOYMENT    N/A" | ||||
|   assert_output --partial "CURRENT DEPLOYMENT    -" | ||||
|   assert_output --partial "ENV VERSION           ${latestRelease}" | ||||
|   assert_output --partial "NEW DEPLOYMENT        ${latestRelease}" | ||||
|   assert_output --partial "IMAGES                nginx: ${latestRelease##*+} (new)" | ||||
| @ -102,7 +102,7 @@ teardown(){ | ||||
|   assert_success | ||||
|  | ||||
|   assert_output --partial 'NEW DEPLOY OVERVIEW' | ||||
|   assert_output --partial "CURRENT DEPLOYMENT    N/A" | ||||
|   assert_output --partial "CURRENT DEPLOYMENT    -" | ||||
|   assert_output --partial "ENV VERSION           0.1.1+1.20.2" | ||||
|   assert_output --partial "NEW DEPLOYMENT        0.1.1+1.20.2" | ||||
|  | ||||
| @ -125,7 +125,7 @@ teardown(){ | ||||
|   assert_success | ||||
|  | ||||
|   assert_output --partial 'NEW DEPLOY OVERVIEW' | ||||
|   assert_output --partial "CURRENT DEPLOYMENT    N/A" | ||||
|   assert_output --partial "CURRENT DEPLOYMENT    -" | ||||
|   assert_output --partial "ENV VERSION           0.1.1+1.20.2" | ||||
|   assert_output --partial "NEW DEPLOYMENT        ${latestRelease}" | ||||
|  | ||||
| @ -163,7 +163,7 @@ teardown(){ | ||||
|   assert_success | ||||
| } | ||||
|  | ||||
| @test "can not redeploy chaos version without --chaos" { | ||||
| @test "cannot redeploy chaos version without --chaos" { | ||||
|   headHash=$(_get_head_hash) | ||||
|   latestRelease=$(_latest_release) | ||||
|  | ||||
| @ -181,7 +181,7 @@ teardown(){ | ||||
|   run $ABRA app deploy "$TEST_APP_DOMAIN" \ | ||||
|     --no-input --no-converge-checks --force --debug | ||||
|   assert_failure | ||||
|   assert_output --regexp 'can not redeploy chaos version .*' + "${headHash:0:8}+U" | ||||
|   assert_output --regexp 'cannot redeploy previous chaos version .*' + "${headHash:0:8}+U" | ||||
| } | ||||
|  | ||||
| @test "deploy then force commit deploy" { | ||||
| @ -219,7 +219,7 @@ teardown(){ | ||||
|   assert_success | ||||
|  | ||||
|   assert_output --partial 'NEW DEPLOY OVERVIEW' | ||||
|   assert_output --partial "CURRENT DEPLOYMENT    N/A" | ||||
|   assert_output --partial "CURRENT DEPLOYMENT    -" | ||||
|   assert_output --partial "ENV VERSION           ${latestRelease}" | ||||
|   assert_output --partial "NEW DEPLOYMENT        ${headHash:0:8}" | ||||
|  | ||||
|  | ||||
| @ -15,6 +15,7 @@ teardown_file(){ | ||||
|   _undeploy_app | ||||
|   _rm_app | ||||
|   _rm_server | ||||
|   _reset_recipe | ||||
|  | ||||
|   if [[ -d "$ABRA_DIR/servers/foo" ]]; then | ||||
|     run rm -rf "$ABRA_DIR/servers/foo" | ||||
| @ -173,7 +174,7 @@ teardown(){ | ||||
|  | ||||
|   run $ABRA app ls --status | ||||
|   assert_success | ||||
|   assert_output --partial "unknown server" | ||||
|   assert_output --partial "server missing context" | ||||
| } | ||||
|  | ||||
| # bats test_tags=slow | ||||
| @ -193,3 +194,16 @@ teardown(){ | ||||
|     <(jq -S "." <(echo '{}')) | ||||
|   assert_success | ||||
| } | ||||
|  | ||||
| # bats test_tags=slow | ||||
| @test "list ignores borked tags" { | ||||
|   run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" tag \ | ||||
|     -a "2.4.8_1" -m "feat: completely borked tag" | ||||
|   assert_success | ||||
|  | ||||
|   _deploy_app | ||||
|  | ||||
|   run $ABRA app ls --status --debug | ||||
|   assert_success | ||||
|   assert_output --partial "unable to parse 2.4.8_1" | ||||
| } | ||||
|  | ||||
| @ -22,8 +22,15 @@ teardown(){ | ||||
|   _reset_recipe | ||||
|   _reset_tags | ||||
|  | ||||
|   run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo" | ||||
|   assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" | ||||
|   if [[ -f "$ABRA_DIR/recipes/$TEST_RECIPE/foo" ]]; then | ||||
|     run rm -rf "$ABRA_DIR/recipes/$TEST_RECIPE/foo" | ||||
|     assert_not_exists "$ABRA_DIR/recipes/$TEST_RECIPE/foo" | ||||
|   fi | ||||
|  | ||||
|   if [[ -f "$ABRA_DIR/servers/$TEST_SERVER/rauthy.$TEST_APP_DOMAIN.env" ]]; then | ||||
|     run rm -rf "$ABRA_DIR/servers/$TEST_SERVER/rauthy.$TEST_APP_DOMAIN.env" | ||||
|     assert_not_exists "$ABRA_DIR/servers/$TEST_SERVER/rauthy.$TEST_APP_DOMAIN.env" | ||||
|   fi | ||||
| } | ||||
|  | ||||
| @test "create new app" { | ||||
| @ -270,3 +277,32 @@ teardown(){ | ||||
|   assert_success | ||||
|   refute_output --partial "requires secret generation" | ||||
| } | ||||
|  | ||||
| @test "do not warn about generation when generate=false" { | ||||
|   run $ABRA app new --domain "$TEST_APP_DOMAIN" renovate "1.0.1+41-full" | ||||
|   assert_success | ||||
|   refute_output --partial "requires secret generation" | ||||
| } | ||||
|  | ||||
| @test "warn about insertion when generate=false" { | ||||
|   run $ABRA app new --domain "$TEST_APP_DOMAIN" renovate "1.0.1+41-full" | ||||
|   assert_success | ||||
|   assert_output --partial "requires secret insertion" | ||||
| } | ||||
|  | ||||
| @test "warn about both insert/generate when generate=false/true" { | ||||
|   run $ABRA app new rauthy "1.0.0+0.32.3" \ | ||||
|     --no-input \ | ||||
|     --server "$TEST_SERVER" \ | ||||
|     --domain "rauthy.$TEST_APP_DOMAIN" | ||||
|   assert_success | ||||
|   assert_exists "$ABRA_DIR/servers/$TEST_SERVER/rauthy.$TEST_APP_DOMAIN.env" | ||||
|   assert_output --partial "requires secret generation" | ||||
|   assert_output --partial "requires secret insertion" | ||||
| } | ||||
|  | ||||
| @test "no warn about generation if already generated" { | ||||
|   run $ABRA app new "$TEST_RECIPE" --domain "$TEST_APP_DOMAIN" --secrets | ||||
|   assert_success | ||||
|   refute_output --partial "requires secret generation" | ||||
| } | ||||
|  | ||||
| @ -181,7 +181,7 @@ teardown(){ | ||||
| } | ||||
|  | ||||
| # bats test_tags=slow | ||||
| @test "rollback chaos deployment is not possible" { | ||||
| @test "rollback chaos deployment is possible" { | ||||
|   tagHash=$(_get_tag_hash "0.2.0+1.21.0") | ||||
|   run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash" | ||||
|   assert_success | ||||
| @ -191,12 +191,13 @@ teardown(){ | ||||
|   assert_output --partial "${tagHash:0:8}" | ||||
|  | ||||
|   run $ABRA app rollback "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks | ||||
|   assert_failure | ||||
|   assert_output --partial 'current deployment' + "${tagHash:0:8}" + 'is not a known version' | ||||
|   assert_success | ||||
|   assert_output --regexp "CURRENT DEPLOYMENT.*${tagHash:0:8}" | ||||
|   assert_output --regexp "ENV VERSION.*${tagHash:0:8}" | ||||
| } | ||||
|  | ||||
| # bats test_tags=slow | ||||
| @test "chaos commit rollback not possible" { | ||||
| @test "specific chaos commit rollback not possible" { | ||||
|   _deploy_app | ||||
|  | ||||
|   tagHash=$(_get_tag_hash "0.2.0+1.21.0") | ||||
|  | ||||
| @ -75,7 +75,7 @@ teardown(){ | ||||
|  | ||||
|   assert_output --partial 'DOWNGRADE OVERVIEW' | ||||
|   assert_output --partial 'CURRENT DEPLOYMENT    0.2.0+1.21.0' | ||||
|   assert_output --partial 'ENV VERSION           N/A' | ||||
|   assert_output --partial 'ENV VERSION           -' | ||||
|   assert_output --partial 'NEW DEPLOYMENT        0.1.0+1.20.0' | ||||
|  | ||||
|   run grep -q "TYPE=$TEST_RECIPE:${latestRelease}" \ | ||||
|  | ||||
| @ -106,6 +106,7 @@ teardown(){ | ||||
|  | ||||
|   run $ABRA app undeploy "$TEST_APP_DOMAIN" --no-input | ||||
|   assert_success | ||||
|   assert_output --regexp "CURRENT DEPLOYMENT.*${_get_head_hash:0:8}" | ||||
| } | ||||
|  | ||||
| # bats test_tags=slow | ||||
|  | ||||
| @ -36,7 +36,7 @@ teardown(){ | ||||
|   assert_output --partial 'UNDEPLOY OVERVIEW' | ||||
|   assert_output --partial 'CURRENT DEPLOYMENT    0.1.0+1.20.0' | ||||
|   assert_output --partial 'ENV VERSION           0.1.0+1.20.0' | ||||
|   assert_output --partial 'NEW DEPLOYMENT        N/A' | ||||
|   assert_output --partial 'NEW DEPLOYMENT        -' | ||||
|  | ||||
|   run grep -q "TYPE=$TEST_RECIPE:0.1.0+1.20.0" \ | ||||
|     "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" | ||||
| @ -57,7 +57,7 @@ teardown(){ | ||||
|   assert_output --partial 'UNDEPLOY OVERVIEW' | ||||
|   assert_output --partial "CURRENT DEPLOYMENT    ${headHash:0:8}" | ||||
|   assert_output --partial "ENV VERSION           ${headHash:0:8}" | ||||
|   assert_output --partial 'NEW DEPLOYMENT        N/A' | ||||
|   assert_output --partial 'NEW DEPLOYMENT        -' | ||||
|  | ||||
|   run grep -q "TYPE=$TEST_RECIPE:${headHash:0:8}" \ | ||||
|     "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" | ||||
| @ -81,7 +81,7 @@ teardown(){ | ||||
|   assert_output --partial 'UNDEPLOY OVERVIEW' | ||||
|   assert_output --partial "CURRENT DEPLOYMENT    ${headHash:0:8}+U" | ||||
|   assert_output --partial "ENV VERSION           ${headHash:0:8}+U" | ||||
|   assert_output --partial 'NEW DEPLOYMENT        N/A' | ||||
|   assert_output --partial 'NEW DEPLOYMENT        -' | ||||
|  | ||||
|   run grep -q "TYPE=$TEST_RECIPE:${headHash:0:8}" \ | ||||
|     "$ABRA_DIR/servers/$TEST_SERVER/$TEST_APP_DOMAIN.env" | ||||
|  | ||||
| @ -256,7 +256,7 @@ teardown(){ | ||||
| } | ||||
|  | ||||
| # bats test_tags=slow | ||||
| @test "upgrade commit deployment not possible" { | ||||
| @test "commit deploy upgrade is possible" { | ||||
|   tagHash=$(_get_tag_hash "0.1.0+1.20.0") | ||||
|   run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash" | ||||
|   assert_success | ||||
| @ -266,11 +266,12 @@ teardown(){ | ||||
|   assert_output --partial "${tagHash:0:8}" | ||||
|  | ||||
|   run $ABRA app upgrade "$TEST_APP_DOMAIN" "0.1.1+1.20.2" --no-input --no-converge-checks | ||||
|   assert_failure | ||||
|   assert_output --partial "not a known version" | ||||
|   assert_success | ||||
|   assert_output --regexp "CURRENT DEPLOYMENT.*${tagHash:0:8}" | ||||
|   assert_output --regexp "ENV VERSION.*${tagHash:0:8}" | ||||
| } | ||||
|  | ||||
| @test "chaos commit upgrade not possible" { | ||||
| @test "chaos commit upgrade is possible" { | ||||
|   run $ABRA app deploy "$TEST_APP_DOMAIN" "0.1.0+1.20.0" --no-input --no-converge-checks | ||||
|   assert_success | ||||
|   assert_output --partial '0.1.0+1.20.0' | ||||
| @ -278,8 +279,7 @@ teardown(){ | ||||
|   tagHash=$(_get_tag_hash "0.2.0+1.21.0") | ||||
|  | ||||
|   run $ABRA app upgrade "$TEST_APP_DOMAIN" "$tagHash" --no-input --no-converge-checks | ||||
|   assert_failure | ||||
|   assert_output --partial "not a known version" | ||||
|   assert_success | ||||
| } | ||||
|  | ||||
| # bats test_tags=slow | ||||
|  | ||||
| @ -75,7 +75,7 @@ teardown(){ | ||||
|  | ||||
|   assert_output --partial 'UPGRADE OVERVIEW' | ||||
|   assert_output --partial 'CURRENT DEPLOYMENT    0.2.0+1.21.0' | ||||
|   assert_output --partial 'ENV VERSION           N/A' | ||||
|   assert_output --partial 'ENV VERSION           -' | ||||
|   assert_output --partial "NEW DEPLOYMENT        $latestRelease" | ||||
|  | ||||
|   run grep -q "TYPE=$TEST_RECIPE:${latestRelease}" \ | ||||
|  | ||||
| @ -53,7 +53,8 @@ teardown(){ | ||||
|     --domain "foobar.$TEST_SERVER" | ||||
|   assert_success | ||||
|  | ||||
|   run $ABRA app deploy "foobar.$TEST_SERVER" --no-input | ||||
|   run $ABRA app deploy "foobar.$TEST_SERVER" \ | ||||
|     --no-input --no-converge-checks | ||||
|   assert_success | ||||
| } | ||||
|  | ||||
|  | ||||
| @ -127,3 +127,27 @@ teardown() { | ||||
|   assert_exists "$ABRA_DIR/recipes/$TEST_RECIPE/release/0.4.0+1.21.0" | ||||
|   assert_file_contains "$ABRA_DIR/recipes/$TEST_RECIPE/release/0.4.0+1.21.0" "those are some release notes for the next release" | ||||
| } | ||||
|  | ||||
| @test "recipe release conflict fails" { | ||||
|   tagHash=$(_get_tag_hash "0.2.0+1.21.0") | ||||
|   run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" checkout "$tagHash" | ||||
|   assert_success | ||||
|  | ||||
|   run sed -i "s/nginx:1.21.0/nginx:1.29.1/g" "$ABRA_DIR/recipes/$TEST_RECIPE/compose.yml" | ||||
|   assert_success | ||||
|  | ||||
|   run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" diff | ||||
|   assert_success | ||||
|   assert_output --regexp 'nginx:1.29.1' | ||||
|  | ||||
|   run sed -i "s/0.2.0+1.21.0/0.2.0+1.29.1/g" "$ABRA_DIR/recipes/$TEST_RECIPE/compose.yml" | ||||
|   assert_success | ||||
|  | ||||
|   run git -C "$ABRA_DIR/recipes/$TEST_RECIPE" diff | ||||
|   assert_success | ||||
|   assert_output --regexp 'coop-cloud\.\$\{STACK_NAME\}\.version=0\.2\.0\+1\.29\.1' | ||||
|  | ||||
|   run $ABRA recipe release "$TEST_RECIPE" --no-input --minor | ||||
|   assert_failure | ||||
|   assert_output --partial '0.2.0+... conflicts with a previous release: 0.2.0+1.21.0' | ||||
| } | ||||
|  | ||||
| @ -12,11 +12,7 @@ setup_suite(){ | ||||
|   fi | ||||
|  | ||||
|   if [[ ! -f "$PWD/abra" ]]; then | ||||
|     make build-abra | ||||
|   fi | ||||
|  | ||||
|   if [[ ! -f "$PWD/kadabra" ]]; then | ||||
|     make build-kadabra | ||||
|     make | ||||
|   fi | ||||
|  | ||||
|   if [[ -d "$ABRA_DIR" ]]; then | ||||
|  | ||||
							
								
								
									
										22
									
								
								vendor/github.com/atotto/clipboard/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								vendor/github.com/atotto/clipboard/.travis.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,22 @@ | ||||
| language: go | ||||
|  | ||||
| os: | ||||
|  - linux | ||||
|  - osx | ||||
|  - windows | ||||
|  | ||||
| go: | ||||
|  - go1.13.x | ||||
|  - go1.x | ||||
|  | ||||
| services: | ||||
|  - xvfb | ||||
|  | ||||
| before_install: | ||||
|  - export DISPLAY=:99.0 | ||||
|  | ||||
| script: | ||||
|  - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xsel; fi | ||||
|  - go test -v . | ||||
|  - if [ "$TRAVIS_OS_NAME" = "linux" ]; then sudo apt-get install xclip; fi | ||||
|  - go test -v . | ||||
							
								
								
									
										4
									
								
								vendor/golang.org/x/sync/LICENSE → vendor/github.com/atotto/clipboard/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/golang.org/x/sync/LICENSE → vendor/github.com/atotto/clipboard/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,4 +1,4 @@ | ||||
| Copyright 2009 The Go Authors. | ||||
| Copyright (c) 2013 Ato Araki. All rights reserved. | ||||
| 
 | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are | ||||
| @ -10,7 +10,7 @@ notice, this list of conditions and the following disclaimer. | ||||
| copyright notice, this list of conditions and the following disclaimer | ||||
| in the documentation and/or other materials provided with the | ||||
| distribution. | ||||
|    * Neither the name of Google LLC nor the names of its | ||||
|    * Neither the name of @atotto. nor the names of its | ||||
| contributors may be used to endorse or promote products derived from | ||||
| this software without specific prior written permission. | ||||
| 
 | ||||
							
								
								
									
										48
									
								
								vendor/github.com/atotto/clipboard/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								vendor/github.com/atotto/clipboard/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,48 @@ | ||||
| [](https://travis-ci.org/atotto/clipboard) | ||||
|  | ||||
| [](http://godoc.org/github.com/atotto/clipboard) | ||||
|  | ||||
| # Clipboard for Go | ||||
|  | ||||
| Provide copying and pasting to the Clipboard for Go. | ||||
|  | ||||
| Build: | ||||
|  | ||||
|     $ go get github.com/atotto/clipboard | ||||
|  | ||||
| Platforms: | ||||
|  | ||||
| * OSX | ||||
| * Windows 7 (probably work on other Windows) | ||||
| * Linux, Unix (requires 'xclip' or 'xsel' command to be installed) | ||||
|  | ||||
|  | ||||
| Document:  | ||||
|  | ||||
| * http://godoc.org/github.com/atotto/clipboard | ||||
|  | ||||
| Notes: | ||||
|  | ||||
| * Text string only | ||||
| * UTF-8 text encoding only (no conversion) | ||||
|  | ||||
| TODO: | ||||
|  | ||||
| * Clipboard watcher(?) | ||||
|  | ||||
| ## Commands: | ||||
|  | ||||
| paste shell command: | ||||
|  | ||||
|     $ go get github.com/atotto/clipboard/cmd/gopaste | ||||
|     $ # example: | ||||
|     $ gopaste > document.txt | ||||
|  | ||||
| copy shell command: | ||||
|  | ||||
|     $ go get github.com/atotto/clipboard/cmd/gocopy | ||||
|     $ # example: | ||||
|     $ cat document.txt | gocopy | ||||
|  | ||||
|  | ||||
|  | ||||
							
								
								
									
										20
									
								
								vendor/github.com/atotto/clipboard/clipboard.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								vendor/github.com/atotto/clipboard/clipboard.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| // Copyright 2013 @atotto. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // Package clipboard read/write on clipboard | ||||
| package clipboard | ||||
|  | ||||
| // ReadAll read string from clipboard | ||||
| func ReadAll() (string, error) { | ||||
| 	return readAll() | ||||
| } | ||||
|  | ||||
| // WriteAll write string to clipboard | ||||
| func WriteAll(text string) error { | ||||
| 	return writeAll(text) | ||||
| } | ||||
|  | ||||
| // Unsupported might be set true during clipboard init, to help callers decide | ||||
| // whether or not to offer clipboard options. | ||||
| var Unsupported bool | ||||
							
								
								
									
										52
									
								
								vendor/github.com/atotto/clipboard/clipboard_darwin.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								vendor/github.com/atotto/clipboard/clipboard_darwin.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,52 @@ | ||||
| // Copyright 2013 @atotto. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // +build darwin | ||||
|  | ||||
| package clipboard | ||||
|  | ||||
| import ( | ||||
| 	"os/exec" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	pasteCmdArgs = "pbpaste" | ||||
| 	copyCmdArgs  = "pbcopy" | ||||
| ) | ||||
|  | ||||
| func getPasteCommand() *exec.Cmd { | ||||
| 	return exec.Command(pasteCmdArgs) | ||||
| } | ||||
|  | ||||
| func getCopyCommand() *exec.Cmd { | ||||
| 	return exec.Command(copyCmdArgs) | ||||
| } | ||||
|  | ||||
| func readAll() (string, error) { | ||||
| 	pasteCmd := getPasteCommand() | ||||
| 	out, err := pasteCmd.Output() | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return string(out), nil | ||||
| } | ||||
|  | ||||
| func writeAll(text string) error { | ||||
| 	copyCmd := getCopyCommand() | ||||
| 	in, err := copyCmd.StdinPipe() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := copyCmd.Start(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if _, err := in.Write([]byte(text)); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := in.Close(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return copyCmd.Wait() | ||||
| } | ||||
							
								
								
									
										42
									
								
								vendor/github.com/atotto/clipboard/clipboard_plan9.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										42
									
								
								vendor/github.com/atotto/clipboard/clipboard_plan9.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,42 @@ | ||||
| // Copyright 2013 @atotto. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // +build plan9 | ||||
|  | ||||
| package clipboard | ||||
|  | ||||
| import ( | ||||
| 	"os" | ||||
| 	"io/ioutil" | ||||
| ) | ||||
|  | ||||
| func readAll() (string, error) { | ||||
| 	f, err := os.Open("/dev/snarf") | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	defer f.Close() | ||||
|  | ||||
| 	str, err := ioutil.ReadAll(f) | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	 | ||||
| 	return string(str), nil | ||||
| } | ||||
|  | ||||
| func writeAll(text string) error { | ||||
| 	f, err := os.OpenFile("/dev/snarf", os.O_WRONLY, 0666) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	defer f.Close() | ||||
| 	 | ||||
| 	_, err = f.Write([]byte(text)) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	 | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										149
									
								
								vendor/github.com/atotto/clipboard/clipboard_unix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										149
									
								
								vendor/github.com/atotto/clipboard/clipboard_unix.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,149 @@ | ||||
| // Copyright 2013 @atotto. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // +build freebsd linux netbsd openbsd solaris dragonfly | ||||
|  | ||||
| package clipboard | ||||
|  | ||||
| import ( | ||||
| 	"errors" | ||||
| 	"os" | ||||
| 	"os/exec" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	xsel               = "xsel" | ||||
| 	xclip              = "xclip" | ||||
| 	powershellExe      = "powershell.exe" | ||||
| 	clipExe            = "clip.exe" | ||||
| 	wlcopy             = "wl-copy" | ||||
| 	wlpaste            = "wl-paste" | ||||
| 	termuxClipboardGet = "termux-clipboard-get" | ||||
| 	termuxClipboardSet = "termux-clipboard-set" | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	Primary bool | ||||
| 	trimDos bool | ||||
|  | ||||
| 	pasteCmdArgs []string | ||||
| 	copyCmdArgs  []string | ||||
|  | ||||
| 	xselPasteArgs = []string{xsel, "--output", "--clipboard"} | ||||
| 	xselCopyArgs  = []string{xsel, "--input", "--clipboard"} | ||||
|  | ||||
| 	xclipPasteArgs = []string{xclip, "-out", "-selection", "clipboard"} | ||||
| 	xclipCopyArgs  = []string{xclip, "-in", "-selection", "clipboard"} | ||||
|  | ||||
| 	powershellExePasteArgs = []string{powershellExe, "Get-Clipboard"} | ||||
| 	clipExeCopyArgs        = []string{clipExe} | ||||
|  | ||||
| 	wlpasteArgs = []string{wlpaste, "--no-newline"} | ||||
| 	wlcopyArgs  = []string{wlcopy} | ||||
|  | ||||
| 	termuxPasteArgs = []string{termuxClipboardGet} | ||||
| 	termuxCopyArgs  = []string{termuxClipboardSet} | ||||
|  | ||||
| 	missingCommands = errors.New("No clipboard utilities available. Please install xsel, xclip, wl-clipboard or Termux:API add-on for termux-clipboard-get/set.") | ||||
| ) | ||||
|  | ||||
| func init() { | ||||
| 	if os.Getenv("WAYLAND_DISPLAY") != "" { | ||||
| 		pasteCmdArgs = wlpasteArgs | ||||
| 		copyCmdArgs = wlcopyArgs | ||||
|  | ||||
| 		if _, err := exec.LookPath(wlcopy); err == nil { | ||||
| 			if _, err := exec.LookPath(wlpaste); err == nil { | ||||
| 				return | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	pasteCmdArgs = xclipPasteArgs | ||||
| 	copyCmdArgs = xclipCopyArgs | ||||
|  | ||||
| 	if _, err := exec.LookPath(xclip); err == nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pasteCmdArgs = xselPasteArgs | ||||
| 	copyCmdArgs = xselCopyArgs | ||||
|  | ||||
| 	if _, err := exec.LookPath(xsel); err == nil { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	pasteCmdArgs = termuxPasteArgs | ||||
| 	copyCmdArgs = termuxCopyArgs | ||||
|  | ||||
| 	if _, err := exec.LookPath(termuxClipboardSet); err == nil { | ||||
| 		if _, err := exec.LookPath(termuxClipboardGet); err == nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	pasteCmdArgs = powershellExePasteArgs | ||||
| 	copyCmdArgs = clipExeCopyArgs | ||||
| 	trimDos = true | ||||
|  | ||||
| 	if _, err := exec.LookPath(clipExe); err == nil { | ||||
| 		if _, err := exec.LookPath(powershellExe); err == nil { | ||||
| 			return | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	Unsupported = true | ||||
| } | ||||
|  | ||||
| func getPasteCommand() *exec.Cmd { | ||||
| 	if Primary { | ||||
| 		pasteCmdArgs = pasteCmdArgs[:1] | ||||
| 	} | ||||
| 	return exec.Command(pasteCmdArgs[0], pasteCmdArgs[1:]...) | ||||
| } | ||||
|  | ||||
| func getCopyCommand() *exec.Cmd { | ||||
| 	if Primary { | ||||
| 		copyCmdArgs = copyCmdArgs[:1] | ||||
| 	} | ||||
| 	return exec.Command(copyCmdArgs[0], copyCmdArgs[1:]...) | ||||
| } | ||||
|  | ||||
| func readAll() (string, error) { | ||||
| 	if Unsupported { | ||||
| 		return "", missingCommands | ||||
| 	} | ||||
| 	pasteCmd := getPasteCommand() | ||||
| 	out, err := pasteCmd.Output() | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	result := string(out) | ||||
| 	if trimDos && len(result) > 1 { | ||||
| 		result = result[:len(result)-2] | ||||
| 	} | ||||
| 	return result, nil | ||||
| } | ||||
|  | ||||
| func writeAll(text string) error { | ||||
| 	if Unsupported { | ||||
| 		return missingCommands | ||||
| 	} | ||||
| 	copyCmd := getCopyCommand() | ||||
| 	in, err := copyCmd.StdinPipe() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	if err := copyCmd.Start(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if _, err := in.Write([]byte(text)); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	if err := in.Close(); err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	return copyCmd.Wait() | ||||
| } | ||||
							
								
								
									
										157
									
								
								vendor/github.com/atotto/clipboard/clipboard_windows.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										157
									
								
								vendor/github.com/atotto/clipboard/clipboard_windows.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,157 @@ | ||||
| // Copyright 2013 @atotto. All rights reserved. | ||||
| // Use of this source code is governed by a BSD-style | ||||
| // license that can be found in the LICENSE file. | ||||
|  | ||||
| // +build windows | ||||
|  | ||||
| package clipboard | ||||
|  | ||||
| import ( | ||||
| 	"runtime" | ||||
| 	"syscall" | ||||
| 	"time" | ||||
| 	"unsafe" | ||||
| ) | ||||
|  | ||||
| const ( | ||||
| 	cfUnicodetext = 13 | ||||
| 	gmemMoveable  = 0x0002 | ||||
| ) | ||||
|  | ||||
| var ( | ||||
| 	user32                     = syscall.MustLoadDLL("user32") | ||||
| 	isClipboardFormatAvailable = user32.MustFindProc("IsClipboardFormatAvailable") | ||||
| 	openClipboard              = user32.MustFindProc("OpenClipboard") | ||||
| 	closeClipboard             = user32.MustFindProc("CloseClipboard") | ||||
| 	emptyClipboard             = user32.MustFindProc("EmptyClipboard") | ||||
| 	getClipboardData           = user32.MustFindProc("GetClipboardData") | ||||
| 	setClipboardData           = user32.MustFindProc("SetClipboardData") | ||||
|  | ||||
| 	kernel32     = syscall.NewLazyDLL("kernel32") | ||||
| 	globalAlloc  = kernel32.NewProc("GlobalAlloc") | ||||
| 	globalFree   = kernel32.NewProc("GlobalFree") | ||||
| 	globalLock   = kernel32.NewProc("GlobalLock") | ||||
| 	globalUnlock = kernel32.NewProc("GlobalUnlock") | ||||
| 	lstrcpy      = kernel32.NewProc("lstrcpyW") | ||||
| ) | ||||
|  | ||||
| // waitOpenClipboard opens the clipboard, waiting for up to a second to do so. | ||||
| func waitOpenClipboard() error { | ||||
| 	started := time.Now() | ||||
| 	limit := started.Add(time.Second) | ||||
| 	var r uintptr | ||||
| 	var err error | ||||
| 	for time.Now().Before(limit) { | ||||
| 		r, _, err = openClipboard.Call(0) | ||||
| 		if r != 0 { | ||||
| 			return nil | ||||
| 		} | ||||
| 		time.Sleep(time.Millisecond) | ||||
| 	} | ||||
| 	return err | ||||
| } | ||||
|  | ||||
| func readAll() (string, error) { | ||||
| 	// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution). | ||||
| 	// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock. | ||||
| 	runtime.LockOSThread() | ||||
| 	defer runtime.UnlockOSThread() | ||||
| 	if formatAvailable, _, err := isClipboardFormatAvailable.Call(cfUnicodetext); formatAvailable == 0 { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	err := waitOpenClipboard() | ||||
| 	if err != nil { | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	h, _, err := getClipboardData.Call(cfUnicodetext) | ||||
| 	if h == 0 { | ||||
| 		_, _, _ = closeClipboard.Call() | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	l, _, err := globalLock.Call(h) | ||||
| 	if l == 0 { | ||||
| 		_, _, _ = closeClipboard.Call() | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	text := syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(l))[:]) | ||||
|  | ||||
| 	r, _, err := globalUnlock.Call(h) | ||||
| 	if r == 0 { | ||||
| 		_, _, _ = closeClipboard.Call() | ||||
| 		return "", err | ||||
| 	} | ||||
|  | ||||
| 	closed, _, err := closeClipboard.Call() | ||||
| 	if closed == 0 { | ||||
| 		return "", err | ||||
| 	} | ||||
| 	return text, nil | ||||
| } | ||||
|  | ||||
| func writeAll(text string) error { | ||||
| 	// LockOSThread ensure that the whole method will keep executing on the same thread from begin to end (it actually locks the goroutine thread attribution). | ||||
| 	// Otherwise if the goroutine switch thread during execution (which is a common practice), the OpenClipboard and CloseClipboard will happen on two different threads, and it will result in a clipboard deadlock. | ||||
| 	runtime.LockOSThread() | ||||
| 	defer runtime.UnlockOSThread() | ||||
|  | ||||
| 	err := waitOpenClipboard() | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	r, _, err := emptyClipboard.Call(0) | ||||
| 	if r == 0 { | ||||
| 		_, _, _ = closeClipboard.Call() | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	data := syscall.StringToUTF16(text) | ||||
|  | ||||
| 	// "If the hMem parameter identifies a memory object, the object must have | ||||
| 	// been allocated using the function with the GMEM_MOVEABLE flag." | ||||
| 	h, _, err := globalAlloc.Call(gmemMoveable, uintptr(len(data)*int(unsafe.Sizeof(data[0])))) | ||||
| 	if h == 0 { | ||||
| 		_, _, _ = closeClipboard.Call() | ||||
| 		return err | ||||
| 	} | ||||
| 	defer func() { | ||||
| 		if h != 0 { | ||||
| 			globalFree.Call(h) | ||||
| 		} | ||||
| 	}() | ||||
|  | ||||
| 	l, _, err := globalLock.Call(h) | ||||
| 	if l == 0 { | ||||
| 		_, _, _ = closeClipboard.Call() | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	r, _, err = lstrcpy.Call(l, uintptr(unsafe.Pointer(&data[0]))) | ||||
| 	if r == 0 { | ||||
| 		_, _, _ = closeClipboard.Call() | ||||
| 		return err | ||||
| 	} | ||||
|  | ||||
| 	r, _, err = globalUnlock.Call(h) | ||||
| 	if r == 0 { | ||||
| 		if err.(syscall.Errno) != 0 { | ||||
| 			_, _, _ = closeClipboard.Call() | ||||
| 			return err | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	r, _, err = setClipboardData.Call(cfUnicodetext, h) | ||||
| 	if r == 0 { | ||||
| 		_, _, _ = closeClipboard.Call() | ||||
| 		return err | ||||
| 	} | ||||
| 	h = 0 // suppress deferred cleanup | ||||
| 	closed, _, err := closeClipboard.Call() | ||||
| 	if closed == 0 { | ||||
| 		return err | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										21
									
								
								vendor/github.com/charmbracelet/bubbles/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/charmbracelet/bubbles/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| MIT License | ||||
|  | ||||
| Copyright (c) 2020-2023 Charmbracelet, Inc | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
							
								
								
									
										219
									
								
								vendor/github.com/charmbracelet/bubbles/cursor/cursor.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										219
									
								
								vendor/github.com/charmbracelet/bubbles/cursor/cursor.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,219 @@ | ||||
| // Package cursor provides cursor functionality for Bubble Tea applications. | ||||
| package cursor | ||||
|  | ||||
| import ( | ||||
| 	"context" | ||||
| 	"time" | ||||
|  | ||||
| 	tea "github.com/charmbracelet/bubbletea" | ||||
| 	"github.com/charmbracelet/lipgloss" | ||||
| ) | ||||
|  | ||||
| const defaultBlinkSpeed = time.Millisecond * 530 | ||||
|  | ||||
| // initialBlinkMsg initializes cursor blinking. | ||||
| type initialBlinkMsg struct{} | ||||
|  | ||||
| // BlinkMsg signals that the cursor should blink. It contains metadata that | ||||
| // allows us to tell if the blink message is the one we're expecting. | ||||
| type BlinkMsg struct { | ||||
| 	id  int | ||||
| 	tag int | ||||
| } | ||||
|  | ||||
| // blinkCanceled is sent when a blink operation is canceled. | ||||
| type blinkCanceled struct{} | ||||
|  | ||||
| // blinkCtx manages cursor blinking. | ||||
| type blinkCtx struct { | ||||
| 	ctx    context.Context | ||||
| 	cancel context.CancelFunc | ||||
| } | ||||
|  | ||||
| // Mode describes the behavior of the cursor. | ||||
| type Mode int | ||||
|  | ||||
| // Available cursor modes. | ||||
| const ( | ||||
| 	CursorBlink Mode = iota | ||||
| 	CursorStatic | ||||
| 	CursorHide | ||||
| ) | ||||
|  | ||||
| // String returns the cursor mode in a human-readable format. This method is | ||||
| // provisional and for informational purposes only. | ||||
| func (c Mode) String() string { | ||||
| 	return [...]string{ | ||||
| 		"blink", | ||||
| 		"static", | ||||
| 		"hidden", | ||||
| 	}[c] | ||||
| } | ||||
|  | ||||
| // Model is the Bubble Tea model for this cursor element. | ||||
| type Model struct { | ||||
| 	BlinkSpeed time.Duration | ||||
| 	// Style for styling the cursor block. | ||||
| 	Style lipgloss.Style | ||||
| 	// TextStyle is the style used for the cursor when it is hidden (when blinking). | ||||
| 	// I.e. displaying normal text. | ||||
| 	TextStyle lipgloss.Style | ||||
|  | ||||
| 	// char is the character under the cursor | ||||
| 	char string | ||||
| 	// The ID of this Model as it relates to other cursors | ||||
| 	id int | ||||
| 	// focus indicates whether the containing input is focused | ||||
| 	focus bool | ||||
| 	// Cursor Blink state. | ||||
| 	Blink bool | ||||
| 	// Used to manage cursor blink | ||||
| 	blinkCtx *blinkCtx | ||||
| 	// The ID of the blink message we're expecting to receive. | ||||
| 	blinkTag int | ||||
| 	// mode determines the behavior of the cursor | ||||
| 	mode Mode | ||||
| } | ||||
|  | ||||
| // New creates a new model with default settings. | ||||
| func New() Model { | ||||
| 	return Model{ | ||||
| 		BlinkSpeed: defaultBlinkSpeed, | ||||
|  | ||||
| 		Blink: true, | ||||
| 		mode:  CursorBlink, | ||||
|  | ||||
| 		blinkCtx: &blinkCtx{ | ||||
| 			ctx: context.Background(), | ||||
| 		}, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Update updates the cursor. | ||||
| func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { | ||||
| 	switch msg := msg.(type) { | ||||
| 	case initialBlinkMsg: | ||||
| 		// We accept all initialBlinkMsgs generated by the Blink command. | ||||
|  | ||||
| 		if m.mode != CursorBlink || !m.focus { | ||||
| 			return m, nil | ||||
| 		} | ||||
|  | ||||
| 		cmd := m.BlinkCmd() | ||||
| 		return m, cmd | ||||
|  | ||||
| 	case tea.FocusMsg: | ||||
| 		return m, m.Focus() | ||||
|  | ||||
| 	case tea.BlurMsg: | ||||
| 		m.Blur() | ||||
| 		return m, nil | ||||
|  | ||||
| 	case BlinkMsg: | ||||
| 		// We're choosy about whether to accept blinkMsgs so that our cursor | ||||
| 		// only exactly when it should. | ||||
|  | ||||
| 		// Is this model blink-able? | ||||
| 		if m.mode != CursorBlink || !m.focus { | ||||
| 			return m, nil | ||||
| 		} | ||||
|  | ||||
| 		// Were we expecting this blink message? | ||||
| 		if msg.id != m.id || msg.tag != m.blinkTag { | ||||
| 			return m, nil | ||||
| 		} | ||||
|  | ||||
| 		var cmd tea.Cmd | ||||
| 		if m.mode == CursorBlink { | ||||
| 			m.Blink = !m.Blink | ||||
| 			cmd = m.BlinkCmd() | ||||
| 		} | ||||
| 		return m, cmd | ||||
|  | ||||
| 	case blinkCanceled: // no-op | ||||
| 		return m, nil | ||||
| 	} | ||||
| 	return m, nil | ||||
| } | ||||
|  | ||||
| // Mode returns the model's cursor mode. For available cursor modes, see | ||||
| // type Mode. | ||||
| func (m Model) Mode() Mode { | ||||
| 	return m.mode | ||||
| } | ||||
|  | ||||
| // SetMode sets the model's cursor mode. This method returns a command. | ||||
| // | ||||
| // For available cursor modes, see type CursorMode. | ||||
| func (m *Model) SetMode(mode Mode) tea.Cmd { | ||||
| 	// Adjust the mode value if it's value is out of range | ||||
| 	if mode < CursorBlink || mode > CursorHide { | ||||
| 		return nil | ||||
| 	} | ||||
| 	m.mode = mode | ||||
| 	m.Blink = m.mode == CursorHide || !m.focus | ||||
| 	if mode == CursorBlink { | ||||
| 		return Blink | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // BlinkCmd is a command used to manage cursor blinking. | ||||
| func (m *Model) BlinkCmd() tea.Cmd { | ||||
| 	if m.mode != CursorBlink { | ||||
| 		return nil | ||||
| 	} | ||||
|  | ||||
| 	if m.blinkCtx != nil && m.blinkCtx.cancel != nil { | ||||
| 		m.blinkCtx.cancel() | ||||
| 	} | ||||
|  | ||||
| 	ctx, cancel := context.WithTimeout(m.blinkCtx.ctx, m.BlinkSpeed) | ||||
| 	m.blinkCtx.cancel = cancel | ||||
|  | ||||
| 	m.blinkTag++ | ||||
|  | ||||
| 	return func() tea.Msg { | ||||
| 		defer cancel() | ||||
| 		<-ctx.Done() | ||||
| 		if ctx.Err() == context.DeadlineExceeded { | ||||
| 			return BlinkMsg{id: m.id, tag: m.blinkTag} | ||||
| 		} | ||||
| 		return blinkCanceled{} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Blink is a command used to initialize cursor blinking. | ||||
| func Blink() tea.Msg { | ||||
| 	return initialBlinkMsg{} | ||||
| } | ||||
|  | ||||
| // Focus focuses the cursor to allow it to blink if desired. | ||||
| func (m *Model) Focus() tea.Cmd { | ||||
| 	m.focus = true | ||||
| 	m.Blink = m.mode == CursorHide // show the cursor unless we've explicitly hidden it | ||||
|  | ||||
| 	if m.mode == CursorBlink && m.focus { | ||||
| 		return m.BlinkCmd() | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
|  | ||||
| // Blur blurs the cursor. | ||||
| func (m *Model) Blur() { | ||||
| 	m.focus = false | ||||
| 	m.Blink = true | ||||
| } | ||||
|  | ||||
| // SetChar sets the character under the cursor. | ||||
| func (m *Model) SetChar(char string) { | ||||
| 	m.char = char | ||||
| } | ||||
|  | ||||
| // View displays the cursor. | ||||
| func (m Model) View() string { | ||||
| 	if m.Blink { | ||||
| 		return m.TextStyle.Inline(true).Render(m.char) | ||||
| 	} | ||||
| 	return m.Style.Inline(true).Reverse(true).Render(m.char) | ||||
| } | ||||
							
								
								
									
										140
									
								
								vendor/github.com/charmbracelet/bubbles/key/key.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								vendor/github.com/charmbracelet/bubbles/key/key.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,140 @@ | ||||
| // Package key provides some types and functions for generating user-definable | ||||
| // keymappings useful in Bubble Tea components. There are a few different ways | ||||
| // you can define a keymapping with this package. Here's one example: | ||||
| // | ||||
| //	type KeyMap struct { | ||||
| //	    Up key.Binding | ||||
| //	    Down key.Binding | ||||
| //	} | ||||
| // | ||||
| //	var DefaultKeyMap = KeyMap{ | ||||
| //	    Up: key.NewBinding( | ||||
| //	        key.WithKeys("k", "up"),        // actual keybindings | ||||
| //	        key.WithHelp("↑/k", "move up"), // corresponding help text | ||||
| //	    ), | ||||
| //	    Down: key.NewBinding( | ||||
| //	        key.WithKeys("j", "down"), | ||||
| //	        key.WithHelp("↓/j", "move down"), | ||||
| //	    ), | ||||
| //	} | ||||
| // | ||||
| //	func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { | ||||
| //	    switch msg := msg.(type) { | ||||
| //	    case tea.KeyMsg: | ||||
| //	        switch { | ||||
| //	        case key.Matches(msg, DefaultKeyMap.Up): | ||||
| //	            // The user pressed up | ||||
| //	        case key.Matches(msg, DefaultKeyMap.Down): | ||||
| //	            // The user pressed down | ||||
| //	        } | ||||
| //	    } | ||||
| // | ||||
| //	    // ... | ||||
| //	} | ||||
| // | ||||
| // The help information, which is not used in the example above, can be used | ||||
| // to render help text for keystrokes in your views. | ||||
| package key | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| // Binding describes a set of keybindings and, optionally, their associated | ||||
| // help text. | ||||
| type Binding struct { | ||||
| 	keys     []string | ||||
| 	help     Help | ||||
| 	disabled bool | ||||
| } | ||||
|  | ||||
| // BindingOpt is an initialization option for a keybinding. It's used as an | ||||
| // argument to NewBinding. | ||||
| type BindingOpt func(*Binding) | ||||
|  | ||||
| // NewBinding returns a new keybinding from a set of BindingOpt options. | ||||
| func NewBinding(opts ...BindingOpt) Binding { | ||||
| 	b := &Binding{} | ||||
| 	for _, opt := range opts { | ||||
| 		opt(b) | ||||
| 	} | ||||
| 	return *b | ||||
| } | ||||
|  | ||||
| // WithKeys initializes a keybinding with the given keystrokes. | ||||
| func WithKeys(keys ...string) BindingOpt { | ||||
| 	return func(b *Binding) { | ||||
| 		b.keys = keys | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WithHelp initializes a keybinding with the given help text. | ||||
| func WithHelp(key, desc string) BindingOpt { | ||||
| 	return func(b *Binding) { | ||||
| 		b.help = Help{Key: key, Desc: desc} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WithDisabled initializes a disabled keybinding. | ||||
| func WithDisabled() BindingOpt { | ||||
| 	return func(b *Binding) { | ||||
| 		b.disabled = true | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // SetKeys sets the keys for the keybinding. | ||||
| func (b *Binding) SetKeys(keys ...string) { | ||||
| 	b.keys = keys | ||||
| } | ||||
|  | ||||
| // Keys returns the keys for the keybinding. | ||||
| func (b Binding) Keys() []string { | ||||
| 	return b.keys | ||||
| } | ||||
|  | ||||
| // SetHelp sets the help text for the keybinding. | ||||
| func (b *Binding) SetHelp(key, desc string) { | ||||
| 	b.help = Help{Key: key, Desc: desc} | ||||
| } | ||||
|  | ||||
| // Help returns the Help information for the keybinding. | ||||
| func (b Binding) Help() Help { | ||||
| 	return b.help | ||||
| } | ||||
|  | ||||
| // Enabled returns whether or not the keybinding is enabled. Disabled | ||||
| // keybindings won't be activated and won't show up in help. Keybindings are | ||||
| // enabled by default. | ||||
| func (b Binding) Enabled() bool { | ||||
| 	return !b.disabled && b.keys != nil | ||||
| } | ||||
|  | ||||
| // SetEnabled enables or disables the keybinding. | ||||
| func (b *Binding) SetEnabled(v bool) { | ||||
| 	b.disabled = !v | ||||
| } | ||||
|  | ||||
| // Unbind removes the keys and help from this binding, effectively nullifying | ||||
| // it. This is a step beyond disabling it, since applications can enable | ||||
| // or disable key bindings based on application state. | ||||
| func (b *Binding) Unbind() { | ||||
| 	b.keys = nil | ||||
| 	b.help = Help{} | ||||
| } | ||||
|  | ||||
| // Help is help information for a given keybinding. | ||||
| type Help struct { | ||||
| 	Key  string | ||||
| 	Desc string | ||||
| } | ||||
|  | ||||
| // Matches checks if the given key matches the given bindings. | ||||
| func Matches[Key fmt.Stringer](k Key, b ...Binding) bool { | ||||
| 	keys := k.String() | ||||
| 	for _, binding := range b { | ||||
| 		for _, v := range binding.keys { | ||||
| 			if keys == v && binding.Enabled() { | ||||
| 				return true | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 	return false | ||||
| } | ||||
							
								
								
									
										102
									
								
								vendor/github.com/charmbracelet/bubbles/runeutil/runeutil.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										102
									
								
								vendor/github.com/charmbracelet/bubbles/runeutil/runeutil.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,102 @@ | ||||
| // Package runeutil provides a utility function for use in Bubbles | ||||
| // that can process Key messages containing runes. | ||||
| package runeutil | ||||
|  | ||||
| import ( | ||||
| 	"unicode" | ||||
| 	"unicode/utf8" | ||||
| ) | ||||
|  | ||||
| // Sanitizer is a helper for bubble widgets that want to process | ||||
| // Runes from input key messages. | ||||
| type Sanitizer interface { | ||||
| 	// Sanitize removes control characters from runes in a KeyRunes | ||||
| 	// message, and optionally replaces newline/carriage return/tabs by a | ||||
| 	// specified character. | ||||
| 	// | ||||
| 	// The rune array is modified in-place if possible. In that case, the | ||||
| 	// returned slice is the original slice shortened after the control | ||||
| 	// characters have been removed/translated. | ||||
| 	Sanitize(runes []rune) []rune | ||||
| } | ||||
|  | ||||
| // NewSanitizer constructs a rune sanitizer. | ||||
| func NewSanitizer(opts ...Option) Sanitizer { | ||||
| 	s := sanitizer{ | ||||
| 		replaceNewLine: []rune("\n"), | ||||
| 		replaceTab:     []rune("    "), | ||||
| 	} | ||||
| 	for _, o := range opts { | ||||
| 		s = o(s) | ||||
| 	} | ||||
| 	return &s | ||||
| } | ||||
|  | ||||
| // Option is the type of option that can be passed to Sanitize(). | ||||
| type Option func(sanitizer) sanitizer | ||||
|  | ||||
| // ReplaceTabs replaces tabs by the specified string. | ||||
| func ReplaceTabs(tabRepl string) Option { | ||||
| 	return func(s sanitizer) sanitizer { | ||||
| 		s.replaceTab = []rune(tabRepl) | ||||
| 		return s | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // ReplaceNewlines replaces newline characters by the specified string. | ||||
| func ReplaceNewlines(nlRepl string) Option { | ||||
| 	return func(s sanitizer) sanitizer { | ||||
| 		s.replaceNewLine = []rune(nlRepl) | ||||
| 		return s | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (s *sanitizer) Sanitize(runes []rune) []rune { | ||||
| 	// dstrunes are where we are storing the result. | ||||
| 	dstrunes := runes[:0:len(runes)] | ||||
| 	// copied indicates whether dstrunes is an alias of runes | ||||
| 	// or a copy. We need a copy when dst moves past src. | ||||
| 	// We use this as an optimization to avoid allocating | ||||
| 	// a new rune slice in the common case where the output | ||||
| 	// is smaller or equal to the input. | ||||
| 	copied := false | ||||
|  | ||||
| 	for src := 0; src < len(runes); src++ { | ||||
| 		r := runes[src] | ||||
| 		switch { | ||||
| 		case r == utf8.RuneError: | ||||
| 			// skip | ||||
|  | ||||
| 		case r == '\r' || r == '\n': | ||||
| 			if len(dstrunes)+len(s.replaceNewLine) > src && !copied { | ||||
| 				dst := len(dstrunes) | ||||
| 				dstrunes = make([]rune, dst, len(runes)+len(s.replaceNewLine)) | ||||
| 				copy(dstrunes, runes[:dst]) | ||||
| 				copied = true | ||||
| 			} | ||||
| 			dstrunes = append(dstrunes, s.replaceNewLine...) | ||||
|  | ||||
| 		case r == '\t': | ||||
| 			if len(dstrunes)+len(s.replaceTab) > src && !copied { | ||||
| 				dst := len(dstrunes) | ||||
| 				dstrunes = make([]rune, dst, len(runes)+len(s.replaceTab)) | ||||
| 				copy(dstrunes, runes[:dst]) | ||||
| 				copied = true | ||||
| 			} | ||||
| 			dstrunes = append(dstrunes, s.replaceTab...) | ||||
|  | ||||
| 		case unicode.IsControl(r): | ||||
| 			// Other control characters: skip. | ||||
|  | ||||
| 		default: | ||||
| 			// Keep the character. | ||||
| 			dstrunes = append(dstrunes, runes[src]) | ||||
| 		} | ||||
| 	} | ||||
| 	return dstrunes | ||||
| } | ||||
|  | ||||
| type sanitizer struct { | ||||
| 	replaceNewLine []rune | ||||
| 	replaceTab     []rune | ||||
| } | ||||
							
								
								
									
										224
									
								
								vendor/github.com/charmbracelet/bubbles/spinner/spinner.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										224
									
								
								vendor/github.com/charmbracelet/bubbles/spinner/spinner.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,224 @@ | ||||
| // Package spinner provides a spinner component for Bubble Tea applications. | ||||
| package spinner | ||||
|  | ||||
| import ( | ||||
| 	"sync/atomic" | ||||
| 	"time" | ||||
|  | ||||
| 	tea "github.com/charmbracelet/bubbletea" | ||||
| 	"github.com/charmbracelet/lipgloss" | ||||
| ) | ||||
|  | ||||
| // Internal ID management. Used during animating to ensure that frame messages | ||||
| // are received only by spinner components that sent them. | ||||
| var lastID int64 | ||||
|  | ||||
| func nextID() int { | ||||
| 	return int(atomic.AddInt64(&lastID, 1)) | ||||
| } | ||||
|  | ||||
| // Spinner is a set of frames used in animating the spinner. | ||||
| type Spinner struct { | ||||
| 	Frames []string | ||||
| 	FPS    time.Duration | ||||
| } | ||||
|  | ||||
| // Some spinners to choose from. You could also make your own. | ||||
| var ( | ||||
| 	Line = Spinner{ | ||||
| 		Frames: []string{"|", "/", "-", "\\"}, | ||||
| 		FPS:    time.Second / 10, //nolint:mnd | ||||
| 	} | ||||
| 	Dot = Spinner{ | ||||
| 		Frames: []string{"⣾ ", "⣽ ", "⣻ ", "⢿ ", "⡿ ", "⣟ ", "⣯ ", "⣷ "}, | ||||
| 		FPS:    time.Second / 10, //nolint:mnd | ||||
| 	} | ||||
| 	MiniDot = Spinner{ | ||||
| 		Frames: []string{"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"}, | ||||
| 		FPS:    time.Second / 12, //nolint:mnd | ||||
| 	} | ||||
| 	Jump = Spinner{ | ||||
| 		Frames: []string{"⢄", "⢂", "⢁", "⡁", "⡈", "⡐", "⡠"}, | ||||
| 		FPS:    time.Second / 10, //nolint:mnd | ||||
| 	} | ||||
| 	Pulse = Spinner{ | ||||
| 		Frames: []string{"█", "▓", "▒", "░"}, | ||||
| 		FPS:    time.Second / 8, //nolint:mnd | ||||
| 	} | ||||
| 	Points = Spinner{ | ||||
| 		Frames: []string{"∙∙∙", "●∙∙", "∙●∙", "∙∙●"}, | ||||
| 		FPS:    time.Second / 7, //nolint:mnd | ||||
| 	} | ||||
| 	Globe = Spinner{ | ||||
| 		Frames: []string{"🌍", "🌎", "🌏"}, | ||||
| 		FPS:    time.Second / 4, //nolint:mnd | ||||
| 	} | ||||
| 	Moon = Spinner{ | ||||
| 		Frames: []string{"🌑", "🌒", "🌓", "🌔", "🌕", "🌖", "🌗", "🌘"}, | ||||
| 		FPS:    time.Second / 8, //nolint:mnd | ||||
| 	} | ||||
| 	Monkey = Spinner{ | ||||
| 		Frames: []string{"🙈", "🙉", "🙊"}, | ||||
| 		FPS:    time.Second / 3, //nolint:mnd | ||||
| 	} | ||||
| 	Meter = Spinner{ | ||||
| 		Frames: []string{ | ||||
| 			"▱▱▱", | ||||
| 			"▰▱▱", | ||||
| 			"▰▰▱", | ||||
| 			"▰▰▰", | ||||
| 			"▰▰▱", | ||||
| 			"▰▱▱", | ||||
| 			"▱▱▱", | ||||
| 		}, | ||||
| 		FPS: time.Second / 7, //nolint:mnd | ||||
| 	} | ||||
| 	Hamburger = Spinner{ | ||||
| 		Frames: []string{"☱", "☲", "☴", "☲"}, | ||||
| 		FPS:    time.Second / 3, //nolint:mnd | ||||
| 	} | ||||
| 	Ellipsis = Spinner{ | ||||
| 		Frames: []string{"", ".", "..", "..."}, | ||||
| 		FPS:    time.Second / 3, //nolint:mnd | ||||
| 	} | ||||
| ) | ||||
|  | ||||
| // Model contains the state for the spinner. Use New to create new models | ||||
| // rather than using Model as a struct literal. | ||||
| type Model struct { | ||||
| 	// Spinner settings to use. See type Spinner. | ||||
| 	Spinner Spinner | ||||
|  | ||||
| 	// Style sets the styling for the spinner. Most of the time you'll just | ||||
| 	// want foreground and background coloring, and potentially some padding. | ||||
| 	// | ||||
| 	// For an introduction to styling with Lip Gloss see: | ||||
| 	// https://github.com/charmbracelet/lipgloss | ||||
| 	Style lipgloss.Style | ||||
|  | ||||
| 	frame int | ||||
| 	id    int | ||||
| 	tag   int | ||||
| } | ||||
|  | ||||
| // ID returns the spinner's unique ID. | ||||
| func (m Model) ID() int { | ||||
| 	return m.id | ||||
| } | ||||
|  | ||||
| // New returns a model with default values. | ||||
| func New(opts ...Option) Model { | ||||
| 	m := Model{ | ||||
| 		Spinner: Line, | ||||
| 		id:      nextID(), | ||||
| 	} | ||||
|  | ||||
| 	for _, opt := range opts { | ||||
| 		opt(&m) | ||||
| 	} | ||||
|  | ||||
| 	return m | ||||
| } | ||||
|  | ||||
| // NewModel returns a model with default values. | ||||
| // | ||||
| // Deprecated: use [New] instead. | ||||
| var NewModel = New | ||||
|  | ||||
| // TickMsg indicates that the timer has ticked and we should render a frame. | ||||
| type TickMsg struct { | ||||
| 	Time time.Time | ||||
| 	tag  int | ||||
| 	ID   int | ||||
| } | ||||
|  | ||||
| // Update is the Tea update function. | ||||
| func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { | ||||
| 	switch msg := msg.(type) { | ||||
| 	case TickMsg: | ||||
| 		// If an ID is set, and the ID doesn't belong to this spinner, reject | ||||
| 		// the message. | ||||
| 		if msg.ID > 0 && msg.ID != m.id { | ||||
| 			return m, nil | ||||
| 		} | ||||
|  | ||||
| 		// If a tag is set, and it's not the one we expect, reject the message. | ||||
| 		// This prevents the spinner from receiving too many messages and | ||||
| 		// thus spinning too fast. | ||||
| 		if msg.tag > 0 && msg.tag != m.tag { | ||||
| 			return m, nil | ||||
| 		} | ||||
|  | ||||
| 		m.frame++ | ||||
| 		if m.frame >= len(m.Spinner.Frames) { | ||||
| 			m.frame = 0 | ||||
| 		} | ||||
|  | ||||
| 		m.tag++ | ||||
| 		return m, m.tick(m.id, m.tag) | ||||
| 	default: | ||||
| 		return m, nil | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // View renders the model's view. | ||||
| func (m Model) View() string { | ||||
| 	if m.frame >= len(m.Spinner.Frames) { | ||||
| 		return "(error)" | ||||
| 	} | ||||
|  | ||||
| 	return m.Style.Render(m.Spinner.Frames[m.frame]) | ||||
| } | ||||
|  | ||||
| // Tick is the command used to advance the spinner one frame. Use this command | ||||
| // to effectively start the spinner. | ||||
| func (m Model) Tick() tea.Msg { | ||||
| 	return TickMsg{ | ||||
| 		// The time at which the tick occurred. | ||||
| 		Time: time.Now(), | ||||
|  | ||||
| 		// The ID of the spinner that this message belongs to. This can be | ||||
| 		// helpful when routing messages, however bear in mind that spinners | ||||
| 		// will ignore messages that don't contain ID by default. | ||||
| 		ID: m.id, | ||||
|  | ||||
| 		tag: m.tag, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (m Model) tick(id, tag int) tea.Cmd { | ||||
| 	return tea.Tick(m.Spinner.FPS, func(t time.Time) tea.Msg { | ||||
| 		return TickMsg{ | ||||
| 			Time: t, | ||||
| 			ID:   id, | ||||
| 			tag:  tag, | ||||
| 		} | ||||
| 	}) | ||||
| } | ||||
|  | ||||
| // Tick is the command used to advance the spinner one frame. Use this command | ||||
| // to effectively start the spinner. | ||||
| // | ||||
| // Deprecated: Use [Model.Tick] instead. | ||||
| func Tick() tea.Msg { | ||||
| 	return TickMsg{Time: time.Now()} | ||||
| } | ||||
|  | ||||
| // Option is used to set options in New. For example: | ||||
| // | ||||
| //	spinner := New(WithSpinner(Dot)) | ||||
| type Option func(*Model) | ||||
|  | ||||
| // WithSpinner is an option to set the spinner. | ||||
| func WithSpinner(spinner Spinner) Option { | ||||
| 	return func(m *Model) { | ||||
| 		m.Spinner = spinner | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // WithStyle is an option to set the spinner style. | ||||
| func WithStyle(style lipgloss.Style) Option { | ||||
| 	return func(m *Model) { | ||||
| 		m.Style = style | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										898
									
								
								vendor/github.com/charmbracelet/bubbles/textinput/textinput.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										898
									
								
								vendor/github.com/charmbracelet/bubbles/textinput/textinput.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,898 @@ | ||||
| // Package textinput provides a text input component for Bubble Tea | ||||
| // applications. | ||||
| package textinput | ||||
|  | ||||
| import ( | ||||
| 	"reflect" | ||||
| 	"strings" | ||||
| 	"time" | ||||
| 	"unicode" | ||||
|  | ||||
| 	"github.com/atotto/clipboard" | ||||
| 	"github.com/charmbracelet/bubbles/cursor" | ||||
| 	"github.com/charmbracelet/bubbles/key" | ||||
| 	"github.com/charmbracelet/bubbles/runeutil" | ||||
| 	tea "github.com/charmbracelet/bubbletea" | ||||
| 	"github.com/charmbracelet/lipgloss" | ||||
| 	rw "github.com/mattn/go-runewidth" | ||||
| 	"github.com/rivo/uniseg" | ||||
| ) | ||||
|  | ||||
| // Internal messages for clipboard operations. | ||||
| type ( | ||||
| 	pasteMsg    string | ||||
| 	pasteErrMsg struct{ error } | ||||
| ) | ||||
|  | ||||
| // EchoMode sets the input behavior of the text input field. | ||||
| type EchoMode int | ||||
|  | ||||
| const ( | ||||
| 	// EchoNormal displays text as is. This is the default behavior. | ||||
| 	EchoNormal EchoMode = iota | ||||
|  | ||||
| 	// EchoPassword displays the EchoCharacter mask instead of actual | ||||
| 	// characters. This is commonly used for password fields. | ||||
| 	EchoPassword | ||||
|  | ||||
| 	// EchoNone displays nothing as characters are entered. This is commonly | ||||
| 	// seen for password fields on the command line. | ||||
| 	EchoNone | ||||
| ) | ||||
|  | ||||
| // ValidateFunc is a function that returns an error if the input is invalid. | ||||
| type ValidateFunc func(string) error | ||||
|  | ||||
| // KeyMap is the key bindings for different actions within the textinput. | ||||
| type KeyMap struct { | ||||
| 	CharacterForward        key.Binding | ||||
| 	CharacterBackward       key.Binding | ||||
| 	WordForward             key.Binding | ||||
| 	WordBackward            key.Binding | ||||
| 	DeleteWordBackward      key.Binding | ||||
| 	DeleteWordForward       key.Binding | ||||
| 	DeleteAfterCursor       key.Binding | ||||
| 	DeleteBeforeCursor      key.Binding | ||||
| 	DeleteCharacterBackward key.Binding | ||||
| 	DeleteCharacterForward  key.Binding | ||||
| 	LineStart               key.Binding | ||||
| 	LineEnd                 key.Binding | ||||
| 	Paste                   key.Binding | ||||
| 	AcceptSuggestion        key.Binding | ||||
| 	NextSuggestion          key.Binding | ||||
| 	PrevSuggestion          key.Binding | ||||
| } | ||||
|  | ||||
| // DefaultKeyMap is the default set of key bindings for navigating and acting | ||||
| // upon the textinput. | ||||
| var DefaultKeyMap = KeyMap{ | ||||
| 	CharacterForward:        key.NewBinding(key.WithKeys("right", "ctrl+f")), | ||||
| 	CharacterBackward:       key.NewBinding(key.WithKeys("left", "ctrl+b")), | ||||
| 	WordForward:             key.NewBinding(key.WithKeys("alt+right", "ctrl+right", "alt+f")), | ||||
| 	WordBackward:            key.NewBinding(key.WithKeys("alt+left", "ctrl+left", "alt+b")), | ||||
| 	DeleteWordBackward:      key.NewBinding(key.WithKeys("alt+backspace", "ctrl+w")), | ||||
| 	DeleteWordForward:       key.NewBinding(key.WithKeys("alt+delete", "alt+d")), | ||||
| 	DeleteAfterCursor:       key.NewBinding(key.WithKeys("ctrl+k")), | ||||
| 	DeleteBeforeCursor:      key.NewBinding(key.WithKeys("ctrl+u")), | ||||
| 	DeleteCharacterBackward: key.NewBinding(key.WithKeys("backspace", "ctrl+h")), | ||||
| 	DeleteCharacterForward:  key.NewBinding(key.WithKeys("delete", "ctrl+d")), | ||||
| 	LineStart:               key.NewBinding(key.WithKeys("home", "ctrl+a")), | ||||
| 	LineEnd:                 key.NewBinding(key.WithKeys("end", "ctrl+e")), | ||||
| 	Paste:                   key.NewBinding(key.WithKeys("ctrl+v")), | ||||
| 	AcceptSuggestion:        key.NewBinding(key.WithKeys("tab")), | ||||
| 	NextSuggestion:          key.NewBinding(key.WithKeys("down", "ctrl+n")), | ||||
| 	PrevSuggestion:          key.NewBinding(key.WithKeys("up", "ctrl+p")), | ||||
| } | ||||
|  | ||||
| // Model is the Bubble Tea model for this text input element. | ||||
| type Model struct { | ||||
| 	Err error | ||||
|  | ||||
| 	// General settings. | ||||
| 	Prompt        string | ||||
| 	Placeholder   string | ||||
| 	EchoMode      EchoMode | ||||
| 	EchoCharacter rune | ||||
| 	Cursor        cursor.Model | ||||
|  | ||||
| 	// Deprecated: use [cursor.BlinkSpeed] instead. | ||||
| 	BlinkSpeed time.Duration | ||||
|  | ||||
| 	// Styles. These will be applied as inline styles. | ||||
| 	// | ||||
| 	// For an introduction to styling with Lip Gloss see: | ||||
| 	// https://github.com/charmbracelet/lipgloss | ||||
| 	PromptStyle      lipgloss.Style | ||||
| 	TextStyle        lipgloss.Style | ||||
| 	PlaceholderStyle lipgloss.Style | ||||
| 	CompletionStyle  lipgloss.Style | ||||
|  | ||||
| 	// Deprecated: use Cursor.Style instead. | ||||
| 	CursorStyle lipgloss.Style | ||||
|  | ||||
| 	// CharLimit is the maximum amount of characters this input element will | ||||
| 	// accept. If 0 or less, there's no limit. | ||||
| 	CharLimit int | ||||
|  | ||||
| 	// Width is the maximum number of characters that can be displayed at once. | ||||
| 	// It essentially treats the text field like a horizontally scrolling | ||||
| 	// viewport. If 0 or less this setting is ignored. | ||||
| 	Width int | ||||
|  | ||||
| 	// KeyMap encodes the keybindings recognized by the widget. | ||||
| 	KeyMap KeyMap | ||||
|  | ||||
| 	// Underlying text value. | ||||
| 	value []rune | ||||
|  | ||||
| 	// focus indicates whether user input focus should be on this input | ||||
| 	// component. When false, ignore keyboard input and hide the cursor. | ||||
| 	focus bool | ||||
|  | ||||
| 	// Cursor position. | ||||
| 	pos int | ||||
|  | ||||
| 	// Used to emulate a viewport when width is set and the content is | ||||
| 	// overflowing. | ||||
| 	offset      int | ||||
| 	offsetRight int | ||||
|  | ||||
| 	// Validate is a function that checks whether or not the text within the | ||||
| 	// input is valid. If it is not valid, the `Err` field will be set to the | ||||
| 	// error returned by the function. If the function is not defined, all | ||||
| 	// input is considered valid. | ||||
| 	Validate ValidateFunc | ||||
|  | ||||
| 	// rune sanitizer for input. | ||||
| 	rsan runeutil.Sanitizer | ||||
|  | ||||
| 	// Should the input suggest to complete | ||||
| 	ShowSuggestions bool | ||||
|  | ||||
| 	// suggestions is a list of suggestions that may be used to complete the | ||||
| 	// input. | ||||
| 	suggestions            [][]rune | ||||
| 	matchedSuggestions     [][]rune | ||||
| 	currentSuggestionIndex int | ||||
| } | ||||
|  | ||||
| // New creates a new model with default settings. | ||||
| func New() Model { | ||||
| 	return Model{ | ||||
| 		Prompt:           "> ", | ||||
| 		EchoCharacter:    '*', | ||||
| 		CharLimit:        0, | ||||
| 		PlaceholderStyle: lipgloss.NewStyle().Foreground(lipgloss.Color("240")), | ||||
| 		ShowSuggestions:  false, | ||||
| 		CompletionStyle:  lipgloss.NewStyle().Foreground(lipgloss.Color("240")), | ||||
| 		Cursor:           cursor.New(), | ||||
| 		KeyMap:           DefaultKeyMap, | ||||
|  | ||||
| 		suggestions: [][]rune{}, | ||||
| 		value:       nil, | ||||
| 		focus:       false, | ||||
| 		pos:         0, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // NewModel creates a new model with default settings. | ||||
| // | ||||
| // Deprecated: Use [New] instead. | ||||
| var NewModel = New | ||||
|  | ||||
| // SetValue sets the value of the text input. | ||||
| func (m *Model) SetValue(s string) { | ||||
| 	// Clean up any special characters in the input provided by the | ||||
| 	// caller. This avoids bugs due to e.g. tab characters and whatnot. | ||||
| 	runes := m.san().Sanitize([]rune(s)) | ||||
| 	err := m.validate(runes) | ||||
| 	m.setValueInternal(runes, err) | ||||
| } | ||||
|  | ||||
| func (m *Model) setValueInternal(runes []rune, err error) { | ||||
| 	m.Err = err | ||||
|  | ||||
| 	empty := len(m.value) == 0 | ||||
|  | ||||
| 	if m.CharLimit > 0 && len(runes) > m.CharLimit { | ||||
| 		m.value = runes[:m.CharLimit] | ||||
| 	} else { | ||||
| 		m.value = runes | ||||
| 	} | ||||
| 	if (m.pos == 0 && empty) || m.pos > len(m.value) { | ||||
| 		m.SetCursor(len(m.value)) | ||||
| 	} | ||||
| 	m.handleOverflow() | ||||
| } | ||||
|  | ||||
| // Value returns the value of the text input. | ||||
| func (m Model) Value() string { | ||||
| 	return string(m.value) | ||||
| } | ||||
|  | ||||
| // Position returns the cursor position. | ||||
| func (m Model) Position() int { | ||||
| 	return m.pos | ||||
| } | ||||
|  | ||||
| // SetCursor moves the cursor to the given position. If the position is | ||||
| // out of bounds the cursor will be moved to the start or end accordingly. | ||||
| func (m *Model) SetCursor(pos int) { | ||||
| 	m.pos = clamp(pos, 0, len(m.value)) | ||||
| 	m.handleOverflow() | ||||
| } | ||||
|  | ||||
| // CursorStart moves the cursor to the start of the input field. | ||||
| func (m *Model) CursorStart() { | ||||
| 	m.SetCursor(0) | ||||
| } | ||||
|  | ||||
| // CursorEnd moves the cursor to the end of the input field. | ||||
| func (m *Model) CursorEnd() { | ||||
| 	m.SetCursor(len(m.value)) | ||||
| } | ||||
|  | ||||
| // Focused returns the focus state on the model. | ||||
| func (m Model) Focused() bool { | ||||
| 	return m.focus | ||||
| } | ||||
|  | ||||
| // Focus sets the focus state on the model. When the model is in focus it can | ||||
| // receive keyboard input and the cursor will be shown. | ||||
| func (m *Model) Focus() tea.Cmd { | ||||
| 	m.focus = true | ||||
| 	return m.Cursor.Focus() | ||||
| } | ||||
|  | ||||
| // Blur removes the focus state on the model.  When the model is blurred it can | ||||
| // not receive keyboard input and the cursor will be hidden. | ||||
| func (m *Model) Blur() { | ||||
| 	m.focus = false | ||||
| 	m.Cursor.Blur() | ||||
| } | ||||
|  | ||||
| // Reset sets the input to its default state with no input. | ||||
| func (m *Model) Reset() { | ||||
| 	m.value = nil | ||||
| 	m.SetCursor(0) | ||||
| } | ||||
|  | ||||
| // SetSuggestions sets the suggestions for the input. | ||||
| func (m *Model) SetSuggestions(suggestions []string) { | ||||
| 	m.suggestions = make([][]rune, len(suggestions)) | ||||
| 	for i, s := range suggestions { | ||||
| 		m.suggestions[i] = []rune(s) | ||||
| 	} | ||||
|  | ||||
| 	m.updateSuggestions() | ||||
| } | ||||
|  | ||||
| // rsan initializes or retrieves the rune sanitizer. | ||||
| func (m *Model) san() runeutil.Sanitizer { | ||||
| 	if m.rsan == nil { | ||||
| 		// Textinput has all its input on a single line so collapse | ||||
| 		// newlines/tabs to single spaces. | ||||
| 		m.rsan = runeutil.NewSanitizer( | ||||
| 			runeutil.ReplaceTabs(" "), runeutil.ReplaceNewlines(" ")) | ||||
| 	} | ||||
| 	return m.rsan | ||||
| } | ||||
|  | ||||
| func (m *Model) insertRunesFromUserInput(v []rune) { | ||||
| 	// Clean up any special characters in the input provided by the | ||||
| 	// clipboard. This avoids bugs due to e.g. tab characters and | ||||
| 	// whatnot. | ||||
| 	paste := m.san().Sanitize(v) | ||||
|  | ||||
| 	var availSpace int | ||||
| 	if m.CharLimit > 0 { | ||||
| 		availSpace = m.CharLimit - len(m.value) | ||||
|  | ||||
| 		// If the char limit's been reached, cancel. | ||||
| 		if availSpace <= 0 { | ||||
| 			return | ||||
| 		} | ||||
|  | ||||
| 		// If there's not enough space to paste the whole thing cut the pasted | ||||
| 		// runes down so they'll fit. | ||||
| 		if availSpace < len(paste) { | ||||
| 			paste = paste[:availSpace] | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Stuff before and after the cursor | ||||
| 	head := m.value[:m.pos] | ||||
| 	tailSrc := m.value[m.pos:] | ||||
| 	tail := make([]rune, len(tailSrc)) | ||||
| 	copy(tail, tailSrc) | ||||
|  | ||||
| 	// Insert pasted runes | ||||
| 	for _, r := range paste { | ||||
| 		head = append(head, r) | ||||
| 		m.pos++ | ||||
| 		if m.CharLimit > 0 { | ||||
| 			availSpace-- | ||||
| 			if availSpace <= 0 { | ||||
| 				break | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Put it all back together | ||||
| 	value := append(head, tail...) | ||||
| 	inputErr := m.validate(value) | ||||
| 	m.setValueInternal(value, inputErr) | ||||
| } | ||||
|  | ||||
| // If a max width is defined, perform some logic to treat the visible area | ||||
| // as a horizontally scrolling viewport. | ||||
| func (m *Model) handleOverflow() { | ||||
| 	if m.Width <= 0 || uniseg.StringWidth(string(m.value)) <= m.Width { | ||||
| 		m.offset = 0 | ||||
| 		m.offsetRight = len(m.value) | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Correct right offset if we've deleted characters | ||||
| 	m.offsetRight = min(m.offsetRight, len(m.value)) | ||||
|  | ||||
| 	if m.pos < m.offset { | ||||
| 		m.offset = m.pos | ||||
|  | ||||
| 		w := 0 | ||||
| 		i := 0 | ||||
| 		runes := m.value[m.offset:] | ||||
|  | ||||
| 		for i < len(runes) && w <= m.Width { | ||||
| 			w += rw.RuneWidth(runes[i]) | ||||
| 			if w <= m.Width+1 { | ||||
| 				i++ | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		m.offsetRight = m.offset + i | ||||
| 	} else if m.pos >= m.offsetRight { | ||||
| 		m.offsetRight = m.pos | ||||
|  | ||||
| 		w := 0 | ||||
| 		runes := m.value[:m.offsetRight] | ||||
| 		i := len(runes) - 1 | ||||
|  | ||||
| 		for i > 0 && w < m.Width { | ||||
| 			w += rw.RuneWidth(runes[i]) | ||||
| 			if w <= m.Width { | ||||
| 				i-- | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		m.offset = m.offsetRight - (len(runes) - 1 - i) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // deleteBeforeCursor deletes all text before the cursor. | ||||
| func (m *Model) deleteBeforeCursor() { | ||||
| 	m.value = m.value[m.pos:] | ||||
| 	m.Err = m.validate(m.value) | ||||
| 	m.offset = 0 | ||||
| 	m.SetCursor(0) | ||||
| } | ||||
|  | ||||
| // deleteAfterCursor deletes all text after the cursor. If input is masked | ||||
| // delete everything after the cursor so as not to reveal word breaks in the | ||||
| // masked input. | ||||
| func (m *Model) deleteAfterCursor() { | ||||
| 	m.value = m.value[:m.pos] | ||||
| 	m.Err = m.validate(m.value) | ||||
| 	m.SetCursor(len(m.value)) | ||||
| } | ||||
|  | ||||
| // deleteWordBackward deletes the word left to the cursor. | ||||
| func (m *Model) deleteWordBackward() { | ||||
| 	if m.pos == 0 || len(m.value) == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if m.EchoMode != EchoNormal { | ||||
| 		m.deleteBeforeCursor() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	// Linter note: it's critical that we acquire the initial cursor position | ||||
| 	// here prior to altering it via SetCursor() below. As such, moving this | ||||
| 	// call into the corresponding if clause does not apply here. | ||||
| 	oldPos := m.pos //nolint:ifshort | ||||
|  | ||||
| 	m.SetCursor(m.pos - 1) | ||||
| 	for unicode.IsSpace(m.value[m.pos]) { | ||||
| 		if m.pos <= 0 { | ||||
| 			break | ||||
| 		} | ||||
| 		// ignore series of whitespace before cursor | ||||
| 		m.SetCursor(m.pos - 1) | ||||
| 	} | ||||
|  | ||||
| 	for m.pos > 0 { | ||||
| 		if !unicode.IsSpace(m.value[m.pos]) { | ||||
| 			m.SetCursor(m.pos - 1) | ||||
| 		} else { | ||||
| 			if m.pos > 0 { | ||||
| 				// keep the previous space | ||||
| 				m.SetCursor(m.pos + 1) | ||||
| 			} | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if oldPos > len(m.value) { | ||||
| 		m.value = m.value[:m.pos] | ||||
| 	} else { | ||||
| 		m.value = append(m.value[:m.pos], m.value[oldPos:]...) | ||||
| 	} | ||||
| 	m.Err = m.validate(m.value) | ||||
| } | ||||
|  | ||||
| // deleteWordForward deletes the word right to the cursor. If input is masked | ||||
| // delete everything after the cursor so as not to reveal word breaks in the | ||||
| // masked input. | ||||
| func (m *Model) deleteWordForward() { | ||||
| 	if m.pos >= len(m.value) || len(m.value) == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if m.EchoMode != EchoNormal { | ||||
| 		m.deleteAfterCursor() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	oldPos := m.pos | ||||
| 	m.SetCursor(m.pos + 1) | ||||
| 	for unicode.IsSpace(m.value[m.pos]) { | ||||
| 		// ignore series of whitespace after cursor | ||||
| 		m.SetCursor(m.pos + 1) | ||||
|  | ||||
| 		if m.pos >= len(m.value) { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for m.pos < len(m.value) { | ||||
| 		if !unicode.IsSpace(m.value[m.pos]) { | ||||
| 			m.SetCursor(m.pos + 1) | ||||
| 		} else { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	if m.pos > len(m.value) { | ||||
| 		m.value = m.value[:oldPos] | ||||
| 	} else { | ||||
| 		m.value = append(m.value[:oldPos], m.value[m.pos:]...) | ||||
| 	} | ||||
| 	m.Err = m.validate(m.value) | ||||
|  | ||||
| 	m.SetCursor(oldPos) | ||||
| } | ||||
|  | ||||
| // wordBackward moves the cursor one word to the left. If input is masked, move | ||||
| // input to the start so as not to reveal word breaks in the masked input. | ||||
| func (m *Model) wordBackward() { | ||||
| 	if m.pos == 0 || len(m.value) == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if m.EchoMode != EchoNormal { | ||||
| 		m.CursorStart() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	i := m.pos - 1 | ||||
| 	for i >= 0 { | ||||
| 		if unicode.IsSpace(m.value[i]) { | ||||
| 			m.SetCursor(m.pos - 1) | ||||
| 			i-- | ||||
| 		} else { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for i >= 0 { | ||||
| 		if !unicode.IsSpace(m.value[i]) { | ||||
| 			m.SetCursor(m.pos - 1) | ||||
| 			i-- | ||||
| 		} else { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // wordForward moves the cursor one word to the right. If the input is masked, | ||||
| // move input to the end so as not to reveal word breaks in the masked input. | ||||
| func (m *Model) wordForward() { | ||||
| 	if m.pos >= len(m.value) || len(m.value) == 0 { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if m.EchoMode != EchoNormal { | ||||
| 		m.CursorEnd() | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	i := m.pos | ||||
| 	for i < len(m.value) { | ||||
| 		if unicode.IsSpace(m.value[i]) { | ||||
| 			m.SetCursor(m.pos + 1) | ||||
| 			i++ | ||||
| 		} else { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	for i < len(m.value) { | ||||
| 		if !unicode.IsSpace(m.value[i]) { | ||||
| 			m.SetCursor(m.pos + 1) | ||||
| 			i++ | ||||
| 		} else { | ||||
| 			break | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (m Model) echoTransform(v string) string { | ||||
| 	switch m.EchoMode { | ||||
| 	case EchoPassword: | ||||
| 		return strings.Repeat(string(m.EchoCharacter), uniseg.StringWidth(v)) | ||||
| 	case EchoNone: | ||||
| 		return "" | ||||
| 	case EchoNormal: | ||||
| 		return v | ||||
| 	default: | ||||
| 		return v | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // Update is the Bubble Tea update loop. | ||||
| func (m Model) Update(msg tea.Msg) (Model, tea.Cmd) { | ||||
| 	if !m.focus { | ||||
| 		return m, nil | ||||
| 	} | ||||
|  | ||||
| 	// Need to check for completion before, because key is configurable and might be double assigned | ||||
| 	keyMsg, ok := msg.(tea.KeyMsg) | ||||
| 	if ok && key.Matches(keyMsg, m.KeyMap.AcceptSuggestion) { | ||||
| 		if m.canAcceptSuggestion() { | ||||
| 			m.value = append(m.value, m.matchedSuggestions[m.currentSuggestionIndex][len(m.value):]...) | ||||
| 			m.CursorEnd() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Let's remember where the position of the cursor currently is so that if | ||||
| 	// the cursor position changes, we can reset the blink. | ||||
| 	oldPos := m.pos | ||||
|  | ||||
| 	switch msg := msg.(type) { | ||||
| 	case tea.KeyMsg: | ||||
| 		switch { | ||||
| 		case key.Matches(msg, m.KeyMap.DeleteWordBackward): | ||||
| 			m.deleteWordBackward() | ||||
| 		case key.Matches(msg, m.KeyMap.DeleteCharacterBackward): | ||||
| 			m.Err = nil | ||||
| 			if len(m.value) > 0 { | ||||
| 				m.value = append(m.value[:max(0, m.pos-1)], m.value[m.pos:]...) | ||||
| 				m.Err = m.validate(m.value) | ||||
| 				if m.pos > 0 { | ||||
| 					m.SetCursor(m.pos - 1) | ||||
| 				} | ||||
| 			} | ||||
| 		case key.Matches(msg, m.KeyMap.WordBackward): | ||||
| 			m.wordBackward() | ||||
| 		case key.Matches(msg, m.KeyMap.CharacterBackward): | ||||
| 			if m.pos > 0 { | ||||
| 				m.SetCursor(m.pos - 1) | ||||
| 			} | ||||
| 		case key.Matches(msg, m.KeyMap.WordForward): | ||||
| 			m.wordForward() | ||||
| 		case key.Matches(msg, m.KeyMap.CharacterForward): | ||||
| 			if m.pos < len(m.value) { | ||||
| 				m.SetCursor(m.pos + 1) | ||||
| 			} | ||||
| 		case key.Matches(msg, m.KeyMap.LineStart): | ||||
| 			m.CursorStart() | ||||
| 		case key.Matches(msg, m.KeyMap.DeleteCharacterForward): | ||||
| 			if len(m.value) > 0 && m.pos < len(m.value) { | ||||
| 				m.value = append(m.value[:m.pos], m.value[m.pos+1:]...) | ||||
| 				m.Err = m.validate(m.value) | ||||
| 			} | ||||
| 		case key.Matches(msg, m.KeyMap.LineEnd): | ||||
| 			m.CursorEnd() | ||||
| 		case key.Matches(msg, m.KeyMap.DeleteAfterCursor): | ||||
| 			m.deleteAfterCursor() | ||||
| 		case key.Matches(msg, m.KeyMap.DeleteBeforeCursor): | ||||
| 			m.deleteBeforeCursor() | ||||
| 		case key.Matches(msg, m.KeyMap.Paste): | ||||
| 			return m, Paste | ||||
| 		case key.Matches(msg, m.KeyMap.DeleteWordForward): | ||||
| 			m.deleteWordForward() | ||||
| 		case key.Matches(msg, m.KeyMap.NextSuggestion): | ||||
| 			m.nextSuggestion() | ||||
| 		case key.Matches(msg, m.KeyMap.PrevSuggestion): | ||||
| 			m.previousSuggestion() | ||||
| 		default: | ||||
| 			// Input one or more regular characters. | ||||
| 			m.insertRunesFromUserInput(msg.Runes) | ||||
| 		} | ||||
|  | ||||
| 		// Check again if can be completed | ||||
| 		// because value might be something that does not match the completion prefix | ||||
| 		m.updateSuggestions() | ||||
|  | ||||
| 	case pasteMsg: | ||||
| 		m.insertRunesFromUserInput([]rune(msg)) | ||||
|  | ||||
| 	case pasteErrMsg: | ||||
| 		m.Err = msg | ||||
| 	} | ||||
|  | ||||
| 	var cmds []tea.Cmd | ||||
| 	var cmd tea.Cmd | ||||
|  | ||||
| 	m.Cursor, cmd = m.Cursor.Update(msg) | ||||
| 	cmds = append(cmds, cmd) | ||||
|  | ||||
| 	if oldPos != m.pos && m.Cursor.Mode() == cursor.CursorBlink { | ||||
| 		m.Cursor.Blink = false | ||||
| 		cmds = append(cmds, m.Cursor.BlinkCmd()) | ||||
| 	} | ||||
|  | ||||
| 	m.handleOverflow() | ||||
| 	return m, tea.Batch(cmds...) | ||||
| } | ||||
|  | ||||
| // View renders the textinput in its current state. | ||||
| func (m Model) View() string { | ||||
| 	// Placeholder text | ||||
| 	if len(m.value) == 0 && m.Placeholder != "" { | ||||
| 		return m.placeholderView() | ||||
| 	} | ||||
|  | ||||
| 	styleText := m.TextStyle.Inline(true).Render | ||||
|  | ||||
| 	value := m.value[m.offset:m.offsetRight] | ||||
| 	pos := max(0, m.pos-m.offset) | ||||
| 	v := styleText(m.echoTransform(string(value[:pos]))) | ||||
|  | ||||
| 	if pos < len(value) { //nolint:nestif | ||||
| 		char := m.echoTransform(string(value[pos])) | ||||
| 		m.Cursor.SetChar(char) | ||||
| 		v += m.Cursor.View()                                   // cursor and text under it | ||||
| 		v += styleText(m.echoTransform(string(value[pos+1:]))) // text after cursor | ||||
| 		v += m.completionView(0)                               // suggested completion | ||||
| 	} else { | ||||
| 		if m.focus && m.canAcceptSuggestion() { | ||||
| 			suggestion := m.matchedSuggestions[m.currentSuggestionIndex] | ||||
| 			if len(value) < len(suggestion) { | ||||
| 				m.Cursor.TextStyle = m.CompletionStyle | ||||
| 				m.Cursor.SetChar(m.echoTransform(string(suggestion[pos]))) | ||||
| 				v += m.Cursor.View() | ||||
| 				v += m.completionView(1) | ||||
| 			} else { | ||||
| 				m.Cursor.SetChar(" ") | ||||
| 				v += m.Cursor.View() | ||||
| 			} | ||||
| 		} else { | ||||
| 			m.Cursor.SetChar(" ") | ||||
| 			v += m.Cursor.View() | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// If a max width and background color were set fill the empty spaces with | ||||
| 	// the background color. | ||||
| 	valWidth := uniseg.StringWidth(string(value)) | ||||
| 	if m.Width > 0 && valWidth <= m.Width { | ||||
| 		padding := max(0, m.Width-valWidth) | ||||
| 		if valWidth+padding <= m.Width && pos < len(value) { | ||||
| 			padding++ | ||||
| 		} | ||||
| 		v += styleText(strings.Repeat(" ", padding)) | ||||
| 	} | ||||
|  | ||||
| 	return m.PromptStyle.Render(m.Prompt) + v | ||||
| } | ||||
|  | ||||
| // placeholderView returns the prompt and placeholder view, if any. | ||||
| func (m Model) placeholderView() string { | ||||
| 	var ( | ||||
| 		v     string | ||||
| 		style = m.PlaceholderStyle.Inline(true).Render | ||||
| 	) | ||||
|  | ||||
| 	p := make([]rune, m.Width+1) | ||||
| 	copy(p, []rune(m.Placeholder)) | ||||
|  | ||||
| 	m.Cursor.TextStyle = m.PlaceholderStyle | ||||
| 	m.Cursor.SetChar(string(p[:1])) | ||||
| 	v += m.Cursor.View() | ||||
|  | ||||
| 	// If the entire placeholder is already set and no padding is needed, finish | ||||
| 	if m.Width < 1 && len(p) <= 1 { | ||||
| 		return m.PromptStyle.Render(m.Prompt) + v | ||||
| 	} | ||||
|  | ||||
| 	// If Width is set then size placeholder accordingly | ||||
| 	if m.Width > 0 { | ||||
| 		// available width is width - len + cursor offset of 1 | ||||
| 		minWidth := lipgloss.Width(m.Placeholder) | ||||
| 		availWidth := m.Width - minWidth + 1 | ||||
|  | ||||
| 		// if width < len, 'subtract'(add) number to len and dont add padding | ||||
| 		if availWidth < 0 { | ||||
| 			minWidth += availWidth | ||||
| 			availWidth = 0 | ||||
| 		} | ||||
| 		// append placeholder[len] - cursor, append padding | ||||
| 		v += style(string(p[1:minWidth])) | ||||
| 		v += style(strings.Repeat(" ", availWidth)) | ||||
| 	} else { | ||||
| 		// if there is no width, the placeholder can be any length | ||||
| 		v += style(string(p[1:])) | ||||
| 	} | ||||
|  | ||||
| 	return m.PromptStyle.Render(m.Prompt) + v | ||||
| } | ||||
|  | ||||
| // Blink is a command used to initialize cursor blinking. | ||||
| func Blink() tea.Msg { | ||||
| 	return cursor.Blink() | ||||
| } | ||||
|  | ||||
| // Paste is a command for pasting from the clipboard into the text input. | ||||
| func Paste() tea.Msg { | ||||
| 	str, err := clipboard.ReadAll() | ||||
| 	if err != nil { | ||||
| 		return pasteErrMsg{err} | ||||
| 	} | ||||
| 	return pasteMsg(str) | ||||
| } | ||||
|  | ||||
| func clamp(v, low, high int) int { | ||||
| 	if high < low { | ||||
| 		low, high = high, low | ||||
| 	} | ||||
| 	return min(high, max(low, v)) | ||||
| } | ||||
|  | ||||
| // Deprecated. | ||||
|  | ||||
| // Deprecated: use [cursor.Mode]. | ||||
| // | ||||
| //nolint:revive | ||||
| type CursorMode int | ||||
|  | ||||
| //nolint:revive | ||||
| const ( | ||||
| 	// Deprecated: use [cursor.CursorBlink]. | ||||
| 	CursorBlink = CursorMode(cursor.CursorBlink) | ||||
| 	// Deprecated: use [cursor.CursorStatic]. | ||||
| 	CursorStatic = CursorMode(cursor.CursorStatic) | ||||
| 	// Deprecated: use [cursor.CursorHide]. | ||||
| 	CursorHide = CursorMode(cursor.CursorHide) | ||||
| ) | ||||
|  | ||||
| func (c CursorMode) String() string { | ||||
| 	return cursor.Mode(c).String() | ||||
| } | ||||
|  | ||||
| // Deprecated: use [cursor.Mode]. | ||||
| // | ||||
| //nolint:revive | ||||
| func (m Model) CursorMode() CursorMode { | ||||
| 	return CursorMode(m.Cursor.Mode()) | ||||
| } | ||||
|  | ||||
| // Deprecated: use cursor.SetMode(). | ||||
| // | ||||
| //nolint:revive | ||||
| func (m *Model) SetCursorMode(mode CursorMode) tea.Cmd { | ||||
| 	return m.Cursor.SetMode(cursor.Mode(mode)) | ||||
| } | ||||
|  | ||||
| func (m Model) completionView(offset int) string { | ||||
| 	var ( | ||||
| 		value = m.value | ||||
| 		style = m.PlaceholderStyle.Inline(true).Render | ||||
| 	) | ||||
|  | ||||
| 	if m.canAcceptSuggestion() { | ||||
| 		suggestion := m.matchedSuggestions[m.currentSuggestionIndex] | ||||
| 		if len(value) < len(suggestion) { | ||||
| 			return style(string(suggestion[len(value)+offset:])) | ||||
| 		} | ||||
| 	} | ||||
| 	return "" | ||||
| } | ||||
|  | ||||
| func (m *Model) getSuggestions(sugs [][]rune) []string { | ||||
| 	suggestions := make([]string, len(sugs)) | ||||
| 	for i, s := range sugs { | ||||
| 		suggestions[i] = string(s) | ||||
| 	} | ||||
| 	return suggestions | ||||
| } | ||||
|  | ||||
| // AvailableSuggestions returns the list of available suggestions. | ||||
| func (m *Model) AvailableSuggestions() []string { | ||||
| 	return m.getSuggestions(m.suggestions) | ||||
| } | ||||
|  | ||||
| // MatchedSuggestions returns the list of matched suggestions. | ||||
| func (m *Model) MatchedSuggestions() []string { | ||||
| 	return m.getSuggestions(m.matchedSuggestions) | ||||
| } | ||||
|  | ||||
| // CurrentSuggestionIndex returns the currently selected suggestion index. | ||||
| func (m *Model) CurrentSuggestionIndex() int { | ||||
| 	return m.currentSuggestionIndex | ||||
| } | ||||
|  | ||||
| // CurrentSuggestion returns the currently selected suggestion. | ||||
| func (m *Model) CurrentSuggestion() string { | ||||
| 	if m.currentSuggestionIndex >= len(m.matchedSuggestions) { | ||||
| 		return "" | ||||
| 	} | ||||
|  | ||||
| 	return string(m.matchedSuggestions[m.currentSuggestionIndex]) | ||||
| } | ||||
|  | ||||
| // canAcceptSuggestion returns whether there is an acceptable suggestion to | ||||
| // autocomplete the current value. | ||||
| func (m *Model) canAcceptSuggestion() bool { | ||||
| 	return len(m.matchedSuggestions) > 0 | ||||
| } | ||||
|  | ||||
| // updateSuggestions refreshes the list of matching suggestions. | ||||
| func (m *Model) updateSuggestions() { | ||||
| 	if !m.ShowSuggestions { | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	if len(m.value) <= 0 || len(m.suggestions) <= 0 { | ||||
| 		m.matchedSuggestions = [][]rune{} | ||||
| 		return | ||||
| 	} | ||||
|  | ||||
| 	matches := [][]rune{} | ||||
| 	for _, s := range m.suggestions { | ||||
| 		suggestion := string(s) | ||||
|  | ||||
| 		if strings.HasPrefix(strings.ToLower(suggestion), strings.ToLower(string(m.value))) { | ||||
| 			matches = append(matches, []rune(suggestion)) | ||||
| 		} | ||||
| 	} | ||||
| 	if !reflect.DeepEqual(matches, m.matchedSuggestions) { | ||||
| 		m.currentSuggestionIndex = 0 | ||||
| 	} | ||||
|  | ||||
| 	m.matchedSuggestions = matches | ||||
| } | ||||
|  | ||||
| // nextSuggestion selects the next suggestion. | ||||
| func (m *Model) nextSuggestion() { | ||||
| 	m.currentSuggestionIndex = (m.currentSuggestionIndex + 1) | ||||
| 	if m.currentSuggestionIndex >= len(m.matchedSuggestions) { | ||||
| 		m.currentSuggestionIndex = 0 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // previousSuggestion selects the previous suggestion. | ||||
| func (m *Model) previousSuggestion() { | ||||
| 	m.currentSuggestionIndex = (m.currentSuggestionIndex - 1) | ||||
| 	if m.currentSuggestionIndex < 0 { | ||||
| 		m.currentSuggestionIndex = len(m.matchedSuggestions) - 1 | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (m Model) validate(v []rune) error { | ||||
| 	if m.Validate != nil { | ||||
| 		return m.Validate(string(v)) | ||||
| 	} | ||||
| 	return nil | ||||
| } | ||||
							
								
								
									
										4
									
								
								vendor/github.com/charmbracelet/bubbletea/.golangci.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/charmbracelet/bubbletea/.golangci.yml
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -26,6 +26,10 @@ linters: | ||||
|     - whitespace | ||||
|     - wrapcheck | ||||
|   exclusions: | ||||
|     rules: | ||||
|       - text: '(slog|log)\.\w+' | ||||
|         linters: | ||||
|           - noctx | ||||
|     generated: lax | ||||
|     presets: | ||||
|       - common-false-positives | ||||
|  | ||||
							
								
								
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1,6 +1,6 @@ | ||||
| MIT License | ||||
|  | ||||
| Copyright (c) 2020-2023 Charmbracelet, Inc | ||||
| Copyright (c) 2020-2025 Charmbracelet, Inc | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
|  | ||||
							
								
								
									
										4
									
								
								vendor/github.com/charmbracelet/bubbletea/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								vendor/github.com/charmbracelet/bubbletea/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -9,7 +9,7 @@ | ||||
|     <br> | ||||
|     <a href="https://github.com/charmbracelet/bubbletea/releases"><img src="https://img.shields.io/github/release/charmbracelet/bubbletea.svg" alt="Latest Release"></a> | ||||
|     <a href="https://pkg.go.dev/github.com/charmbracelet/bubbletea?tab=doc"><img src="https://godoc.org/github.com/charmbracelet/bubbletea?status.svg" alt="GoDoc"></a> | ||||
|     <a href="https://github.com/charmbracelet/bubbletea/actions"><img src="https://github.com/charmbracelet/bubbletea/actions/workflows/build.yml/badge.svg" alt="Build Status"></a> | ||||
|     <a href="https://github.com/charmbracelet/bubbletea/actions"><img src="https://github.com/charmbracelet/bubbletea/actions/workflows/build.yml/badge.svg?branch=main" alt="Build Status"></a> | ||||
| </p> | ||||
|  | ||||
| The fun, functional and stateful way to build terminal apps. A Go framework | ||||
| @ -395,6 +395,6 @@ of days past. | ||||
|  | ||||
| Part of [Charm](https://charm.sh). | ||||
|  | ||||
| <a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-badge.jpg" width="400"></a> | ||||
| <a href="https://charm.sh/"><img alt="The Charm logo" src="https://stuff.charm.sh/charm-banner-next.jpg" width="400"></a> | ||||
|  | ||||
| Charm热爱开源 • Charm loves open source • نحنُ نحب المصادر المفتوحة | ||||
|  | ||||
							
								
								
									
										38
									
								
								vendor/github.com/charmbracelet/bubbletea/commands.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										38
									
								
								vendor/github.com/charmbracelet/bubbletea/commands.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -13,6 +13,27 @@ import ( | ||||
| //		       return tea.Batch(someCommand, someOtherCommand) | ||||
| //	    } | ||||
| func Batch(cmds ...Cmd) Cmd { | ||||
| 	return compactCmds[BatchMsg](cmds) | ||||
| } | ||||
|  | ||||
| // BatchMsg is a message used to perform a bunch of commands concurrently with | ||||
| // no ordering guarantees. You can send a BatchMsg with Batch. | ||||
| type BatchMsg []Cmd | ||||
|  | ||||
| // Sequence runs the given commands one at a time, in order. Contrast this with | ||||
| // Batch, which runs commands concurrently. | ||||
| func Sequence(cmds ...Cmd) Cmd { | ||||
| 	return compactCmds[sequenceMsg](cmds) | ||||
| } | ||||
|  | ||||
| // sequenceMsg is used internally to run the given commands in order. | ||||
| type sequenceMsg []Cmd | ||||
|  | ||||
| // compactCmds ignores any nil commands in cmds, and returns the most direct | ||||
| // command possible. That is, considering the non-nil commands, if there are | ||||
| // none it returns nil, if there is exactly one it returns that command | ||||
| // directly, else it returns the non-nil commands as type T. | ||||
| func compactCmds[T ~[]Cmd](cmds []Cmd) Cmd { | ||||
| 	var validCmds []Cmd //nolint:prealloc | ||||
| 	for _, c := range cmds { | ||||
| 		if c == nil { | ||||
| @ -27,26 +48,11 @@ func Batch(cmds ...Cmd) Cmd { | ||||
| 		return validCmds[0] | ||||
| 	default: | ||||
| 		return func() Msg { | ||||
| 			return BatchMsg(validCmds) | ||||
| 			return T(validCmds) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // BatchMsg is a message used to perform a bunch of commands concurrently with | ||||
| // no ordering guarantees. You can send a BatchMsg with Batch. | ||||
| type BatchMsg []Cmd | ||||
|  | ||||
| // Sequence runs the given commands one at a time, in order. Contrast this with | ||||
| // Batch, which runs commands concurrently. | ||||
| func Sequence(cmds ...Cmd) Cmd { | ||||
| 	return func() Msg { | ||||
| 		return sequenceMsg(cmds) | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // sequenceMsg is used internally to run the given commands in order. | ||||
| type sequenceMsg []Cmd | ||||
|  | ||||
| // Every is a command that ticks in sync with the system clock. So, if you | ||||
| // wanted to tick with the system clock every second, minute or hour you | ||||
| // could use this. It's also handy for having different things tick in sync. | ||||
|  | ||||
							
								
								
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/inputreader_windows.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/inputreader_windows.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -108,7 +108,7 @@ func prepareConsole(input windows.Handle, modes ...uint32) (originalMode uint32, | ||||
| 	return originalMode, nil | ||||
| } | ||||
|  | ||||
| // cancelMixin represents a goroutine-safe cancelation status. | ||||
| // cancelMixin represents a goroutine-safe cancellation status. | ||||
| type cancelMixin struct { | ||||
| 	unsafeCanceled bool | ||||
| 	lock           sync.Mutex | ||||
|  | ||||
							
								
								
									
										16
									
								
								vendor/github.com/charmbracelet/bubbletea/key_windows.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										16
									
								
								vendor/github.com/charmbracelet/bubbletea/key_windows.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -109,12 +109,12 @@ func peekAndReadConsInput(con *conInputReader) ([]coninput.InputRecord, error) { | ||||
| 	return events, nil | ||||
| } | ||||
|  | ||||
| // Convert i to unit32 or panic if it cannot be converted. Check satisifes lint G115. | ||||
| // Convert i to unit32 or panic if it cannot be converted. Check satisfies lint G115. | ||||
| func intToUint32OrDie(i int) uint32 { | ||||
| 	if i < 0 { | ||||
| 		panic("cannot convert numEvents " + fmt.Sprint(i) + " to uint32") | ||||
| 	} | ||||
| 	return uint32(i) | ||||
| 	return uint32(i) //nolint:gosec | ||||
| } | ||||
|  | ||||
| // Keeps peeking until there is data or the input is cancelled. | ||||
| @ -158,16 +158,16 @@ func mouseEventButton(p, s coninput.ButtonState) (button MouseButton, action Mou | ||||
| 		return button, action | ||||
| 	} | ||||
|  | ||||
| 	switch { | ||||
| 	case btn == coninput.FROM_LEFT_1ST_BUTTON_PRESSED: // left button | ||||
| 	switch btn { | ||||
| 	case coninput.FROM_LEFT_1ST_BUTTON_PRESSED: // left button | ||||
| 		button = MouseButtonLeft | ||||
| 	case btn == coninput.RIGHTMOST_BUTTON_PRESSED: // right button | ||||
| 	case coninput.RIGHTMOST_BUTTON_PRESSED: // right button | ||||
| 		button = MouseButtonRight | ||||
| 	case btn == coninput.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button | ||||
| 	case coninput.FROM_LEFT_2ND_BUTTON_PRESSED: // middle button | ||||
| 		button = MouseButtonMiddle | ||||
| 	case btn == coninput.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward) | ||||
| 	case coninput.FROM_LEFT_3RD_BUTTON_PRESSED: // unknown (possibly mouse backward) | ||||
| 		button = MouseButtonBackward | ||||
| 	case btn == coninput.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward) | ||||
| 	case coninput.FROM_LEFT_4TH_BUTTON_PRESSED: // unknown (possibly mouse forward) | ||||
| 		button = MouseButtonForward | ||||
| 	} | ||||
|  | ||||
|  | ||||
							
								
								
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/screen.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/screen.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -131,7 +131,7 @@ func EnableBracketedPaste() Msg { | ||||
| type enableBracketedPasteMsg struct{} | ||||
|  | ||||
| // DisableBracketedPaste is a special command that tells the Bubble Tea program | ||||
| // to accept bracketed paste input. | ||||
| // to stop processing bracketed paste input. | ||||
| // | ||||
| // Note that bracketed paste will be automatically disabled when the | ||||
| // program quits. | ||||
|  | ||||
							
								
								
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/standard_renderer.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/standard_renderer.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -277,7 +277,7 @@ func (r *standardRenderer) flush() { | ||||
| 		// using the full terminal window. | ||||
| 		buf.WriteString(ansi.CursorPosition(0, len(newLines))) | ||||
| 	} else { | ||||
| 		buf.WriteString(ansi.CursorBackward(r.width)) | ||||
| 		buf.WriteByte('\r') | ||||
| 	} | ||||
|  | ||||
| 	_, _ = r.out.Write(buf.Bytes()) | ||||
|  | ||||
							
								
								
									
										109
									
								
								vendor/github.com/charmbracelet/bubbletea/tea.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										109
									
								
								vendor/github.com/charmbracelet/bubbletea/tea.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -24,7 +24,6 @@ import ( | ||||
|  | ||||
| 	"github.com/charmbracelet/x/term" | ||||
| 	"github.com/muesli/cancelreader" | ||||
| 	"golang.org/x/sync/errgroup" | ||||
| ) | ||||
|  | ||||
| // ErrProgramPanic is returned by [Program.Run] when the program recovers from a panic. | ||||
| @ -73,7 +72,7 @@ const ( | ||||
| 	customInput | ||||
| ) | ||||
|  | ||||
| // String implements the stringer interface for [inputType]. It is inteded to | ||||
| // String implements the stringer interface for [inputType]. It is intended to | ||||
| // be used in testing. | ||||
| func (i inputType) String() string { | ||||
| 	return [...]string{ | ||||
| @ -220,7 +219,7 @@ func Suspend() Msg { | ||||
| // You can send this message with [Suspend()]. | ||||
| type SuspendMsg struct{} | ||||
|  | ||||
| // ResumeMsg can be listen to to do something once a program is resumed back | ||||
| // ResumeMsg can be listen to do something once a program is resumed back | ||||
| // from a suspend state. | ||||
| type ResumeMsg struct{} | ||||
|  | ||||
| @ -472,42 +471,12 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { | ||||
| 				p.exec(msg.cmd, msg.fn) | ||||
|  | ||||
| 			case BatchMsg: | ||||
| 				for _, cmd := range msg { | ||||
| 					select { | ||||
| 					case <-p.ctx.Done(): | ||||
| 						return model, nil | ||||
| 					case cmds <- cmd: | ||||
| 					} | ||||
| 				} | ||||
| 				go p.execBatchMsg(msg) | ||||
| 				continue | ||||
|  | ||||
| 			case sequenceMsg: | ||||
| 				go func() { | ||||
| 					// Execute commands one at a time, in order. | ||||
| 					for _, cmd := range msg { | ||||
| 						if cmd == nil { | ||||
| 							continue | ||||
| 						} | ||||
|  | ||||
| 						msg := cmd() | ||||
| 						if batchMsg, ok := msg.(BatchMsg); ok { | ||||
| 							g, _ := errgroup.WithContext(p.ctx) | ||||
| 							for _, cmd := range batchMsg { | ||||
| 								cmd := cmd | ||||
| 								g.Go(func() error { | ||||
| 									p.Send(cmd()) | ||||
| 									return nil | ||||
| 								}) | ||||
| 							} | ||||
|  | ||||
| 							//nolint:errcheck,gosec | ||||
| 							g.Wait() // wait for all commands from batch msg to finish | ||||
| 							continue | ||||
| 						} | ||||
|  | ||||
| 						p.Send(msg) | ||||
| 					} | ||||
| 				}() | ||||
| 				go p.execSequenceMsg(msg) | ||||
| 				continue | ||||
|  | ||||
| 			case setWindowTitleMsg: | ||||
| 				p.SetWindowTitle(string(msg)) | ||||
| @ -535,6 +504,74 @@ func (p *Program) eventLoop(model Model, cmds chan Cmd) (Model, error) { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *Program) execSequenceMsg(msg sequenceMsg) { | ||||
| 	if !p.startupOptions.has(withoutCatchPanics) { | ||||
| 		defer func() { | ||||
| 			if r := recover(); r != nil { | ||||
| 				p.recoverFromGoPanic(r) | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
|  | ||||
| 	// Execute commands one at a time, in order. | ||||
| 	for _, cmd := range msg { | ||||
| 		if cmd == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		msg := cmd() | ||||
| 		switch msg := msg.(type) { | ||||
| 		case BatchMsg: | ||||
| 			p.execBatchMsg(msg) | ||||
| 		case sequenceMsg: | ||||
| 			p.execSequenceMsg(msg) | ||||
| 		default: | ||||
| 			p.Send(msg) | ||||
| 		} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (p *Program) execBatchMsg(msg BatchMsg) { | ||||
| 	if !p.startupOptions.has(withoutCatchPanics) { | ||||
| 		defer func() { | ||||
| 			if r := recover(); r != nil { | ||||
| 				p.recoverFromGoPanic(r) | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
|  | ||||
| 	// Execute commands one at a time. | ||||
| 	var wg sync.WaitGroup | ||||
| 	for _, cmd := range msg { | ||||
| 		if cmd == nil { | ||||
| 			continue | ||||
| 		} | ||||
| 		wg.Add(1) | ||||
| 		go func() { | ||||
| 			defer wg.Done() | ||||
|  | ||||
| 			if !p.startupOptions.has(withoutCatchPanics) { | ||||
| 				defer func() { | ||||
| 					if r := recover(); r != nil { | ||||
| 						p.recoverFromGoPanic(r) | ||||
| 					} | ||||
| 				}() | ||||
| 			} | ||||
|  | ||||
| 			msg := cmd() | ||||
| 			switch msg := msg.(type) { | ||||
| 			case BatchMsg: | ||||
| 				p.execBatchMsg(msg) | ||||
| 			case sequenceMsg: | ||||
| 				p.execSequenceMsg(msg) | ||||
| 			default: | ||||
| 				p.Send(msg) | ||||
| 			} | ||||
| 		}() | ||||
| 	} | ||||
|  | ||||
| 	wg.Wait() // wait for all commands from batch msg to finish | ||||
| } | ||||
|  | ||||
| // Run initializes the program and runs its event loops, blocking until it gets | ||||
| // terminated by either [Program.Quit], [Program.Kill], or its signal handler. | ||||
| // Returns the final model. | ||||
|  | ||||
							
								
								
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/tty_windows.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/charmbracelet/bubbletea/tty_windows.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -56,7 +56,7 @@ func (p *Program) initInput() (err error) { | ||||
|  | ||||
| // Open the Windows equivalent of a TTY. | ||||
| func openInputTTY() (*os.File, error) { | ||||
| 	f, err := os.OpenFile("CONIN$", os.O_RDWR, 0o644) | ||||
| 	f, err := os.OpenFile("CONIN$", os.O_RDWR, 0o644) //nolint:gosec | ||||
| 	if err != nil { | ||||
| 		return nil, fmt.Errorf("error opening file: %w", err) | ||||
| 	} | ||||
|  | ||||
							
								
								
									
										24
									
								
								vendor/github.com/charmbracelet/x/ansi/inband.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								vendor/github.com/charmbracelet/x/ansi/inband.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,24 @@ | ||||
| package ansi | ||||
|  | ||||
| import "fmt" | ||||
|  | ||||
| // InBandResize encodes an in-band terminal resize event sequence. | ||||
| // | ||||
| //	CSI 48 ; height_cells ; widht_cells ; height_pixels ; width_pixels t | ||||
| // | ||||
| // See https://gist.github.com/rockorager/e695fb2924d36b2bcf1fff4a3704bd83 | ||||
| func InBandResize(heightCells, widthCells, heightPixels, widthPixels int) string { | ||||
| 	if heightCells < 0 { | ||||
| 		heightCells = 0 | ||||
| 	} | ||||
| 	if widthCells < 0 { | ||||
| 		widthCells = 0 | ||||
| 	} | ||||
| 	if heightPixels < 0 { | ||||
| 		heightPixels = 0 | ||||
| 	} | ||||
| 	if widthPixels < 0 { | ||||
| 		widthPixels = 0 | ||||
| 	} | ||||
| 	return fmt.Sprintf("\x1b[48;%d;%d;%d;%dt", heightCells, widthCells, heightPixels, widthPixels) | ||||
| } | ||||
							
								
								
									
										34
									
								
								vendor/github.com/charmbracelet/x/ansi/palette.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										34
									
								
								vendor/github.com/charmbracelet/x/ansi/palette.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,34 @@ | ||||
| package ansi | ||||
|  | ||||
| import ( | ||||
| 	"fmt" | ||||
| 	"image/color" | ||||
| ) | ||||
|  | ||||
| // SetPalette sets the palette color for the given index. The index is a 16 | ||||
| // color index between 0 and 15. The color is a 24-bit RGB color. | ||||
| // | ||||
| //	OSC P n rrggbb BEL | ||||
| // | ||||
| // Where n is the color index in hex (0-f), and rrggbb is the color in | ||||
| // hexadecimal format (e.g., ff0000 for red). | ||||
| // | ||||
| // This sequence is specific to the Linux Console and may not work in other | ||||
| // terminal emulators. | ||||
| // | ||||
| // See https://man7.org/linux/man-pages/man4/console_codes.4.html | ||||
| func SetPalette(i int, c color.Color) string { | ||||
| 	if c == nil || i < 0 || i > 15 { | ||||
| 		return "" | ||||
| 	} | ||||
| 	r, g, b, _ := c.RGBA() | ||||
| 	return fmt.Sprintf("\x1b]P%x%02x%02x%02x\x07", i, r>>8, g>>8, b>>8) | ||||
| } | ||||
|  | ||||
| // ResetPalette resets the color palette to the default values. | ||||
| // | ||||
| // This sequence is specific to the Linux Console and may not work in other | ||||
| // terminal emulators. | ||||
| // | ||||
| // See https://man7.org/linux/man-pages/man4/console_codes.4.html | ||||
| const ResetPalette = "\x1b]R\x07" | ||||
							
								
								
									
										49
									
								
								vendor/github.com/charmbracelet/x/ansi/progress.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								vendor/github.com/charmbracelet/x/ansi/progress.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | ||||
| package ansi | ||||
|  | ||||
| import "strconv" | ||||
|  | ||||
| // ResetProgressBar is a sequence that resets the progress bar to its default | ||||
| // state (hidden). | ||||
| // | ||||
| // OSC 9 ; 4 ; 0 BEL | ||||
| // | ||||
| // See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences | ||||
| const ResetProgressBar = "\x1b]9;4;0\x07" | ||||
|  | ||||
| // SetProgressBar returns a sequence for setting the progress bar to a specific | ||||
| // percentage (0-100) in the "default" state. | ||||
| // | ||||
| // OSC 9 ; 4 ; 1 Percentage BEL | ||||
| // | ||||
| // See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences | ||||
| func SetProgressBar(percentage int) string { | ||||
| 	return "\x1b]9;4;1;" + strconv.Itoa(min(max(0, percentage), 100)) + "\x07" | ||||
| } | ||||
|  | ||||
| // SetErrorProgressBar returns a sequence for setting the progress bar to a | ||||
| // specific percentage (0-100) in the "Error" state.. | ||||
| // | ||||
| // OSC 9 ; 4 ; 2 Percentage BEL | ||||
| // | ||||
| // See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences | ||||
| func SetErrorProgressBar(percentage int) string { | ||||
| 	return "\x1b]9;4;2;" + strconv.Itoa(min(max(0, percentage), 100)) + "\x07" | ||||
| } | ||||
|  | ||||
| // SetIndeterminateProgressBar is a sequence that sets the progress bar to the | ||||
| // indeterminate state. | ||||
| // | ||||
| // OSC 9 ; 4 ; 3 BEL | ||||
| // | ||||
| // See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences | ||||
| const SetIndeterminateProgressBar = "\x1b]9;4;3\x07" | ||||
|  | ||||
| // SetWarningProgressBar is a sequence that sets the progress bar to the | ||||
| // "Warning" state. | ||||
| // | ||||
| // OSC 9 ; 4 ; 4 Percentage BEL | ||||
| // | ||||
| // See: https://learn.microsoft.com/en-us/windows/terminal/tutorials/progress-bar-sequences | ||||
| func SetWarningProgressBar(percentage int) string { | ||||
| 	return "\x1b]9;4;4;" + strconv.Itoa(min(max(0, percentage), 100)) + "\x07" | ||||
| } | ||||
							
								
								
									
										6
									
								
								vendor/github.com/charmbracelet/x/ansi/winop.go
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								vendor/github.com/charmbracelet/x/ansi/winop.go
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -8,16 +8,22 @@ import ( | ||||
| const ( | ||||
| 	// ResizeWindowWinOp is a window operation that resizes the terminal | ||||
| 	// window. | ||||
| 	// | ||||
| 	// Deprecated: Use constant number directly with [WindowOp]. | ||||
| 	ResizeWindowWinOp = 4 | ||||
|  | ||||
| 	// RequestWindowSizeWinOp is a window operation that requests a report of | ||||
| 	// the size of the terminal window in pixels. The response is in the form: | ||||
| 	//  CSI 4 ; height ; width t | ||||
| 	// | ||||
| 	// Deprecated: Use constant number directly with [WindowOp]. | ||||
| 	RequestWindowSizeWinOp = 14 | ||||
|  | ||||
| 	// RequestCellSizeWinOp is a window operation that requests a report of | ||||
| 	// the size of the terminal cell size in pixels. The response is in the form: | ||||
| 	//  CSI 6 ; height ; width t | ||||
| 	// | ||||
| 	// Deprecated: Use constant number directly with [WindowOp]. | ||||
| 	RequestCellSizeWinOp = 16 | ||||
| ) | ||||
|  | ||||
|  | ||||
							
								
								
									
										21
									
								
								vendor/github.com/clipperhouse/uax29/v2/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/clipperhouse/uax29/v2/LICENSE
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | ||||
| MIT License | ||||
|  | ||||
| Copyright (c) 2020 Matt Sherman | ||||
|  | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
|  | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
|  | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
							
								
								
									
										82
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/README.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,82 @@ | ||||
| An implementation of grapheme cluster boundaries from [Unicode text segmentation](https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries) (UAX 29), for Unicode version 15.0.0. | ||||
|  | ||||
| ## Quick start | ||||
|  | ||||
| ``` | ||||
| go get "github.com/clipperhouse/uax29/v2/graphemes" | ||||
| ``` | ||||
|  | ||||
| ```go | ||||
| import "github.com/clipperhouse/uax29/v2/graphemes" | ||||
|  | ||||
| text := "Hello, 世界. Nice dog! 👍🐶" | ||||
|  | ||||
| tokens := graphemes.FromString(text) | ||||
|  | ||||
| for tokens.Next() {                     // Next() returns true until end of data | ||||
| 	fmt.Println(tokens.Value())         // Do something with the current grapheme | ||||
| } | ||||
| ``` | ||||
|  | ||||
| [](https://pkg.go.dev/github.com/clipperhouse/uax29/v2/graphemes) | ||||
|  | ||||
| _A grapheme is a “single visible character”, which might be a simple as a single letter, or a complex emoji that consists of several Unicode code points._ | ||||
|  | ||||
| ## Conformance | ||||
|  | ||||
| We use the Unicode [test suite](https://unicode.org/reports/tr41/tr41-26.html#Tests29). Status: | ||||
|  | ||||
|  | ||||
|  | ||||
| ## APIs | ||||
|  | ||||
| ### If you have a `string` | ||||
|  | ||||
| ```go | ||||
| text := "Hello, 世界. Nice dog! 👍🐶" | ||||
|  | ||||
| tokens := graphemes.FromString(text) | ||||
|  | ||||
| for tokens.Next() {                     // Next() returns true until end of data | ||||
| 	fmt.Println(tokens.Value())         // Do something with the current grapheme | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### If you have an `io.Reader` | ||||
|  | ||||
| `FromReader` embeds a [`bufio.Scanner`](https://pkg.go.dev/bufio#Scanner), so just use those methods. | ||||
|  | ||||
| ```go | ||||
| r := getYourReader()                        // from a file or network maybe | ||||
| tokens := graphemes.FromReader(r) | ||||
|  | ||||
| for tokens.Scan() {                         // Scan() returns true until error or EOF | ||||
| 	fmt.Println(tokens.Text())              // Do something with the current grapheme | ||||
| } | ||||
|  | ||||
| if tokens.Err() != nil {                    // Check the error | ||||
| 	log.Fatal(tokens.Err()) | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### If you have a `[]byte` | ||||
|  | ||||
| ```go | ||||
| b := []byte("Hello, 世界. Nice dog! 👍🐶") | ||||
|  | ||||
| tokens := graphemes.FromBytes(b) | ||||
|  | ||||
| for tokens.Next() {                     // Next() returns true until end of data | ||||
| 	fmt.Println(tokens.Value())         // Do something with the current grapheme | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Performance | ||||
|  | ||||
| On a Mac M2 laptop, we see around 200MB/s, or around 100 million graphemes per second. You should see ~constant memory, and no allocations. | ||||
|  | ||||
| ### Invalid inputs | ||||
|  | ||||
| Invalid UTF-8 input is considered undefined behavior. We test to ensure that bad inputs will not cause pathological outcomes, such as a panic or infinite loop. Callers should expect “garbage-in, garbage-out”. | ||||
|  | ||||
| Your pipeline should probably include a call to [`utf8.Valid()`](https://pkg.go.dev/unicode/utf8#Valid). | ||||
							
								
								
									
										28
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/iterator.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/iterator.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,28 @@ | ||||
| package graphemes | ||||
|  | ||||
| import "github.com/clipperhouse/uax29/v2/internal/iterators" | ||||
|  | ||||
| type Iterator[T iterators.Stringish] struct { | ||||
| 	*iterators.Iterator[T] | ||||
| } | ||||
|  | ||||
| var ( | ||||
| 	splitFuncString = splitFunc[string] | ||||
| 	splitFuncBytes  = splitFunc[[]byte] | ||||
| ) | ||||
|  | ||||
| // FromString returns an iterator for the grapheme clusters in the input string. | ||||
| // Iterate while Next() is true, and access the grapheme via Value(). | ||||
| func FromString(s string) Iterator[string] { | ||||
| 	return Iterator[string]{ | ||||
| 		iterators.New(splitFuncString, s), | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // FromBytes returns an iterator for the grapheme clusters in the input bytes. | ||||
| // Iterate while Next() is true, and access the grapheme via Value(). | ||||
| func FromBytes(b []byte) Iterator[[]byte] { | ||||
| 	return Iterator[[]byte]{ | ||||
| 		iterators.New(splitFuncBytes, b), | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										25
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/reader.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/reader.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | ||||
| // Package graphemes implements Unicode grapheme cluster boundaries: https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries | ||||
| package graphemes | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
| 	"io" | ||||
| ) | ||||
|  | ||||
| type Scanner struct { | ||||
| 	*bufio.Scanner | ||||
| } | ||||
|  | ||||
| // FromReader returns a Scanner, to split graphemes per | ||||
| // https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries. | ||||
| // | ||||
| // It embeds a [bufio.Scanner], so you can use its methods. | ||||
| // | ||||
| // Iterate through graphemes by calling Scan() until false, then check Err(). | ||||
| func FromReader(r io.Reader) *Scanner { | ||||
| 	sc := bufio.NewScanner(r) | ||||
| 	sc.Split(SplitFunc) | ||||
| 	return &Scanner{ | ||||
| 		Scanner: sc, | ||||
| 	} | ||||
| } | ||||
							
								
								
									
										174
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/splitfunc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										174
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/splitfunc.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,174 @@ | ||||
| package graphemes | ||||
|  | ||||
| import ( | ||||
| 	"bufio" | ||||
|  | ||||
| 	"github.com/clipperhouse/uax29/v2/internal/iterators" | ||||
| ) | ||||
|  | ||||
| // is determines if lookup intersects propert(ies) | ||||
| func (lookup property) is(properties property) bool { | ||||
| 	return (lookup & properties) != 0 | ||||
| } | ||||
|  | ||||
| const _Ignore = _Extend | ||||
|  | ||||
| // SplitFunc is a bufio.SplitFunc implementation of Unicode grapheme cluster segmentation, for use with bufio.Scanner. | ||||
| // | ||||
| // See https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries. | ||||
| var SplitFunc bufio.SplitFunc = splitFunc[[]byte] | ||||
|  | ||||
| func splitFunc[T iterators.Stringish](data T, atEOF bool) (advance int, token T, err error) { | ||||
| 	var empty T | ||||
| 	if len(data) == 0 { | ||||
| 		return 0, empty, nil | ||||
| 	} | ||||
|  | ||||
| 	// These vars are stateful across loop iterations | ||||
| 	var pos int | ||||
| 	var lastExIgnore property = 0     // "last excluding ignored categories" | ||||
| 	var lastLastExIgnore property = 0 // "last one before that" | ||||
| 	var regionalIndicatorCount int | ||||
|  | ||||
| 	// Rules are usually of the form Cat1 × Cat2; "current" refers to the first property | ||||
| 	// to the right of the ×, from which we look back or forward | ||||
|  | ||||
| 	current, w := lookup(data[pos:]) | ||||
| 	if w == 0 { | ||||
| 		if !atEOF { | ||||
| 			// Rune extends past current data, request more | ||||
| 			return 0, empty, nil | ||||
| 		} | ||||
| 		pos = len(data) | ||||
| 		return pos, data[:pos], nil | ||||
| 	} | ||||
|  | ||||
| 	// https://unicode.org/reports/tr29/#GB1 | ||||
| 	// Start of text always advances | ||||
| 	pos += w | ||||
|  | ||||
| 	for { | ||||
| 		eot := pos == len(data) // "end of text" | ||||
|  | ||||
| 		if eot { | ||||
| 			if !atEOF { | ||||
| 				// Token extends past current data, request more | ||||
| 				return 0, empty, nil | ||||
| 			} | ||||
|  | ||||
| 			// https://unicode.org/reports/tr29/#GB2 | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		/* | ||||
| 			We've switched the evaluation order of GB1↓ and GB2↑. It's ok: | ||||
| 			because we've checked for len(data) at the top of this function, | ||||
| 			sot and eot are mutually exclusive, order doesn't matter. | ||||
| 		*/ | ||||
|  | ||||
| 		// Rules are usually of the form Cat1 × Cat2; "current" refers to the first property | ||||
| 		// to the right of the ×, from which we look back or forward | ||||
|  | ||||
| 		// Remember previous properties to avoid lookups/lookbacks | ||||
| 		last := current | ||||
| 		if !last.is(_Ignore) { | ||||
| 			lastLastExIgnore = lastExIgnore | ||||
| 			lastExIgnore = last | ||||
| 		} | ||||
|  | ||||
| 		current, w = lookup(data[pos:]) | ||||
| 		if w == 0 { | ||||
| 			if atEOF { | ||||
| 				// Just return the bytes, we can't do anything with them | ||||
| 				pos = len(data) | ||||
| 				break | ||||
| 			} | ||||
| 			// Rune extends past current data, request more | ||||
| 			return 0, empty, nil | ||||
| 		} | ||||
|  | ||||
| 		// Optimization: no rule can possibly apply | ||||
| 		if current|last == 0 { // i.e. both are zero | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB3 | ||||
| 		if current.is(_LF) && last.is(_CR) { | ||||
| 			pos += w | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB4 | ||||
| 		// https://unicode.org/reports/tr29/#GB5 | ||||
| 		if (current | last).is(_Control | _CR | _LF) { | ||||
| 			break | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB6 | ||||
| 		if current.is(_L|_V|_LV|_LVT) && last.is(_L) { | ||||
| 			pos += w | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB7 | ||||
| 		if current.is(_V|_T) && last.is(_LV|_V) { | ||||
| 			pos += w | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB8 | ||||
| 		if current.is(_T) && last.is(_LVT|_T) { | ||||
| 			pos += w | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB9 | ||||
| 		if current.is(_Extend | _ZWJ) { | ||||
| 			pos += w | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB9a | ||||
| 		if current.is(_SpacingMark) { | ||||
| 			pos += w | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB9b | ||||
| 		if last.is(_Prepend) { | ||||
| 			pos += w | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB9c | ||||
| 		// TODO(clipperhouse): | ||||
| 		// It appears to be added in Unicode 15.1.0: | ||||
| 		// https://unicode.org/versions/Unicode15.1.0/#Migration | ||||
| 		// This package currently supports Unicode 15.0.0, so | ||||
| 		// out of scope for now | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB11 | ||||
| 		if current.is(_ExtendedPictographic) && last.is(_ZWJ) && lastLastExIgnore.is(_ExtendedPictographic) { | ||||
| 			pos += w | ||||
| 			continue | ||||
| 		} | ||||
|  | ||||
| 		// https://unicode.org/reports/tr29/#GB12 | ||||
| 		// https://unicode.org/reports/tr29/#GB13 | ||||
| 		if (current & last).is(_RegionalIndicator) { | ||||
| 			regionalIndicatorCount++ | ||||
|  | ||||
| 			odd := regionalIndicatorCount%2 == 1 | ||||
| 			if odd { | ||||
| 				pos += w | ||||
| 				continue | ||||
| 			} | ||||
| 		} | ||||
|  | ||||
| 		// If we fall through all the above rules, it's a grapheme cluster break | ||||
| 		break | ||||
| 	} | ||||
|  | ||||
| 	// Return token | ||||
| 	return pos, data[:pos], nil | ||||
| } | ||||
							
								
								
									
										1409
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										1409
									
								
								vendor/github.com/clipperhouse/uax29/v2/graphemes/trie.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										85
									
								
								vendor/github.com/clipperhouse/uax29/v2/internal/iterators/iterator.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								vendor/github.com/clipperhouse/uax29/v2/internal/iterators/iterator.go
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | ||||
| package iterators | ||||
|  | ||||
| type Stringish interface { | ||||
| 	[]byte | string | ||||
| } | ||||
|  | ||||
| type SplitFunc[T Stringish] func(T, bool) (int, T, error) | ||||
|  | ||||
| // Iterator is a generic iterator for words that are either []byte or string. | ||||
| // Iterate while Next() is true, and access the word via Value(). | ||||
| type Iterator[T Stringish] struct { | ||||
| 	split SplitFunc[T] | ||||
| 	data  T | ||||
| 	start int | ||||
| 	pos   int | ||||
| } | ||||
|  | ||||
| // New creates a new Iterator for the given data and SplitFunc. | ||||
| func New[T Stringish](split SplitFunc[T], data T) *Iterator[T] { | ||||
| 	return &Iterator[T]{ | ||||
| 		split: split, | ||||
| 		data:  data, | ||||
| 	} | ||||
| } | ||||
|  | ||||
| // SetText sets the text for the iterator to operate on, and resets all state. | ||||
| func (iter *Iterator[T]) SetText(data T) { | ||||
| 	iter.data = data | ||||
| 	iter.start = 0 | ||||
| 	iter.pos = 0 | ||||
| } | ||||
|  | ||||
| // Split sets the SplitFunc for the Iterator. | ||||
| func (iter *Iterator[T]) Split(split SplitFunc[T]) { | ||||
| 	iter.split = split | ||||
| } | ||||
|  | ||||
| // Next advances the iterator to the next token. It returns false when there | ||||
| // are no remaining tokens or an error occurred. | ||||
| func (iter *Iterator[T]) Next() bool { | ||||
| 	if iter.pos == len(iter.data) { | ||||
| 		return false | ||||
| 	} | ||||
| 	if iter.pos > len(iter.data) { | ||||
| 		panic("SplitFunc advanced beyond the end of the data") | ||||
| 	} | ||||
|  | ||||
| 	iter.start = iter.pos | ||||
|  | ||||
| 	advance, _, err := iter.split(iter.data[iter.pos:], true) | ||||
| 	if err != nil { | ||||
| 		panic(err) | ||||
| 	} | ||||
| 	if advance <= 0 { | ||||
| 		panic("SplitFunc returned a zero or negative advance") | ||||
| 	} | ||||
|  | ||||
| 	iter.pos += advance | ||||
| 	if iter.pos > len(iter.data) { | ||||
| 		panic("SplitFunc advanced beyond the end of the data") | ||||
| 	} | ||||
|  | ||||
| 	return true | ||||
| } | ||||
|  | ||||
| // Value returns the current token. | ||||
| func (iter *Iterator[T]) Value() T { | ||||
| 	return iter.data[iter.start:iter.pos] | ||||
| } | ||||
|  | ||||
| // Start returns the byte position of the current token in the original data. | ||||
| func (iter *Iterator[T]) Start() int { | ||||
| 	return iter.start | ||||
| } | ||||
|  | ||||
| // End returns the byte position after the current token in the original data. | ||||
| func (iter *Iterator[T]) End() int { | ||||
| 	return iter.pos | ||||
| } | ||||
|  | ||||
| // Reset resets the iterator to the beginning of the data. | ||||
| func (iter *Iterator[T]) Reset() { | ||||
| 	iter.start = 0 | ||||
| 	iter.pos = 0 | ||||
| } | ||||
							
								
								
									
										56
									
								
								vendor/github.com/cyphar/filepath-securejoin/.golangci.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								vendor/github.com/cyphar/filepath-securejoin/.golangci.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,56 @@ | ||||
| # SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| # Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| # Copyright (C) 2025 SUSE LLC | ||||
| # | ||||
| # This Source Code Form is subject to the terms of the Mozilla Public | ||||
| # License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| # file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| version: "2" | ||||
|  | ||||
| linters: | ||||
|   enable: | ||||
|     - asasalint | ||||
|     - asciicheck | ||||
|     - containedctx | ||||
|     - contextcheck | ||||
|     - errcheck | ||||
|     - errorlint | ||||
|     - exhaustive | ||||
|     - forcetypeassert | ||||
|     - godot | ||||
|     - goprintffuncname | ||||
|     - govet | ||||
|     - importas | ||||
|     - ineffassign | ||||
|     - makezero | ||||
|     - misspell | ||||
|     - musttag | ||||
|     - nilerr | ||||
|     - nilnesserr | ||||
|     - nilnil | ||||
|     - noctx | ||||
|     - prealloc | ||||
|     - revive | ||||
|     - staticcheck | ||||
|     - testifylint | ||||
|     - unconvert | ||||
|     - unparam | ||||
|     - unused | ||||
|     - usetesting | ||||
|   settings: | ||||
|     govet: | ||||
|       enable: | ||||
|         - nilness | ||||
|     testifylint: | ||||
|       enable-all: true | ||||
|  | ||||
| formatters: | ||||
|   enable: | ||||
|     - gofumpt | ||||
|     - goimports | ||||
|   settings: | ||||
|     goimports: | ||||
|       local-prefixes: | ||||
|         - github.com/cyphar/filepath-securejoin | ||||
							
								
								
									
										121
									
								
								vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										121
									
								
								vendor/github.com/cyphar/filepath-securejoin/CHANGELOG.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -6,6 +6,122 @@ and this project adheres to [Semantic Versioning](http://semver.org/). | ||||
|  | ||||
| ## [Unreleased] ## | ||||
|  | ||||
| ## [0.5.0] - 2025-09-26 ## | ||||
|  | ||||
| > Let the past die. Kill it if you have to. | ||||
|  | ||||
| > **NOTE**: With this release, some parts of | ||||
| > `github.com/cyphar/filepath-securejoin` are now licensed under the Mozilla | ||||
| > Public License (version 2). Please see [COPYING.md][] as well as the the | ||||
| > license header in each file for more details. | ||||
|  | ||||
| [COPYING.md]: ./COPYING.md | ||||
|  | ||||
| ### Breaking ### | ||||
| - The new API introduced in the [0.3.0][] release has been moved to a new | ||||
|   subpackage called `pathrs-lite`. This was primarily done to better indicate | ||||
|   the split between the new and old APIs, as well as indicate to users the | ||||
|   purpose of this subpackage (it is a less complete version of [libpathrs][]). | ||||
|  | ||||
|   We have added some wrappers to the top-level package to ease the transition, | ||||
|   but those are deprecated and will be removed in the next minor release of | ||||
|   filepath-securejoin. Users should update their import paths. | ||||
|  | ||||
|   This new subpackage has also been relicensed under the Mozilla Public License | ||||
|   (version 2), please see [COPYING.md][] for more details. | ||||
|  | ||||
| ### Added ### | ||||
| - Most of the key bits the safe `procfs` API have now been exported and are | ||||
|   available in `github.com/cyphar/filepath-securejoin/pathrs-lite/procfs`. At | ||||
|   the moment this primarily consists of a new `procfs.Handle` API: | ||||
|  | ||||
|    * `OpenProcRoot` returns a new handle to `/proc`, endeavouring to make it | ||||
|      safe if possible (`subset=pid` to protect against mistaken write attacks | ||||
|      and leaks, as well as using `fsopen(2)` to avoid racing mount attacks). | ||||
|  | ||||
|      `OpenUnsafeProcRoot` returns a handle without attempting to create one | ||||
|      with `subset=pid`, which makes it more dangerous to leak. Most users | ||||
|      should use `OpenProcRoot` (even if you need to use `ProcRoot` as the base | ||||
|      of an operation, as filepath-securejoin will internally open a handle when | ||||
|      necessary). | ||||
|  | ||||
|    * The `(*procfs.Handle).Open*` family of methods lets you get a safe | ||||
|      `O_PATH` handle to subpaths within `/proc` for certain subpaths. | ||||
|  | ||||
|      For `OpenThreadSelf`, the returned `ProcThreadSelfCloser` needs to be | ||||
|      called after you completely finish using the handle (this is necessary | ||||
|      because Go is multi-threaded and `ProcThreadSelf` references | ||||
|      `/proc/thread-self` which may disappear if we do not | ||||
|      `runtime.LockOSThread` -- `ProcThreadSelfCloser` is currently equivalent | ||||
|      to `runtime.UnlockOSThread`). | ||||
|  | ||||
|      Note that you cannot open any `procfs` symlinks (most notably magic-links) | ||||
|      using this API. At the moment, filepath-securejoin does not support this | ||||
|      feature (but [libpathrs][] does). | ||||
|  | ||||
|    * `ProcSelfFdReadlink` lets you get the in-kernel path representation of a | ||||
|      file descriptor (think `readlink("/proc/self/fd/...")`), except that we | ||||
|      verify that there aren't any tricky overmounts that could fool the | ||||
|      process. | ||||
|  | ||||
|      Please be aware that the returned string is simply a snapshot at that | ||||
|      particular moment, and an attacker could move the file being pointed to. | ||||
|      In addition, complex namespace configurations could result in non-sensical | ||||
|      or confusing paths to be returned. The value received from this function | ||||
|      should only be used as secondary verification of some security property, | ||||
|      not as proof that a particular handle has a particular path. | ||||
|  | ||||
|   The procfs handle used internally by the API is the same as the rest of | ||||
|   `filepath-securejoin` (for privileged programs this is usually a private | ||||
|   in-process `procfs` instance created with `fsopen(2)`). | ||||
|  | ||||
|   As before, this is intended as a stop-gap before users migrate to | ||||
|   [libpathrs][], which provides a far more extensive safe `procfs` API and is | ||||
|   generally more robust. | ||||
|  | ||||
| - Previously, the hardened procfs implementation (used internally within | ||||
|   `Reopen` and `Open(at)InRoot`) only protected against overmount attacks on | ||||
|   systems with `openat2(2)` (Linux 5.6) or systems with `fsopen(2)` or | ||||
|   `open_tree(2)` (Linux 5.2) and programs with privileges to use them (with | ||||
|   some caveats about locked mounts that probably affect very few users). For | ||||
|   other users, an attacker with the ability to create malicious mounts (on most | ||||
|   systems, a sysadmin) could trick you into operating on files you didn't | ||||
|   expect. This attack only really makes sense in the context of container | ||||
|   runtime implementations. | ||||
|  | ||||
|   This was considered a reasonable trade-off, as the long-term intention was to | ||||
|   get all users to just switch to [libpathrs][] if they wanted to use the safe | ||||
|   `procfs` API (which had more extensive protections, and is what these new | ||||
|   protections in `filepath-securejoin` are based on). However, as the API | ||||
|   is now being exported it seems unwise to advertise the API as "safe" if we do | ||||
|   not protect against known attacks. | ||||
|  | ||||
|   The procfs API is now more protected against attackers on systems lacking the | ||||
|   aforementioned protections. However, the most comprehensive of these | ||||
|   protections effectively rely on [`statx(STATX_MNT_ID)`][statx.2] (Linux 5.8). | ||||
|   On older kernel versions, there is no effective protection (there is some | ||||
|   minimal protection against non-`procfs` filesystem components but a | ||||
|   sufficiently clever attacker can work around those). In addition, | ||||
|   `STATX_MNT_ID` is vulnerable to mount ID reuse attacks by sufficiently | ||||
|   motivated and privileged attackers -- this problem is mitigated with | ||||
|   `STATX_MNT_ID_UNIQUE` (Linux 6.8) but that raises the minimum kernel version | ||||
|   for more protection. | ||||
|  | ||||
|   The fact that these protections are quite limited despite needing a fair bit | ||||
|   of extra code to handle was one of the primary reasons we did not initially | ||||
|   implement this in `filepath-securejoin` ([libpathrs][] supports all of this, | ||||
|   of course). | ||||
|  | ||||
| ### Fixed ### | ||||
| - RHEL 8 kernels have backports of `fsopen(2)` but in some testing we've found | ||||
|   that it has very bad (and very difficult to debug) performance issues, and so | ||||
|   we will explicitly refuse to use `fsopen(2)` if the running kernel version is | ||||
|   pre-5.2 and will instead fallback to `open("/proc")`. | ||||
|  | ||||
| [CVE-2024-21626]: https://github.com/opencontainers/runc/security/advisories/GHSA-xr7r-f8xq-vfvv | ||||
| [libpathrs]: https://github.com/cyphar/libpathrs | ||||
| [statx.2]: https://www.man7.org/linux/man-pages/man2/statx.2.html | ||||
|  | ||||
| ## [0.4.1] - 2025-01-28 ## | ||||
|  | ||||
| ### Fixed ### | ||||
| @ -173,7 +289,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). | ||||
|   safe to start migrating to as we have extensive tests ensuring they behave | ||||
|   correctly and are safe against various races and other attacks. | ||||
|  | ||||
| [libpathrs]: https://github.com/openSUSE/libpathrs | ||||
| [libpathrs]: https://github.com/cyphar/libpathrs | ||||
| [open.2]: https://www.man7.org/linux/man-pages/man2/open.2.html | ||||
|  | ||||
| ## [0.2.5] - 2024-05-03 ## | ||||
| @ -238,7 +354,8 @@ This is our first release of `github.com/cyphar/filepath-securejoin`, | ||||
| containing a full implementation with a coverage of 93.5% (the only missing | ||||
| cases are the error cases, which are hard to mocktest at the moment). | ||||
|  | ||||
| [Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...HEAD | ||||
| [Unreleased]: https://github.com/cyphar/filepath-securejoin/compare/v0.5.0...HEAD | ||||
| [0.5.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.1...v0.5.0 | ||||
| [0.4.1]: https://github.com/cyphar/filepath-securejoin/compare/v0.4.0...v0.4.1 | ||||
| [0.4.0]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.6...v0.4.0 | ||||
| [0.3.6]: https://github.com/cyphar/filepath-securejoin/compare/v0.3.5...v0.3.6 | ||||
|  | ||||
							
								
								
									
										447
									
								
								vendor/github.com/cyphar/filepath-securejoin/COPYING.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										447
									
								
								vendor/github.com/cyphar/filepath-securejoin/COPYING.md
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,447 @@ | ||||
| ## COPYING ## | ||||
|  | ||||
| `SPDX-License-Identifier: BSD-3-Clause AND MPL-2.0` | ||||
|  | ||||
| This project is made up of code licensed under different licenses. Which code | ||||
| you use will have an impact on whether only one or both licenses apply to your | ||||
| usage of this library. | ||||
|  | ||||
| Note that **each file** in this project individually has a code comment at the | ||||
| start describing the license of that particular file -- this is the most | ||||
| accurate license information of this project; in case there is any conflict | ||||
| between this document and the comment at the start of a file, the comment shall | ||||
| take precedence. The only purpose of this document is to work around [a known | ||||
| technical limitation of pkg.go.dev's license checking tool when dealing with | ||||
| non-trivial project licenses][go75067]. | ||||
|  | ||||
| [go75067]: https://go.dev/issue/75067 | ||||
|  | ||||
| ### `BSD-3-Clause` ### | ||||
|  | ||||
| At time of writing, the following files and directories are licensed under the | ||||
| BSD-3-Clause license: | ||||
|  | ||||
|  * `doc.go` | ||||
|  * `join*.go` | ||||
|  * `vfs.go` | ||||
|  * `internal/consts/*.go` | ||||
|  * `pathrs-lite/internal/gocompat/*.go` | ||||
|  * `pathrs-lite/internal/kernelversion/*.go` | ||||
|  | ||||
| The text of the BSD-3-Clause license used by this project is the following (the | ||||
| text is also available from the [`LICENSE.BSD`](./LICENSE.BSD) file): | ||||
|  | ||||
| ``` | ||||
| Copyright (C) 2014-2015 Docker Inc & Go Authors. All rights reserved. | ||||
| Copyright (C) 2017-2024 SUSE LLC. All rights reserved. | ||||
|  | ||||
| Redistribution and use in source and binary forms, with or without | ||||
| modification, are permitted provided that the following conditions are | ||||
| met: | ||||
|  | ||||
|    * Redistributions of source code must retain the above copyright | ||||
| notice, this list of conditions and the following disclaimer. | ||||
|    * Redistributions in binary form must reproduce the above | ||||
| copyright notice, this list of conditions and the following disclaimer | ||||
| in the documentation and/or other materials provided with the | ||||
| distribution. | ||||
|    * Neither the name of Google Inc. nor the names of its | ||||
| contributors may be used to endorse or promote products derived from | ||||
| this software without specific prior written permission. | ||||
|  | ||||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS | ||||
| "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT | ||||
| LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR | ||||
| A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT | ||||
| OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, | ||||
| SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT | ||||
| LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | ||||
| DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | ||||
| THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE | ||||
| OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | ||||
| ``` | ||||
|  | ||||
| ### `MPL-2.0` ### | ||||
|  | ||||
| All other files (unless otherwise marked) are licensed under the Mozilla Public | ||||
| License (version 2.0). | ||||
|  | ||||
| The text of the Mozilla Public License (version 2.0) is the following (the text | ||||
| is also available from the [`LICENSE.MPL-2.0`](./LICENSE.MPL-2.0) file): | ||||
|  | ||||
| ``` | ||||
| Mozilla Public License Version 2.0 | ||||
| ================================== | ||||
|  | ||||
| 1. Definitions | ||||
| -------------- | ||||
|  | ||||
| 1.1. "Contributor" | ||||
|     means each individual or legal entity that creates, contributes to | ||||
|     the creation of, or owns Covered Software. | ||||
|  | ||||
| 1.2. "Contributor Version" | ||||
|     means the combination of the Contributions of others (if any) used | ||||
|     by a Contributor and that particular Contributor's Contribution. | ||||
|  | ||||
| 1.3. "Contribution" | ||||
|     means Covered Software of a particular Contributor. | ||||
|  | ||||
| 1.4. "Covered Software" | ||||
|     means Source Code Form to which the initial Contributor has attached | ||||
|     the notice in Exhibit A, the Executable Form of such Source Code | ||||
|     Form, and Modifications of such Source Code Form, in each case | ||||
|     including portions thereof. | ||||
|  | ||||
| 1.5. "Incompatible With Secondary Licenses" | ||||
|     means | ||||
|  | ||||
|     (a) that the initial Contributor has attached the notice described | ||||
|         in Exhibit B to the Covered Software; or | ||||
|  | ||||
|     (b) that the Covered Software was made available under the terms of | ||||
|         version 1.1 or earlier of the License, but not also under the | ||||
|         terms of a Secondary License. | ||||
|  | ||||
| 1.6. "Executable Form" | ||||
|     means any form of the work other than Source Code Form. | ||||
|  | ||||
| 1.7. "Larger Work" | ||||
|     means a work that combines Covered Software with other material, in | ||||
|     a separate file or files, that is not Covered Software. | ||||
|  | ||||
| 1.8. "License" | ||||
|     means this document. | ||||
|  | ||||
| 1.9. "Licensable" | ||||
|     means having the right to grant, to the maximum extent possible, | ||||
|     whether at the time of the initial grant or subsequently, any and | ||||
|     all of the rights conveyed by this License. | ||||
|  | ||||
| 1.10. "Modifications" | ||||
|     means any of the following: | ||||
|  | ||||
|     (a) any file in Source Code Form that results from an addition to, | ||||
|         deletion from, or modification of the contents of Covered | ||||
|         Software; or | ||||
|  | ||||
|     (b) any new file in Source Code Form that contains any Covered | ||||
|         Software. | ||||
|  | ||||
| 1.11. "Patent Claims" of a Contributor | ||||
|     means any patent claim(s), including without limitation, method, | ||||
|     process, and apparatus claims, in any patent Licensable by such | ||||
|     Contributor that would be infringed, but for the grant of the | ||||
|     License, by the making, using, selling, offering for sale, having | ||||
|     made, import, or transfer of either its Contributions or its | ||||
|     Contributor Version. | ||||
|  | ||||
| 1.12. "Secondary License" | ||||
|     means either the GNU General Public License, Version 2.0, the GNU | ||||
|     Lesser General Public License, Version 2.1, the GNU Affero General | ||||
|     Public License, Version 3.0, or any later versions of those | ||||
|     licenses. | ||||
|  | ||||
| 1.13. "Source Code Form" | ||||
|     means the form of the work preferred for making modifications. | ||||
|  | ||||
| 1.14. "You" (or "Your") | ||||
|     means an individual or a legal entity exercising rights under this | ||||
|     License. For legal entities, "You" includes any entity that | ||||
|     controls, is controlled by, or is under common control with You. For | ||||
|     purposes of this definition, "control" means (a) the power, direct | ||||
|     or indirect, to cause the direction or management of such entity, | ||||
|     whether by contract or otherwise, or (b) ownership of more than | ||||
|     fifty percent (50%) of the outstanding shares or beneficial | ||||
|     ownership of such entity. | ||||
|  | ||||
| 2. License Grants and Conditions | ||||
| -------------------------------- | ||||
|  | ||||
| 2.1. Grants | ||||
|  | ||||
| Each Contributor hereby grants You a world-wide, royalty-free, | ||||
| non-exclusive license: | ||||
|  | ||||
| (a) under intellectual property rights (other than patent or trademark) | ||||
|     Licensable by such Contributor to use, reproduce, make available, | ||||
|     modify, display, perform, distribute, and otherwise exploit its | ||||
|     Contributions, either on an unmodified basis, with Modifications, or | ||||
|     as part of a Larger Work; and | ||||
|  | ||||
| (b) under Patent Claims of such Contributor to make, use, sell, offer | ||||
|     for sale, have made, import, and otherwise transfer either its | ||||
|     Contributions or its Contributor Version. | ||||
|  | ||||
| 2.2. Effective Date | ||||
|  | ||||
| The licenses granted in Section 2.1 with respect to any Contribution | ||||
| become effective for each Contribution on the date the Contributor first | ||||
| distributes such Contribution. | ||||
|  | ||||
| 2.3. Limitations on Grant Scope | ||||
|  | ||||
| The licenses granted in this Section 2 are the only rights granted under | ||||
| this License. No additional rights or licenses will be implied from the | ||||
| distribution or licensing of Covered Software under this License. | ||||
| Notwithstanding Section 2.1(b) above, no patent license is granted by a | ||||
| Contributor: | ||||
|  | ||||
| (a) for any code that a Contributor has removed from Covered Software; | ||||
|     or | ||||
|  | ||||
| (b) for infringements caused by: (i) Your and any other third party's | ||||
|     modifications of Covered Software, or (ii) the combination of its | ||||
|     Contributions with other software (except as part of its Contributor | ||||
|     Version); or | ||||
|  | ||||
| (c) under Patent Claims infringed by Covered Software in the absence of | ||||
|     its Contributions. | ||||
|  | ||||
| This License does not grant any rights in the trademarks, service marks, | ||||
| or logos of any Contributor (except as may be necessary to comply with | ||||
| the notice requirements in Section 3.4). | ||||
|  | ||||
| 2.4. Subsequent Licenses | ||||
|  | ||||
| No Contributor makes additional grants as a result of Your choice to | ||||
| distribute the Covered Software under a subsequent version of this | ||||
| License (see Section 10.2) or under the terms of a Secondary License (if | ||||
| permitted under the terms of Section 3.3). | ||||
|  | ||||
| 2.5. Representation | ||||
|  | ||||
| Each Contributor represents that the Contributor believes its | ||||
| Contributions are its original creation(s) or it has sufficient rights | ||||
| to grant the rights to its Contributions conveyed by this License. | ||||
|  | ||||
| 2.6. Fair Use | ||||
|  | ||||
| This License is not intended to limit any rights You have under | ||||
| applicable copyright doctrines of fair use, fair dealing, or other | ||||
| equivalents. | ||||
|  | ||||
| 2.7. Conditions | ||||
|  | ||||
| Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted | ||||
| in Section 2.1. | ||||
|  | ||||
| 3. Responsibilities | ||||
| ------------------- | ||||
|  | ||||
| 3.1. Distribution of Source Form | ||||
|  | ||||
| All distribution of Covered Software in Source Code Form, including any | ||||
| Modifications that You create or to which You contribute, must be under | ||||
| the terms of this License. You must inform recipients that the Source | ||||
| Code Form of the Covered Software is governed by the terms of this | ||||
| License, and how they can obtain a copy of this License. You may not | ||||
| attempt to alter or restrict the recipients' rights in the Source Code | ||||
| Form. | ||||
|  | ||||
| 3.2. Distribution of Executable Form | ||||
|  | ||||
| If You distribute Covered Software in Executable Form then: | ||||
|  | ||||
| (a) such Covered Software must also be made available in Source Code | ||||
|     Form, as described in Section 3.1, and You must inform recipients of | ||||
|     the Executable Form how they can obtain a copy of such Source Code | ||||
|     Form by reasonable means in a timely manner, at a charge no more | ||||
|     than the cost of distribution to the recipient; and | ||||
|  | ||||
| (b) You may distribute such Executable Form under the terms of this | ||||
|     License, or sublicense it under different terms, provided that the | ||||
|     license for the Executable Form does not attempt to limit or alter | ||||
|     the recipients' rights in the Source Code Form under this License. | ||||
|  | ||||
| 3.3. Distribution of a Larger Work | ||||
|  | ||||
| You may create and distribute a Larger Work under terms of Your choice, | ||||
| provided that You also comply with the requirements of this License for | ||||
| the Covered Software. If the Larger Work is a combination of Covered | ||||
| Software with a work governed by one or more Secondary Licenses, and the | ||||
| Covered Software is not Incompatible With Secondary Licenses, this | ||||
| License permits You to additionally distribute such Covered Software | ||||
| under the terms of such Secondary License(s), so that the recipient of | ||||
| the Larger Work may, at their option, further distribute the Covered | ||||
| Software under the terms of either this License or such Secondary | ||||
| License(s). | ||||
|  | ||||
| 3.4. Notices | ||||
|  | ||||
| You may not remove or alter the substance of any license notices | ||||
| (including copyright notices, patent notices, disclaimers of warranty, | ||||
| or limitations of liability) contained within the Source Code Form of | ||||
| the Covered Software, except that You may alter any license notices to | ||||
| the extent required to remedy known factual inaccuracies. | ||||
|  | ||||
| 3.5. Application of Additional Terms | ||||
|  | ||||
| You may choose to offer, and to charge a fee for, warranty, support, | ||||
| indemnity or liability obligations to one or more recipients of Covered | ||||
| Software. However, You may do so only on Your own behalf, and not on | ||||
| behalf of any Contributor. You must make it absolutely clear that any | ||||
| such warranty, support, indemnity, or liability obligation is offered by | ||||
| You alone, and You hereby agree to indemnify every Contributor for any | ||||
| liability incurred by such Contributor as a result of warranty, support, | ||||
| indemnity or liability terms You offer. You may include additional | ||||
| disclaimers of warranty and limitations of liability specific to any | ||||
| jurisdiction. | ||||
|  | ||||
| 4. Inability to Comply Due to Statute or Regulation | ||||
| --------------------------------------------------- | ||||
|  | ||||
| If it is impossible for You to comply with any of the terms of this | ||||
| License with respect to some or all of the Covered Software due to | ||||
| statute, judicial order, or regulation then You must: (a) comply with | ||||
| the terms of this License to the maximum extent possible; and (b) | ||||
| describe the limitations and the code they affect. Such description must | ||||
| be placed in a text file included with all distributions of the Covered | ||||
| Software under this License. Except to the extent prohibited by statute | ||||
| or regulation, such description must be sufficiently detailed for a | ||||
| recipient of ordinary skill to be able to understand it. | ||||
|  | ||||
| 5. Termination | ||||
| -------------- | ||||
|  | ||||
| 5.1. The rights granted under this License will terminate automatically | ||||
| if You fail to comply with any of its terms. However, if You become | ||||
| compliant, then the rights granted under this License from a particular | ||||
| Contributor are reinstated (a) provisionally, unless and until such | ||||
| Contributor explicitly and finally terminates Your grants, and (b) on an | ||||
| ongoing basis, if such Contributor fails to notify You of the | ||||
| non-compliance by some reasonable means prior to 60 days after You have | ||||
| come back into compliance. Moreover, Your grants from a particular | ||||
| Contributor are reinstated on an ongoing basis if such Contributor | ||||
| notifies You of the non-compliance by some reasonable means, this is the | ||||
| first time You have received notice of non-compliance with this License | ||||
| from such Contributor, and You become compliant prior to 30 days after | ||||
| Your receipt of the notice. | ||||
|  | ||||
| 5.2. If You initiate litigation against any entity by asserting a patent | ||||
| infringement claim (excluding declaratory judgment actions, | ||||
| counter-claims, and cross-claims) alleging that a Contributor Version | ||||
| directly or indirectly infringes any patent, then the rights granted to | ||||
| You by any and all Contributors for the Covered Software under Section | ||||
| 2.1 of this License shall terminate. | ||||
|  | ||||
| 5.3. In the event of termination under Sections 5.1 or 5.2 above, all | ||||
| end user license agreements (excluding distributors and resellers) which | ||||
| have been validly granted by You or Your distributors under this License | ||||
| prior to termination shall survive termination. | ||||
|  | ||||
| ************************************************************************ | ||||
| *                                                                      * | ||||
| *  6. Disclaimer of Warranty                                           * | ||||
| *  -------------------------                                           * | ||||
| *                                                                      * | ||||
| *  Covered Software is provided under this License on an "as is"       * | ||||
| *  basis, without warranty of any kind, either expressed, implied, or  * | ||||
| *  statutory, including, without limitation, warranties that the       * | ||||
| *  Covered Software is free of defects, merchantable, fit for a        * | ||||
| *  particular purpose or non-infringing. The entire risk as to the     * | ||||
| *  quality and performance of the Covered Software is with You.        * | ||||
| *  Should any Covered Software prove defective in any respect, You     * | ||||
| *  (not any Contributor) assume the cost of any necessary servicing,   * | ||||
| *  repair, or correction. This disclaimer of warranty constitutes an   * | ||||
| *  essential part of this License. No use of any Covered Software is   * | ||||
| *  authorized under this License except under this disclaimer.         * | ||||
| *                                                                      * | ||||
| ************************************************************************ | ||||
|  | ||||
| ************************************************************************ | ||||
| *                                                                      * | ||||
| *  7. Limitation of Liability                                          * | ||||
| *  --------------------------                                          * | ||||
| *                                                                      * | ||||
| *  Under no circumstances and under no legal theory, whether tort      * | ||||
| *  (including negligence), contract, or otherwise, shall any           * | ||||
| *  Contributor, or anyone who distributes Covered Software as          * | ||||
| *  permitted above, be liable to You for any direct, indirect,         * | ||||
| *  special, incidental, or consequential damages of any character      * | ||||
| *  including, without limitation, damages for lost profits, loss of    * | ||||
| *  goodwill, work stoppage, computer failure or malfunction, or any    * | ||||
| *  and all other commercial damages or losses, even if such party      * | ||||
| *  shall have been informed of the possibility of such damages. This   * | ||||
| *  limitation of liability shall not apply to liability for death or   * | ||||
| *  personal injury resulting from such party's negligence to the       * | ||||
| *  extent applicable law prohibits such limitation. Some               * | ||||
| *  jurisdictions do not allow the exclusion or limitation of           * | ||||
| *  incidental or consequential damages, so this exclusion and          * | ||||
| *  limitation may not apply to You.                                    * | ||||
| *                                                                      * | ||||
| ************************************************************************ | ||||
|  | ||||
| 8. Litigation | ||||
| ------------- | ||||
|  | ||||
| Any litigation relating to this License may be brought only in the | ||||
| courts of a jurisdiction where the defendant maintains its principal | ||||
| place of business and such litigation shall be governed by laws of that | ||||
| jurisdiction, without reference to its conflict-of-law provisions. | ||||
| Nothing in this Section shall prevent a party's ability to bring | ||||
| cross-claims or counter-claims. | ||||
|  | ||||
| 9. Miscellaneous | ||||
| ---------------- | ||||
|  | ||||
| This License represents the complete agreement concerning the subject | ||||
| matter hereof. If any provision of this License is held to be | ||||
| unenforceable, such provision shall be reformed only to the extent | ||||
| necessary to make it enforceable. Any law or regulation which provides | ||||
| that the language of a contract shall be construed against the drafter | ||||
| shall not be used to construe this License against a Contributor. | ||||
|  | ||||
| 10. Versions of the License | ||||
| --------------------------- | ||||
|  | ||||
| 10.1. New Versions | ||||
|  | ||||
| Mozilla Foundation is the license steward. Except as provided in Section | ||||
| 10.3, no one other than the license steward has the right to modify or | ||||
| publish new versions of this License. Each version will be given a | ||||
| distinguishing version number. | ||||
|  | ||||
| 10.2. Effect of New Versions | ||||
|  | ||||
| You may distribute the Covered Software under the terms of the version | ||||
| of the License under which You originally received the Covered Software, | ||||
| or under the terms of any subsequent version published by the license | ||||
| steward. | ||||
|  | ||||
| 10.3. Modified Versions | ||||
|  | ||||
| If you create software not governed by this License, and you want to | ||||
| create a new license for such software, you may create and use a | ||||
| modified version of this License if you rename the license and remove | ||||
| any references to the name of the license steward (except to note that | ||||
| such modified license differs from this License). | ||||
|  | ||||
| 10.4. Distributing Source Code Form that is Incompatible With Secondary | ||||
| Licenses | ||||
|  | ||||
| If You choose to distribute Source Code Form that is Incompatible With | ||||
| Secondary Licenses under the terms of this version of the License, the | ||||
| notice described in Exhibit B of this License must be attached. | ||||
|  | ||||
| Exhibit A - Source Code Form License Notice | ||||
| ------------------------------------------- | ||||
|  | ||||
|   This Source Code Form is subject to the terms of the Mozilla Public | ||||
|   License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|   file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| If it is not possible or desirable to put the notice in a particular | ||||
| file, then You may include the notice in a location (such as a LICENSE | ||||
| file in a relevant directory) where a recipient would be likely to look | ||||
| for such a notice. | ||||
|  | ||||
| You may add additional accurate notices of copyright ownership. | ||||
|  | ||||
| Exhibit B - "Incompatible With Secondary Licenses" Notice | ||||
| --------------------------------------------------------- | ||||
|  | ||||
|   This Source Code Form is "Incompatible With Secondary Licenses", as | ||||
|   defined by the Mozilla Public License, v. 2.0. | ||||
| ``` | ||||
							
								
								
									
										373
									
								
								vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										373
									
								
								vendor/github.com/cyphar/filepath-securejoin/LICENSE.MPL-2.0
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,373 @@ | ||||
| Mozilla Public License Version 2.0 | ||||
| ================================== | ||||
|  | ||||
| 1. Definitions | ||||
| -------------- | ||||
|  | ||||
| 1.1. "Contributor" | ||||
|     means each individual or legal entity that creates, contributes to | ||||
|     the creation of, or owns Covered Software. | ||||
|  | ||||
| 1.2. "Contributor Version" | ||||
|     means the combination of the Contributions of others (if any) used | ||||
|     by a Contributor and that particular Contributor's Contribution. | ||||
|  | ||||
| 1.3. "Contribution" | ||||
|     means Covered Software of a particular Contributor. | ||||
|  | ||||
| 1.4. "Covered Software" | ||||
|     means Source Code Form to which the initial Contributor has attached | ||||
|     the notice in Exhibit A, the Executable Form of such Source Code | ||||
|     Form, and Modifications of such Source Code Form, in each case | ||||
|     including portions thereof. | ||||
|  | ||||
| 1.5. "Incompatible With Secondary Licenses" | ||||
|     means | ||||
|  | ||||
|     (a) that the initial Contributor has attached the notice described | ||||
|         in Exhibit B to the Covered Software; or | ||||
|  | ||||
|     (b) that the Covered Software was made available under the terms of | ||||
|         version 1.1 or earlier of the License, but not also under the | ||||
|         terms of a Secondary License. | ||||
|  | ||||
| 1.6. "Executable Form" | ||||
|     means any form of the work other than Source Code Form. | ||||
|  | ||||
| 1.7. "Larger Work" | ||||
|     means a work that combines Covered Software with other material, in | ||||
|     a separate file or files, that is not Covered Software. | ||||
|  | ||||
| 1.8. "License" | ||||
|     means this document. | ||||
|  | ||||
| 1.9. "Licensable" | ||||
|     means having the right to grant, to the maximum extent possible, | ||||
|     whether at the time of the initial grant or subsequently, any and | ||||
|     all of the rights conveyed by this License. | ||||
|  | ||||
| 1.10. "Modifications" | ||||
|     means any of the following: | ||||
|  | ||||
|     (a) any file in Source Code Form that results from an addition to, | ||||
|         deletion from, or modification of the contents of Covered | ||||
|         Software; or | ||||
|  | ||||
|     (b) any new file in Source Code Form that contains any Covered | ||||
|         Software. | ||||
|  | ||||
| 1.11. "Patent Claims" of a Contributor | ||||
|     means any patent claim(s), including without limitation, method, | ||||
|     process, and apparatus claims, in any patent Licensable by such | ||||
|     Contributor that would be infringed, but for the grant of the | ||||
|     License, by the making, using, selling, offering for sale, having | ||||
|     made, import, or transfer of either its Contributions or its | ||||
|     Contributor Version. | ||||
|  | ||||
| 1.12. "Secondary License" | ||||
|     means either the GNU General Public License, Version 2.0, the GNU | ||||
|     Lesser General Public License, Version 2.1, the GNU Affero General | ||||
|     Public License, Version 3.0, or any later versions of those | ||||
|     licenses. | ||||
|  | ||||
| 1.13. "Source Code Form" | ||||
|     means the form of the work preferred for making modifications. | ||||
|  | ||||
| 1.14. "You" (or "Your") | ||||
|     means an individual or a legal entity exercising rights under this | ||||
|     License. For legal entities, "You" includes any entity that | ||||
|     controls, is controlled by, or is under common control with You. For | ||||
|     purposes of this definition, "control" means (a) the power, direct | ||||
|     or indirect, to cause the direction or management of such entity, | ||||
|     whether by contract or otherwise, or (b) ownership of more than | ||||
|     fifty percent (50%) of the outstanding shares or beneficial | ||||
|     ownership of such entity. | ||||
|  | ||||
| 2. License Grants and Conditions | ||||
| -------------------------------- | ||||
|  | ||||
| 2.1. Grants | ||||
|  | ||||
| Each Contributor hereby grants You a world-wide, royalty-free, | ||||
| non-exclusive license: | ||||
|  | ||||
| (a) under intellectual property rights (other than patent or trademark) | ||||
|     Licensable by such Contributor to use, reproduce, make available, | ||||
|     modify, display, perform, distribute, and otherwise exploit its | ||||
|     Contributions, either on an unmodified basis, with Modifications, or | ||||
|     as part of a Larger Work; and | ||||
|  | ||||
| (b) under Patent Claims of such Contributor to make, use, sell, offer | ||||
|     for sale, have made, import, and otherwise transfer either its | ||||
|     Contributions or its Contributor Version. | ||||
|  | ||||
| 2.2. Effective Date | ||||
|  | ||||
| The licenses granted in Section 2.1 with respect to any Contribution | ||||
| become effective for each Contribution on the date the Contributor first | ||||
| distributes such Contribution. | ||||
|  | ||||
| 2.3. Limitations on Grant Scope | ||||
|  | ||||
| The licenses granted in this Section 2 are the only rights granted under | ||||
| this License. No additional rights or licenses will be implied from the | ||||
| distribution or licensing of Covered Software under this License. | ||||
| Notwithstanding Section 2.1(b) above, no patent license is granted by a | ||||
| Contributor: | ||||
|  | ||||
| (a) for any code that a Contributor has removed from Covered Software; | ||||
|     or | ||||
|  | ||||
| (b) for infringements caused by: (i) Your and any other third party's | ||||
|     modifications of Covered Software, or (ii) the combination of its | ||||
|     Contributions with other software (except as part of its Contributor | ||||
|     Version); or | ||||
|  | ||||
| (c) under Patent Claims infringed by Covered Software in the absence of | ||||
|     its Contributions. | ||||
|  | ||||
| This License does not grant any rights in the trademarks, service marks, | ||||
| or logos of any Contributor (except as may be necessary to comply with | ||||
| the notice requirements in Section 3.4). | ||||
|  | ||||
| 2.4. Subsequent Licenses | ||||
|  | ||||
| No Contributor makes additional grants as a result of Your choice to | ||||
| distribute the Covered Software under a subsequent version of this | ||||
| License (see Section 10.2) or under the terms of a Secondary License (if | ||||
| permitted under the terms of Section 3.3). | ||||
|  | ||||
| 2.5. Representation | ||||
|  | ||||
| Each Contributor represents that the Contributor believes its | ||||
| Contributions are its original creation(s) or it has sufficient rights | ||||
| to grant the rights to its Contributions conveyed by this License. | ||||
|  | ||||
| 2.6. Fair Use | ||||
|  | ||||
| This License is not intended to limit any rights You have under | ||||
| applicable copyright doctrines of fair use, fair dealing, or other | ||||
| equivalents. | ||||
|  | ||||
| 2.7. Conditions | ||||
|  | ||||
| Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted | ||||
| in Section 2.1. | ||||
|  | ||||
| 3. Responsibilities | ||||
| ------------------- | ||||
|  | ||||
| 3.1. Distribution of Source Form | ||||
|  | ||||
| All distribution of Covered Software in Source Code Form, including any | ||||
| Modifications that You create or to which You contribute, must be under | ||||
| the terms of this License. You must inform recipients that the Source | ||||
| Code Form of the Covered Software is governed by the terms of this | ||||
| License, and how they can obtain a copy of this License. You may not | ||||
| attempt to alter or restrict the recipients' rights in the Source Code | ||||
| Form. | ||||
|  | ||||
| 3.2. Distribution of Executable Form | ||||
|  | ||||
| If You distribute Covered Software in Executable Form then: | ||||
|  | ||||
| (a) such Covered Software must also be made available in Source Code | ||||
|     Form, as described in Section 3.1, and You must inform recipients of | ||||
|     the Executable Form how they can obtain a copy of such Source Code | ||||
|     Form by reasonable means in a timely manner, at a charge no more | ||||
|     than the cost of distribution to the recipient; and | ||||
|  | ||||
| (b) You may distribute such Executable Form under the terms of this | ||||
|     License, or sublicense it under different terms, provided that the | ||||
|     license for the Executable Form does not attempt to limit or alter | ||||
|     the recipients' rights in the Source Code Form under this License. | ||||
|  | ||||
| 3.3. Distribution of a Larger Work | ||||
|  | ||||
| You may create and distribute a Larger Work under terms of Your choice, | ||||
| provided that You also comply with the requirements of this License for | ||||
| the Covered Software. If the Larger Work is a combination of Covered | ||||
| Software with a work governed by one or more Secondary Licenses, and the | ||||
| Covered Software is not Incompatible With Secondary Licenses, this | ||||
| License permits You to additionally distribute such Covered Software | ||||
| under the terms of such Secondary License(s), so that the recipient of | ||||
| the Larger Work may, at their option, further distribute the Covered | ||||
| Software under the terms of either this License or such Secondary | ||||
| License(s). | ||||
|  | ||||
| 3.4. Notices | ||||
|  | ||||
| You may not remove or alter the substance of any license notices | ||||
| (including copyright notices, patent notices, disclaimers of warranty, | ||||
| or limitations of liability) contained within the Source Code Form of | ||||
| the Covered Software, except that You may alter any license notices to | ||||
| the extent required to remedy known factual inaccuracies. | ||||
|  | ||||
| 3.5. Application of Additional Terms | ||||
|  | ||||
| You may choose to offer, and to charge a fee for, warranty, support, | ||||
| indemnity or liability obligations to one or more recipients of Covered | ||||
| Software. However, You may do so only on Your own behalf, and not on | ||||
| behalf of any Contributor. You must make it absolutely clear that any | ||||
| such warranty, support, indemnity, or liability obligation is offered by | ||||
| You alone, and You hereby agree to indemnify every Contributor for any | ||||
| liability incurred by such Contributor as a result of warranty, support, | ||||
| indemnity or liability terms You offer. You may include additional | ||||
| disclaimers of warranty and limitations of liability specific to any | ||||
| jurisdiction. | ||||
|  | ||||
| 4. Inability to Comply Due to Statute or Regulation | ||||
| --------------------------------------------------- | ||||
|  | ||||
| If it is impossible for You to comply with any of the terms of this | ||||
| License with respect to some or all of the Covered Software due to | ||||
| statute, judicial order, or regulation then You must: (a) comply with | ||||
| the terms of this License to the maximum extent possible; and (b) | ||||
| describe the limitations and the code they affect. Such description must | ||||
| be placed in a text file included with all distributions of the Covered | ||||
| Software under this License. Except to the extent prohibited by statute | ||||
| or regulation, such description must be sufficiently detailed for a | ||||
| recipient of ordinary skill to be able to understand it. | ||||
|  | ||||
| 5. Termination | ||||
| -------------- | ||||
|  | ||||
| 5.1. The rights granted under this License will terminate automatically | ||||
| if You fail to comply with any of its terms. However, if You become | ||||
| compliant, then the rights granted under this License from a particular | ||||
| Contributor are reinstated (a) provisionally, unless and until such | ||||
| Contributor explicitly and finally terminates Your grants, and (b) on an | ||||
| ongoing basis, if such Contributor fails to notify You of the | ||||
| non-compliance by some reasonable means prior to 60 days after You have | ||||
| come back into compliance. Moreover, Your grants from a particular | ||||
| Contributor are reinstated on an ongoing basis if such Contributor | ||||
| notifies You of the non-compliance by some reasonable means, this is the | ||||
| first time You have received notice of non-compliance with this License | ||||
| from such Contributor, and You become compliant prior to 30 days after | ||||
| Your receipt of the notice. | ||||
|  | ||||
| 5.2. If You initiate litigation against any entity by asserting a patent | ||||
| infringement claim (excluding declaratory judgment actions, | ||||
| counter-claims, and cross-claims) alleging that a Contributor Version | ||||
| directly or indirectly infringes any patent, then the rights granted to | ||||
| You by any and all Contributors for the Covered Software under Section | ||||
| 2.1 of this License shall terminate. | ||||
|  | ||||
| 5.3. In the event of termination under Sections 5.1 or 5.2 above, all | ||||
| end user license agreements (excluding distributors and resellers) which | ||||
| have been validly granted by You or Your distributors under this License | ||||
| prior to termination shall survive termination. | ||||
|  | ||||
| ************************************************************************ | ||||
| *                                                                      * | ||||
| *  6. Disclaimer of Warranty                                           * | ||||
| *  -------------------------                                           * | ||||
| *                                                                      * | ||||
| *  Covered Software is provided under this License on an "as is"       * | ||||
| *  basis, without warranty of any kind, either expressed, implied, or  * | ||||
| *  statutory, including, without limitation, warranties that the       * | ||||
| *  Covered Software is free of defects, merchantable, fit for a        * | ||||
| *  particular purpose or non-infringing. The entire risk as to the     * | ||||
| *  quality and performance of the Covered Software is with You.        * | ||||
| *  Should any Covered Software prove defective in any respect, You     * | ||||
| *  (not any Contributor) assume the cost of any necessary servicing,   * | ||||
| *  repair, or correction. This disclaimer of warranty constitutes an   * | ||||
| *  essential part of this License. No use of any Covered Software is   * | ||||
| *  authorized under this License except under this disclaimer.         * | ||||
| *                                                                      * | ||||
| ************************************************************************ | ||||
|  | ||||
| ************************************************************************ | ||||
| *                                                                      * | ||||
| *  7. Limitation of Liability                                          * | ||||
| *  --------------------------                                          * | ||||
| *                                                                      * | ||||
| *  Under no circumstances and under no legal theory, whether tort      * | ||||
| *  (including negligence), contract, or otherwise, shall any           * | ||||
| *  Contributor, or anyone who distributes Covered Software as          * | ||||
| *  permitted above, be liable to You for any direct, indirect,         * | ||||
| *  special, incidental, or consequential damages of any character      * | ||||
| *  including, without limitation, damages for lost profits, loss of    * | ||||
| *  goodwill, work stoppage, computer failure or malfunction, or any    * | ||||
| *  and all other commercial damages or losses, even if such party      * | ||||
| *  shall have been informed of the possibility of such damages. This   * | ||||
| *  limitation of liability shall not apply to liability for death or   * | ||||
| *  personal injury resulting from such party's negligence to the       * | ||||
| *  extent applicable law prohibits such limitation. Some               * | ||||
| *  jurisdictions do not allow the exclusion or limitation of           * | ||||
| *  incidental or consequential damages, so this exclusion and          * | ||||
| *  limitation may not apply to You.                                    * | ||||
| *                                                                      * | ||||
| ************************************************************************ | ||||
|  | ||||
| 8. Litigation | ||||
| ------------- | ||||
|  | ||||
| Any litigation relating to this License may be brought only in the | ||||
| courts of a jurisdiction where the defendant maintains its principal | ||||
| place of business and such litigation shall be governed by laws of that | ||||
| jurisdiction, without reference to its conflict-of-law provisions. | ||||
| Nothing in this Section shall prevent a party's ability to bring | ||||
| cross-claims or counter-claims. | ||||
|  | ||||
| 9. Miscellaneous | ||||
| ---------------- | ||||
|  | ||||
| This License represents the complete agreement concerning the subject | ||||
| matter hereof. If any provision of this License is held to be | ||||
| unenforceable, such provision shall be reformed only to the extent | ||||
| necessary to make it enforceable. Any law or regulation which provides | ||||
| that the language of a contract shall be construed against the drafter | ||||
| shall not be used to construe this License against a Contributor. | ||||
|  | ||||
| 10. Versions of the License | ||||
| --------------------------- | ||||
|  | ||||
| 10.1. New Versions | ||||
|  | ||||
| Mozilla Foundation is the license steward. Except as provided in Section | ||||
| 10.3, no one other than the license steward has the right to modify or | ||||
| publish new versions of this License. Each version will be given a | ||||
| distinguishing version number. | ||||
|  | ||||
| 10.2. Effect of New Versions | ||||
|  | ||||
| You may distribute the Covered Software under the terms of the version | ||||
| of the License under which You originally received the Covered Software, | ||||
| or under the terms of any subsequent version published by the license | ||||
| steward. | ||||
|  | ||||
| 10.3. Modified Versions | ||||
|  | ||||
| If you create software not governed by this License, and you want to | ||||
| create a new license for such software, you may create and use a | ||||
| modified version of this License if you rename the license and remove | ||||
| any references to the name of the license steward (except to note that | ||||
| such modified license differs from this License). | ||||
|  | ||||
| 10.4. Distributing Source Code Form that is Incompatible With Secondary | ||||
| Licenses | ||||
|  | ||||
| If You choose to distribute Source Code Form that is Incompatible With | ||||
| Secondary Licenses under the terms of this version of the License, the | ||||
| notice described in Exhibit B of this License must be attached. | ||||
|  | ||||
| Exhibit A - Source Code Form License Notice | ||||
| ------------------------------------------- | ||||
|  | ||||
|   This Source Code Form is subject to the terms of the Mozilla Public | ||||
|   License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
|   file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| If it is not possible or desirable to put the notice in a particular | ||||
| file, then You may include the notice in a location (such as a LICENSE | ||||
| file in a relevant directory) where a recipient would be likely to look | ||||
| for such a notice. | ||||
|  | ||||
| You may add additional accurate notices of copyright ownership. | ||||
|  | ||||
| Exhibit B - "Incompatible With Secondary Licenses" Notice | ||||
| --------------------------------------------------------- | ||||
|  | ||||
|   This Source Code Form is "Incompatible With Secondary Licenses", as | ||||
|   defined by the Mozilla Public License, v. 2.0. | ||||
							
								
								
									
										21
									
								
								vendor/github.com/cyphar/filepath-securejoin/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										21
									
								
								vendor/github.com/cyphar/filepath-securejoin/README.md
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -67,7 +67,8 @@ func SecureJoin(root, unsafePath string) (string, error) { | ||||
| [libpathrs]: https://github.com/openSUSE/libpathrs | ||||
| [go#20126]: https://github.com/golang/go/issues/20126 | ||||
|  | ||||
| ### New API ### | ||||
| ### <a name="new-api" /> New API ### | ||||
| [#new-api]: #new-api | ||||
|  | ||||
| While we recommend users switch to [libpathrs][libpathrs] as soon as it has a | ||||
| stable release, some methods implemented by libpathrs have been ported to this | ||||
| @ -165,5 +166,19 @@ after `MkdirAll`). | ||||
|  | ||||
| ### License ### | ||||
|  | ||||
| The license of this project is the same as Go, which is a BSD 3-clause license | ||||
| available in the `LICENSE` file. | ||||
| `SPDX-License-Identifier: BSD-3-Clause AND MPL-2.0` | ||||
|  | ||||
| Some of the code in this project is derived from Go, and is licensed under a | ||||
| BSD 3-clause license (available in `LICENSE.BSD`). Other files (many of which | ||||
| are derived from [libpathrs][libpathrs]) are licensed under the Mozilla Public | ||||
| License version 2.0 (available in `LICENSE.MPL-2.0`). If you are using the | ||||
| ["New API" described above][#new-api], you are probably using code from files | ||||
| released under this license. | ||||
|  | ||||
| Every source file in this project has a copyright header describing its | ||||
| license. Please check the license headers of each file to see what license | ||||
| applies to it. | ||||
|  | ||||
| See [COPYING.md](./COPYING.md) for some more details. | ||||
|  | ||||
| [umoci]: https://github.com/opencontainers/umoci | ||||
|  | ||||
							
								
								
									
										2
									
								
								vendor/github.com/cyphar/filepath-securejoin/VERSION
									
									
									
										generated
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								vendor/github.com/cyphar/filepath-securejoin/VERSION
									
									
									
										generated
									
									
										vendored
									
									
								
							| @ -1 +1 @@ | ||||
| 0.4.1 | ||||
| 0.5.0 | ||||
|  | ||||
							
								
								
									
										29
									
								
								vendor/github.com/cyphar/filepath-securejoin/codecov.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								vendor/github.com/cyphar/filepath-securejoin/codecov.yml
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| # SPDX-License-Identifier: MPL-2.0 | ||||
|  | ||||
| # Copyright (C) 2025 Aleksa Sarai <cyphar@cyphar.com> | ||||
| # Copyright (C) 2025 SUSE LLC | ||||
| # | ||||
| # This Source Code Form is subject to the terms of the Mozilla Public | ||||
| # License, v. 2.0. If a copy of the MPL was not distributed with this | ||||
| # file, You can obtain one at https://mozilla.org/MPL/2.0/. | ||||
|  | ||||
| comment: | ||||
|   layout: "condensed_header, reach, diff, components, condensed_files, condensed_footer" | ||||
|   require_changes: true | ||||
|   branches: | ||||
|     - main | ||||
|  | ||||
| coverage: | ||||
|   range: 60..100 | ||||
|   status: | ||||
|     project: | ||||
|       default: | ||||
|         target: 85% | ||||
|         threshold: 0% | ||||
|     patch: | ||||
|       default: | ||||
|         target: auto | ||||
|         informational: true | ||||
|  | ||||
| github_checks: | ||||
|   annotations: false | ||||
Some files were not shown because too many files have changed in this diff Show More
		Reference in New Issue
	
	Block a user
	