Skip to content
Open
57 changes: 57 additions & 0 deletions docs-v2/content/en/docs/deployers/helm.md
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,63 @@ If `skipBuildDependencies` is `false` then `skaffold dev` does **not** watch the

If `skipBuildDependencies` is `true` then `skaffold dev` watches all files inside the Helm chart.

## GCS References in Values Files

Skaffold supports referencing Helm values files stored in Google Cloud Storage (GCS) buckets.
This allows you to centrally manage configuration files across different environments. It inherits the application default credentials to authenticate with GCP.

### Supported GCS URL Formats

Skaffold automatically detects and downloads GCS references in the following formats:

- `gs://bucket-name/path/to/values.yaml` (recommended)
- `https://storage.googleapis.com/bucket-name/path/to/values.yaml`

### Usage Examples

**Direct valuesFiles:**
```yaml
deploy:
helm:
releases:
- name: my-release
chartPath: ./helm-chart
valuesFiles:
- gs://my-config-bucket/prod-values.yaml
- local-values.yaml
```

**Profile patches with flags:**
```yaml
profiles:
- name: production
activation:
- env: ENVIRONMENT_TYPE=prod
patches:
- op: add
path: /deploy/helm/flags/install/-
value: "--values=gs://my-config-bucket/prod-values.yaml"
- op: add
path: /deploy/helm/flags/upgrade/-
value: "--values=gs://my-config-bucket/prod-values.yaml"
```

**Profile patches with valuesFiles:**
```yaml
profiles:
- name: production
patches:
- op: add
path: /deploy/helm/releases/0/valuesFiles
value:
- gs://my-config-bucket/prod-values.yaml
```

When deploying, Skaffold will automatically:
1. Download the GCS files to temporary local paths
2. Pass the local paths to Helm commands
3. Clean up temporary files after deployment

### `skaffold.yaml` Configuration

The `helm` type offers the following options:
Expand Down
36 changes: 36 additions & 0 deletions docs-v2/content/en/docs/environment/profiles.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,42 @@ defines a different Dockerfile to use for the first artifact.

{{% readfile file="samples/profiles/patches.yaml" %}}

#### Using GCS References in Profile Patches

Profiles support referencing Helm values files stored in Google Cloud Storage (GCS). This is particularly
useful for environment-specific configurations that are managed centrally.

```yaml
profiles:
- name: production
activation:
- env: ENVIRONMENT_TYPE=prod
patches:
# Add GCS reference to valuesFiles array
- op: add
path: /deploy/helm/releases/0/valuesFiles
value:
- gs://my-config-bucket/prod-values.yaml
# Or add GCS reference as --values flag
- op: add
path: /deploy/helm/flags/install/-
value: "--values=gs://my-config-bucket/prod-values.yaml"
- op: add
path: /deploy/helm/flags/upgrade/-
value: "--values=gs://my-config-bucket/prod-values.yaml"
- name: staging
activation:
- env: ENVIRONMENT_TYPE=staging
patches:
- op: add
path: /deploy/helm/releases/0/valuesFiles
value:
- gs://my-config-bucket/staging-values.yaml
```

Skaffold automatically downloads GCS files before passing them to Helm, supporting both `gs://` and
`https://storage.googleapis.com/` URL formats.

### Activating multiple profiles at the same time

Multiple profiles can be specified either by using the `-p` flag multiple times or by comma separated profiles.
Expand Down
51 changes: 51 additions & 0 deletions docs-v2/content/en/samples/profiles/gcs-values.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
apiVersion: skaffold/v4beta13
kind: Config
build:
artifacts:
- image: gcr.io/k8s-skaffold/skaffold-example
deploy:
helm:
releases:
- name: skaffold-helm
chartPath: skaffold-helm
# Direct GCS reference in valuesFiles
valuesFiles:
- gs://config-bucket/default-values.yaml
flags:
install:
- --atomic=true
upgrade:
- --atomic=true
profiles:
- name: production
activation:
- env: ENVIRONMENT_TYPE=prod
patches:
# Add GCS reference to valuesFiles array
- op: add
path: /deploy/helm/releases/0/valuesFiles
value:
- gs://config-bucket/prod-values.yaml

- name: staging
activation:
- env: ENVIRONMENT_TYPE=staging
patches:
# Add GCS reference as --values flag
- op: add
path: /deploy/helm/flags/install/-
value: "--values=gs://config-bucket/staging-values.yaml"
- op: add
path: /deploy/helm/flags/upgrade/-
value: "--values=gs://config-bucket/staging-values.yaml"

