diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index edf75db..a204869 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -8,7 +8,7 @@ package v1alpha1 import ( - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" runtime "k8s.io/apimachinery/pkg/runtime" ) diff --git a/cmd/main.go b/cmd/main.go index 16c7574..0da4501 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -75,7 +75,9 @@ func main() { var ipxeServiceProtocol string var ipxeServicePort int var imageServerURL string + var architecture string + flag.StringVar(&architecture, "architecture", "amd64", "Target system architecture (e.g., amd64, arm64)") flag.IntVar(&ipxeServicePort, "ipxe-service-port", 5000, "IPXE Service port to listen on.") flag.StringVar(&ipxeServiceProtocol, "ipxe-service-protocol", "http", "IPXE Service Protocol.") flag.StringVar(&ipxeServiceURL, "ipxe-service-url", "", "IPXE Service URL.") @@ -192,6 +194,7 @@ func main() { Client: mgr.GetClient(), Scheme: mgr.GetScheme(), IPXEServiceURL: ipxeServiceURL, + Architecture: architecture, }).SetupWithManager(mgr); err != nil { setupLog.Error(err, "unable to create controller", "controller", "ServerBootConfigPxe") os.Exit(1) diff --git a/config/crd/bases/metal.ironcore.dev_httpbootconfigs.yaml b/config/crd/bases/metal.ironcore.dev_httpbootconfigs.yaml new file mode 100644 index 0000000..775f1d0 --- /dev/null +++ b/config/crd/bases/metal.ironcore.dev_httpbootconfigs.yaml @@ -0,0 +1,87 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.0 + name: httpbootconfigs.metal.ironcore.dev +spec: + group: metal.ironcore.dev + names: + kind: HTTPBootConfig + listKind: HTTPBootConfigList + plural: httpbootconfigs + singular: httpbootconfig + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.state + name: State + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: HTTPBootConfig is the Schema for the httpbootconfigs API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: HTTPBootConfigSpec defines the desired state of HTTPBootConfig + properties: + ignitionSecretRef: + description: IgnitionSecretRef is a reference to the secret containing + Ignition configuration. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + systemIPs: + description: SystemIPs is a list of IP addresses assigned to the server. + items: + type: string + type: array + systemUUID: + description: SystemUUID is the unique identifier (UUID) of the server. + type: string + ukiURL: + description: UKIURL is the URL where the UKI (Unified Kernel Image) + is hosted. + type: string + type: object + status: + description: HTTPBootConfigStatus defines the observed state of HTTPBootConfig + properties: + state: + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/config/crd/bases/metal.ironcore.dev_ipxebootconfigs.yaml b/config/crd/bases/metal.ironcore.dev_ipxebootconfigs.yaml new file mode 100644 index 0000000..ce30084 --- /dev/null +++ b/config/crd/bases/metal.ironcore.dev_ipxebootconfigs.yaml @@ -0,0 +1,119 @@ +--- +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + annotations: + controller-gen.kubebuilder.io/version: v0.16.0 + name: ipxebootconfigs.metal.ironcore.dev +spec: + group: metal.ironcore.dev + names: + kind: IPXEBootConfig + listKind: IPXEBootConfigList + plural: ipxebootconfigs + singular: ipxebootconfig + scope: Namespaced + versions: + - additionalPrinterColumns: + - jsonPath: .status.state + name: State + type: string + - jsonPath: .metadata.creationTimestamp + name: Age + type: date + name: v1alpha1 + schema: + openAPIV3Schema: + description: IPXEBootConfig is the Schema for the ipxebootconfigs API + properties: + apiVersion: + description: |- + APIVersion defines the versioned schema of this representation of an object. + Servers should convert recognized schemas to the latest internal value, and + may reject unrecognized values. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources + type: string + kind: + description: |- + Kind is a string value representing the REST resource this object represents. + Servers may infer this from the endpoint the client submits requests to. + Cannot be updated. + In CamelCase. + More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds + type: string + metadata: + type: object + spec: + description: IPXEBootConfigSpec defines the desired state of IPXEBootConfig + properties: + ignitionSecretRef: + description: IgnitionSecretRef is a reference to the secret containing + the Ignition configuration. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + image: + description: Image is deprecated and will be removed. + type: string + initrdURL: + description: InitrdURL is the URL where the Initrd (initial RAM disk) + of the OS is hosted, eg. the URL to the Initrd layer of the OS OCI + image. + type: string + ipxeScriptSecretRef: + description: IPXEScriptSecretRef is a reference to the secret containing + the custom IPXE script. + properties: + name: + default: "" + description: |- + Name of the referent. + This field is effectively required, but due to backwards compatibility is + allowed to be empty. Instances of this type with an empty value here are + almost certainly wrong. + More info: https://kubernetes.io/docs/concepts/overview/working-with-objects/names/#names + type: string + type: object + x-kubernetes-map-type: atomic + ipxeServerURL: + description: IPXEServerURL is deprecated and will be removed. + type: string + kernelURL: + description: KernelURL is the URL where the kernel of the OS is hosted, + eg. the URL to the Kernel layer of the OS OCI image. + type: string + squashfsURL: + description: SquashfsURL is the URL where the Squashfs of the OS is + hosted, eg. the URL to the Squashfs layer of the OS OCI image. + type: string + systemIPs: + description: SystemIPs is a list of IP addresses assigned to the server. + items: + type: string + type: array + systemUUID: + description: SystemUUID is the unique identifier (UUID) of the server. + type: string + type: object + status: + description: IPXEBootConfigStatus defines the observed state of IPXEBootConfig + properties: + state: + description: 'Important: Run "make" to regenerate code after modifying + this file' + type: string + type: object + type: object + served: true + storage: true + subresources: + status: {} diff --git a/go.mod b/go.mod index 7a1b224..7ff967f 100644 --- a/go.mod +++ b/go.mod @@ -3,10 +3,10 @@ module github.com/ironcore-dev/boot-operator go 1.23.0 require ( + github.com/containerd/containerd v1.7.25 github.com/coreos/butane v0.23.0 github.com/go-logr/logr v1.4.2 github.com/ironcore-dev/controller-utils v0.9.7 - github.com/ironcore-dev/ironcore-image v0.2.4 github.com/ironcore-dev/metal v0.0.0-20240624131301-18385f342755 github.com/ironcore-dev/metal-operator v0.0.0-20241009145147-7ccca8caf3b1 github.com/onsi/ginkgo/v2 v2.22.2 @@ -20,12 +20,12 @@ require ( ) require ( + github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 // indirect github.com/aws/aws-sdk-go v1.55.5 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/clarketm/json v1.17.1 // indirect - github.com/containerd/containerd v1.7.23 // indirect - github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs v0.3.0 // indirect github.com/containerd/log v0.1.0 // indirect github.com/containerd/platforms v0.2.1 // indirect github.com/coreos/go-json v0.0.0-20230131223807-18775e0fb4fb // indirect @@ -36,8 +36,10 @@ require ( github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect github.com/emicklei/go-restful/v3 v3.12.0 // indirect github.com/evanphx/json-patch/v5 v5.9.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect + github.com/go-logr/stdr v1.2.2 // indirect github.com/go-logr/zapr v1.3.0 // indirect github.com/go-openapi/jsonpointer v0.21.0 // indirect github.com/go-openapi/jsonreference v0.21.0 // indirect @@ -52,8 +54,9 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.17.4 // indirect + github.com/klauspost/compress v1.16.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect + github.com/moby/locker v1.0.1 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -69,6 +72,10 @@ require ( github.com/stretchr/testify v1.10.0 // indirect github.com/vincent-petithory/dataurl v1.0.0 // indirect github.com/x448/float16 v0.8.4 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 // indirect + go.opentelemetry.io/otel v1.28.0 // indirect + go.opentelemetry.io/otel/metric v1.28.0 // indirect + go.opentelemetry.io/otel/trace v1.28.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.27.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect diff --git a/go.sum b/go.sum index c916e04..a6a4de7 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,9 @@ +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9vkmnHYOMsOr4WLk+Vo07yKIzd94sVoIqshQ4bU= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/hcsshim v0.11.7 h1:vl/nj3Bar/CvJSYo7gIQPyRWc9f3c6IeSNavBTSZNZQ= +github.com/Microsoft/hcsshim v0.11.7/go.mod h1:MV8xMfmECjl5HdO7U/3/hFVnkmSBjAjmA09d4bExKcU= github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= @@ -6,10 +12,14 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/clarketm/json v1.17.1 h1:U1IxjqJkJ7bRK4L6dyphmoO840P6bdhPdbbLySourqI= github.com/clarketm/json v1.17.1/go.mod h1:ynr2LRfb0fQU34l07csRNBTcivjySLLiY1YzQqKVfdo= -github.com/containerd/containerd v1.7.23 h1:H2CClyUkmpKAGlhQp95g2WXHfLYc7whAuvZGBNYOOwQ= -github.com/containerd/containerd v1.7.23/go.mod h1:7QUzfURqZWCZV7RLNEn1XjUCQLEf0bkaK4GjUaZehxw= -github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= -github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/cgroups v1.1.0 h1:v8rEWFl6EoqHB+swVNjVoCJE8o3jX7e8nqBGPLaDFBM= +github.com/containerd/cgroups v1.1.0/go.mod h1:6ppBcbh/NOOUU+dMKrykgaBnK9lCIBxHqJDGwsa1mIw= +github.com/containerd/containerd v1.7.25 h1:khEQOAXOEJalRO228yzVsuASLH42vT7DIo9Ss+9SMFQ= +github.com/containerd/containerd v1.7.25/go.mod h1:tWfHzVI0azhw4CT2vaIjsb2CoV4LJ9PrMPaULAr21Ok= +github.com/containerd/continuity v0.4.4 h1:/fNVfTJ7wIl/YPMHjf+5H32uFhl63JucB34PlCpMKII= +github.com/containerd/continuity v0.4.4/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/containerd/errdefs v0.3.0 h1:FSZgGOeK4yuT/+DnF07/Olde/q4KBoMsaamhXxIMDp4= +github.com/containerd/errdefs v0.3.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= @@ -36,12 +46,17 @@ github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8 github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= github.com/evanphx/json-patch/v5 v5.9.0 h1:kcBlZQbplgElYIlo/n1hJbls2z/1awpXxpRi0/FOJfg= github.com/evanphx/json-patch/v5 v5.9.0/go.mod h1:VNkHZ/282BpEyt/tObQO8s5CMPmYYq14uClGH4abBuQ= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY= github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= github.com/go-openapi/jsonpointer v0.21.0 h1:YgdVicSA9vH5RiHs9TZW5oyafXZFc6+2Vc1rr/O9oNQ= @@ -55,6 +70,8 @@ github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/gnostic-models v0.6.8 h1:yo/ABAfM5IMRsS1VnXjTBvUb61tFIHozhlYvRgGre9I= @@ -71,8 +88,6 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/ironcore-dev/controller-utils v0.9.7 h1:ywNcB6lDeOe6UWxJaptbdgb9qb6cDx4+jxBcIGkMQD8= github.com/ironcore-dev/controller-utils v0.9.7/go.mod h1:7X6JUmq75o4KFe05zUa9rEXnS39dSrlXqUnt9Wuiug0= -github.com/ironcore-dev/ironcore-image v0.2.4 h1:yzmnXnXPKXCfJIL8TmyapyHfK6gup8zA+WV9Kq9pYqA= -github.com/ironcore-dev/ironcore-image v0.2.4/go.mod h1:ZwUrZ+pZumYAu6F9RJYe04824Leylma3tvIkNANwiyE= github.com/ironcore-dev/metal v0.0.0-20240624131301-18385f342755 h1:EmR3Ngg2wmOXJkxgsdYVuPXLRfwWmO2Fi+htjih6QGY= github.com/ironcore-dev/metal v0.0.0-20240624131301-18385f342755/go.mod h1:+/bmkghOE7acqXDT/LDH57RemaUzlVwnQjttsOjdoyg= github.com/ironcore-dev/metal-operator v0.0.0-20241009145147-7ccca8caf3b1 h1:akpmbhVHx8vUMY8sBzsaI6nwNYc/pEZQI5aSEsFfJs0= @@ -83,14 +98,20 @@ github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnr github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= -github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/klauspost/compress v1.16.7 h1:2mk3MPGNzKyxErAw8YaohYh69+pa4sIQSC0fPGCFR9I= +github.com/klauspost/compress v1.16.7/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/sys/mountinfo v0.6.2 h1:BzJjoreD5BMFNmD9Rus6gdd1pLuecOFPt8wC+Vygl78= +github.com/moby/sys/mountinfo v0.6.2/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -136,6 +157,16 @@ github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0 h1:4K4tsIXefpVJtvA/8srF4V4y0akAoPHkIslgAkjixJA= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.53.0/go.mod h1:jjdQuTGVsXV4vSs+CJ2qYDeDPf9yIJV23qlIzBm73Vg= +go.opentelemetry.io/otel v1.28.0 h1:/SqNcYk+idO0CxKEUOtKQClMK/MimZihKYMruSMViUo= +go.opentelemetry.io/otel v1.28.0/go.mod h1:q68ijF8Fc8CnMHKyzqL6akLO46ePnjkgfIMIjUIX9z4= +go.opentelemetry.io/otel/metric v1.28.0 h1:f0HGvSl1KRAU1DLgLGFjrwVyismPlnuU6JD6bOeuA5Q= +go.opentelemetry.io/otel/metric v1.28.0/go.mod h1:Fb1eVBFZmLVTMb6PPohq3TO9IIhUisDsbJoL/+uQW4s= +go.opentelemetry.io/otel/trace v1.28.0 h1:GhQ9cUuQGmNDd5BTCP2dAvv75RdMxEfTmYejp+lkx9g= +go.opentelemetry.io/otel/trace v1.28.0/go.mod h1:jPyXzNPg6da9+38HEwElrQiHlVMTnVfM3/yv2OlIHaI= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= diff --git a/internal/controller/serverbootconfiguration_pxe_controller.go b/internal/controller/serverbootconfiguration_pxe_controller.go index 4de2e2c..27aa318 100644 --- a/internal/controller/serverbootconfiguration_pxe_controller.go +++ b/internal/controller/serverbootconfiguration_pxe_controller.go @@ -18,11 +18,12 @@ package controller import ( "context" + "encoding/json" "fmt" + "io" "strings" "github.com/ironcore-dev/boot-operator/api/v1alpha1" - ironcoreimage "github.com/ironcore-dev/ironcore-image" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/controller/controllerutil" @@ -31,14 +32,29 @@ import ( "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" + + "github.com/containerd/containerd/remotes" + "github.com/containerd/containerd/remotes/docker" + ocispec "github.com/opencontainers/image-spec/specs-go/v1" ) type ServerBootConfigurationPXEReconciler struct { client.Client Scheme *runtime.Scheme IPXEServiceURL string + Architecture string } +const ( + MediaTypeKernel = "application/io.gardenlinux.kernel" + MediaTypeInitrd = "application/io.gardenlinux.initrd" + MediaTypeSquashFS = "application/io.gardenlinux.squashfs" + AnnotationArchitecture = "io.gardenlinux.image.layer.architecture" + CNAMEPrefixMetalPXE = "metal_pxe" + ArchitectureAMD64 = "amd64" + ArchitectureARM64 = "arm64" +) + //+kubebuilder:rbac:groups=metal.ironcore.dev,resources=serverbootconfigurations,verbs=get;list;watch //+kubebuilder:rbac:groups=metal.ironcore.dev,resources=serverbootconfigurations/status,verbs=get;update;patch //+kubebuilder:rbac:groups=metal.ironcore.dev,resources=serverbootconfigurations/finalizers,verbs=update @@ -65,7 +81,7 @@ func (r *ServerBootConfigurationPXEReconciler) reconcileExists(ctx context.Conte return r.reconcile(ctx, log, config) } -func (r *ServerBootConfigurationPXEReconciler) delete(ctx context.Context, log logr.Logger, config *metalv1alpha1.ServerBootConfiguration) (ctrl.Result, error) { +func (r *ServerBootConfigurationPXEReconciler) delete(_ context.Context, _ logr.Logger, _ *metalv1alpha1.ServerBootConfiguration) (ctrl.Result, error) { return ctrl.Result{}, nil } @@ -177,20 +193,116 @@ func (r *ServerBootConfigurationPXEReconciler) getSystemIPFromBootConfig(ctx con return systemIPs, nil } -func (r *ServerBootConfigurationPXEReconciler) getImageDetailsFromConfig(_ context.Context, config *metalv1alpha1.ServerBootConfiguration) (string, string, string, error) { +func (r *ServerBootConfigurationPXEReconciler) getImageDetailsFromConfig(ctx context.Context, config *metalv1alpha1.ServerBootConfiguration) (string, string, string, error) { imageDetails := strings.Split(config.Spec.Image, ":") if len(imageDetails) != 2 { return "", "", "", fmt.Errorf("invalid image format") } - kernelURL := fmt.Sprintf("%s/image?imageName=%s&version=%s&layerName=%s", r.IPXEServiceURL, imageDetails[0], imageDetails[1], ironcoreimage.KernelLayerMediaType) - initrdURL := fmt.Sprintf("%s/image?imageName=%s&version=%s&layerName=%s", r.IPXEServiceURL, imageDetails[0], imageDetails[1], ironcoreimage.InitRAMFSLayerMediaType) - // TODO: move this const to ironcore-image - const squashFSMediaTypeLayer = "application/vnd.ironcore.image.squashfs.v1alpha1.squashfs" - squashFSURL := fmt.Sprintf("%s/image?imageName=%s&version=%s&layerName=%s", r.IPXEServiceURL, imageDetails[0], imageDetails[1], squashFSMediaTypeLayer) + + kernelDigest, initrdDigest, squashFSDigest, err := r.getLayerDigestsFromNestedManifest(ctx, imageDetails[0], imageDetails[1]) + if err != nil { + return "", "", "", fmt.Errorf("failed to fetch layer digests: %w", err) + } + + kernelURL := fmt.Sprintf("%s/image?imageName=%s&version=%s&layerDigest=%s", r.IPXEServiceURL, imageDetails[0], imageDetails[1], kernelDigest) + initrdURL := fmt.Sprintf("%s/image?imageName=%s&version=%s&layerDigest=%s", r.IPXEServiceURL, imageDetails[0], imageDetails[1], initrdDigest) + squashFSURL := fmt.Sprintf("%s/image?imageName=%s&version=%s&layerDigest=%s", r.IPXEServiceURL, imageDetails[0], imageDetails[1], squashFSDigest) return kernelURL, initrdURL, squashFSURL, nil } +func (r *ServerBootConfigurationPXEReconciler) getLayerDigestsFromNestedManifest(ctx context.Context, imageName, imageVersion string) (string, string, string, error) { + resolver := docker.NewResolver(docker.ResolverOptions{}) + imageRef := fmt.Sprintf("%s:%s", imageName, imageVersion) + name, desc, err := resolver.Resolve(ctx, imageRef) + if err != nil { + return "", "", "", fmt.Errorf("failed to resolve image reference: %w", err) + } + + indexData, err := fetchContent(ctx, resolver, name, desc) + if err != nil { + return "", "", "", fmt.Errorf("failed to fetch index manifest: %w", err) + } + + var indexManifest ocispec.Index + if err := json.Unmarshal(indexData, &indexManifest); err != nil { + return "", "", "", fmt.Errorf("failed to unmarshal index manifest: %w", err) + } + + var targetManifestDesc ocispec.Descriptor + for _, manifest := range indexManifest.Manifests { + if strings.HasPrefix(manifest.Annotations["cname"], CNAMEPrefixMetalPXE) { + if manifest.Annotations["architecture"] == r.Architecture { + targetManifestDesc = manifest + break + } + } + } + + if targetManifestDesc.Digest == "" { + return "", "", "", fmt.Errorf("failed to find target manifest with cname annotation") + } + + nestedData, err := fetchContent(ctx, resolver, name, targetManifestDesc) + if err != nil { + return "", "", "", fmt.Errorf("failed to fetch nested manifest: %w", err) + } + + var nestedManifest ocispec.Manifest + if err := json.Unmarshal(nestedData, &nestedManifest); err != nil { + return "", "", "", fmt.Errorf("failed to unmarshal nested manifest: %w", err) + } + + var kernelDigest, initrdDigest, squashFSDigest string + for _, layer := range nestedManifest.Layers { + if layer.Annotations[AnnotationArchitecture] == r.Architecture { + switch layer.MediaType { + case MediaTypeKernel: + kernelDigest = layer.Digest.String() + case MediaTypeInitrd: + initrdDigest = layer.Digest.String() + case MediaTypeSquashFS: + squashFSDigest = layer.Digest.String() + } + } + } + + if kernelDigest == "" || initrdDigest == "" || squashFSDigest == "" { + return "", "", "", fmt.Errorf("failed to find all required layer digests") + } + + return kernelDigest, initrdDigest, squashFSDigest, nil +} + +func fetchContent(ctx context.Context, resolver remotes.Resolver, ref string, desc ocispec.Descriptor) ([]byte, error) { + fetcher, err := resolver.Fetcher(ctx, ref) + if err != nil { + return nil, fmt.Errorf("failed to get fetcher: %w", err) + } + + reader, err := fetcher.Fetch(ctx, desc) + if err != nil { + return nil, fmt.Errorf("failed to fetch content: %w", err) + } + + defer func() { + if cerr := reader.Close(); cerr != nil { + fmt.Printf("failed to close reader: %v\n", cerr) + } + }() + + data, err := io.ReadAll(reader) + if err != nil { + return nil, fmt.Errorf("failed to read content: %w", err) + } + + if int64(len(data)) != desc.Size { + return nil, fmt.Errorf("size mismatch: expected %d, got %d", desc.Size, len(data)) + } + + return data, nil +} + // SetupWithManager sets up the controller with the Manager. func (r *ServerBootConfigurationPXEReconciler) SetupWithManager(mgr ctrl.Manager) error { return ctrl.NewControllerManagedBy(mgr). diff --git a/internal/controller/serverbootconfiguration_pxe_controller_test.go b/internal/controller/serverbootconfiguration_pxe_controller_test.go index acf6abd..4fc1f87 100644 --- a/internal/controller/serverbootconfiguration_pxe_controller_test.go +++ b/internal/controller/serverbootconfiguration_pxe_controller_test.go @@ -65,7 +65,7 @@ var _ = Describe("ServerBootConfiguration Controller", func() { ServerRef: corev1.LocalObjectReference{ Name: server.Name, }, - Image: "foo:bar", + Image: "ghcr.io/gardenlinux/gardenlinux:1758.0", IgnitionSecretRef: &corev1.LocalObjectReference{Name: "foo"}, }, } @@ -89,9 +89,6 @@ var _ = Describe("ServerBootConfiguration Controller", func() { })), HaveField("Spec.SystemUUID", server.Spec.UUID), HaveField("Spec.SystemIPs", ContainElement("1.1.1.1")), - HaveField("Spec.KernelURL", "http://localhost:5000/image?imageName=foo&version=bar&layerName=application/vnd.ironcore.image.vmlinuz.v1alpha1.vmlinuz"), - HaveField("Spec.InitrdURL", "http://localhost:5000/image?imageName=foo&version=bar&layerName=application/vnd.ironcore.image.initramfs.v1alpha1.initramfs"), - HaveField("Spec.SquashfsURL", "http://localhost:5000/image?imageName=foo&version=bar&layerName=application/vnd.ironcore.image.squashfs.v1alpha1.squashfs"), HaveField("Spec.IgnitionSecretRef.Name", "foo"), )) }) diff --git a/internal/controller/suite_test.go b/internal/controller/suite_test.go index ece733b..b62aea0 100644 --- a/internal/controller/suite_test.go +++ b/internal/controller/suite_test.go @@ -132,6 +132,7 @@ func SetupTest() *corev1.Namespace { Client: k8sManager.GetClient(), Scheme: k8sManager.GetScheme(), IPXEServiceURL: "http://localhost:5000", + Architecture: "amd64", }).SetupWithManager(k8sManager)).To(Succeed()) Expect((&ServerBootConfigurationHTTPReconciler{ diff --git a/server/imageproxyserver.go b/server/imageproxyserver.go index 4814399..6436cf5 100644 --- a/server/imageproxyserver.go +++ b/server/imageproxyserver.go @@ -13,15 +13,14 @@ import ( "strings" "github.com/go-logr/logr" - ociimage "github.com/opencontainers/image-spec/specs-go/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) const ( - ghcrIOKey = "ghcr.io/" - imageKey = "imageName" - layerKey = "layerName" - versionKey = "version" + ghcrIOKey = "ghcr.io/" + imageKey = "imageName" + layerDigestKey = "layerDigest" + versionKey = "version" ) type TokenResponse struct { @@ -31,7 +30,7 @@ type TokenResponse struct { type ImageDetails struct { OCIImageName string RepositoryName string - LayerName string + LayerDigest string Version string } @@ -69,13 +68,7 @@ func handleGHCR(w http.ResponseWriter, r *http.Request, imageDetails *ImageDetai return } - digest, err := imageDetails.getLayerDigest(bearerToken) - if err != nil { - http.Error(w, "Resource Not Found", http.StatusNotFound) - log.Info("Error: Failed to obtain layer digest", "error", err) - return - } - + digest := imageDetails.LayerDigest targetURL := fmt.Sprintf("https://ghcr.io/v2/%s/blobs/%s", imageDetails.RepositoryName, digest) proxyURL, _ := url.Parse(targetURL) @@ -162,18 +155,18 @@ func replaceResponse(originalResp, redirectResp *http.Response) { func parseImageURL(queries url.Values) (imageDetails ImageDetails, err error) { ociImageName := queries.Get(imageKey) - layerName := queries.Get(layerKey) + layerDigest := queries.Get(layerDigestKey) version := queries.Get(versionKey) repositoryName := strings.TrimPrefix(ociImageName, ghcrIOKey) - if ociImageName == "" || layerName == "" || version == "" { + if ociImageName == "" || layerDigest == "" || version == "" { return ImageDetails{}, fmt.Errorf("missing required query parameters 'image' or 'layer' or 'version'") } return ImageDetails{ OCIImageName: ociImageName, RepositoryName: repositoryName, - LayerName: layerName, + LayerDigest: layerDigest, Version: version, }, nil } @@ -186,35 +179,3 @@ func (ImageDetails ImageDetails) modifyDirector(proxyURL *url.URL, bearerToken s req.Header.Set("Authorization", "Bearer "+bearerToken) } } - -func (imageDetails ImageDetails) getLayerDigest(token string) (string, error) { - url := fmt.Sprintf("https://ghcr.io/v2/%s/manifests/%s", imageDetails.RepositoryName, imageDetails.Version) - req, err := http.NewRequest("GET", url, nil) - if err != nil { - return "", fmt.Errorf("http request to fetch manifest failed %w", err) - } - - req.Header.Set("Authorization", "Bearer "+token) - req.Header.Set("Accept", "application/vnd.oci.image.manifest.v1+json") - - resp, err := http.DefaultClient.Do(req) - if err != nil { - return "", fmt.Errorf("http client connection failed %w", err) - } - defer func() { - _ = resp.Body.Close() - }() - - var manifest ociimage.Manifest - if err := json.NewDecoder(resp.Body).Decode(&manifest); err != nil { - return "", fmt.Errorf("unable to decode the manifest %w", err) - } - - for _, layer := range manifest.Layers { - if strings.Contains(layer.MediaType, imageDetails.LayerName) { - return string(layer.Digest), nil - } - } - - return "", fmt.Errorf("%s layer not found in the manifest", imageDetails.LayerName) -}