Skip to content

Commit

Permalink
Add tilt for local development
Browse files Browse the repository at this point in the history
  • Loading branch information
defo89 committed Sep 12, 2024
1 parent a4264ab commit a07fd9b
Show file tree
Hide file tree
Showing 17 changed files with 476 additions and 4 deletions.
2 changes: 1 addition & 1 deletion .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ issues:
linters:
disable-all: true
enable:
- copyloopvar
- dupl
- errcheck
- exportloopref
- goconst
- gocyclo
- gofmt
Expand Down
35 changes: 34 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ else
GOBIN=$(shell go env GOBIN)
endif

GOARCH := $(shell go env GOARCH)
GOOS := $(shell go env GOOS)

# CONTAINER_TOOL defines the container tool to be used for building images.
# Be aware that the target commands are only tested with Docker which is
# scaffolded by default. However, you might want to replace it to use other
Expand Down Expand Up @@ -181,8 +184,12 @@ LOCALBIN ?= $(shell pwd)/bin
$(LOCALBIN):
mkdir -p $(LOCALBIN)

# curl retries
CURL_RETRIES=3

## Tool Binaries
KUBECTL ?= kubectl
KUBECTL ?= $(LOCALBIN)/kubectl-$(ENVTEST_K8S_VERSION)
KUBECTL_BIN ?= $(LOCALBIN)/kubectl
KUSTOMIZE ?= $(LOCALBIN)/kustomize-$(KUSTOMIZE_VERSION)
CONTROLLER_GEN ?= $(LOCALBIN)/controller-gen-$(CONTROLLER_TOOLS_VERSION)
ENVTEST ?= $(LOCALBIN)/setup-envtest-$(ENVTEST_VERSION)
Expand All @@ -203,6 +210,13 @@ kustomize: $(KUSTOMIZE) ## Download kustomize locally if necessary.
$(KUSTOMIZE): $(LOCALBIN)
$(call go-install-tool,$(KUSTOMIZE),sigs.k8s.io/kustomize/kustomize/v5,$(KUSTOMIZE_VERSION))

.PHONY: kubectl
kubectl: $(KUBECTL) ## Download kubectl locally if necessary.
$(KUBECTL): $(LOCALBIN)
curl --retry $(CURL_RETRIES) -fsL https://dl.k8s.io/release/v$(ENVTEST_K8S_VERSION)/bin/$(GOOS)/$(GOARCH)/kubectl -o $(KUBECTL)
ln -sf "$(KUBECTL)" "$(KUBECTL_BIN)"
chmod +x "$(KUBECTL_BIN)" "$(KUBECTL)"

.PHONY: controller-gen
controller-gen: $(CONTROLLER_GEN) ## Download controller-gen locally if necessary.
$(CONTROLLER_GEN): $(LOCALBIN)
Expand Down Expand Up @@ -241,3 +255,22 @@ GOBIN=$(LOCALBIN) go install $${package} ;\
mv "$$(echo "$(1)" | sed "s/-$(3)$$//")" $(1) ;\
}
endef

## --------------------------------------
## Tilt / Kind
## --------------------------------------

KIND_CLUSTER_NAME ?= metal

.PHONY: kind-create
kind-create: $(ENVTEST) ## create metal kind cluster if needed
./scripts/kind-with-registry.sh

.PHONY: kind-delete
kind-delete: ## Destroys the "metal" kind cluster.
kind delete cluster --name=$(KIND_CLUSTER_NAME)
docker stop kind-registry && docker rm kind-registry

.PHONY: tilt-up
tilt-up: $(ENVTEST) $(KUSTOMIZE) kind-create ## start tilt and build kind cluster if needed
EXP_CLUSTER_RESOURCE_SET=true tilt up
89 changes: 89 additions & 0 deletions Tiltfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
#!/usr/bin/env bash
#// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors
#// SPDX-License-Identifier: Apache-2.0

update_settings(k8s_upsert_timeout_secs=60) # on first tilt up, often can take longer than 30 seconds

settings = {
"allowed_contexts": [
"kind-metal"
],
"kubectl": "./bin/kubectl",
"boot_image": "ghcr.io/ironcore-dev/boot-operator:latest",
"cert_manager_version": "v1.15.3",
"new_args": {
"boot": [
"--health-probe-bind-address=:8081",
"--metrics-bind-address=127.0.0.1:8085",
"--leader-elect",
"--default-ipxe-server-url=http://boot-service:30007",
"--default-kernel-url=http://boot-service:30007/image?imageName=ghcr.io/ironcore-dev/os-images/gardenlinux&version=1443.10&layerName=vmlinuz",
"--default-initrd-url=http://boot-service:30007/image?imageName=ghcr.io/ironcore-dev/os-images/gardenlinux&version=1443.10&layerName=initramfs",
"--default-squashfs-url=http://boot-service:30007/image?imageName=ghcr.io/ironcore-dev/os-images/gardenlinux&version=1443.10&layerName=squashfs",
"--ipxe-service-url=http://boot-service:30007",
"--ipxe-service-port=30007",
"--controllers=ipxebootconfig,serverbootconfigpxe,serverbootconfighttp,httpbootconfig",
],
}
}

