diff --git a/.golangci.yaml b/.golangci.yaml index e812364..a8b9b80 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,57 +1,56 @@ -# More info on config here: https://github.com/golangci/golangci-lint#config-file -run: - deadline: 10s - issues-exit-code: 1 - tests: true - +# yaml-language-server: $schema=https://golangci-lint.run/jsonschema/golangci.jsonschema.json +version: "2" output: formats: - - format: colored-line-number - print-issued-lines: true - print-linter-name: true - -linters-settings: - govet: - shadow: true - golint: - min-confidence: 0 - dupl: - threshold: 100 - goconst: - min-len: 2 - min-occurrences: 2 - + text: + path: stdout + print-linter-name: true + print-issued-lines: true linters: - disable-all: true + default: none enable: - - revive - - govet - - errcheck - - unused - - ineffassign - - typecheck - dupl + - errcheck - goconst - gosec - - goimports - - gosimple + - govet + - ineffassign + - revive - staticcheck - unused - -issues: - exclude-use-default: false - exclude-dirs: - - bin - - vendor - - var - - tmp - exclude-files: - - \.pb\.go$ - - \.pb\.goclay\.go$ - - \.gen.go$ - - server/streams.go # @fixme remove when implemented - exclude: -# # _ instead of err checks -# - G104 - # errcheck: Almost all programs ignore errors on these functions and in most cases it's ok - - Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv|.*Rollback). is not checked + settings: + dupl: + threshold: 100 + goconst: + min-len: 2 + min-occurrences: 2 + exclusions: + generated: lax + rules: + - path: (.+)\.go$ + text: Error return value of .((os\.)?std(out|err)\..*|.*Close|.*Flush|os\.Remove(All)?|.*printf?|os\.(Un)?Setenv|.*Rollback). is not checked + paths: + - \.pb\.go$ + - \.pb\.goclay\.go$ + - bin + - vendor + - var + - tmp + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - goimports + exclusions: + generated: lax + paths: + - \.pb\.go$ + - \.pb\.goclay\.go$ + - bin + - vendor + - var + - tmp + - third_party$ + - builtin$ + - examples$ diff --git a/Makefile b/Makefile index a810f63..22c4e8d 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,6 @@ GOPATH?=$(HOME)/go FIRST_GOPATH:=$(firstword $(subst :, ,$(GOPATH))) -NODE_TAG=22 # Always use LTS -SWAGGER_UI_DIR:=./swagger-ui - # Build available information. GIT_HASH:=$(shell git log --format="%h" -n 1 2> /dev/null) GIT_BRANCH:=$(shell git rev-parse --abbrev-ref HEAD) @@ -26,83 +23,186 @@ LOCAL_BIN:=$(CURDIR)/bin # Linter config. GOLANGCI_BIN:=$(LOCAL_BIN)/golangci-lint -GOLANGCI_TAG:=1.64.5 +GOLANGCI_TAG:=2.5.0 + +GOTESTFMT_BIN:=$(GOBIN)/gotestfmt + +# Color definitions +RED=\033[0;31m +GREEN=\033[0;32m +YELLOW=\033[0;33m +BLUE=\033[0;34m +MAGENTA=\033[0;35m +CYAN=\033[0;36m +WHITE=\033[0;37m +BOLD=\033[1m +RESET=\033[0m + +# Disable colors on Windows. +ifeq ($(OS),Windows_NT) + RED= + GREEN= + YELLOW= + BLUE= + MAGENTA= + CYAN= + WHITE= + BOLD= + RESET= +endif + +# Print functions +define print_header + @echo "$(BOLD)$(CYAN)╔═════════════════════════════════════════════════════════════╗$(RESET)" + @echo "$(BOLD)$(CYAN)║ LAUNCHR ║$(RESET)" + @echo "$(BOLD)$(CYAN)╚═════════════════════════════════════════════════════════════╝$(RESET)" +endef + +define print_success + @echo "$(BOLD)$(GREEN)✅ $(1)$(RESET)" + @echo +endef + +define print_info + @echo "$(BOLD)$(BLUE)📋 $(1)$(RESET)" + @echo +endef + +define print_warning + @echo "$(BOLD)$(YELLOW)⚠️ $(1)$(RESET)" + @echo +endef + +define print_error + @echo "$(BOLD)$(RED)❌ $(1)$(RESET)" + @echo +endef + +define print_step + @echo "$(BOLD)$(MAGENTA)🔧 $(1)$(RESET)" +endef .PHONY: all -all: deps front test build +all: banner deps test-short build + $(call print_success,"🎉 All tasks completed successfully!") + +.PHONY: banner +banner: + $(call print_header) + @echo "$(BOLD)$(WHITE)📦 Version: $(APP_VERSION)$(RESET)" + @echo "$(BOLD)$(WHITE)🌿 Branch: $(GIT_BRANCH)$(RESET)" + @echo "$(BOLD)$(WHITE)🔗 Hash: $(GIT_HASH)$(RESET)" + @echo # Install go dependencies .PHONY: deps deps: - $(info Installing go dependencies...) - go mod download - -# Build front dependencies. -.PHONY: front -front: front-install front-build - @if [ ! -d "$(SWAGGER_UI_DIR)" ]; then \ - echo "Downloading Swagger UI..."; \ - curl -Ss https://api.github.com/repos/swagger-api/swagger-ui/releases/latest | grep tarball_url | cut -d '"' -f 4 |\ - xargs curl -LsS -o swagger-ui.tar.gz; \ - rm -rf $(SWAGGER_UI_DIR) $(SWAGGER_UI_DIR)-tmp && mkdir $(SWAGGER_UI_DIR)-tmp; \ - tar xzf swagger-ui.tar.gz -C $(SWAGGER_UI_DIR)-tmp --strip=1; \ - mv $(SWAGGER_UI_DIR)-tmp/dist $(SWAGGER_UI_DIR); \ - rm -rf $(SWAGGER_UI_DIR)-tmp && rm swagger-ui.tar.gz; \ - sed -i.bkp "s|https://petstore.swagger.io/v2/swagger.json|/api/swagger.json|g" $(SWAGGER_UI_DIR)/swagger-initializer.js; \ - fi + $(call print_step,"Installing go dependencies...") + @go mod download + $(call print_success,"Dependencies installed successfully!") # Run all tests .PHONY: test -test: - $(info Running tests...) - go test ./... +test: .install-gotestfmt + $(call print_step,"Running all tests...") + @go test -json -v ./... | $(GOTESTFMT_BIN) -hide all && \ + echo "$(BOLD)$(GREEN)🧪 ✅ All tests passed$(RESET)" || \ + echo "$(BOLD)$(RED)🧪 ❌ Some tests failed$(RESET)" + @echo + +# Run short tests +.PHONY: test-short +test-short: .install-gotestfmt + $(call print_step,"Running short tests...") + @go test -json -short -v ./... | $(GOTESTFMT_BIN) -hide all && \ + echo "$(BOLD)$(GREEN)🧪 ✅ All short tests passed$(RESET)" || \ + echo "$(BOLD)$(RED)🧪 ❌ Some short tests failed$(RESET)" + @echo # Build launchr .PHONY: build build: - $(info Building launchr...) + $(call print_step,"Building launchr...") # Application related information available on build time. $(eval LDFLAGS:=-X '$(GOPKG).name=launchr' -X '$(GOPKG).version=$(APP_VERSION)' $(LDFLAGS_EXTRA)) $(eval BIN?=$(LOCAL_BIN)/launchr) - go generate ./... - $(BUILD_ENVPARMS) go build -ldflags "$(LDFLAGS)" $(BUILD_OPTS) -o $(BIN) ./cmd/launchr + @go generate ./... + @$(BUILD_ENVPARMS) go build -ldflags "$(LDFLAGS)" $(BUILD_OPTS) -o $(BIN) ./cmd/launchr + $(call print_success,"🔨 Build completed: $(BIN)") # Install launchr .PHONY: install install: all -install: - $(info Installing launchr to GOPATH...) - cp $(LOCAL_BIN)/launchr $(GOBIN)/launchr + $(call print_step,"Installing launchr to GOPATH...") + @cp $(LOCAL_BIN)/launchr $(GOBIN)/launchr + $(call print_success,"🚀 launchr installed to $(GOBIN)/launchr") # Install and run linters .PHONY: lint -lint: .install-lint .lint +lint: .install-lint .lint-fix # Install golangci-lint binary .PHONY: .install-lint .install-lint: ifeq ($(wildcard $(GOLANGCI_BIN)),) - curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(LOCAL_BIN) v$(GOLANGCI_TAG) + $(call print_step,"Installing golangci-lint v$(GOLANGCI_TAG)...") + @curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(LOCAL_BIN) v$(GOLANGCI_TAG) + $(call print_success,"golangci-lint installed!") +endif + +# Install gotestfmt binary +.PHONY: .install-gotestfmt +.install-gotestfmt: +ifeq ($(wildcard $(GOTESTFMT_BIN)),) + $(call print_step,"Installing gotestfmt...") + @go install github.com/gotesttools/gotestfmt/v2/cmd/gotestfmt@latest + $(call print_success,"gotestfmt installed!") endif # Runs linters +.PHONY: .lint-fix +.lint-fix: + $(call print_step,"Running linters with auto-fix...") + @$(GOLANGCI_BIN) run --fix ./... && \ + echo "$(BOLD)$(GREEN)🔍 ✅ All linting checks passed$(RESET)" || \ + echo "$(BOLD)$(YELLOW)🔍 ⚠️ Some linting issues found - please review$(RESET)" + @echo + .PHONY: .lint .lint: - $(info Running lint...) - $(GOLANGCI_BIN) run --fix ./... - -# Front tasks. -front-install: - docker run --rm -it -v $(PWD)/client:/usr/src/app -w /usr/src/app node:$(NODE_TAG) sh -c "corepack install && corepack enable && yarn install" - -front-build: - docker run --rm -it -v $(PWD)/client:/usr/src/app -w /usr/src/app node:$(NODE_TAG) sh -c "corepack install && corepack enable && yarn build" - -front-dev: - docker run --rm -it -v $(PWD)/client:/usr/src/app -p 5173:5173 -w /usr/src/app node:$(NODE_TAG) sh -c "corepack install && corepack enable && yarn dev -- --host" - -front-lint-fix: - docker run --rm -it -v $(PWD)/client:/usr/src/app -w /usr/src/app node:$(NODE_TAG) sh -c "corepack install && corepack enable && yarn lint --fix" - -dev: - DEV=1 make build && LAUNCHR_ACTIONS_PATH=example ./bin/launchr web --foreground -vvvv \ No newline at end of file + $(call print_step,"Running linters...") + @$(GOLANGCI_BIN) run && \ + echo "$(BOLD)$(GREEN)🔍 ✅ All linting checks passed$(RESET)" || \ + echo "$(BOLD)$(YELLOW)🔍 ⚠️ Some linting issues found - please review$(RESET)" + @echo + +# Clean build artifacts +.PHONY: clean +clean: + $(call print_step,"Cleaning build artifacts...") + @rm -rf $(LOCAL_BIN) + $(call print_success,"🧹 Cleanup completed!") + +# Show help +.PHONY: help +help: + $(call print_header) + @echo "$(BOLD)$(WHITE)Available targets:$(RESET)" + @echo "" + @echo " $(BOLD)$(GREEN)all$(RESET) 🎯 Run deps, test, and build" + @echo " $(BOLD)$(GREEN)deps$(RESET) 📦 Install go dependencies" + @echo " $(BOLD)$(GREEN)test$(RESET) 🧪 Run all tests" + @echo " $(BOLD)$(GREEN)test-short$(RESET) ⚡ Run short tests only" + @echo " $(BOLD)$(GREEN)build$(RESET) 🔨 Build launchr binary" + @echo " $(BOLD)$(GREEN)install$(RESET) 🚀 Install launchr to GOPATH" + @echo " $(BOLD)$(GREEN)lint$(RESET) 🔍 Run linters with auto-fix" + @echo " $(BOLD)$(GREEN)clean$(RESET) 🧹 Clean build artifacts" + @echo " $(BOLD)$(GREEN)help$(RESET) ❓ Show this help message" + @echo "" + @echo "$(BOLD)$(CYAN)Environment variables:$(RESET)" + @echo " $(BOLD)$(YELLOW)DEBUG=1$(RESET) Enable debug build" + @echo " $(BOLD)$(YELLOW)BIN=path$(RESET) Custom binary output path" + @echo "" + +# Default target shows help +.DEFAULT_GOAL := help diff --git a/action.token.yaml b/action.token.yaml new file mode 100644 index 0000000..882af17 --- /dev/null +++ b/action.token.yaml @@ -0,0 +1,28 @@ +runtime: plugin +action: + title: Web Token Management + description: Manage tokens for web server authentication + arguments: + - name: operation + title: Operation + description: "Token operation: create, list, delete, purge" + type: string + enum: [ create, list, export, delete, purge ] + required: true + - name: name + title: Token name + description: Token name to create/delete + type: string + default: "" + - name: size + title: Token Entropy Size + description: Number of random bytes used to generate the token + type: integer + enum: [ 16, 24, 32, 48, 64 ] + default: 32 + options: + - name: expires-in + title: Expires In + description: Token expiration duration (e.g., 24h, 7d, 30d), 0 for forever. Empty for 30d. + type: string + default: "" diff --git a/action.yaml b/action.web.yaml similarity index 73% rename from action.yaml rename to action.web.yaml index 2ba5746..530fca7 100644 --- a/action.yaml +++ b/action.web.yaml @@ -40,3 +40,17 @@ action: description: Serve swagger.json on /api/swagger.json and Swagger UI on /api/swagger-ui from specified directory. type: string default: "" + - name: no-ui + title: Headless Mode + description: Run server without web interface (API-only mode) + type: boolean + default: false + - name: enable-token-auth + title: Enable Token Authentication + description: >- + Requires valid authentication tokens for Web access. + type: boolean + process: + - processor: config.GetValue + options: + path: web.enable_token_auth \ No newline at end of file diff --git a/docs/api.http b/docs/api.http new file mode 100644 index 0000000..6c98d79 --- /dev/null +++ b/docs/api.http @@ -0,0 +1,88 @@ +## HTTP File for API Testing + +### Launchr Web API Testing +### This file contains HTTP requests for testing the Launchr Web API +### Replace the variables below with your actual values +### Variables +@baseUrl = http://localhost:8080 +@apiPrefix = /api +@username = "user" +@token = your-token +### ============================================== +### Authentication Testing +### ============================================== + +### Test Authentication - Valid Token +GET {{baseUrl}}{{apiPrefix}}/swagger-ui +Authorization: Basic {{username}}:{{token}} + +### +### Test Authentication - Invalid Token (should return 401) +GET {{baseUrl}}{{apiPrefix}}/actions +Authorization: Basic {{username}}:invalid-token-here + +### +### Test Authentication - No Auth Header (should return 401) +GET {{baseUrl}}{{apiPrefix}}/actions + +### +### ============================================== +### Actions API +### ============================================== +### Get All Available Actions +GET {{baseUrl}}{{apiPrefix}}/actions +Authorization: Basic {{username}}:{{token}} + +### +### Get Specific Action Details +GET {{baseUrl}}{{apiPrefix}}/actions/example.rjsf:strings +Authorization: Basic {{username}}:{{token}} + +### +### Run Action (example.rjsf:strings) +POST {{baseUrl}}{{apiPrefix}}/actions/example.rjsf:strings +Authorization: Basic {{username}}:{{token}} +Content-Type: application/json + +{ + "arguments": { + "color": "#4a90e2", + "simpleString": "With default value from schema" + }, + "options": {}, + "persistent": { + "log-format": "pretty", + "log-level": "NONE", + "quiet": false + }, + "runtime": { + "entrypoint": "", + "exec": false, + "no-cache": false, + "rebuild-image": true, + "remote-copy-back": false, + "remote-runtime": false, + "remove-image": false + }, + "changed": [] +} + +### Get Running Action Details +GET {{baseUrl}}{{apiPrefix}}/actions/example.rjsf:strings/running +Authorization: Basic {{username}}:{{token}} + +### Replace ID with real value +@runID = 1754908323___example.rjsf:strings +### Cancel Running Action +POST {{baseUrl}}{{apiPrefix}}/actions/example.rjsf:strings/running/{{runID}}/cancel +Authorization: Basic {{username}}:{{token}} + +{} + +### Get Action Streams (Logs) +GET {{baseUrl}}{{apiPrefix}}/actions/example.rjsf:strings/running/{{runID}}/streams +Authorization: Basic {{username}}:{{token}} + +### Test 404 - Non-existent Action +GET {{baseUrl}}{{apiPrefix}}/actions/non-existent-action +Authorization: Basic {{username}}:{{token}} diff --git a/docs/tokens.md b/docs/tokens.md new file mode 100644 index 0000000..cfadf63 --- /dev/null +++ b/docs/tokens.md @@ -0,0 +1,180 @@ +# Launchr Web Plugin - Token Management & Authentication + +## Overview + +The Launchr Web Plugin provides a secure web interface for managing Launchr actions through UI or API. It includes +comprehensive token-based authentication and management capabilities. + +## Enabling Token Authentication in Web UI + +To enable token authentication, add the following to your configuration file: +```yaml +web: + enable_token_auth: true +``` + +If no configuration is provided, token authentication is disabled by default. + +Alternatively, you can enable it by passing the option when running the web action: `--enable-token-auth` +- Enable token authentication: `launchr web --enable-token-auth` +- Disable token authentication: `launchr web --enable-token-auth=false` + +## Token Management + +### Available Operations + +The plugin provides five main token operations through the `web-token` command: + +- **create** - Generate new authentication tokens +- **list** - View all existing tokens +- **delete** - Permanently remove tokens +- **purge** - Clean up expired tokens + +### Creating Tokens + +Generate new authentication tokens for API access: + +#### Basic Usage + +```bash +# Create a token with default 30-day expiration +launchr web-token create my-api-token +``` + +#### Advanced Options + +```bash +# Create a token with custom expiration +launchr web-token create my-token --expires-in="7d" + +# Create a token that never expires +launchr web-token create my-token --expires-in=0 + +# Create a token with custom size (affects entropy) +launchr web-token create my-token 64 +``` + +#### Arguments and Parameters + +| Parameter | Type | Required | Description | +|----------------|----------|----------|---------------------------------------------------------------------------------------| +| `name` | string | Yes | Unique identifier for the token | +| `size` | int | No | Token size in bytes (default 32) | +| `--expires-in` | duration | No | Expiration time (e.g., `24h`, `7d`, `30d`, `1w`, `1m`, `1y`) or `0` for no expiration | + +#### Output + +Upon successful creation, the command displays: + +- Token name +- The actual token string (save this securely!) +- Expiration date (if applicable) + +### Listing Tokens + +View all tokens and their current status: + +``` bash +launchr web-token list +``` + +#### Output Information + +For each token, the following details are displayed: + +- **Name**: Token identifier +- **Created**: Creation timestamp +- **Expires**: Expiration date (or "Never" if no expiration) + +#### Example Output + +``` +Found 3 token(s): + • production-api + Created: 2024-01-15 10:30:45 + Expires: 2024-02-14 10:30:45 + + • development-token + Created: 2024-01-20 14:15:22 + Expires: Never +``` + +### Deleting Tokens + +Permanently remove tokens from storage: + +``` bash +launchr web-token delete my-api-token +``` + +#### Behavior + +- Token is immediately invalid for authentication +- All token metadata is permanently removed +- Cannot be recovered after deletion +- Use with caution in production environments + +### Purging Inactive Tokens + +Clean up expired tokens: + +``` bash +launchr web-token purge +``` + +#### Behavior + +The purge operation performs two cleanup actions: + +1. **Expire Active Tokens**: Marks expired but still active tokens as inactive +2. **Remove Inactive Tokens**: Permanently deletes all expired tokens + +#### Output + +``` +Expired tokens: 2 +``` + +## Authentication System + +### Authentication Method + +The Launchr web server uses **HTTP Basic Authentication** with the following specifications: + +| Property | Value | +|--------------|---------------------------------------| +| **Type** | HTTP Basic Authentication | +| **Realm** | "Launchr Web UI" | +| **Username** | Any value (ignored during validation) | +| **Password** | Valid, active authentication token | + +### Authentication Flow + +1. **Request Made**: Client sends request to protected endpoint +2. **Header Check**: Server examines `Authorization: Basic` header +3. **Credential Extraction**: Server extracts username and password from Base64-encoded credentials +4. **Token Validation**: Server validates the password portion as a token (username is ignored) +5. **Access Decision**: Server grants or denies access based on token validity + +### Token Validation Criteria + +A token is considered valid if it meets all the following conditions: + +- ✅ **Exists**: Token is found in the token store +- ✅ **Not Expired**: Current time is before expiration date (if set) + +### Authentication Header Format + +``` http +Authorization: Basic +``` + +Where `` is the Base64 encoding of `username:token`. + +#### Example + +``` http +Authorization: Basic YW55dXNlcjp5b3VyLXRva2VuLWhlcmU= +``` + +This represents `anyuser:your-token-here` encoded in Base64. diff --git a/example/foundation/software/flatcar/actions/bump/action.yaml b/example/foundation/software/flatcar/actions/bump/action.yaml index 2343ceb..c5f1909 100644 --- a/example/foundation/software/flatcar/actions/bump/action.yaml +++ b/example/foundation/software/flatcar/actions/bump/action.yaml @@ -22,4 +22,4 @@ action: runtime: type: container image: python:3.7-slim - command: python3 %s + command: [pwd] diff --git a/example/foundation/software/flatcar/actions/deploy/action.yaml b/example/foundation/software/flatcar/actions/deploy/action.yaml index 6a83a9d..bfe940b 100644 --- a/example/foundation/software/flatcar/actions/deploy/action.yaml +++ b/example/foundation/software/flatcar/actions/deploy/action.yaml @@ -16,4 +16,4 @@ action: runtime: type: container image: python:3.7-slim - command: python3 %s + command: [pwd] diff --git a/example/interaction/actions/test-action-1/action.yaml b/example/interaction/actions/test-action-1/action.yaml index de4d402..7fc4f57 100644 --- a/example/interaction/actions/test-action-1/action.yaml +++ b/example/interaction/actions/test-action-1/action.yaml @@ -16,4 +16,4 @@ action: runtime: type: container image: python:3.7-slim - command: python3 %s + command: [pwd] diff --git a/example/interaction/actions/test-action-2/action.yaml b/example/interaction/actions/test-action-2/action.yaml index 216ac09..d7cdd9a 100644 --- a/example/interaction/actions/test-action-2/action.yaml +++ b/example/interaction/actions/test-action-2/action.yaml @@ -16,4 +16,4 @@ action: runtime: type: container image: python:3.7-slim - command: python3 %s + command: [pwd] diff --git a/example/interaction/applications/actions/test-action-3/action.yaml b/example/interaction/applications/actions/test-action-3/action.yaml index fec3f9f..3ff81fc 100644 --- a/example/interaction/applications/actions/test-action-3/action.yaml +++ b/example/interaction/applications/actions/test-action-3/action.yaml @@ -16,4 +16,4 @@ action: runtime: type: container image: python:3.7-slim - command: python3 %s + command: [pwd] \ No newline at end of file diff --git a/example/interaction/applications/roles/actions/test-action-4/action.yaml b/example/interaction/applications/roles/actions/test-action-4/action.yaml index 01e7238..6a67f4e 100644 --- a/example/interaction/applications/roles/actions/test-action-4/action.yaml +++ b/example/interaction/applications/roles/actions/test-action-4/action.yaml @@ -16,4 +16,4 @@ action: runtime: type: container image: python:3.7-slim - command: python3 %s + command: [pwd] diff --git a/example/interaction/applications/roles/erp/actions/collect-data/action.yaml b/example/interaction/applications/roles/erp/actions/collect-data/action.yaml index aba6c87..cbd5fce 100644 --- a/example/interaction/applications/roles/erp/actions/collect-data/action.yaml +++ b/example/interaction/applications/roles/erp/actions/collect-data/action.yaml @@ -16,4 +16,4 @@ action: runtime: type: container image: python:3.7-slim - command: python3 %s + command: [pwd] diff --git a/example/interaction/applications/roles/erp/actions/generate-report/action.yaml b/example/interaction/applications/roles/erp/actions/generate-report/action.yaml index a64c47c..e07c098 100644 --- a/example/interaction/applications/roles/erp/actions/generate-report/action.yaml +++ b/example/interaction/applications/roles/erp/actions/generate-report/action.yaml @@ -16,4 +16,4 @@ action: runtime: type: container image: python:3.7-slim - command: python3 %s + command: [pwd] diff --git a/example/interaction/applications/roles/mail/actions/create-account/action.yaml b/example/interaction/applications/roles/mail/actions/create-account/action.yaml index 67e5afb..31c52c4 100644 --- a/example/interaction/applications/roles/mail/actions/create-account/action.yaml +++ b/example/interaction/applications/roles/mail/actions/create-account/action.yaml @@ -16,4 +16,4 @@ action: runtime: type: container image: python:3.7-slim - command: python3 %s + command: [pwd] diff --git a/example/interaction/applications/roles/mail/actions/send-mails/action.yaml b/example/interaction/applications/roles/mail/actions/send-mails/action.yaml index 1a865cb..7b781f8 100644 --- a/example/interaction/applications/roles/mail/actions/send-mails/action.yaml +++ b/example/interaction/applications/roles/mail/actions/send-mails/action.yaml @@ -16,4 +16,4 @@ action: runtime: type: container image: python:3.7-slim - command: python3 %s + command: [pwd] \ No newline at end of file diff --git a/example/interaction/applications/roles/messenger/actions/create-account/action.yaml b/example/interaction/applications/roles/messenger/actions/create-account/action.yaml index 67e5afb..31c52c4 100644 --- a/example/interaction/applications/roles/messenger/actions/create-account/action.yaml +++ b/example/interaction/applications/roles/messenger/actions/create-account/action.yaml @@ -16,4 +16,4 @@ action: runtime: type: container image: python:3.7-slim - command: python3 %s + command: [pwd] diff --git a/example/interaction/applications/roles/messenger/actions/create-channel/action.yaml b/example/interaction/applications/roles/messenger/actions/create-channel/action.yaml index 5fed06f..5719abd 100644 --- a/example/interaction/applications/roles/messenger/actions/create-channel/action.yaml +++ b/example/interaction/applications/roles/messenger/actions/create-channel/action.yaml @@ -16,4 +16,4 @@ action: runtime: type: container image: python:3.7-slim - command: python3 %s + command: [pwd] diff --git a/example/platform/actions/build/action.yaml b/example/platform/actions/build/action.yaml index 1628ed6..bd24dba 100644 --- a/example/platform/actions/build/action.yaml +++ b/example/platform/actions/build/action.yaml @@ -32,8 +32,5 @@ action: runtime: type: container -# image: python:3.7-slim - image: ubuntu -# command: python3 {{ .opt4 }} -# command: ["sh", "-c", "for i in $(seq 60); do echo $$i; sleep 1; done"] - command: /bin/bash + image: python:3.7-slim + command: [pwd] diff --git a/example/platform/actions/bump/action.yaml b/example/platform/actions/bump/action.yaml index 99dc731..5a78581 100644 --- a/example/platform/actions/bump/action.yaml +++ b/example/platform/actions/bump/action.yaml @@ -16,4 +16,4 @@ action: runtime: type: container image: python:3.7-slim - command: python3 %s + command: [pwd] diff --git a/example/very-long-name-action-group/very-long-action-childgroup/actions/very-long-name-action-copy/action.yaml b/example/very-long-name-action-group/very-long-action-childgroup/actions/very-long-name-action-copy/action.yaml index 9eb996b..b8d4326 100644 --- a/example/very-long-name-action-group/very-long-action-childgroup/actions/very-long-name-action-copy/action.yaml +++ b/example/very-long-name-action-group/very-long-action-childgroup/actions/very-long-name-action-copy/action.yaml @@ -16,4 +16,4 @@ action: runtime: type: container image: python:3.7-slim - command: python3 %s \ No newline at end of file + command: [pwd] \ No newline at end of file diff --git a/example/very-long-name-action-group/very-long-action-childgroup/actions/very-long-name-action/action.yaml b/example/very-long-name-action-group/very-long-action-childgroup/actions/very-long-name-action/action.yaml index 0772a86..e493647 100644 --- a/example/very-long-name-action-group/very-long-action-childgroup/actions/very-long-name-action/action.yaml +++ b/example/very-long-name-action-group/very-long-action-childgroup/actions/very-long-name-action/action.yaml @@ -16,4 +16,4 @@ action: runtime: type: container image: python:3.7-slim - command: python3 %s + command: [pwd] diff --git a/go.mod b/go.mod index ce1156f..d2d8d38 100644 --- a/go.mod +++ b/go.mod @@ -1,63 +1,76 @@ module github.com/launchrctl/web -go 1.24.0 - -toolchain go1.24.1 +go 1.25.0 require ( github.com/getkin/kin-openapi v0.131.0 github.com/go-chi/chi/v5 v5.2.1 github.com/go-chi/cors v1.2.1 github.com/go-chi/render v1.0.3 + github.com/goccy/go-yaml v1.18.0 github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 github.com/knadh/koanf v1.5.0 - github.com/launchrctl/launchr v0.21.2 + github.com/launchrctl/keyring v0.7.0 + github.com/launchrctl/launchr v0.22.0 github.com/oapi-codegen/nethttp-middleware v1.0.2 github.com/oapi-codegen/oapi-codegen/v2 v2.4.1 github.com/oapi-codegen/runtime v1.1.1 - golang.org/x/sys v0.32.0 - gopkg.in/yaml.v3 v3.0.1 + golang.org/x/sys v0.36.0 ) require ( atomicgo.dev/cursor v0.2.0 // indirect atomicgo.dev/keyboard v0.2.9 // indirect atomicgo.dev/schedule v0.1.0 // indirect + filippo.io/age v1.2.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/Microsoft/go-winio v0.6.2 // indirect github.com/ajg/form v1.5.1 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/containerd/console v1.0.4 // indirect + github.com/clipperhouse/uax29/v2 v2.2.0 // indirect + github.com/containerd/console v1.0.5 // 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/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/distribution/reference v0.6.0 // indirect - github.com/docker/docker v28.1.1+incompatible // indirect - github.com/docker/go-connections v0.5.0 // indirect + github.com/docker/docker v28.4.0+incompatible // indirect + github.com/docker/go-connections v0.6.0 // indirect github.com/docker/go-units v0.5.0 // indirect github.com/dprotaso/go-yit v0.0.0-20240618133044-5a0af90af097 // indirect - github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.6.0 // indirect - github.com/fxamacker/cbor/v2 v2.8.0 // indirect - github.com/go-logr/logr v1.4.2 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/jsonpointer v0.21.1 // indirect - github.com/go-openapi/jsonreference v0.21.0 // indirect - github.com/go-openapi/swag v0.23.1 // indirect + github.com/go-openapi/jsonpointer v0.22.1 // indirect + github.com/go-openapi/jsonreference v0.21.2 // indirect + github.com/go-openapi/swag v0.25.1 // indirect + github.com/go-openapi/swag/cmdutils v0.25.1 // indirect + github.com/go-openapi/swag/conv v0.25.1 // indirect + github.com/go-openapi/swag/fileutils v0.25.1 // indirect + github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/go-openapi/swag/jsonutils v0.25.1 // indirect + github.com/go-openapi/swag/loading v0.25.1 // indirect + github.com/go-openapi/swag/mangling v0.25.1 // indirect + github.com/go-openapi/swag/netutils v0.25.1 // indirect + github.com/go-openapi/swag/stringutils v0.25.1 // indirect + github.com/go-openapi/swag/typeutils v0.25.1 // indirect + github.com/go-openapi/swag/yamlutils v0.25.1 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/google/gnostic-models v0.6.9 // indirect - github.com/google/go-cmp v0.7.0 // indirect + github.com/google/gnostic-models v0.7.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/gookit/color v1.5.4 // indirect + github.com/gookit/color v1.6.0 // indirect github.com/gorilla/mux v1.8.1 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/lithammer/fuzzysearch v1.1.8 // indirect - github.com/mailru/easyjson v0.9.0 // indirect - github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mailru/easyjson v0.9.1 // indirect + github.com/mattn/go-runewidth v0.0.19 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect @@ -71,7 +84,7 @@ require ( github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -84,49 +97,49 @@ require ( github.com/perimeterx/marshmallow v1.1.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/pterm/pterm v0.12.80 // indirect - github.com/rivo/uniseg v0.4.7 // indirect - github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect + github.com/pterm/pterm v0.12.81 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 // indirect github.com/sirupsen/logrus v1.9.3 // indirect github.com/speakeasy-api/jsonpath v0.6.1 // indirect github.com/speakeasy-api/openapi-overlay v0.10.1 // indirect - github.com/spf13/cobra v1.9.1 // indirect - github.com/spf13/pflag v1.0.6 // indirect - github.com/stretchr/testify v1.10.0 // indirect + github.com/spf13/cobra v1.10.1 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/stretchr/testify v1.11.1 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect github.com/x448/float16 v0.8.4 // 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.60.0 // indirect - go.opentelemetry.io/otel v1.35.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0 // indirect - go.opentelemetry.io/otel/metric v1.35.0 // indirect - go.opentelemetry.io/otel/trace v1.35.0 // indirect - go.opentelemetry.io/proto/otlp v1.3.1 // indirect - go.uber.org/mock v0.5.1 // indirect - golang.org/x/mod v0.24.0 // indirect - golang.org/x/net v0.39.0 // indirect - golang.org/x/oauth2 v0.29.0 // indirect - golang.org/x/sync v0.13.0 // indirect - golang.org/x/term v0.31.0 // indirect - golang.org/x/text v0.24.0 // indirect - golang.org/x/time v0.11.0 // indirect - golang.org/x/tools v0.32.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/metric v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.uber.org/mock v0.6.0 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.42.0 // indirect + golang.org/x/mod v0.28.0 // indirect + golang.org/x/net v0.44.0 // indirect + golang.org/x/oauth2 v0.31.0 // indirect + golang.org/x/sync v0.17.0 // indirect + golang.org/x/term v0.35.0 // indirect + golang.org/x/text v0.29.0 // indirect + golang.org/x/time v0.13.0 // indirect + golang.org/x/tools v0.36.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect - google.golang.org/grpc v1.67.1 // indirect - google.golang.org/protobuf v1.36.6 // indirect - gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect + google.golang.org/protobuf v1.36.9 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/api v0.33.0 // indirect - k8s.io/apimachinery v0.33.0 // indirect - k8s.io/client-go v0.33.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/api v0.34.1 // indirect + k8s.io/apimachinery v0.34.1 // indirect + k8s.io/client-go v0.34.1 // indirect k8s.io/klog/v2 v2.130.1 // indirect - k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect - k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e // indirect - sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect + k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 // indirect + k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect - sigs.k8s.io/structured-merge-diff/v4 v4.7.0 // indirect - sigs.k8s.io/yaml v1.4.0 // indirect + sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect + sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/go.sum b/go.sum index f96b212..cc2268b 100644 --- a/go.sum +++ b/go.sum @@ -6,8 +6,12 @@ atomicgo.dev/keyboard v0.2.9 h1:tOsIid3nlPLZ3lwgG8KZMp/SFmr7P0ssEN5JUsm78K8= atomicgo.dev/keyboard v0.2.9/go.mod h1:BC4w9g00XkxH/f1HXhW2sXmJFOCWbKn9xrOunSFtExQ= atomicgo.dev/schedule v0.1.0 h1:nTthAbhZS5YZmgYbb2+DH8uQIZcTlIrd4eYr3UQxEjs= atomicgo.dev/schedule v0.1.0/go.mod h1:xeUa3oAkiuHYh8bKiQBRojqAMq3PXXbJujjb0hw8pEU= +c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805 h1:u2qwJeEvnypw+OCPUHmoZE3IqwfuN5kgDfo5MLzpNM0= +c2sp.org/CCTV/age v0.0.0-20240306222714-3ec4d716e805/go.mod h1:FomMrUJ2Lxt5jCLmZkG3FHa72zUprnhd3v/Z18Snm4w= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +filippo.io/age v1.2.1 h1:X0TZjehAZylOIj4DubWYU1vWQxv9bJpo+Uu2/LGhi1o= +filippo.io/age v1.2.1/go.mod h1:JL9ew2lTN+Pyft4RiNGguFfOpewKwSHm5ayKD/A4004= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= @@ -65,11 +69,17 @@ github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWR github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 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/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/containerd/console v1.0.3/go.mod h1:7LqA/THxQ86k76b8c/EMSiaJ3h1eZkMkXar0TQ1gf3U= -github.com/containerd/console v1.0.4 h1:F2g4+oChYvBTsASRTz8NP6iIAi97J3TtSAsLbIFn4ro= -github.com/containerd/console v1.0.4/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc= +github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= @@ -85,18 +95,18 @@ 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/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= -github.com/docker/docker v28.1.1+incompatible h1:49M11BFLsVO1gxY9UX9p/zwkE/rswggs8AdFmXQw51I= -github.com/docker/docker v28.1.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/go-connections v0.5.0 h1:USnMq7hx7gwdVZq1L49hLXaFtUdTADjXGp+uj1Br63c= -github.com/docker/go-connections v0.5.0/go.mod h1:ov60Kzw0kKElRwhNs9UlUHAE/F9Fe6GLaXnqyDdmEXc= +github.com/docker/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/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= github.com/dprotaso/go-yit v0.0.0-20191028211022-135eb7262960/go.mod h1:9HQzr9D/0PGwMEbC3d5AB7oi67+h4TsQqItC1GVYG58= github.com/dprotaso/go-yit v0.0.0-20240618133044-5a0af90af097 h1:f5nA5Ys8RXqFXtKc0XofVRiuwNTuJzPIwTmbjLz9vj8= github.com/dprotaso/go-yit v0.0.0-20240618133044-5a0af90af097/go.mod h1:FTAVyH6t+SlS97rv6EXRVuBDLkQqcIe/xQw9f4IFUI4= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= -github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= @@ -109,10 +119,10 @@ github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2 github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= -github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/fxamacker/cbor/v2 v2.8.0 h1:fFtUGXUzXPHTIUdne5+zzMPTfffl3RD5qYnkY40vtxU= -github.com/fxamacker/cbor/v2 v2.8.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/getkin/kin-openapi v0.131.0 h1:NO2UeHnFKRYhZ8wg6Nyh5Cq7dHk4suQQr72a4pMrDxE= github.com/getkin/kin-openapi v0.131.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -130,16 +140,40 @@ github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9 github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= -github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-openapi/jsonpointer v0.21.1 h1:whnzv/pNXtK2FbX/W9yJfRmE2gsmkfahjMKB0fZvcic= -github.com/go-openapi/jsonpointer v0.21.1/go.mod h1:50I1STOfbY1ycR8jGz8DaMeLCdXiI6aDteEdRNNzpdk= -github.com/go-openapi/jsonreference v0.21.0 h1:Rs+Y7hSXT83Jacb7kFyjn4ijOuVGSvOdF2+tg1TRrwQ= -github.com/go-openapi/jsonreference v0.21.0/go.mod h1:LmZmgsrTkVg9LG4EaHeY8cBDslNPMo06cago5JNLkm4= -github.com/go-openapi/swag v0.23.1 h1:lpsStH0n2ittzTnbaSloVZLuB5+fvSY/+hnagBjSNZU= -github.com/go-openapi/swag v0.23.1/go.mod h1:STZs8TbRvEQQKUA+JZNAm3EWlgaOBGpyFDqQnDHMef0= +github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= +github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonreference v0.21.2 h1:Wxjda4M/BBQllegefXrY/9aq1fxBA8sI5M/lFU6tSWU= +github.com/go-openapi/jsonreference v0.21.2/go.mod h1:pp3PEjIsJ9CZDGCNOyXIQxsNuroxm8FAJ/+quA0yKzQ= +github.com/go-openapi/swag v0.25.1 h1:6uwVsx+/OuvFVPqfQmOOPsqTcm5/GkBhNwLqIR916n8= +github.com/go-openapi/swag v0.25.1/go.mod h1:bzONdGlT0fkStgGPd3bhZf1MnuPkf2YAys6h+jZipOo= +github.com/go-openapi/swag/cmdutils v0.25.1 h1:nDke3nAFDArAa631aitksFGj2omusks88GF1VwdYqPY= +github.com/go-openapi/swag/cmdutils v0.25.1/go.mod h1:pdae/AFo6WxLl5L0rq87eRzVPm/XRHM3MoYgRMvG4A0= +github.com/go-openapi/swag/conv v0.25.1 h1:+9o8YUg6QuqqBM5X6rYL/p1dpWeZRhoIt9x7CCP+he0= +github.com/go-openapi/swag/conv v0.25.1/go.mod h1:Z1mFEGPfyIKPu0806khI3zF+/EUXde+fdeksUl2NiDs= +github.com/go-openapi/swag/fileutils v0.25.1 h1:rSRXapjQequt7kqalKXdcpIegIShhTPXx7yw0kek2uU= +github.com/go-openapi/swag/fileutils v0.25.1/go.mod h1:+NXtt5xNZZqmpIpjqcujqojGFek9/w55b3ecmOdtg8M= +github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= +github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/swag/jsonutils v0.25.1 h1:AihLHaD0brrkJoMqEZOBNzTLnk81Kg9cWr+SPtxtgl8= +github.com/go-openapi/swag/jsonutils v0.25.1/go.mod h1:JpEkAjxQXpiaHmRO04N1zE4qbUEg3b7Udll7AMGTNOo= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1 h1:DSQGcdB6G0N9c/KhtpYc71PzzGEIc/fZ1no35x4/XBY= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.1/go.mod h1:kjmweouyPwRUEYMSrbAidoLMGeJ5p6zdHi9BgZiqmsg= +github.com/go-openapi/swag/loading v0.25.1 h1:6OruqzjWoJyanZOim58iG2vj934TysYVptyaoXS24kw= +github.com/go-openapi/swag/loading v0.25.1/go.mod h1:xoIe2EG32NOYYbqxvXgPzne989bWvSNoWoyQVWEZicc= +github.com/go-openapi/swag/mangling v0.25.1 h1:XzILnLzhZPZNtmxKaz/2xIGPQsBsvmCjrJOWGNz/ync= +github.com/go-openapi/swag/mangling v0.25.1/go.mod h1:CdiMQ6pnfAgyQGSOIYnZkXvqhnnwOn997uXZMAd/7mQ= +github.com/go-openapi/swag/netutils v0.25.1 h1:2wFLYahe40tDUHfKT1GRC4rfa5T1B4GWZ+msEFA4Fl4= +github.com/go-openapi/swag/netutils v0.25.1/go.mod h1:CAkkvqnUJX8NV96tNhEQvKz8SQo2KF0f7LleiJwIeRE= +github.com/go-openapi/swag/stringutils v0.25.1 h1:Xasqgjvk30eUe8VKdmyzKtjkVjeiXx1Iz0zDfMNpPbw= +github.com/go-openapi/swag/stringutils v0.25.1/go.mod h1:JLdSAq5169HaiDUbTvArA2yQxmgn4D6h4A+4HqVvAYg= +github.com/go-openapi/swag/typeutils v0.25.1 h1:rD/9HsEQieewNt6/k+JBwkxuAHktFtH3I3ysiFZqukA= +github.com/go-openapi/swag/typeutils v0.25.1/go.mod h1:9McMC/oCdS4BKwk2shEB7x17P6HmMmA6dQRtAkSnNb8= +github.com/go-openapi/swag/yamlutils v0.25.1 h1:mry5ez8joJwzvMbaTGLhw8pXUnhDK91oSJLDPF1bmGk= +github.com/go-openapi/swag/yamlutils v0.25.1/go.mod h1:cm9ywbzncy3y6uPm/97ysW8+wZ09qsks+9RS8fLWKqg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I= github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= @@ -148,6 +182,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZ github.com/go-test/deep v1.0.2-0.20181118220953-042da051cf31/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= github.com/go-test/deep v1.0.8 h1:TDsG77qcSprGbC6vTN8OuXp5g+J+b5Pcguhf7Zt61VM= github.com/go-test/deep v1.0.8/go.mod h1:5C2ZWiW0ErCdrYzpqxLbTX7MG14M9iiw8DgHncVwcsE= +github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw= +github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= @@ -170,8 +206,8 @@ github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaS github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/gnostic-models v0.6.9 h1:MU/8wDLif2qCXZmzncUQ/BOfxWfthHi63KqpoNbWqVw= -github.com/google/gnostic-models v0.6.9/go.mod h1:CiWsm0s6BSQd1hRn8/QmxqB6BesYcbSZxsz9b0KuDBw= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= @@ -181,7 +217,6 @@ github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/ github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= -github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= @@ -191,16 +226,17 @@ github.com/google/pprof v0.0.0-20241029153458-d1b30febd7db/go.mod h1:vavhavw2zAx github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gookit/assert v0.1.1 h1:lh3GcawXe/p+cU7ESTZ5Ui3Sm/x8JWpIis4/1aF0mY0= +github.com/gookit/assert v0.1.1/go.mod h1:jS5bmIVQZTIwk42uXl4lyj4iaaxx32tqH16CFj0VX2E= github.com/gookit/color v1.4.2/go.mod h1:fqRyamkC1W8uxl+lxCQxOT09l/vYfZ+QeiX3rKQHCoQ= github.com/gookit/color v1.5.0/go.mod h1:43aQb+Zerm/BWh2GnrgOQm7ffz7tvQXEKV6BFMl7wAo= -github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= -github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= +github.com/gookit/color v1.6.0 h1:JjJXBTk1ETNyqyilJhkTXJYYigHG24TM9Xa2M1xAhRA= +github.com/gookit/color v1.6.0/go.mod h1:9ACFc7/1IpHGBW8RwuDm/0YEnhg3dwwXpoMsmtyHfjs= github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.16.0 h1:gmcG1KaJ57LophUzW0Hy8NmPhnMZb4M0+kPpLofRdBo= github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0 h1:bkypFPDjIYGfCYD5mRBvpqxfYX1YCS1PXdKYWi8FsN0= github.com/grpc-ecosystem/grpc-gateway/v2 v2.20.0/go.mod h1:P+Lt/0by1T8bfcF3z737NnSbmxQAppXMRziHUxPOC8k= @@ -281,12 +317,14 @@ github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/launchrctl/launchr v0.21.2 h1:D53UHpal9+/Qf+2di5+WS3/9RMWPOJMqeWVtv/gqesI= -github.com/launchrctl/launchr v0.21.2/go.mod h1:C7H4FHMSjNi4fUt36rzfyE/2xSpdBdUuq7n7IPAzqEo= +github.com/launchrctl/keyring v0.7.0 h1:IrmhjnguZrcNZHg2vSVajVdWvmxYh6tnpsu/Q4Rw8ck= +github.com/launchrctl/keyring v0.7.0/go.mod h1:HE8OurqAYUt3x0ArDEDa6w9TAL4mbUwxqp/PJr/EPJE= +github.com/launchrctl/launchr v0.22.0 h1:JJJgvaSBjaxwX7vfyZ+ujwt7kIXqp/V0xy5k3JgtMyU= +github.com/launchrctl/launchr v0.22.0/go.mod h1:cLETGKQKp6WBg1uPQ2WmrHTjPcWTfseUI0R5NyODxUs= github.com/lithammer/fuzzysearch v1.1.8 h1:/HIuJnjHuXS8bKaiTMeeDlW2/AyIWk2brx1V8LFgLN4= github.com/lithammer/fuzzysearch v1.1.8/go.mod h1:IdqeyBClc3FFqSzYq/MXESsS4S0FsZ5ajtkr5xPLts4= -github.com/mailru/easyjson v0.9.0 h1:PrnmzHw7262yW8sTBwxi1PdJA3Iw/EKBa8psRf7d9a4= -github.com/mailru/easyjson v0.9.0/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= +github.com/mailru/easyjson v0.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8= +github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU= github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= @@ -296,8 +334,8 @@ github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcME github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= -github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= +github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= github.com/miekg/dns v1.1.41/go.mod h1:p6aan82bvRIyn+zDIv9xYNUpwa73JcSh9BKwknJysuI= @@ -342,8 +380,9 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= @@ -427,21 +466,19 @@ github.com/pterm/pterm v0.12.31/go.mod h1:32ZAWZVXD7ZfG0s8qqHXePte42kdz8ECtRyEej github.com/pterm/pterm v0.12.33/go.mod h1:x+h2uL+n7CP/rel9+bImHD5lF3nM9vJj80k9ybiiTTE= github.com/pterm/pterm v0.12.36/go.mod h1:NjiL09hFhT/vWjQHSj1athJpx6H8cjpHXNAK5bUw8T8= github.com/pterm/pterm v0.12.40/go.mod h1:ffwPLwlbXxP+rxT0GsgDTzS3y3rmpAO1NMjUkGTYf8s= -github.com/pterm/pterm v0.12.80 h1:mM55B+GnKUnLMUSqhdINe4s6tOuVQIetQ3my8JGyAIg= -github.com/pterm/pterm v0.12.80/go.mod h1:c6DeF9bSnOSeFPZlfs4ZRAFcf5SCoTwvwQ5xaKGQlHo= +github.com/pterm/pterm v0.12.81 h1:ju+j5I2++FO1jBKMmscgh5h5DPFDFMB7epEjSoKehKA= +github.com/pterm/pterm v0.12.81/go.mod h1:TyuyrPjnxfwP+ccJdBTeWHtd/e0ybQHkOS/TakajZCw= github.com/rhnvrm/simples3 v0.6.1/go.mod h1:Y+3vYm2V7Y4VijFoJHHTrja6OgPrJ2cBti8dPGkC3sA= 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= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= -github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= -github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= github.com/ryanuber/go-glob v1.0.0/go.mod h1:807d1WSdnB0XRJzKNil9Om6lcp/3a0v4qIHxIXzX/Yc= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= -github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= @@ -456,11 +493,12 @@ github.com/speakeasy-api/jsonpath v0.6.1 h1:FWbuCEPGaJTVB60NZg2orcYHGZlelbNJAcIk github.com/speakeasy-api/jsonpath v0.6.1/go.mod h1:ymb2iSkyOycmzKwbEAYPJV/yi2rSmvBCLZJcyD+VVWw= github.com/speakeasy-api/openapi-overlay v0.10.1 h1:XFx/GvJvtAGf4dcQ6bxzsLNf76x/QWE2X0SSZrWojBQ= github.com/speakeasy-api/openapi-overlay v0.10.1/go.mod h1:n0iOU7AqKpNFfEt6tq7qYITC4f0yzVVdFw0S7hukemg= -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/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= -github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.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/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -472,8 +510,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/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/vmware-labs/yaml-jsonpath v0.3.2 h1:/5QKeCBGdsInyDCyVNLbXyilb61MXGi9NP674f9Hobk= @@ -490,37 +528,43 @@ github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5t go.etcd.io/etcd/api/v3 v3.5.4/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A= go.etcd.io/etcd/client/pkg/v3 v3.5.4/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= go.etcd.io/etcd/client/v3 v3.5.4/go.mod h1:ZaRkVgBZC+L+dLCjTcF1hRXpgZXQPOvnA/Ak/gq3kiY= -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.60.0 h1:sbiXRNDSWJOTobXh5HyQKjq6wUC5tNybqjIqDpAY4CU= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.60.0/go.mod h1:69uWxva0WgAA/4bu2Yy70SLDBwZXuQ6PbBpbsa5iZrQ= -go.opentelemetry.io/otel v1.35.0 h1:xKWKPxrxB6OtMCbmMY021CqC45J+3Onta9MqjhnusiQ= -go.opentelemetry.io/otel v1.35.0/go.mod h1:UEqy8Zp11hpkUrL73gSlELM0DupHoiq72dR+Zqel/+Y= +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/otlptrace v1.31.0 h1:K0XaT3DwHAcV4nKLzcQvwAgSyisUghWoY20I7huthMk= go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.31.0/go.mod h1:B5Ki776z/MBnVha1Nzwp5arlzBbE3+1jk+pGmaP5HME= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0 h1:lUsI2TYsQw2r1IASwoROaCnjdj2cvC2+Jbxvk6nHnWU= go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.31.0/go.mod h1:2HpZxxQurfGxJlJDblybejHB6RX6pmExPNe517hREw4= -go.opentelemetry.io/otel/metric v1.35.0 h1:0znxYu2SNyuMSQT4Y9WDWej0VpcsxkuklLa4/siN90M= -go.opentelemetry.io/otel/metric v1.35.0/go.mod h1:nKVFgxBZ2fReX6IlyW28MgZojkoAkJGaE8CpgeAU3oE= -go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY= -go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg= -go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o= -go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w= -go.opentelemetry.io/otel/trace v1.35.0 h1:dPpEfJu1sDIqruz7BHFG3c7528f6ddfSWfFDVt/xgMs= -go.opentelemetry.io/otel/trace v1.35.0/go.mod h1:WUk7DtFp1Aw2MkvqGdwiXYDZZNvA/1J8o6xRXLrIkyc= +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 v1.3.1 h1:TrMUixzpM0yuc/znrFTP9MMRh8trP93mkCiDVeXrui0= go.opentelemetry.io/proto/otlp v1.3.1/go.mod h1:0X1WI4de4ZsLrrJNLAQbFeLCm3T7yBkR0XqQ7niQU+8= go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/mock v0.5.1 h1:ASgazW/qBmR+A32MYFDB6E2POoTgOwT509VP0CT/fjs= -go.uber.org/mock v0.5.1/go.mod h1:ge71pBPLYDk7QIi1LupWxdAykm7KIEFchiOqd6z7qMM= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +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= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +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-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= @@ -534,8 +578,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= -golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/mod v0.28.0 h1:gQBtGhjxykdjY9YhZpSlZIsbnaE2+PgjfLWUQTnoZ1U= +golang.org/x/mod v0.28.0/go.mod h1:yfB/L0NOf/kmEbXjzCPOx1iK1fRutOydrCMsqRhEBxI= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -559,13 +603,13 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.39.0 h1:ZCu7HMWDxpXpaiKdhzIfaltL9Lp31x/3fCP11bc6/fY= -golang.org/x/net v0.39.0/go.mod h1:X7NRbYVEA+ewNkCNyJ513WmMdQ3BineSwVtN2zD/d+E= +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-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.29.0 h1:WdYw2tdTK1S8olAzWHdgeqfy+Mtm9XNhv/xJsY65d98= -golang.org/x/oauth2 v0.29.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= +golang.org/x/oauth2 v0.31.0 h1:8Fq0yVZLh4j4YA47vHKFTa9Ew5XIrCP8LC6UeNZnLxo= +golang.org/x/oauth2 v0.31.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -577,8 +621,8 @@ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJ 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.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610= -golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= +golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -623,18 +667,17 @@ golang.org/x/sys v0.0.0-20220319134239-a9b59b0215f8/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.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-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= -golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= -golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +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.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= @@ -644,11 +687,11 @@ 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.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0= -golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU= +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-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.11.0 h1:/bpjEDfN9tkoN/ryeYHnv5hcMlc8ncjMcM4XBk5NWV0= -golang.org/x/time v0.11.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-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= @@ -663,8 +706,8 @@ golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4f golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.32.0 h1:Q7N1vhpkQv7ybVzLFtTjvQya2ewbwNDZzUgfXGqtMWU= -golang.org/x/tools v0.32.0/go.mod h1:ZxrU41P/wAbZD8EDa6dDCa6XfpkhJ7HFMjHJXfBDu8s= +golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg= +golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= @@ -702,16 +745,16 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= 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.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY= -google.golang.org/protobuf v1.36.6/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/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.12.0 h1:n6jtcsulIzXPJaxegRbvFNNrZDjbij7ny3gmSPG+6V4= -gopkg.in/evanphx/json-patch.v4 v4.12.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= @@ -737,25 +780,24 @@ gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -k8s.io/api v0.33.0 h1:yTgZVn1XEe6opVpP1FylmNrIFWuDqe2H0V8CT5gxfIU= -k8s.io/api v0.33.0/go.mod h1:CTO61ECK/KU7haa3qq8sarQ0biLq2ju405IZAd9zsiM= -k8s.io/apimachinery v0.33.0 h1:1a6kHrJxb2hs4t8EE5wuR/WxKDwGN1FKH3JvDtA0CIQ= -k8s.io/apimachinery v0.33.0/go.mod h1:BHW0YOu7n22fFv/JkYOEfkUYNRN0fj0BlvMFWA7b+SM= -k8s.io/client-go v0.33.0 h1:UASR0sAYVUzs2kYuKn/ZakZlcs2bEHaizrrHUZg0G98= -k8s.io/client-go v0.33.0/go.mod h1:kGkd+l/gNGg8GYWAPr0xF1rRKvVWvzh9vmZAMXtaKOg= +k8s.io/api v0.34.1 h1:jC+153630BMdlFukegoEL8E/yT7aLyQkIVuwhmwDgJM= +k8s.io/api v0.34.1/go.mod h1:SB80FxFtXn5/gwzCoN6QCtPD7Vbu5w2n1S0J5gFfTYk= +k8s.io/apimachinery v0.34.1 h1:dTlxFls/eikpJxmAC7MVE8oOeP1zryV7iRyIjB0gky4= +k8s.io/apimachinery v0.34.1/go.mod h1:/GwIlEcWuTX9zKIg2mbw0LRFIsXwrfoVxn+ef0X13lw= +k8s.io/client-go v0.34.1 h1:ZUPJKgXsnKwVwmKKdPfw4tB58+7/Ik3CrjOEhsiZ7mY= +k8s.io/client-go v0.34.1/go.mod h1:kA8v0FP+tk6sZA0yKLRG67LWjqufAoSHA2xVGKw9Of8= k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff h1:/usPimJzUKKu+m+TE36gUyGcf03XZEP0ZIKgKj35LS4= -k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff/go.mod h1:5jIi+8yX4RIb8wk3XwBo5Pq2ccx4FP10ohkbSKCZoK8= -k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e h1:KqK5c/ghOm8xkHYhlodbp6i6+r+ChV2vuAuVRdFbLro= -k8s.io/utils v0.0.0-20250321185631-1f6e0b77f77e/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 h1:gBQPwqORJ8d8/YNZWEjoZs7npUVDpVXUUOFfW6CgAqE= -sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/randfill v0.0.0-20250304075658-069ef1bbf016/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912 h1:Y3gxNAuB0OBLImH611+UDZcmKS3g6CthxToOb37KgwE= +k8s.io/kube-openapi v0.0.0-20250910181357-589584f1c912/go.mod h1:kdmbQkyfwUagLfXIad1y2TdrjPFWp2Q89B3qkRwf/pQ= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d h1:wAhiDyZ4Tdtt7e46e9M5ZSAJ/MnPGPs+Ki1gHw4w1R0= +k8s.io/utils v0.0.0-20250820121507-0af2bda4dd1d/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v4 v4.7.0 h1:qPeWmscJcXP0snki5IYF79Z8xrl8ETFxgMd7wez1XkI= -sigs.k8s.io/structured-merge-diff/v4 v4.7.0/go.mod h1:dDy58f92j70zLsuZVuUX5Wp9vtxXpaZnkPGWeqDfCps= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0 h1:jTijUJbW353oVOd9oTlifJqOGEkUw2jB/fXCbTiQEco= +sigs.k8s.io/structured-merge-diff/v6 v6.3.0/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= -sigs.k8s.io/yaml v1.4.0 h1:Mk1wCc2gy/F0THH0TAp1QYyJNzRm2KCLy3o5ASXVI5E= -sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/plugin.go b/plugin.go index f6f33e4..08716cb 100644 --- a/plugin.go +++ b/plugin.go @@ -7,7 +7,10 @@ import ( "fmt" "os" "path/filepath" + "time" + "github.com/goccy/go-yaml" + "github.com/launchrctl/keyring" "github.com/launchrctl/launchr" "github.com/launchrctl/launchr/pkg/action" @@ -15,16 +18,20 @@ import ( ) const ( - pluginName = "web" - stopArg = "stop" - pidFile = "web.pid" + pluginName = "web" + stopArg = "stop" + pidFile = "web.pid" + runningInstanceDir = "instance" // APIPrefix is a default api prefix on the server. APIPrefix = "/api" ) -//go:embed action.yaml -var actionYaml []byte +//go:embed action.web.yaml +var actionWebYaml []byte + +//go:embed action.token.yaml +var actionWebTokenYaml []byte //go:embed ui-schema.default.yaml var defaultUISchema []byte @@ -36,7 +43,6 @@ func init() { // Plugin is [launchr.Plugin] providing web ui. type Plugin struct { app launchr.App - cfg launchr.Config } // PluginInfo implements [launchr.Plugin] interface. @@ -46,7 +52,6 @@ func (p *Plugin) PluginInfo() launchr.PluginInfo { // OnAppInit implements [launchr.OnAppInitPlugin] interface. func (p *Plugin) OnAppInit(app launchr.App) error { - app.GetService(&p.cfg) p.app = app return nil @@ -56,71 +61,73 @@ type webFlags struct { action.WithLogger action.WithTerm - Port int - IsPortSet bool - ProxyClient string - PluginDir string - FrontendCustomize server.FrontendCustomize - DefaultUISchema []byte + // Common Web flags. + NoUI bool + EnableTokenAuth bool + Port int + IsPortSet bool + ProxyClient string + InstanceDir string + WebCustomize server.WebCustomize + DefaultUISchema []byte } // DiscoverActions implements [launchr.ActionDiscoveryPlugin] interface. func (p *Plugin) DiscoverActions(_ context.Context) ([]*action.Action, error) { - a := action.NewFromYAML("web", actionYaml) - a.SetRuntime(action.NewFnRuntime(func(ctx context.Context, a *action.Action) error { - pluginTmpDir := p.cfg.Path(pluginName) - webPidFile := filepath.Join(pluginTmpDir, pidFile) + var cfg launchr.Config + var k keyring.Keyring + + p.app.Services().Get(&cfg) + p.app.Services().Get(&k) + + aw := action.NewFromYAML("web", actionWebYaml) + aw.SetRuntime(action.NewFnRuntime(func(ctx context.Context, a *action.Action) error { + instanceDir := cfg.Path(pluginName, runningInstanceDir) + webPidFile := filepath.Join(instanceDir, pidFile) input := a.Input() - webRunFlags := webFlags{ - PluginDir: pluginTmpDir, - Port: input.Opt("port").(int), - IsPortSet: input.IsOptChanged("port"), - ProxyClient: input.Opt("proxy-client").(string), - FrontendCustomize: server.FrontendCustomize{ + + wf := webFlags{ + EnableTokenAuth: input.Opt("enable-token-auth").(bool), + NoUI: input.Opt("no-ui").(bool), + InstanceDir: instanceDir, + Port: input.Opt("port").(int), + IsPortSet: input.IsOptChanged("port"), + ProxyClient: input.Opt("proxy-client").(string), + WebCustomize: server.WebCustomize{ Variables: make(map[string]any), ExcludedActions: make(map[string]bool), }, DefaultUISchema: defaultUISchema, } + log, term := action.GetLoggerAndTerminal(a) + wf.SetLogger(log) + wf.SetTerm(term) + // Retrieve a list of excluded actions from config. var excludedActions []string - err := p.cfg.Get("web.excluded_actions", &excludedActions) + err := cfg.Get("web.excluded_actions", &excludedActions) if err != nil { return err } for _, ea := range excludedActions { - webRunFlags.FrontendCustomize.ExcludedActions[ea] = true + wf.WebCustomize.ExcludedActions[ea] = true } var variables map[string]any - err = p.cfg.Get("web.variables", &variables) + err = cfg.Get("web.variables", &variables) if err != nil { - return err - } - if variables != nil { - webRunFlags.FrontendCustomize.Variables = variables + term.Warning().Println("error on getting web.variables %s", err) + } else if variables != nil { + wf.WebCustomize.Variables = variables } - // Set action logger. Fallback to default launchr logger. - log := launchr.Log() - if rt, ok := a.Runtime().(action.RuntimeLoggerAware); ok { - log = rt.LogWith() - } - webRunFlags.SetLogger(log) - // Set action term. Fallback to default launchr term. - term := launchr.Term() - if rt, ok := a.Runtime().(action.RuntimeTermAware); ok { - term = rt.Term() - } - webRunFlags.SetTerm(term) - foreground := input.Opt("foreground").(bool) // Override client assets. clientAssets := input.Opt("ui-assets").(string) if clientAssets != "" { path := launchr.MustAbs(clientAssets) - _, err := os.Stat(path) + _, err = os.Stat(path) if os.IsNotExist(err) { return fmt.Errorf("ui assets are not available on path: %s", path) } @@ -138,14 +145,14 @@ func (p *Plugin) DiscoverActions(_ context.Context) ([]*action.Action, error) { SetSwaggerUIAssetsFS(os.DirFS(path)) } - // If 'stop' arg passed, try to interrupt process and remove PID file. + // If 'stop' arg passed, try to interrupt the process and remove a PID file. op := input.Arg("op") switch op { case stopArg: - return stopWeb(webPidFile, webRunFlags) + return stopWeb(webPidFile, wf) } - url, err := getExistingWeb(webPidFile, webRunFlags.PluginDir) + url, err := getExistingWeb(webPidFile, wf.InstanceDir) if err != nil { launchr.Log().Debug("error on getting server run info", "error", err) } @@ -155,10 +162,150 @@ func (p *Plugin) DiscoverActions(_ context.Context) ([]*action.Action, error) { } if foreground { - return p.runWeb(ctx, webRunFlags) + return p.runWeb(ctx, wf) } - return p.runBackgroundWeb(ctx, webRunFlags, webPidFile) + return p.runBackgroundWeb(ctx, wf, webPidFile) })) - return []*action.Action{a}, nil + + at := action.NewFromYAML("web-token", actionWebTokenYaml) + at.SetRuntime(action.NewFnRuntime(func(_ context.Context, a *action.Action) error { + input := a.Input() + operation := input.Arg("operation") + + tokenStore, err := server.NewTokenStore(k) + if err != nil { + return fmt.Errorf("failed to initialize token store: %w", err) + } + + logger, _ := action.GetLoggerAndTerminal(a) + tokenStore.SetLogger(logger) + + switch operation { + case "create": + return createToken(tokenStore, a) + case "list": + listTokens(tokenStore, a) + return nil + case "export": + return exportTokens(tokenStore, a) + case "delete": + return deleteToken(tokenStore, a) + case "purge": + purgeTokens(tokenStore, a) + return nil + default: + return fmt.Errorf("unknown operation: %s", operation) + } + })) + + return []*action.Action{aw, at}, nil +} + +func createToken(store *server.TokenStore, a *action.Action) error { + _, term := action.GetLoggerAndTerminal(a) + name := a.Input().Arg("name").(string) + if name == "" { + return fmt.Errorf("token name is required") + } + size := a.Input().Arg("size").(int) + + var expiresIn time.Duration + expiresInStr := a.Input().Opt("expires-in").(string) + if expiresInStr != "0" { + if expiresInStr == "" { + // Default expiration (e.g., 30 days) + defaultDuration := 30 * 24 * time.Hour + expiresIn = defaultDuration + } else { + duration, err := server.ParseDurationWithDays(expiresInStr) + if err != nil { + return fmt.Errorf("invalid expiration duration: %w", err) + } + expiresIn = duration + } + } + + token, info, err := store.CreateToken(name, size, expiresIn) + if err != nil { + return fmt.Errorf("failed to create token: %w", err) + } + + term.Success().Printfln("Token created successfully!") + term.Info().Printfln("Name: %s", info.Name) + term.Info().Printfln("Token: %s", token) + if info.Expires != nil { + term.Info().Printfln("Expires: %s", info.Expires.Format("2006-01-02 15:04:05")) + } else { + term.Info().Printfln("Expires: Never") + } + term.Warning().Printfln("Save this token in a safe place. It will not be shown again.") + + return nil +} + +func listTokens(store *server.TokenStore, a *action.Action) { + _, term := action.GetLoggerAndTerminal(a) + + tokens := store.ListTokens() + if len(tokens) == 0 { + term.Info().Println("No tokens found.") + return + } + + term.Info().Printfln("Found %d token(s):", len(tokens)) + for _, token := range tokens { + term.Info().Printfln(" • %s", token.Name) + term.Info().Printfln(" Created: %s", token.Created.Format("2006-01-02 15:04:05")) + if token.Expires != nil { + term.Info().Printfln(" Expires: %s", token.Expires.Format("2006-01-02 15:04:05")) + } else { + term.Info().Printfln(" Expires: Never") + } + + term.Info().Println() + } +} + +func exportTokens(store *server.TokenStore, a *action.Action) error { + _, term := action.GetLoggerAndTerminal(a) + + tokens := store.ListTokens() + if len(tokens) == 0 { + term.Info().Println("No tokens found.") + return nil + } + + yamlData, err := yaml.Marshal(tokens) + if err != nil { + return fmt.Errorf("failed to parse tokens: %w", err) + } + + term.Print(string(yamlData)) + return nil +} + +func deleteToken(store *server.TokenStore, a *action.Action) error { + _, term := action.GetLoggerAndTerminal(a) + + name := a.Input().Arg("name").(string) + if name == "" { + return fmt.Errorf("token name is required") + } + + if store.DeleteToken(name) { + term.Success().Printfln("Token '%s' has been deleted.", name) + } else { + term.Error().Printfln("Token '%s' not found.", name) + } + + return nil +} + +func purgeTokens(store *server.TokenStore, a *action.Action) { + _, term := action.GetLoggerAndTerminal(a) + exp := store.PurgeInactiveTokens() + if exp > 0 { + term.Success().Printfln("Expired tokens: %d", exp) + } } diff --git a/server/api.go b/server/api.go index 764d441..7a9649b 100644 --- a/server/api.go +++ b/server/api.go @@ -14,11 +14,11 @@ import ( "sync" "time" + "github.com/goccy/go-yaml" "github.com/knadh/koanf" yamlparser "github.com/knadh/koanf/parsers/yaml" "github.com/knadh/koanf/providers/file" "github.com/knadh/koanf/providers/rawbytes" - "gopkg.in/yaml.v3" "github.com/launchrctl/launchr" "github.com/launchrctl/launchr/pkg/action" @@ -27,27 +27,42 @@ import ( // customizationPlatformNameKey used to set the layout-flow root name const customizationPlatformNameKey = "root_name" +// WebCustomize stores variables to customize web appearance. +type WebCustomize struct { + Variables map[string]any + ExcludedActions map[string]bool +} + type launchrServer struct { action.WithLogger action.WithTerm actionMngr action.Manager stateMngr *StateManager + tokenStore *TokenStore cfg launchr.Config ctx context.Context baseURL string apiPrefix string wsMutex sync.Mutex - customize FrontendCustomize + customize WebCustomize uiSchemaBase []byte logsDirPath string app launchr.App } -// FrontendCustomize stores variables to customize web appearance. -type FrontendCustomize struct { - Variables map[string]any - ExcludedActions map[string]bool +func (l *launchrServer) authMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Use basic HTTP auth for all requests. + _, token, ok := r.BasicAuth() + if !ok || !l.tokenStore.ValidateToken(token) { + w.Header().Set("WWW-Authenticate", fmt.Sprintf(`Basic realm="%s web"`, launchr.Version().Name)) + http.Error(w, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized) + return + } + + next.ServeHTTP(w, r) + }) } func (l *launchrServer) GetCustomisationConfig(w http.ResponseWriter, _ *http.Request) { @@ -523,17 +538,19 @@ func convertUserInput(a *action.Action, persistentFlagsDef action.ParametersList "persistent": changedPersistent, } - for _, p := range *params.Changed { - split := strings.Split(p, "____") - if len(split) < 3 { - continue - } + if params.Changed != nil { + for _, p := range *params.Changed { + split := strings.Split(p, "____") + if len(split) < 3 { + continue + } - category := split[1] - name := split[2] + category := split[1] + name := split[2] - if targetMap, exists := categoryMaps[category]; exists { - targetMap[name] = true + if targetMap, exists := categoryMaps[category]; exists { + targetMap[name] = true + } } } diff --git a/server/openapi.gen.go b/server/openapi.gen.go index 57d3bca..08e3e3f 100644 --- a/server/openapi.gen.go +++ b/server/openapi.gen.go @@ -1,6 +1,6 @@ // Package server provides primitives to interact with the openapi HTTP API. // -// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.4.1 DO NOT EDIT. +// Code generated by github.com/oapi-codegen/oapi-codegen/v2 version v2.5.0 DO NOT EDIT. package server import ( diff --git a/server/server.go b/server/server.go index 0c46ac1..6ca45c4 100644 --- a/server/server.go +++ b/server/server.go @@ -25,6 +25,7 @@ import ( "github.com/go-chi/cors" "github.com/go-chi/render" "github.com/gorilla/websocket" + "github.com/launchrctl/keyring" middleware "github.com/oapi-codegen/nethttp-middleware" "github.com/launchrctl/launchr" @@ -40,15 +41,17 @@ type RunOptions struct { // If empty, :80 is used. Addr string // APIPrefix specifies subpath where Api is served. - APIPrefix string + APIPrefix string + NoUI bool + EnableTokenAuth bool // SwaggerUIFS enables serving of swagger.json for swagger ui if set. SwaggerUIFS fs.FS // Client server. - ClientFS fs.FS - ProxyClient string - FrontendCustomize FrontendCustomize - DefaultUISchema []byte - LogsDirPath string + ClientFS fs.FS + ProxyClient string + Customize WebCustomize + DefaultUISchema []byte + LogsDirPath string } // BaseURL returns base url for run options. @@ -77,10 +80,23 @@ func Run(ctx context.Context, app launchr.App, opts *RunOptions) error { err = os.MkdirAll(opts.LogsDirPath, 0750) if err != nil { - return fmt.Errorf("can't create logs dir") + return fmt.Errorf("can't create logs dir: %w", err) } - ctx, cancel := context.WithCancel(ctx) + store := &launchrServer{ + ctx: ctx, + baseURL: opts.BaseURL(), + apiPrefix: opts.APIPrefix, + customize: opts.Customize, + logsDirPath: opts.LogsDirPath, + uiSchemaBase: opts.DefaultUISchema, + app: app, + stateMngr: NewStateManager(), + } + store.SetLogger(opts.Log()) + store.SetTerm(opts.Term()) + app.Services().Get(&store.actionMngr) + app.Services().Get(&store.cfg) // Prepare router and openapi. r := chi.NewRouter() @@ -92,45 +108,34 @@ func Run(ctx context.Context, app launchr.App, opts *RunOptions) error { AllowCredentials: false, MaxAge: 300, // Maximum value not ignored by any of major browsers })) - store := &launchrServer{ - ctx: ctx, - baseURL: opts.BaseURL(), - apiPrefix: opts.APIPrefix, - customize: opts.FrontendCustomize, - logsDirPath: opts.LogsDirPath, - uiSchemaBase: opts.DefaultUISchema, - app: app, - stateMngr: NewStateManager(), + + if opts.EnableTokenAuth { + // Apply authMiddleware to all routes. + var k keyring.Keyring + app.Services().Get(&k) + store.tokenStore, err = NewTokenStore(k) + if err != nil { + return fmt.Errorf("can't create token store: %w", err) + } + + r.Use(store.authMiddleware) + } + + ctx, cancel := context.WithCancel(ctx) + + // Mode-specific setup + if opts.NoUI { + setupAPIOnlyMode(r, store, opts, swagger) + } else { + setupFullUIMode(r, store, opts, cancel, swagger) } - store.SetLogger(opts.Log()) - store.SetTerm(opts.Term()) - app.GetService(&store.actionMngr) - app.GetService(&store.cfg) // Provide Swagger UI. if opts.SwaggerUIFS != nil { + store.Term().Info().Println("Serving Swagger UI") serveSwaggerUI(swagger, r, opts) } - r.HandleFunc("/ws", wsHandler(store)) - - // Serve frontend files. - r.HandleFunc("/*", spaHandler(opts)) - - // Use the validation middleware to check all requests against the OpenAPI schema on Api subroutes. - r.Route(opts.APIPrefix, func(r chi.Router) { - r.Use( - middleware.OapiRequestValidator(swagger), - ) - }) - - r.Post("/api/shutdown", func(w http.ResponseWriter, _ *http.Request) { - cancel() - _, _ = w.Write([]byte("Server is shutting down...")) - }) - - // Register router in openapi and start the server. - HandlerFromMuxWithBaseURL(store, r, opts.APIPrefix) s := &http.Server{ Handler: r, Addr: opts.Addr, @@ -140,11 +145,48 @@ func Run(ctx context.Context, app launchr.App, opts *RunOptions) error { // @todo remove all stopped containers when stopped // @todo add special prefix for web run containers. store.baseURL = opts.BaseURL() + store.Term().Info().Printfln("Web Server running at: %s", store.baseURL) + if opts.SwaggerUIFS != nil { - store.Term().Info(). - Printfln("Swagger UI: %s\nswagger.json: %s", store.basePath()+swaggerUIPath, store.basePath()+swaggerJSONPath) + store.Term().Info().Printfln("Swagger UI: %s%s", store.basePath(), swaggerUIPath) } + return handleServerLifecycle(ctx, cancel, s, store) +} + +func setupAPIOnlyMode(r chi.Router, store *launchrServer, opts *RunOptions, swagger *openapi3.T) { + // API routes with validation + r.Route(opts.APIPrefix, func(r chi.Router) { + r.Use(middleware.OapiRequestValidator(swagger)) + }) + + // Register API handlers + HandlerFromMuxWithBaseURL(store, r, opts.APIPrefix) +} + +func setupFullUIMode(r chi.Router, store *launchrServer, opts *RunOptions, cancel context.CancelFunc, swagger *openapi3.T) { + // Frontend file serving + r.HandleFunc("/*", spaHandler(opts)) + + // WebSocket endpoint + r.HandleFunc("/ws", wsHandler(store)) + + // API routes with validation + r.Route(opts.APIPrefix, func(r chi.Router) { + r.Use(middleware.OapiRequestValidator(swagger)) + }) + + // Shutdown endpoint for UI + r.Post("/api/shutdown", func(w http.ResponseWriter, _ *http.Request) { + cancel() // This needs to be passed in somehow + _, _ = w.Write([]byte("Server is shutting down...")) + }) + + // Register API handlers + HandlerFromMuxWithBaseURL(store, r, opts.APIPrefix) +} + +func handleServerLifecycle(ctx context.Context, cancel context.CancelFunc, s *http.Server, store *launchrServer) error { signals := make(chan os.Signal, 1) signal.Notify(signals, syscall.SIGINT, syscall.SIGTERM) @@ -161,18 +203,17 @@ func Run(ctx context.Context, app launchr.App, opts *RunOptions) error { ctxShut, cancelShut := context.WithTimeout(context.Background(), time.Second*10) defer cancelShut() errShutdown = s.Shutdown(ctxShut) - // Force shutdown. if errShutdown != nil { errShutdown = s.Close() } }() - if err = s.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { + if err := s.ListenAndServe(); !errors.Is(err, http.ErrServerClosed) { return err } if errShutdown != nil { - store.Log().Error("error on shutting down", "error", err) + store.Log().Error("error on shutting down", "error", errShutdown) return errShutdown } diff --git a/server/streams.go b/server/streams.go index 8cb8955..0eceb5e 100644 --- a/server/streams.go +++ b/server/streams.go @@ -79,13 +79,13 @@ func (w *wrappedWriter) Close() error { return nil } -func createFileStreams(streamsDir, runId string, app launchr.App, quiet bool) (*webCli, error) { - outfile, err := os.Create(filepath.Join(streamsDir, runId+"-out.txt")) +func createFileStreams(streamsDir, runID string, app launchr.App, quiet bool) (*webCli, error) { + outfile, err := os.Create(filepath.Clean(filepath.Join(streamsDir, runID+"-out.txt"))) if err != nil { return nil, fmt.Errorf("error creating output file: %w", err) } - errfile, err := os.Create(filepath.Join(streamsDir, runId+"-err.txt")) + errfile, err := os.Create(filepath.Clean(filepath.Join(streamsDir, runID+"-err.txt"))) if err != nil { return nil, fmt.Errorf("error creating error file: %w", err) } diff --git a/server/token.go b/server/token.go new file mode 100644 index 0000000..478b6a9 --- /dev/null +++ b/server/token.go @@ -0,0 +1,339 @@ +package server + +import ( + "crypto/rand" + "crypto/sha256" + "encoding/hex" + "errors" + "fmt" + "strconv" + "strings" + "sync" + "time" + + "github.com/launchrctl/keyring" + "github.com/launchrctl/launchr/pkg/action" +) + +const tokensKey = "web-tokens" + +// TokenStore manages storage, validation, and persistence of web tokens with file-based synchronization. +type TokenStore struct { + action.WithLogger + + tokens map[string]*TokenInfo + loaded bool + + mutex sync.RWMutex + k keyring.Keyring +} + +// TokenInfo represents metadata associated with an web token. +type TokenInfo struct { + TokenHash string + Name string + Created time.Time + Expires *time.Time +} + +// NewTokenStore creates a new TokenStore. +func NewTokenStore(k keyring.Keyring) (*TokenStore, error) { + store := &TokenStore{ + tokens: make(map[string]*TokenInfo), + k: k, + } + + err := store.Load() + return store, err +} + +// hashToken creates an SHA-256 hash of the token +func (ts *TokenStore) hashToken(token string) string { + hash := sha256.Sum256([]byte(token)) + return hex.EncodeToString(hash[:]) +} + +// generateToken creates a new random token +func (ts *TokenStore) generateToken(length int) (string, error) { + bytes := make([]byte, length) + if _, err := rand.Read(bytes); err != nil { + return "", fmt.Errorf("failed to generate token: %w", err) + } + return hex.EncodeToString(bytes), nil +} + +// Load loads the tokens from the keyring. +func (ts *TokenStore) Load() error { + ts.mutex.Lock() + defer ts.mutex.Unlock() + + if ts.loaded { + return nil + } + + item, err := ts.k.GetForKey(tokensKey) + if err != nil { + if errors.Is(err, keyring.ErrNotFound) { + return nil + } + return err + } + + if data, ok := item.Value.([]any); ok { + var tokens []*TokenInfo + for i, tokenData := range data { + tokenMap, okToken := tokenData.(map[string]any) + if !okToken { + return fmt.Errorf("item at index %d is not a map, got %T", i, item) + } + + tokenInfo, err := ts.convertMapToTokenInfo(tokenMap) + if err != nil { + return fmt.Errorf("can't convert item at index %d: %w", i, err) + } + + tokens = append(tokens, tokenInfo) + } + + ts.loaded = true + ts.tokens = make(map[string]*TokenInfo) + for _, token := range tokens { + ts.tokens[token.TokenHash] = token + } + return nil + } + + return fmt.Errorf("failed to retrieve tokens from storage") +} + +func (ts *TokenStore) convertMapToTokenInfo(itemMap map[string]any) (*TokenInfo, error) { + tokenInfo := &TokenInfo{} + + // Convert token_hash + if tokenHash, exists := itemMap["tokenhash"]; exists { + if str, ok := tokenHash.(string); ok { + tokenInfo.TokenHash = str + } else { + return nil, fmt.Errorf("token_hash is not a string, got %T", tokenHash) + } + } + + // Convert name + if name, exists := itemMap["name"]; exists { + if str, ok := name.(string); ok { + tokenInfo.Name = str + } else { + return nil, fmt.Errorf("name is not a string, got %T", name) + } + } + + // Convert created_at + if created, exists := itemMap["created"]; exists { + if t, ok := created.(time.Time); ok { + tokenInfo.Created = t + } else { + return nil, fmt.Errorf("created_at is not a time.Time, got %T", created) + } + } + + // Convert expires (optional) + if expires, exists := itemMap["expires"]; exists && expires != nil { + if t, ok := expires.(time.Time); ok { + tokenInfo.Expires = &t + } else { + return nil, fmt.Errorf("expires is not a string, got %T", expires) + } + } + + return tokenInfo, nil +} + +func (ts *TokenStore) save() error { + var tokens []*TokenInfo + for _, token := range ts.tokens { + tokens = append(tokens, token) + } + + if len(tokens) > 0 { + item := keyring.KeyValueItem{ + Value: tokens, + Key: tokensKey, + } + + err := ts.k.AddItem(item) + if err != nil { + return err + } + } else { + err := ts.k.RemoveByKey(tokensKey) + if err != nil && !errors.Is(err, keyring.ErrNotFound) { + return err + } + } + + err := ts.k.Save() + if err != nil { + return err + } + + ts.loaded = false + + return nil +} + +// CreateToken generates a new web token and persists the data to a file. +func (ts *TokenStore) CreateToken(name string, size int, expires time.Duration) (string, *TokenInfo, error) { + ts.mutex.Lock() + defer ts.mutex.Unlock() + + if strings.TrimSpace(name) == "" { + return "", nil, fmt.Errorf("token name cannot be empty") + } + + // Check if the name already exists + for _, tokenInfo := range ts.tokens { + if tokenInfo.Name == name { + return "", nil, fmt.Errorf("token with name '%s' already exists", name) + } + } + + // Generate an actual token (this is returned to the user) + token, err := ts.generateToken(size) + if err != nil { + return "", nil, fmt.Errorf("failed to generate token: %w", err) + } + + tokenHash := ts.hashToken(token) + + // Hash the token for storage + tokenInfo := &TokenInfo{ + TokenHash: tokenHash, + Name: name, + Created: time.Now(), + } + + if expires != 0 { + expiresAt := time.Now().Add(expires) + tokenInfo.Expires = &expiresAt + } + + ts.tokens[tokenHash] = tokenInfo + + if err = ts.save(); err != nil { + return "", nil, fmt.Errorf("failed to save token: %w", err) + } + + return token, tokenInfo, nil +} + +// ValidateToken validates a token and updates the last used time. +func (ts *TokenStore) ValidateToken(token string) bool { + ts.mutex.Lock() + defer ts.mutex.Unlock() + + hashedToken := ts.hashToken(token) + tokenInfo, exists := ts.tokens[hashedToken] + if !exists { + return false + } + + return !ts.isTokenExpired(tokenInfo) +} + +// ListTokens returns a list of all tokens in the store. +func (ts *TokenStore) ListTokens() []*TokenInfo { + ts.mutex.RLock() + defer ts.mutex.RUnlock() + + var tokens []*TokenInfo + for _, tokenInfo := range ts.tokens { + tokens = append(tokens, tokenInfo) + } + return tokens +} + +// DeleteToken removes a token from the store and persists the updated store to a file. +func (ts *TokenStore) DeleteToken(name string) bool { + ts.mutex.Lock() + defer ts.mutex.Unlock() + + var ti *TokenInfo + for _, tokenInfo := range ts.tokens { + if tokenInfo.Name == name { + ti = tokenInfo + break + } + } + + if ti == nil { + return false + } + + delete(ts.tokens, ti.TokenHash) + err := ts.save() + if err != nil { + ts.Log().Error("failed to save token", "error", err) + } + return true +} + +func (ts *TokenStore) isTokenExpired(tokenInfo *TokenInfo) bool { + return tokenInfo.Expires != nil && time.Now().After(*tokenInfo.Expires) +} + +// PurgeInactiveTokens removes all expired tokens from storage +func (ts *TokenStore) PurgeInactiveTokens() (expired int) { + ts.mutex.Lock() + defer ts.mutex.Unlock() + + // mark expired tokens as inactive + for hash, tokenInfo := range ts.tokens { + if ts.isTokenExpired(tokenInfo) { + delete(ts.tokens, hash) + expired++ + } + } + + if expired < 1 { + return 0 + } + + // Save changes if tokens were removed + if err := ts.save(); err != nil { + ts.Log().Error("failed to save token", "error", err) + } + + return expired +} + +// ParseDurationWithDays parses a string with token date formats into a time.Duration. +func ParseDurationWithDays(s string) (time.Duration, error) { + // Convert to lowercase for case-insensitive matching + s = strings.ToLower(s) + + // Token-appropriate time units only + unitMap := map[string]time.Duration{ + "h": time.Hour, // hours + "d": 24 * time.Hour, // days + "w": 7 * 24 * time.Hour, // weeks + "m": 30 * 24 * time.Hour, // months (30 days) + "y": 365 * 24 * time.Hour, // years (365 days) + } + + // Parse custom units + for unit, multiplier := range unitMap { + if strings.HasSuffix(s, unit) { + numStr := strings.TrimSuffix(s, unit) + num, err := strconv.ParseFloat(numStr, 64) + if err != nil { + return 0, fmt.Errorf("invalid numeric value '%s' for unit '%s'", numStr, unit) + } + if num <= 0 { + return 0, fmt.Errorf("duration must be positive, got %g%s", num, unit) + } + return time.Duration(float64(multiplier) * num), nil + } + } + + return 0, fmt.Errorf("invalid duration format: %s (supported: h, d, w, m, y)", s) +} diff --git a/web_runner.go b/web_runner.go index 3fd491f..b8c3ea0 100644 --- a/web_runner.go +++ b/web_runner.go @@ -44,30 +44,37 @@ func (p *Plugin) runWeb(ctx context.Context, webOpts webFlags) error { } serverOpts := &server.RunOptions{ - Addr: fmt.Sprintf(":%d", port), // @todo use proper addr - APIPrefix: APIPrefix, - ProxyClient: webOpts.ProxyClient, - ClientFS: GetClientAssetsFS(), - SwaggerUIFS: GetSwaggerUIAssetsFS(), - FrontendCustomize: webOpts.FrontendCustomize, - DefaultUISchema: webOpts.DefaultUISchema, - LogsDirPath: filepath.Join(webOpts.PluginDir, "logs"), + Addr: fmt.Sprintf(":%d", port), // @todo use proper addr + APIPrefix: APIPrefix, + NoUI: webOpts.NoUI, + EnableTokenAuth: webOpts.EnableTokenAuth, + ProxyClient: webOpts.ProxyClient, + SwaggerUIFS: GetSwaggerUIAssetsFS(), + Customize: webOpts.WebCustomize, + DefaultUISchema: webOpts.DefaultUISchema, + LogsDirPath: filepath.Join(webOpts.InstanceDir, "logs"), } serverOpts.SetLogger(webOpts.Log()) serverOpts.SetTerm(webOpts.Term()) - go func() { - time.Sleep(time.Second) - err := openInBrowserWhenReady(serverOpts.BaseURL()) - if err != nil { - launchr.Term().Error().Println(err) - } - }() - err = storeServerInfo(serverInfo{URL: serverOpts.BaseURL()}, webOpts.PluginDir) + err = storeServerInfo(serverInfo{URL: serverOpts.BaseURL()}, webOpts.InstanceDir) if err != nil { return err } - defer cleanupPluginTemp(webOpts.PluginDir) + defer cleanupPluginTemp(webOpts.InstanceDir) + + if !serverOpts.NoUI { + go func() { + time.Sleep(time.Second) + err = openInBrowserWhenReady(serverOpts.BaseURL()) + if err != nil { + launchr.Term().Error().Println(err) + } + }() + + serverOpts.ClientFS = GetClientAssetsFS() + } + return server.Run(ctx, p.app, serverOpts) } @@ -102,10 +109,10 @@ func (p *Plugin) runBackgroundWeb(ctx context.Context, flags webFlags, pidFile s _ = killProcess(pid) // Cleanup temp dir - cleanupPluginTemp(flags.PluginDir) + cleanupPluginTemp(flags.InstanceDir) return errors.New("couldn't start background process") case <-ticker.C: - info, _ := getServerInfo(flags.PluginDir) + info, _ := getServerInfo(flags.InstanceDir) if info == nil { continue } @@ -133,7 +140,7 @@ func stopWeb(pidFile string, webOpts webFlags) (err error) { // If we don't have pid, probably there is a server running in foreground. // We may also not have access to the pid file, prompt user the same. - serverRunInfo, err := getServerInfo(webOpts.PluginDir) + serverRunInfo, err := getServerInfo(webOpts.InstanceDir) if err != nil { return err } @@ -146,7 +153,7 @@ func stopWeb(pidFile string, webOpts webFlags) (err error) { if err = checkHealth(serverRunInfo.URL); err == nil { return fmt.Errorf("the web UI is currently running at %s\nPlease stop it through the user interface or terminate the process", serverRunInfo.URL) } - cleanupPluginTemp(webOpts.PluginDir) + cleanupPluginTemp(webOpts.InstanceDir) launchr.Term().Success().Println(onSuccess) return nil } @@ -169,7 +176,7 @@ func runBackgroundCmd(pidFile string) (int, error) { return 0, fmt.Errorf("failed to start the process in background: %w", err) } - err = os.WriteFile(pidFile, []byte(strconv.Itoa(command.Process.Pid)), os.FileMode(0644)) + err = createPIDFile(command.Process.Pid, pidFile) if err != nil { return 0, fmt.Errorf("failed to write PID file: %w", err) } @@ -177,13 +184,27 @@ func runBackgroundCmd(pidFile string) (int, error) { return command.Process.Pid, nil } +func createPIDFile(pid int, file string) error { + err := launchr.EnsurePath(filepath.Dir(file)) + if err != nil { + return fmt.Errorf("cannot create tmp directory for %q", file) + } + + err = os.WriteFile(file, []byte(strconv.Itoa(pid)), os.FileMode(0600)) + if err != nil { + return fmt.Errorf("failed to write PID file: %w", err) + } + + return err +} + func redirectOutputs(app launchr.App, webOpts webFlags) error { - err := launchr.EnsurePath(webOpts.PluginDir) + err := launchr.EnsurePath(webOpts.InstanceDir) if err != nil { return fmt.Errorf("can't create plugin temporary directory") } - outLog, err := os.Create(filepath.Join(webOpts.PluginDir, "out.log")) //nolint G304 // Path is clean. + outLog, err := os.Create(filepath.Join(webOpts.InstanceDir, "out.log")) //nolint G304 // Path is clean. if err != nil { return err } @@ -227,14 +248,20 @@ func cleanupPluginTemp(dir string) { } } -// checkHealth helper to check if server is available by request. +// checkHealth helper to check if a server is available by request. func checkHealth(url string) error { - resp, err := http.Head(url) //nolint G107 // @todo URL may come from user input, potential vulnerability. + req, err := http.NewRequest(http.MethodHead, url, nil) //nolint G107 // @todo URL may come from user input, potential vulnerability. + if err != nil { + return err + } + + resp, err := http.DefaultClient.Do(req) if err != nil { return err } _ = resp.Body.Close() - if resp.StatusCode == http.StatusOK { + + if resp.StatusCode == http.StatusOK || resp.StatusCode == http.StatusUnauthorized { return nil } return fmt.Errorf("bad response code %d", resp.StatusCode)