Skip to content

Commit

Permalink
Allow rendering userdata using jsonnet
Browse files Browse the repository at this point in the history
  • Loading branch information
bastjan committed Oct 17, 2024
1 parent 266f44e commit 31a2fa8
Show file tree
Hide file tree
Showing 6 changed files with 110 additions and 4 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ toolchain go1.23.2

require (
github.com/cloudscale-ch/cloudscale-go-sdk/v5 v5.0.1
github.com/google/go-jsonnet v0.20.0
github.com/openshift/api v0.0.0-20240924155631-232984653385
github.com/openshift/library-go v0.0.0-20240919205913-c96b82b3762b
github.com/openshift/machine-api-operator v0.2.1-0.20241009125928-52a965a42fac
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,8 @@ github.com/google/gnostic-models v0.6.8/go.mod h1:5n7qKqH0f5wFt+aWF8CW6pZLLNOfYu
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-jsonnet v0.20.0 h1:WG4TTSARuV7bSm4PMB4ohjxe33IHT5WVTrJSU33uT4g=
github.com/google/go-jsonnet v0.20.0/go.mod h1:VbgWF9JX7ztlv770x/TolZNGGFfiHEVx9G6ca2eUmeA=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
Expand Down
48 changes: 44 additions & 4 deletions pkg/machine/actuator.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"strings"

"github.com/cloudscale-ch/cloudscale-go-sdk/v5"
"github.com/google/go-jsonnet"
machinev1beta1 "github.com/openshift/api/machine/v1beta1"
machinecontroller "github.com/openshift/machine-api-operator/pkg/controller/machine"
corev1 "k8s.io/api/core/v1"
Expand Down Expand Up @@ -66,7 +67,7 @@ func (a *Actuator) Create(ctx context.Context, machine *machinev1beta1.Machine)
spec := mctx.spec
sc := a.ServerClientFactory(mctx.token)

userData, err := a.loadUserDataSecret(ctx, mctx)
userData, err := a.loadAndRenderUserDataSecret(ctx, mctx)
if err != nil {
return fmt.Errorf("failed to load user data secret: %w", err)
}
Expand All @@ -77,6 +78,11 @@ func (a *Actuator) Create(ctx context.Context, machine *machinev1beta1.Machine)
}
spec.Tags[machineNameTag] = machine.Name

// Null is not allowed for SSH keys in the cloudscale API
if spec.SSHKeys == nil {
spec.SSHKeys = []string{}
}

serverGroups := spec.ServerGroups
if spec.AntiAffinityKey != "" {
sgc := a.ServerGroupClientFactory(mctx.token)
Expand Down Expand Up @@ -398,7 +404,7 @@ func (a *Actuator) getMachineContext(ctx context.Context, machine *machinev1beta
}, nil
}

func (a *Actuator) loadUserDataSecret(ctx context.Context, mctx *machineContext) (string, error) {
func (a *Actuator) loadAndRenderUserDataSecret(ctx context.Context, mctx *machineContext) (string, error) {
const userDataKey = "userData"

if mctx.spec.UserDataSecret == nil {
Expand All @@ -410,10 +416,44 @@ func (a *Actuator) loadUserDataSecret(ctx context.Context, mctx *machineContext)
return "", fmt.Errorf("failed to get secret %q: %w", mctx.spec.UserDataSecret.Name, err)
}

userData, ok := secret.Data[userDataKey]
userDataRaw, ok := secret.Data[userDataKey]
if !ok {
return "", fmt.Errorf("%q key not found in secret %q", userDataKey, mctx.spec.UserDataSecret.Name)
}
userData := string(userDataRaw)

if userData == "" {
return "", nil
}

return string(userData), nil
data := make(map[string]string, len(secret.Data))
for k, v := range secret.Data {
data[k] = string(v)
}

jvm, err := jsonnetVMWithContext(mctx.machine, data)
if err != nil {
return "", fmt.Errorf("userData: failed to create jsonnet VM: %w", err)
}
ud, err := jvm.EvaluateAnonymousSnippet("context", userData)
if err != nil {
return "", fmt.Errorf("userData: failed to evaluate jsonnet: %w", err)
}

return ud, nil
}

func jsonnetVMWithContext(machine *machinev1beta1.Machine, data map[string]string) (*jsonnet.VM, error) {
jcr, err := json.Marshal(map[string]any{
"machine": machine,
"data": data,
})
if err != nil {
return nil, fmt.Errorf("unable to marshal jsonnet context: %w", err)
}
jvm := jsonnet.MakeVM()
jvm.ExtCode("context", string(jcr))
// Don't allow imports
jvm.Importer(&jsonnet.MemoryImporter{})
return jvm, nil
}
14 changes: 14 additions & 0 deletions pkg/machine/userdata/secret.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
apiVersion: 'v1',
kind: 'Secret',
metadata: {
name: 'cloudscale-user-data',
namespace: 'openshift-machine-api',
},
stringData: {
ignitionHost: std.extVar('ignitionHost'),
ignitionCA: std.extVar('ignitionCA'),
userData: (importstr './userdata.jsonnet'),
},
type: 'Opaque',
}
13 changes: 13 additions & 0 deletions pkg/machine/userdata/userdata-secret-from-maintfjson.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
#!/bin/bash
# This script is used to generate the userdata file from the main.tf.json output by component openshift4-terraform
# First optional positional argument is the path to the main.tf.json file

set -euo pipefail

maintf="${1:-main.tf.json}"
sdir="$(dirname "$(readlink -f "$0")")"

ignitionCA="$(jq -er '.module.cluster.ignition_ca' "$maintf")"
ignitionHost="api-int.$(jq -er '.module.cluster.cluster_name' "$maintf").$(jq -er '.module.cluster.base_domain' "$maintf")"

jsonnet "${sdir}/secret.jsonnet" --ext-str "ignitionHost=${ignitionHost}" --ext-str "ignitionCA=${ignitionCA}"
36 changes: 36 additions & 0 deletions pkg/machine/userdata/userdata.jsonnet
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
local context = std.extVar('context');

{
ignition: {
version: '3.1.0',
config: {
merge: [ {
source: 'https://%s:22623/config/%s' % [ context.data.ignitionHost, std.get(context.data, 'ignitionConfigName', 'worker') ],
} ],
},
security: {
tls: {
certificateAuthorities: [ {
source: 'data:text/plain;charset=utf-8;base64,%s' % [ std.base64(context.data.ignitionCA) ],
} ],
},
},
},
systemd: {
units: [ {
name: 'cloudscale-hostkeys.service',
enabled: true,
contents: "[Unit]\nDescription=Print SSH Public Keys to tty\nAfter=sshd-keygen.target\n\n[Install]\nWantedBy=multi-user.target\n\n[Service]\nType=oneshot\nStandardOutput=tty\nTTYPath=/dev/ttyS0\nExecStart=/bin/sh -c \"echo '-----BEGIN SSH HOST KEY KEYS-----'; cat /etc/ssh/ssh_host_*key.pub; echo '-----END SSH HOST KEY KEYS-----'\"",
} ],
},
storage: {
files: [ {
filesystem: 'root',
path: '/etc/hostname',
mode: 420,
contents: {
source: 'data:,%s' % context.machine.metadata.name,
},
} ],
},
}

0 comments on commit 31a2fa8

Please sign in to comment.