Add helm-secrets-encrypted values template file (#1701)

Secret files ending with .gotmpl are now also rendered as a gotemplate.

```
releases:
- name: myapp
  secrets:
  - secrets.yaml.gotmpl
```

Note that currently, .gotmpl files must be valid YAML files as well.

The expected use-case of this feature is to compose a YAML array from values and encrypted secrets.

Without this feature, you would have tried to do something like the below, which didn't work.

**Example (doesn't work!)**

`values.yaml.gotmpl`:

```
environment:
  -   name: MY_EXTERNAL_IP
      value: |
          {{ exec "./get-external-ip.sh" (list "") }}
```

`secrets.yaml`:
```
_sops:
  #...
environment:
  - name: MY_SECRET_VALUE
    value: (encrypted by sops)
```

`helmfile.yaml`:

```
releases:
- name: foo
  values:
  - values.yaml
  secrets:
  - secrets.yaml
```

This doesn't work because `values.yaml` and the decrypted `secrets.yaml` are passed to `helm` to be merged, and helm overrides the array instead of merging or concatenating the arrays.

**Example (works!)**

Instead of `values.yaml` and `secrets.yaml`, you provide a single `secrets.yaml.gotmpl` that is a valid YAML and encrypted by sops:

```
_sops:
  #...
environment:
  -   name: MY_EXTERNAL_IP
      value: |
          {{ exec "./get-external-ip.sh" (list "") }}
  - name: MY_SECRET_VALUE
    value: (encrypted by sops)
```

`helmfile.yaml`:

```
releases:
- name: foo
  secrets:
  - secrets.yaml.gotmpl
```

Helmfile decrypts the gotmpl by handing it over to helm-secrets and then renders the result as a gotmpl file. The end result is that you have a two-element array `environments` that can be just passed to helm.

Resolves #1700

Co-authored-by: Yusuke Kuoka <ykuoka@gmail.com>
This commit is contained in:
Philipp Hossner
2021-04-06 14:20:42 +09:00
committed by GitHub
co-authored by Yusuke Kuoka
parent a161796dc4
commit 85accf7330
19 changed files with 186 additions and 47 deletions
+11 -2
View File
@@ -278,7 +278,14 @@ func (helm *execer) DecryptSecret(context HelmContext, name string, flags ...str
if len(decSuffix) == 0 {
decSuffix = ".yaml.dec"
}
decFilename := strings.Replace(absPath, ".yaml", decSuffix, 1)
// helm secrets replaces the extension with its suffix ONLY when the extension is ".yaml"
var decFilename string
if strings.HasSuffix(absPath, ".yaml") {
decFilename = strings.Replace(absPath, ".yaml", decSuffix, 1)
} else {
decFilename = absPath + decSuffix
}
secretBytes, err := ioutil.ReadFile(decFilename)
if err != nil {
@@ -308,7 +315,9 @@ func (helm *execer) DecryptSecret(context HelmContext, name string, flags ...str
if tempFile == nil {
tempFile = func(content []byte) (string, error) {
tmpFile, err := ioutil.TempFile("", "secret")
dir := filepath.Dir(name)
extension := filepath.Ext(name)
tmpFile, err := ioutil.TempFile(dir, "secret*"+extension)
if err != nil {
return "", err
}
+23
View File
@@ -289,6 +289,29 @@ Found secret in cache %s/secretName
}
}
func Test_DecryptSecretWithGotmpl(t *testing.T) {
var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug")
helm := MockExecer(logger, "dev")
tmpFilePath := "path/to/temp/file"
helm.writeTempFile = func(content []byte) (string, error) {
return tmpFilePath, nil
}
secretName := "secretName.yaml.gotmpl"
_, decryptErr := helm.DecryptSecret(HelmContext{}, secretName)
cwd, err := filepath.Abs(".")
if err != nil {
t.Errorf("Error: %v", err)
}
expected := fmt.Sprintf(`%s/%s.yaml.dec`, cwd, secretName)
if d := cmp.Diff(expected, decryptErr.(*os.PathError).Path); d != "" {
t.Errorf("helmexec.DecryptSecret(): want (-), got (+):\n%s", d)
}
}
func Test_DiffRelease(t *testing.T) {
var buffer bytes.Buffer
logger := NewLogger(&buffer, "debug")
+10 -2
View File
@@ -2601,7 +2601,7 @@ func (st *HelmState) generateVanillaValuesFiles(release *ReleaseSpec) ([]string,
}
func (st *HelmState) generateSecretValuesFiles(helm helmexec.Interface, release *ReleaseSpec, workerIndex int) ([]string, error) {
var generatedFiles []string
var generatedDecryptedFiles []interface{}
for _, v := range release.Secrets {
var (
@@ -2652,8 +2652,16 @@ func (st *HelmState) generateSecretValuesFiles(helm helmexec.Interface, release
if err != nil {
return nil, err
}
defer func() {
_ = os.Remove(valfile)
}()
generatedFiles = append(generatedFiles, valfile)
generatedDecryptedFiles = append(generatedDecryptedFiles, valfile)
}
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, generatedDecryptedFiles, release.MissingFileHandler)
if err != nil {
return nil, err
}
return generatedFiles, nil