Add helmfile state validate policy (#592)

This commit is contained in:
yxxhero 2022-12-21 10:49:31 +08:00 committed by GitHub
parent 8ec51c2826
commit d8d0bf830a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 148 additions and 8 deletions

2
go.mod
View File

@ -32,6 +32,8 @@ require (
k8s.io/apimachinery v0.26.0
)
replace gopkg.in/yaml.v3 => github.com/colega/go-yaml-yaml v0.0.0-20220720070545-aaba007ebc22
require (
cloud.google.com/go v0.102.1 // indirect
cloud.google.com/go/compute v1.7.0 // indirect

9
go.sum
View File

@ -223,6 +223,8 @@ github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWH
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/colega/go-yaml-yaml v0.0.0-20220720070545-aaba007ebc22 h1:uVG5v+c6ndz9seCorYjQmlVlPbh3OMcMWJzAJZWdM/g=
github.com/colega/go-yaml-yaml v0.0.0-20220720070545-aaba007ebc22/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
github.com/containerd/cgroups v1.0.3 h1:ADZftAkglvCiD44c77s5YmMqaP2pzVCFZvBmAlBdAP4=
github.com/containerd/containerd v1.6.6 h1:xJNPhbrmz8xAMDNoVjHy9YHtWwEQNS+CDkcIRh7t8Y0=
github.com/containerd/containerd v1.6.6/go.mod h1:ZoP1geJldzCVY3Tonoz7b1IXk8rIX0Nltt5QE4OMNk0=
@ -1391,13 +1393,6 @@ gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200121175148-a6ecf24a6d71/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107172259-749611fa9fcc/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=

37
pkg/policy/checker.go Normal file
View File

@ -0,0 +1,37 @@
// Package policy provides a policy checker for the helmfile state.
package policy
import (
"errors"
)
var (
EnvironmentsAndReleasesWithinSameYamlPartErr = errors.New("environments and releases cannot be defined within the same YAML part. Use --- to extract the environments into a dedicated part")
)
// checkerFunc is a function that checks the helmState.
type checkerFunc func(map[string]interface{}) (bool, error)
func forbidEnvironmentsWithReleases(releaseState map[string]interface{}) (bool, error) {
// forbid environments and releases to be defined at the same yaml part
_, hasEnvironments := releaseState["environments"]
_, hasReleases := releaseState["releases"]
if hasEnvironments && hasReleases {
return false, EnvironmentsAndReleasesWithinSameYamlPartErr
}
return false, nil
}
var checkerFuncs = []checkerFunc{
forbidEnvironmentsWithReleases,
}
// Checker is a policy checker for the helmfile state.
func Checker(helmState map[string]interface{}) (bool, error) {
for _, fn := range checkerFuncs {
if isStrict, err := fn(helmState); err != nil {
return isStrict, err
}
}
return false, nil
}

View File

@ -0,0 +1,54 @@
package policy
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestForbidEnvironmentsWithReleases(t *testing.T) {
testCases := []struct {
name string
helmState map[string]interface{}
expectedErr bool
isStrict bool
}{
{
name: "no error when only releases",
helmState: map[string]interface{}{
"releases": interface{}(nil),
},
expectedErr: false,
isStrict: false,
},
{
name: "no error when only environments",
helmState: map[string]interface{}{
"environments": map[string]interface{}{},
},
expectedErr: false,
isStrict: false,
},
{
name: "error when both releases and environments",
helmState: map[string]interface{}{
"environments": interface{}(nil),
"releases": interface{}(nil),
},
expectedErr: true,
isStrict: false,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
isStrict, err := forbidEnvironmentsWithReleases(tc.helmState)
require.Equal(t, tc.isStrict, isStrict, "expected isStrict=%v, got=%v", tc.isStrict, isStrict)
if tc.expectedErr {
require.ErrorIsf(t, err, EnvironmentsAndReleasesWithinSameYamlPartErr, "expected error=%v, got=%v", EnvironmentsAndReleasesWithinSameYamlPartErr, err)
} else {
require.NoError(t, err, "expected no error but got error: %v", err)
}
})
}
}

View File

@ -29,6 +29,7 @@ import (
"github.com/helmfile/helmfile/pkg/filesystem"
"github.com/helmfile/helmfile/pkg/helmexec"
"github.com/helmfile/helmfile/pkg/maputil"
"github.com/helmfile/helmfile/pkg/policy"
"github.com/helmfile/helmfile/pkg/remote"
"github.com/helmfile/helmfile/pkg/tmpl"
)
@ -83,6 +84,30 @@ type ReleaseSetSpec struct {
LockFile string `yaml:"lockFilePath,omitempty"`
}
// helmStateAlias is helm state alias
type helmStateAlias HelmState
func (hs HelmState) MarshalYAML() (interface{}, error) {
return helmStateAlias(hs), nil
}
func (hs *HelmState) UnmarshalYAML(value *yaml.Node) error {
helmStateInfo := make(map[string]interface{})
if err := value.DecodeWithOptions(&helmStateInfo, yaml.DecodeOptions{KnownFields: true}); err != nil {
return err
}
isStrict, err := policy.Checker(helmStateInfo)
if err != nil {
if isStrict {
return err
}
fmt.Fprintf(os.Stderr, "Warning: %v\n", err)
}
return value.DecodeWithOptions((*helmStateAlias)(hs), yaml.DecodeOptions{KnownFields: true})
}
// HelmState structure for the helmfile
type HelmState struct {
basePath string

View File

@ -0,0 +1,8 @@
localChartRepoServer:
enabled: true
port: 18083
chartifyTempDir: envs_releases_within_same_yaml_part
helmfileArgs:
- --environment
- prod
- template

View File

@ -0,0 +1,11 @@
environments:
prod:
staging:
releases:
- name: raw
chart: ../../charts/raw-0.0.1
values:
- templates:
- |
chartVersion: {{`{{ .Chart.Version }}`}}

View File

@ -0,0 +1,8 @@
Warning: environments and releases cannot be defined within the same YAML part. Use --- to extract the environments into a dedicated part
Warning: environments and releases cannot be defined within the same YAML part. Use --- to extract the environments into a dedicated part
Building dependency release=raw, chart=../../charts/raw-0.0.1
Templating release=raw, chart=../../charts/raw-0.0.1
---
# Source: raw/templates/resources.yaml
chartVersion: 0.0.1

View File

@ -5,7 +5,7 @@ repositories:
releases:
- name: elasticsearch
chart: bitnami/elasticsearch
version: 17.5.7
version: 19.0.1
jsonPatches:
- target:
version: v1