Merge "Add document filtering in KRM toolbox by GVK"

This commit is contained in:
Zuul 2021-04-30 01:15:32 +00:00 committed by Gerrit Code Review
commit ab7bbfce89
9 changed files with 227 additions and 12 deletions

View File

@ -66,6 +66,8 @@ The toolbox image has pre-installed `sh` shell,`kubectl` and `calicoctl`.
## Input bundle usage
The KRM function writes to filesystem input bundle specified in `documentEntryPoint` in phase declaration and imports the path to this bundle in `RENDERED_BUNDLE_PATH` environment variable. For example it can be used with `calicoctl` as `calicoctl apply -f $RENDERED_BUNDLE_PATH`
Documents can be filtered by group, version and kind. You need to set `RESOURCE_GROUP_FILTER`, `RESOURCE_VERSION_FILTER` and/or`RESOURCE_KIND_FILTER` in executor definition to enable filtering.
## Important notes
The script must write to STDOUT valid yaml or redirect output to STDERR otherwise phase will fail with `mapping values are not allowed in this context`
1. The script must write to STDOUT valid yaml or redirect output to STDERR otherwise phase will fail with `mapping values are not allowed in this context`
2. All shell scripts must begin with `set -xe`. This allows errors to be passed from the container to the airshipctl itself. Without this flags the container will never fail.

View File

