Merge "Add document filtering in KRM toolbox by GVK"
This commit is contained in:
commit
ab7bbfce89
@ -66,6 +66,8 @@ The toolbox image has pre-installed `sh` shell,`kubectl` and `calicoctl`.
|
|||||||
## Input bundle usage
|
## 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`
|
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
|
## 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.
|
||||||
|
@ -28,12 +28,19 @@ import (
|
|||||||
"sigs.k8s.io/kustomize/kyaml/kio"
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
|
kyaml "sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
|
|
||||||
|
"opendev.org/airship/airshipctl/pkg/document/plugin/kyamlutils"
|
||||||
"opendev.org/airship/airshipctl/pkg/log"
|
"opendev.org/airship/airshipctl/pkg/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// EnvRenderedBundlePath will be passed to the script, it will contain path to the rendered bundle
|
// EnvRenderedBundlePath will be passed to the script, it will contain path to the rendered bundle
|
||||||
EnvRenderedBundlePath = "RENDERED_BUNDLE_PATH"
|
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"
|
scriptPath = "script.sh"
|
||||||
scriptKey = "script"
|
scriptKey = "script"
|
||||||
bundleFile = "bundle.yaml"
|
bundleFile = "bundle.yaml"
|
||||||
@ -137,6 +144,11 @@ func (c *ScriptRunner) writeBundle(path string, items []*kyaml.RNode) error {
|
|||||||
}
|
}
|
||||||
defer f.Close()
|
defer f.Close()
|
||||||
|
|
||||||
|
items, err = c.FilterBundle(items)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
pipeline := kio.Pipeline{
|
pipeline := kio.Pipeline{
|
||||||
Outputs: []kio.Writer{
|
Outputs: []kio.Writer{
|
||||||
kio.ByteWriter{
|
kio.ByteWriter{
|
||||||
@ -150,3 +162,15 @@ func (c *ScriptRunner) writeBundle(path string, items []*kyaml.RNode) error {
|
|||||||
|
|
||||||
return pipeline.Execute()
|
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)
|
||||||
|
}
|
||||||
|
@ -17,6 +17,7 @@ package main
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@ -24,6 +25,7 @@ import (
|
|||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
v1 "k8s.io/api/core/v1"
|
v1 "k8s.io/api/core/v1"
|
||||||
"sigs.k8s.io/kustomize/kyaml/fn/framework"
|
"sigs.k8s.io/kustomize/kyaml/fn/framework"
|
||||||
|
"sigs.k8s.io/kustomize/kyaml/kio"
|
||||||
"sigs.k8s.io/kustomize/kyaml/yaml"
|
"sigs.k8s.io/kustomize/kyaml/yaml"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -166,3 +168,132 @@ func TestCmdRunCleanup(t *testing.T) {
|
|||||||
}()
|
}()
|
||||||
assert.NoError(t, err)
|
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)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -5,3 +5,4 @@ resources:
|
|||||||
- wait_deploy
|
- wait_deploy
|
||||||
- get_node
|
- get_node
|
||||||
- wait_pods
|
- wait_pods
|
||||||
|
- pause_bmh
|
||||||
|
@ -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
|
@ -0,0 +1,6 @@
|
|||||||
|
configMapGenerator:
|
||||||
|
- name: kubectl-pause-bmh
|
||||||
|
options:
|
||||||
|
disableNameSuffixHash: true
|
||||||
|
files:
|
||||||
|
- script=kubectl_pause_bmh.sh
|
@ -391,3 +391,22 @@ configRef:
|
|||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: KubevalOptions
|
kind: KubevalOptions
|
||||||
name: kubeval-options
|
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
|
||||||
|
@ -366,3 +366,15 @@ config:
|
|||||||
apiVersion: airshipit.org/v1alpha1
|
apiVersion: airshipit.org/v1alpha1
|
||||||
kind: GenericContainer
|
kind: GenericContainer
|
||||||
name: kubectl-wait-pods
|
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
|
||||||
|
@ -17,16 +17,14 @@ set -xe
|
|||||||
#Default wait timeout is 3600 seconds
|
#Default wait timeout is 3600 seconds
|
||||||
export TIMEOUT=${TIMEOUT:-3600}
|
export TIMEOUT=${TIMEOUT:-3600}
|
||||||
export KUBECONFIG=${KUBECONFIG:-"$HOME/.airship/kubeconfig"}
|
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 KUBECONFIG_TARGET_CONTEXT=${KUBECONFIG_TARGET_CONTEXT:-"target-cluster"}
|
||||||
export TARGET_NODE=${TARGET_NODE:-"node01"}
|
|
||||||
export CLUSTER_NAMESPACE=${CLUSTER_NAMESPACE:-"default"}
|
export CLUSTER_NAMESPACE=${CLUSTER_NAMESPACE:-"default"}
|
||||||
|
|
||||||
echo "Check Cluster Status"
|
# Annotating BMH objects with a pause label
|
||||||
kubectl --kubeconfig $KUBECONFIG --context $KUBECONFIG_EPHEMERAL_CONTEXT -n $CLUSTER_NAMESPACE get cluster target-cluster -o json | jq '.status.controlPlaneReady'
|
# 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`
|
||||||
echo "Annotate BMH for target node"
|
# and find ConfigMap with name kubectl-get-pods
|
||||||
kubectl --kubeconfig $KUBECONFIG --context $KUBECONFIG_EPHEMERAL_CONTEXT -n $CLUSTER_NAMESPACE annotate bmh $TARGET_NODE baremetalhost.metal3.io/paused=true
|
airshipctl phase run kubectl-pause-bmh --debug
|
||||||
|
|
||||||
echo "Move Cluster Object to Target Cluster"
|
echo "Move Cluster Object to Target Cluster"
|
||||||
airshipctl phase run clusterctl-move
|
airshipctl phase run clusterctl-move
|
||||||
|
Loading…
Reference in New Issue
Block a user