kubectl = settings.get("kubectl")

if "allowed_contexts" in settings:
allow_k8s_contexts(settings.get("allowed_contexts"))

def deploy_cert_manager():
version = settings.get("cert_manager_version")
print("Installing cert-manager")
local("{} apply -f https://github.com/cert-manager/cert-manager/releases/download/{}/cert-manager.yaml".format(kubectl, version), quiet=True, echo_off=True)

print("Waiting for cert-manager to start")
local("{} wait --for=condition=Available --timeout=300s -n cert-manager deployment/cert-manager".format(kubectl), quiet=True, echo_off=True)
local("{} wait --for=condition=Available --timeout=300s -n cert-manager deployment/cert-manager-cainjector".format(kubectl), quiet=True, echo_off=True)
local("{} wait --for=condition=Available --timeout=300s -n cert-manager deployment/cert-manager-webhook".format(kubectl), quiet=True, echo_off=True)

# deploy boot-operator
def deploy_boot():
version = settings.get("boot_version")
image = settings.get("boot_image")
new_args = settings.get("new_args").get("boot")
boot_uri = "https://github.com/ironcore-dev/boot-operator//config/dev"
cmd = "{} apply -k {}".format(kubectl, boot_uri)
local(cmd, quiet=True)

replace_args_with_new_args("boot-operator-system", "boot-operator-controller-manager", new_args)
patch_image("boot-operator-system", "boot-operator-controller-manager", image)

def patch_image(namespace, name, image):
patch = [{
"op": "replace",
"path": "/spec/template/spec/containers/0/image",
"value": image,
}]
local("{} patch deployment {} -n {} --type json -p='{}'".format(kubectl, name, namespace, str(encode_json(patch)).replace("\n", "")))

def replace_args_with_new_args(namespace, name, extra_args):
patch = [{
"op": "replace",
"path": "/spec/template/spec/containers/0/args",
"value": extra_args,
}]
local("{} patch deployment {} -n {} --type json -p='{}'".format(kubectl, name, namespace, str(encode_json(patch)).replace("\n", "")))

def waitforsystem():
print("Waiting for metal-operator to start")
local("{} wait --for=condition=ready --timeout=300s -n metal-operator-system pod --all".format(kubectl), quiet=False, echo_off=True)

##############################
# Actual work happens here
##############################

deploy_cert_manager()

docker_build('controller', '.', target = 'manager')

deploy_boot()

yaml = kustomize('./config/dev')

k8s_yaml(yaml)
1 change: 1 addition & 0 deletions api/v1alpha1/bmc_types.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ const (
BMCType = "bmc"
ProtocolRedfish = "Redfish"
ProtocolRedfishLocal = "RedfishLocal"
ProtocolRedfishKube = "RedfishKube"
)

// BMCSpec defines the desired state of BMC
Expand Down
150 changes: 150 additions & 0 deletions bmc/redfish_kube.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// SPDX-FileCopyrightText: 2024 SAP SE or an SAP affiliate company and IronCore contributors
// SPDX-License-Identifier: Apache-2.0

package bmc

import (
"context"
"fmt"

"sigs.k8s.io/controller-runtime/pkg/client"

"github.com/stmcginnis/gofish/redfish"
v1 "k8s.io/api/batch/v1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/utils/ptr"
)

const (
metalJobLabel = "kube.ironcore.dev/job"
devServerUUID = "38947555-7742-3448-3784-823347823834"
registryURL = "http://metal-registry.metal-operator-system.svc.cluster.local:30000/register"
)

var _ BMC = (*RedfishKubeBMC)(nil)

type KubeClient struct {
client client.Client
namespace string
}

// RedfishKubeBMC is an implementation of the BMC interface for Redfish.
type RedfishKubeBMC struct {
*RedfishBMC
*KubeClient
poweredOn bool
}

// NewRedfishKubeBMCClient creates a new RedfishKubeBMC with the given connection details.
func NewRedfishKubeBMCClient(
ctx context.Context,
endpoint, username, password string,
basicAuth bool,
c client.Client,
ns string,
) (BMC, error) {
bmc, err := NewRedfishBMCClient(ctx, endpoint, username, password, basicAuth)
if err != nil {
return nil, err
}
redfishKubeBMC := &RedfishKubeBMC{
RedfishBMC: bmc,
KubeClient: &KubeClient{
client: c,
namespace: ns,
},
}

// TODO find a general way to call PowerOn method for mockup servers
if !redfishKubeBMC.poweredOn {
if err = redfishKubeBMC.PowerOn(devServerUUID); err != nil {
return nil, fmt.Errorf("failed to power on system: %w", err)
}
redfishKubeBMC.poweredOn = true
}

return redfishKubeBMC, nil
}