@ -28,12 +28,19 @@ import (
"sigs.k8s.io/kustomize/kyaml/kio"
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
"opendev.org/airship/airshipctl/pkg/document/plugin/kyamlutils"
"opendev.org/airship/airshipctl/pkg/log"
)
const (
// EnvRenderedBundlePath will be passed to the script, it will contain path to the rendered bundle
EnvRenderedBundlePath = "RENDERED_BUNDLE_PATH"
// ResourceGroupFilter used for filtering input bundle by document group
ResourceGroupFilter = "RESOURCE_GROUP_FILTER"
// ResourceVersionFilter used for filtering input bundle by document version
ResourceVersionFilter = "RESOURCE_VERSION_FILTER"
// ResourceKindFilter used for filtering input bundle by document kind
ResourceKindFilter = "RESOURCE_KIND_FILTER"
scriptPath = "script.sh"
scriptKey = "script"
bundleFile = "bundle.yaml"
@ -137,6 +144,11 @@ func (c *ScriptRunner) writeBundle(path string, items []*kyaml.RNode) error {
}
defer f.Close()
items, err = c.FilterBundle(items)
if err != nil {
return err
}
pipeline := kio.Pipeline{
Outputs: []kio.Writer{
kio.ByteWriter{
@ -150,3 +162,15 @@ func (c *ScriptRunner) writeBundle(path string, items []*kyaml.RNode) error {
return pipeline.Execute()
}
// FilterBundle uses for filtering input documents
func (c *ScriptRunner) FilterBundle(items []*kyaml.RNode) ([]*kyaml.RNode, error) {
group := os.Getenv(ResourceGroupFilter)
version := os.Getenv(ResourceVersionFilter)
kind := os.Getenv(ResourceKindFilter)
log.Printf("Filtering input bundle by Group: %s, Version: %s, Kind: %s",
group, version, kind)
return kyamlutils.DocumentSelector{}.
ByGVK(group, version, kind).
Filter(items)
}

View File

@ -17,6 +17,7 @@ package main
import (
"bytes"
"io/ioutil"
"os"
"path/filepath"
"testing"
@ -24,6 +25,7 @@ import (
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
"sigs.k8s.io/kustomize/kyaml/fn/framework"
"sigs.k8s.io/kustomize/kyaml/kio"
"sigs.k8s.io/kustomize/kyaml/yaml"
)
@ -166,3 +168,132 @@ func TestCmdRunCleanup(t *testing.T) {
}()
assert.NoError(t, err)
}
func TestFilterBundle(t *testing.T) {
rawDocs := `---
apiVersion: test/v1
kind: Pod
metadata:
name: t1
---
apiVersion: test/v1
kind: Secret
metadata:
name: t2
---
apiVersion: test/v1beta1
kind: Deployment
metadata:
name: t3
`
docs, err := (&kio.ByteReader{Reader: bytes.NewBufferString(rawDocs)}).Read()
require.NoError(t, err)
testCases := []struct {
name string
inputDocs []*yaml.RNode
groupFilter string
versionFilter string
kindFilter string
errContains string
expectedDocs string
}{
{
name: "Correct documents",
inputDocs: docs,
groupFilter: "test",
versionFilter: "v1",
kindFilter: "Pod",
errContains: "",
expectedDocs: `apiVersion: test/v1
kind: Pod
metadata:
name: t1
`,
},
{
name: "Empty kind",
inputDocs: docs,
groupFilter: "test",
versionFilter: "v1beta1",
kindFilter: "",
errContains: "",
expectedDocs: `apiVersion: test/v1beta1
kind: Deployment
metadata:
name: t3
`,
},
{
name: "Empty all filters",
inputDocs: docs,
groupFilter: "",
versionFilter: "",
kindFilter: "",
errContains: "",
expectedDocs: `apiVersion: test/v1
kind: Pod
metadata:
name: t1
---
apiVersion: test/v1
kind: Secret
metadata:
name: t2
---
apiVersion: test/v1beta1
kind: Deployment
metadata:
name: t3
`,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
cMap := &v1.ConfigMap{
Data: map[string]string{
dataKey: script,
},
}
input, err := yaml.Parse(inputString)
require.NoError(t, err)
stderr := bytes.NewBuffer([]byte{})
stdout := bytes.NewBuffer([]byte{})
os.Setenv(ResourceGroupFilter, tc.groupFilter)
defer os.Unsetenv(ResourceGroupFilter)
os.Setenv(ResourceVersionFilter, tc.versionFilter)
defer os.Unsetenv(ResourceVersionFilter)
os.Setenv(ResourceKindFilter, tc.kindFilter)
defer os.Unsetenv(ResourceKindFilter)
cmd := &ScriptRunner{
ScriptFile: targetFile,
WorkDir: dir,
DataKey: dataKey,
ErrStream: stderr,
OutStream: stdout,
ResourceList: &framework.ResourceList{Items: []*yaml.RNode{input}},
ConfigMap: cMap,
RenderedBundleFile: bundlePath,
}
filteredDocs, err := cmd.FilterBundle(tc.inputDocs)
if tc.errContains != "" {
require.Error(t, err)
assert.Contains(t, err.Error(), tc.errContains)
} else {
require.NoError(t, err)
buf := &bytes.Buffer{}
err = kio.ByteWriter{Writer: buf}.Write(filteredDocs)
assert.Equal(t, tc.expectedDocs, buf.String())
require.NoError(t, err)
}
})
}
}

View File

@ -5,3 +5,4 @@ resources:
- wait_deploy
- get_node
- wait_pods
- pause_bmh

View File

@ -0,0 +1,22 @@
#!/bin/sh
# 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.
set -xe
echo "Annotating BMH objects with a pause label in cluster with kubectl context ${KCTL_CONTEXT}" 1>&2
kubectl annotate \
--context $KCTL_CONTEXT \
--namespace $CLUSTER_NAMESPACE \
--overwrite \
-f $RENDERED_BUNDLE_PATH baremetalhost.metal3.io/paused=true 1>&2

View File

@ -0,0 +1,6 @@
configMapGenerator:
- name: kubectl-pause-bmh
options:
disableNameSuffixHash: true
files:
- script=kubectl_pause_bmh.sh

View File

@ -391,3 +391,22 @@ configRef:
apiVersion: airshipit.org/v1alpha1
kind: KubevalOptions
name: kubeval-options
---
apiVersion: airshipit.org/v1alpha1
kind: GenericContainer
metadata:
name: kubectl-pause-bmh
labels:
airshipit.org/deploy-k8s: "false"
spec:
image: quay.io/airshipit/toolbox:latest
hostNetwork: true
envVars:
- CLUSTER_NAMESPACE=default
- RESOURCE_GROUP_FILTER=metal3.io
- RESOURCE_VERSION_FILTER=v1alpha1
- RESOURCE_KIND_FILTER=BareMetalHost
configRef:
kind: ConfigMap
name: kubectl-pause-bmh
apiVersion: v1

View File

@ -366,3 +366,15 @@ config:
apiVersion: airshipit.org/v1alpha1
kind: GenericContainer
name: kubectl-wait-pods
---
apiVersion: airshipit.org/v1alpha1
kind: Phase
metadata:
name: kubectl-pause-bmh
clusterName: ephemeral-cluster
config:
executorRef:
apiVersion: airshipit.org/v1alpha1
kind: GenericContainer
name: kubectl-pause-bmh
documentEntryPoint: ephemeral/controlplane

View File

@ -17,16 +17,14 @@ set -xe
#Default wait timeout is 3600 seconds
export TIMEOUT=${TIMEOUT:-3600}
export KUBECONFIG=${KUBECONFIG:-"$HOME/.airship/kubeconfig"}
export KUBECONFIG_EPHEMERAL_CONTEXT=${KUBECONFIG_EPHEMERAL_CONTEXT:-"ephemeral-cluster"}
export KUBECONFIG_TARGET_CONTEXT=${KUBECONFIG_TARGET_CONTEXT:-"target-cluster"}
export TARGET_NODE=${TARGET_NODE:-"node01"}
export CLUSTER_NAMESPACE=${CLUSTER_NAMESPACE:-"default"}
echo "Check Cluster Status"
kubectl --kubeconfig $KUBECONFIG --context $KUBECONFIG_EPHEMERAL_CONTEXT -n $CLUSTER_NAMESPACE get cluster target-cluster -o json | jq '.status.controlPlaneReady'
echo "Annotate BMH for target node"
kubectl --kubeconfig $KUBECONFIG --context $KUBECONFIG_EPHEMERAL_CONTEXT -n $CLUSTER_NAMESPACE annotate bmh $TARGET_NODE baremetalhost.metal3.io/paused=true
# Annotating BMH objects with a pause label
# Scripts for this phase placed in manifests/function/phase-helpers/pause_bmh/
# To get ConfigMap for this phase, execute `airshipctl phase render --source config -k ConfigMap`
# and find ConfigMap with name kubectl-get-pods
airshipctl phase run kubectl-pause-bmh --debug
echo "Move Cluster Object to Target Cluster"
airshipctl phase run clusterctl-move