- name: development
activation:
- env: ENVIRONMENT_TYPE=dev
patches:
# Support both HTTPS and gs:// formats
- op: add
path: /deploy/helm/releases/0/valuesFiles
value:
- https://storage.googleapis.com/config-bucket/dev-values.yaml
- gs://config-bucket/dev-override.yaml
80 changes: 78 additions & 2 deletions pkg/skaffold/deploy/helm/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,14 @@ limitations under the License.
package helm

import (
"fmt"
"os"
"strings"

"github.com/blang/semver"

"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/constants"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/gcs"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/graph"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/helm"
"github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/schema/latest"
Expand All @@ -44,7 +49,11 @@ func (h *Deployer) installArgs(r latest.HelmRelease, builds []graph.Artifact, o
var args []string
if o.upgrade {
args = append(args, "upgrade", o.releaseName)
args = append(args, o.flags...)
processedFlags, err := processGCSFlags(o.flags)
if err != nil {
return nil, err
}
args = append(args, processedFlags...)

if o.force {
args = append(args, "--force")
Expand All @@ -56,7 +65,11 @@ func (h *Deployer) installArgs(r latest.HelmRelease, builds []graph.Artifact, o
} else {
args = append(args, "install")
args = append(args, o.releaseName)
args = append(args, o.flags...)
processedFlags, err := processGCSFlags(o.flags)
if err != nil {
return nil, err
}
args = append(args, processedFlags...)
}

// There are 2 strategies:
Expand Down Expand Up @@ -108,3 +121,66 @@ func (h *Deployer) installArgs(r latest.HelmRelease, builds []graph.Artifact, o

return args, nil
}

// extractValueFileFromGCSFunc is a function variable that can be mocked in tests
var extractValueFileFromGCSFunc = func(gcsPath, tempDir string, gcs gcs.Gsutil) (string, error) {
return helm.ExtractValueFileFromGCS(gcsPath, tempDir, gcs)
}

// processGCSFlags processes helm flags to handle gs:// URLs in --values flags
func processGCSFlags(flags []string) ([]string, error) {
if len(flags) == 0 {
return flags, nil
}

var processedFlags []string
gcs := gcs.NewGsutil()

for i := 0; i < len(flags); i++ {
flag := flags[i]

// Check for --values flag with equals sign (--values=gs://...)
if strings.HasPrefix(flag, "--values=") {
value := strings.TrimPrefix(flag, "--values=")
if strings.HasPrefix(value, "gs://") {
tempDir, err := os.MkdirTemp("", "helm_values_from_gcs")
if err != nil {
return nil, fmt.Errorf("failed to create temp directory: %w", err)
}
processedValue, err := extractValueFileFromGCSFunc(value, tempDir, gcs)
if err != nil {
return nil, err
}
processedFlags = append(processedFlags, "--values="+processedValue)
} else {
processedFlags = append(processedFlags, flag)
}
} else if flag == "--values" || flag == "-f" {
// Check for --values flag with separate argument (--values gs://... or -f gs://...)
if i+1 < len(flags) {
nextFlag := flags[i+1]
if strings.HasPrefix(nextFlag, "gs://") {
tempDir, err := os.MkdirTemp("", "helm_values_from_gcs")
if err != nil {
return nil, fmt.Errorf("failed to create temp directory: %w", err)
}
processedValue, err := extractValueFileFromGCSFunc(nextFlag, tempDir, gcs)
if err != nil {
return nil, err
}
processedFlags = append(processedFlags, flag, processedValue)
i++ // Skip the next flag since we processed it
} else {
processedFlags = append(processedFlags, flag, nextFlag)
i++
}
} else {
processedFlags = append(processedFlags, flag)
}
} else {
processedFlags = append(processedFlags, flag)
}
}

return processedFlags, nil
}
Comment on lines +131 to +186
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The function processGCSFlags creates temporary directories using os.MkdirTemp for each GCS values file, but it doesn't arrange for them to be cleaned up. This will result in a resource leak, as temporary directories will accumulate on the user's machine. These directories should be cleaned up after the helm command that uses them has finished.

A common pattern in Skaffold is to have the Deployer manage the cleanup of temporary resources. I recommend modifying processGCSFlags to return the paths of all temporary directories it creates. The caller (installArgs) can then add these to a list on the Deployer, and the Deployer.Cleanup method can be updated to remove them.

Loading