Skip to content

Commit 6bc749b

Browse files
committed
Add Starlark-based provisioner backend
- New provisioner implementation that delegates every `pkg/provisioner.Provisioner` method to a named function in a user-supplied Starlark script, enabled via `--starlark-provisioner-script` CLI arg. - The reference script `efi-vmedia.star` that targets Ironic redfish-virtualmedia UEFI (custom-deploy or qcow2) and rejects unsupported specs. Signed-off-by: s3rj1k <evasive.gyron@gmail.com>
1 parent 5a65e4a commit 6bc749b

20 files changed

Lines changed: 2985 additions & 25 deletions

File tree

.github/workflows/build-images-action.yml

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,48 @@ name: build-images-action
22

33
permissions:
44
contents: read
5+
packages: write
56

67
on:
78
push:
89
branches:
910
- 'main'
1011
- 'release-*'
12+
- 'starlark'
1113
tags:
1214
- 'v*'
1315

1416
jobs:
1517
build_bmo:
16-
name: Build BMO container image
17-
if: github.repository == 'metal3-io/baremetal-operator'
18-
permissions:
19-
contents: read
20-
id-token: write
21-
uses: metal3-io/project-infra/.github/workflows/container-image-build.yml@main # zizmor: ignore[unpinned-uses]
22-
with:
23-
image-name: 'baremetal-operator'
24-
pushImage: true
25-
generate-sbom: true
26-
sign-image: true
27-
secrets:
28-
QUAY_USERNAME: ${{ secrets.QUAY_USERNAME }}
29-
QUAY_PASSWORD: ${{ secrets.QUAY_PASSWORD }}
30-
SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }}
18+
name: Build and push BMO container image
19+
runs-on: ubuntu-latest
20+
steps:
21+
- name: Checkout code
22+
uses: actions/checkout@v6
23+
with:
24+
persist-credentials: false
25+
26+
- name: Calculate go version
27+
id: vars
28+
run: echo "go_version=$(make go-version)" >> $GITHUB_OUTPUT
29+
30+
- name: Set up Go
31+
uses: actions/setup-go@v6
32+
with:
33+
go-version: ${{ steps.vars.outputs.go_version }}
34+
35+
- name: Log in to GHCR
36+
uses: docker/login-action@v4
37+
with:
38+
registry: ghcr.io
39+
username: ${{ github.actor }}
40+
password: ${{ secrets.GITHUB_TOKEN }}
41+
42+
- name: Build image
43+
run: make docker-build
44+
env:
45+
REGISTRY: ghcr.io/${{ github.repository_owner }}
46+
IMG_TAG: ${{ github.ref_name }}
47+
48+
- name: Push image
49+
run: docker push ghcr.io/${{ github.repository_owner }}/baremetal-operator-amd64:${{ github.ref_name }}

