Skip to content
Merged
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: 5 additions & 2 deletions internal/cli/cluster/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ func newStartCmd() *cobra.Command {
var maxMemory int
var exposePath string
var hostNetworkPopulator bool
var targetImgRef string

cmd := &cobra.Command{
Use: "start",
Expand All @@ -44,7 +45,7 @@ func newStartCmd() *cobra.Command {
bink cluster start --memory 4096 --expose ./kubeconfig`,
RunE: func(cmd *cobra.Command, args []string) error {
logger := logrus.New()
return runStart(cmd.Context(), logger, nodeName, nodeImage, apiPort, memory, maxMemory, exposePath, hostNetworkPopulator)
return runStart(cmd.Context(), logger, nodeName, nodeImage, apiPort, memory, maxMemory, exposePath, hostNetworkPopulator, targetImgRef)
},
}

Expand All @@ -55,11 +56,12 @@ func newStartCmd() *cobra.Command {
cmd.Flags().IntVar(&maxMemory, "max-memory", 0, "VM max memory in MB for balloon (0 = use role default: 4096 for control-plane, 2048 for worker)")
cmd.Flags().StringVar(&exposePath, "expose", "", "Expose API and save kubeconfig to PATH after cluster is up")
cmd.Flags().BoolVar(&hostNetworkPopulator, "host-network-populator", false, "Use host networking for the image populator container (fixes DNS in nested podman)")
cmd.Flags().StringVar(&targetImgRef, "target-imgref", "", "Override the bootc image reference tracked by the VM (e.g., registry.cluster.local:5000/node:latest)")

return cmd
}

func runStart(ctx context.Context, logger *logrus.Logger, nodeName string, nodeImage string, apiPort int, memory int, maxMemory int, exposePath string, hostNetworkPopulator bool) error {
func runStart(ctx context.Context, logger *logrus.Logger, nodeName string, nodeImage string, apiPort int, memory int, maxMemory int, exposePath string, hostNetworkPopulator bool, targetImgRef string) error {
logger.Info("=== Creating Kubernetes cluster ===")
logger.Info("")

Expand Down Expand Up @@ -140,6 +142,7 @@ func runStart(ctx context.Context, logger *logrus.Logger, nodeName string, nodeI
node.WithMaxMemory(maxMemory),
node.WithDNSIP(dnsIP),
node.WithClusterImagesVolume(clusterImagesVolume),
node.WithTargetImgRef(targetImgRef),
)
if err != nil {
return fmt.Errorf("creating node: %w", err)
Expand Down
7 changes: 5 additions & 2 deletions internal/cli/node/add.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ func newAddCmd() *cobra.Command {
var maxMemory int
var hostNetworkPopulator bool
var labelFlags []string
var targetImgRef string

cmd := &cobra.Command{
Use: "add <node-name>",
Expand All @@ -47,7 +48,7 @@ func newAddCmd() *cobra.Command {
return err
}
logger := logrus.New()
return runAdd(cmd.Context(), args[0], nodeImage, role, memory, maxMemory, hostNetworkPopulator, labels, logger)
return runAdd(cmd.Context(), args[0], nodeImage, role, memory, maxMemory, hostNetworkPopulator, labels, targetImgRef, logger)
},
}

Expand All @@ -57,6 +58,7 @@ func newAddCmd() *cobra.Command {
cmd.Flags().IntVar(&maxMemory, "max-memory", 0, "VM max memory in MB for balloon (0 = use role default: 4096 for control-plane, 2048 for worker)")
cmd.Flags().BoolVar(&hostNetworkPopulator, "host-network-populator", false, "Use host networking for the image populator container (fixes DNS in nested podman)")
cmd.Flags().StringArrayVarP(&labelFlags, "label", "l", nil, "Node label in key=value format (can be specified multiple times)")
cmd.Flags().StringVar(&targetImgRef, "target-imgref", "", "Override the bootc image reference tracked by the VM (e.g., registry.cluster.local:5000/node:latest)")

cmd.RegisterFlagCompletionFunc("role", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
return []string{"worker", "control-plane"}, cobra.ShellCompDirectiveNoFileComp
Expand Down Expand Up @@ -84,7 +86,7 @@ func parseLabels(labelFlags []string) (map[string]string, error) {
return labels, nil
}

func runAdd(ctx context.Context, nodeName, nodeImage, role string, memory int, maxMemory int, hostNetworkPopulator bool, labels map[string]string, logger *logrus.Logger) error {
func runAdd(ctx context.Context, nodeName, nodeImage, role string, memory int, maxMemory int, hostNetworkPopulator bool, labels map[string]string, targetImgRef string, logger *logrus.Logger) error {
// Validate and convert role to boolean
var isControlPlane bool
switch role {
Expand Down Expand Up @@ -165,6 +167,7 @@ func runAdd(ctx context.Context, nodeName, nodeImage, role string, memory int, m
node.WithUsedIPs(usedIPs),
node.WithDNSIP(dnsIP),
node.WithClusterImagesVolume(clusterImagesVolume),
node.WithTargetImgRef(targetImgRef),
}
if isControlPlane {
nodeOpts = append(nodeOpts, node.WithAPIPort(-1))
Expand Down
2 changes: 2 additions & 0 deletions internal/node/cloudinit.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ type CloudInitData struct {
RegistryPort int
RegistryHostname string
ServiceCIDR string
TargetImgRef string
}

func (n *Node) newCloudInitData(sshPubKey string) CloudInitData {
Expand All @@ -54,6 +55,7 @@ func (n *Node) newCloudInitData(sshPubKey string) CloudInitData {
RegistryPort: config.RegistryPort,
RegistryHostname: config.RegistryHostname,
ServiceCIDR: config.ServiceCIDR,
TargetImgRef: n.TargetImgRef,
}
}

Expand Down
8 changes: 8 additions & 0 deletions internal/node/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type Node struct {
ClusterImagesVolume string
APIPort int // Configured API port (0 = auto-assign)
AssignedAPIPort int // Actual assigned port after container creation
TargetImgRef string

usedIPs []string
podman *podman.Client
Expand Down Expand Up @@ -93,6 +94,13 @@ func WithDNSIP(ip string) NodeOption {
}
}

func WithTargetImgRef(ref string) NodeOption {
return func(n *Node) error {
n.TargetImgRef = ref
return nil
}
}

func WithClusterImagesVolume(volumeName string) NodeOption {
return func(n *Node) error {
n.ClusterImagesVolume = volumeName
Expand Down
8 changes: 8 additions & 0 deletions internal/node/templates/user-data.yaml.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -85,3 +85,11 @@ runcmd:
- nmcli connection up "cloud-init enp2s0"
- systemctl enable --now crio
- systemctl enable kubelet
{{- if .TargetImgRef}}
- |
mount -o remount,rw /sysroot
DEPLOY_PATH=$(bootc status --json | jq -r '.status.booted.ostree.deploySerial // empty')
CHECKSUM=$(bootc status --json | jq -r '.status.booted.ostree.checksum')
Comment thread
alicefr marked this conversation as resolved.
ORIGIN="/ostree/deploy/default/deploy/${CHECKSUM}.${DEPLOY_PATH}.origin"
sed -i 's|^container-image-reference=.*|container-image-reference=ostree-unverified-registry:{{.TargetImgRef}}|' "$ORIGIN"
{{- end}}
10 changes: 8 additions & 2 deletions test/integration/cluster_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,10 @@ var _ = Describe("Cluster Lifecycle", func() {
kubeconfigPath := fmt.Sprintf("../../kubeconfig-%s", clusterName)
defer helpers.CleanupKubeconfig(kubeconfigPath)

By("Creating cluster with --expose, custom node name, and memory ballooning")
cmd := helpers.BinkCmd("cluster", "start", "--cluster-name", clusterName, "--api-port", "0", "--memory", "1900", "--max-memory", "4096", "--node-name", customNodeName, "--expose", kubeconfigPath)
targetImgRef := "registry.cluster.local:5000/node:latest"

By("Creating cluster with --expose, custom node name, memory ballooning, and target-imgref")
cmd := helpers.BinkCmd("cluster", "start", "--cluster-name", clusterName, "--api-port", "0", "--memory", "1900", "--max-memory", "4096", "--node-name", customNodeName, "--expose", kubeconfigPath, "--target-imgref", targetImgRef)
session := helpers.RunCommand(cmd)

By("Verifying cluster creation command succeeded")
Expand Down Expand Up @@ -93,6 +95,10 @@ var _ = Describe("Cluster Lifecycle", func() {
sshOutput := string(sshSession.Out.Contents())
Expect(sshOutput).To(ContainSubstring(customNodeName), "SSH command output should contain the node hostname")

By("Verifying bootc status shows the overridden image reference")
bootcOutput := helpers.SSHExec(clusterName, customNodeName, "sudo bootc status")
Expect(bootcOutput).To(ContainSubstring(targetImgRef), "bootc status should show the target image reference")

By("Verifying bink node ssh propagates non-zero exit codes")
failCmd := helpers.BinkCmd("node", "ssh", customNodeName, "--cluster-name", clusterName, "--", "exit", "42")
failSession := helpers.RunCommand(failCmd)
Expand Down
Loading