func (r RedfishKubeBMC) PowerOn(systemUUID string) error {
system, err := r.getSystemByUUID(systemUUID)
if err != nil {
return fmt.Errorf("failed to get system: %w", err)
}
system.PowerState = redfish.OnPowerState
systemURI := fmt.Sprintf("/redfish/v1/Systems/%s", system.ID)
if err := system.Patch(systemURI, system); err != nil {
return fmt.Errorf("failed to set power state %s for system %s: %w", redfish.OnPowerState, systemUUID, err)
}
netData := `{"networkInterfaces":[{"name":"dummy0","ipAddress":"127.0.0.2","macAddress":"aa:bb:cc:dd:ee:ff"}]`
curlCmd := fmt.Sprintf(
`apk add curl && curl -H 'Content-Type: application/json' \
-d '{"SystemUUID":"%s","data":%s}}' \
-X POST %s`,
systemUUID, netData, registryURL)
cmd := []string{
"/bin/sh",
"-c",
curlCmd,
}
if err := r.createJob(context.TODO(), r.KubeClient.client, cmd, r.KubeClient.namespace, systemUUID); err != nil {
return fmt.Errorf("failed to create job for system %s: %w", systemUUID, err)
}
return nil
}

func (r RedfishKubeBMC) createJob(
ctx context.Context,
c client.Client,
cmd []string,
namespace,
systemUUID string,
) error {
// Check if a job with the same label already exists
jobList := &v1.JobList{}
listOpts := []client.ListOption{
client.InNamespace(namespace),
client.MatchingLabels{metalJobLabel: systemUUID},
}
if err := c.List(ctx, jobList, listOpts...); err != nil {
return fmt.Errorf("failed to list jobs: %w", err)
}
if len(jobList.Items) > 0 {
return nil // Job already exists, do not create a new one
}

job := &v1.Job{
ObjectMeta: metav1.ObjectMeta{
GenerateName: fmt.Sprintf("register-%s-", systemUUID),
Namespace: namespace,
Labels: map[string]string{
metalJobLabel: systemUUID,
},
},
Spec: v1.JobSpec{
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: map[string]string{
metalJobLabel: systemUUID,
},
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{
{
Name: "registry-job",
Image: "alpine:latest",
Command: cmd,
},
},
RestartPolicy: corev1.RestartPolicyNever,
},
},
TTLSecondsAfterFinished: ptr.To(int32(30)),
},
}
if err := c.Create(ctx, job); err != nil {
return err
}
return nil
}
9 changes: 9 additions & 0 deletions config/dev/kustomization.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
resources:
- ../default
- ../redfish-mockup
- registry_service.yaml

patches:
- path: delete_manager_auth_proxy_patch.yaml
- path: manager_patch.yaml

secretGenerator:
- name: macdb
namespace: metal-operator-system
files:
- macdb.yaml
12 changes: 12 additions & 0 deletions config/dev/macdb.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
macPrefixes:
- macPrefix: 23
manufacturer: Foo
protocol: RedfishKube
port: 8000
type: bmc
defaultCredentials:
- username: foo
password: bar
console:
type: ssh
port: 22
36 changes: 36 additions & 0 deletions config/dev/manager_patch.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: controller-manager
namespace: system
spec:
template:
spec:
containers:
- name: manager
args:
- --health-probe-bind-address=:8081
- --metrics-bind-address=127.0.0.1:8080
- --leader-elect
- --mac-prefixes-file=/etc/macdb/macdb.yaml
- --probe-image=ghcr.io/ironcore-dev/metalprobe:latest
- --probe-os-image=ghcr.io/ironcore-dev/os-images/gardenlinux:1443.10
- --registry-url=http://127.0.0.1:30000
- --registry-port=30000
- --enforce-first-boot
ports:
- containerPort: 30000
volumeMounts:
- mountPath: /etc/macdb/
name: macdb
- name: redfish
image: dmtf/redfish-mockup-server:latest
ports:
- containerPort: 8000
securityContext:
runAsNonRoot: false
volumes:
- name: macdb
secret:
defaultMode: 420
secretName: macdb
12 changes: 12 additions & 0 deletions config/dev/registry_service.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
apiVersion: v1
kind: Service
metadata:
name: metal-registry
namespace: metal-operator-system
spec:
ports:
- name: registry-server
port: 30000
targetPort: 30000
selector:
control-plane: controller-manager
Loading

0 comments on commit a07fd9b

Please sign in to comment.