go.mod

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ require (
1515
github.com/prometheus/client_golang v1.23.2
1616
github.com/stretchr/testify v1.11.1
1717
go.etcd.io/etcd/client/pkg/v3 v3.6.10
18+
go.starlark.net v0.0.0-20260326113308-fadfc96def35
1819
go.uber.org/zap v1.27.1
1920
k8s.io/api v0.34.6
2021
k8s.io/apimachinery v0.34.6
@@ -90,7 +91,7 @@ require (
9091
golang.org/x/oauth2 v0.34.0 // indirect
9192
golang.org/x/sync v0.19.0 // indirect
9293
golang.org/x/sys v0.42.0 // indirect
93-
golang.org/x/term v0.39.0 // indirect
94+
golang.org/x/term v0.41.0 // indirect
9495
golang.org/x/text v0.33.0 // indirect
9596
golang.org/x/time v0.9.0 // indirect
9697
golang.org/x/tools v0.41.0 // indirect

go.sum

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,8 @@ go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09
217217
go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0=
218218
go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A=
219219
go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4=
220+
go.starlark.net v0.0.0-20260326113308-fadfc96def35 h1:VYAqieSOJNxBDX8KJneTAwvdf4J4zRDE2u+UFXtt9h4=
221+
go.starlark.net v0.0.0-20260326113308-fadfc96def35/go.mod h1:Iue6g6iirlfLoVi/DYCi5/x0h/bAOuWF3dULTKpt2Vo=
220222
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
221223
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
222224
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
@@ -256,8 +258,8 @@ golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7w
256258
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
257259
golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo=
258260
golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
259-
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
260-
golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
261+
golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU=
262+
golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A=
261263
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
262264
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
263265
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=

internal/controller/metal3.io/baremetalhost_controller.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -828,7 +828,7 @@ func (r *BareMetalHostReconciler) registerHost(ctx context.Context, prov provisi
828828
dirty = true
829829
}
830830

831-
preprovImgFormats, err := prov.PreprovisioningImageFormats()
831+
preprovImgFormats, err := prov.PreprovisioningImageFormats(ctx)
832832
if err != nil {
833833
return actionError{err}
834834
}

internal/controller/metal3.io/host_state_machine_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1319,7 +1319,7 @@ func (m *mockProvisioner) Register(_ context.Context, _ provisioner.ManagementAc
13191319
return m.getNextResultByMethod("ValidateManagementAccess"), "", err
13201320
}
13211321

1322-
func (m *mockProvisioner) PreprovisioningImageFormats() ([]metal3api.ImageFormat, error) {
1322+
func (m *mockProvisioner) PreprovisioningImageFormats(_ context.Context) ([]metal3api.ImageFormat, error) {
13231323
return nil, nil
13241324
}
13251325

main.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import (
3333
"github.com/metal3-io/baremetal-operator/pkg/provisioner/demo"
3434
"github.com/metal3-io/baremetal-operator/pkg/provisioner/fixture"
3535
"github.com/metal3-io/baremetal-operator/pkg/provisioner/ironic"
36+
starlarkprov "github.com/metal3-io/baremetal-operator/pkg/provisioner/starlark"
3637
"github.com/metal3-io/baremetal-operator/pkg/secretutils"
3738
"github.com/metal3-io/baremetal-operator/pkg/version"
3839
ironicv1alpha1 "github.com/metal3-io/ironic-standalone-operator/api/v1alpha1"
@@ -133,6 +134,7 @@ func main() {
133134
var devLogging bool
134135
var runInTestMode bool
135136
var runInDemoMode bool
137+
var starlarkScript string
136138
var webhookPort int
137139
var restConfigQPS float64
138140
var restConfigBurst int
@@ -157,6 +159,8 @@ func main() {
157159
flag.BoolVar(&runInTestMode, "test-mode", false, "disable ironic communication")
158160
flag.BoolVar(&runInDemoMode, "demo-mode", false,
159161
"use the demo provisioner to set host states")
162+
flag.StringVar(&starlarkScript, "starlark-provisioner-script", os.Getenv("STARLARK_PROVISIONER_SCRIPT"),
163+
"path to a Starlark script implementing the provisioner interface")
160164
flag.StringVar(&healthAddr, "health-addr", ":9440",
161165
"The address the health endpoint binds to.")
162166
flag.IntVar(&webhookPort, "webhook-port", 9443, //nolint:mnd
@@ -201,6 +205,23 @@ func main() {
201205

202206
printVersion()
203207

208+
// At most one provisioner mode may be selected. Silently picking the
209+
// first true mode (the previous behavior) hides a misconfiguration
210+
// that would otherwise surface much later as confusing reconcile
211+
// behavior. Run this guard before any expensive setup so the operator
212+
// fails fast at startup. Enumerating the three pairwise conflicts
213+
// covers every "two or more true" combination (the all-three case
214+
// matches all three disjuncts).
215+
if (runInTestMode && runInDemoMode) ||
216+
(runInTestMode && starlarkScript != "") ||
217+
(runInDemoMode && starlarkScript != "") {
218+
setupLog.Error(nil, "only one provisioner mode may be set",
219+
"test-mode", runInTestMode,
220+
"demo-mode", runInDemoMode,
221+
"starlark-provisioner-script", starlarkScript)
222+
os.Exit(1)
223+
}
224+
204225
enableWebhook := webhookPort != 0
205226

206227
leaderElectionNamespace := os.Getenv("POD_NAMESPACE")
@@ -320,6 +341,13 @@ func main() {
320341
} else if runInDemoMode {
321342
ctrl.Log.Info("using demo provisioner")
322343
provisionerFactory = &demo.Demo{}
344+
} else if starlarkScript != "" {
345+
ctrl.Log.Info("using starlark provisioner", "script", starlarkScript)
346+
provisionerFactory, err = starlarkprov.NewProvisionerFactory(starlarkScript)
347+
if err != nil {
348+
setupLog.Error(err, "cannot start starlark provisioner")
349+
os.Exit(1)
350+
}
323351
} else {
324352
provLog := zap.New(zap.UseFlagOptions(&logOpts)).WithName("provisioner")
325353
// Check if we should use Ironic CR integration

pkg/provisioner/demo/demo.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ func (p *demoProvisioner) Register(_ context.Context, _ provisioner.ManagementAc
111111
return
112112
}
113113

114-
func (p *demoProvisioner) PreprovisioningImageFormats() ([]metal3api.ImageFormat, error) {
114+
func (p *demoProvisioner) PreprovisioningImageFormats(_ context.Context) ([]metal3api.ImageFormat, error) {
115115
return nil, nil
116116
}
117117

pkg/provisioner/fixture/fixture.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func (p *fixtureProvisioner) Register(_ context.Context, _ provisioner.Managemen
149149
return
150150
}
151151

152-
func (p *fixtureProvisioner) PreprovisioningImageFormats() ([]metal3api.ImageFormat, error) {
152+
func (p *fixtureProvisioner) PreprovisioningImageFormats(_ context.Context) ([]metal3api.ImageFormat, error) {
153153
return nil, nil
154154
}
155155

pkg/provisioner/ironic/ironic.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ func (p *ironicProvisioner) configureNode(ctx context.Context, data provisioner.
382382
// PreprovisioningImageFormats returns a list of acceptable formats for a
383383
// pre-provisioning image to be built by a PreprovisioningImage object. The
384384
// list should be nil if no image build is requested.
385-
func (p *ironicProvisioner) PreprovisioningImageFormats() ([]metal3api.ImageFormat, error) {
385+
func (p *ironicProvisioner) PreprovisioningImageFormats(_ context.Context) ([]metal3api.ImageFormat, error) {
386386
if !p.config.havePreprovImgBuilder {
387387
return nil, nil
388388
}

pkg/provisioner/ironic/register_test.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package ironic
22

33
import (
4+
"context"
45
"net/http"
56
"testing"
67

@@ -964,7 +965,7 @@ func TestPreprovisioningImageFormats(t *testing.T) {
964965
prov, _ := newProvisionerWithSettings(host, bmc.Credentials{}, nil, ironicEndpoint, auth)
965966
prov.config.havePreprovImgBuilder = tc.PreprovImgEnabled
966967

967-
fmts, err := prov.PreprovisioningImageFormats()
968+
fmts, err := prov.PreprovisioningImageFormats(context.Background())
968969

969970
require.NoError(t, err)
970971
assert.Equal(t, tc.Expected, fmts)

0 commit comments

Comments
 (0)