Allow container config to be referenced as objects

Now GenericContainer input config can be referenced as another
object inside the config bundle (with phase and executor objects).

Change-Id: Iff35e0844b1e9ce4beb72d939e229410208dcb16
This commit is contained in:
Kostiantyn Kalynovskyi 2021-02-11 00:21:47 +00:00
parent 40f24de8b0
commit efc4399e17
8 changed files with 117 additions and 24 deletions

View File

@ -15,6 +15,7 @@
package v1alpha1
import (
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
@ -38,8 +39,15 @@ type GenericContainer struct {
// Holds container configuration
Spec GenericContainerSpec `json:"spec,omitempty"`
// Config for the RunFns function in a custom format
// Config will be passed to stdin of the container togather with other objects
// more information on easy ways to consume the config can be found here
// https://googlecontainertools.github.io/kpt/guides/producer/functions/golang/
Config string `json:"config,omitempty"`
// Reference is a reference to a configuration object, that must reside in the same
// bundle as this GenericContainer object, if specified, Config string will be
// ignored and referenced object in ConfigRef will be used into the Config string
// instead and passed further into the container stdin
ConfigRef *v1.ObjectReference `json:"configRef,omitempty"`
}
// GenericContainerType specify type of the container, there are currently two types:

View File

@ -358,6 +358,11 @@ func (in *GenericContainer) DeepCopyInto(out *GenericContainer) {
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.Spec.DeepCopyInto(&out.Spec)
if in.ConfigRef != nil {
in, out := &in.ConfigRef, &out.ConfigRef
*out = new(v1.ObjectReference)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericContainer.

View File

@ -50,7 +50,7 @@ func TestNewBMHExecutor(t *testing.T) {
executor, err := executors.NewBaremetalExecutor(ifc.ExecutorConfig{
ExecutorDocument: execDoc,
BundleFactory: testBundleFactory(singleExecutorBundlePath),
Helper: makeDefaultHelper(t, "../testdata"),
Helper: makeDefaultHelper(t, "../testdata", defaultMetadataPath),
})
assert.NoError(t, err)
assert.NotNil(t, executor)
@ -64,7 +64,7 @@ func TestNewBMHExecutor(t *testing.T) {
executor, actualErr := executors.NewBaremetalExecutor(ifc.ExecutorConfig{
ExecutorDocument: execDoc,
BundleFactory: testBundleFactory(singleExecutorBundlePath),
Helper: makeDefaultHelper(t, "../testdata"),
Helper: makeDefaultHelper(t, "../testdata", defaultMetadataPath),
})
assert.Equal(t, exepectedErr, actualErr)
assert.Nil(t, executor)
@ -121,7 +121,7 @@ func TestBMHExecutorRun(t *testing.T) {
executor, err := executors.NewBaremetalExecutor(ifc.ExecutorConfig{
ExecutorDocument: tt.execDoc,
BundleFactory: testBundleFactory(singleExecutorBundlePath),
Helper: makeDefaultHelper(t, "../testdata/"),
Helper: makeDefaultHelper(t, "../testdata/", defaultMetadataPath),
})
require.NoError(t, err)
require.NotNil(t, executor)
@ -180,7 +180,7 @@ func TestBMHValidate(t *testing.T) {
executor, err := executors.NewBaremetalExecutor(ifc.ExecutorConfig{
ExecutorDocument: tt.execDoc,
BundleFactory: testBundleFactory(singleExecutorBundlePath),
Helper: makeDefaultHelper(t, "../testdata/"),
Helper: makeDefaultHelper(t, "../testdata/", defaultMetadataPath),
})
require.NoError(t, err)
require.NotNil(t, executor)
@ -202,7 +202,7 @@ func TestBMHManagerRender(t *testing.T) {
executor, err := executors.NewBaremetalExecutor(ifc.ExecutorConfig{
ExecutorDocument: execDoc,
BundleFactory: testBundleFactory(singleExecutorBundlePath),
Helper: makeDefaultHelper(t, "../testdata"),
Helper: makeDefaultHelper(t, "../testdata", defaultMetadataPath),
})
require.NoError(t, err)
require.NotNil(t, executor)

View File

@ -77,7 +77,7 @@ func TestNewExecutor(t *testing.T) {
}{
{
name: "New Clusterctl Executor",
helper: makeDefaultHelper(t, "../../clusterctl/client/testdata"),
helper: makeDefaultHelper(t, "../../clusterctl/client/testdata", defaultMetadataPath),
},
}
for _, test := range testCases {
@ -165,7 +165,7 @@ func TestExecutorRun(t *testing.T) {
executor, err := executors.NewClusterctlExecutor(
ifc.ExecutorConfig{
ExecutorDocument: tt.cfgDoc,
Helper: makeDefaultHelper(t, "../../clusterctl/client/testdata"),
Helper: makeDefaultHelper(t, "../../clusterctl/client/testdata", defaultMetadataPath),
KubeConfig: kubeCfg,
ClusterMap: tt.clusterMap,
})
@ -196,7 +196,7 @@ func TestExecutorValidate(t *testing.T) {
executor, err := executors.NewClusterctlExecutor(
ifc.ExecutorConfig{
ExecutorDocument: sampleCfgDoc,
Helper: makeDefaultHelper(t, "../../clusterctl/client/testdata"),
Helper: makeDefaultHelper(t, "../../clusterctl/client/testdata", defaultMetadataPath),
})
require.NoError(t, err)
expectedErr := airerrors.ErrNotImplemented{}
@ -209,7 +209,7 @@ func TestExecutorRender(t *testing.T) {
executor, err := executors.NewClusterctlExecutor(
ifc.ExecutorConfig{
ExecutorDocument: sampleCfgDoc,
Helper: makeDefaultHelper(t, "../../clusterctl/client/testdata"),
Helper: makeDefaultHelper(t, "../../clusterctl/client/testdata", defaultMetadataPath),
})
require.NoError(t, err)
actualOut := &bytes.Buffer{}

View File

@ -29,6 +29,10 @@ import (
"opendev.org/airship/airshipctl/pkg/phase/ifc"
)
const (
defaultMetadataPath = "metadata.yaml"
)
func TestRegisterExecutor(t *testing.T) {
testCases := []struct {
name string
@ -100,11 +104,11 @@ func TestRegisterExecutor(t *testing.T) {
}
}
func makeDefaultHelper(t *testing.T, targetPath string) ifc.Helper {
func makeDefaultHelper(t *testing.T, targetPath, metaPath string) ifc.Helper {
t.Helper()
cfg := config.NewConfig()
cfg.Manifests[config.AirshipDefaultManifest].TargetPath = targetPath
cfg.Manifests[config.AirshipDefaultManifest].MetadataPath = "metadata.yaml"
cfg.Manifests[config.AirshipDefaultManifest].MetadataPath = metaPath
cfg.Manifests[config.AirshipDefaultManifest].Repositories[config.DefaultTestPhaseRepo].URLString = ""
cfg.SetLoadedConfigPath(".")
helper, err := phase.NewHelper(cfg)

View File

@ -25,6 +25,7 @@ import (
"opendev.org/airship/airshipctl/pkg/document"
"opendev.org/airship/airshipctl/pkg/errors"
"opendev.org/airship/airshipctl/pkg/events"
"opendev.org/airship/airshipctl/pkg/log"
"opendev.org/airship/airshipctl/pkg/phase/ifc"
)
@ -38,6 +39,7 @@ type ContainerExecutor struct {
ClientFunc container.ClientV1Alpha1FactoryFunc
ExecutorBundle document.Bundle
ExecutorDocument document.Document
Helper ifc.Helper
}
// NewContainerExecutor creates instance of phase executor
@ -66,8 +68,8 @@ func NewContainerExecutor(cfg ifc.ExecutorConfig) (ifc.Executor, error) {
ExecutorDocument: cfg.ExecutorDocument,
// TODO extend tests with proper client, make it interface
ClientFunc: container.NewClientV1Alpha1,
Container: apiObj,
Helper: cfg.Helper,
Container: apiObj,
}, nil
}
@ -93,6 +95,10 @@ func (c *ContainerExecutor) Run(evtCh chan events.Event, opts ifc.RunOptions) {
// set output only if the output if resulting directory is not defined
output = os.Stdout
}
if err = c.setConfig(); err != nil {
handleError(evtCh, err)
return
}
// TODO check the executor type when dryrun is set
if opts.DryRun {
@ -130,3 +136,30 @@ func (c *ContainerExecutor) Validate() error {
func (c *ContainerExecutor) Render(_ io.Writer, _ ifc.RenderOptions) error {
return errors.ErrNotImplemented{}
}
func (c *ContainerExecutor) setConfig() error {
if c.Container.ConfigRef != nil {
log.Printf("Config reference is specified, looking for the object in config ref: '%v'", c.Container.ConfigRef)
log.Printf("using bundle root %s", c.Helper.PhaseBundleRoot())
bundle, err := document.NewBundleByPath(c.Helper.PhaseBundleRoot())
if err != nil {
return err
}
gvk := c.Container.ConfigRef.GroupVersionKind()
selector := document.NewSelector().
ByName(c.Container.ConfigRef.Name).
ByNamespace(c.Container.ConfigRef.Namespace).
ByGvk(gvk.Group, gvk.Version, gvk.Kind)
doc, err := bundle.SelectOne(selector)
if err != nil {
return err
}
config, err := doc.AsYAML()
if err != nil {
return err
}
c.Container.Config = string(config)
return nil
}
return nil
}

View File

@ -18,6 +18,7 @@ import (
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
"opendev.org/airship/airshipctl/pkg/api/v1alpha1"
"opendev.org/airship/airshipctl/pkg/container"
@ -52,6 +53,7 @@ const (
data:
cmd: encrypt
unencrypted-regex: '^(kind|apiVersion|group|metadata)$'`
singleExecutorBundlePath = "../../container/testdata/single"
)
@ -63,7 +65,7 @@ func TestNewContainerExecutor(t *testing.T) {
e, err := executors.NewContainerExecutor(ifc.ExecutorConfig{
ExecutorDocument: execDoc,
BundleFactory: testBundleFactory(singleExecutorBundlePath),
Helper: makeDefaultHelper(t, "../../container/testdata"),
Helper: makeDefaultHelper(t, "../../container/testdata", "metadata.yaml"),
})
assert.NoError(t, err)
assert.NotNil(t, e)
@ -75,7 +77,7 @@ func TestNewContainerExecutor(t *testing.T) {
BundleFactory: func() (document.Bundle, error) {
return nil, fmt.Errorf("bundle error")
},
Helper: makeDefaultHelper(t, "../../container/testdata"),
Helper: makeDefaultHelper(t, "../../container/testdata", "metadata.yaml"),
})
assert.Error(t, err)
assert.Nil(t, e)
@ -84,9 +86,11 @@ func TestNewContainerExecutor(t *testing.T) {
func TestGenericContainer(t *testing.T) {
tests := []struct {
name string
outputPath string
expectedErr string
name string
outputPath string
expectedErr string
targetPath string
resultConfig string
containerAPI *v1alpha1.GenericContainer
executorConfig ifc.ExecutorConfig
@ -102,6 +106,7 @@ func TestGenericContainer(t *testing.T) {
},
},
clientFunc: container.NewClientV1Alpha1,
targetPath: singleExecutorBundlePath,
},
{
name: "error kyaml cant parse config",
@ -114,11 +119,47 @@ func TestGenericContainer(t *testing.T) {
runOptions: ifc.RunOptions{},
expectedErr: "wrong Node Kind",
clientFunc: container.NewClientV1Alpha1,
targetPath: singleExecutorBundlePath,
},
{
name: "error no object referenced in config",
containerAPI: &v1alpha1.GenericContainer{
ConfigRef: &v1.ObjectReference{
Kind: "no such kind",
Name: "no such name",
},
},
runOptions: ifc.RunOptions{DryRun: true},
expectedErr: "found no documents",
targetPath: singleExecutorBundlePath,
},
{
name: "success dry run",
containerAPI: &v1alpha1.GenericContainer{},
runOptions: ifc.RunOptions{DryRun: true},
targetPath: singleExecutorBundlePath,
},
{
name: "success referenced config present",
containerAPI: &v1alpha1.GenericContainer{
ConfigRef: &v1.ObjectReference{
Kind: "Secret",
Name: "test-script",
APIVersion: "v1",
},
},
runOptions: ifc.RunOptions{DryRun: true},
targetPath: singleExecutorBundlePath,
resultConfig: `apiVersion: v1
kind: Secret
metadata:
name: test-script
stringData:
script.sh: |
#!/bin/sh
echo WORKS! $var >&2
type: Opaque
`,
},
}
@ -132,6 +173,7 @@ func TestGenericContainer(t *testing.T) {
ExecutorBundle: b,
Container: tt.containerAPI,
ClientFunc: tt.clientFunc,
Helper: makeDefaultHelper(t, tt.targetPath, "../metadata.yaml"),
}
ch := make(chan events.Event)
@ -152,6 +194,7 @@ func TestGenericContainer(t *testing.T) {
assert.NoError(t, e.ErrorEvent.Error)
assert.Equal(t, e.Type, events.GenericContainerType)
assert.Equal(t, e.GenericContainerEvent.Operation, events.GenericContainerStop)
assert.Equal(t, tt.resultConfig, container.Container.Config)
}
})
}

View File

@ -94,7 +94,7 @@ func TestNewKubeApplierExecutor(t *testing.T) {
name: "valid executor",
cfgDoc: ValidExecutorDoc,
kubeconf: testKubeconfig(testValidKubeconfig),
helper: makeDefaultHelper(t, "../../k8s/applier/testdata"),
helper: makeDefaultHelper(t, "../../k8s/applier/testdata", defaultMetadataPath),
bundleFactory: testBundleFactory("../../k8s/applier/testdata/source_bundle"),
},
{
@ -107,7 +107,7 @@ metadata:
labels:
cli-utils.sigs.k8s.io/inventory-id: "some id"`,
expectedErr: "wrong config document",
helper: makeDefaultHelper(t, "../../k8s/applier/testdata"),
helper: makeDefaultHelper(t, "../../k8s/applier/testdata", defaultMetadataPath),
bundleFactory: testBundleFactory("../../k8s/applier/testdata/source_bundle"),
},
@ -116,7 +116,7 @@ metadata:
cfgDoc: ValidExecutorDoc,
expectedErr: "no such file or directory",
kubeconf: testKubeconfig(testValidKubeconfig),
helper: makeDefaultHelper(t, "../../k8s/applier/testdata"),
helper: makeDefaultHelper(t, "../../k8s/applier/testdata", defaultMetadataPath),
bundleFactory: testBundleFactory("does not exist"),
},
}
@ -165,7 +165,7 @@ func TestKubeApplierExecutorRun(t *testing.T) {
{
name: "cant read kubeconfig error",
containsErr: "no such file or directory",
helper: makeDefaultHelper(t, "../../k8s/applier/testdata"),
helper: makeDefaultHelper(t, "../../k8s/applier/testdata", defaultMetadataPath),
bundleFactory: testBundleFactory("../../k8s/applier/testdata/source_bundle"),
kubeconf: testKubeconfig(`invalid kubeconfig`),
execDoc: executorDoc(t, ValidExecutorDocNamespaced),
@ -179,7 +179,7 @@ func TestKubeApplierExecutorRun(t *testing.T) {
{
name: "error cluster not defined",
containsErr: "cluster is not defined in in cluster map",
helper: makeDefaultHelper(t, "../../k8s/applier/testdata"),
helper: makeDefaultHelper(t, "../../k8s/applier/testdata", defaultMetadataPath),
bundleFactory: testBundleFactory("../../k8s/applier/testdata/source_bundle"),
kubeconf: testKubeconfig(testValidKubeconfig),
execDoc: executorDoc(t, ValidExecutorDocNamespaced),