Compare commits
74 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
a6fab4dc75 | |
|
|
a45d681a08 | |
|
|
1c8e3d087d | |
|
|
d3908e6a3c | |
|
|
daebbfb0ad | |
|
|
8034acff6e | |
|
|
55adae872e | |
|
|
4fdc4affae | |
|
|
fc54ff76d2 | |
|
|
377ca5c1a2 | |
|
|
160753c87f | |
|
|
ff60d0b565 | |
|
|
391c677058 | |
|
|
310cdead2e | |
|
|
c2d783e872 | |
|
|
98d9cf4b28 | |
|
|
6673ebad84 | |
|
|
1b8f2871f6 | |
|
|
e34ea571fc | |
|
|
70205ac9ce | |
|
|
3a5c57e144 | |
|
|
e4267a4317 | |
|
|
d94a7ada2b | |
|
|
c31ecf5061 | |
|
|
a30957409d | |
|
|
c354768e60 | |
|
|
4de4c46f87 | |
|
|
d646b3cbd4 | |
|
|
3f5d4110f6 | |
|
|
c443baa103 | |
|
|
9c1b393b35 | |
|
|
3728b6f647 | |
|
|
2ad21b3df0 | |
|
|
e3de97fcbd | |
|
|
1ecffc87e4 | |
|
|
f708d06200 | |
|
|
55030e4eee | |
|
|
27d6fb08c6 | |
|
|
6fc2278f5f | |
|
|
2116c93cc4 | |
|
|
fc900dda54 | |
|
|
074de257f8 | |
|
|
ce6197a514 | |
|
|
31b3bd4e62 | |
|
|
a9594eb158 | |
|
|
a5814ff01c | |
|
|
7842a0cd09 | |
|
|
ae9b6872db | |
|
|
d14e894cf3 | |
|
|
6d756bdf8a | |
|
|
0ac9ea7993 | |
|
|
d37f937c9e | |
|
|
135ff63aa3 | |
|
|
e695637b08 | |
|
|
a05b93de5c | |
|
|
008a5322bd | |
|
|
0fa965e011 | |
|
|
33b6fca12c | |
|
|
ceef3f1a6b | |
|
|
7f18858182 | |
|
|
8c123dcdda | |
|
|
bb6df72463 | |
|
|
444275281f | |
|
|
4aae348a46 | |
|
|
9dbb4a4a27 | |
|
|
64d676a7e3 | |
|
|
959aae5791 | |
|
|
9a88372449 | |
|
|
a76bec234c | |
|
|
b0911ab1a2 | |
|
|
6fd4048653 | |
|
|
4a3f923b1a | |
|
|
899b7791d2 | |
|
|
0b29a3bf31 |
|
|
@ -0,0 +1,234 @@
|
|||
# Copilot Instructions for Helmfile
|
||||
|
||||
## Repository Overview
|
||||
|
||||
Helmfile is a tool for deploying Helm charts that manages Kubernetes deployments as code. It provides templating, environment management, and GitOps workflows for Helm chart deployments.
|
||||
|
||||
Helmfile is a declarative tool. In Helmfile, all elements of the desired state for deployments must be included in the `helmfile.yaml` config file and any associated files. Only operational matters can be provided dynamically, via command-line flags and environment variables.
|
||||
|
||||
**Key Details:**
|
||||
- **Language:** Go 1.24.2+
|
||||
- **Type:** CLI tool / Kubernetes deployment management
|
||||
- **Size:** Large codebase (~229MB binary, 200+ dependencies)
|
||||
- **Runtime:** Requires Helm 3.x as external dependency
|
||||
- **Target Platform:** Linux/macOS/Windows, Kubernetes clusters
|
||||
|
||||
## Build and Validation Commands
|
||||
|
||||
### Essential Setup
|
||||
Helmfile requires Helm 3.x and Kustomize 5.x as runtime dependencies:
|
||||
```bash
|
||||
# Check for Helm dependency (REQUIRED)
|
||||
helm version # Must show version 3.x
|
||||
|
||||
# Initialize Helm plugins after helmfile installation
|
||||
./helmfile init # Installs required helm-diff plugin
|
||||
|
||||
# Alternative: Force install without prompts
|
||||
./helmfile init --force
|
||||
```
|
||||
|
||||
### Build Process
|
||||
```bash
|
||||
# Standard build (takes 2-3 minutes due to many dependencies)
|
||||
make build
|
||||
|
||||
# Alternative direct build
|
||||
go build -o helmfile .
|
||||
|
||||
# Build with test tools (required for integration tests, ~1 minute)
|
||||
make build-test-tools # Creates diff-yamls and downloads dyff
|
||||
|
||||
# Cross-platform builds
|
||||
make cross
|
||||
```
|
||||
|
||||
**Build Timing:** First build downloads 200+ Go packages and takes 2-3 minutes. Subsequent builds are faster due to module cache. Test tools build is faster (~1 minute).
|
||||
|
||||
### Validation Pipeline
|
||||
Run in this exact order to match CI requirements:
|
||||
|
||||
```bash
|
||||
# 1. Code formatting and linting
|
||||
make check # Run go vet (required - always works)
|
||||
# Note: make fmt requires gci tool (go install github.com/daixiang0/gci@latest)
|
||||
|
||||
# 2. Unit tests (fast, ~30 seconds)
|
||||
go test -v ./pkg/... -race -p=1
|
||||
|
||||
# 3. Integration tests (requires Kubernetes - see Environment Setup)
|
||||
make integration # Takes 5-10 minutes, needs minikube/k8s cluster
|
||||
|
||||
# 4. E2E tests (optional, needs expect package)
|
||||
sudo apt-get install expect # On Ubuntu/Debian
|
||||
bash test/e2e/helmfile-init/init_linux.sh
|
||||
```
|
||||
|
||||
### Linting Configuration
|
||||
Uses golangci-lint with configuration in `.golangci.yaml`. Install via:
|
||||
```bash
|
||||
# For local development
|
||||
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v2.1.6
|
||||
golangci-lint run
|
||||
```
|
||||
|
||||
**Critical Lint Rules:** staticcheck, errcheck, revive, unused. Fix lint errors before committing.
|
||||
|
||||
## Environment Setup Requirements
|
||||
|
||||
### Dependencies for Development
|
||||
```bash
|
||||
# Required for building/testing
|
||||
go version # Must be 1.24.2+
|
||||
helm version # Must be 3.x
|
||||
kubectl version # For K8s integration
|
||||
|
||||
# Required for integration tests
|
||||
minikube start # Or other K8s cluster
|
||||
kustomize version # v5.2.1+ for some tests
|
||||
```
|
||||
|
||||
### Integration Test Environment
|
||||
Integration tests require a running Kubernetes cluster:
|
||||
|
||||
```bash
|
||||
# Using minikube (recommended for CI)
|
||||
minikube start
|
||||
export KUBECONFIG=$(minikube kubeconfig-path)
|
||||
|
||||
# Using kind (alternative)
|
||||
kind create cluster --name helmfile-test
|
||||
|
||||
# Verify cluster access
|
||||
kubectl cluster-info
|
||||
```
|
||||
|
||||
**Timing:** Integration tests take 5-10 minutes and may fail due to timing issues in resource-constrained environments.
|
||||
|
||||
## Project Architecture and Layout
|
||||
|
||||
### Core Directory Structure
|
||||
```
|
||||
/
|
||||
├── main.go # Entry point - CLI initialization and signal handling
|
||||
├── cmd/ # CLI commands (apply, diff, sync, template, etc.)
|
||||
│ ├── root.go # Main cobra command setup and global flags
|
||||
│ ├── apply.go # helmfile apply command
|
||||
│ ├── diff.go # helmfile diff command
|
||||
│ └── ... # Other subcommands
|
||||
├── pkg/ # Core library packages
|
||||
│ ├── app/ # Main application logic and execution
|
||||
│ ├── state/ # Helmfile state management and chart dependencies
|
||||
│ ├── helmexec/ # Helm execution and command wrapper
|
||||
│ ├── tmpl/ # Go template processing and functions
|
||||
│ ├── environment/ # Environment and values management
|
||||
│ └── ... # Other core packages
|
||||
├── test/ # Test suites
|
||||
│ ├── integration/ # Integration tests with real K8s clusters
|
||||
│ ├── e2e/ # End-to-end user workflow tests
|
||||
│ └── advanced/ # Advanced feature tests
|
||||
├── docs/ # Documentation (mkdocs format)
|
||||
├── examples/ # Example helmfile configurations
|
||||
└── .github/workflows/ # CI/CD pipeline definitions
|
||||
```
|
||||
|
||||
### Key Source Files
|
||||
- **main.go:** Signal handling, CLI execution entry point
|
||||
- **cmd/root.go:** Global CLI configuration, error handling, logging setup
|
||||
- **pkg/app/app.go:** Main application orchestration, state management
|
||||
- **pkg/state/state.go:** Helmfile state parsing, release management
|
||||
- **pkg/helmexec/exec.go:** Helm command execution, version detection
|
||||
|
||||
### Configuration Files
|
||||
- **go.mod/go.sum:** Go dependencies (many cloud providers, k8s libraries)
|
||||
- **.golangci.yaml:** Linting rules and settings
|
||||
- **Makefile:** Build targets and development workflows
|
||||
- **mkdocs.yml:** Documentation generation configuration
|
||||
- **.github/workflows/ci.yaml:** Complete CI pipeline definition
|
||||
|
||||
## Continuous Integration Pipeline
|
||||
|
||||
### GitHub Actions Workflow (`.github/workflows/ci.yaml`)
|
||||
1. **Lint Job:** golangci-lint with custom configuration (~5 minutes)
|
||||
2. **Test Job:** Unit tests + binary build (~10 minutes)
|
||||
3. **Integration Job:** Tests with multiple Helm/Kustomize versions (~15-20 minutes each)
|
||||
4. **E2E Job:** User workflow validation (~5 minutes)
|
||||
|
||||
**Matrix Testing:** CI tests against multiple Helm versions (3.17.x, 3.18.x) and Kustomize versions (5.2.x, 5.4.x).
|
||||
|
||||
### Pre-commit Validation Steps
|
||||
Always run these locally before pushing:
|
||||
```bash
|
||||
make check # Format and vet (required)
|
||||
go test ./pkg/... # Unit tests
|
||||
make build # Verify build works
|
||||
# Note: make fmt requires gci tool: go install github.com/daixiang0/gci@latest
|
||||
```
|
||||
|
||||
### Common CI Failure Causes
|
||||
- **Linting errors:** Run `golangci-lint run` locally first
|
||||
- **Integration test timeouts:** K8s cluster setup timing issues
|
||||
- **Version compatibility:** Ensure Go 1.24.2+ and Helm 3.x
|
||||
- **Race conditions:** Some tests are sensitive to parallel execution
|
||||
|
||||
## Development Gotchas and Known Issues
|
||||
|
||||
### Build Issues
|
||||
- **Long initial build time:** First `make build` downloads 200+ packages (~2-3 minutes)
|
||||
- **Memory usage:** Large binary size due to embedded dependencies
|
||||
- **Git tags:** Build may show version warnings if not on tagged commit
|
||||
- **Tool dependencies:** `make fmt` requires `gci` tool installation
|
||||
|
||||
### Testing Issues
|
||||
- **Integration tests require K8s:** Will fail without cluster access
|
||||
- **Test isolation:** Use `-p=1` flag to avoid race conditions
|
||||
- **Minikube timing:** May need to wait for cluster ready state
|
||||
- **Plugin dependencies:** Tests need helm-diff and helm-secrets plugins
|
||||
|
||||
### Runtime Requirements
|
||||
- **Helm dependency:** Always required at runtime, not just build time (available in CI)
|
||||
- **kubectl access:** Most operations need valid kubeconfig
|
||||
- **Plugin management:** `helmfile init` must be run after installation
|
||||
|
||||
### Common Error Patterns
|
||||
```bash
|
||||
# Missing Helm
|
||||
"helm: command not found" → Install Helm first
|
||||
|
||||
# Plugin missing
|
||||
"Error: plugin 'diff' not found" → Run helmfile init
|
||||
|
||||
# K8s access
|
||||
"connection refused" → Check kubectl cluster-info
|
||||
|
||||
# Permission errors
|
||||
"permission denied" → Check kubeconfig and cluster access
|
||||
|
||||
# Missing tools
|
||||
"gci: No such file or directory" → go install github.com/daixiang0/gci@latest
|
||||
```
|
||||
|
||||
## Working with the Codebase
|
||||
|
||||
### Making Changes
|
||||
- **Follow Helmfile design**: Helmfile is a declarative deployment tool. Anything that is part of the desired state of the deployments needs to be managed by Helmfile configs. Only operational knowledge that affects "how" to apply the desired state needs to be runtime options, like command-like flags and environment variables.
|
||||
- **Small, focused changes:** Each PR should address single concern
|
||||
- **Test coverage:** Add unit tests for new pkg/ functionality
|
||||
- **Integration tests:** Update test-cases/ for new CLI features
|
||||
- **Documentation:** Update docs/ for user-facing changes
|
||||
|
||||
### Key Packages to Understand
|
||||
- **pkg/app:** Main business logic, start here for feature changes
|
||||
- **pkg/state:** Helmfile parsing and release orchestration
|
||||
- **cmd/:** CLI interface changes and new subcommands
|
||||
- **pkg/helmexec:** Helm integration and command execution
|
||||
|
||||
### Architecture Patterns
|
||||
- **Dependency injection:** App uses interfaces for testability
|
||||
- **State management:** Immutable state objects, functional transforms
|
||||
- **Error handling:** Custom error types with exit codes
|
||||
- **Plugin system:** Extensible via Helm plugins and Go templates
|
||||
|
||||
---
|
||||
|
||||
**Trust these instructions:** This information is validated against the current codebase. Only search for additional details if these instructions are incomplete or found to be incorrect for your specific task.
|
||||
|
|
@ -13,8 +13,8 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
timeout-minutes: 10
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
cache: false
|
||||
|
|
@ -25,10 +25,10 @@ jobs:
|
|||
tests:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
- name: Build
|
||||
|
|
@ -37,7 +37,7 @@ jobs:
|
|||
run: make check test
|
||||
- name: Archive built binaries
|
||||
run: tar -cvf built-binaries.tar helmfile diff-yamls dyff
|
||||
- uses: actions/upload-artifact@v4
|
||||
- uses: actions/upload-artifact@v5
|
||||
with:
|
||||
name: built-binaries-${{ github.run_id }}
|
||||
path: built-binaries.tar
|
||||
|
|
@ -54,12 +54,12 @@ jobs:
|
|||
# Helm maintains the latest minor version only and therefore each Helmfile version supports 2 Helm minor versions.
|
||||
# That's why we cover only 2 Helm minor versions in this matrix.
|
||||
# See https://github.com/helmfile/helmfile/pull/286#issuecomment-1250161182 for more context.
|
||||
- helm-version: v3.17.3
|
||||
- helm-version: v3.18.6
|
||||
kustomize-version: v5.2.1
|
||||
plugin-secrets-version: 4.6.5
|
||||
plugin-diff-version: 3.11.0
|
||||
extra-helmfile-flags: ''
|
||||
- helm-version: v3.17.3
|
||||
- helm-version: v3.18.6
|
||||
kustomize-version: v5.4.3
|
||||
# We assume that the helm-secrets plugin is supposed to
|
||||
# work with the two most recent helm minor versions.
|
||||
|
|
@ -67,32 +67,32 @@ jobs:
|
|||
# we will mark this combination as failable,
|
||||
# and instruct users to upgrade helm and helm-secrets at once.
|
||||
plugin-secrets-version: 4.6.5
|
||||
plugin-diff-version: 3.12.3
|
||||
plugin-diff-version: 3.12.5
|
||||
extra-helmfile-flags: ''
|
||||
- helm-version: v3.18.4
|
||||
- helm-version: v3.19.0
|
||||
kustomize-version: v5.2.1
|
||||
plugin-secrets-version: 4.6.5
|
||||
plugin-diff-version: 3.11.0
|
||||
extra-helmfile-flags: ''
|
||||
- helm-version: v3.18.4
|
||||
- helm-version: v3.19.0
|
||||
kustomize-version: v5.4.3
|
||||
plugin-secrets-version: 4.6.5
|
||||
plugin-diff-version: 3.12.3
|
||||
plugin-diff-version: 3.12.5
|
||||
extra-helmfile-flags: ''
|
||||
# In case you need to test some optional helmfile features,
|
||||
# enable it via extra-helmfile-flags below.
|
||||
- helm-version: v3.18.4
|
||||
- helm-version: v3.19.0
|
||||
kustomize-version: v5.4.3
|
||||
plugin-secrets-version: 4.6.5
|
||||
plugin-diff-version: 3.12.3
|
||||
plugin-diff-version: 3.12.5
|
||||
extra-helmfile-flags: '--enable-live-output'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/checkout@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: built-binaries-${{ github.run_id }}
|
||||
- name: install semver
|
||||
|
|
@ -109,6 +109,8 @@ jobs:
|
|||
run: make -C .github/workflows helm vault sops kustomize
|
||||
- name: Start minikube
|
||||
uses: medyagh/setup-minikube@latest
|
||||
with:
|
||||
kubernetes-version: v1.33.1
|
||||
- name: Execute integration tests
|
||||
run: make integration
|
||||
env:
|
||||
|
|
@ -122,10 +124,10 @@ jobs:
|
|||
needs: tests
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/download-artifact@v4
|
||||
- uses: actions/download-artifact@v6
|
||||
with:
|
||||
name: built-binaries-${{ github.run_id }}
|
||||
- name: Extract tar to get built binaries
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ jobs:
|
|||
suffix: "-ubuntu"
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
|
|
|
|||
|
|
@ -22,12 +22,25 @@ jobs:
|
|||
goreleaser:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@v5
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: actions/setup-go@v5
|
||||
- uses: actions/setup-go@v6
|
||||
with:
|
||||
go-version-file: go.mod
|
||||
- name: check disk usage
|
||||
run: df -h
|
||||
- name: cleanup disk
|
||||
run: |
|
||||
sudo rm -rf /usr/share/dotnet
|
||||
sudo rm -rf /opt/ghc
|
||||
sudo rm -rf /usr/local/share/boost
|
||||
sudo rm -fr /usr/local/lib/android
|
||||
sudo rm -fr /opt/hostedtoolcache/CodeQL
|
||||
sudo docker image prune --all --force
|
||||
sudo docker builder prune -a
|
||||
- name: check disk usage
|
||||
run: df -h
|
||||
- uses: goreleaser/goreleaser-action@v6
|
||||
with:
|
||||
version: latest
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ linters:
|
|||
lines: 280
|
||||
statements: 140
|
||||
gocognit:
|
||||
min-complexity: 100
|
||||
min-complexity: 110
|
||||
goconst:
|
||||
min-len: 3
|
||||
min-occurrences: 8
|
||||
|
|
|
|||
12
Dockerfile
12
Dockerfile
|
|
@ -12,11 +12,11 @@ RUN make static-${TARGETOS}-${TARGETARCH}
|
|||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
FROM alpine:3.19
|
||||
FROM alpine:3.22
|
||||
|
||||
LABEL org.opencontainers.image.source=https://github.com/helmfile/helmfile
|
||||
|
||||
RUN apk add --no-cache ca-certificates git bash curl jq openssh-client gnupg
|
||||
RUN apk add --no-cache ca-certificates git bash curl jq yq openssh-client gnupg
|
||||
|
||||
ARG TARGETARCH TARGETOS TARGETPLATFORM
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}"
|
|||
ARG HELM_DATA_HOME="${HOME}/.local/share/helm"
|
||||
ENV HELM_DATA_HOME="${HELM_DATA_HOME}"
|
||||
|
||||
ARG HELM_VERSION="v3.18.4"
|
||||
ARG HELM_VERSION="v3.19.0"
|
||||
ENV HELM_VERSION="${HELM_VERSION}"
|
||||
ARG HELM_LOCATION="https://get.helm.sh"
|
||||
ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz"
|
||||
|
|
@ -38,8 +38,8 @@ RUN set -x && \
|
|||
curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \
|
||||
echo Verifying ${HELM_FILENAME}... && \
|
||||
case ${TARGETPLATFORM} in \
|
||||
"linux/amd64") HELM_SHA256="f8180838c23d7c7d797b208861fecb591d9ce1690d8704ed1e4cb8e2add966c1" ;; \
|
||||
"linux/arm64") HELM_SHA256="c0a45e67eef0c7416a8a8c9e9d5d2d30d70e4f4d3f7bea5de28241fffa8f3b89" ;; \
|
||||
"linux/amd64") HELM_SHA256="a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496" ;; \
|
||||
"linux/arm64") HELM_SHA256="440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad" ;; \
|
||||
esac && \
|
||||
echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \
|
||||
echo Extracting ${HELM_FILENAME}... && \
|
||||
|
|
@ -93,7 +93,7 @@ RUN set -x && \
|
|||
[ "$(age --version)" = "${AGE_VERSION}" ] && \
|
||||
[ "$(age-keygen --version)" = "${AGE_VERSION}" ]
|
||||
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.12.3 && \
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.13.1 && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \
|
||||
helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \
|
||||
helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ RUN apt update -qq && \
|
|||
|
||||
ARG TARGETARCH TARGETOS TARGETPLATFORM
|
||||
|
||||
RUN wget https://github.com/mikefarah/yq/releases/latest/download/yq_${TARGETOS}_${TARGETARCH} -O /usr/local/bin/yq &&\
|
||||
chmod +x /usr/local/bin/yq
|
||||
|
||||
# Set Helm home variables so that also non-root users can use plugins etc.
|
||||
ARG HOME="/helm"
|
||||
ENV HOME="${HOME}"
|
||||
|
|
@ -35,7 +38,7 @@ ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}"
|
|||
ARG HELM_DATA_HOME="${HOME}/.local/share/helm"
|
||||
ENV HELM_DATA_HOME="${HELM_DATA_HOME}"
|
||||
|
||||
ARG HELM_VERSION="v3.18.4"
|
||||
ARG HELM_VERSION="v3.19.0"
|
||||
ENV HELM_VERSION="${HELM_VERSION}"
|
||||
ARG HELM_LOCATION="https://get.helm.sh"
|
||||
ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz"
|
||||
|
|
@ -43,8 +46,8 @@ RUN set -x && \
|
|||
curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \
|
||||
echo Verifying ${HELM_FILENAME}... && \
|
||||
case ${TARGETPLATFORM} in \
|
||||
"linux/amd64") HELM_SHA256="f8180838c23d7c7d797b208861fecb591d9ce1690d8704ed1e4cb8e2add966c1" ;; \
|
||||
"linux/arm64") HELM_SHA256="c0a45e67eef0c7416a8a8c9e9d5d2d30d70e4f4d3f7bea5de28241fffa8f3b89" ;; \
|
||||
"linux/amd64") HELM_SHA256="a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496" ;; \
|
||||
"linux/arm64") HELM_SHA256="440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad" ;; \
|
||||
esac && \
|
||||
echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \
|
||||
echo Extracting ${HELM_FILENAME}... && \
|
||||
|
|
@ -99,7 +102,7 @@ RUN set -x && \
|
|||
[ "$(age --version)" = "${AGE_VERSION}" ] && \
|
||||
[ "$(age-keygen --version)" = "${AGE_VERSION}" ]
|
||||
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.12.3 && \
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.13.1 && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \
|
||||
helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \
|
||||
helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ RUN make static-${TARGETOS}-${TARGETARCH}
|
|||
|
||||
# -----------------------------------------------------------------------------
|
||||
|
||||
FROM ubuntu:24.10
|
||||
FROM ubuntu:24.04
|
||||
|
||||
LABEL org.opencontainers.image.source=https://github.com/helmfile/helmfile
|
||||
|
||||
|
|
@ -25,6 +25,9 @@ RUN apt update -qq && \
|
|||
|
||||
ARG TARGETARCH TARGETOS TARGETPLATFORM
|
||||
|
||||
RUN wget https://github.com/mikefarah/yq/releases/latest/download/yq_${TARGETOS}_${TARGETARCH} -O /usr/local/bin/yq &&\
|
||||
chmod +x /usr/local/bin/yq
|
||||
|
||||
# Set Helm home variables so that also non-root users can use plugins etc.
|
||||
ARG HOME="/helm"
|
||||
ENV HOME="${HOME}"
|
||||
|
|
@ -35,7 +38,7 @@ ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}"
|
|||
ARG HELM_DATA_HOME="${HOME}/.local/share/helm"
|
||||
ENV HELM_DATA_HOME="${HELM_DATA_HOME}"
|
||||
|
||||
ARG HELM_VERSION="v3.18.4"
|
||||
ARG HELM_VERSION="v3.19.0"
|
||||
ENV HELM_VERSION="${HELM_VERSION}"
|
||||
ARG HELM_LOCATION="https://get.helm.sh"
|
||||
ARG HELM_FILENAME="helm-${HELM_VERSION}-${TARGETOS}-${TARGETARCH}.tar.gz"
|
||||
|
|
@ -43,8 +46,8 @@ RUN set -x && \
|
|||
curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \
|
||||
echo Verifying ${HELM_FILENAME}... && \
|
||||
case ${TARGETPLATFORM} in \
|
||||
"linux/amd64") HELM_SHA256="f8180838c23d7c7d797b208861fecb591d9ce1690d8704ed1e4cb8e2add966c1" ;; \
|
||||
"linux/arm64") HELM_SHA256="c0a45e67eef0c7416a8a8c9e9d5d2d30d70e4f4d3f7bea5de28241fffa8f3b89" ;; \
|
||||
"linux/amd64") HELM_SHA256="a7f81ce08007091b86d8bd696eb4d86b8d0f2e1b9f6c714be62f82f96a594496" ;; \
|
||||
"linux/arm64") HELM_SHA256="440cf7add0aee27ebc93fada965523c1dc2e0ab340d4348da2215737fc0d76ad" ;; \
|
||||
esac && \
|
||||
echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \
|
||||
echo Extracting ${HELM_FILENAME}... && \
|
||||
|
|
@ -99,7 +102,7 @@ RUN set -x && \
|
|||
[ "$(age --version)" = "${AGE_VERSION}" ] && \
|
||||
[ "$(age-keygen --version)" = "${AGE_VERSION}" ]
|
||||
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.12.3 && \
|
||||
RUN helm plugin install https://github.com/databus23/helm-diff --version v3.13.1 && \
|
||||
helm plugin install https://github.com/jkroepke/helm-secrets --version v4.6.5 && \
|
||||
helm plugin install https://github.com/hypnoglow/helm-s3.git --version v0.16.3 && \
|
||||
helm plugin install https://github.com/aslafy-z/helm-git.git --version v1.3.0 && \
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
[](https://slack.sweetops.com)
|
||||
[](https://helmfile.readthedocs.io/en/latest/)
|
||||
[](https://gurubase.io/g/helmfile)
|
||||
[](https://zread.ai/helmfile/helmfile)
|
||||
|
||||
声明式Helm Chart管理工具
|
||||
<br />
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@
|
|||
[](https://slack.sweetops.com)
|
||||
[](https://helmfile.readthedocs.io/en/latest/)
|
||||
[](https://gurubase.io/g/helmfile)
|
||||
[](https://zread.ai/helmfile/helmfile)
|
||||
|
||||
Deploy Kubernetes Helm Charts
|
||||
<br />
|
||||
|
|
@ -33,7 +34,9 @@ Helmfile is a declarative spec for deploying helm charts. It lets you...
|
|||
* Apply CI/CD to configuration changes.
|
||||
* Periodically sync to avoid skew in environments.
|
||||
|
||||
To avoid upgrades for each iteration of `helm`, the `helmfile` executable delegates to `helm` - as a result, `helm` must be installed.
|
||||
To avoid upgrades for each iteration of `helm`, the `helmfile` executable delegates to `helm` - as a result, the following must be installed
|
||||
- [helm](https://helm.sh/docs/intro/install/)
|
||||
- [helm-diff](https://github.com/databus23/helm-diff)
|
||||
|
||||
## Highlights
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +0,0 @@
|
|||
Vagrant.configure("2") do |config|
|
||||
config.vm.box = "ubuntu/focal64"
|
||||
config.vm.hostname = "minikube.box"
|
||||
config.vm.provision :shell, privileged: false,
|
||||
inline: <<-EOS
|
||||
set -e
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y make docker.io
|
||||
sudo systemctl start docker
|
||||
sudo usermod -G docker $USER
|
||||
cd /vagrant/.circleci
|
||||
make all
|
||||
EOS
|
||||
|
||||
config.vm.provider "virtualbox" do |v|
|
||||
v.memory = 2048
|
||||
v.cpus = 2
|
||||
end
|
||||
end
|
||||
|
|
@ -76,7 +76,7 @@ func NewRootCmd(globalConfig *config.GlobalOptions) (*cobra.Command, error) {
|
|||
// Set the global options for the root command.
|
||||
setGlobalOptionsForRootCmd(flags, globalConfig)
|
||||
|
||||
flags.ParseErrorsWhitelist.UnknownFlags = true
|
||||
flags.ParseErrorsAllowlist.UnknownFlags = true
|
||||
|
||||
globalImpl := config.NewGlobalImpl(globalConfig)
|
||||
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ func NewSyncCmd(globalCfg *config.GlobalImpl) *cobra.Command {
|
|||
f.BoolVar(&syncOptions.SyncReleaseLabels, "sync-release-labels", false, "sync release labels to the target release")
|
||||
f.BoolVar(&syncOptions.Wait, "wait", false, `Override helmDefaults.wait setting "helm upgrade --install --wait"`)
|
||||
f.BoolVar(&syncOptions.WaitForJobs, "wait-for-jobs", false, `Override helmDefaults.waitForJobs setting "helm upgrade --install --wait-for-jobs"`)
|
||||
f.IntVar(&syncOptions.Timeout, "timeout", 0, `Override helmDefaults.timeout setting "helm upgrade --install --timeout" (default 0, which means no timeout)`)
|
||||
f.BoolVar(&syncOptions.ReuseValues, "reuse-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reuse-values"`)
|
||||
f.BoolVar(&syncOptions.ResetValues, "reset-values", false, `Override helmDefaults.reuseValues "helm upgrade --install --reset-values"`)
|
||||
f.StringVar(&syncOptions.PostRenderer, "post-renderer", "", `pass --post-renderer to "helm template" or "helm upgrade --install"`)
|
||||
|
|
|
|||
|
|
@ -194,8 +194,9 @@ helmDefaults:
|
|||
skipSchemaValidation: false
|
||||
# wait for k8s resources via --wait. (default false)
|
||||
wait: true
|
||||
# if set and --wait enabled, will retry any failed check on resource state subject to the specified number of retries (default 0)
|
||||
waitRetries: 3
|
||||
# DEPRECATED: waitRetries is no longer supported as the --wait-retries flag was removed from Helm.
|
||||
# This configuration is ignored and preserved only for backward compatibility.
|
||||
# waitRetries: 3
|
||||
# if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout (default false, Implemented in Helm3.5)
|
||||
waitForJobs: true
|
||||
# time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300)
|
||||
|
|
@ -318,7 +319,8 @@ releases:
|
|||
# --skip-schema-validation flag to helm 'install', 'upgrade' and 'lint', starts with helm 3.16.0 (default false)
|
||||
skipSchemaValidation: false
|
||||
wait: true
|
||||
waitRetries: 3
|
||||
# DEPRECATED: waitRetries is no longer supported - see documentation above
|
||||
# waitRetries: 3
|
||||
waitForJobs: true
|
||||
timeout: 60
|
||||
recreatePods: true
|
||||
|
|
@ -326,6 +328,9 @@ releases:
|
|||
reuseValues: false
|
||||
# set `false` to uninstall this release on sync. (default true)
|
||||
installed: true
|
||||
# Defines the strategy to use when updating. Possible value is:
|
||||
# - "reinstallIfForbidden": Performs an uninstall before the update only if the update is forbidden (e.g., due to permission issues or conflicts).
|
||||
updateStrategy: ""
|
||||
# restores previous state in case of failed release (default false)
|
||||
atomic: true
|
||||
# when true, cleans up any new resources created during a failed release (default false)
|
||||
|
|
@ -409,9 +414,16 @@ helmfiles:
|
|||
# The nested-state file is locally checked-out along with the remote directory containing it.
|
||||
# Therefore all the local paths in the file are resolved relative to the file
|
||||
path: git::https://github.com/cloudposse/helmfiles.git@releases/kiam.yaml?ref=0.40.0
|
||||
- # By default git repositories aren't updated unless the ref is updated.
|
||||
# Alternatively, refer to a named ref and disable the caching.
|
||||
path: git::ssh://git@github.com/cloudposse/helmfiles.git@releases/kiam.yaml?ref=main&cache=false
|
||||
# If set to "Error", return an error when a subhelmfile points to a
|
||||
# non-existent path. The default behavior is to print a warning and continue.
|
||||
missingFileHandler: Error
|
||||
missingFileHandlerConfig:
|
||||
# Ignores missing git branch error so that the Debug/Info/Warn handler can treat a missing branch as non-error.
|
||||
# See https://github.com/helmfile/helmfile/issues/392
|
||||
ignoreMissingGitBranch: true
|
||||
|
||||
#
|
||||
# Advanced Configuration: Environments
|
||||
|
|
@ -567,7 +579,7 @@ Helmfile uses some OS environment variables to override default behaviour:
|
|||
* `HELMFILE_ENVIRONMENT` - specify [Helmfile environment](https://helmfile.readthedocs.io/en/latest/#environment), it has lower priority than CLI argument `--environment`
|
||||
* `HELMFILE_TEMPDIR` - specify directory to store temporary files
|
||||
* `HELMFILE_UPGRADE_NOTICE_DISABLED` - expecting any non-empty value to skip the check for the latest version of Helmfile in [helmfile version](https://helmfile.readthedocs.io/en/latest/#version)
|
||||
* `HELMFILE_GO_YAML_V3` - use *gopkg.in/yaml.v3* instead of *gopkg.in/yaml.v2*. It's `false` by default in Helmfile v0.x, and `true` in Helmfile v1.x.
|
||||
* `HELMFILE_GO_YAML_V3` - use *go.yaml.in/yaml/v3* instead of *go.yaml.in/yaml/v2*. It's `false` by default in Helmfile v0.x, and `true` in Helmfile v1.x.
|
||||
* `HELMFILE_CACHE_HOME` - specify directory to store cached files for remote operations
|
||||
* `HELMFILE_FILE_PATH` - specify the path to the helmfile.yaml file
|
||||
* `HELMFILE_INTERACTIVE` - enable interactive mode, expecting `true` lower case. The same as `--interactive` CLI flag
|
||||
|
|
@ -577,7 +589,7 @@ Helmfile uses some OS environment variables to override default behaviour:
|
|||
```
|
||||
Declaratively deploy your Kubernetes manifests, Kustomize configs, and Charts as Helm releases in one shot
|
||||
V1 mode = false
|
||||
YAML library = gopkg.in/yaml.v3
|
||||
YAML library = go.yaml.in/yaml/v3
|
||||
|
||||
Usage:
|
||||
helmfile [command]
|
||||
|
|
@ -639,6 +651,8 @@ Flags:
|
|||
Use "helmfile [command] --help" for more information about a command.
|
||||
```
|
||||
|
||||
**Note:** Each command has its own specific flags. Use `helmfile [command] --help` to see command-specific options. For example, `helmfile sync --help` shows operational flags like `--timeout`, `--wait`, and `--wait-for-jobs`.
|
||||
|
||||
### init
|
||||
|
||||
The `helmfile init` sub-command checks the dependencies required for helmfile operation, such as `helm`, `helm diff plugin`, `helm secrets plugin`, `helm helm-git plugin`, `helm s3 plugin`. When it does not exist or the version is too low, it can be installed automatically.
|
||||
|
|
@ -654,6 +668,25 @@ The `helmfile sync` sub-command sync your cluster state as described in your `he
|
|||
Under the covers, Helmfile executes `helm upgrade --install` for each `release` declared in the manifest, by optionally decrypting [secrets](#secrets) to be consumed as helm chart values. It also updates specified chart repositories and updates the
|
||||
dependencies of any referenced local charts.
|
||||
|
||||
#### Common sync flags
|
||||
|
||||
* `--timeout SECONDS` - Override the default timeout for all releases in this sync operation. This takes precedence over `helmDefaults.timeout` and per-release `timeout` settings.
|
||||
* `--wait` - Override the default wait behavior for all releases
|
||||
* `--wait-for-jobs` - Override the default wait-for-jobs behavior for all releases
|
||||
|
||||
Examples:
|
||||
|
||||
```bash
|
||||
# Override timeout for all releases to 10 minutes
|
||||
helmfile sync --timeout 600
|
||||
|
||||
# Combine timeout with wait flags
|
||||
helmfile sync --timeout 900 --wait --wait-for-jobs
|
||||
|
||||
# Target specific releases with custom timeout
|
||||
helmfile sync --selector tier=backend --timeout 1200
|
||||
```
|
||||
|
||||
For Helm 2.9+ you can use a username and password to authenticate to a remote repository.
|
||||
|
||||
### deps
|
||||
|
|
|
|||
313
go.mod
313
go.mod
|
|
@ -1,42 +1,44 @@
|
|||
module github.com/helmfile/helmfile
|
||||
|
||||
go 1.24.2
|
||||
go 1.24.6
|
||||
|
||||
require (
|
||||
dario.cat/mergo v1.0.2
|
||||
github.com/Masterminds/semver/v3 v3.4.0
|
||||
github.com/Masterminds/sprig/v3 v3.3.0
|
||||
github.com/aws/aws-sdk-go-v2/config v1.31.15
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.88.7
|
||||
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc
|
||||
github.com/go-test/deep v1.1.1
|
||||
github.com/golang/mock v1.6.0
|
||||
github.com/google/go-cmp v0.7.0
|
||||
github.com/gosuri/uitable v0.0.4
|
||||
github.com/hashicorp/go-getter v1.7.8
|
||||
github.com/hashicorp/go-getter v1.8.2
|
||||
github.com/hashicorp/hcl/v2 v2.24.0
|
||||
github.com/helmfile/chartify v0.24.6
|
||||
github.com/helmfile/vals v0.41.2
|
||||
github.com/spf13/cobra v1.9.1
|
||||
github.com/spf13/pflag v1.0.6
|
||||
github.com/stretchr/testify v1.10.0
|
||||
github.com/helmfile/chartify v0.25.0
|
||||
github.com/helmfile/vals v0.42.4
|
||||
github.com/spf13/cobra v1.10.1
|
||||
github.com/spf13/pflag v1.0.10
|
||||
github.com/stretchr/testify v1.11.1
|
||||
github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939
|
||||
github.com/tj/assert v0.0.3
|
||||
github.com/variantdev/dag v1.1.0
|
||||
github.com/zclconf/go-cty v1.16.3
|
||||
github.com/zclconf/go-cty v1.17.0
|
||||
github.com/zclconf/go-cty-yaml v1.1.0
|
||||
go.szostok.io/version v1.2.0
|
||||
go.uber.org/zap v1.27.0
|
||||
golang.org/x/sync v0.16.0
|
||||
golang.org/x/term v0.33.0
|
||||
gopkg.in/yaml.v2 v2.4.0
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
helm.sh/helm/v3 v3.18.4
|
||||
k8s.io/apimachinery v0.33.2
|
||||
go.yaml.in/yaml/v2 v2.4.3
|
||||
go.yaml.in/yaml/v3 v3.0.4
|
||||
golang.org/x/sync v0.17.0
|
||||
golang.org/x/term v0.36.0
|
||||
helm.sh/helm/v3 v3.19.0
|
||||
k8s.io/apimachinery v0.34.1
|
||||
)
|
||||
|
||||
require (
|
||||
cloud.google.com/go v0.116.0 // indirect
|
||||
cloud.google.com/go/iam v1.2.2 // indirect
|
||||
cloud.google.com/go/storage v1.50.0 // indirect
|
||||
cloud.google.com/go v0.121.6 // indirect
|
||||
cloud.google.com/go/iam v1.5.2 // indirect
|
||||
cloud.google.com/go/storage v1.57.0 // indirect
|
||||
filippo.io/age v1.2.1 // indirect
|
||||
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
|
||||
github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect
|
||||
|
|
@ -46,190 +48,198 @@ require (
|
|||
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
|
||||
github.com/Masterminds/goutils v1.1.1 // indirect
|
||||
github.com/a8m/envsubst v1.4.3 // indirect
|
||||
github.com/aws/aws-sdk-go v1.55.7
|
||||
github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect
|
||||
github.com/blang/semver v3.5.1+incompatible // indirect
|
||||
github.com/dimchansky/utfbom v1.1.1 // indirect
|
||||
github.com/fatih/color v1.18.0
|
||||
github.com/fujiwara/tfstate-lookup v1.6.0 // indirect
|
||||
github.com/fujiwara/tfstate-lookup v1.7.1 // indirect
|
||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
|
||||
github.com/google/go-querystring v1.1.0 // indirect
|
||||
github.com/google/uuid v1.6.0 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.14.1 // indirect
|
||||
github.com/googleapis/gax-go/v2 v2.15.0 // indirect
|
||||
github.com/goware/prefixer v0.0.0-20160118172347-395022866408 // indirect
|
||||
github.com/hashicorp/errwrap v1.1.0 // indirect
|
||||
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
|
||||
github.com/hashicorp/go-multierror v1.1.1 // indirect
|
||||
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
|
||||
github.com/hashicorp/go-safetemp v1.0.0 // indirect
|
||||
github.com/hashicorp/go-slug v0.16.3 // indirect
|
||||
github.com/hashicorp/go-slug v0.16.4 // indirect
|
||||
github.com/hashicorp/go-sockaddr v1.0.7 // indirect
|
||||
github.com/hashicorp/go-tfe v1.56.0 // indirect
|
||||
github.com/hashicorp/go-tfe v1.84.0 // indirect
|
||||
github.com/hashicorp/golang-lru v1.0.2 // indirect
|
||||
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||
github.com/hashicorp/jsonapi v1.3.1 // indirect
|
||||
github.com/hashicorp/vault/api v1.16.0 // indirect
|
||||
github.com/hashicorp/hcl v1.0.1-vault-7 // indirect
|
||||
github.com/hashicorp/jsonapi v1.4.3-0.20250220162346-81a76b606f3e // indirect
|
||||
github.com/hashicorp/vault/api v1.22.0 // indirect
|
||||
github.com/huandu/xstrings v1.5.0 // indirect
|
||||
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||
github.com/itchyny/gojq v0.12.16 // indirect
|
||||
github.com/jmespath/go-jmespath v0.4.0 // indirect
|
||||
github.com/klauspost/compress v1.18.0 // indirect
|
||||
github.com/lib/pq v1.10.9 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||
github.com/mattn/go-runewidth v0.0.15 // indirect
|
||||
github.com/mitchellh/copystructure v1.2.0 // indirect
|
||||
github.com/mitchellh/go-homedir v1.1.0 // indirect
|
||||
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
|
||||
github.com/mitchellh/go-wordwrap v1.0.1 // indirect
|
||||
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||
github.com/mitchellh/reflectwalk v1.0.2 // indirect
|
||||
github.com/otiai10/copy v1.14.1 // indirect
|
||||
github.com/otiai10/copy v1.14.1
|
||||
github.com/pkg/errors v0.9.1
|
||||
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
|
||||
github.com/ryanuber/go-glob v1.0.0 // indirect
|
||||
github.com/shopspring/decimal v1.4.0 // indirect
|
||||
github.com/sirupsen/logrus v1.9.3 // indirect
|
||||
github.com/spf13/cast v1.7.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.10 // indirect
|
||||
go.opencensus.io v0.24.0 // indirect
|
||||
github.com/ulikunitz/xz v0.5.15 // indirect
|
||||
go.uber.org/atomic v1.9.0 // indirect
|
||||
golang.org/x/net v0.40.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/sys v0.34.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
golang.org/x/time v0.9.0 // indirect
|
||||
google.golang.org/api v0.215.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20241118233622-e639e219e697 // indirect
|
||||
google.golang.org/grpc v1.68.1 // indirect
|
||||
google.golang.org/protobuf v1.36.5 // indirect
|
||||
golang.org/x/net v0.44.0 // indirect
|
||||
golang.org/x/oauth2 v0.31.0 // indirect
|
||||
golang.org/x/sys v0.37.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
golang.org/x/time v0.13.0 // indirect
|
||||
google.golang.org/api v0.252.0 // indirect
|
||||
google.golang.org/genproto v0.0.0-20250603155806-513f23925822 // indirect
|
||||
google.golang.org/grpc v1.75.1 // indirect
|
||||
google.golang.org/protobuf v1.36.10 // indirect
|
||||
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241010143419-9aa6b5e7a4b3 // indirect
|
||||
sigs.k8s.io/yaml v1.4.0 // indirect
|
||||
sigs.k8s.io/json v0.0.0-20241014173422-cfa47c3a1cc8 // indirect
|
||||
sigs.k8s.io/yaml v1.6.0 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
al.essio.dev/pkg/shellescape v1.5.1 // indirect
|
||||
cel.dev/expr v0.19.1 // indirect
|
||||
cloud.google.com/go/auth v0.13.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.6 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.6.0 // indirect
|
||||
cloud.google.com/go/kms v1.20.5 // indirect
|
||||
cloud.google.com/go/longrunning v0.6.2 // indirect
|
||||
cloud.google.com/go/monitoring v1.21.2 // indirect
|
||||
cloud.google.com/go/secretmanager v1.14.3 // indirect
|
||||
al.essio.dev/pkg/shellescape v1.6.0 // indirect
|
||||
cel.dev/expr v0.24.0 // indirect
|
||||
cloud.google.com/go/auth v0.17.0 // indirect
|
||||
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
|
||||
cloud.google.com/go/compute/metadata v0.9.0 // indirect
|
||||
cloud.google.com/go/kms v1.23.0 // indirect
|
||||
cloud.google.com/go/longrunning v0.6.7 // indirect
|
||||
cloud.google.com/go/monitoring v1.24.2 // indirect
|
||||
cloud.google.com/go/secretmanager v1.15.0 // indirect
|
||||
filippo.io/edwards25519 v1.1.0 // indirect
|
||||
github.com/1Password/connect-sdk-go v1.5.3 // indirect
|
||||
github.com/1password/onepassword-sdk-go v0.3.0 // indirect
|
||||
github.com/1password/onepassword-sdk-go v0.3.1 // indirect
|
||||
github.com/AlecAivazis/survey/v2 v2.3.6 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.18.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.10.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.3.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.3.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.1.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azcore v1.19.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.12.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.2.0 // indirect
|
||||
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v1.1.0 // indirect
|
||||
github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.2 // indirect
|
||||
github.com/AzureAD/microsoft-authentication-library-for-go v1.5.0 // indirect
|
||||
github.com/DopplerHQ/cli v0.5.11-0.20230908185655-7aef4713e1a4 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.25.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.48.1 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.48.1 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.29.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.53.0 // indirect
|
||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.53.0 // indirect
|
||||
github.com/MakeNowJust/heredoc v1.0.0 // indirect
|
||||
github.com/Masterminds/squirrel v1.5.4 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.1.3 // indirect
|
||||
github.com/ProtonMail/go-crypto v1.3.0 // indirect
|
||||
github.com/agext/levenshtein v1.2.3 // indirect
|
||||
github.com/antchfx/jsonquery v1.3.6 // indirect
|
||||
github.com/antchfx/xpath v1.3.4 // indirect
|
||||
github.com/antchfx/xpath v1.3.5 // indirect
|
||||
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
|
||||
github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect
|
||||
github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect
|
||||
github.com/atotto/clipboard v0.1.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.36.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.6.10 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/config v1.29.14 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.17.67 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.16.30 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.17.72 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.3.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.6.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.3.34 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.12.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.7.0 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.12.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.18.15 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.37.7 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/s3 v1.79.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.25.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.30.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.33.19 // indirect
|
||||
github.com/aws/smithy-go v1.22.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2 v1.39.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/credentials v1.18.19 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.19.9 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.2 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.11 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/kms v1.45.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.39.6 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssm v1.65.1 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sso v1.29.8 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.3 // indirect
|
||||
github.com/aws/aws-sdk-go-v2/service/sts v1.38.9 // indirect
|
||||
github.com/aws/smithy-go v1.23.1 // indirect
|
||||
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||
github.com/blang/semver/v4 v4.0.0 // indirect
|
||||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||
github.com/census-instrumentation/opencensus-proto v0.4.1 // indirect
|
||||
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||
github.com/chai2010/gettext-go v1.0.2 // indirect
|
||||
github.com/cloudflare/circl v1.6.1 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20240905190251-b4127c9b8d78 // indirect
|
||||
github.com/containerd/containerd v1.7.27 // indirect
|
||||
github.com/cncf/xds/go v0.0.0-20250501225837-2ac532fd4443 // indirect
|
||||
github.com/containerd/containerd v1.7.28 // indirect
|
||||
github.com/containerd/errdefs v0.3.0 // indirect
|
||||
github.com/containerd/log v0.1.0 // indirect
|
||||
github.com/containerd/platforms v0.2.1 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.6 // indirect
|
||||
github.com/cyberark/conjur-api-go v0.13.0 // indirect
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
|
||||
github.com/cyberark/conjur-api-go v0.13.7 // indirect
|
||||
github.com/danieljoos/wincred v1.2.2 // indirect
|
||||
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||
github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.1 // indirect
|
||||
github.com/envoyproxy/go-control-plane v0.13.1 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect
|
||||
github.com/emicklei/go-restful/v3 v3.12.2 // indirect
|
||||
github.com/envoyproxy/go-control-plane/envoy v1.32.4 // indirect
|
||||
github.com/envoyproxy/protoc-gen-validate v1.2.1 // indirect
|
||||
github.com/evanphx/json-patch v5.9.11+incompatible // indirect
|
||||
github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect
|
||||
github.com/extism/go-sdk v1.7.0 // indirect
|
||||
github.com/felixge/httpsnoop v1.0.4 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.7.0 // indirect
|
||||
github.com/getsops/gopgagent v0.0.0-20240527072608-0c14999532fe // indirect
|
||||
github.com/getsops/sops/v3 v3.9.2 // indirect
|
||||
github.com/fxamacker/cbor/v2 v2.9.0 // indirect
|
||||
github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e // indirect
|
||||
github.com/getsops/sops/v3 v3.11.0 // indirect
|
||||
github.com/ghodss/yaml v1.0.0 // indirect
|
||||
github.com/go-errors/errors v1.4.2 // indirect
|
||||
github.com/go-gorp/gorp/v3 v3.1.0 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.0.5 // indirect
|
||||
github.com/go-logr/logr v1.4.2 // indirect
|
||||
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
|
||||
github.com/go-logr/logr v1.4.3 // indirect
|
||||
github.com/go-logr/stdr v1.2.2 // indirect
|
||||
github.com/go-openapi/analysis v0.23.0 // indirect
|
||||
github.com/go-openapi/errors v0.22.1 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.21.0 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.0 // indirect
|
||||
github.com/go-openapi/loads v0.22.0 // indirect
|
||||
github.com/go-openapi/runtime v0.28.0 // indirect
|
||||
github.com/go-openapi/spec v0.21.0 // indirect
|
||||
github.com/go-openapi/strfmt v0.23.0 // indirect
|
||||
github.com/go-openapi/swag v0.23.1 // indirect
|
||||
github.com/go-openapi/validate v0.24.0 // indirect
|
||||
github.com/go-openapi/analysis v0.24.0 // indirect
|
||||
github.com/go-openapi/errors v0.22.3 // indirect
|
||||
github.com/go-openapi/jsonpointer v0.22.1 // indirect
|
||||
github.com/go-openapi/jsonreference v0.21.2 // indirect
|
||||
github.com/go-openapi/loads v0.23.1 // indirect
|
||||
github.com/go-openapi/runtime v0.29.0 // indirect
|
||||
github.com/go-openapi/spec v0.22.0 // indirect
|
||||
github.com/go-openapi/strfmt v0.24.0 // indirect
|
||||
github.com/go-openapi/swag v0.24.1 // indirect
|
||||
github.com/go-openapi/swag/cmdutils v0.24.0 // 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.24.0 // 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/go-openapi/validate v0.25.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.4.0 // indirect
|
||||
github.com/gobwas/glob v0.2.3 // indirect
|
||||
github.com/goccy/go-yaml v1.17.1 // indirect
|
||||
github.com/godbus/dbus/v5 v5.1.0 // indirect
|
||||
github.com/gogo/protobuf v1.3.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
|
||||
github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
|
||||
github.com/google/btree v1.1.3 // indirect
|
||||
github.com/google/gnostic-models v0.6.9 // indirect
|
||||
github.com/google/gnostic-models v0.7.0 // indirect
|
||||
github.com/google/go-jsonnet v0.20.0 // indirect
|
||||
github.com/google/s2a-go v0.1.8 // indirect
|
||||
github.com/google/s2a-go v0.1.9 // indirect
|
||||
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.4 // indirect
|
||||
github.com/googleapis/enterprise-certificate-proxy v0.3.6 // indirect
|
||||
github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 // indirect
|
||||
github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.7 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.1.8 // indirect
|
||||
github.com/hashicorp/aws-sdk-go-base/v2 v2.0.0-beta.65 // indirect
|
||||
github.com/hashicorp/go-retryablehttp v0.7.8 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect
|
||||
github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect
|
||||
github.com/hashicorp/go-version v1.6.0 // indirect
|
||||
github.com/hashicorp/hcp-sdk-go v0.144.0 // indirect
|
||||
github.com/hashicorp/go-version v1.7.0 // indirect
|
||||
github.com/hashicorp/hcp-sdk-go v0.162.0 // indirect
|
||||
github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f // indirect
|
||||
github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect
|
||||
github.com/itchyny/timefmt-go v0.1.6 // indirect
|
||||
github.com/jmoiron/sqlx v1.4.0 // indirect
|
||||
github.com/josharian/intern v1.0.0 // indirect
|
||||
github.com/json-iterator/go v1.1.12 // indirect
|
||||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
|
||||
github.com/kylelemons/godebug v1.1.0 // indirect
|
||||
|
|
@ -237,14 +247,13 @@ require (
|
|||
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
|
||||
github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect
|
||||
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||
github.com/mailru/easyjson v0.9.0 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect
|
||||
github.com/mitchellh/colorstring v0.0.0-20190213212951-d06e56a500db // indirect
|
||||
github.com/moby/spdystream v0.5.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/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect
|
||||
github.com/muesli/termenv v0.15.1 // indirect
|
||||
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
|
||||
|
|
@ -260,7 +269,9 @@ require (
|
|||
github.com/rivo/uniseg v0.4.7 // indirect
|
||||
github.com/rubenv/sql-migrate v1.8.0 // indirect
|
||||
github.com/russross/blackfriday/v2 v2.1.0 // indirect
|
||||
github.com/scaleway/scaleway-sdk-go v1.0.0-beta.33 // indirect
|
||||
github.com/skratchdot/open-golang v0.0.0-20200116055534-eef842397966 // indirect
|
||||
github.com/spiffe/go-spiffe/v2 v2.5.0 // indirect
|
||||
github.com/tetratelabs/wabin v0.0.0-20230304001439-f6f874872834 // indirect
|
||||
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||
github.com/tidwall/gjson v1.18.0 // indirect
|
||||
|
|
@ -268,44 +279,48 @@ require (
|
|||
github.com/tidwall/pretty v1.2.0 // indirect
|
||||
github.com/uber/jaeger-client-go v2.30.0+incompatible // indirect
|
||||
github.com/uber/jaeger-lib v2.4.1+incompatible // indirect
|
||||
github.com/urfave/cli v1.22.16 // indirect
|
||||
github.com/urfave/cli v1.22.17 // indirect
|
||||
github.com/x448/float16 v0.8.4 // indirect
|
||||
github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect
|
||||
github.com/xlab/treeprint v1.2.0 // indirect
|
||||
github.com/yandex-cloud/go-genproto v0.29.0 // indirect
|
||||
github.com/yandex-cloud/go-sdk v0.22.0 // indirect
|
||||
github.com/zalando/go-keyring v0.2.6 // indirect
|
||||
go.mongodb.org/mongo-driver v1.14.0 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.1.0 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.29.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.58.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.58.0 // indirect
|
||||
go.opentelemetry.io/otel v1.33.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.33.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.33.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.32.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.33.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.4.0 // indirect
|
||||
github.com/zeebo/errs v1.4.0 // indirect
|
||||
go.mongodb.org/mongo-driver v1.17.4 // indirect
|
||||
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
|
||||
go.opentelemetry.io/contrib/detectors/gcp v1.36.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
|
||||
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
|
||||
go.opentelemetry.io/otel v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect
|
||||
go.opentelemetry.io/otel/trace v1.38.0 // indirect
|
||||
go.opentelemetry.io/proto/otlp v1.5.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
golang.org/x/crypto v0.39.0 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20241209162323-e6fa225c2576 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241223144023-3abc09e42ca8 // indirect
|
||||
google.golang.org/grpc/stats/opentelemetry v0.0.0-20240907200651-3ffb98b2c93a // indirect
|
||||
golang.org/x/crypto v0.42.0 // indirect
|
||||
golang.org/x/mod v0.27.0 // indirect
|
||||
golang.org/x/tools v0.36.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250818200422-3122310a409c // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20251002232023-7c0ddcbb5797 // indirect
|
||||
gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect
|
||||
gopkg.in/gookit/color.v1 v1.1.6 // indirect
|
||||
gopkg.in/inf.v0 v0.9.1 // indirect
|
||||
k8s.io/api v0.33.2 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.33.2 // indirect
|
||||
k8s.io/cli-runtime v0.33.2 // indirect
|
||||
k8s.io/client-go v0.33.2 // indirect
|
||||
k8s.io/component-base v0.33.2 // indirect
|
||||
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
k8s.io/api v0.34.1 // indirect
|
||||
k8s.io/apiextensions-apiserver v0.34.0 // indirect
|
||||
k8s.io/cli-runtime v0.34.0 // indirect
|
||||
k8s.io/client-go v0.34.1 // indirect
|
||||
k8s.io/component-base v0.34.0 // indirect
|
||||
k8s.io/klog/v2 v2.130.1 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250318190949-c8a335a9a2ff // indirect
|
||||
k8s.io/kubectl v0.33.2 // indirect
|
||||
k8s.io/utils v0.0.0-20241104100929-3ea5e8cea738 // indirect
|
||||
k8s.io/kube-openapi v0.0.0-20250710124328-f3f2b991d03b // indirect
|
||||
k8s.io/kubectl v0.34.0 // indirect
|
||||
k8s.io/utils v0.0.0-20250604170112-4c0f3b243397 // indirect
|
||||
oras.land/oras-go/v2 v2.6.0 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.19.0 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.19.0 // indirect
|
||||
sigs.k8s.io/kustomize/api v0.20.1 // indirect
|
||||
sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect
|
||||
sigs.k8s.io/randfill v1.0.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v4 v4.6.0 // indirect
|
||||
sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect
|
||||
)
|
||||
|
|
|
|||
|
|
@ -199,7 +199,7 @@ func (a *App) Diff(c DiffConfigProvider) error {
|
|||
}
|
||||
|
||||
if c.DetailedExitcode() && (len(allDiffDetectedErrs) > 0 || affectedAny) {
|
||||
// We take the first release error w/ exit status 2 (although all the defered errs should have exit status 2)
|
||||
// We take the first release error w/ exit status 2 (although all the deferred errs should have exit status 2)
|
||||
// to just let helmfile itself to exit with 2
|
||||
// See https://github.com/roboll/helmfile/issues/749
|
||||
code := 2
|
||||
|
|
@ -345,8 +345,7 @@ func (a *App) Fetch(c FetchConfigProvider) error {
|
|||
OutputDir: c.OutputDir(),
|
||||
OutputDirTemplate: c.OutputDirTemplate(),
|
||||
Concurrency: c.Concurrency(),
|
||||
}, func() {
|
||||
})
|
||||
}, func() {})
|
||||
|
||||
if prepErr != nil {
|
||||
errs = append(errs, prepErr)
|
||||
|
|
@ -681,7 +680,7 @@ func (a *App) within(dir string, do func() error) error {
|
|||
|
||||
prev, err := a.fs.Getwd()
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed getting current working direcotyr: %v", err)
|
||||
return fmt.Errorf("failed getting current working directory: %v", err)
|
||||
}
|
||||
|
||||
absDir, err := a.fs.Abs(dir)
|
||||
|
|
@ -785,7 +784,7 @@ func createHelmKey(bin, kubectx string) helmKey {
|
|||
//
|
||||
// This is currently used for running all the helm commands for reconciling releases. But this may change in the future
|
||||
// once we enable each release to have its own helm binary/version.
|
||||
func (a *App) getHelm(st *state.HelmState) helmexec.Interface {
|
||||
func (a *App) getHelm(st *state.HelmState) (helmexec.Interface, error) {
|
||||
a.helmsMutex.Lock()
|
||||
defer a.helmsMutex.Unlock()
|
||||
|
||||
|
|
@ -800,14 +799,18 @@ func (a *App) getHelm(st *state.HelmState) helmexec.Interface {
|
|||
key := createHelmKey(bin, kubectx)
|
||||
|
||||
if _, ok := a.helms[key]; !ok {
|
||||
a.helms[key] = helmexec.New(bin, helmexec.HelmExecOptions{EnableLiveOutput: a.EnableLiveOutput, DisableForceUpdate: a.DisableForceUpdate}, a.Logger, kubeconfig, kubectx, &helmexec.ShellRunner{
|
||||
exec, err := helmexec.New(bin, helmexec.HelmExecOptions{EnableLiveOutput: a.EnableLiveOutput, DisableForceUpdate: a.DisableForceUpdate}, a.Logger, kubeconfig, kubectx, &helmexec.ShellRunner{
|
||||
Logger: a.Logger,
|
||||
Ctx: a.ctx,
|
||||
StripArgsValuesOnExitError: a.StripArgsValuesOnExitError,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
a.helms[key] = exec
|
||||
}
|
||||
|
||||
return a.helms[key]
|
||||
return a.helms[key], nil
|
||||
}
|
||||
|
||||
func (a *App) visitStates(fileOrDir string, defOpts LoadOpts, converge func(*state.HelmState) (bool, []error)) error {
|
||||
|
|
@ -959,7 +962,10 @@ var (
|
|||
func (a *App) ForEachState(do func(*Run) (bool, []error), includeTransitiveNeeds bool, o ...LoadOption) error {
|
||||
ctx := NewContext()
|
||||
err := a.visitStatesWithSelectorsAndRemoteSupport(a.FileOrDir, func(st *state.HelmState) (bool, []error) {
|
||||
helm := a.getHelm(st)
|
||||
helm, err := a.getHelm(st)
|
||||
if err != nil {
|
||||
return false, []error{err}
|
||||
}
|
||||
|
||||
run, err := NewRun(st, helm, ctx)
|
||||
if err != nil {
|
||||
|
|
|
|||
|
|
@ -415,4 +415,28 @@ releases:
|
|||
},
|
||||
})
|
||||
})
|
||||
|
||||
t.Run("show diff on changed selected release with reinstall", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
helmfile: `
|
||||
releases:
|
||||
- name: a
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
updateStrategy: reinstallIfForbidden
|
||||
- name: b
|
||||
chart: incubator/raw
|
||||
namespace: default
|
||||
`,
|
||||
selectors: []string{"name=a"},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^a$", Flags: listFlags("default", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
foo 4 Fri Nov 1 08:40:07 2019 DEPLOYED raw-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
diffed: []exectest.Release{
|
||||
{Name: "a", Flags: []string{"--kube-context", "default", "--namespace", "default", "--reset-values"}},
|
||||
},
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -411,7 +411,7 @@ releases:
|
|||
})
|
||||
}
|
||||
|
||||
t.Run("fail due to unknown field with gopkg.in/yaml.v3", func(t *testing.T) {
|
||||
t.Run("fail due to unknown field with go.yaml.in/yaml/v3", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
GoYamlV3: true,
|
||||
error: `in ./helmfile.yaml: failed to read helmfile.yaml: reading document at index 1. Started seeing this since Helmfile v1? Add the .gotmpl file extension: yaml: unmarshal errors:
|
||||
|
|
@ -419,7 +419,7 @@ releases:
|
|||
})
|
||||
})
|
||||
|
||||
t.Run("fail due to unknown field with gopkg.in/yaml.v2", func(t *testing.T) {
|
||||
t.Run("fail due to unknown field with go.yaml.in/yaml/v2", func(t *testing.T) {
|
||||
check(t, testcase{
|
||||
GoYamlV3: false,
|
||||
error: `in ./helmfile.yaml: failed to read helmfile.yaml: reading document at index 1. Started seeing this since Helmfile v1? Add the .gotmpl file extension: yaml: unmarshal errors:
|
||||
|
|
|
|||
|
|
@ -220,6 +220,97 @@ releases:
|
|||
}
|
||||
}
|
||||
|
||||
func TestUpdateStrategyParamValidation(t *testing.T) {
|
||||
cases := []struct {
|
||||
files map[string]string
|
||||
updateStrategy string
|
||||
isValid bool
|
||||
}{
|
||||
{map[string]string{
|
||||
"/path/to/helmfile.yaml": `releases:
|
||||
- name: zipkin
|
||||
chart: stable/zipkin
|
||||
updateStrategy: reinstallIfForbidden
|
||||
`},
|
||||
"reinstallIfForbidden",
|
||||
true},
|
||||
{map[string]string{
|
||||
"/path/to/helmfile.yaml": `releases:
|
||||
- name: zipkin
|
||||
chart: stable/zipkin
|
||||
updateStrategy: reinstallIfForbidden
|
||||
`},
|
||||
"reinstallIfForbidden",
|
||||
true},
|
||||
{map[string]string{
|
||||
"/path/to/helmfile.yaml": `releases:
|
||||
- name: zipkin
|
||||
chart: stable/zipkin
|
||||
updateStrategy:
|
||||
`},
|
||||
"",
|
||||
true},
|
||||
{map[string]string{
|
||||
"/path/to/helmfile.yaml": `releases:
|
||||
- name: zipkin
|
||||
chart: stable/zipkin
|
||||
updateStrategy: foo
|
||||
`},
|
||||
"foo",
|
||||
false},
|
||||
{map[string]string{
|
||||
"/path/to/helmfile.yaml": `releases:
|
||||
- name: zipkin
|
||||
chart: stable/zipkin
|
||||
updateStrategy: reinstal
|
||||
`},
|
||||
"reinstal",
|
||||
false},
|
||||
{map[string]string{
|
||||
"/path/to/helmfile.yaml": `releases:
|
||||
- name: zipkin
|
||||
chart: stable/zipkin
|
||||
updateStrategy: reinstall1
|
||||
`},
|
||||
"reinstall1",
|
||||
false},
|
||||
}
|
||||
|
||||
for idx, c := range cases {
|
||||
fs := testhelper.NewTestFs(c.files)
|
||||
app := &App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Namespace: "",
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
}
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
||||
app = injectFs(app, fs)
|
||||
|
||||
err := app.ForEachState(
|
||||
Noop,
|
||||
false,
|
||||
SetFilter(true),
|
||||
)
|
||||
|
||||
if c.isValid && err != nil {
|
||||
t.Errorf("[case: %d] Unexpected error for valid case: %v", idx, err)
|
||||
} else if !c.isValid {
|
||||
var invalidUpdateStrategy state.InvalidUpdateStrategyError
|
||||
invalidUpdateStrategy.UpdateStrategy = c.updateStrategy
|
||||
if err == nil {
|
||||
t.Errorf("[case: %d] Expected error for invalid case", idx)
|
||||
} else if !strings.Contains(err.Error(), invalidUpdateStrategy.Error()) {
|
||||
t.Errorf("[case: %d] Unexpected error returned for invalid case\ngot: %v\nexpected underlying error: %s", idx, err, invalidUpdateStrategy.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestVisitDesiredStatesWithReleasesFiltered_Issue1008_MissingNonDefaultEnvInBase(t *testing.T) {
|
||||
files := map[string]string{
|
||||
"/path/to/base.yaml": `
|
||||
|
|
@ -2492,9 +2583,12 @@ func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string
|
|||
return []byte{}, nil
|
||||
}
|
||||
|
||||
func MockExecer(logger *zap.SugaredLogger, kubeContext string) helmexec.Interface {
|
||||
execer := helmexec.New("helm", helmexec.HelmExecOptions{}, logger, "", kubeContext, &mockRunner{})
|
||||
return execer
|
||||
func MockExecer(logger *zap.SugaredLogger, kubeContext string) (helmexec.Interface, error) {
|
||||
execer, err := helmexec.New("helm", helmexec.HelmExecOptions{}, logger, "", kubeContext, &mockRunner{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return execer, nil
|
||||
}
|
||||
|
||||
// mocking helmexec.Interface
|
||||
|
|
@ -3073,6 +3167,97 @@ baz 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart3-3.1.0 3.1.0 defau
|
|||
concurrency: 1,
|
||||
},
|
||||
//
|
||||
// install with upgrade with reinstallIfForbidden
|
||||
//
|
||||
{
|
||||
name: "install-with-upgrade-with-reinstallIfForbidden",
|
||||
loc: location(),
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: baz
|
||||
chart: stable/mychart3
|
||||
disableValidationOnInstall: true
|
||||
updateStrategy: reinstallIfForbidden
|
||||
- name: foo
|
||||
chart: stable/mychart1
|
||||
disableValidationOnInstall: true
|
||||
needs:
|
||||
- bar
|
||||
- name: bar
|
||||
chart: stable/mychart2
|
||||
disableValidation: true
|
||||
updateStrategy: reinstallIfForbidden
|
||||
`,
|
||||
},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "baz", Chart: "stable/mychart3", Flags: "--kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "foo", Chart: "stable/mychart1", Flags: "--disable-validation --kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "bar", Chart: "stable/mychart2", Flags: "--disable-validation --kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^foo$", Flags: listFlags("", "default")}: ``,
|
||||
{Filter: "^bar$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^baz$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
baz 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart3-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
upgraded: []exectest.Release{
|
||||
{Name: "baz", Flags: []string{"--kube-context", "default"}},
|
||||
{Name: "bar", Flags: []string{"--kube-context", "default"}},
|
||||
{Name: "foo", Flags: []string{"--kube-context", "default"}},
|
||||
},
|
||||
deleted: []exectest.Release{},
|
||||
concurrency: 1,
|
||||
},
|
||||
//
|
||||
// install with upgrade and --skip-diff-on-install with reinstallIfForbidden
|
||||
//
|
||||
{
|
||||
name: "install-with-upgrade-with-skip-diff-on-install-with-reinstallIfForbidden",
|
||||
loc: location(),
|
||||
skipDiffOnInstall: true,
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: baz
|
||||
chart: stable/mychart3
|
||||
disableValidationOnInstall: true
|
||||
updateStrategy: reinstallIfForbidden
|
||||
- name: foo
|
||||
chart: stable/mychart1
|
||||
disableValidationOnInstall: true
|
||||
needs:
|
||||
- bar
|
||||
- name: bar
|
||||
chart: stable/mychart2
|
||||
disableValidation: true
|
||||
updateStrategy: reinstallIfForbidden
|
||||
`,
|
||||
},
|
||||
diffs: map[exectest.DiffKey]error{
|
||||
{Name: "baz", Chart: "stable/mychart3", Flags: "--kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
{Name: "bar", Chart: "stable/mychart2", Flags: "--disable-validation --kube-context default --reset-values --detailed-exitcode"}: helmexec.ExitError{Code: 2},
|
||||
},
|
||||
lists: map[exectest.ListKey]string{
|
||||
{Filter: "^foo$", Flags: listFlags("", "default")}: ``,
|
||||
{Filter: "^bar$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
bar 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart2-3.1.0 3.1.0 default
|
||||
`,
|
||||
{Filter: "^baz$", Flags: listFlags("", "default")}: `NAME REVISION UPDATED STATUS CHART APP VERSION NAMESPACE
|
||||
baz 4 Fri Nov 1 08:40:07 2019 DEPLOYED mychart3-3.1.0 3.1.0 default
|
||||
`,
|
||||
},
|
||||
upgraded: []exectest.Release{
|
||||
{Name: "baz", Flags: []string{"--kube-context", "default"}},
|
||||
{Name: "bar", Flags: []string{"--kube-context", "default"}},
|
||||
{Name: "foo", Flags: []string{"--kube-context", "default"}},
|
||||
},
|
||||
concurrency: 1,
|
||||
},
|
||||
//
|
||||
// upgrades
|
||||
//
|
||||
{
|
||||
|
|
@ -3769,7 +3954,7 @@ releases:
|
|||
}
|
||||
for flagIdx := range wantDeletes[relIdx].Flags {
|
||||
if wantDeletes[relIdx].Flags[flagIdx] != helm.Deleted[relIdx].Flags[flagIdx] {
|
||||
t.Errorf("releaes[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
|
||||
t.Errorf("releases[%d].flags[%d]: got %v, want %v", relIdx, flagIdx, helm.Deleted[relIdx].Flags[flagIdx], wantDeletes[relIdx].Flags[flagIdx])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -4161,21 +4346,21 @@ releases:
|
|||
}
|
||||
|
||||
func TestSetValuesTemplate(t *testing.T) {
|
||||
t.Run("with gopkg.in/yaml.v3", func(t *testing.T) {
|
||||
t.Run("with go.yaml.in/yaml/v3", func(t *testing.T) {
|
||||
testSetValuesTemplate(t, true)
|
||||
})
|
||||
|
||||
t.Run("with gopkg.in/yaml.v2", func(t *testing.T) {
|
||||
t.Run("with go.yaml.in/yaml/v2", func(t *testing.T) {
|
||||
testSetValuesTemplate(t, false)
|
||||
})
|
||||
}
|
||||
|
||||
func TestSetStringValuesTemplate(t *testing.T) {
|
||||
t.Run("with gopkg.in/yaml.v3", func(t *testing.T) {
|
||||
t.Run("with go.yaml.in/yaml/v3", func(t *testing.T) {
|
||||
testSetStringValuesTemplate(t, true)
|
||||
})
|
||||
|
||||
t.Run("with gopkg.in/yaml.v2", func(t *testing.T) {
|
||||
t.Run("with go.yaml.in/yaml/v2", func(t *testing.T) {
|
||||
testSetStringValuesTemplate(t, false)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
|
||||
"dario.cat/mergo"
|
||||
"github.com/helmfile/vals"
|
||||
|
|
@ -34,7 +35,7 @@ type desiredStateLoader struct {
|
|||
chart string
|
||||
fs *filesystem.FileSystem
|
||||
|
||||
getHelm func(*state.HelmState) helmexec.Interface
|
||||
getHelm func(*state.HelmState) (helmexec.Interface, error)
|
||||
|
||||
remote *remote.Remote
|
||||
logger *zap.SugaredLogger
|
||||
|
|
@ -285,6 +286,18 @@ func (ld *desiredStateLoader) load(env, overrodeEnv *environment.Environment, ba
|
|||
}
|
||||
}
|
||||
|
||||
// Validate updateStrategy value if set in the releases
|
||||
for i := range finalState.Releases {
|
||||
if finalState.Releases[i].UpdateStrategy != "" {
|
||||
if !slices.Contains(state.ValidUpdateStrategyValues, finalState.Releases[i].UpdateStrategy) {
|
||||
return nil, &state.StateLoadError{
|
||||
Msg: fmt.Sprintf("failed to read %s", finalState.FilePath),
|
||||
Cause: &state.InvalidUpdateStrategyError{UpdateStrategy: finalState.Releases[i].UpdateStrategy},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
finalState.OrginReleases = finalState.Releases
|
||||
return finalState, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
HelmRequiredVersion = "v3.17.3"
|
||||
HelmDiffRecommendedVersion = "v3.12.3"
|
||||
HelmRecommendedVersion = "v3.18.4"
|
||||
HelmRequiredVersion = "v3.18.6"
|
||||
HelmDiffRecommendedVersion = "v3.13.1"
|
||||
HelmRecommendedVersion = "v3.19.0"
|
||||
HelmSecretsRecommendedVersion = "v4.6.5"
|
||||
HelmGitRecommendedVersion = "v1.3.0"
|
||||
HelmS3RecommendedVersion = "v0.16.3"
|
||||
|
|
@ -163,7 +163,10 @@ func (h *HelmfileInit) WhetherContinue(ask string) error {
|
|||
|
||||
func (h *HelmfileInit) CheckHelmPlugins() error {
|
||||
settings := cli.New()
|
||||
helm := helmexec.New(h.helmBinary, helmexec.HelmExecOptions{}, h.logger, "", "", h.runner)
|
||||
helm, err := helmexec.New(h.helmBinary, helmexec.HelmExecOptions{}, h.logger, "", "", h.runner)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, p := range helmPlugins {
|
||||
pluginVersion, err := helmexec.GetPluginVersion(p.name, settings.PluginsDirectory)
|
||||
if err != nil {
|
||||
|
|
|
|||
11
pkg/app/testdata/app_diff_test/show_diff_on_changed_selected_release_with_reinstall
vendored
Normal file
11
pkg/app/testdata/app_diff_test/show_diff_on_changed_selected_release_with_reinstall
vendored
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
processing file "helmfile.yaml" in directory "."
|
||||
changing working directory to "/path/to"
|
||||
merged environment: &{default map[] map[]}
|
||||
1 release(s) matching name=a found in helmfile.yaml
|
||||
|
||||
processing 1 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default/default/a
|
||||
|
||||
processing releases in group 1/1: default/default/a
|
||||
changing working directory back to "/path/to"
|
||||
35
pkg/app/testdata/testapply/install-with-upgrade-with-reinstallifforbidden/log
vendored
Normal file
35
pkg/app/testdata/testapply/install-with-upgrade-with-reinstallifforbidden/log
vendored
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
processing file "helmfile.yaml" in directory "."
|
||||
changing working directory to "/path/to"
|
||||
merged environment: &{default map[] map[]}
|
||||
3 release(s) found in helmfile.yaml
|
||||
|
||||
Affected releases are:
|
||||
bar (stable/mychart2) UPDATED
|
||||
baz (stable/mychart3) UPDATED
|
||||
foo (stable/mychart1) UPDATED
|
||||
|
||||
invoking preapply hooks for 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default//foo
|
||||
2 default//baz, default//bar
|
||||
|
||||
invoking preapply hooks for releases in group 1/2: default//foo
|
||||
invoking preapply hooks for releases in group 2/2: default//baz, default//bar
|
||||
processing 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default//baz, default//bar
|
||||
2 default//foo
|
||||
|
||||
processing releases in group 1/2: default//baz, default//bar
|
||||
update strategy - sync success
|
||||
update strategy - sync success
|
||||
processing releases in group 2/2: default//foo
|
||||
getting deployed release version failed: Failed to get the version for: mychart1
|
||||
|
||||
UPDATED RELEASES:
|
||||
NAME NAMESPACE CHART VERSION DURATION
|
||||
baz stable/mychart3 3.1.0 0s
|
||||
bar stable/mychart2 3.1.0 0s
|
||||
foo stable/mychart1 0s
|
||||
|
||||
changing working directory back to "/path/to"
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
processing file "helmfile.yaml" in directory "."
|
||||
changing working directory to "/path/to"
|
||||
merged environment: &{default map[] map[]}
|
||||
3 release(s) found in helmfile.yaml
|
||||
|
||||
Affected releases are:
|
||||
bar (stable/mychart2) UPDATED
|
||||
baz (stable/mychart3) UPDATED
|
||||
foo (stable/mychart1) UPDATED
|
||||
|
||||
invoking preapply hooks for 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default//foo
|
||||
2 default//baz, default//bar
|
||||
|
||||
invoking preapply hooks for releases in group 1/2: default//foo
|
||||
invoking preapply hooks for releases in group 2/2: default//baz, default//bar
|
||||
processing 2 groups of releases in this order:
|
||||
GROUP RELEASES
|
||||
1 default//baz, default//bar
|
||||
2 default//foo
|
||||
|
||||
processing releases in group 1/2: default//baz, default//bar
|
||||
update strategy - sync success
|
||||
update strategy - sync success
|
||||
processing releases in group 2/2: default//foo
|
||||
getting deployed release version failed: Failed to get the version for: mychart1
|
||||
|
||||
UPDATED RELEASES:
|
||||
NAME NAMESPACE CHART VERSION DURATION
|
||||
baz stable/mychart3 3.1.0 0s
|
||||
bar stable/mychart2 3.1.0 0s
|
||||
foo stable/mychart1 0s
|
||||
|
||||
changing working directory back to "/path/to"
|
||||
|
|
@ -24,6 +24,8 @@ type SyncOptions struct {
|
|||
WaitRetries int
|
||||
// WaitForJobs is the wait for jobs flag
|
||||
WaitForJobs bool
|
||||
// Timeout is the timeout flag in seconds
|
||||
Timeout int
|
||||
// ReuseValues is true if the helm command should reuse the values
|
||||
ReuseValues bool
|
||||
// ResetValues is true if helm command should reset values to charts' default
|
||||
|
|
@ -124,6 +126,11 @@ func (t *SyncImpl) WaitForJobs() bool {
|
|||
return t.SyncOptions.WaitForJobs
|
||||
}
|
||||
|
||||
// Timeout returns the timeout
|
||||
func (t *SyncImpl) Timeout() int {
|
||||
return t.SyncOptions.Timeout
|
||||
}
|
||||
|
||||
// ReuseValues returns the ReuseValues.
|
||||
func (t *SyncImpl) ReuseValues() bool {
|
||||
if !t.ResetValues() {
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ type Release struct {
|
|||
|
||||
type Affected struct {
|
||||
Upgraded []*Release
|
||||
Reinstalled []*Release
|
||||
Deleted []*Release
|
||||
Failed []*Release
|
||||
}
|
||||
|
|
@ -107,7 +108,24 @@ func (helm *Helm) RegistryLogin(name, username, password, caFile, certFile, keyF
|
|||
return nil
|
||||
}
|
||||
func (helm *Helm) SyncRelease(context helmexec.HelmContext, name, chart, namespace string, flags ...string) error {
|
||||
if strings.Contains(name, "error") {
|
||||
if strings.Contains(name, "forbidden") {
|
||||
releaseExists := false
|
||||
for _, release := range helm.Releases {
|
||||
if release.Name == name {
|
||||
releaseExists = true
|
||||
}
|
||||
}
|
||||
releaseDeleted := false
|
||||
for _, release := range helm.Deleted {
|
||||
if release.Name == name {
|
||||
releaseDeleted = true
|
||||
}
|
||||
}
|
||||
// Only fail if the release is present in the helm.Releases to simulate a forbidden update if it exists
|
||||
if releaseExists && !releaseDeleted {
|
||||
return fmt.Errorf("cannot patch %q with kind StatefulSet: StatefulSet.apps %q is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'ordinals', 'template', 'updateStrategy', 'persistentVolumeClaimRetentionPolicy' and 'minReadySeconds' are forbidden", name, name)
|
||||
}
|
||||
} else if strings.Contains(name, "error") {
|
||||
return errors.New("error")
|
||||
}
|
||||
helm.sync(helm.ReleasesMutex, func() {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
copyDir "github.com/otiai10/copy"
|
||||
)
|
||||
|
||||
type fileStat struct {
|
||||
|
|
@ -36,6 +38,7 @@ type FileSystem struct {
|
|||
Chdir func(string) error
|
||||
Abs func(string) (string, error)
|
||||
EvalSymlinks func(string) (string, error)
|
||||
CopyDir func(src, dst string) error
|
||||
}
|
||||
|
||||
func DefaultFileSystem() *FileSystem {
|
||||
|
|
@ -55,6 +58,7 @@ func DefaultFileSystem() *FileSystem {
|
|||
dfs.DirectoryExistsAt = dfs.directoryExistsDefault
|
||||
dfs.FileExists = dfs.fileExistsDefault
|
||||
dfs.Abs = dfs.absDefault
|
||||
dfs.CopyDir = dfs.copyDirDefault
|
||||
return &dfs
|
||||
}
|
||||
|
||||
|
|
@ -100,6 +104,9 @@ func FromFileSystem(params FileSystem) *FileSystem {
|
|||
if params.Dir != nil {
|
||||
dfs.Dir = params.Dir
|
||||
}
|
||||
if params.CopyDir != nil {
|
||||
dfs.CopyDir = params.CopyDir
|
||||
}
|
||||
return dfs
|
||||
}
|
||||
|
||||
|
|
@ -180,3 +187,8 @@ func (filesystem *FileSystem) absDefault(path string) (string, error) {
|
|||
}
|
||||
return filepath.Abs(path)
|
||||
}
|
||||
|
||||
// copyDirDefault recursively copies a directory tree, preserving permissions.
|
||||
func (filesystem *FileSystem) copyDirDefault(src string, dst string) error {
|
||||
return copyDir.Copy(src, dst, copyDir.Options{Sync: true})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,7 +69,13 @@ func parseHelmVersion(versionStr string) (*semver.Version, error) {
|
|||
return nil, fmt.Errorf("empty helm version")
|
||||
}
|
||||
|
||||
v, err := chartify.FindSemVerInfo(versionStr)
|
||||
// Check if version string starts with "v", if not add it
|
||||
processedVersion := strings.TrimSpace(versionStr)
|
||||
if !strings.HasPrefix(processedVersion, "v") {
|
||||
processedVersion = "v" + processedVersion
|
||||
}
|
||||
|
||||
v, err := chartify.FindSemVerInfo(processedVersion)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error find helm srmver version '%s': %w", versionStr, err)
|
||||
|
|
@ -116,11 +122,10 @@ func redactedURL(chart string) string {
|
|||
}
|
||||
|
||||
// New for running helm commands
|
||||
func New(helmBinary string, options HelmExecOptions, logger *zap.SugaredLogger, kubeconfig string, kubeContext string, runner Runner) *execer {
|
||||
// TODO: proper error handling
|
||||
func New(helmBinary string, options HelmExecOptions, logger *zap.SugaredLogger, kubeconfig string, kubeContext string, runner Runner) (*execer, error) {
|
||||
version, err := GetHelmVersion(helmBinary, runner)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if version.Prerelease() != "" {
|
||||
|
|
@ -137,7 +142,7 @@ func New(helmBinary string, options HelmExecOptions, logger *zap.SugaredLogger,
|
|||
kubeContext: kubeContext,
|
||||
runner: runner,
|
||||
decryptedSecrets: make(map[string]*decryptedSecret),
|
||||
}
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (helm *execer) SetExtraArgs(args ...string) {
|
||||
|
|
|
|||
|
|
@ -36,16 +36,22 @@ func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string
|
|||
return mock.output, mock.err
|
||||
}
|
||||
|
||||
func MockExecer(logger *zap.SugaredLogger, kubeconfig, kubeContext string) *execer {
|
||||
execer := New("helm", HelmExecOptions{}, logger, kubeconfig, kubeContext, &mockRunner{})
|
||||
return execer
|
||||
func MockExecer(logger *zap.SugaredLogger, kubeconfig, kubeContext string) (*execer, error) {
|
||||
execer, err := New("helm", HelmExecOptions{}, logger, kubeconfig, kubeContext, &mockRunner{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return execer, nil
|
||||
}
|
||||
|
||||
// Test methods
|
||||
|
||||
func TestNewHelmExec(t *testing.T) {
|
||||
buffer := bytes.NewBufferString("something")
|
||||
helm := MockExecer(NewLogger(buffer, "debug"), "config", "dev")
|
||||
helm, err := MockExecer(NewLogger(buffer, "debug"), "config", "dev")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if helm.kubeContext != "dev" {
|
||||
t.Error("helmexec.New() - kubeContext")
|
||||
}
|
||||
|
|
@ -58,7 +64,10 @@ func TestNewHelmExec(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_SetExtraArgs(t *testing.T) {
|
||||
helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev")
|
||||
helm, err := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
helm.SetExtraArgs()
|
||||
if len(helm.extra) != 0 {
|
||||
t.Error("helmexec.SetExtraArgs() - passing no arguments should not change extra field")
|
||||
|
|
@ -74,7 +83,10 @@ func Test_SetExtraArgs(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_SetHelmBinary(t *testing.T) {
|
||||
helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev")
|
||||
helm, err := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if helm.helmBinary != "helm" {
|
||||
t.Error("helmexec.command - default command is not helm")
|
||||
}
|
||||
|
|
@ -85,7 +97,10 @@ func Test_SetHelmBinary(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_SetEnableLiveOutput(t *testing.T) {
|
||||
helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev")
|
||||
helm, err := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if helm.options.EnableLiveOutput {
|
||||
t.Error("helmexec.options.EnableLiveOutput should not be enabled by default")
|
||||
}
|
||||
|
|
@ -96,7 +111,10 @@ func Test_SetEnableLiveOutput(t *testing.T) {
|
|||
}
|
||||
|
||||
func Test_SetDisableForceUpdate(t *testing.T) {
|
||||
helm := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev")
|
||||
helm, err := MockExecer(NewLogger(os.Stdout, "info"), "config", "dev")
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
if helm.options.DisableForceUpdate {
|
||||
t.Error("helmexec.options.ForceUpdate should not be enabled by default")
|
||||
}
|
||||
|
|
@ -155,11 +173,14 @@ exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.e
|
|||
func Test_AddRepo(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "config", "dev")
|
||||
helm, err := MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
// Test case with certfile and keyfile
|
||||
buffer.Reset()
|
||||
err := helm.AddRepo("myRepo", "https://repo.example.com/", "", "cert.pem", "key.pem", "", "", "", false, false)
|
||||
err = helm.AddRepo("myRepo", "https://repo.example.com/", "", "cert.pem", "key.pem", "", "", "", false, false)
|
||||
expected := `Adding repo myRepo https://repo.example.com/
|
||||
exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.example.com/ --cert-file cert.pem --key-file key.pem
|
||||
`
|
||||
|
|
@ -292,8 +313,11 @@ exec: helm --kubeconfig config --kube-context dev repo add myRepo https://repo.e
|
|||
func Test_UpdateRepo(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "config", "dev")
|
||||
err := helm.UpdateRepo()
|
||||
helm, err := MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = helm.UpdateRepo()
|
||||
expected := `Updating repo
|
||||
exec: helm --kubeconfig config --kube-context dev repo update
|
||||
`
|
||||
|
|
@ -346,8 +370,11 @@ exec: helm --kubeconfig config --kube-context dev registry login repo.example.co
|
|||
func Test_SyncRelease(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "config", "dev")
|
||||
err := helm.SyncRelease(HelmContext{}, "release", "chart", "default", "--timeout 10", "--wait", "--wait-for-jobs")
|
||||
helm, err := MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = helm.SyncRelease(HelmContext{}, "release", "chart", "default", "--timeout 10", "--wait", "--wait-for-jobs")
|
||||
expected := `Upgrading release=release, chart=chart, namespace=default
|
||||
exec: helm --kubeconfig config --kube-context dev upgrade --install release chart --timeout 10 --wait --wait-for-jobs --history-max 0
|
||||
`
|
||||
|
|
@ -386,8 +413,11 @@ exec: helm --kubeconfig config --kube-context dev upgrade --install release http
|
|||
func Test_UpdateDeps(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "config", "dev")
|
||||
err := helm.UpdateDeps("./chart/foo")
|
||||
helm, err := MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = helm.UpdateDeps("./chart/foo")
|
||||
expected := `Updating dependency ./chart/foo
|
||||
exec: helm --kubeconfig config --kube-context dev dependency update ./chart/foo
|
||||
`
|
||||
|
|
@ -416,8 +446,11 @@ func Test_BuildDeps(t *testing.T) {
|
|||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm3Runner := mockRunner{output: []byte("v3.2.4+ge29ce2a")}
|
||||
helm := New("helm", HelmExecOptions{}, logger, "config", "dev", &helm3Runner)
|
||||
err := helm.BuildDeps("foo", "./chart/foo", []string{"--skip-refresh"}...)
|
||||
helm, err := New("helm", HelmExecOptions{}, logger, "config", "dev", &helm3Runner)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = helm.BuildDeps("foo", "./chart/foo", []string{"--skip-refresh"}...)
|
||||
expected := `Building dependency release=foo, chart=./chart/foo
|
||||
exec: helm --kubeconfig config --kube-context dev dependency build ./chart/foo --skip-refresh
|
||||
v3.2.4+ge29ce2a
|
||||
|
|
@ -458,7 +491,10 @@ v3.2.4+ge29ce2a
|
|||
|
||||
buffer.Reset()
|
||||
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94")}
|
||||
helm = New("helm", HelmExecOptions{}, logger, "config", "dev", &helm2Runner)
|
||||
helm, err = New("helm", HelmExecOptions{}, logger, "config", "dev", &helm2Runner)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = helm.BuildDeps("foo", "./chart/foo")
|
||||
expected = `Building dependency release=foo, chart=./chart/foo
|
||||
exec: helm --kubeconfig config --kube-context dev dependency build ./chart/foo
|
||||
|
|
@ -484,14 +520,17 @@ func Test_DecryptSecret(t *testing.T) {
|
|||
}()
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "config", "dev")
|
||||
helm, err := MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
tmpFilePath := "path/to/temp/file"
|
||||
helm.writeTempFile = func(content []byte) (string, error) {
|
||||
return tmpFilePath, nil
|
||||
}
|
||||
|
||||
_, err := helm.DecryptSecret(HelmContext{}, "secretName")
|
||||
_, err = helm.DecryptSecret(HelmContext{}, "secretName")
|
||||
if err != nil {
|
||||
t.Errorf("Error: %v", err)
|
||||
}
|
||||
|
|
@ -533,7 +572,10 @@ func Test_DecryptSecretWithGotmpl(t *testing.T) {
|
|||
}()
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "config", "dev")
|
||||
helm, err := MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
tmpFilePath := "path/to/temp/file"
|
||||
helm.writeTempFile = func(content []byte) (string, error) {
|
||||
|
|
@ -541,7 +583,7 @@ func Test_DecryptSecretWithGotmpl(t *testing.T) {
|
|||
}
|
||||
|
||||
secretName := "secretName.yaml.gotmpl"
|
||||
_, err := helm.DecryptSecret(HelmContext{}, secretName)
|
||||
_, err = helm.DecryptSecret(HelmContext{}, secretName)
|
||||
if err != nil {
|
||||
t.Errorf("Error: %v", err)
|
||||
}
|
||||
|
|
@ -566,8 +608,11 @@ Decrypted %s/secretName.yaml.gotmpl into %s
|
|||
func Test_DiffRelease(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "config", "dev")
|
||||
err := helm.DiffRelease(HelmContext{}, "release", "chart", "default", false, "--timeout 10", "--wait", "--wait-for-jobs")
|
||||
helm, err := MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = helm.DiffRelease(HelmContext{}, "release", "chart", "default", false, "--timeout 10", "--wait", "--wait-for-jobs")
|
||||
expected := `Comparing release=release, chart=chart, namespace=default
|
||||
|
||||
exec: helm --kubeconfig config --kube-context dev diff upgrade --allow-unreleased release chart --timeout 10 --wait --wait-for-jobs
|
||||
|
|
@ -609,8 +654,11 @@ exec: helm --kubeconfig config --kube-context dev diff upgrade --allow-unrelease
|
|||
func Test_DeleteRelease(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "config", "dev")
|
||||
err := helm.DeleteRelease(HelmContext{}, "release")
|
||||
helm, err := MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = helm.DeleteRelease(HelmContext{}, "release")
|
||||
expected := `Deleting release
|
||||
exec: helm --kubeconfig config --kube-context dev delete release
|
||||
`
|
||||
|
|
@ -624,8 +672,11 @@ exec: helm --kubeconfig config --kube-context dev delete release
|
|||
func Test_DeleteRelease_Flags(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "config", "dev")
|
||||
err := helm.DeleteRelease(HelmContext{}, "release", "--purge")
|
||||
helm, err := MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = helm.DeleteRelease(HelmContext{}, "release", "--purge")
|
||||
expected := `Deleting release
|
||||
exec: helm --kubeconfig config --kube-context dev delete release --purge
|
||||
`
|
||||
|
|
@ -640,8 +691,11 @@ exec: helm --kubeconfig config --kube-context dev delete release --purge
|
|||
func Test_TestRelease(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "config", "dev")
|
||||
err := helm.TestRelease(HelmContext{}, "release")
|
||||
helm, err := MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = helm.TestRelease(HelmContext{}, "release")
|
||||
expected := `Testing release
|
||||
exec: helm --kubeconfig config --kube-context dev test release
|
||||
`
|
||||
|
|
@ -655,8 +709,11 @@ exec: helm --kubeconfig config --kube-context dev test release
|
|||
func Test_TestRelease_Flags(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "config", "dev")
|
||||
err := helm.TestRelease(HelmContext{}, "release", "--cleanup", "--timeout", "60")
|
||||
helm, err := MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = helm.TestRelease(HelmContext{}, "release", "--cleanup", "--timeout", "60")
|
||||
expected := `Testing release
|
||||
exec: helm --kubeconfig config --kube-context dev test release --cleanup --timeout 60
|
||||
`
|
||||
|
|
@ -671,8 +728,11 @@ exec: helm --kubeconfig config --kube-context dev test release --cleanup --timeo
|
|||
func Test_ReleaseStatus(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "config", "dev")
|
||||
err := helm.ReleaseStatus(HelmContext{}, "myRelease")
|
||||
helm, err := MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = helm.ReleaseStatus(HelmContext{}, "myRelease")
|
||||
expected := `Getting status myRelease
|
||||
exec: helm --kubeconfig config --kube-context dev status myRelease
|
||||
`
|
||||
|
|
@ -687,9 +747,12 @@ exec: helm --kubeconfig config --kube-context dev status myRelease
|
|||
func Test_exec(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "", "")
|
||||
helm, err := MockExecer(logger, "", "")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
env := map[string]string{}
|
||||
_, err := helm.exec([]string{"version"}, env, nil)
|
||||
_, err = helm.exec([]string{"version"}, env, nil)
|
||||
expected := `exec: helm version
|
||||
`
|
||||
if err != nil {
|
||||
|
|
@ -699,14 +762,20 @@ func Test_exec(t *testing.T) {
|
|||
t.Errorf("helmexec.exec()\nactual = %v\nexpect = %v", buffer.String(), expected)
|
||||
}
|
||||
|
||||
helm = MockExecer(logger, "config", "dev")
|
||||
helm, err = MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
ret, _ := helm.exec([]string{"diff"}, env, nil)
|
||||
if len(ret) != 0 {
|
||||
t.Error("helmexec.exec() - expected empty return value")
|
||||
}
|
||||
|
||||
buffer.Reset()
|
||||
helm = MockExecer(logger, "config", "dev")
|
||||
helm, err = MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
_, err = helm.exec([]string{"diff", "release", "chart", "--timeout 10", "--wait", "--wait-for-jobs"}, env, nil)
|
||||
expected = `exec: helm --kubeconfig config --kube-context dev diff release chart --timeout 10 --wait --wait-for-jobs
|
||||
`
|
||||
|
|
@ -741,7 +810,10 @@ func Test_exec(t *testing.T) {
|
|||
}
|
||||
|
||||
buffer.Reset()
|
||||
helm = MockExecer(logger, "", "")
|
||||
helm, err = MockExecer(logger, "", "")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
helm.SetHelmBinary("overwritten")
|
||||
_, err = helm.exec([]string{"version"}, env, nil)
|
||||
expected = `exec: overwritten version
|
||||
|
|
@ -757,8 +829,11 @@ func Test_exec(t *testing.T) {
|
|||
func Test_Lint(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "config", "dev")
|
||||
err := helm.Lint("release", "path/to/chart", "--values", "file.yml")
|
||||
helm, err := MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = helm.Lint("release", "path/to/chart", "--values", "file.yml")
|
||||
expected := `Linting release=release, chart=path/to/chart
|
||||
exec: helm --kubeconfig config --kube-context dev lint path/to/chart --values file.yml
|
||||
`
|
||||
|
|
@ -773,8 +848,11 @@ exec: helm --kubeconfig config --kube-context dev lint path/to/chart --values fi
|
|||
func Test_Fetch(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "config", "dev")
|
||||
err := helm.Fetch("chart", "--version", "1.2.3", "--untar", "--untardir", "/tmp/dir")
|
||||
helm, err := MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = helm.Fetch("chart", "--version", "1.2.3", "--untar", "--untardir", "/tmp/dir")
|
||||
expected := `Fetching chart
|
||||
exec: helm --kubeconfig config --kube-context dev fetch chart --version 1.2.3 --untar --untardir /tmp/dir
|
||||
`
|
||||
|
|
@ -848,8 +926,11 @@ exec: helm --kubeconfig config --kube-context dev pull oci://repo/helm-charts --
|
|||
tt := tests[i]
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
buffer.Reset()
|
||||
helm := New(tt.helmBin, HelmExecOptions{}, logger, "config", "dev", &mockRunner{output: []byte(tt.helmVersion)})
|
||||
err := helm.ChartPull(tt.chartName, tt.chartPath, tt.chartFlags...)
|
||||
helm, err := New(tt.helmBin, HelmExecOptions{}, logger, "config", "dev", &mockRunner{output: []byte(tt.helmVersion)})
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = helm.ChartPull(tt.chartName, tt.chartPath, tt.chartFlags...)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -922,8 +1003,11 @@ func Test_LogLevels(t *testing.T) {
|
|||
for logLevel, expected := range logLevelTests {
|
||||
buffer.Reset()
|
||||
logger := NewLogger(&buffer, logLevel)
|
||||
helm := MockExecer(logger, "", "")
|
||||
err := helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "example_user", "example_password", "", false, false)
|
||||
helm, err := MockExecer(logger, "", "")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = helm.AddRepo("myRepo", "https://repo.example.com/", "", "", "", "example_user", "example_password", "", false, false)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
|
|
@ -951,8 +1035,11 @@ func Test_mergeEnv(t *testing.T) {
|
|||
func Test_Template(t *testing.T) {
|
||||
var buffer bytes.Buffer
|
||||
logger := NewLogger(&buffer, "debug")
|
||||
helm := MockExecer(logger, "config", "dev")
|
||||
err := helm.TemplateRelease("release", "path/to/chart", "--values", "file.yml")
|
||||
helm, err := MockExecer(logger, "config", "dev")
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
err = helm.TemplateRelease("release", "path/to/chart", "--values", "file.yml")
|
||||
expected := `Templating release=release, chart=path/to/chart
|
||||
exec: helm --kubeconfig config --kube-context dev template release path/to/chart --values file.yml
|
||||
`
|
||||
|
|
@ -978,13 +1065,19 @@ exec: helm --kubeconfig config --kube-context dev template release https://examp
|
|||
|
||||
func Test_IsHelm3(t *testing.T) {
|
||||
helm2Runner := mockRunner{output: []byte("Client: v2.16.0+ge13bc94\n")}
|
||||
helm := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner)
|
||||
helm, err := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if helm.IsHelm3() {
|
||||
t.Error("helmexec.IsHelm3() - Detected Helm 3 with Helm 2 version")
|
||||
}
|
||||
|
||||
helm3Runner := mockRunner{output: []byte("v3.0.0+ge29ce2a\n")}
|
||||
helm = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner)
|
||||
helm, err = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if !helm.IsHelm3() {
|
||||
t.Error("helmexec.IsHelm3() - Failed to detect Helm 3")
|
||||
}
|
||||
|
|
@ -1012,14 +1105,20 @@ func Test_GetPluginVersion(t *testing.T) {
|
|||
|
||||
func Test_GetVersion(t *testing.T) {
|
||||
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")}
|
||||
helm := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner)
|
||||
helm, err := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
ver := helm.GetVersion()
|
||||
if ver.Major != 2 || ver.Minor != 16 || ver.Patch != 1 {
|
||||
t.Errorf("helmexec.GetVersion - did not detect correct Helm2 version; it was: %+v", ver)
|
||||
}
|
||||
|
||||
helm3Runner := mockRunner{output: []byte("v3.2.4+ge29ce2a\n")}
|
||||
helm = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner)
|
||||
helm, err = New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
ver = helm.GetVersion()
|
||||
if ver.Major != 3 || ver.Minor != 2 || ver.Patch != 4 {
|
||||
t.Errorf("helmexec.GetVersion - did not detect correct Helm3 version; it was: %+v", ver)
|
||||
|
|
@ -1028,7 +1127,10 @@ func Test_GetVersion(t *testing.T) {
|
|||
|
||||
func Test_IsVersionAtLeast(t *testing.T) {
|
||||
helm2Runner := mockRunner{output: []byte("Client: v2.16.1+ge13bc94\n")}
|
||||
helm := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner)
|
||||
helm, err := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm2Runner)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if !helm.IsVersionAtLeast("2.1.0") {
|
||||
t.Error("helmexec.IsVersionAtLeast - 2.16.1 not atleast 2.1")
|
||||
}
|
||||
|
|
@ -1140,6 +1242,24 @@ func TestParseHelmVersion(t *testing.T) {
|
|||
want: nil,
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "version without v prefix",
|
||||
version: "3.2.4",
|
||||
want: semver.MustParse("v3.2.4"),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "version without v prefix with build info",
|
||||
version: "3.2.4+ge29ce2a",
|
||||
want: semver.MustParse("v3.2.4+ge29ce2a"),
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "version without v prefix with spaces",
|
||||
version: " 3.2.4 ",
|
||||
want: semver.MustParse("v3.2.4"),
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ func Output(ctx context.Context, c *exec.Cmd, stripArgsValuesOnExitError bool, l
|
|||
exitStatus := waitStatus.ExitStatus()
|
||||
err = newExitError(c.Path, c.Args, exitStatus, ee, stderr.String(), combined.String(), stripArgsValuesOnExitError)
|
||||
default:
|
||||
panic(fmt.Sprintf("unexpected error: %v", err))
|
||||
err = fmt.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"net/http"
|
||||
neturl "net/url"
|
||||
"os"
|
||||
|
|
@ -14,9 +15,8 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/aws/aws-sdk-go/aws"
|
||||
"github.com/aws/aws-sdk-go/aws/session"
|
||||
"github.com/aws/aws-sdk-go/service/s3"
|
||||
"github.com/aws/aws-sdk-go-v2/config"
|
||||
"github.com/aws/aws-sdk-go-v2/service/s3"
|
||||
"github.com/hashicorp/go-getter"
|
||||
"github.com/hashicorp/go-getter/helper/url"
|
||||
"go.uber.org/zap"
|
||||
|
|
@ -213,13 +213,16 @@ func (r *Remote) Fetch(path string, cacheDirOpt ...string) (string, error) {
|
|||
return "", fmt.Errorf("[bug] cacheDirOpt's length: want 0 or 1, got %d", len(cacheDirOpt))
|
||||
}
|
||||
|
||||
query := u.RawQuery
|
||||
query, _ := neturl.ParseQuery(u.RawQuery)
|
||||
|
||||
should_cache := query.Get("cache") != "false"
|
||||
delete(query, "cache")
|
||||
|
||||
var cacheKey string
|
||||
replacer := strings.NewReplacer(":", "", "//", "_", "/", "_", ".", "_")
|
||||
dirKey := replacer.Replace(srcDir)
|
||||
if len(query) > 0 {
|
||||
q, _ := neturl.ParseQuery(query)
|
||||
q := maps.Clone(query)
|
||||
if q.Has("sshkey") {
|
||||
q.Set("sshkey", "redacted")
|
||||
}
|
||||
|
|
@ -262,7 +265,7 @@ func (r *Remote) Fetch(path string, cacheDirOpt ...string) (string, error) {
|
|||
}
|
||||
}
|
||||
|
||||
if !cached {
|
||||
if !cached || !should_cache {
|
||||
var getterSrc string
|
||||
if u.User != "" {
|
||||
getterSrc = fmt.Sprintf("%s://%s@%s%s", u.Scheme, u.User, u.Host, u.Dir)
|
||||
|
|
@ -271,7 +274,7 @@ func (r *Remote) Fetch(path string, cacheDirOpt ...string) (string, error) {
|
|||
}
|
||||
|
||||
if len(query) > 0 {
|
||||
getterSrc = strings.Join([]string{getterSrc, query}, "?")
|
||||
getterSrc = strings.Join([]string{getterSrc, query.Encode()}, "?")
|
||||
}
|
||||
|
||||
r.Logger.Debugf("remote> downloading %s to %s", getterSrc, getterDst)
|
||||
|
|
@ -364,22 +367,22 @@ func (g *S3Getter) Get(wd, src, dst string) error {
|
|||
return err
|
||||
}
|
||||
|
||||
// Create a new AWS session using the default AWS configuration
|
||||
sess := session.Must(session.NewSessionWithOptions(session.Options{
|
||||
SharedConfigState: session.SharedConfigEnable,
|
||||
Config: aws.Config{
|
||||
Region: aws.String(region),
|
||||
},
|
||||
}))
|
||||
// Create a new AWS config and S3 client using AWS SDK v2
|
||||
cfg, err := config.LoadDefaultConfig(context.TODO(),
|
||||
config.WithRegion(region),
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// Create an S3 client using the session
|
||||
s3Client := s3.New(sess)
|
||||
// Create an S3 client using the config
|
||||
s3Client := s3.NewFromConfig(cfg)
|
||||
|
||||
getObjectInput := &s3.GetObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
}
|
||||
resp, err := s3Client.GetObject(getObjectInput)
|
||||
resp, err := s3Client.GetObject(context.TODO(), getObjectInput)
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
|
|
@ -463,48 +466,47 @@ func (g *S3Getter) S3FileExists(path string) (string, error) {
|
|||
}
|
||||
|
||||
// Region
|
||||
g.Logger.Debugf("Creating session for determining S3 region %s", path)
|
||||
sess := session.Must(session.NewSessionWithOptions(session.Options{
|
||||
SharedConfigState: session.SharedConfigEnable,
|
||||
}))
|
||||
g.Logger.Debugf("Creating config for determining S3 region %s", path)
|
||||
cfg, err := config.LoadDefaultConfig(context.TODO())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
g.Logger.Debugf("Getting bucket %s location %s", bucket, path)
|
||||
s3Client := s3.New(sess)
|
||||
s3Client := s3.NewFromConfig(cfg)
|
||||
bucketRegion := "us-east-1"
|
||||
getBucketLocationInput := &s3.GetBucketLocationInput{
|
||||
Bucket: aws.String(bucket),
|
||||
Bucket: &bucket,
|
||||
}
|
||||
resp, err := s3Client.GetBucketLocation(getBucketLocationInput)
|
||||
resp, err := s3Client.GetBucketLocation(context.TODO(), getBucketLocationInput)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Error: Failed to retrieve bucket location: %v\n", err)
|
||||
return "", fmt.Errorf("failed to retrieve bucket location: %v", err)
|
||||
}
|
||||
if resp == nil || resp.LocationConstraint == nil {
|
||||
if resp == nil || string(resp.LocationConstraint) == "" {
|
||||
g.Logger.Debugf("Bucket has no location Assuming us-east-1")
|
||||
} else {
|
||||
bucketRegion = *resp.LocationConstraint
|
||||
bucketRegion = string(resp.LocationConstraint)
|
||||
}
|
||||
g.Logger.Debugf("Got bucket location %s", bucketRegion)
|
||||
|
||||
// File existence
|
||||
g.Logger.Debugf("Creating new session with region to see if file exists")
|
||||
regionSession, err := session.NewSessionWithOptions(session.Options{
|
||||
SharedConfigState: session.SharedConfigEnable,
|
||||
Config: aws.Config{
|
||||
Region: aws.String(bucketRegion),
|
||||
},
|
||||
})
|
||||
g.Logger.Debugf("Creating new config with region to see if file exists")
|
||||
regionCfg, err := config.LoadDefaultConfig(context.TODO(),
|
||||
config.WithRegion(bucketRegion),
|
||||
)
|
||||
if err != nil {
|
||||
g.Logger.Error(err)
|
||||
return bucketRegion, err
|
||||
}
|
||||
g.Logger.Debugf("Creating new s3 client to check if object exists")
|
||||
s3Client = s3.New(regionSession)
|
||||
s3Client = s3.NewFromConfig(regionCfg)
|
||||
headObjectInput := &s3.HeadObjectInput{
|
||||
Bucket: &bucket,
|
||||
Key: &key,
|
||||
}
|
||||
|
||||
g.Logger.Debugf("Fethcing head %s", path)
|
||||
_, err = s3Client.HeadObject(headObjectInput)
|
||||
_, err = s3Client.HeadObject(context.TODO(), headObjectInput)
|
||||
return bucketRegion, err
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -179,7 +179,7 @@ func TestRemote_SShGitHub_WithSshKey(t *testing.T) {
|
|||
if wd != CacheDir() {
|
||||
return fmt.Errorf("unexpected wd: %s", wd)
|
||||
}
|
||||
if src != "git::ssh://git@github.com/helmfile/helmfiles.git?ref=0.40.0&sshkey=ZWNkc2Etc2hhMi1uaXN0cDI1NiBBQUFBRTJWalpITmhMWE5vWVRJdGJtbHpkSEF5TlRZQUFBQUlibWx6ZEhBeU5UWUFBQUJCQkJTU3dOY2xoVzQ2Vm9VR3dMQ3JscVRHYUdOVWdRVUVEUEptc1ZzdUViL2RBNUcrQk9YMWxGaUVMYU9HQ2F6bS9KQkR2V3Y2Y0ZDQUtVRjVocVJOUjdJPSA=" {
|
||||
if src != "git::ssh://git@github.com/helmfile/helmfiles.git?ref=0.40.0&sshkey=ZWNkc2Etc2hhMi1uaXN0cDI1NiBBQUFBRTJWalpITmhMWE5vWVRJdGJtbHpkSEF5TlRZQUFBQUlibWx6ZEhBeU5UWUFBQUJCQkJTU3dOY2xoVzQ2Vm9VR3dMQ3JscVRHYUdOVWdRVUVEUEptc1ZzdUViL2RBNUcrQk9YMWxGaUVMYU9HQ2F6bS9KQkR2V3Y2Y0ZDQUtVRjVocVJOUjdJPSA%3D" {
|
||||
return fmt.Errorf("unexpected src: %s", src)
|
||||
}
|
||||
|
||||
|
|
@ -219,6 +219,73 @@ func TestRemote_SShGitHub_WithSshKey(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRemote_SShGitHub_WithDisableCacheKey(t *testing.T) {
|
||||
cleanfs := map[string]string{
|
||||
CacheDir(): "",
|
||||
}
|
||||
cachefs := map[string]string{
|
||||
filepath.Join(CacheDir(), "ssh_github_com_helmfile_helmfiles_git.ref=main/releases/kiam.yaml"): "foo: bar",
|
||||
}
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
files map[string]string
|
||||
expectCacheHit bool
|
||||
}{
|
||||
{name: "not expectCacheHit", files: cleanfs, expectCacheHit: false},
|
||||
{name: "forceNoCacheHit", files: cachefs, expectCacheHit: false},
|
||||
}
|
||||
|
||||
for _, tt := range testcases {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
testfs := testhelper.NewTestFs(tt.files)
|
||||
|
||||
hit := true
|
||||
|
||||
get := func(wd, src, dst string) error {
|
||||
if wd != CacheDir() {
|
||||
return fmt.Errorf("unexpected wd: %s", wd)
|
||||
}
|
||||
if src != "git::ssh://git@github.com/helmfile/helmfiles.git?ref=main" {
|
||||
return fmt.Errorf("unexpected src: %s", src)
|
||||
}
|
||||
|
||||
hit = false
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
getter := &testGetter{
|
||||
get: get,
|
||||
}
|
||||
remote := &Remote{
|
||||
Logger: helmexec.NewLogger(io.Discard, "debug"),
|
||||
Home: CacheDir(),
|
||||
Getter: getter,
|
||||
fs: testfs.ToFileSystem(),
|
||||
}
|
||||
|
||||
url := "git::ssh://git@github.com/helmfile/helmfiles.git@releases/kiam.yaml?ref=main&cache=false"
|
||||
file, err := remote.Locate(url)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
expectedFile := filepath.Join(CacheDir(), "ssh_github_com_helmfile_helmfiles_git.ref=main/releases/kiam.yaml")
|
||||
if file != expectedFile {
|
||||
t.Errorf("unexpected file located: %s vs expected: %s", file, expectedFile)
|
||||
}
|
||||
|
||||
if tt.expectCacheHit && !hit {
|
||||
t.Errorf("unexpected result: unexpected cache miss")
|
||||
}
|
||||
if !tt.expectCacheHit && hit {
|
||||
t.Errorf("unexpected result: unexpected cache hit")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemote_S3(t *testing.T) {
|
||||
cleanfs := map[string]string{
|
||||
CacheDir(): "",
|
||||
|
|
|
|||
|
|
@ -9,15 +9,15 @@ import (
|
|||
|
||||
var (
|
||||
// GoYamlV3 is set to true in order to let Helmfile use
|
||||
// gopkg.in/yaml.v3 instead of gopkg.in/yaml.v2.
|
||||
// go.yaml.in/yaml/v3 instead of go.yaml.in/yaml/v2.
|
||||
// It's false by default in Helmfile v0.x and true in Helmfile v1.x.
|
||||
GoYamlV3 bool
|
||||
)
|
||||
|
||||
func Info() string {
|
||||
yamlLib := "gopkg.in/yaml.v2"
|
||||
yamlLib := "go.yaml.in/yaml/v2"
|
||||
if GoYamlV3 {
|
||||
yamlLib = "gopkg.in/yaml.v3"
|
||||
yamlLib = "go.yaml.in/yaml/v3"
|
||||
}
|
||||
|
||||
return fmt.Sprintf("YAML library = %v", yamlLib)
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ const (
|
|||
DefaultHCLFileExtension = ".hcl"
|
||||
)
|
||||
|
||||
var ValidUpdateStrategyValues = []string{UpdateStrategyReinstallIfForbidden}
|
||||
|
||||
type StateLoadError struct {
|
||||
Msg string
|
||||
Cause error
|
||||
|
|
@ -43,6 +45,14 @@ func (e *UndefinedEnvError) Error() string {
|
|||
return fmt.Sprintf("environment \"%s\" is not defined", e.Env)
|
||||
}
|
||||
|
||||
type InvalidUpdateStrategyError struct {
|
||||
UpdateStrategy string
|
||||
}
|
||||
|
||||
func (e *InvalidUpdateStrategyError) Error() string {
|
||||
return fmt.Sprintf("updateStrategy %q is invalid, valid values are: %s or not set", e.UpdateStrategy, strings.Join(ValidUpdateStrategyValues, ", "))
|
||||
}
|
||||
|
||||
type StateCreator struct {
|
||||
logger *zap.SugaredLogger
|
||||
|
||||
|
|
@ -54,7 +64,7 @@ type StateCreator struct {
|
|||
|
||||
LoadFile func(inheritedEnv, overrodeEnv *environment.Environment, baseDir, file string, evaluateBases bool) (*HelmState, error)
|
||||
|
||||
getHelm func(*HelmState) helmexec.Interface
|
||||
getHelm func(*HelmState) (helmexec.Interface, error)
|
||||
|
||||
overrideHelmBinary string
|
||||
|
||||
|
|
@ -67,7 +77,7 @@ type StateCreator struct {
|
|||
lockFile string
|
||||
}
|
||||
|
||||
func NewCreator(logger *zap.SugaredLogger, fs *filesystem.FileSystem, valsRuntime vals.Evaluator, getHelm func(*HelmState) helmexec.Interface, overrideHelmBinary string, overrideKustomizeBinary string, remote *remote.Remote, enableLiveOutput bool, lockFile string) *StateCreator {
|
||||
func NewCreator(logger *zap.SugaredLogger, fs *filesystem.FileSystem, valsRuntime vals.Evaluator, getHelm func(*HelmState) (helmexec.Interface, error), overrideHelmBinary string, overrideKustomizeBinary string, remote *remote.Remote, enableLiveOutput bool, lockFile string) *StateCreator {
|
||||
return &StateCreator{
|
||||
logger: logger,
|
||||
|
||||
|
|
@ -116,11 +126,19 @@ func (c *StateCreator) Parse(content []byte, baseDir, file string) (*HelmState,
|
|||
return nil, &StateLoadError{fmt.Sprintf("failed to read %s: reading document at index %d", file, i), err}
|
||||
}
|
||||
|
||||
if err := mergo.Merge(&state, &intermediate, mergo.WithAppendSlice); err != nil {
|
||||
if err := mergo.Merge(&state, &intermediate, mergo.WithAppendSlice, mergo.WithOverride); err != nil {
|
||||
return nil, &StateLoadError{fmt.Sprintf("failed to read %s: merging document at index %d", file, i), err}
|
||||
}
|
||||
}
|
||||
|
||||
state.logger = c.logger
|
||||
state.valsRuntime = c.valsRuntime
|
||||
|
||||
return &state, nil
|
||||
}
|
||||
|
||||
// applyDefaultsAndOverrides applies default binary paths and command-line overrides
|
||||
func (c *StateCreator) applyDefaultsAndOverrides(state *HelmState) {
|
||||
if c.overrideHelmBinary != "" && c.overrideHelmBinary != DefaultHelmBinary {
|
||||
state.DefaultHelmBinary = c.overrideHelmBinary
|
||||
} else if state.DefaultHelmBinary == "" {
|
||||
|
|
@ -134,11 +152,6 @@ func (c *StateCreator) Parse(content []byte, baseDir, file string) (*HelmState,
|
|||
// Let `helmfile --kustomize-binary ""` not break this helmfile run
|
||||
state.DefaultKustomizeBinary = DefaultKustomizeBinary
|
||||
}
|
||||
|
||||
state.logger = c.logger
|
||||
state.valsRuntime = c.valsRuntime
|
||||
|
||||
return &state, nil
|
||||
}
|
||||
|
||||
// LoadEnvValues loads environment values files relative to the `baseDir`
|
||||
|
|
@ -181,6 +194,11 @@ func (c *StateCreator) ParseAndLoad(content []byte, baseDir, file string, envNam
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Apply default binaries and command-line overrides only for the main helmfile
|
||||
// after loading and merging all bases. This ensures that values from bases are
|
||||
// properly respected and that later bases/documents can override earlier ones.
|
||||
c.applyDefaultsAndOverrides(state)
|
||||
}
|
||||
|
||||
state, err = c.LoadEnvValues(state, envName, failOnMissingEnv, envValues, overrode)
|
||||
|
|
@ -216,7 +234,7 @@ func (c *StateCreator) loadBases(envValues, overrodeEnv *environment.Environment
|
|||
layers = append(layers, st)
|
||||
|
||||
for i := 1; i < len(layers); i++ {
|
||||
if err := mergo.Merge(layers[0], layers[i], mergo.WithAppendSlice); err != nil {
|
||||
if err := mergo.Merge(layers[0], layers[i], mergo.WithAppendSlice, mergo.WithOverride); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
|
@ -224,6 +242,31 @@ func (c *StateCreator) loadBases(envValues, overrodeEnv *environment.Environment
|
|||
return layers[0], nil
|
||||
}
|
||||
|
||||
// getEnvMissingFileHandlerConfig returns the first non-nil MissingFileHandlerConfig from the environment spec, state, or default.
|
||||
func (st *HelmState) getEnvMissingFileHandlerConfig(es EnvironmentSpec) *MissingFileHandlerConfig {
|
||||
switch {
|
||||
case es.MissingFileHandlerConfig != nil:
|
||||
return es.MissingFileHandlerConfig
|
||||
case st.MissingFileHandlerConfig != nil:
|
||||
return st.MissingFileHandlerConfig
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// getEnvMissingFileHandler returns the first non-nil MissingFileHandler from the environment spec, state, or default.
|
||||
func (st *HelmState) getEnvMissingFileHandler(es EnvironmentSpec) *string {
|
||||
defaultMissingFileHandler := "Error"
|
||||
switch {
|
||||
case es.MissingFileHandler != nil:
|
||||
return es.MissingFileHandler
|
||||
case st.MissingFileHandler != nil:
|
||||
return st.MissingFileHandler
|
||||
default:
|
||||
return &defaultMissingFileHandler
|
||||
}
|
||||
}
|
||||
|
||||
// nolint: unparam
|
||||
func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEnv bool, ctxEnv, overrode *environment.Environment) (*environment.Environment, error) {
|
||||
secretVals := map[string]any{}
|
||||
|
|
@ -240,7 +283,7 @@ func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEn
|
|||
var envSecretFiles []string
|
||||
if len(envSpec.Secrets) > 0 {
|
||||
for _, urlOrPath := range envSpec.Secrets {
|
||||
resolved, skipped, err := st.storage().resolveFile(envSpec.MissingFileHandler, "environment values", urlOrPath, envSpec.MissingFileHandlerConfig.resolveFileOptions()...)
|
||||
resolved, skipped, err := st.storage().resolveFile(st.getEnvMissingFileHandler(envSpec), "environment values", urlOrPath, st.getEnvMissingFileHandlerConfig(envSpec).resolveFileOptions()...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -317,7 +360,10 @@ func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEn
|
|||
func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles []string, envVals map[string]any, keepFileExtensions []string) ([]string, error) {
|
||||
var errs []error
|
||||
var decryptedFilesKeeper []string
|
||||
helm := c.getHelm(st)
|
||||
helm, err := c.getHelm(st)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
inputs := envSecretFiles
|
||||
inputsSize := len(inputs)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
|
@ -525,3 +526,205 @@ releaseContext:
|
|||
t.Errorf("unexpected values: expected=%v, actual=%v", expectedValues, actualValues)
|
||||
}
|
||||
}
|
||||
|
||||
// TestHelmBinaryInBases tests that helmBinary and kustomizeBinary settings
|
||||
// from bases are properly merged with later values overriding earlier ones
|
||||
func TestHelmBinaryInBases(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
files map[string]string
|
||||
mainFile string
|
||||
expectedHelmBinary string
|
||||
expectedKustomizeBinary string
|
||||
}{
|
||||
{
|
||||
name: "helmBinary in second base should be used",
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `bases:
|
||||
- ./bases/env.yaml
|
||||
---
|
||||
bases:
|
||||
- ./bases/repos.yaml
|
||||
---
|
||||
bases:
|
||||
- ./bases/releases.yaml
|
||||
`,
|
||||
"/path/to/bases/env.yaml": `environments:
|
||||
default:
|
||||
values:
|
||||
- key: value1
|
||||
`,
|
||||
"/path/to/bases/repos.yaml": `repositories:
|
||||
- name: stable
|
||||
url: https://charts.helm.sh/stable
|
||||
helmBinary: /path/to/custom/helm
|
||||
`,
|
||||
"/path/to/bases/releases.yaml": `releases:
|
||||
- name: myapp
|
||||
chart: stable/nginx
|
||||
`,
|
||||
},
|
||||
mainFile: "/path/to/helmfile.yaml",
|
||||
expectedHelmBinary: "/path/to/custom/helm",
|
||||
expectedKustomizeBinary: DefaultKustomizeBinary,
|
||||
},
|
||||
{
|
||||
name: "helmBinary in main file after bases should override",
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `bases:
|
||||
- ./bases/env.yaml
|
||||
---
|
||||
bases:
|
||||
- ./bases/repos.yaml
|
||||
---
|
||||
bases:
|
||||
- ./bases/releases.yaml
|
||||
helmBinary: /path/to/main/helm
|
||||
`,
|
||||
"/path/to/bases/env.yaml": `environments:
|
||||
default:
|
||||
values:
|
||||
- key: value1
|
||||
`,
|
||||
"/path/to/bases/repos.yaml": `repositories:
|
||||
- name: stable
|
||||
url: https://charts.helm.sh/stable
|
||||
helmBinary: /path/to/base/helm
|
||||
`,
|
||||
"/path/to/bases/releases.yaml": `releases:
|
||||
- name: myapp
|
||||
chart: stable/nginx
|
||||
`,
|
||||
},
|
||||
mainFile: "/path/to/helmfile.yaml",
|
||||
expectedHelmBinary: "/path/to/main/helm",
|
||||
expectedKustomizeBinary: DefaultKustomizeBinary,
|
||||
},
|
||||
{
|
||||
name: "helmBinary in main file between bases should override earlier bases",
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `bases:
|
||||
- ./bases/env.yaml
|
||||
---
|
||||
bases:
|
||||
- ./bases/repos.yaml
|
||||
helmBinary: /path/to/middle/helm
|
||||
---
|
||||
bases:
|
||||
- ./bases/releases.yaml
|
||||
`,
|
||||
"/path/to/bases/env.yaml": `environments:
|
||||
default:
|
||||
values:
|
||||
- key: value1
|
||||
`,
|
||||
"/path/to/bases/repos.yaml": `repositories:
|
||||
- name: stable
|
||||
url: https://charts.helm.sh/stable
|
||||
helmBinary: /path/to/base/helm
|
||||
`,
|
||||
"/path/to/bases/releases.yaml": `releases:
|
||||
- name: myapp
|
||||
chart: stable/nginx
|
||||
`,
|
||||
},
|
||||
mainFile: "/path/to/helmfile.yaml",
|
||||
expectedHelmBinary: "/path/to/middle/helm",
|
||||
expectedKustomizeBinary: DefaultKustomizeBinary,
|
||||
},
|
||||
{
|
||||
name: "kustomizeBinary in base should be used",
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `bases:
|
||||
- ./bases/base.yaml
|
||||
`,
|
||||
"/path/to/bases/base.yaml": `kustomizeBinary: /path/to/custom/kustomize
|
||||
releases:
|
||||
- name: myapp
|
||||
chart: mychart
|
||||
`,
|
||||
},
|
||||
mainFile: "/path/to/helmfile.yaml",
|
||||
expectedHelmBinary: DefaultHelmBinary,
|
||||
expectedKustomizeBinary: "/path/to/custom/kustomize",
|
||||
},
|
||||
{
|
||||
name: "both helmBinary and kustomizeBinary in different bases",
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `bases:
|
||||
- ./bases/helm.yaml
|
||||
---
|
||||
bases:
|
||||
- ./bases/kustomize.yaml
|
||||
`,
|
||||
"/path/to/bases/helm.yaml": `helmBinary: /path/to/custom/helm
|
||||
`,
|
||||
"/path/to/bases/kustomize.yaml": `kustomizeBinary: /path/to/custom/kustomize
|
||||
`,
|
||||
},
|
||||
mainFile: "/path/to/helmfile.yaml",
|
||||
expectedHelmBinary: "/path/to/custom/helm",
|
||||
expectedKustomizeBinary: "/path/to/custom/kustomize",
|
||||
},
|
||||
{
|
||||
name: "later base overrides earlier base for helmBinary",
|
||||
files: map[string]string{
|
||||
"/path/to/helmfile.yaml": `bases:
|
||||
- ./bases/first.yaml
|
||||
---
|
||||
bases:
|
||||
- ./bases/second.yaml
|
||||
`,
|
||||
"/path/to/bases/first.yaml": `helmBinary: /path/to/first/helm
|
||||
`,
|
||||
"/path/to/bases/second.yaml": `helmBinary: /path/to/second/helm
|
||||
`,
|
||||
},
|
||||
mainFile: "/path/to/helmfile.yaml",
|
||||
expectedHelmBinary: "/path/to/second/helm",
|
||||
expectedKustomizeBinary: DefaultKustomizeBinary,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
testFs := testhelper.NewTestFs(tt.files)
|
||||
if testFs.Cwd == "" {
|
||||
testFs.Cwd = "/"
|
||||
}
|
||||
|
||||
r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem())
|
||||
creator := NewCreator(logger, testFs.ToFileSystem(), nil, nil, "", "", r, false, "")
|
||||
|
||||
// Set up LoadFile for recursive base loading
|
||||
creator.LoadFile = func(inheritedEnv, overrodeEnv *environment.Environment, baseDir, file string, evaluateBases bool) (*HelmState, error) {
|
||||
path := filepath.Join(baseDir, file)
|
||||
content, ok := tt.files[path]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("file not found: %s", path)
|
||||
}
|
||||
return creator.ParseAndLoad([]byte(content), filepath.Dir(path), path, DefaultEnv, true, evaluateBases, inheritedEnv, overrodeEnv)
|
||||
}
|
||||
|
||||
yamlContent, ok := tt.files[tt.mainFile]
|
||||
if !ok {
|
||||
t.Fatalf("no file named %q registered", tt.mainFile)
|
||||
}
|
||||
|
||||
state, err := creator.ParseAndLoad([]byte(yamlContent), filepath.Dir(tt.mainFile), tt.mainFile, DefaultEnv, true, true, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if state.DefaultHelmBinary != tt.expectedHelmBinary {
|
||||
t.Errorf("helmBinary mismatch: expected=%s, actual=%s",
|
||||
tt.expectedHelmBinary, state.DefaultHelmBinary)
|
||||
}
|
||||
|
||||
if state.DefaultKustomizeBinary != tt.expectedKustomizeBinary {
|
||||
t.Errorf("kustomizeBinary mismatch: expected=%s, actual=%s",
|
||||
tt.expectedKustomizeBinary, state.DefaultKustomizeBinary)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,64 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/exectest"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
)
|
||||
|
||||
func TestIsReleaseInstalled_HandlesConnectionError(t *testing.T) {
|
||||
logger := zap.NewNop().Sugar()
|
||||
|
||||
state := &HelmState{
|
||||
logger: logger,
|
||||
}
|
||||
|
||||
// Create a custom helm mock that fails on List operations
|
||||
helm := &CustomFailingHelm{
|
||||
Helm: &exectest.Helm{
|
||||
DiffMutex: &sync.Mutex{},
|
||||
ChartsMutex: &sync.Mutex{},
|
||||
ReleasesMutex: &sync.Mutex{},
|
||||
Helm3: true,
|
||||
},
|
||||
}
|
||||
|
||||
release := ReleaseSpec{
|
||||
Name: "test-release",
|
||||
Chart: "test/chart",
|
||||
Namespace: "default",
|
||||
}
|
||||
|
||||
// This should return an error due to connection failure
|
||||
_, err := state.isReleaseInstalled(helmexec.HelmContext{}, helm, release)
|
||||
|
||||
// Verify that error was propagated
|
||||
if err == nil {
|
||||
t.Fatalf("expected isReleaseInstalled to return error when Kubernetes is unreachable, but got no error")
|
||||
}
|
||||
|
||||
if err.Error() == "" {
|
||||
t.Fatalf("expected isReleaseInstalled to return meaningful error when Kubernetes is unreachable, but got empty error")
|
||||
}
|
||||
|
||||
// Check if the error contains the expected message
|
||||
expectedMsg := "Kubernetes cluster unreachable"
|
||||
if err.Error() != expectedMsg && !strings.Contains(err.Error(), "Kubernetes cluster unreachable") {
|
||||
t.Fatalf("expected error to contain 'Kubernetes cluster unreachable', but got: %v", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// CustomFailingHelm wraps exectest.Helm and overrides List to simulate failures
|
||||
type CustomFailingHelm struct {
|
||||
*exectest.Helm
|
||||
}
|
||||
|
||||
func (h *CustomFailingHelm) List(context helmexec.HelmContext, filter string, flags ...string) (string, error) {
|
||||
return "", errors.New("Kubernetes cluster unreachable: Get \"http://localhost:8080/version\": dial tcp [::1]:8080: connect: connection refused")
|
||||
}
|
||||
|
|
@ -14,5 +14,5 @@ type EnvironmentSpec struct {
|
|||
// a message about the missing file at the log-level.
|
||||
MissingFileHandler *string `yaml:"missingFileHandler,omitempty"`
|
||||
// MissingFileHandlerConfig is composed of various settings for the MissingFileHandler
|
||||
MissingFileHandlerConfig MissingFileHandlerConfig `yaml:"missingFileHandlerConfig,omitempty"`
|
||||
MissingFileHandlerConfig *MissingFileHandlerConfig `yaml:"missingFileHandlerConfig,omitempty"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@ import (
|
|||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/helmfile/chartify"
|
||||
|
|
@ -158,31 +157,17 @@ func (st *HelmState) appendWaitForJobsFlags(flags []string, release *ReleaseSpec
|
|||
return flags
|
||||
}
|
||||
|
||||
func (st *HelmState) appendWaitFlags(flags []string, helm helmexec.Interface, release *ReleaseSpec, ops *SyncOpts) []string {
|
||||
var hasWait bool
|
||||
func (st *HelmState) appendWaitFlags(flags []string, release *ReleaseSpec, ops *SyncOpts) []string {
|
||||
switch {
|
||||
case release.Wait != nil && *release.Wait:
|
||||
hasWait = true
|
||||
flags = append(flags, "--wait")
|
||||
case ops != nil && ops.Wait:
|
||||
hasWait = true
|
||||
flags = append(flags, "--wait")
|
||||
case release.Wait == nil && st.HelmDefaults.Wait:
|
||||
hasWait = true
|
||||
flags = append(flags, "--wait")
|
||||
}
|
||||
// see https://github.com/helm/helm/releases/tag/v3.15.0
|
||||
// https://github.com/helm/helm/commit/fc74964
|
||||
if hasWait && helm.IsVersionAtLeast("3.15.0") {
|
||||
switch {
|
||||
case release.WaitRetries != nil && *release.WaitRetries > 0:
|
||||
flags = append(flags, "--wait-retries", strconv.Itoa(*release.WaitRetries))
|
||||
case ops != nil && ops.WaitRetries > 0:
|
||||
flags = append(flags, "--wait-retries", strconv.Itoa(ops.WaitRetries))
|
||||
case release.WaitRetries == nil && st.HelmDefaults.WaitRetries > 0:
|
||||
flags = append(flags, "--wait-retries", strconv.Itoa(st.HelmDefaults.WaitRetries))
|
||||
}
|
||||
}
|
||||
// Note: --wait-retries flag has been removed from Helm and is no longer supported
|
||||
// WaitRetries configuration is preserved for backward compatibility but ignored
|
||||
return flags
|
||||
}
|
||||
|
||||
|
|
@ -350,7 +335,7 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
|
|||
|
||||
jsonPatches := release.JSONPatches
|
||||
if len(jsonPatches) > 0 {
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, jsonPatches, release.MissingFileHandler)
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, jsonPatches)
|
||||
if err != nil {
|
||||
return nil, clean, err
|
||||
}
|
||||
|
|
@ -364,7 +349,7 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
|
|||
|
||||
strategicMergePatches := release.StrategicMergePatches
|
||||
if len(strategicMergePatches) > 0 {
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, strategicMergePatches, release.MissingFileHandler)
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, strategicMergePatches)
|
||||
if err != nil {
|
||||
return nil, clean, err
|
||||
}
|
||||
|
|
@ -378,7 +363,7 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
|
|||
|
||||
transformers := release.Transformers
|
||||
if len(transformers) > 0 {
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, transformers, release.MissingFileHandler)
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, transformers)
|
||||
if err != nil {
|
||||
return nil, clean, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -76,7 +76,6 @@ func TestAppendWaitFlags(t *testing.T) {
|
|||
name string
|
||||
release *ReleaseSpec
|
||||
syncOpts *SyncOpts
|
||||
helm helmexec.Interface
|
||||
helmSpec HelmSpec
|
||||
expected []string
|
||||
}{
|
||||
|
|
@ -85,7 +84,6 @@ func TestAppendWaitFlags(t *testing.T) {
|
|||
name: "release wait",
|
||||
release: &ReleaseSpec{Wait: &[]bool{true}[0]},
|
||||
syncOpts: nil,
|
||||
helm: testutil.NewVersionHelmExec("3.11.0"),
|
||||
helmSpec: HelmSpec{},
|
||||
expected: []string{"--wait"},
|
||||
},
|
||||
|
|
@ -93,7 +91,6 @@ func TestAppendWaitFlags(t *testing.T) {
|
|||
name: "cli flags wait",
|
||||
release: &ReleaseSpec{},
|
||||
syncOpts: &SyncOpts{Wait: true},
|
||||
helm: testutil.NewVersionHelmExec("3.11.0"),
|
||||
helmSpec: HelmSpec{},
|
||||
expected: []string{"--wait"},
|
||||
},
|
||||
|
|
@ -101,7 +98,6 @@ func TestAppendWaitFlags(t *testing.T) {
|
|||
name: "helm defaults wait",
|
||||
release: &ReleaseSpec{},
|
||||
syncOpts: nil,
|
||||
helm: testutil.NewVersionHelmExec("3.11.0"),
|
||||
helmSpec: HelmSpec{Wait: true},
|
||||
expected: []string{"--wait"},
|
||||
},
|
||||
|
|
@ -109,7 +105,6 @@ func TestAppendWaitFlags(t *testing.T) {
|
|||
name: "release wait false",
|
||||
release: &ReleaseSpec{Wait: &[]bool{false}[0]},
|
||||
syncOpts: nil,
|
||||
helm: testutil.NewVersionHelmExec("3.11.0"),
|
||||
helmSpec: HelmSpec{Wait: true},
|
||||
expected: []string{},
|
||||
},
|
||||
|
|
@ -117,7 +112,6 @@ func TestAppendWaitFlags(t *testing.T) {
|
|||
name: "cli flags wait false",
|
||||
release: &ReleaseSpec{},
|
||||
syncOpts: &SyncOpts{},
|
||||
helm: testutil.NewVersionHelmExec("3.11.0"),
|
||||
helmSpec: HelmSpec{Wait: true},
|
||||
expected: []string{"--wait"},
|
||||
},
|
||||
|
|
@ -125,66 +119,58 @@ func TestAppendWaitFlags(t *testing.T) {
|
|||
name: "helm defaults wait false",
|
||||
release: &ReleaseSpec{},
|
||||
syncOpts: nil,
|
||||
helm: testutil.NewVersionHelmExec("3.11.0"),
|
||||
helmSpec: HelmSpec{Wait: false},
|
||||
expected: []string{},
|
||||
},
|
||||
// --wait-retries
|
||||
// --wait-retries flag has been removed from Helm
|
||||
{
|
||||
name: "release wait and retry unsupported",
|
||||
release: &ReleaseSpec{Wait: &[]bool{true}[0], WaitRetries: &[]int{1}[0]},
|
||||
syncOpts: nil,
|
||||
helm: testutil.NewVersionHelmExec("3.11.0"),
|
||||
helmSpec: HelmSpec{},
|
||||
expected: []string{"--wait"},
|
||||
},
|
||||
{
|
||||
name: "release wait and retry supported",
|
||||
name: "release wait and retry - retries ignored",
|
||||
release: &ReleaseSpec{Wait: &[]bool{true}[0], WaitRetries: &[]int{1}[0]},
|
||||
syncOpts: nil,
|
||||
helm: testutil.NewVersionHelmExec("3.15.0"),
|
||||
helmSpec: HelmSpec{},
|
||||
expected: []string{"--wait", "--wait-retries", "1"},
|
||||
expected: []string{"--wait"},
|
||||
},
|
||||
{
|
||||
name: "no wait retry",
|
||||
release: &ReleaseSpec{WaitRetries: &[]int{1}[0]},
|
||||
syncOpts: nil,
|
||||
helm: testutil.NewVersionHelmExec("3.15.0"),
|
||||
helmSpec: HelmSpec{},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "cli flags wait and retry",
|
||||
name: "cli flags wait and retry - retries ignored",
|
||||
release: &ReleaseSpec{},
|
||||
syncOpts: &SyncOpts{Wait: true, WaitRetries: 2},
|
||||
helm: testutil.NewVersionHelmExec("3.15.0"),
|
||||
helmSpec: HelmSpec{},
|
||||
expected: []string{"--wait", "--wait-retries", "2"},
|
||||
expected: []string{"--wait"},
|
||||
},
|
||||
{
|
||||
name: "helm defaults wait retry",
|
||||
name: "helm defaults wait retry - retries ignored",
|
||||
release: &ReleaseSpec{},
|
||||
syncOpts: nil,
|
||||
helm: testutil.NewVersionHelmExec("3.15.0"),
|
||||
helmSpec: HelmSpec{Wait: true, WaitRetries: 3},
|
||||
expected: []string{"--wait", "--wait-retries", "3"},
|
||||
expected: []string{"--wait"},
|
||||
},
|
||||
{
|
||||
name: "release wait default retries",
|
||||
name: "release wait default retries - retries ignored",
|
||||
release: &ReleaseSpec{Wait: &[]bool{true}[0]},
|
||||
syncOpts: nil,
|
||||
helm: testutil.NewVersionHelmExec("3.15.0"),
|
||||
helmSpec: HelmSpec{WaitRetries: 4},
|
||||
expected: []string{"--wait", "--wait-retries", "4"},
|
||||
expected: []string{"--wait"},
|
||||
},
|
||||
{
|
||||
name: "release retries default wait",
|
||||
name: "release retries default wait - retries ignored",
|
||||
release: &ReleaseSpec{WaitRetries: &[]int{5}[0]},
|
||||
syncOpts: nil,
|
||||
helm: testutil.NewVersionHelmExec("3.15.0"),
|
||||
helmSpec: HelmSpec{Wait: true},
|
||||
expected: []string{"--wait", "--wait-retries", "5"},
|
||||
expected: []string{"--wait"},
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -192,7 +178,7 @@ func TestAppendWaitFlags(t *testing.T) {
|
|||
t.Run(tt.name, func(t *testing.T) {
|
||||
st := &HelmState{}
|
||||
st.HelmDefaults = tt.helmSpec
|
||||
got := st.appendWaitFlags([]string{}, tt.helm, tt.release, tt.syncOpts)
|
||||
got := st.appendWaitFlags([]string{}, tt.release, tt.syncOpts)
|
||||
require.Equalf(t, tt.expected, got, "appendWaitFlags() = %v, want %v", got, tt.expected)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,9 @@ const (
|
|||
// This is used by an interim solution to make the urfave/cli command report to the helmfile internal about that the
|
||||
// --timeout flag is missingl
|
||||
EmptyTimeout = -1
|
||||
|
||||
// Valid enum for updateStrategy values
|
||||
UpdateStrategyReinstallIfForbidden = "reinstallIfForbidden"
|
||||
)
|
||||
|
||||
// ReleaseSetSpec is release set spec
|
||||
|
|
@ -84,9 +87,9 @@ type ReleaseSetSpec struct {
|
|||
// If set to "Error", return an error when a subhelmfile points to a
|
||||
// non-existent path. The default behavior is to print a warning. Note the
|
||||
// differing default compared to other MissingFileHandlers.
|
||||
MissingFileHandler string `yaml:"missingFileHandler,omitempty"`
|
||||
MissingFileHandler *string `yaml:"missingFileHandler,omitempty"`
|
||||
// MissingFileHandlerConfig is composed of various settings for the MissingFileHandler
|
||||
MissingFileHandlerConfig MissingFileHandlerConfig `yaml:"missingFileHandlerConfig,omitempty"`
|
||||
MissingFileHandlerConfig *MissingFileHandlerConfig `yaml:"missingFileHandlerConfig,omitempty"`
|
||||
|
||||
LockFile string `yaml:"lockFilePath,omitempty"`
|
||||
}
|
||||
|
|
@ -163,6 +166,7 @@ type HelmSpec struct {
|
|||
// Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful
|
||||
Wait bool `yaml:"wait"`
|
||||
// WaitRetries, if set and --wait enabled, will retry any failed check on resource state, except if HTTP status code < 500 is received, subject to the specified number of retries
|
||||
// DEPRECATED: This field is ignored as the --wait-retries flag was removed from Helm. Preserved for backward compatibility.
|
||||
WaitRetries int `yaml:"waitRetries"`
|
||||
// WaitForJobs, if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout
|
||||
WaitForJobs bool `yaml:"waitForJobs"`
|
||||
|
|
@ -264,6 +268,7 @@ type ReleaseSpec struct {
|
|||
// Wait, if set to true, will wait until all Pods, PVCs, Services, and minimum number of Pods of a Deployment are in a ready state before marking the release as successful
|
||||
Wait *bool `yaml:"wait,omitempty"`
|
||||
// WaitRetries, if set and --wait enabled, will retry any failed check on resource state, except if HTTP status code < 500 is received, subject to the specified number of retries
|
||||
// DEPRECATED: This field is ignored as the --wait-retries flag was removed from Helm. Preserved for backward compatibility.
|
||||
WaitRetries *int `yaml:"waitRetries,omitempty"`
|
||||
// WaitForJobs, if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout
|
||||
WaitForJobs *bool `yaml:"waitForJobs,omitempty"`
|
||||
|
|
@ -275,6 +280,8 @@ type ReleaseSpec struct {
|
|||
Force *bool `yaml:"force,omitempty"`
|
||||
// Installed, when set to true, `delete --purge` the release
|
||||
Installed *bool `yaml:"installed,omitempty"`
|
||||
// UpdateStrategy, when set, indicate the strategy to use to update the release
|
||||
UpdateStrategy string `yaml:"updateStrategy,omitempty"`
|
||||
// Atomic, when set to true, restore previous state in case of a failed install/upgrade attempt
|
||||
Atomic *bool `yaml:"atomic,omitempty"`
|
||||
// CleanupOnFail, when set to true, the --cleanup-on-fail helm flag is passed to the upgrade command
|
||||
|
|
@ -307,6 +314,10 @@ type ReleaseSpec struct {
|
|||
// MissingFileHandler is set to either "Error" or "Warn". "Error" instructs helmfile to fail when unable to find a values or secrets file. When "Warn", it prints the file and continues.
|
||||
// The default value for MissingFileHandler is "Error".
|
||||
MissingFileHandler *string `yaml:"missingFileHandler,omitempty"`
|
||||
|
||||
// MissingFileHandlerConfig is composed of various settings for the MissingFileHandler
|
||||
MissingFileHandlerConfig *MissingFileHandlerConfig `yaml:"missingFileHandlerConfig,omitempty"`
|
||||
|
||||
// Needs is the [KUBECONTEXT/][NS/]NAME representations of releases that this release depends on.
|
||||
Needs []string `yaml:"needs,omitempty"`
|
||||
|
||||
|
|
@ -461,6 +472,7 @@ type SetValue struct {
|
|||
// AffectedReleases hold the list of released that where updated, deleted, or in error
|
||||
type AffectedReleases struct {
|
||||
Upgraded []*ReleaseSpec
|
||||
Reinstalled []*ReleaseSpec
|
||||
Deleted []*ReleaseSpec
|
||||
Failed []*ReleaseSpec
|
||||
DeleteFailed []*ReleaseSpec
|
||||
|
|
@ -803,6 +815,7 @@ type SyncOpts struct {
|
|||
Wait bool
|
||||
WaitRetries int
|
||||
WaitForJobs bool
|
||||
Timeout int
|
||||
SyncReleaseLabels bool
|
||||
ReuseValues bool
|
||||
ResetValues bool
|
||||
|
|
@ -1030,7 +1043,10 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme
|
|||
}
|
||||
m.Unlock()
|
||||
}
|
||||
} else if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil {
|
||||
} else if release.UpdateStrategy == UpdateStrategyReinstallIfForbidden {
|
||||
relErr = st.performSyncOrReinstallOfRelease(affectedReleases, helm, context, release, chart, m, flags...)
|
||||
} else {
|
||||
if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil {
|
||||
m.Lock()
|
||||
affectedReleases.Failed = append(affectedReleases.Failed, release)
|
||||
m.Unlock()
|
||||
|
|
@ -1046,6 +1062,7 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme
|
|||
release.installedVersion = installedVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if _, err := st.triggerPostsyncEvent(release, relErr, "sync"); err != nil {
|
||||
if relErr == nil {
|
||||
|
|
@ -1089,6 +1106,77 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme
|
|||
return nil
|
||||
}
|
||||
|
||||
func (st *HelmState) performSyncOrReinstallOfRelease(affectedReleases *AffectedReleases, helm helmexec.Interface, context helmexec.HelmContext, release *ReleaseSpec, chart string, m *sync.Mutex, flags ...string) *ReleaseError {
|
||||
if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil {
|
||||
st.logger.Debugf("update strategy - sync failed: %s", err.Error())
|
||||
// Only fail if a different error than forbidden updates
|
||||
if !strings.Contains(err.Error(), "Forbidden: updates") {
|
||||
st.logger.Debugf("update strategy - sync failed not due to Forbidden updates")
|
||||
m.Lock()
|
||||
affectedReleases.Failed = append(affectedReleases.Failed, release)
|
||||
m.Unlock()
|
||||
return newReleaseFailedError(release, err)
|
||||
}
|
||||
} else {
|
||||
st.logger.Debugf("update strategy - sync success")
|
||||
m.Lock()
|
||||
affectedReleases.Upgraded = append(affectedReleases.Upgraded, release)
|
||||
m.Unlock()
|
||||
installedVersion, err := st.getDeployedVersion(context, helm, release)
|
||||
if err != nil { // err is not really impacting so just log it
|
||||
st.logger.Debugf("update strategy - getting deployed release version failed: %v", err)
|
||||
} else {
|
||||
release.installedVersion = installedVersion
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
st.logger.Infof("Failed to sync due to forbidden updates, attempting to reinstall %q allowed by update strategy", release.Name)
|
||||
installed, err := st.isReleaseInstalled(context, helm, *release)
|
||||
if err != nil {
|
||||
return newReleaseFailedError(release, err)
|
||||
}
|
||||
if installed {
|
||||
var args []string
|
||||
if release.Namespace != "" {
|
||||
args = append(args, "--namespace", release.Namespace)
|
||||
}
|
||||
deleteWaitFlag := true
|
||||
release.DeleteWait = &deleteWaitFlag
|
||||
args = st.appendDeleteWaitFlags(args, release)
|
||||
deletionFlags := st.appendConnectionFlags(args, release)
|
||||
m.Lock()
|
||||
if _, err := st.triggerReleaseEvent("preuninstall", nil, release, "sync"); err != nil {
|
||||
affectedReleases.Failed = append(affectedReleases.Failed, release)
|
||||
return newReleaseFailedError(release, err)
|
||||
} else if err := helm.DeleteRelease(context, release.Name, deletionFlags...); err != nil {
|
||||
affectedReleases.Failed = append(affectedReleases.Failed, release)
|
||||
return newReleaseFailedError(release, err)
|
||||
} else if _, err := st.triggerReleaseEvent("postuninstall", nil, release, "sync"); err != nil {
|
||||
affectedReleases.Failed = append(affectedReleases.Failed, release)
|
||||
return newReleaseFailedError(release, err)
|
||||
}
|
||||
m.Unlock()
|
||||
}
|
||||
if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil {
|
||||
m.Lock()
|
||||
affectedReleases.Failed = append(affectedReleases.Failed, release)
|
||||
m.Unlock()
|
||||
return newReleaseFailedError(release, err)
|
||||
} else {
|
||||
m.Lock()
|
||||
affectedReleases.Reinstalled = append(affectedReleases.Reinstalled, release)
|
||||
m.Unlock()
|
||||
installedVersion, err := st.getDeployedVersion(context, helm, release)
|
||||
if err != nil { // err is not really impacting so just log it
|
||||
st.logger.Debugf("update strategy - getting deployed release version failed: %v", err)
|
||||
} else {
|
||||
release.installedVersion = installedVersion
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (st *HelmState) listReleases(context helmexec.HelmContext, helm helmexec.Interface, release *ReleaseSpec) (string, error) {
|
||||
flags := st.kubeConnectionFlags(release)
|
||||
if release.Namespace != "" {
|
||||
|
|
@ -1132,6 +1220,16 @@ func releasesNeedCharts(releases []ReleaseSpec) []ReleaseSpec {
|
|||
return result
|
||||
}
|
||||
|
||||
func filterReleasesForBuild(releases []ReleaseSpec) []ReleaseSpec {
|
||||
var filteredReleases []ReleaseSpec
|
||||
for _, r := range releases {
|
||||
if len(r.JSONPatches) == 0 && len(r.StrategicMergePatches) == 0 && len(r.Transformers) == 0 {
|
||||
filteredReleases = append(filteredReleases, r)
|
||||
}
|
||||
}
|
||||
return filteredReleases
|
||||
}
|
||||
|
||||
type ChartPrepareOptions struct {
|
||||
ForceDownload bool
|
||||
SkipRepos bool
|
||||
|
|
@ -1184,6 +1282,19 @@ func (st *HelmState) GetRepositoryAndNameFromChartName(chartName string) (*Repos
|
|||
return nil, chartName
|
||||
}
|
||||
|
||||
var mutexMap sync.Map
|
||||
|
||||
// retrieves or creates a sync.Mutex for a given name
|
||||
func (st *HelmState) getNamedMutex(name string) *sync.Mutex {
|
||||
mu, ok := mutexMap.Load(name)
|
||||
if ok {
|
||||
return mu.(*sync.Mutex)
|
||||
}
|
||||
newMu := &sync.Mutex{}
|
||||
actualMu, _ := mutexMap.LoadOrStore(name, newMu)
|
||||
return actualMu.(*sync.Mutex)
|
||||
}
|
||||
|
||||
type PrepareChartKey struct {
|
||||
Namespace, Name, KubeContext string
|
||||
}
|
||||
|
|
@ -1202,100 +1313,8 @@ type PrepareChartKey struct {
|
|||
// Otheriwse, if a chart is not a helm chart, it will call "chartify" to turn it into a chart.
|
||||
//
|
||||
// If exists, it will also patch resources by json patches, strategic-merge patches, and injectors.
|
||||
func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurrency int, helmfileCommand string, opts ChartPrepareOptions) (map[PrepareChartKey]string, []error) {
|
||||
if !opts.SkipResolve {
|
||||
updated, err := st.ResolveDeps()
|
||||
if err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
*st = *updated
|
||||
}
|
||||
selected, err := st.GetSelectedReleases(opts.IncludeTransitiveNeeds)
|
||||
if err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
|
||||
releases := releasesNeedCharts(selected)
|
||||
|
||||
var prepareChartInfoMutex sync.Mutex
|
||||
|
||||
prepareChartInfo := make(map[PrepareChartKey]string, len(releases))
|
||||
|
||||
errs := []error{}
|
||||
|
||||
jobQueue := make(chan *ReleaseSpec, len(releases))
|
||||
results := make(chan *chartPrepareResult, len(releases))
|
||||
|
||||
var builds []*chartPrepareResult
|
||||
|
||||
st.scatterGather(
|
||||
concurrency,
|
||||
len(releases),
|
||||
func() {
|
||||
for i := 0; i < len(releases); i++ {
|
||||
jobQueue <- &releases[i]
|
||||
}
|
||||
close(jobQueue)
|
||||
},
|
||||
func(workerIndex int) {
|
||||
for release := range jobQueue {
|
||||
if st.OverrideChart != "" {
|
||||
release.Chart = st.OverrideChart
|
||||
}
|
||||
// Call user-defined `prepare` hooks to create/modify local charts to be used by
|
||||
// the later process.
|
||||
//
|
||||
// If it wasn't called here, Helmfile can end up an issue like
|
||||
// https://github.com/roboll/helmfile/issues/1328
|
||||
if _, err := st.triggerPrepareEvent(release, helmfileCommand); err != nil {
|
||||
results <- &chartPrepareResult{err: err}
|
||||
return
|
||||
}
|
||||
|
||||
chartName := release.Chart
|
||||
|
||||
chartPath, err := st.downloadChartWithGoGetter(release)
|
||||
if err != nil {
|
||||
results <- &chartPrepareResult{err: fmt.Errorf("release %q: %w", release.Name, err)}
|
||||
return
|
||||
}
|
||||
chartFetchedByGoGetter := chartPath != chartName
|
||||
|
||||
if !chartFetchedByGoGetter {
|
||||
ociChartPath, err := st.getOCIChart(release, dir, helm, opts.OutputDirTemplate)
|
||||
if err != nil {
|
||||
results <- &chartPrepareResult{err: fmt.Errorf("release %q: %w", release.Name, err)}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if ociChartPath != nil {
|
||||
chartPath = *ociChartPath
|
||||
}
|
||||
}
|
||||
|
||||
isLocal := st.fs.DirectoryExistsAt(normalizeChart(st.basePath, chartName))
|
||||
|
||||
chartification, clean, err := st.PrepareChartify(helm, release, chartPath, workerIndex)
|
||||
|
||||
if !opts.SkipCleanup {
|
||||
// nolint: staticcheck
|
||||
defer clean()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
results <- &chartPrepareResult{err: err}
|
||||
return
|
||||
}
|
||||
|
||||
var buildDeps bool
|
||||
|
||||
skipDepsGlobal := opts.SkipDeps
|
||||
skipDepsRelease := release.SkipDeps != nil && *release.SkipDeps
|
||||
skipDepsDefault := release.SkipDeps == nil && st.HelmDefaults.SkipDeps
|
||||
skipDeps := (!isLocal && !chartFetchedByGoGetter) || skipDepsGlobal || skipDepsRelease || skipDepsDefault
|
||||
|
||||
if chartification != nil && helmfileCommand != "pull" {
|
||||
// processChartification handles the chartification process
|
||||
func (st *HelmState) processChartification(chartification *Chartify, release *ReleaseSpec, chartPath string, opts ChartPrepareOptions, skipDeps bool) (string, bool, error) {
|
||||
c := chartify.New(
|
||||
chartify.HelmBin(st.DefaultHelmBinary),
|
||||
chartify.KustomizeBin(st.DefaultKustomizeBinary),
|
||||
|
|
@ -1334,41 +1353,135 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre
|
|||
|
||||
out, err := c.Chartify(release.Name, chartPath, chartify.WithChartifyOpts(chartifyOpts))
|
||||
if err != nil {
|
||||
results <- &chartPrepareResult{err: err}
|
||||
return
|
||||
} else {
|
||||
chartPath = out
|
||||
return "", false, err
|
||||
}
|
||||
|
||||
chartPath = out
|
||||
// Skip `helm dep build` and `helm dep up` altogether when the chart is from remote or the dep is
|
||||
// explicitly skipped.
|
||||
buildDeps = !skipDeps
|
||||
} else if normalizedChart := normalizeChart(st.basePath, chartPath); st.fs.DirectoryExistsAt(normalizedChart) {
|
||||
// At this point, we are sure that chartPath is a local directory containing either:
|
||||
// - A remote chart fetched by go-getter or
|
||||
// - A local chart
|
||||
//
|
||||
// The chart may have Chart.yaml(and requirements.yaml for Helm 2), and optionally Chart.lock/requirements.lock,
|
||||
// but no `charts/` directory populated at all, or a subet of chart dependencies are missing in the directory.
|
||||
//
|
||||
// In such situation, Helm fails with an error like:
|
||||
// Error: found in Chart.yaml, but missing in charts/ directory: cert-manager, prometheus, postgresql, gitlab-runner, grafana, redis
|
||||
//
|
||||
// (See also https://github.com/roboll/helmfile/issues/1401#issuecomment-670854495)
|
||||
//
|
||||
// To avoid it, we need to call a `helm dep build` command on the chart.
|
||||
// But the command may consistently fail when an outdated Chart.lock exists.
|
||||
//
|
||||
// (I've mentioned about such case in https://github.com/roboll/helmfile/pull/1400.)
|
||||
//
|
||||
// Trying to run `helm dep build` on the chart regardless of if it's from local or remote is
|
||||
// problematic, as usually the user would have no way to fix the remote chart on their own.
|
||||
//
|
||||
// Given that, we always run `helm dep build` on the chart here, but tolerate any error caused by it
|
||||
// for a remote chart, so that the user can notice/fix the issue in a local chart while
|
||||
// a broken remote chart won't completely block their job.
|
||||
chartPath = normalizedChart
|
||||
buildDeps := !skipDeps
|
||||
return chartPath, buildDeps, nil
|
||||
}
|
||||
|
||||
// processLocalChart handles local chart processing
|
||||
func (st *HelmState) processLocalChart(normalizedChart, dir string, release *ReleaseSpec, helmfileCommand string, opts ChartPrepareOptions, isLocal bool) (string, error) {
|
||||
chartPath := normalizedChart
|
||||
if helmfileCommand == "pull" && isLocal {
|
||||
chartAbsPath := strings.TrimSuffix(filepath.Clean(normalizedChart), "/")
|
||||
var err error
|
||||
chartPath, err = generateChartPath(filepath.Base(chartAbsPath), dir, release, opts.OutputDirTemplate)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := st.fs.CopyDir(normalizedChart, filepath.Clean(chartPath)); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
return chartPath, nil
|
||||
}
|
||||
|
||||
// forcedDownloadChart handles forced chart downloads
|
||||
func (st *HelmState) forcedDownloadChart(chartName, dir string, release *ReleaseSpec, helm helmexec.Interface, opts ChartPrepareOptions) (string, error) {
|
||||
// Check global chart cache first for non-OCI charts
|
||||
cacheKey := st.getChartCacheKey(release)
|
||||
if cachedPath, exists := st.checkChartCache(cacheKey); exists && st.fs.DirectoryExistsAt(cachedPath) {
|
||||
st.logger.Debugf("Chart %s:%s already downloaded, using cached version at %s", chartName, release.Version, cachedPath)
|
||||
return cachedPath, nil
|
||||
}
|
||||
|
||||
chartPath, err := generateChartPath(chartName, dir, release, opts.OutputDirTemplate)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// only fetch chart if it is not already fetched
|
||||
if _, err := os.Stat(chartPath); os.IsNotExist(err) {
|
||||
var fetchFlags []string
|
||||
fetchFlags = st.appendChartVersionFlags(fetchFlags, release)
|
||||
fetchFlags = append(fetchFlags, "--untar", "--untardir", chartPath)
|
||||
if err := helm.Fetch(chartName, fetchFlags...); err != nil {
|
||||
return "", err
|
||||
}
|
||||
} else {
|
||||
st.logger.Infof("\"%s\" has not been downloaded because the output directory \"%s\" already exists", chartName, chartPath)
|
||||
}
|
||||
|
||||
// Set chartPath to be the path containing Chart.yaml, if found
|
||||
fullChartPath, err := findChartDirectory(chartPath)
|
||||
if err == nil {
|
||||
chartPath = filepath.Dir(fullChartPath)
|
||||
}
|
||||
|
||||
// Add to global chart cache
|
||||
st.addToChartCache(cacheKey, chartPath)
|
||||
|
||||
return chartPath, nil
|
||||
}
|
||||
|
||||
// prepareChartForRelease processes a single release and prepares its chart
|
||||
func (st *HelmState) prepareChartForRelease(release *ReleaseSpec, helm helmexec.Interface, dir string, helmfileCommand string, opts ChartPrepareOptions, workerIndex int) *chartPrepareResult {
|
||||
if st.OverrideChart != "" {
|
||||
release.Chart = st.OverrideChart
|
||||
}
|
||||
|
||||
// Call user-defined `prepare` hooks to create/modify local charts to be used by
|
||||
// the later process.
|
||||
//
|
||||
// If it wasn't called here, Helmfile can end up an issue like
|
||||
// https://github.com/roboll/helmfile/issues/1328
|
||||
if _, err := st.triggerPrepareEvent(release, helmfileCommand); err != nil {
|
||||
return &chartPrepareResult{err: err}
|
||||
}
|
||||
|
||||
chartName := release.Chart
|
||||
|
||||
chartPath, err := st.downloadChartWithGoGetter(release)
|
||||
if err != nil {
|
||||
return &chartPrepareResult{err: fmt.Errorf("release %q: %w", release.Name, err)}
|
||||
}
|
||||
chartFetchedByGoGetter := chartPath != chartName
|
||||
|
||||
if !chartFetchedByGoGetter {
|
||||
ociChartPath, err := st.getOCIChart(release, dir, helm, opts)
|
||||
if err != nil {
|
||||
return &chartPrepareResult{err: fmt.Errorf("release %q: %w", release.Name, err)}
|
||||
}
|
||||
|
||||
if ociChartPath != nil {
|
||||
chartPath = *ociChartPath
|
||||
}
|
||||
}
|
||||
|
||||
isLocal := st.fs.DirectoryExistsAt(normalizeChart(st.basePath, chartName))
|
||||
|
||||
chartification, clean, err := st.PrepareChartify(helm, release, chartPath, workerIndex)
|
||||
|
||||
if !opts.SkipCleanup {
|
||||
// nolint: staticcheck
|
||||
defer clean()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return &chartPrepareResult{err: err}
|
||||
}
|
||||
|
||||
var buildDeps bool
|
||||
|
||||
skipDepsGlobal := opts.SkipDeps
|
||||
skipDepsRelease := release.SkipDeps != nil && *release.SkipDeps
|
||||
skipDepsDefault := release.SkipDeps == nil && st.HelmDefaults.SkipDeps
|
||||
skipDeps := (!isLocal && !chartFetchedByGoGetter) || skipDepsGlobal || skipDepsRelease || skipDepsDefault
|
||||
|
||||
if chartification != nil && helmfileCommand != "pull" {
|
||||
chartPath, buildDeps, err = st.processChartification(chartification, release, chartPath, opts, skipDeps)
|
||||
if err != nil {
|
||||
return &chartPrepareResult{err: err}
|
||||
}
|
||||
} else if normalizedChart := normalizeChart(st.basePath, chartPath); st.fs.DirectoryExistsAt(normalizedChart) {
|
||||
chartPath, err = st.processLocalChart(normalizedChart, dir, release, helmfileCommand, opts, isLocal)
|
||||
if err != nil {
|
||||
return &chartPrepareResult{err: err}
|
||||
}
|
||||
buildDeps = !skipDeps
|
||||
} else if !opts.ForceDownload {
|
||||
// At this point, we are sure that either:
|
||||
|
|
@ -1383,33 +1496,13 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre
|
|||
// For helm 2, we `helm fetch` with the version flags and call `helm template`
|
||||
// WITHOUT the version flags.
|
||||
} else {
|
||||
chartPath, err = generateChartPath(chartName, dir, release, opts.OutputDirTemplate)
|
||||
chartPath, err = st.forcedDownloadChart(chartName, dir, release, helm, opts)
|
||||
if err != nil {
|
||||
results <- &chartPrepareResult{err: err}
|
||||
return
|
||||
}
|
||||
|
||||
// only fetch chart if it is not already fetched
|
||||
if _, err := os.Stat(chartPath); os.IsNotExist(err) {
|
||||
var fetchFlags []string
|
||||
fetchFlags = st.appendChartVersionFlags(fetchFlags, release)
|
||||
fetchFlags = append(fetchFlags, "--untar", "--untardir", chartPath)
|
||||
if err := helm.Fetch(chartName, fetchFlags...); err != nil {
|
||||
results <- &chartPrepareResult{err: err}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
st.logger.Infof("\"%s\" has not been downloaded because the output directory \"%s\" already exists", chartName, chartPath)
|
||||
}
|
||||
|
||||
// Set chartPath to be the path containing Chart.yaml, if found
|
||||
fullChartPath, err := findChartDirectory(chartPath)
|
||||
if err == nil {
|
||||
chartPath = filepath.Dir(fullChartPath)
|
||||
return &chartPrepareResult{err: err}
|
||||
}
|
||||
}
|
||||
|
||||
results <- &chartPrepareResult{
|
||||
return &chartPrepareResult{
|
||||
releaseName: release.Name,
|
||||
chartName: chartName,
|
||||
releaseNamespace: release.Namespace,
|
||||
|
|
@ -1419,6 +1512,53 @@ func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurre
|
|||
skipRefresh: !isLocal || opts.SkipRefresh,
|
||||
chartFetchedByGoGetter: chartFetchedByGoGetter,
|
||||
}
|
||||
}
|
||||
|
||||
func (st *HelmState) PrepareCharts(helm helmexec.Interface, dir string, concurrency int, helmfileCommand string, opts ChartPrepareOptions) (map[PrepareChartKey]string, []error) {
|
||||
if !opts.SkipResolve {
|
||||
updated, err := st.ResolveDeps()
|
||||
if err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
*st = *updated
|
||||
}
|
||||
selected, err := st.GetSelectedReleases(opts.IncludeTransitiveNeeds)
|
||||
if err != nil {
|
||||
return nil, []error{err}
|
||||
}
|
||||
|
||||
releases := releasesNeedCharts(selected)
|
||||
|
||||
// For build command, skip releases that require chartify (jsonPatches, etc.)
|
||||
// as we only need to output state, not actually template the charts
|
||||
if helmfileCommand == "build" {
|
||||
releases = filterReleasesForBuild(releases)
|
||||
}
|
||||
|
||||
var prepareChartInfoMutex sync.Mutex
|
||||
|
||||
prepareChartInfo := make(map[PrepareChartKey]string, len(releases))
|
||||
|
||||
errs := []error{}
|
||||
|
||||
jobQueue := make(chan *ReleaseSpec, len(releases))
|
||||
results := make(chan *chartPrepareResult, len(releases))
|
||||
|
||||
var builds []*chartPrepareResult
|
||||
|
||||
st.scatterGather(
|
||||
concurrency,
|
||||
len(releases),
|
||||
func() {
|
||||
for i := 0; i < len(releases); i++ {
|
||||
jobQueue <- &releases[i]
|
||||
}
|
||||
close(jobQueue)
|
||||
},
|
||||
func(workerIndex int) {
|
||||
for release := range jobQueue {
|
||||
result := st.prepareChartForRelease(release, helm, dir, helmfileCommand, opts, workerIndex)
|
||||
results <- result
|
||||
}
|
||||
},
|
||||
func() {
|
||||
|
|
@ -1870,7 +2010,7 @@ func (st *HelmState) prepareDiffReleases(helm helmexec.Interface, additionalValu
|
|||
mu := &sync.RWMutex{}
|
||||
installedReleases := map[string]bool{}
|
||||
|
||||
isInstalled := func(r *ReleaseSpec) bool {
|
||||
isInstalled := func(r *ReleaseSpec) (bool, error) {
|
||||
id := ReleaseToID(r)
|
||||
|
||||
mu.RLock()
|
||||
|
|
@ -1878,19 +2018,19 @@ func (st *HelmState) prepareDiffReleases(helm helmexec.Interface, additionalValu
|
|||
mu.RUnlock()
|
||||
|
||||
if ok {
|
||||
return v
|
||||
return v, nil
|
||||
}
|
||||
|
||||
v, err := st.isReleaseInstalled(st.createHelmContext(r, 0), helm, *r)
|
||||
if err != nil {
|
||||
st.logger.Warnf("confirming if the release is already installed or not: %v", err)
|
||||
} else {
|
||||
return false, err
|
||||
}
|
||||
|
||||
mu.Lock()
|
||||
installedReleases[id] = v
|
||||
mu.Unlock()
|
||||
}
|
||||
|
||||
return v
|
||||
return v, nil
|
||||
}
|
||||
|
||||
releases := []*ReleaseSpec{}
|
||||
|
|
@ -1935,12 +2075,25 @@ func (st *HelmState) prepareDiffReleases(helm helmexec.Interface, additionalValu
|
|||
suppressDiff = true
|
||||
}
|
||||
|
||||
if opt.SkipDiffOnInstall && !isInstalled(release) {
|
||||
if opt.SkipDiffOnInstall {
|
||||
installed, err := isInstalled(release)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
} else if !installed {
|
||||
results <- diffPrepareResult{release: release, upgradeDueToSkippedDiff: true, suppressDiff: suppressDiff}
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
disableValidation := release.DisableValidationOnInstall != nil && *release.DisableValidationOnInstall && !isInstalled(release)
|
||||
var disableValidation bool
|
||||
if release.DisableValidationOnInstall != nil && *release.DisableValidationOnInstall {
|
||||
installed, err := isInstalled(release)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
disableValidation = !installed
|
||||
}
|
||||
}
|
||||
|
||||
// TODO We need a long-term fix for this :)
|
||||
// See https://github.com/roboll/helmfile/issues/737
|
||||
|
|
@ -2255,7 +2408,7 @@ func (st *HelmState) TestReleases(helm helmexec.Interface, cleanup bool, timeout
|
|||
}
|
||||
|
||||
if timeout == EmptyTimeout {
|
||||
flags = append(flags, st.timeoutFlags(&release)...)
|
||||
flags = append(flags, st.timeoutFlags(&release, nil)...)
|
||||
} else {
|
||||
duration := strconv.Itoa(timeout)
|
||||
duration += "s"
|
||||
|
|
@ -2682,6 +2835,14 @@ func (st *HelmState) appendKeyringFlags(flags []string, release *ReleaseSpec) []
|
|||
return flags
|
||||
}
|
||||
|
||||
// appendEnableDNSFlags append the helm command-line flag for DNS resolution
|
||||
func (st *HelmState) appendEnableDNSFlags(flags []string, release *ReleaseSpec) []string {
|
||||
if release.EnableDNS != nil && *release.EnableDNS || release.EnableDNS == nil && st.HelmDefaults.EnableDNS {
|
||||
flags = append(flags, "--enable-dns")
|
||||
}
|
||||
return flags
|
||||
}
|
||||
|
||||
func (st *HelmState) kubeConnectionFlags(release *ReleaseSpec) []string {
|
||||
flags := []string{}
|
||||
if release.KubeContext != "" {
|
||||
|
|
@ -2735,13 +2896,16 @@ func (st *HelmState) needsInsecureSkipTLSVerify(release *ReleaseSpec, repo *Repo
|
|||
return relSkipTLSVerify || st.HelmDefaults.InsecureSkipTLSVerify || repoSkipTLSVerify
|
||||
}
|
||||
|
||||
func (st *HelmState) timeoutFlags(release *ReleaseSpec) []string {
|
||||
func (st *HelmState) timeoutFlags(release *ReleaseSpec, ops *SyncOpts) []string {
|
||||
var flags []string
|
||||
|
||||
timeout := st.HelmDefaults.Timeout
|
||||
if release.Timeout != nil {
|
||||
timeout = *release.Timeout
|
||||
}
|
||||
if ops != nil && ops.Timeout > 0 {
|
||||
timeout = ops.Timeout
|
||||
}
|
||||
if timeout != 0 {
|
||||
duration := strconv.Itoa(timeout)
|
||||
duration += "s"
|
||||
|
|
@ -2754,11 +2918,9 @@ func (st *HelmState) timeoutFlags(release *ReleaseSpec) []string {
|
|||
func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSpec, workerIndex int, opt *SyncOpts) ([]string, []string, error) {
|
||||
var flags []string
|
||||
flags = st.appendChartVersionFlags(flags, release)
|
||||
if release.EnableDNS != nil && *release.EnableDNS || release.EnableDNS == nil && st.HelmDefaults.EnableDNS {
|
||||
flags = append(flags, "--enable-dns")
|
||||
}
|
||||
flags = st.appendEnableDNSFlags(flags, release)
|
||||
|
||||
flags = st.appendWaitFlags(flags, helm, release, opt)
|
||||
flags = st.appendWaitFlags(flags, release, opt)
|
||||
flags = st.appendWaitForJobsFlags(flags, release, opt)
|
||||
|
||||
// non-OCI chart should be verified here
|
||||
|
|
@ -2767,7 +2929,7 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp
|
|||
flags = st.appendKeyringFlags(flags, release)
|
||||
}
|
||||
|
||||
flags = append(flags, st.timeoutFlags(release)...)
|
||||
flags = append(flags, st.timeoutFlags(release, opt)...)
|
||||
|
||||
if release.Force != nil && *release.Force || release.Force == nil && st.HelmDefaults.Force {
|
||||
flags = append(flags, "--force")
|
||||
|
|
@ -2881,6 +3043,7 @@ func (st *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec,
|
|||
settings := cli.New()
|
||||
var flags []string
|
||||
flags = st.appendChartVersionFlags(flags, release)
|
||||
flags = st.appendEnableDNSFlags(flags, release)
|
||||
|
||||
disableOpenAPIValidation := false
|
||||
if release.DisableOpenAPIValidation != nil {
|
||||
|
|
@ -3194,7 +3357,7 @@ func (st *HelmState) ExpandedHelmfiles() ([]SubHelmfileSpec, error) {
|
|||
}
|
||||
if len(matches) == 0 {
|
||||
err := fmt.Errorf("no matches for path: %s", hf.Path)
|
||||
if st.MissingFileHandler == "Error" {
|
||||
if *st.getMissingFileHandler() == "Error" {
|
||||
return nil, err
|
||||
}
|
||||
st.logger.Warnf("no matches for path: %s", hf.Path)
|
||||
|
|
@ -3241,19 +3404,67 @@ func (st *HelmState) removeFiles(files []string) {
|
|||
}
|
||||
}
|
||||
|
||||
func (c MissingFileHandlerConfig) resolveFileOptions() []resolveFileOption {
|
||||
func (c *MissingFileHandlerConfig) resolveFileOptions() []resolveFileOption {
|
||||
if c == nil {
|
||||
return []resolveFileOption{
|
||||
ignoreMissingGitBranch(false),
|
||||
}
|
||||
}
|
||||
return []resolveFileOption{
|
||||
ignoreMissingGitBranch(c.IgnoreMissingGitBranch),
|
||||
}
|
||||
}
|
||||
|
||||
func (st *HelmState) generateTemporaryReleaseValuesFiles(release *ReleaseSpec, values []any, missingFileHandler *string) ([]string, error) {
|
||||
// getReleaseMissingFileHandlerConfig returns the first non-nil MissingFileHandlerConfig in the following order:
|
||||
// - release.MissingFileHandlerConfig
|
||||
// - st.MissingFileHandlerConfig
|
||||
func (st *HelmState) getReleaseMissingFileHandlerConfig(release *ReleaseSpec) *MissingFileHandlerConfig {
|
||||
switch {
|
||||
case release.MissingFileHandlerConfig != nil:
|
||||
return release.MissingFileHandlerConfig
|
||||
case st.MissingFileHandlerConfig != nil:
|
||||
return st.MissingFileHandlerConfig
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// getReleaseMissingFileHandler returns the first non-nil MissingFileHandler in the following order:
|
||||
// - release.MissingFileHandler
|
||||
// - st.MissingFileHandler
|
||||
// - "Error"
|
||||
func (st *HelmState) getReleaseMissingFileHandler(release *ReleaseSpec) *string {
|
||||
defaultMissingFileHandler := "Error"
|
||||
switch {
|
||||
case release.MissingFileHandler != nil:
|
||||
return release.MissingFileHandler
|
||||
case st.MissingFileHandler != nil:
|
||||
return st.MissingFileHandler
|
||||
default:
|
||||
return &defaultMissingFileHandler
|
||||
}
|
||||
}
|
||||
|
||||
// getMissingFileHandler returns the first non-nil MissingFileHandler in the following order:
|
||||
// - st.MissingFileHandler
|
||||
// - "Error"
|
||||
func (st *HelmState) getMissingFileHandler() *string {
|
||||
defaultMissingFileHandler := "Error"
|
||||
switch {
|
||||
case st.MissingFileHandler != nil:
|
||||
return st.MissingFileHandler
|
||||
default:
|
||||
return &defaultMissingFileHandler
|
||||
}
|
||||
}
|
||||
|
||||
func (st *HelmState) generateTemporaryReleaseValuesFiles(release *ReleaseSpec, values []any) ([]string, error) {
|
||||
generatedFiles := []string{}
|
||||
|
||||
for _, value := range values {
|
||||
switch typedValue := value.(type) {
|
||||
case string:
|
||||
paths, skip, err := st.storage().resolveFile(missingFileHandler, "values", typedValue, st.MissingFileHandlerConfig.resolveFileOptions()...)
|
||||
paths, skip, err := st.storage().resolveFile(st.getReleaseMissingFileHandler(release), "values", typedValue, st.getReleaseMissingFileHandlerConfig(release).resolveFileOptions()...)
|
||||
if err != nil {
|
||||
return generatedFiles, err
|
||||
}
|
||||
|
|
@ -3334,7 +3545,7 @@ func (st *HelmState) generateVanillaValuesFiles(release *ReleaseSpec) ([]string,
|
|||
return nil, fmt.Errorf("Failed to render values in %s for release %s: type %T isn't supported", st.FilePath, release.Name, valuesMapSecretsRendered["values"])
|
||||
}
|
||||
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, valuesSecretsRendered, release.MissingFileHandler)
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, valuesSecretsRendered)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -3400,7 +3611,7 @@ func (st *HelmState) generateSecretValuesFiles(helm helmexec.Interface, release
|
|||
generatedDecryptedFiles = append(generatedDecryptedFiles, valfile)
|
||||
}
|
||||
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, generatedDecryptedFiles, release.MissingFileHandler)
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, generatedDecryptedFiles)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -3605,6 +3816,28 @@ func (ar *AffectedReleases) DisplayAffectedReleases(logger *zap.SugaredLogger) {
|
|||
}
|
||||
logger.Info(tbl.String())
|
||||
}
|
||||
if len(ar.Reinstalled) > 0 {
|
||||
logger.Info("\nREINSTALLED RELEASES:")
|
||||
tbl, _ := prettytable.NewTable(prettytable.Column{Header: "NAME"},
|
||||
prettytable.Column{Header: "NAMESPACE", MinWidth: 6},
|
||||
prettytable.Column{Header: "CHART", MinWidth: 6},
|
||||
prettytable.Column{Header: "VERSION", MinWidth: 6},
|
||||
prettytable.Column{Header: "DURATION", AlignRight: true},
|
||||
)
|
||||
tbl.Separator = " "
|
||||
for _, release := range ar.Reinstalled {
|
||||
modifiedChart, modErr := hideChartCredentials(release.Chart)
|
||||
if modErr != nil {
|
||||
logger.Warn("Could not modify chart credentials, %v", modErr)
|
||||
continue
|
||||
}
|
||||
err := tbl.AddRow(release.Name, release.Namespace, modifiedChart, release.installedVersion, release.duration.Round(time.Second))
|
||||
if err != nil {
|
||||
logger.Warn("Could not add row, %v", err)
|
||||
}
|
||||
}
|
||||
logger.Info(tbl.String())
|
||||
}
|
||||
if len(ar.Deleted) > 0 {
|
||||
logger.Info("\nDELETED RELEASES:")
|
||||
tbl, _ := prettytable.NewTable(prettytable.Column{Header: "NAME"},
|
||||
|
|
@ -3937,7 +4170,48 @@ func (st *HelmState) Reverse() {
|
|||
}
|
||||
}
|
||||
|
||||
func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helmexec.Interface, outputDirTemplate string) (*string, error) {
|
||||
// Chart cache for both OCI and non-OCI charts to avoid duplicate downloads
|
||||
type ChartCacheKey struct {
|
||||
Chart string
|
||||
Version string
|
||||
}
|
||||
|
||||
var downloadedCharts = make(map[ChartCacheKey]string) // key -> chart path
|
||||
var downloadedChartsMutex sync.RWMutex
|
||||
|
||||
// Legacy OCI-specific cache (kept for backward compatibility)
|
||||
var downloadedOCICharts = make(map[string]bool)
|
||||
var downloadedOCIMutex sync.RWMutex
|
||||
|
||||
// getChartCacheKey creates a cache key for a chart and version
|
||||
func (st *HelmState) getChartCacheKey(release *ReleaseSpec) ChartCacheKey {
|
||||
version := release.Version
|
||||
if version == "" {
|
||||
// Use empty string for latest version
|
||||
version = ""
|
||||
}
|
||||
return ChartCacheKey{
|
||||
Chart: release.Chart,
|
||||
Version: version,
|
||||
}
|
||||
}
|
||||
|
||||
// checkChartCache checks if a chart is already downloaded and returns its path
|
||||
func (st *HelmState) checkChartCache(key ChartCacheKey) (string, bool) {
|
||||
downloadedChartsMutex.RLock()
|
||||
defer downloadedChartsMutex.RUnlock()
|
||||
path, exists := downloadedCharts[key]
|
||||
return path, exists
|
||||
}
|
||||
|
||||
// addToChartCache adds a chart to the cache
|
||||
func (st *HelmState) addToChartCache(key ChartCacheKey, path string) {
|
||||
downloadedChartsMutex.Lock()
|
||||
defer downloadedChartsMutex.Unlock()
|
||||
downloadedCharts[key] = path
|
||||
}
|
||||
|
||||
func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helmexec.Interface, opts ChartPrepareOptions) (*string, error) {
|
||||
qualifiedChartName, chartName, chartVersion, err := st.getOCIQualifiedChartName(release, helm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -3947,7 +4221,41 @@ func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helm
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
chartPath, _ := st.getOCIChartPath(tempDir, release, chartName, chartVersion, outputDirTemplate)
|
||||
// Check global chart cache first
|
||||
cacheKey := st.getChartCacheKey(release)
|
||||
if cachedPath, exists := st.checkChartCache(cacheKey); exists && st.fs.DirectoryExistsAt(cachedPath) {
|
||||
st.logger.Debugf("OCI chart %s:%s already downloaded, using cached version at %s", chartName, chartVersion, cachedPath)
|
||||
return &cachedPath, nil
|
||||
}
|
||||
|
||||
if opts.OutputDirTemplate == "" {
|
||||
tempDir = remote.CacheDir()
|
||||
}
|
||||
|
||||
chartPath, _ := st.getOCIChartPath(tempDir, release, chartName, chartVersion, opts.OutputDirTemplate)
|
||||
|
||||
mu := st.getNamedMutex(chartPath)
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
|
||||
_, err = os.Stat(tempDir)
|
||||
if err != nil {
|
||||
err = os.MkdirAll(tempDir, 0755)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
downloadedOCIMutex.RLock()
|
||||
alreadyDownloadedFlag := downloadedOCICharts[chartPath]
|
||||
downloadedOCIMutex.RUnlock()
|
||||
|
||||
if !opts.SkipDeps && !opts.SkipRefresh && !alreadyDownloadedFlag {
|
||||
err = os.RemoveAll(chartPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if st.fs.DirectoryExistsAt(chartPath) {
|
||||
st.logger.Debugf("chart already exists at %s", chartPath)
|
||||
|
|
@ -3974,8 +4282,15 @@ func (st *HelmState) getOCIChart(release *ReleaseSpec, tempDir string, helm helm
|
|||
return nil, err
|
||||
}
|
||||
|
||||
downloadedOCIMutex.Lock()
|
||||
downloadedOCICharts[chartPath] = true
|
||||
downloadedOCIMutex.Unlock()
|
||||
|
||||
chartPath = filepath.Dir(fullChartPath)
|
||||
|
||||
// Add to global chart cache
|
||||
st.addToChartCache(cacheKey, chartPath)
|
||||
|
||||
return &chartPath, nil
|
||||
}
|
||||
|
||||
|
|
@ -4040,15 +4355,15 @@ func (st *HelmState) getOCIChartPath(tempDir string, release *ReleaseSpec, chart
|
|||
|
||||
pathElems := []string{tempDir}
|
||||
|
||||
if release.Namespace != "" {
|
||||
pathElems = append(pathElems, release.Namespace)
|
||||
}
|
||||
|
||||
if release.KubeContext != "" {
|
||||
pathElems = append(pathElems, release.KubeContext)
|
||||
}
|
||||
|
||||
pathElems = append(pathElems, release.Name, chartName, safeVersionPath(chartVersion))
|
||||
replacer := strings.NewReplacer(
|
||||
":", "_",
|
||||
"//", "_",
|
||||
".", "_",
|
||||
"&", "_",
|
||||
)
|
||||
qName := strings.Split(replacer.Replace(release.Chart), "/")
|
||||
|
||||
pathElems = append(pathElems, qName...)
|
||||
pathElems = append(pathElems, safeVersionPath(chartVersion))
|
||||
return filepath.Join(pathElems...), nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -170,6 +170,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
|
|||
version *semver.Version
|
||||
defaults HelmSpec
|
||||
release *ReleaseSpec
|
||||
syncOpts *SyncOpts
|
||||
want []string
|
||||
wantErr string
|
||||
}{
|
||||
|
|
@ -455,6 +456,27 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
|
|||
"--namespace", "test-namespace",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "timeout-from-cli-flag",
|
||||
defaults: HelmSpec{
|
||||
Timeout: 123,
|
||||
},
|
||||
release: &ReleaseSpec{
|
||||
Chart: "test/chart",
|
||||
Version: "0.1",
|
||||
Timeout: some(456),
|
||||
Name: "test-charts",
|
||||
Namespace: "test-namespace",
|
||||
},
|
||||
syncOpts: &SyncOpts{
|
||||
Timeout: 789,
|
||||
},
|
||||
want: []string{
|
||||
"--version", "0.1",
|
||||
"--timeout", "789s",
|
||||
"--namespace", "test-namespace",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "atomic",
|
||||
defaults: HelmSpec{
|
||||
|
|
@ -737,7 +759,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) {
|
|||
Version: tt.version,
|
||||
}
|
||||
|
||||
args, _, err := state.flagsForUpgrade(helm, tt.release, 0, nil)
|
||||
args, _, err := state.flagsForUpgrade(helm, tt.release, 0, tt.syncOpts)
|
||||
if err != nil && tt.wantErr == "" {
|
||||
t.Errorf("unexpected error flagsForUpgrade: %v", err)
|
||||
}
|
||||
|
|
@ -1618,6 +1640,112 @@ func TestHelmState_SyncReleasesAffectedRealeases(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestHelmState_SyncReleasesAffectedReleasesWithReinstallIfForbidden(t *testing.T) {
|
||||
no := false
|
||||
tests := []struct {
|
||||
name string
|
||||
releases []ReleaseSpec
|
||||
installed []bool
|
||||
wantAffected exectest.Affected
|
||||
}{
|
||||
{
|
||||
name: "2 new",
|
||||
releases: []ReleaseSpec{
|
||||
{
|
||||
Name: "releaseNameFoo-forbidden",
|
||||
Chart: "foo",
|
||||
UpdateStrategy: "reinstallIfForbidden",
|
||||
},
|
||||
{
|
||||
Name: "releaseNameBar-forbidden",
|
||||
Chart: "foo",
|
||||
UpdateStrategy: "reinstallIfForbidden",
|
||||
},
|
||||
},
|
||||
wantAffected: exectest.Affected{
|
||||
Upgraded: []*exectest.Release{
|
||||
{Name: "releaseNameFoo-forbidden", Flags: []string{}},
|
||||
{Name: "releaseNameBar-forbidden", Flags: []string{}},
|
||||
},
|
||||
Reinstalled: nil,
|
||||
Deleted: nil,
|
||||
Failed: nil,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "1 removed, 1 new, 1 reinstalled first new",
|
||||
releases: []ReleaseSpec{
|
||||
{
|
||||
Name: "releaseNameFoo-forbidden",
|
||||
Chart: "foo",
|
||||
UpdateStrategy: "reinstallIfForbidden",
|
||||
},
|
||||
{
|
||||
Name: "releaseNameBar",
|
||||
Chart: "foo",
|
||||
UpdateStrategy: "reinstallIfForbidden",
|
||||
Installed: &no,
|
||||
},
|
||||
{
|
||||
Name: "releaseNameFoo-forbidden",
|
||||
Chart: "foo",
|
||||
UpdateStrategy: "reinstallIfForbidden",
|
||||
},
|
||||
},
|
||||
installed: []bool{true, true, true},
|
||||
wantAffected: exectest.Affected{
|
||||
Upgraded: []*exectest.Release{
|
||||
{Name: "releaseNameFoo-forbidden", Flags: []string{}},
|
||||
},
|
||||
Reinstalled: []*exectest.Release{
|
||||
{Name: "releaseNameFoo-forbidden", Flags: []string{}},
|
||||
},
|
||||
Deleted: []*exectest.Release{
|
||||
{Name: "releaseNameBar", Flags: []string{}},
|
||||
},
|
||||
Failed: nil,
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
state := &HelmState{
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: tt.releases,
|
||||
},
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
RenderedValues: map[string]any{},
|
||||
}
|
||||
helm := &exectest.Helm{
|
||||
Lists: map[exectest.ListKey]string{},
|
||||
}
|
||||
//simulate the release is already installed
|
||||
for i, release := range tt.releases {
|
||||
if tt.installed != nil && tt.installed[i] {
|
||||
helm.Lists[exectest.ListKey{Filter: "^" + release.Name + "$", Flags: "--uninstalling --deployed --failed --pending"}] = release.Name
|
||||
}
|
||||
}
|
||||
|
||||
affectedReleases := AffectedReleases{}
|
||||
if err := state.SyncReleases(&affectedReleases, helm, []string{}, 1); err != nil {
|
||||
if !testEq(affectedReleases.Failed, tt.wantAffected.Failed) {
|
||||
t.Errorf("HelmState.SyncReleases() error failed for [%s] = %v, want %v", tt.name, affectedReleases.Failed, tt.wantAffected.Failed)
|
||||
} //else expected error
|
||||
}
|
||||
if !testEq(affectedReleases.Upgraded, tt.wantAffected.Upgraded) {
|
||||
t.Errorf("HelmState.SyncReleases() upgrade failed for [%s] = %v, want %v", tt.name, affectedReleases.Upgraded, tt.wantAffected.Upgraded)
|
||||
}
|
||||
if !testEq(affectedReleases.Reinstalled, tt.wantAffected.Reinstalled) {
|
||||
t.Errorf("HelmState.SyncReleases() reinstalled failed for [%s] = %v, want %v", tt.name, affectedReleases.Reinstalled, tt.wantAffected.Reinstalled)
|
||||
}
|
||||
if !testEq(affectedReleases.Deleted, tt.wantAffected.Deleted) {
|
||||
t.Errorf("HelmState.SyncReleases() deleted failed for [%s] = %v, want %v", tt.name, affectedReleases.Deleted, tt.wantAffected.Deleted)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func testEq(a []*ReleaseSpec, b []*exectest.Release) bool {
|
||||
// If one is nil, the other must also be nil.
|
||||
if (a == nil) != (b == nil) {
|
||||
|
|
@ -1836,14 +1964,19 @@ func TestHelmState_DiffReleases(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestHelmState_DiffFlags(t *testing.T) {
|
||||
enable := true
|
||||
disable := false
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
defaults HelmSpec
|
||||
releases []ReleaseSpec
|
||||
helm *exectest.Helm
|
||||
wantDiffFlags []string
|
||||
}{
|
||||
{
|
||||
name: "release with api version and kubeversion",
|
||||
defaults: HelmSpec{},
|
||||
releases: []ReleaseSpec{
|
||||
{
|
||||
Name: "releaseName",
|
||||
|
|
@ -1857,6 +1990,7 @@ func TestHelmState_DiffFlags(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "release with kubeversion and plain http which is ignored",
|
||||
defaults: HelmSpec{},
|
||||
releases: []ReleaseSpec{
|
||||
{
|
||||
Name: "releaseName",
|
||||
|
|
@ -1868,6 +2002,44 @@ func TestHelmState_DiffFlags(t *testing.T) {
|
|||
helm: &exectest.Helm{},
|
||||
wantDiffFlags: []string{"--kube-version", "1.21"},
|
||||
},
|
||||
{
|
||||
name: "release with enable-dns",
|
||||
defaults: HelmSpec{EnableDNS: false},
|
||||
releases: []ReleaseSpec{
|
||||
{
|
||||
Name: "releaseName",
|
||||
Chart: "foo",
|
||||
EnableDNS: &enable,
|
||||
},
|
||||
},
|
||||
helm: &exectest.Helm{},
|
||||
wantDiffFlags: []string{"--enable-dns"},
|
||||
},
|
||||
{
|
||||
name: "release with disable-dns override",
|
||||
defaults: HelmSpec{EnableDNS: true},
|
||||
releases: []ReleaseSpec{
|
||||
{
|
||||
Name: "releaseName",
|
||||
Chart: "foo",
|
||||
EnableDNS: &disable,
|
||||
},
|
||||
},
|
||||
helm: &exectest.Helm{},
|
||||
wantDiffFlags: nil,
|
||||
},
|
||||
{
|
||||
name: "release with enable-dns from default",
|
||||
defaults: HelmSpec{EnableDNS: true},
|
||||
releases: []ReleaseSpec{
|
||||
{
|
||||
Name: "releaseName",
|
||||
Chart: "foo",
|
||||
},
|
||||
},
|
||||
helm: &exectest.Helm{},
|
||||
wantDiffFlags: []string{"--enable-dns"},
|
||||
},
|
||||
}
|
||||
for i := range tests {
|
||||
tt := tests[i]
|
||||
|
|
@ -1875,6 +2047,7 @@ func TestHelmState_DiffFlags(t *testing.T) {
|
|||
state := &HelmState{
|
||||
ReleaseSetSpec: ReleaseSetSpec{
|
||||
Releases: tt.releases,
|
||||
HelmDefaults: tt.defaults,
|
||||
},
|
||||
logger: logger,
|
||||
valsRuntime: valsRuntime,
|
||||
|
|
@ -4571,3 +4744,58 @@ func TestPrepareSyncReleases_ValueControlReleaseOverride(t *testing.T) {
|
|||
require.Equal(t, tt.flags, r.flags, "Wrong value control flag for release %s", r.release.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChartCacheKey(t *testing.T) {
|
||||
st := &HelmState{}
|
||||
|
||||
// Test case 1: release with version
|
||||
release1 := &ReleaseSpec{
|
||||
Chart: "stable/nginx",
|
||||
Version: "1.2.3",
|
||||
}
|
||||
|
||||
key1 := st.getChartCacheKey(release1)
|
||||
expected1 := ChartCacheKey{Chart: "stable/nginx", Version: "1.2.3"}
|
||||
|
||||
if key1 != expected1 {
|
||||
t.Errorf("Expected %+v, got %+v", expected1, key1)
|
||||
}
|
||||
|
||||
// Test case 2: release without version
|
||||
release2 := &ReleaseSpec{
|
||||
Chart: "stable/nginx",
|
||||
}
|
||||
|
||||
key2 := st.getChartCacheKey(release2)
|
||||
expected2 := ChartCacheKey{Chart: "stable/nginx", Version: ""}
|
||||
|
||||
if key2 != expected2 {
|
||||
t.Errorf("Expected %+v, got %+v", expected2, key2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChartCache(t *testing.T) {
|
||||
st := &HelmState{}
|
||||
|
||||
// Create a test key
|
||||
key := ChartCacheKey{Chart: "stable/test", Version: "1.0.0"}
|
||||
path := "/tmp/test-chart"
|
||||
|
||||
// Initially, chart should not be in cache
|
||||
_, exists := st.checkChartCache(key)
|
||||
if exists {
|
||||
t.Error("Chart should not be in cache initially")
|
||||
}
|
||||
|
||||
// Add to cache
|
||||
st.addToChartCache(key, path)
|
||||
|
||||
// Now chart should be in cache
|
||||
cachedPath, exists := st.checkChartCache(key)
|
||||
if !exists {
|
||||
t.Error("Chart should be in cache after adding")
|
||||
}
|
||||
if cachedPath != path {
|
||||
t.Errorf("Expected path %s, got %s", path, cachedPath)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,39 +38,39 @@ func TestGenerateID(t *testing.T) {
|
|||
run(testcase{
|
||||
subject: "baseline",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
want: "foo-values-57ff559cd5",
|
||||
want: "foo-values-67dc97cbcb",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different bytes content",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
data: []byte(`{"k":"v"}`),
|
||||
want: "foo-values-75cd785b8b",
|
||||
want: "foo-values-75d7c4758c",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different map content",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
data: map[string]any{"k": "v"},
|
||||
want: "foo-values-5b44fdc768",
|
||||
want: "foo-values-685f8cf685",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different chart",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"},
|
||||
want: "foo-values-6ddb5db94d",
|
||||
want: "foo-values-75597d9c57",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different name",
|
||||
release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"},
|
||||
want: "bar-values-79bbb8df74",
|
||||
want: "bar-values-7b77df65ff",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "specific ns",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"},
|
||||
want: "myns-foo-values-c9457ccfb",
|
||||
want: "myns-foo-values-85f979545c",
|
||||
})
|
||||
|
||||
for id, n := range ids {
|
||||
|
|
|
|||
|
|
@ -358,11 +358,11 @@ func testFromYaml(t *testing.T, GoYamlV3 bool) {
|
|||
}
|
||||
|
||||
func TestFromYaml(t *testing.T) {
|
||||
t.Run("with gopkg.in/yaml.v2", func(t *testing.T) {
|
||||
t.Run("with go.yaml.in/yaml/v2", func(t *testing.T) {
|
||||
testFromYaml(t, true)
|
||||
})
|
||||
|
||||
t.Run("with gopkg.in/yaml.v3", func(t *testing.T) {
|
||||
t.Run("with go.yaml.in/yaml/v3", func(t *testing.T) {
|
||||
testFromYaml(t, false)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ import (
|
|||
"bytes"
|
||||
"io"
|
||||
|
||||
v2 "gopkg.in/yaml.v2"
|
||||
v3 "gopkg.in/yaml.v3"
|
||||
v2 "go.yaml.in/yaml/v2"
|
||||
v3 "go.yaml.in/yaml/v3"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/runtime"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -13,9 +13,9 @@ func testYamlMarshal(t *testing.T, GoYamlV3 bool) {
|
|||
|
||||
var yamlLibraryName string
|
||||
if GoYamlV3 {
|
||||
yamlLibraryName = "gopkg.in/yaml.v3"
|
||||
yamlLibraryName = "go.yaml.in/yaml/v3"
|
||||
} else {
|
||||
yamlLibraryName = "gopkg.in/yaml.v2"
|
||||
yamlLibraryName = "go.yaml.in/yaml/v2"
|
||||
}
|
||||
|
||||
v := runtime.GoYamlV3
|
||||
|
|
@ -49,8 +49,8 @@ func testYamlMarshal(t *testing.T, GoYamlV3 bool) {
|
|||
Annotation: "on",
|
||||
}},
|
||||
expected: map[string]string{
|
||||
"gopkg.in/yaml.v2": "name: John\ninfo:\n- age: 20\n address: New York\n annotation: \"on\"\n",
|
||||
"gopkg.in/yaml.v3": "name: John\ninfo:\n - age: 20\n address: New York\n annotation: \"on\"\n",
|
||||
"go.yaml.in/yaml/v2": "name: John\ninfo:\n- age: 20\n address: New York\n annotation: \"on\"\n",
|
||||
"go.yaml.in/yaml/v3": "name: John\ninfo:\n - age: 20\n address: New York\n annotation: \"on\"\n",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
|
@ -63,11 +63,11 @@ func testYamlMarshal(t *testing.T, GoYamlV3 bool) {
|
|||
}
|
||||
|
||||
func TestYamlMarshal(t *testing.T) {
|
||||
t.Run("with gopkg.in/yaml.v2", func(t *testing.T) {
|
||||
t.Run("with go.yaml.in/yaml/v2", func(t *testing.T) {
|
||||
testYamlMarshal(t, true)
|
||||
})
|
||||
|
||||
t.Run("with gopkg.in/yaml.v3", func(t *testing.T) {
|
||||
t.Run("with go.yaml.in/yaml/v3", func(t *testing.T) {
|
||||
testYamlMarshal(t, false)
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,12 +30,6 @@ var (
|
|||
helmShortVersionRegex = regexp.MustCompile(`v\d+\.\d+\.\d+\+[a-z0-9]+`)
|
||||
)
|
||||
|
||||
type ociChart struct {
|
||||
name string
|
||||
version string
|
||||
digest string
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
LocalDockerRegistry struct {
|
||||
Enabled bool `yaml:"enabled"`
|
||||
|
|
@ -58,11 +52,11 @@ func (f fakeInit) Force() bool {
|
|||
}
|
||||
|
||||
func TestHelmfileTemplateWithBuildCommand(t *testing.T) {
|
||||
t.Run("with gopkg.in/yaml.v3", func(t *testing.T) {
|
||||
t.Run("with go.yaml.in/yaml/v3", func(t *testing.T) {
|
||||
testHelmfileTemplateWithBuildCommand(t, true)
|
||||
})
|
||||
|
||||
t.Run("with gopkg.in/yaml.v2", func(t *testing.T) {
|
||||
t.Run("with go.yaml.in/yaml/v2", func(t *testing.T) {
|
||||
testHelmfileTemplateWithBuildCommand(t, false)
|
||||
})
|
||||
}
|
||||
|
|
@ -166,8 +160,6 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
|
|||
}
|
||||
})
|
||||
|
||||
// ociCharts holds a list of chart name, version and digest distributed by local oci registry.
|
||||
ociCharts := []ociChart{}
|
||||
// If localDockerRegistry.enabled is set to `true`,
|
||||
// run the docker registry v2 and push the test charts to the registry
|
||||
// so that it can be accessed by helm and helmfile as a oci registry based chart repository.
|
||||
|
|
@ -201,16 +193,9 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
|
|||
if !c.IsDir() {
|
||||
t.Fatalf("%s is not a directory", c)
|
||||
}
|
||||
chartName, chartVersion := execHelmShowChart(t, chartPath)
|
||||
tgzFile := execHelmPackage(t, chartPath)
|
||||
chartDigest, err := execHelmPush(t, tgzFile, fmt.Sprintf("oci://localhost:%d/myrepo", hostPort))
|
||||
_, err := execHelmPush(t, tgzFile, fmt.Sprintf("oci://localhost:%d/myrepo", hostPort))
|
||||
require.NoError(t, err, "Unable to run helm push to local registry: %v", err)
|
||||
|
||||
ociCharts = append(ociCharts, ociChart{
|
||||
name: chartName,
|
||||
version: chartVersion,
|
||||
digest: chartDigest,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -221,7 +206,7 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
|
|||
helmfileCacheHome := filepath.Join(tmpDir, "helmfile_cache")
|
||||
// HELM_CONFIG_HOME contains the registry auth file (registry.json) and the index of all the repos added via helm-repo-add (repositories.yaml).
|
||||
helmConfigHome := filepath.Join(tmpDir, "helm_config")
|
||||
t.Logf("Using HELM_CACHE_HOME=%s, HELMFILE_CACHE_HOME=%s, HELM_CONFIG_HOME=%s", helmCacheHome, helmfileCacheHome, helmConfigHome)
|
||||
t.Logf("Using HELM_CACHE_HOME=%s, HELMFILE_CACHE_HOME=%s, HELM_CONFIG_HOME=%s, WD=%s", helmCacheHome, helmfileCacheHome, helmConfigHome, wd)
|
||||
|
||||
inputFile := filepath.Join(testdataDir, name, "input.yaml.gotmpl")
|
||||
outputFile := ""
|
||||
|
|
@ -230,6 +215,7 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
|
|||
} else {
|
||||
outputFile = filepath.Join(testdataDir, name, "gopkg.in-yaml.v2-output.yaml")
|
||||
}
|
||||
expectedOutputFile := filepath.Join(testdataDir, name, "output.yaml")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
|
||||
defer cancel()
|
||||
|
|
@ -262,15 +248,8 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
|
|||
gotStr = chartGitFullPathRegex.ReplaceAllString(gotStr, `chart=$$GoGetterPath`)
|
||||
// Replace helm version with $HelmVersion
|
||||
gotStr = helmShortVersionRegex.ReplaceAllString(gotStr, `$$HelmVersion`)
|
||||
// Replace all occurrences of HELMFILE_CACHE_HOME with /home/runner/.cache/helmfile
|
||||
// for stable test result
|
||||
gotStr = strings.ReplaceAll(gotStr, helmfileCacheHome, "/home/runner/.cache/helmfile")
|
||||
|
||||
// OCI based helm charts are pulled and exported under temporary directory.
|
||||
// We are not sure the exact name of the temporary directory generated by helmfile,
|
||||
// so redact its base directory name with $TMP.
|
||||
if config.LocalDockerRegistry.Enabled {
|
||||
var releaseName, chartPath string
|
||||
sc := bufio.NewScanner(strings.NewReader(gotStr))
|
||||
for sc.Scan() {
|
||||
if !strings.HasPrefix(sc.Text(), "Templating ") {
|
||||
|
|
@ -281,28 +260,20 @@ func testHelmfileTemplateWithBuildCommand(t *testing.T, GoYamlV3 bool) {
|
|||
if len(releaseChartParts) != 2 {
|
||||
t.Fatal("Found unexpected log output of templating oci based helm chart, want=\"Templating release=<release_name>, chart=<chart_name>\"")
|
||||
}
|
||||
releaseNamePart, chartPathPart := releaseChartParts[0], releaseChartParts[1]
|
||||
releaseName = strings.TrimPrefix(releaseNamePart, "release=")
|
||||
chartPath = chartPathPart
|
||||
}
|
||||
for _, ociChart := range ociCharts {
|
||||
chartPathWithoutTempDirBase := fmt.Sprintf("/%s/%s/%s/%s", releaseName, ociChart.name, ociChart.version, ociChart.name)
|
||||
var chartPathBase string
|
||||
if strings.HasSuffix(chartPath, chartPathWithoutTempDirBase) {
|
||||
chartPathBase = strings.TrimSuffix(chartPath, chartPathWithoutTempDirBase)
|
||||
}
|
||||
if len(chartPathBase) != 0 {
|
||||
gotStr = strings.ReplaceAll(gotStr, chartPathBase, "chart=$TMP")
|
||||
}
|
||||
gotStr = strings.ReplaceAll(gotStr, fmt.Sprintf("Digest: %s", ociChart.digest), "Digest: $DIGEST")
|
||||
}
|
||||
}
|
||||
|
||||
gotStr = strings.ReplaceAll(gotStr, helmfileCacheHome, "$HELMFILE_CACHE_HOME")
|
||||
gotStr = strings.ReplaceAll(gotStr, wd, "__workingdir__")
|
||||
|
||||
if stat, _ := os.Stat(outputFile); stat != nil {
|
||||
want, err := os.ReadFile(outputFile)
|
||||
wantStr := strings.ReplaceAll(string(want), "__workingdir__", wd)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, wantStr, gotStr)
|
||||
require.Equal(t, string(want), gotStr)
|
||||
} else if stat, _ := os.Stat(expectedOutputFile); stat != nil {
|
||||
want, err := os.ReadFile(expectedOutputFile)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, string(want), gotStr)
|
||||
} else {
|
||||
// To update the test golden image(output.yaml), just remove it and rerun this test.
|
||||
// We automatically capture the output to `output.yaml` in the test case directory
|
||||
|
|
@ -325,23 +296,6 @@ func execDocker(t *testing.T, args ...string) {
|
|||
}
|
||||
}
|
||||
|
||||
func execHelmShowChart(t *testing.T, localChart string) (string, string) {
|
||||
t.Helper()
|
||||
|
||||
name, version := "", ""
|
||||
out := execHelm(t, "show", "chart", localChart)
|
||||
sc := bufio.NewScanner(strings.NewReader(out))
|
||||
for sc.Scan() {
|
||||
if strings.HasPrefix(sc.Text(), "name:") {
|
||||
name = strings.TrimPrefix(sc.Text(), "name: ")
|
||||
}
|
||||
if strings.HasPrefix(sc.Text(), "version:") {
|
||||
version = strings.TrimPrefix(sc.Text(), "version: ")
|
||||
}
|
||||
}
|
||||
return name, version
|
||||
}
|
||||
|
||||
func execHelmPackage(t *testing.T, localChart string) string {
|
||||
t.Helper()
|
||||
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
Pulling localhost:5001/myrepo/raw:0.1.0
|
||||
Templating release=foo, chart=$TMP/foo/raw/0.1.0/raw
|
||||
Templating release=foo, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
|
|
|
|||
6
test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_direct/config.yaml
vendored
Normal file
6
test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_direct/config.yaml
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
localDockerRegistry:
|
||||
enabled: true
|
||||
port: 5001
|
||||
chartifyTempDir: temp2
|
||||
helmfileArgs:
|
||||
- template
|
||||
22
test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_direct/input.yaml.gotmpl
vendored
Normal file
22
test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_direct/input.yaml.gotmpl
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
releases:
|
||||
- name: foo
|
||||
chart: oci://localhost:5001/myrepo/raw
|
||||
version: 0.1.0
|
||||
values: &oci_chart_pull_direct
|
||||
- templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{`{{ .Release.Name }}`}}
|
||||
namespace: {{`{{ .Release.Namespace }}`}}
|
||||
annotations:
|
||||
chart-version: {{`{{ .Chart.Version }}`}}
|
||||
data:
|
||||
values: {{`{{ .Release.Name }}`}}
|
||||
|
||||
- name: bar
|
||||
chart: oci://localhost:5001/myrepo/raw
|
||||
version: 0.1.0
|
||||
namespace: ns2
|
||||
values: *oci_chart_pull_direct
|
||||
27
test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_direct/output.yaml
vendored
Normal file
27
test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_direct/output.yaml
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
Pulling localhost:5001/myrepo/raw:0.1.0
|
||||
Templating release=foo, chart=$HELMFILE_CACHE_HOME/oci__localhost_5001/myrepo/raw/0.1.0/raw
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: foo
|
||||
namespace: default
|
||||
annotations:
|
||||
chart-version: 0.1.0
|
||||
data:
|
||||
values: foo
|
||||
|
||||
Templating release=bar, chart=$HELMFILE_CACHE_HOME/oci__localhost_5001/myrepo/raw/0.1.0/raw
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: bar
|
||||
namespace: ns2
|
||||
annotations:
|
||||
chart-version: 0.1.0
|
||||
data:
|
||||
values: bar
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
# Templating two versions of the same chart with only one pulling of each version
|
||||
localDockerRegistry:
|
||||
enabled: true
|
||||
port: 5001
|
||||
chartifyTempDir: temp3
|
||||
helmfileArgs:
|
||||
# Prevent releases from racing and randomizing the log
|
||||
- --concurrency=1
|
||||
- template
|
||||
43
test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once/input.yaml.gotmpl
vendored
Normal file
43
test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once/input.yaml.gotmpl
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
repositories:
|
||||
- name: myrepo
|
||||
url: localhost:5001/myrepo
|
||||
oci: true
|
||||
|
||||
releases:
|
||||
- name: foo
|
||||
chart: myrepo/raw
|
||||
version: 0.1.0
|
||||
values: &oci_chart_pull_once_values
|
||||
- templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{`{{ .Release.Name }}`}}
|
||||
namespace: {{`{{ .Release.Namespace }}`}}
|
||||
annotations:
|
||||
chart-version: {{`{{ .Chart.Version }}`}}
|
||||
data:
|
||||
values: {{`{{ .Release.Name }}`}}
|
||||
|
||||
- name: bar
|
||||
chart: myrepo/raw
|
||||
version: 0.1.0
|
||||
values: *oci_chart_pull_once_values
|
||||
|
||||
- name: release-no-default-ns
|
||||
chart: myrepo/raw
|
||||
version: 0.1.0
|
||||
namespace: no-default
|
||||
values: *oci_chart_pull_once_values
|
||||
|
||||
- name: second-version-of-chart
|
||||
chart: myrepo/raw
|
||||
version: 0.0.1
|
||||
namespace: foobar
|
||||
values: *oci_chart_pull_once_values
|
||||
|
||||
- name: first-release-version-of-chart
|
||||
chart: myrepo/raw
|
||||
version: 0.1.0
|
||||
values: *oci_chart_pull_once_values
|
||||
67
test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once/output.yaml
vendored
Normal file
67
test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once/output.yaml
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
Pulling localhost:5001/myrepo/raw:0.1.0
|
||||
Pulling localhost:5001/myrepo/raw:0.0.1
|
||||
Templating release=foo, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: foo
|
||||
namespace: default
|
||||
annotations:
|
||||
chart-version: 0.1.0
|
||||
data:
|
||||
values: foo
|
||||
|
||||
Templating release=bar, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: bar
|
||||
namespace: default
|
||||
annotations:
|
||||
chart-version: 0.1.0
|
||||
data:
|
||||
values: bar
|
||||
|
||||
Templating release=release-no-default-ns, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: release-no-default-ns
|
||||
namespace: no-default
|
||||
annotations:
|
||||
chart-version: 0.1.0
|
||||
data:
|
||||
values: release-no-default-ns
|
||||
|
||||
Templating release=second-version-of-chart, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.0.1/raw
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: second-version-of-chart
|
||||
namespace: foobar
|
||||
annotations:
|
||||
chart-version: 0.0.1
|
||||
data:
|
||||
values: second-version-of-chart
|
||||
|
||||
Templating release=first-release-version-of-chart, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: first-release-version-of-chart
|
||||
namespace: default
|
||||
annotations:
|
||||
chart-version: 0.1.0
|
||||
data:
|
||||
values: first-release-version-of-chart
|
||||
|
||||
7
test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once2/config.yaml
vendored
Normal file
7
test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once2/config.yaml
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
# Templating few releases with the same chart\version and only one pulling
|
||||
localDockerRegistry:
|
||||
enabled: true
|
||||
port: 5001
|
||||
chartifyTempDir: temp3
|
||||
helmfileArgs:
|
||||
- template
|
||||
23
test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once2/input.yaml.gotmpl
vendored
Normal file
23
test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once2/input.yaml.gotmpl
vendored
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
repositories:
|
||||
- name: myrepo
|
||||
url: localhost:5001/myrepo
|
||||
oci: true
|
||||
|
||||
releases:
|
||||
{{- range $i := until 5 }}
|
||||
- name: release-{{ $i }}
|
||||
chart: myrepo/raw
|
||||
version: 0.1.0
|
||||
values:
|
||||
- templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: {{`{{ .Release.Name }}`}}
|
||||
namespace: {{`{{ .Release.Namespace }}`}}
|
||||
annotations:
|
||||
chart-version: {{`{{ .Chart.Version }}`}}
|
||||
data:
|
||||
values: {{`{{ .Release.Name }}`}}
|
||||
{{- end }}
|
||||
66
test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once2/output.yaml
vendored
Normal file
66
test/e2e/template/helmfile/testdata/snapshot/oci_chart_pull_once2/output.yaml
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
Pulling localhost:5001/myrepo/raw:0.1.0
|
||||
Templating release=release-0, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: release-0
|
||||
namespace: default
|
||||
annotations:
|
||||
chart-version: 0.1.0
|
||||
data:
|
||||
values: release-0
|
||||
|
||||
Templating release=release-1, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: release-1
|
||||
namespace: default
|
||||
annotations:
|
||||
chart-version: 0.1.0
|
||||
data:
|
||||
values: release-1
|
||||
|
||||
Templating release=release-2, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: release-2
|
||||
namespace: default
|
||||
annotations:
|
||||
chart-version: 0.1.0
|
||||
data:
|
||||
values: release-2
|
||||
|
||||
Templating release=release-3, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: release-3
|
||||
namespace: default
|
||||
annotations:
|
||||
chart-version: 0.1.0
|
||||
data:
|
||||
values: release-3
|
||||
|
||||
Templating release=release-4, chart=$HELMFILE_CACHE_HOME/myrepo/raw/0.1.0/raw
|
||||
---
|
||||
# Source: raw/templates/resources.yaml
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: release-4
|
||||
namespace: default
|
||||
annotations:
|
||||
chart-version: 0.1.0
|
||||
data:
|
||||
values: release-4
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ export HELM_DATA_HOME="${helm_dir}/data"
|
|||
export HELM_HOME="${HELM_DATA_HOME}"
|
||||
export HELM_PLUGINS="${HELM_DATA_HOME}/plugins"
|
||||
export HELM_CONFIG_HOME="${helm_dir}/config"
|
||||
HELM_DIFF_VERSION="${HELM_DIFF_VERSION:-3.12.3}"
|
||||
HELM_DIFF_VERSION="${HELM_DIFF_VERSION:-3.12.5}"
|
||||
HELM_GIT_VERSION="${HELM_GIT_VERSION:-1.3.0}"
|
||||
HELM_SECRETS_VERSION="${HELM_SECRETS_VERSION:-3.15.0}"
|
||||
export GNUPGHOME="${PWD}/${dir}/.gnupg"
|
||||
|
|
@ -80,6 +80,7 @@ ${kubectl} create namespace ${test_ns} || fail "Could not create namespace ${tes
|
|||
|
||||
# TEST CASES----------------------------------------------------------------------------------------------------------
|
||||
|
||||
. ${dir}/test-cases/fetch-forl-local-chart.sh
|
||||
. ${dir}/test-cases/suppress-output-line-regex.sh
|
||||
. ${dir}/test-cases/chartify-jsonPatches-and-strategicMergePatches.sh
|
||||
. ${dir}/test-cases/include-template-func.sh
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
fetch_forl_local_chart_input_dir="${cases_dir}/fetch-forl-local-chart/input"
|
||||
|
||||
fetch_forl_local_chart_tmp=$(mktemp -d)
|
||||
|
||||
case_title="fetch for local chart"
|
||||
|
||||
test_start "$case_title"
|
||||
|
||||
info "Comparing fetch-forl-local-chart diff log #$i"
|
||||
${helmfile} -f ${fetch_forl_local_chart_input_dir}/helmfile.yaml.gotmpl fetch --output-dir ${fetch_forl_local_chart_tmp} || fail "\"helmfile fetch\" shouldn't fail"
|
||||
cat ${fetch_forl_local_chart_tmp}/helmfile-tests/local-chart/raw/latest/Chart.yaml || fail "Chart.yaml should exist in the fetched local chart directory"
|
||||
cat ${fetch_forl_local_chart_tmp}/helmfile-tests/local-chart/raw/latest/templates/resources.yaml || fail "templates/resources.yaml should exist in the fetched local chart directory"
|
||||
echo code=$?
|
||||
|
||||
test_pass "$case_title"
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
releases:
|
||||
- name: local-chart
|
||||
chart: ../../../charts/raw
|
||||
namespace: local-chart
|
||||
Loading…
Reference in New Issue