Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Transformer mode #77

Draft
wants to merge 7 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ API_PLUGIN_PATH ?= $(KUSTOMIZE_PLUGIN_HOME)/policy.open-cluster-management.io/v1
# Kustomize arguments
SOURCE_DIR ?= examples/

# Image settings
IMAGE_TAG ?= policy-generator-plugin:latest

# go-get-tool will 'go install' any package $1 and install it to LOCAL_BIN.
define go-get-tool
@set -e ;\
Expand Down Expand Up @@ -54,6 +57,10 @@ build: layout
build-binary:
go build -o PolicyGenerator cmd/main.go

.PHONY: build-image
build-image:
docker build -f ./build/Dockerfile -t $(IMAGE_TAG) .

.PHONY: build-release
build-release:
@if [[ $(shell git status --porcelain | wc -l) -gt 0 ]]; \
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ For more about Open Cluster Management and its Policy Framework:
**NOTE:** This will default to placing the binary in `${HOME}/.config/kustomize/plugin/`. You can
change this by exporting `KUSTOMIZE_PLUGIN_HOME` to a different path.

#### Configuration
#### Configuration and use as a Generator

1. Create a `kustomization.yaml` file that points to `PolicyGenerator` manifest(s), with any
additional desired patches or customizations (see
Expand All @@ -80,6 +80,14 @@ For more about Open Cluster Management and its Policy Framework:
kustomize build --enable-alpha-plugins
```

#### Configuration and use as a Transformer

The plugin can also be used as a transformer, to wrap all incoming `resources` from a
`kustomization.yaml` file into one Policy. This feature is somewhat experimental.

An example configuration as a transformer (and its output) can be found in the
[`examples/generator/`](./examples/generator/) folder.

### As a standalone binary

In order to bypass Kustomize and run the generator binary directly:
Expand Down
24 changes: 24 additions & 0 deletions build/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Build the manager binary
FROM golang:1.18 as builder

WORKDIR /workspace
# Copy the Go Modules manifests
COPY go.mod go.mod
COPY go.sum go.sum
# cache deps before building and copying source so that we don't need to re-download as much
# and so that source changes don't invalidate our downloaded layer
RUN go mod download

# Copy the go source
COPY cmd cmd
COPY internal internal

# Build
RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -o generator cmd/main.go

FROM registry.access.redhat.com/ubi8/ubi-minimal:latest
WORKDIR /
COPY --from=builder /workspace/generator /generator

WORKDIR /tmp
ENTRYPOINT ["/generator"]
95 changes: 95 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package main

import (
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
"os"

"github.com/spf13/pflag"
"open-cluster-management.io/ocm-kustomize-generator-plugins/internal"
"sigs.k8s.io/kustomize/kyaml/kio"
)

var debug = false
Expand All @@ -23,6 +26,14 @@ func main() {
generators := pflag.Args()
var outputBuffer bytes.Buffer

if len(generators) == 0 {
if err := runKRMplugin(os.Stdin, os.Stdout); err != nil {
errorAndExit(err.Error())
}

return
}

for _, gen := range generators {
outputBuffer.Write(processGeneratorConfig(gen))
}
Expand Down Expand Up @@ -78,3 +89,87 @@ func processGeneratorConfig(filePath string) []byte {

return generatedOutput
}

func runKRMplugin(input io.Reader, output io.Writer) error {
inputReader := kio.ByteReader{Reader: input}

inputs, err := inputReader.Read()
if err != nil {
return fmt.Errorf("failed to read input: %w", err)
}

config, err := inputReader.FunctionConfig.MarshalJSON()
if err != nil {
return fmt.Errorf("failed to marshal KRM configuration from input: %w", err)
}

cwd, err := os.Getwd()
if err != nil {
return fmt.Errorf("failed to determine the current directory: %w", err)
}

p := internal.Plugin{}

err = p.Config(config, cwd)
if err != nil {
return fmt.Errorf("error processing the PolicyGenerator file '[stdin]': %w", err)
}

// in KRM generator mode, this annotation will be set by kustomize
if inputs[0].GetAnnotations()["config.kubernetes.io/local-config"] != "true" {
// in KRM transformer mode, convert the KRM-style input yaml into the
// flat yaml format the generator uses, and write it to a temp file.
inpFile, err := os.CreateTemp(".", "transformer-intput-*.yaml")
if err != nil {
return fmt.Errorf("error creating an input file: %w", err)
}

defer os.Remove(inpFile.Name()) // clean up

inpwriter := kio.ByteWriter{
Writer: inpFile,
ClearAnnotations: []string{
"config.k8s.io/id",
"internal.config.kubernetes.io/annotations-migration-resource-id",
"internal.config.kubernetes.io/id",
"kustomize.config.k8s.io/id",
},
}

err = inpwriter.Write(inputs)
if err != nil {
return fmt.Errorf("error writing input KRM yaml to the temporary manifest: %w", err)
}

if len(p.Policies) == 0 || len(p.Policies[0].Manifests) == 0 {
return errors.New("no manifests in config file")
}

// overwrites the path in the generator yaml, from stdin to the temp file.
p.Policies[0].Manifests[0].Path = inpFile.Name()
}

generatedOutput, err := p.Generate()
if err != nil {
return fmt.Errorf("error generating policies from the PolicyGenerator file: %w", err)
}

nodes, err := (&kio.ByteReader{Reader: bytes.NewReader(generatedOutput)}).Read()
if err != nil {
return fmt.Errorf("error reading generator output: %w", err)
}

// Write the result in a ResourceList
outputWriter := kio.ByteWriter{
Writer: output,
WrappingAPIVersion: "config.kubernetes.io/v1",
WrappingKind: "ResourceList",
}

err = outputWriter.Write(nodes)
if err != nil {
return fmt.Errorf("error writing generator output: %w", err)
}

return nil
}
23 changes: 23 additions & 0 deletions examples/container-generator/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# run this example with `kustomize build . --enable-alpha-plugins --mount type=bind,source=$(pwd),target=/tmp/,readonly > output.yaml`
# Note: the manifests can not use kustomization.yaml files referencing non-local resources (eg github)

generators:
- |-
apiVersion: policy.open-cluster-management.io/v1
kind: PolicyGenerator
metadata:
name: policy-generator
annotations:
config.kubernetes.io/function: |
container:
image: policy-generator-plugin:latest
policyDefaults:
namespace: default
consolidateManifests: false
evaluationInterval:
compliant: 30m
noncompliant: 45s
policies:
- name: made-in-container
manifests:
- path: resources/
59 changes: 59 additions & 0 deletions examples/container-generator/output.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
apiVersion: apps.open-cluster-management.io/v1
kind: PlacementRule
metadata:
name: placement-made-in-container
namespace: default
spec:
clusterSelector:
matchExpressions: []
---
apiVersion: policy.open-cluster-management.io/v1
kind: PlacementBinding
metadata:
name: binding-made-in-container
namespace: default
placementRef:
apiGroup: apps.open-cluster-management.io
kind: PlacementRule
name: placement-made-in-container
subjects:
- apiGroup: policy.open-cluster-management.io
kind: Policy
name: made-in-container
---
apiVersion: policy.open-cluster-management.io/v1
kind: Policy
metadata:
annotations:
policy.open-cluster-management.io/categories: CM Configuration Management
policy.open-cluster-management.io/controls: CM-2 Baseline Configuration
policy.open-cluster-management.io/standards: NIST SP 800-53
name: made-in-container
namespace: default
spec:
disabled: false
policy-templates:
- objectDefinition:
apiVersion: policy.open-cluster-management.io/v1
kind: ConfigurationPolicy
metadata:
name: made-in-container
spec:
evaluationInterval:
compliant: 30m
noncompliant: 45s
object-templates:
- complianceType: musthave
objectDefinition:
apiVersion: v1
data:
game.properties: "enemies=goldfish \n"
ui.properties: |
color.good=neon-green
kind: ConfigMap
metadata:
annotations: {}
name: kustomized-game-config-fish
namespace: default
remediationAction: inform
severity: low
12 changes: 12 additions & 0 deletions examples/container-generator/resources/configmap-fish.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Taken from https://kubernetes.io/docs/tasks/configure-pod-container/configure-pod-configmap/
---
apiVersion: v1
kind: ConfigMap
metadata:
name: game-config-fish
namespace: default
data:
game.properties: |
enemies=goldfish
ui.properties: |
color.good=neon-green
3 changes: 3 additions & 0 deletions examples/container-generator/resources/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
namePrefix: kustomized-
resources:
- ./configmap-fish.yaml
25 changes: 25 additions & 0 deletions examples/container-transformer/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# run this example with `kustomize build . --enable-alpha-plugins > output.yaml`
# Note: the container image must be available

resources:
- github.com/redhat-cop/gitops-catalog/advanced-cluster-management/operator/overlays/release-2.5?ref=main
transformers:
- |-
apiVersion: policy.open-cluster-management.io/v1
kind: PolicyGenerator
metadata:
name: policy-transformer
annotations:
config.kubernetes.io/function: |
container:
image: policy-generator-plugin:latest
policyDefaults:
namespace: default
consolidateManifests: false
evaluationInterval:
compliant: 30m
noncompliant: 45s
policies:
- name: transformed-policy
manifests:
- path: stdin
Loading