Add policy package for applying policy

Move the policy applying code to a separate package so that it can be
tested and maintained independent of the other components, and imported
to other packages where needed.

Signed-off-by: Sunny <github@darkowlzz.space>
This commit is contained in:
Sunny 2024-03-17 12:39:51 +00:00
parent 5b3b1e5fba
commit 83865d39c9
8 changed files with 299 additions and 0 deletions

View File

@ -0,0 +1,66 @@
/*
Copyright 2024 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package policy
import (
"context"
"errors"
"fmt"
securejoin "github.com/cyphar/filepath-securejoin"
"github.com/fluxcd/pkg/runtime/logger"
"sigs.k8s.io/controller-runtime/pkg/log"
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
"github.com/fluxcd/image-automation-controller/pkg/update"
)
var (
// ErrNoUpdateStrategy is an update error when the update strategy is not
// specified.
ErrNoUpdateStrategy = errors.New("no update strategy")
// ErrUnsupportedUpdateStrategy is an update error when the provided update
// strategy is not supported.
ErrUnsupportedUpdateStrategy = errors.New("unsupported update strategy")
)
// ApplyPolicies applies the given set of policies on the source present in the
// workDir based on the provided ImageUpdateAutomation configuration.
func ApplyPolicies(ctx context.Context, workDir string, obj *imagev1.ImageUpdateAutomation, policies []imagev1_reflect.ImagePolicy) (update.ResultV2, error) {
var result update.ResultV2
if obj.Spec.Update == nil {
return result, ErrNoUpdateStrategy
}
if obj.Spec.Update.Strategy != imagev1.UpdateStrategySetters {
return result, fmt.Errorf("%w: %s", ErrUnsupportedUpdateStrategy, obj.Spec.Update.Strategy)
}
// Resolve the path to the manifests to apply policies on.
manifestPath := workDir
if obj.Spec.Update.Path != "" {
p, err := securejoin.SecureJoin(workDir, obj.Spec.Update.Path)
if err != nil {
return result, fmt.Errorf("failed to secure join manifest path: %w", err)
}
manifestPath = p
}
tracelog := log.FromContext(ctx).V(logger.TraceLevel)
return update.UpdateV2WithSetters(tracelog, manifestPath, manifestPath, policies)
}

View File

@ -0,0 +1,173 @@
/*
Copyright 2024 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package policy
import (
"context"
"path/filepath"
"testing"
. "github.com/onsi/gomega"
"github.com/otiai10/copy"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
imagev1_reflect "github.com/fluxcd/image-reflector-controller/api/v1beta2"
imagev1 "github.com/fluxcd/image-automation-controller/api/v1beta2"
"github.com/fluxcd/image-automation-controller/internal/testutil"
"github.com/fluxcd/image-automation-controller/pkg/test"
"github.com/fluxcd/image-automation-controller/pkg/update"
)
func testdataPath(path string) string {
return filepath.Join("testdata", path)
}
func Test_applyPolicies(t *testing.T) {
tests := []struct {
name string
updateStrategy *imagev1.UpdateStrategy
policyLatestImages map[string]string
targetPolicyName string
replaceMarkerFunc func(g *WithT, path string, policyKey types.NamespacedName)
inputPath string
expectedPath string
wantErr bool
wantResult update.Result
}{
{
name: "valid update strategy and one policy",
updateStrategy: &imagev1.UpdateStrategy{
Strategy: imagev1.UpdateStrategySetters,
},
policyLatestImages: map[string]string{
"policy1": "helloworld:1.0.1",
},
targetPolicyName: "policy1",
inputPath: testdataPath("appconfig"),
expectedPath: testdataPath("appconfig-setters-expected"),
wantErr: false,
},
{
name: "no update strategy",
updateStrategy: nil,
wantErr: true,
},
{
name: "unknown update strategy",
updateStrategy: &imagev1.UpdateStrategy{
Strategy: "foo",
},
wantErr: true,
},
{
name: "valid update strategy and multiple policies",
updateStrategy: &imagev1.UpdateStrategy{
Strategy: imagev1.UpdateStrategySetters,
},
policyLatestImages: map[string]string{
"policy1": "foo:1.1.1",
"policy2": "helloworld:1.0.1",
"policy3": "bar:2.2.2",
},
targetPolicyName: "policy2",
inputPath: testdataPath("appconfig"),
expectedPath: testdataPath("appconfig-setters-expected"),
wantErr: false,
},
{
name: "valid update strategy with update path",
updateStrategy: &imagev1.UpdateStrategy{
Strategy: imagev1.UpdateStrategySetters,
Path: "./yes",
},
policyLatestImages: map[string]string{
"policy1": "helloworld:1.0.1",
},
targetPolicyName: "policy1",
replaceMarkerFunc: func(g *WithT, path string, policyKey types.NamespacedName) {
g.Expect(testutil.ReplaceMarker(filepath.Join(path, "yes", "deploy.yaml"), policyKey)).ToNot(HaveOccurred())
g.Expect(testutil.ReplaceMarker(filepath.Join(path, "no", "deploy.yaml"), policyKey)).ToNot(HaveOccurred())
},
inputPath: testdataPath("pathconfig"),
expectedPath: testdataPath("pathconfig-expected"),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
testNS := "test-ns"
workDir := t.TempDir()
// Create all the policy objects.
policyList := []imagev1_reflect.ImagePolicy{}
for name, image := range tt.policyLatestImages {
policy := &imagev1_reflect.ImagePolicy{}
policy.Name = name
policy.Namespace = testNS
policy.Status = imagev1_reflect.ImagePolicyStatus{
LatestImage: image,
}
policyList = append(policyList, *policy)
}
targetPolicyKey := types.NamespacedName{
Name: tt.targetPolicyName, Namespace: testNS,
}
if tt.inputPath != "" {
g.Expect(copy.Copy(tt.inputPath, workDir)).ToNot(HaveOccurred())
// Update the test files with the target policy.
if tt.replaceMarkerFunc != nil {
tt.replaceMarkerFunc(g, workDir, targetPolicyKey)
} else {
g.Expect(testutil.ReplaceMarker(filepath.Join(workDir, "deploy.yaml"), targetPolicyKey)).ToNot(HaveOccurred())
}
}
updateAuto := &imagev1.ImageUpdateAutomation{}
updateAuto.Name = "test-update"
updateAuto.Namespace = testNS
updateAuto.Spec = imagev1.ImageUpdateAutomationSpec{
Update: tt.updateStrategy,
}
scheme := runtime.NewScheme()
imagev1_reflect.AddToScheme(scheme)
imagev1.AddToScheme(scheme)
_, err := ApplyPolicies(context.TODO(), workDir, updateAuto, policyList)
g.Expect(err != nil).To(Equal(tt.wantErr))
// Check the results if there wasn't any error.
if !tt.wantErr {
expected := t.TempDir()
copy.Copy(tt.expectedPath, expected)
// Update the markers in the expected test data.
if tt.replaceMarkerFunc != nil {
tt.replaceMarkerFunc(g, expected, targetPolicyKey)
} else {
g.Expect(testutil.ReplaceMarker(filepath.Join(expected, "deploy.yaml"), targetPolicyKey)).ToNot(HaveOccurred())
}
test.ExpectMatchingDirectories(g, workDir, expected)
}
})
}
}

View File

@ -0,0 +1,10 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
spec:
template:
spec:
containers:
- name: hello
image: helloworld:1.0.1 # SETTER_SITE

View File

@ -0,0 +1,10 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: test
spec:
template:
spec:
containers:
- name: hello
image: helloworld:1.0.0 # SETTER_SITE

View File

@ -0,0 +1,10 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: update-no
spec:
template:
spec:
containers:
- name: hello
image: helloworld:1.0.0 # SETTER_SITE

View File

@ -0,0 +1,10 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: update-yes
spec:
template:
spec:
containers:
- name: hello
image: helloworld:1.0.1 # SETTER_SITE

View File

@ -0,0 +1,10 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: update-no
spec:
template:
spec:
containers:
- name: hello
image: helloworld:1.0.0 # SETTER_SITE

View File

@ -0,0 +1,10 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: update-yes
spec:
template:
spec:
containers:
- name: hello
image: helloworld:1.0.0 # SETTER_SITE