diff --git a/cleanup.sh b/cleanup.sh new file mode 100755 index 00000000..b4bcf94f --- /dev/null +++ b/cleanup.sh @@ -0,0 +1,27 @@ +set -euo pipefail + +# make sure to regenerate manifests and run tests +make all + +# delete any existing convention and deployment +kubectl delete -f samples/ytt-based-conventions/ytt-example.yaml +kapp delete -a conventions -n cartographer-system + +# create a new deployment +kapp deploy -n cartographer-system -a conventions -f <( \ + ko resolve -f <( \ + ytt \ + -f dist/cartographer-conventions.yaml + ) \ +) + +# create the ytt based convention +kubectl apply -f samples/ytt-based-conventions/ytt-example.yaml +#kubectl delete -f samples/ytt-based-conventions/ytt-example.yaml + +# inspect the ytt based convention +kubectl get clusterpodconvention.conventions.carto.run/spring-ytt-sample -o yaml + +# create a workload +kubectl apply -f samples/ytt-based-conventions/workload.yaml +#kubectl delete -f samples/ytt-based-conventions/workload.yaml diff --git a/pkg/apis/conventions/v1alpha1/clusterpodconvention_validation.go b/pkg/apis/conventions/v1alpha1/clusterpodconvention_validation.go index 1e613d9f..8a3f8224 100644 --- a/pkg/apis/conventions/v1alpha1/clusterpodconvention_validation.go +++ b/pkg/apis/conventions/v1alpha1/clusterpodconvention_validation.go @@ -82,9 +82,9 @@ func (s *ClusterPodConventionSpec) Validate() validation.FieldErrors { errs = errs.Also(s.Webhook.Validate().ViaField("webhook")) } // only invoke ytt validations if the webhook configuration is not being used - if s.Webhook == nil && s.Ytt != nil { - errs = errs.Also(s.Webhook.Validate().ViaField("ytt")) - } + // if s.Webhook == nil && s.Ytt != nil { + // errs = errs.Also(s.Ytt.Validate().ViaField("ytt")) + // } } if s.SelectorTarget != PodTemplateSpecLabels && s.SelectorTarget != PodIntentLabels { diff --git a/pkg/controllers/podintent_reconciler.go b/pkg/controllers/podintent_reconciler.go index fb072c6c..f8fb6e7e 100644 --- a/pkg/controllers/podintent_reconciler.go +++ b/pkg/controllers/podintent_reconciler.go @@ -20,16 +20,23 @@ import ( "bytes" "context" "fmt" + "os" + "os/exec" + "path" + "runtime" "sort" "strings" + "time" "github.com/go-logr/logr" "github.com/google/go-containerregistry/pkg/authn/k8schain" "github.com/vmware-labs/reconciler-runtime/apis" "github.com/vmware-labs/reconciler-runtime/reconcilers" "github.com/vmware-labs/reconciler-runtime/tracker" + "gopkg.in/yaml.v2" corev1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" @@ -132,19 +139,15 @@ func ResolveConventions() reconcilers.SubReconciler { convention.ClientConfig = *clientConfig } else if source.Spec.Ytt != nil { log.Info("handling a ytt based convention") - // read template spec and convenert to a sptream of bytes - // template - template := &parent.Spec.Template - // convert template as a stream of bytes - // templatesAsBytes = bytes.NewBuffer([]byte(template(string))) - - // convert template to a stream of bytes - // kubectl = "kubectl" - // ytt = "ytt" - // args - log.Info("your template spec", template.GetObjectMeta()) - - return nil + + // read template spec and convert to string + + log.Info("retrieved pod template spec from the workload", parent) + stampedObj, err := ApplyYtt(ctx, *parent) + if err != nil { + return nil + } + log.Info("stamped out object", stampedObj) } conventions = append(conventions, convention) } @@ -162,6 +165,51 @@ func ResolveConventions() reconcilers.SubReconciler { } } +func ApplyYtt(ctx context.Context, workload conventionsv1alpha1.PodIntent) (interface{}, error) { + template := workload.Spec.Template.AsPodTemplateSpec() + log := logr.FromContextOrDiscard(ctx) + + ctx, cancel := context.WithTimeout(ctx, 4*time.Second) + defer cancel() + + ytt := "ytt" + if kodata, ok := os.LookupEnv("KO_DATA_PATH"); ok { + ytt = path.Join(kodata, fmt.Sprintf("ytt-%s-%s", runtime.GOOS, runtime.GOARCH)) + } + + args := []string{"--", "version"} + stdin := bytes.NewReader([]byte(template.Spec.String())) + stdout := bytes.NewBuffer([]byte{}) + stderr := bytes.NewBuffer([]byte{}) + + cmd := exec.CommandContext(ctx, ytt, args...) + cmd.Stdin = stdin + cmd.Stdout = stdout + cmd.Stderr = stderr + + log.Info("ytt call args", args) + log.Info("ytt call input", template) + + if err := cmd.Run(); err != nil { + msg := stderr.String() + if msg == "" { + log.Error(err, "failed handle ytt") + return nil, err + } + return nil, err + } + output := stdout.String() + log.Info("ytt result", "output", output) + + stampedObject := &unstructured.Unstructured{} + log.Info("your stamped object", stampedObject) + if err := yaml.Unmarshal([]byte(output), stampedObject); err != nil { + // ytt should never return invalid yaml + return nil, err + } + return stampedObject, nil +} + // +kubebuilder:rbac:groups=core,resources=secrets,verbs=get;list;watch // +kubebuilder:rbac:groups=core,resources=serviceaccounts,verbs=get;list;watch diff --git a/samples/ytt-based-conventions/workload.yaml b/samples/ytt-based-conventions/workload.yaml new file mode 100644 index 00000000..2dc0861f --- /dev/null +++ b/samples/ytt-based-conventions/workload.yaml @@ -0,0 +1,11 @@ +--- +apiVersion: conventions.carto.run/v1alpha1 +kind: PodIntent +metadata: + name: spring-sample +spec: + template: + spec: + containers: + - name: workload + image: scothis/petclinic:sbom-20211210@sha256:8b517f21f283229e855e316e2753396239884eb9c4009ab6c797bdf2a041140f