11package flex
22
33import (
4+ "bytes"
5+ _ "embed"
46 "encoding/json"
7+ "fmt"
58 "maps"
69 "strings"
10+ "text/template"
711
812 "github.com/Azure/AKSFlexNode/components/api"
913 "github.com/Azure/AKSFlexNode/components/cri"
@@ -17,6 +21,82 @@ import (
1721 "github.com/Azure/aks-flex/plugin/pkg/util/cloudinit"
1822)
1923
24+ //go:embed assets/bootstrap.sh.tmpl
25+ var bootstrapTmpl string
26+
27+ var bootstrapTemplate = template .Must (template .New ("bootstrap.sh" ).Parse (bootstrapTmpl ))
28+
29+ const (
30+ flexNodeVersion = "v0.0.13"
31+ defaultArch = "amd64"
32+ defaultKubeVer = "1.33.3"
33+ )
34+
35+ // Options configures how the flex node userdata is generated.
36+ type Options struct {
37+ EnableNvidiaGPURuntime bool
38+ KubeVersion string
39+ Arch string
40+ KubeadmConfig * kubeadmapi.Config
41+ }
42+
43+ // Option is a functional option for [UserData].
44+ type Option func (* Options )
45+
46+ // WithEnableNvidiaGPURuntime configures the containerd Nvidia GPU runtime.
47+ func WithEnableNvidiaGPURuntime (enable bool ) Option {
48+ return func (o * Options ) { o .EnableNvidiaGPURuntime = enable }
49+ }
50+
51+ // WithKubeVersion sets the Kubernetes version for the downloaded binaries.
52+ func WithKubeVersion (v string ) Option {
53+ return func (o * Options ) { o .KubeVersion = v }
54+ }
55+
56+ // WithArch sets the CPU architecture for the flex node binary (e.g. "amd64", "arm64").
57+ func WithArch (arch string ) Option {
58+ return func (o * Options ) { o .Arch = arch }
59+ }
60+
61+ // WithKubeadmConfig sets the kubeadm join configuration.
62+ func WithKubeadmConfig (cfg * kubeadmapi.Config ) Option {
63+ return func (o * Options ) { o .KubeadmConfig = cfg }
64+ }
65+
66+ func defaultOptions () * Options {
67+ return & Options {
68+ KubeVersion : defaultKubeVer ,
69+ Arch : defaultArch ,
70+ }
71+ }
72+
73+ // supportedArchs is the set of CPU architectures for which flex node binaries
74+ // are published.
75+ var supportedArchs = map [string ]bool {
76+ "amd64" : true ,
77+ "arm64" : true ,
78+ }
79+
80+ // validate performs least-effort validation on the options. This is intentionally
81+ // minimal to catch obvious mistakes for ad-hoc values; callers should perform
82+ // more thorough validation beforehand.
83+ func (o * Options ) validate () error {
84+ if ! supportedArchs [o .Arch ] {
85+ return fmt .Errorf ("unsupported arch %q, supported: amd64, arm64" , o .Arch )
86+ }
87+ o .KubeVersion = strings .TrimPrefix (o .KubeVersion , "v" )
88+ if o .KubeVersion == "" {
89+ return fmt .Errorf ("kube version must not be empty" )
90+ }
91+ return nil
92+ }
93+
94+ // bootstrapParams holds the template parameters for the bootstrap script.
95+ type bootstrapParams struct {
96+ Arch string
97+ Version string
98+ }
99+
20100func flexMetadata [T proto.Message ](name string ) * api.Metadata {
21101 var zero T
22102 typeName := string (zero .ProtoReflect ().Descriptor ().FullName ())
@@ -27,12 +107,12 @@ func flexMetadata[T proto.Message](name string) *api.Metadata {
27107}
28108
29109func resolveFlexComponentConfigs (
30- hasGPU bool ,
110+ enableNvidiaGPURuntime bool ,
31111 kubeVersion string ,
32112 kubeadmConfig * kubeadmapi.Config ,
33113) ([]byte , error ) {
34114 startCRISpecBuilder := cri.StartContainerdServiceSpec_builder {}
35- if hasGPU {
115+ if enableNvidiaGPURuntime {
36116 startCRISpecBuilder .GpuConfig = cri.GPUConfig_builder {
37117 NvidiaRuntime : cri.NvidiaRuntime_builder {}.Build (),
38118 }.Build ()
@@ -104,8 +184,33 @@ func resolveFlexComponentConfigs(
104184 return b , nil
105185}
106186
107- func UserData (hasGPU bool , kubeVersion string , kubeadmConfig * kubeadmapi.Config ) (* cloudinit.UserData , error ) {
108- componentConfigsJSON , err := resolveFlexComponentConfigs (hasGPU , kubeVersion , kubeadmConfig )
187+ func renderBootstrapScript (arch string ) (string , error ) {
188+ var buf bytes.Buffer
189+ if err := bootstrapTemplate .Execute (& buf , bootstrapParams {
190+ Arch : arch ,
191+ Version : flexNodeVersion ,
192+ }); err != nil {
193+ return "" , fmt .Errorf ("rendering bootstrap script: %w" , err )
194+ }
195+ return buf .String (), nil
196+ }
197+
198+ func UserData (opts ... Option ) (* cloudinit.UserData , error ) {
199+ o := defaultOptions ()
200+ for _ , opt := range opts {
201+ opt (o )
202+ }
203+
204+ if err := o .validate (); err != nil {
205+ return nil , err
206+ }
207+
208+ componentConfigsJSON , err := resolveFlexComponentConfigs (o .EnableNvidiaGPURuntime , o .KubeVersion , o .KubeadmConfig )
209+ if err != nil {
210+ return nil , err
211+ }
212+
213+ bootstrapScript , err := renderBootstrapScript (o .Arch )
109214 if err != nil {
110215 return nil , err
111216 }
@@ -124,15 +229,7 @@ func UserData(hasGPU bool, kubeVersion string, kubeadmConfig *kubeadmapi.Config)
124229 },
125230 RunCmd : []any {
126231 []string {"set" , "-e" },
127- strings .Join ([]string {
128- "mkdir -p /tmp/flex" ,
129- // TODO: this should be overridable
130- "curl -L -o /tmp/flex/aks-flex-node-linux-amd64.tar.gz https://github.com/Azure/AKSFlexNode/releases/download/v0.0.12/aks-flex-node-linux-amd64.tar.gz" ,
131- "tar -xzf /tmp/flex/aks-flex-node-linux-amd64.tar.gz -C /tmp/flex" ,
132- "mv /tmp/flex/aks-flex-node-linux-amd64 /tmp/flex/aks-flex-node" ,
133- "chmod +x /tmp/flex/aks-flex-node" ,
134- "/tmp/flex/aks-flex-node apply -f /tmp/flex-config.json" ,
135- }, "\n " ),
232+ bootstrapScript ,
136233 },
137234 }
138235
0 commit comments