From 353b0fe3394407b7064bf1394d1461d9c09840b2 Mon Sep 17 00:00:00 2001 From: Utku Ozdemir Date: Fri, 15 Mar 2024 12:18:26 +0100 Subject: [PATCH] feat: compute schematic id only from the extensions When determining the schematic ID of a machine, instead of relying the ID on the schematic ID meta-extension, compute the ID by gathering the extensions on the machine. This way, the extension ID will not contain the META values, labels or the kernel args. This ID is actually the ID we need, as when we compare the desired schematic with the actual one during a Talos upgrade, we are only interested in the changes in the list of extensions. This does not cause the kernel args, labels, etc. to disappear, as they are used at installation time and preserved afterward (e.g., during upgrades). Additionally: - Remove the list of extensions from the `Schematic` resource, as it relied upon the schematics always being created through Omni. This is not always the case - i.e., when a partial join config is used. Therefore, instead of relying on it, we store the list of extensions by directly reading them from the machine and storing them on the `MachineStatus` resource. - Skip setting the schematic META section at all if there are no labels set on Download Installation Media screen. Closes siderolabs/omni#55. Signed-off-by: Utku Ozdemir --- client/api/omni/specs/omni.pb.go | 128 +++++++++--------- client/api/omni/specs/omni.proto | 9 +- client/api/omni/specs/omni_vtproto.pb.go | 122 ++++++++--------- client/pkg/omni/resources/omni/schematic.go | 6 +- cmd/integration-test/pkg/tests/talos.go | 10 +- cmd/omni/main.go | 10 +- frontend/src/api/omni/specs/omni.pb.ts | 2 +- frontend/src/api/resources.ts | 2 +- .../omni/Modals/DownloadInstallationMedia.vue | 2 +- internal/backend/grpc/export_test.go | 7 +- internal/backend/grpc/grpc.go | 3 + internal/backend/grpc/grpc_test.go | 30 +++- internal/backend/grpc/management.go | 10 +- internal/backend/grpc/schematics.go | 47 +------ internal/backend/grpc/schematics_test.go | 24 +--- internal/backend/imagefactory/client.go | 122 +++++++++++++++++ internal/backend/imagefactory/imagefactory.go | 7 + .../omni/cluster_machine_config_status.go | 13 +- .../omni/internal/talos/schematic.go | 66 +++++++-- .../omni/internal/task/machine/poll.go | 5 +- .../omni/controllers/omni/machine_status.go | 24 +++- .../omni/machine_status_link_test.go | 4 +- .../controllers/omni/machine_status_test.go | 65 ++++++++- .../omni/controllers/omni/talos_extensions.go | 15 +- .../controllers/omni/talos_extensions_test.go | 7 +- .../controllers/omni/talos_upgrade_status.go | 1 + internal/backend/runtime/omni/omni.go | 9 +- internal/backend/runtime/omni/omni_test.go | 2 +- internal/backend/server.go | 6 +- 29 files changed, 499 insertions(+), 259 deletions(-) create mode 100644 internal/backend/imagefactory/client.go create mode 100644 internal/backend/imagefactory/imagefactory.go diff --git a/client/api/omni/specs/omni.pb.go b/client/api/omni/specs/omni.pb.go index be0e8ee6..7d6a1b6b 100644 --- a/client/api/omni/specs/omni.pb.go +++ b/client/api/omni/specs/omni.pb.go @@ -4669,8 +4669,6 @@ type SchematicSpec struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - - Extensions []string `protobuf:"bytes,1,rep,name=extensions,proto3" json:"extensions,omitempty"` } func (x *SchematicSpec) Reset() { @@ -4705,13 +4703,6 @@ func (*SchematicSpec) Descriptor() ([]byte, []int) { return file_omni_specs_omni_proto_rawDescGZIP(), []int{59} } -func (x *SchematicSpec) GetExtensions() []string { - if x != nil { - return x.Extensions - } - return nil -} - // TalosExtensionsSpec represents all available extensions for a particular Talos version. type TalosExtensionsSpec struct { state protoimpl.MessageState @@ -5095,11 +5086,15 @@ type MachineStatusSpec_Schematic struct { unknownFields protoimpl.UnknownFields // Id is the image factory schematic id used for the image generation. + // + // This must be be plain id computed solely from the system extensions, not including the kernel command line arguments, META content etc. Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` // Invalid marks the machine as having extensions installed bypassing image factory. // Which makes it impossible to detect schematic id and manage the image generation // using image factory. Invalid bool `protobuf:"varint,2,opt,name=invalid,proto3" json:"invalid,omitempty"` + // Extensions is the list of extensions installed on the machine. + Extensions []string `protobuf:"bytes,3,rep,name=extensions,proto3" json:"extensions,omitempty"` } func (x *MachineStatusSpec_Schematic) Reset() { @@ -5148,6 +5143,13 @@ func (x *MachineStatusSpec_Schematic) GetInvalid() bool { return false } +func (x *MachineStatusSpec_Schematic) GetExtensions() []string { + if x != nil { + return x.Extensions + } + return nil +} + type MachineStatusSpec_MaintenanceConfig struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -6367,7 +6369,7 @@ var file_omni_specs_omni_proto_rawDesc = []byte{ 0x65, 0x6e, 0x74, 0x41, 0x64, 0x64, 0x72, 0x65, 0x73, 0x73, 0x12, 0x1c, 0x0a, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x18, 0x02, 0x20, 0x01, 0x28, 0x08, 0x52, 0x09, 0x63, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, - 0x08, 0x04, 0x10, 0x05, 0x22, 0x8e, 0x13, 0x0a, 0x11, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, + 0x08, 0x04, 0x10, 0x05, 0x22, 0xae, 0x13, 0x0a, 0x11, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x53, 0x70, 0x65, 0x63, 0x12, 0x23, 0x0a, 0x0d, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0c, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, @@ -6506,10 +6508,12 @@ var file_omni_specs_omni_proto_rawDesc = []byte{ 0x1f, 0x0a, 0x0b, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x5f, 0x69, 0x64, 0x18, 0x07, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0a, 0x70, 0x72, 0x6f, 0x76, 0x69, 0x64, 0x65, 0x72, 0x49, 0x64, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x70, 0x6f, 0x74, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x04, - 0x73, 0x70, 0x6f, 0x74, 0x1a, 0x35, 0x0a, 0x09, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x74, 0x69, + 0x73, 0x70, 0x6f, 0x74, 0x1a, 0x55, 0x0a, 0x09, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x02, 0x69, 0x64, 0x12, 0x18, 0x0a, 0x07, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x08, 0x52, 0x07, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x1a, 0x2b, 0x0a, 0x11, 0x4d, + 0x28, 0x08, 0x52, 0x07, 0x69, 0x6e, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x12, 0x1e, 0x0a, 0x0a, 0x65, + 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x09, 0x52, + 0x0a, 0x65, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x1a, 0x2b, 0x0a, 0x11, 0x4d, 0x61, 0x69, 0x6e, 0x74, 0x65, 0x6e, 0x61, 0x6e, 0x63, 0x65, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x1a, 0x3e, 0x0a, 0x10, 0x49, 0x6d, 0x61, 0x67, @@ -7164,58 +7168,56 @@ var file_omni_specs_omni_proto_rawDesc = []byte{ 0x0d, 0x52, 0x0a, 0x74, 0x6f, 0x74, 0x61, 0x6c, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x27, 0x0a, 0x0f, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x5f, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0e, 0x72, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x56, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x2f, 0x0a, 0x0d, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, - 0x74, 0x69, 0x63, 0x53, 0x70, 0x65, 0x63, 0x12, 0x1e, 0x0a, 0x0a, 0x65, 0x78, 0x74, 0x65, 0x6e, - 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x0a, 0x65, 0x78, 0x74, - 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0xe7, 0x01, 0x0a, 0x13, 0x54, 0x61, 0x6c, 0x6f, - 0x73, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x70, 0x65, 0x63, 0x12, - 0x35, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, - 0x2e, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x54, 0x61, 0x6c, 0x6f, 0x73, 0x45, 0x78, 0x74, 0x65, - 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, - 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x1a, 0x98, 0x01, 0x0a, 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x12, - 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, - 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x12, 0x18, 0x0a, 0x07, 0x76, - 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, - 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, - 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, - 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, 0x03, 0x72, 0x65, 0x66, 0x18, 0x05, - 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, - 0x65, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, - 0x74, 0x22, 0xc9, 0x01, 0x0a, 0x1a, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x43, - 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x70, 0x65, 0x63, - 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x5f, 0x69, 0x64, - 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x74, 0x69, - 0x63, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x02, 0x20, - 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x53, 0x63, 0x68, 0x65, + 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x22, 0x15, 0x0a, 0x0d, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x74, 0x69, 0x63, 0x53, 0x70, 0x65, 0x63, 0x4a, 0x04, 0x08, 0x01, 0x10, 0x02, 0x22, 0xe7, 0x01, + 0x0a, 0x13, 0x54, 0x61, 0x6c, 0x6f, 0x73, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, + 0x73, 0x53, 0x70, 0x65, 0x63, 0x12, 0x35, 0x0a, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x18, 0x01, + 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1f, 0x2e, 0x73, 0x70, 0x65, 0x63, 0x73, 0x2e, 0x54, 0x61, 0x6c, + 0x6f, 0x73, 0x45, 0x78, 0x74, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x53, 0x70, 0x65, 0x63, + 0x2e, 0x49, 0x6e, 0x66, 0x6f, 0x52, 0x05, 0x69, 0x74, 0x65, 0x6d, 0x73, 0x1a, 0x98, 0x01, 0x0a, + 0x04, 0x49, 0x6e, 0x66, 0x6f, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, + 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x16, 0x0a, 0x06, 0x61, 0x75, 0x74, + 0x68, 0x6f, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x61, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x12, 0x18, 0x0a, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x07, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x12, 0x20, 0x0a, 0x0b, 0x64, + 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x0b, 0x64, 0x65, 0x73, 0x63, 0x72, 0x69, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x10, 0x0a, + 0x03, 0x72, 0x65, 0x66, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x72, 0x65, 0x66, 0x12, + 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x22, 0xc9, 0x01, 0x0a, 0x1a, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x52, 0x06, 0x74, - 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0x46, 0x0a, 0x06, 0x54, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, - 0x0b, 0x0a, 0x07, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, - 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x10, 0x01, - 0x12, 0x0e, 0x0a, 0x0a, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x53, 0x65, 0x74, 0x10, 0x02, - 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x10, 0x03, 0x2a, 0x46, 0x0a, - 0x11, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x41, 0x70, 0x70, 0x6c, 0x79, 0x53, 0x74, 0x61, 0x74, - 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, - 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, - 0x41, 0x50, 0x50, 0x4c, 0x49, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, - 0x4c, 0x45, 0x44, 0x10, 0x03, 0x2a, 0x7a, 0x0a, 0x0f, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, - 0x53, 0x65, 0x74, 0x50, 0x68, 0x61, 0x73, 0x65, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x6e, 0x6b, 0x6e, - 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, - 0x55, 0x70, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x44, - 0x6f, 0x77, 0x6e, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x52, 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, - 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x65, 0x73, 0x74, 0x72, 0x6f, 0x79, 0x69, 0x6e, 0x67, - 0x10, 0x04, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x61, 0x69, 0x6c, 0x65, 0x64, 0x10, 0x05, 0x12, 0x11, - 0x0a, 0x0d, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x69, 0x6e, 0x67, 0x10, - 0x06, 0x2a, 0x48, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, - 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x43, 0x6f, 0x6e, - 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x00, 0x12, 0x08, 0x0a, 0x04, 0x45, 0x74, 0x63, 0x64, - 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x57, 0x69, 0x72, 0x65, 0x67, 0x75, 0x61, 0x72, 0x64, 0x43, - 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x02, 0x42, 0x32, 0x5a, 0x30, 0x67, - 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x73, 0x69, 0x64, 0x65, 0x72, 0x6f, - 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6f, 0x6d, 0x6e, 0x69, 0x2f, 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6f, 0x6d, 0x6e, 0x69, 0x2f, 0x73, 0x70, 0x65, 0x63, 0x73, 0x62, - 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x6f, 0x6e, 0x53, 0x70, 0x65, 0x63, 0x12, 0x21, 0x0a, 0x0c, 0x73, 0x63, 0x68, 0x65, 0x6d, 0x61, + 0x74, 0x69, 0x63, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x0b, 0x73, 0x63, + 0x68, 0x65, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x49, 0x64, 0x12, 0x40, 0x0a, 0x06, 0x74, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0e, 0x32, 0x28, 0x2e, 0x73, 0x70, 0x65, 0x63, + 0x73, 0x2e, 0x53, 0x63, 0x68, 0x65, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x43, 0x6f, 0x6e, 0x66, 0x69, + 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x53, 0x70, 0x65, 0x63, 0x2e, 0x54, 0x61, 0x72, + 0x67, 0x65, 0x74, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x22, 0x46, 0x0a, 0x06, 0x54, + 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, + 0x10, 0x00, 0x12, 0x12, 0x0a, 0x0e, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, 0x72, 0x4d, 0x61, 0x63, + 0x68, 0x69, 0x6e, 0x65, 0x10, 0x01, 0x12, 0x0e, 0x0a, 0x0a, 0x4d, 0x61, 0x63, 0x68, 0x69, 0x6e, + 0x65, 0x53, 0x65, 0x74, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x43, 0x6c, 0x75, 0x73, 0x74, 0x65, + 0x72, 0x10, 0x03, 0x2a, 0x46, 0x0a, 0x11, 0x43, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x41, 0x70, 0x70, + 0x6c, 0x79, 0x53, 0x74, 0x61, 0x74, 0x75, 0x73, 0x12, 0x0b, 0x0a, 0x07, 0x55, 0x4e, 0x4b, 0x4e, + 0x4f, 0x57, 0x4e, 0x10, 0x00, 0x12, 0x0b, 0x0a, 0x07, 0x50, 0x45, 0x4e, 0x44, 0x49, 0x4e, 0x47, + 0x10, 0x01, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x50, 0x50, 0x4c, 0x49, 0x45, 0x44, 0x10, 0x02, 0x12, + 0x0a, 0x0a, 0x06, 0x46, 0x41, 0x49, 0x4c, 0x45, 0x44, 0x10, 0x03, 0x2a, 0x7a, 0x0a, 0x0f, 0x4d, + 0x61, 0x63, 0x68, 0x69, 0x6e, 0x65, 0x53, 0x65, 0x74, 0x50, 0x68, 0x61, 0x73, 0x65, 0x12, 0x0b, + 0x0a, 0x07, 0x55, 0x6e, 0x6b, 0x6e, 0x6f, 0x77, 0x6e, 0x10, 0x00, 0x12, 0x0d, 0x0a, 0x09, 0x53, + 0x63, 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x55, 0x70, 0x10, 0x01, 0x12, 0x0f, 0x0a, 0x0b, 0x53, 0x63, + 0x61, 0x6c, 0x69, 0x6e, 0x67, 0x44, 0x6f, 0x77, 0x6e, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x52, + 0x75, 0x6e, 0x6e, 0x69, 0x6e, 0x67, 0x10, 0x03, 0x12, 0x0e, 0x0a, 0x0a, 0x44, 0x65, 0x73, 0x74, + 0x72, 0x6f, 0x79, 0x69, 0x6e, 0x67, 0x10, 0x04, 0x12, 0x0a, 0x0a, 0x06, 0x46, 0x61, 0x69, 0x6c, + 0x65, 0x64, 0x10, 0x05, 0x12, 0x11, 0x0a, 0x0d, 0x52, 0x65, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, + 0x75, 0x72, 0x69, 0x6e, 0x67, 0x10, 0x06, 0x2a, 0x48, 0x0a, 0x0d, 0x43, 0x6f, 0x6e, 0x64, 0x69, + 0x74, 0x69, 0x6f, 0x6e, 0x54, 0x79, 0x70, 0x65, 0x12, 0x14, 0x0a, 0x10, 0x55, 0x6e, 0x6b, 0x6e, + 0x6f, 0x77, 0x6e, 0x43, 0x6f, 0x6e, 0x64, 0x69, 0x74, 0x69, 0x6f, 0x6e, 0x10, 0x00, 0x12, 0x08, + 0x0a, 0x04, 0x45, 0x74, 0x63, 0x64, 0x10, 0x01, 0x12, 0x17, 0x0a, 0x13, 0x57, 0x69, 0x72, 0x65, + 0x67, 0x75, 0x61, 0x72, 0x64, 0x43, 0x6f, 0x6e, 0x6e, 0x65, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x10, + 0x02, 0x42, 0x32, 0x5a, 0x30, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, + 0x73, 0x69, 0x64, 0x65, 0x72, 0x6f, 0x6c, 0x61, 0x62, 0x73, 0x2f, 0x6f, 0x6d, 0x6e, 0x69, 0x2f, + 0x63, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x6f, 0x6d, 0x6e, 0x69, 0x2f, + 0x73, 0x70, 0x65, 0x63, 0x73, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/client/api/omni/specs/omni.proto b/client/api/omni/specs/omni.proto index 69a1364f..5dcdadd0 100644 --- a/client/api/omni/specs/omni.proto +++ b/client/api/omni/specs/omni.proto @@ -127,12 +127,17 @@ message MachineStatusSpec { message Schematic { // Id is the image factory schematic id used for the image generation. + // + // This must be be plain id computed solely from the system extensions, not including the kernel command line arguments, META content etc. string id = 1; // Invalid marks the machine as having extensions installed bypassing image factory. // Which makes it impossible to detect schematic id and manage the image generation // using image factory. bool invalid = 2; + + // Extensions is the list of extensions installed on the machine. + repeated string extensions = 3; } message MaintenanceConfig { @@ -907,7 +912,7 @@ message ImagePullStatusSpec { // SchematicSpec keeps all schematics generated by Omni. // For each schematic it keeps information about the list of extensions. message SchematicSpec { - repeated string extensions = 1; + reserved 1; } // TalosExtensionsSpec represents all available extensions for a particular Talos version. @@ -937,4 +942,4 @@ message SchematicConfigurationSpec { string schematic_id = 1; Target target = 2; -} \ No newline at end of file +} diff --git a/client/api/omni/specs/omni_vtproto.pb.go b/client/api/omni/specs/omni_vtproto.pb.go index 2265b439..1026278c 100644 --- a/client/api/omni/specs/omni_vtproto.pb.go +++ b/client/api/omni/specs/omni_vtproto.pb.go @@ -235,6 +235,11 @@ func (m *MachineStatusSpec_Schematic) CloneVT() *MachineStatusSpec_Schematic { r := new(MachineStatusSpec_Schematic) r.Id = m.Id r.Invalid = m.Invalid + if rhs := m.Extensions; rhs != nil { + tmpContainer := make([]string, len(rhs)) + copy(tmpContainer, rhs) + r.Extensions = tmpContainer + } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -1725,11 +1730,6 @@ func (m *SchematicSpec) CloneVT() *SchematicSpec { return (*SchematicSpec)(nil) } r := new(SchematicSpec) - if rhs := m.Extensions; rhs != nil { - tmpContainer := make([]string, len(rhs)) - copy(tmpContainer, rhs) - r.Extensions = tmpContainer - } if len(m.unknownFields) > 0 { r.unknownFields = make([]byte, len(m.unknownFields)) copy(r.unknownFields, m.unknownFields) @@ -2135,6 +2135,15 @@ func (this *MachineStatusSpec_Schematic) EqualVT(that *MachineStatusSpec_Schemat if this.Invalid != that.Invalid { return false } + if len(this.Extensions) != len(that.Extensions) { + return false + } + for i, vx := range this.Extensions { + vy := that.Extensions[i] + if vx != vy { + return false + } + } return string(this.unknownFields) == string(that.unknownFields) } @@ -4113,15 +4122,6 @@ func (this *SchematicSpec) EqualVT(that *SchematicSpec) bool { } else if this == nil || that == nil { return false } - if len(this.Extensions) != len(that.Extensions) { - return false - } - for i, vx := range this.Extensions { - vy := that.Extensions[i] - if vx != vy { - return false - } - } return string(this.unknownFields) == string(that.unknownFields) } @@ -4826,6 +4826,15 @@ func (m *MachineStatusSpec_Schematic) MarshalToSizedBufferVT(dAtA []byte) (int, i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } + if len(m.Extensions) > 0 { + for iNdEx := len(m.Extensions) - 1; iNdEx >= 0; iNdEx-- { + i -= len(m.Extensions[iNdEx]) + copy(dAtA[i:], m.Extensions[iNdEx]) + i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Extensions[iNdEx]))) + i-- + dAtA[i] = 0x1a + } + } if m.Invalid { i-- if m.Invalid { @@ -8846,15 +8855,6 @@ func (m *SchematicSpec) MarshalToSizedBufferVT(dAtA []byte) (int, error) { i -= len(m.unknownFields) copy(dAtA[i:], m.unknownFields) } - if len(m.Extensions) > 0 { - for iNdEx := len(m.Extensions) - 1; iNdEx >= 0; iNdEx-- { - i -= len(m.Extensions[iNdEx]) - copy(dAtA[i:], m.Extensions[iNdEx]) - i = protohelpers.EncodeVarint(dAtA, i, uint64(len(m.Extensions[iNdEx]))) - i-- - dAtA[i] = 0xa - } - } return len(dAtA) - i, nil } @@ -9282,6 +9282,12 @@ func (m *MachineStatusSpec_Schematic) SizeVT() (n int) { if m.Invalid { n += 2 } + if len(m.Extensions) > 0 { + for _, s := range m.Extensions { + l = len(s) + n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) + } + } n += len(m.unknownFields) return n } @@ -10821,12 +10827,6 @@ func (m *SchematicSpec) SizeVT() (n int) { } var l int _ = l - if len(m.Extensions) > 0 { - for _, s := range m.Extensions { - l = len(s) - n += 1 + l + protohelpers.SizeOfVarint(uint64(l)) - } - } n += len(m.unknownFields) return n } @@ -12581,6 +12581,38 @@ func (m *MachineStatusSpec_Schematic) UnmarshalVT(dAtA []byte) error { } } m.Invalid = bool(v != 0) + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Extensions", wireType) + } + var stringLen uint64 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return protohelpers.ErrIntOverflow + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + stringLen |= uint64(b&0x7F) << shift + if b < 0x80 { + break + } + } + intStringLen := int(stringLen) + if intStringLen < 0 { + return protohelpers.ErrInvalidLength + } + postIndex := iNdEx + intStringLen + if postIndex < 0 { + return protohelpers.ErrInvalidLength + } + if postIndex > l { + return io.ErrUnexpectedEOF + } + m.Extensions = append(m.Extensions, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) @@ -22313,38 +22345,6 @@ func (m *SchematicSpec) UnmarshalVT(dAtA []byte) error { return fmt.Errorf("proto: SchematicSpec: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Extensions", wireType) - } - var stringLen uint64 - for shift := uint(0); ; shift += 7 { - if shift >= 64 { - return protohelpers.ErrIntOverflow - } - if iNdEx >= l { - return io.ErrUnexpectedEOF - } - b := dAtA[iNdEx] - iNdEx++ - stringLen |= uint64(b&0x7F) << shift - if b < 0x80 { - break - } - } - intStringLen := int(stringLen) - if intStringLen < 0 { - return protohelpers.ErrInvalidLength - } - postIndex := iNdEx + intStringLen - if postIndex < 0 { - return protohelpers.ErrInvalidLength - } - if postIndex > l { - return io.ErrUnexpectedEOF - } - m.Extensions = append(m.Extensions, string(dAtA[iNdEx:postIndex])) - iNdEx = postIndex default: iNdEx = preIndex skippy, err := protohelpers.Skip(dAtA[iNdEx:]) diff --git a/client/pkg/omni/resources/omni/schematic.go b/client/pkg/omni/resources/omni/schematic.go index ba329e5f..b46e6328 100644 --- a/client/pkg/omni/resources/omni/schematic.go +++ b/client/pkg/omni/resources/omni/schematic.go @@ -28,7 +28,11 @@ const ( SchematicType = resource.Type("Schematics.omni.sidero.dev") ) -// Schematic describes previosly generated image factory schematic. +// Schematic describes an image factory schematic. +// +// This resource is used as a cache for the schematics known by Omni: +// - Schematics generated through Omni UI/APIs. +// - Schematics generated/detected from the discovered Talos machines. type Schematic = typed.Resource[SchematicSpec, SchematicExtension] // SchematicSpec wraps specs.SchematicSpec. diff --git a/cmd/integration-test/pkg/tests/talos.go b/cmd/integration-test/pkg/tests/talos.go index 93e47cf5..6469960c 100644 --- a/cmd/integration-test/pkg/tests/talos.go +++ b/cmd/integration-test/pkg/tests/talos.go @@ -585,6 +585,11 @@ func AssertTalosUpgradeIsCancelable(testCtx context.Context, st state.State, clu } } + // wait until the upgraded machine reports the new version + rtestutils.AssertResources(ctx, t, st, ids, func(r *omni.MachineStatus, assert *assert.Assertions) { + assert.Equal(newTalosVersion, strings.TrimLeft(r.TypedSpec().Value.TalosVersion, "v"), resourceDetails(r)) + }) + // revert the update _, err = safe.StateUpdateWithConflicts(ctx, st, omni.NewCluster(resources.DefaultNamespace, clusterName).Metadata(), func(cluster *omni.Cluster) error { cluster.TypedSpec().Value.TalosVersion = currentTalosVersion @@ -593,11 +598,6 @@ func AssertTalosUpgradeIsCancelable(testCtx context.Context, st state.State, clu }) require.NoError(t, err) - // wait until the upgraded machine reports the new version - rtestutils.AssertResources(ctx, t, st, ids, func(r *omni.MachineStatus, assert *assert.Assertions) { - assert.Equal(newTalosVersion, strings.TrimLeft(r.TypedSpec().Value.TalosVersion, "v"), resourceDetails(r)) - }) - rtestutils.AssertResources(ctx, t, st, ids, func(r *omni.ClusterMachineStatus, assert *assert.Assertions) { assert.Equal(specs.ClusterMachineStatusSpec_RUNNING, r.TypedSpec().Value.Stage, resourceDetails(r)) }) diff --git a/cmd/omni/main.go b/cmd/omni/main.go index 1075d34e..993a0c92 100644 --- a/cmd/omni/main.go +++ b/cmd/omni/main.go @@ -30,6 +30,7 @@ import ( authres "github.com/siderolabs/omni/client/pkg/omni/resources/auth" "github.com/siderolabs/omni/internal/backend" "github.com/siderolabs/omni/internal/backend/dns" + "github.com/siderolabs/omni/internal/backend/imagefactory" "github.com/siderolabs/omni/internal/backend/logging" "github.com/siderolabs/omni/internal/backend/resourcelogger" "github.com/siderolabs/omni/internal/backend/runtime/omni" @@ -163,10 +164,16 @@ func runWithState(logger *zap.Logger) func(context.Context, state.State, *virtua } } + imageFactoryClient, err := imagefactory.NewClient(resourceState, config.Config.ImageFactoryBaseURL) + if err != nil { + return fmt.Errorf("failed to set up image factory client: %w", err) + } + linkCounterDeltaCh := make(chan siderolink.LinkCounterDeltas) omniRuntime, err := omni.New(talosClientFactory, dnsService, workloadProxyServiceRegistry, resourceLogger, - linkCounterDeltaCh, resourceState, virtualState, prometheus.DefaultRegisterer, logger.With(logging.Component("omni_runtime"))) + imageFactoryClient, linkCounterDeltaCh, resourceState, virtualState, + prometheus.DefaultRegisterer, logger.With(logging.Component("omni_runtime"))) if err != nil { return fmt.Errorf("failed to set up the controller runtime: %w", err) } @@ -209,6 +216,7 @@ func runWithState(logger *zap.Logger) func(context.Context, state.State, *virtua rootCmdArgs.pprofBindAddress, dnsService, workloadProxyServiceRegistry, + imageFactoryClient, linkCounterDeltaCh, omniRuntime, talosRuntime, diff --git a/frontend/src/api/omni/specs/omni.pb.ts b/frontend/src/api/omni/specs/omni.pb.ts index 5850ea3e..67f7b7b1 100644 --- a/frontend/src/api/omni/specs/omni.pb.ts +++ b/frontend/src/api/omni/specs/omni.pb.ts @@ -187,6 +187,7 @@ export type MachineStatusSpecPlatformMetadata = { export type MachineStatusSpecSchematic = { id?: string invalid?: boolean + extensions?: string[] } export type MachineStatusSpecMaintenanceConfig = { @@ -605,7 +606,6 @@ export type ImagePullStatusSpec = { } export type SchematicSpec = { - extensions?: string[] } export type TalosExtensionsSpecInfo = { diff --git a/frontend/src/api/resources.ts b/frontend/src/api/resources.ts index 3e21a69b..64892d3a 100644 --- a/frontend/src/api/resources.ts +++ b/frontend/src/api/resources.ts @@ -150,7 +150,7 @@ export const KubernetesUsageType = "KubernetesUsages.omni.sidero.dev"; export const PermissionsID = "permissions"; export const PermissionsType = "Permissions.omni.sidero.dev"; export const SecureBoot = "secureboot"; -export const DefaultTalosVersion = "1.6.4"; +export const DefaultTalosVersion = "1.6.6"; export const PatchWeightInstallDisk = 0; export const PatchBaseWeightCluster = 200; export const PatchBaseWeightMachineSet = 400; diff --git a/frontend/src/views/omni/Modals/DownloadInstallationMedia.vue b/frontend/src/views/omni/Modals/DownloadInstallationMedia.vue index 96116ba0..c51d10f0 100644 --- a/frontend/src/views/omni/Modals/DownloadInstallationMedia.vue +++ b/frontend/src/views/omni/Modals/DownloadInstallationMedia.vue @@ -311,7 +311,7 @@ const createSchematic = async () => { meta_values: {}, }; - if (labels.value) { + if (labels.value && Object.keys(labels.value).length > 0) { const l: Record = {}; for (const k in labels.value) { l[k] = labels.value[k].value; diff --git a/internal/backend/grpc/export_test.go b/internal/backend/grpc/export_test.go index 787d8fa8..2171caca 100644 --- a/internal/backend/grpc/export_test.go +++ b/internal/backend/grpc/export_test.go @@ -7,14 +7,17 @@ package grpc import ( "github.com/cosi-project/runtime/pkg/state" + + "github.com/siderolabs/omni/internal/backend/imagefactory" ) type ManagementServer = managementServer //nolint:revive -func NewManagementServer(st state.State) *ManagementServer { +func NewManagementServer(st state.State, imageFactoryClient *imagefactory.Client) *ManagementServer { return &ManagementServer{ - omniState: st, + omniState: st, + imageFactoryClient: imageFactoryClient, } } diff --git a/internal/backend/grpc/grpc.go b/internal/backend/grpc/grpc.go index b58c4bfc..75e8a718 100644 --- a/internal/backend/grpc/grpc.go +++ b/internal/backend/grpc/grpc.go @@ -24,6 +24,7 @@ import ( "github.com/siderolabs/omni/client/api/talos/machine" "github.com/siderolabs/omni/internal/backend/dns" + "github.com/siderolabs/omni/internal/backend/imagefactory" "github.com/siderolabs/omni/internal/backend/logging" "github.com/siderolabs/omni/internal/backend/monitoring" "github.com/siderolabs/omni/internal/memconn" @@ -45,6 +46,7 @@ func MakeServiceServers( oidcProvider OIDCProvider, jwtSigningKeyProvider JWTSigningKeyProvider, dnsService *dns.Service, + imageFactoryClient *imagefactory.Client, logger *zap.Logger, ) ([]ServiceServer, error) { dest, err := generateDest(config.Config.APIURL) @@ -63,6 +65,7 @@ func MakeServiceServers( omniState: state, dnsService: dnsService, jwtSigningKeyProvider: jwtSigningKeyProvider, + imageFactoryClient: imageFactoryClient, logger: logger.With(logging.Component("management_server")), }, &authServer{ diff --git a/internal/backend/grpc/grpc_test.go b/internal/backend/grpc/grpc_test.go index a68c5658..d9743505 100644 --- a/internal/backend/grpc/grpc_test.go +++ b/internal/backend/grpc/grpc_test.go @@ -41,6 +41,7 @@ import ( "github.com/siderolabs/omni/client/pkg/omni/resources/omni" "github.com/siderolabs/omni/internal/backend/dns" grpcomni "github.com/siderolabs/omni/internal/backend/grpc" + "github.com/siderolabs/omni/internal/backend/imagefactory" "github.com/siderolabs/omni/internal/backend/logging" "github.com/siderolabs/omni/internal/backend/runtime" omniruntime "github.com/siderolabs/omni/internal/backend/runtime/omni" @@ -52,11 +53,12 @@ import ( type GrpcSuite struct { suite.Suite - runtime *omniruntime.Runtime - server *grpc.Server - conn *grpc.ClientConn - state state.State - eg errgroup.Group + runtime *omniruntime.Runtime + server *grpc.Server + conn *grpc.ClientConn + imageFactory *imageFactoryMock + state state.State + eg errgroup.Group ctx context.Context //nolint:containedctx ctxCancel context.CancelFunc @@ -76,11 +78,23 @@ func (suite *GrpcSuite) SetupTest() { dnsService := dns.NewService(suite.state, logger) + suite.imageFactory = &imageFactoryMock{} + + suite.Require().NoError(suite.imageFactory.run()) + suite.imageFactory.serve(suite.ctx) + + suite.T().Cleanup(func() { + suite.Require().NoError(suite.imageFactory.eg.Wait()) + }) + + imageFactoryClient, err := imagefactory.NewClient(suite.state, suite.imageFactory.address) + suite.Require().NoError(err) + workloadProxyServiceRegistry, err := workloadproxy.NewServiceRegistry(suite.state, logger) suite.Require().NoError(err) suite.runtime, err = omniruntime.New(clientFactory, dnsService, workloadProxyServiceRegistry, nil, - nil, suite.state, nil, prometheus.NewRegistry(), logger) + imageFactoryClient, nil, suite.state, nil, prometheus.NewRegistry(), logger) suite.Require().NoError(err) runtime.Install(omniruntime.Name, suite.runtime) @@ -91,6 +105,7 @@ func (suite *GrpcSuite) SetupTest() { authConfigInterceptor := interceptor.NewAuthConfig(false, logger.With(logging.Component("interceptor"))) err = suite.newServer( + imageFactoryClient, grpc.ChainUnaryInterceptor(authConfigInterceptor.Unary()), grpc.ChainStreamInterceptor(authConfigInterceptor.Stream()), ) @@ -262,7 +277,7 @@ func (suite *GrpcSuite) TestConfigValidation() { suite.Require().Equalf(codes.InvalidArgument, status.Code(err), err.Error()) } -func (suite *GrpcSuite) newServer(opts ...grpc.ServerOption) error { +func (suite *GrpcSuite) newServer(imageFactoryClient *imagefactory.Client, opts ...grpc.ServerOption) error { var err error suite.socketPath = filepath.Join(suite.T().TempDir(), "socket") @@ -279,6 +294,7 @@ func (suite *GrpcSuite) newServer(opts ...grpc.ServerOption) error { resapi.RegisterResourceServiceServer(suite.server, &grpcomni.ResourceServer{}) management.RegisterManagementServiceServer(suite.server, grpcomni.NewManagementServer( suite.state, + imageFactoryClient, )) go func() { diff --git a/internal/backend/grpc/management.go b/internal/backend/grpc/management.go index 8a5a4b22..3b770b88 100644 --- a/internal/backend/grpc/management.go +++ b/internal/backend/grpc/management.go @@ -48,6 +48,7 @@ import ( ctlcfg "github.com/siderolabs/omni/client/pkg/omnictl/config" "github.com/siderolabs/omni/internal/backend/dns" "github.com/siderolabs/omni/internal/backend/grpc/router" + "github.com/siderolabs/omni/internal/backend/imagefactory" "github.com/siderolabs/omni/internal/backend/runtime" "github.com/siderolabs/omni/internal/backend/runtime/kubernetes" "github.com/siderolabs/omni/internal/backend/runtime/omni" @@ -72,10 +73,11 @@ type managementServer struct { omniState state.State jwtSigningKeyProvider JWTSigningKeyProvider - logHandler *siderolink.LogHandler - logger *zap.Logger - dnsService *dns.Service - omniconfigDest string + logHandler *siderolink.LogHandler + logger *zap.Logger + dnsService *dns.Service + imageFactoryClient *imagefactory.Client + omniconfigDest string } func (s *managementServer) register(server grpc.ServiceRegistrar) { diff --git a/internal/backend/grpc/schematics.go b/internal/backend/grpc/schematics.go index 43fc9ad6..1df6b3da 100644 --- a/internal/backend/grpc/schematics.go +++ b/internal/backend/grpc/schematics.go @@ -12,8 +12,6 @@ import ( "strings" "github.com/cosi-project/runtime/pkg/safe" - "github.com/cosi-project/runtime/pkg/state" - "github.com/siderolabs/image-factory/pkg/client" "github.com/siderolabs/image-factory/pkg/schematic" "github.com/siderolabs/talos/pkg/machinery/resources/runtime" "google.golang.org/grpc/codes" @@ -22,10 +20,8 @@ import ( "github.com/siderolabs/omni/client/api/omni/management" "github.com/siderolabs/omni/client/pkg/meta" "github.com/siderolabs/omni/client/pkg/omni/resources" - "github.com/siderolabs/omni/client/pkg/omni/resources/omni" "github.com/siderolabs/omni/client/pkg/omni/resources/siderolink" "github.com/siderolabs/omni/internal/pkg/auth" - "github.com/siderolabs/omni/internal/pkg/auth/actor" "github.com/siderolabs/omni/internal/pkg/auth/role" "github.com/siderolabs/omni/internal/pkg/config" ) @@ -78,18 +74,9 @@ func (s *managementServer) CreateSchematic(ctx context.Context, request *managem Customization: customization, } - schematicID, err := schematic.ID() + schematicInfo, err := s.imageFactoryClient.EnsureSchematic(ctx, schematic) if err != nil { - return nil, fmt.Errorf("failed to generate schematic ID: %w", err) - } - - schematicResource := omni.NewSchematic( - resources.DefaultNamespace, schematicID, - ) - - res, err := safe.StateGet[*omni.Schematic](ctx, s.omniState, schematicResource.Metadata()) - if err != nil && !state.IsNotFoundError(err) { - return nil, err + return nil, fmt.Errorf("failed to ensure schematic: %w", err) } pxeURL, err := config.Config.GetImageFactoryPXEBaseURL() @@ -97,30 +84,8 @@ func (s *managementServer) CreateSchematic(ctx context.Context, request *managem return nil, err } - response := &management.CreateSchematicResponse{ - SchematicId: schematicID, - PxeUrl: pxeURL.JoinPath("pxe", schematicID).String(), - } - - if res != nil { - return response, nil - } - - client, err := client.New(config.Config.ImageFactoryBaseURL) - if err != nil { - return nil, err - } - - response.SchematicId, err = client.SchematicCreate(ctx, schematic) - if err != nil { - return nil, err - } - - schematicResource.TypedSpec().Value.Extensions = request.Extensions - - if err = s.omniState.Create(actor.MarkContextAsInternalActor(ctx), schematicResource); err != nil && !state.IsConflictError(err) { - return nil, err - } - - return response, nil + return &management.CreateSchematicResponse{ + SchematicId: schematicInfo.FullID, + PxeUrl: pxeURL.JoinPath("pxe", schematicInfo.FullID).String(), + }, nil } diff --git a/internal/backend/grpc/schematics_test.go b/internal/backend/grpc/schematics_test.go index 350dcd84..de8939d1 100644 --- a/internal/backend/grpc/schematics_test.go +++ b/internal/backend/grpc/schematics_test.go @@ -33,7 +33,6 @@ import ( "github.com/siderolabs/omni/client/pkg/omni/resources" "github.com/siderolabs/omni/client/pkg/omni/resources/omni" "github.com/siderolabs/omni/client/pkg/omni/resources/siderolink" - "github.com/siderolabs/omni/internal/pkg/config" ) type imageFactoryMock struct { @@ -153,19 +152,6 @@ func (suite *GrpcSuite) TestSchematicCreate() { suite.Require().NoError(suite.state.Create(ctx, params)) - factory := imageFactoryMock{} - suite.Require().NoError(factory.run()) - - factory.serve(ctx) - - defer func() { - cancel() - - suite.Require().NoError(factory.eg.Wait()) - }() - - config.Config.ImageFactoryBaseURL = factory.address - client := management.NewManagementServiceClient(suite.conn) for _, tt := range []struct { @@ -240,14 +226,12 @@ func (suite *GrpcSuite) TestSchematicCreate() { require.NoError(t, err) require.NotEmpty(t, resp.SchematicId) - rtestutils.AssertResource(ctx, t, suite.state, resp.SchematicId, func(res *omni.Schematic, assert *assert.Assertions) { - assert.EqualValues(req.Extensions, res.TypedSpec().Value.Extensions) - }) + rtestutils.AssertResource(ctx, t, suite.state, resp.SchematicId, func(*omni.Schematic, *assert.Assertions) {}) - factory.schematicMu.Lock() - defer factory.schematicMu.Unlock() + suite.imageFactory.schematicMu.Lock() + defer suite.imageFactory.schematicMu.Unlock() - config, ok := factory.schematics[resp.SchematicId] + config, ok := suite.imageFactory.schematics[resp.SchematicId] require.Truef(t, ok, "the schematic id %q doesn't exist in the image factory", resp.SchematicId) meta := xslices.ToMap(config.Customization.Meta, func(k schematic.MetaValue) (uint32, string) { diff --git a/internal/backend/imagefactory/client.go b/internal/backend/imagefactory/client.go new file mode 100644 index 00000000..b5dcbe07 --- /dev/null +++ b/internal/backend/imagefactory/client.go @@ -0,0 +1,122 @@ +// Copyright (c) 2024 Sidero Labs, Inc. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. + +// Package imagefactory implements image factory operations, enriching them with the Omni state. +package imagefactory + +import ( + "context" + "fmt" + "time" + + "github.com/cosi-project/runtime/pkg/safe" + "github.com/cosi-project/runtime/pkg/state" + "github.com/siderolabs/image-factory/pkg/client" + "github.com/siderolabs/image-factory/pkg/schematic" + + "github.com/siderolabs/omni/client/pkg/omni/resources" + "github.com/siderolabs/omni/client/pkg/omni/resources/omni" + "github.com/siderolabs/omni/internal/pkg/auth/actor" +) + +// Client is the image factory client. +type Client struct { + state state.State + *client.Client +} + +// NewClient creates a new image factory client. +func NewClient(omniState state.State, imageFactoryBaseURL string) (*Client, error) { + factoryClient, err := client.New(imageFactoryBaseURL) + if err != nil { + return nil, err + } + + return &Client{ + state: omniState, + Client: factoryClient, + }, nil +} + +// EnsuredSchematic contains information on the ensured schematics. +type EnsuredSchematic struct { + // FullID is the ID of the full schematic - with the whole content of the schematic.Schematic, i.e., the extra kernel arguments, META values, and system extensions. + FullID string + + // PlainID is the ID of the plain schematic - with only the system extensions. + PlainID string +} + +// EnsureSchematic ensures that the given schematic exists in the image factory. +// +// It ensures two schematics: one with the full content of the schematic.Schematic, and another with only the system extensions. +// +// We do not call the image factory for the schematics that are already known to (cached by) Omni. +func (cli *Client) EnsureSchematic(ctx context.Context, inputSchematic schematic.Schematic) (EnsuredSchematic, error) { + fullSchematicID, err := cli.ensureSingleSchematic(ctx, inputSchematic) + if err != nil { + return EnsuredSchematic{}, fmt.Errorf("failed to ensure single schematic: %w", err) + } + + plainSchematic := schematic.Schematic{ + Customization: schematic.Customization{ + SystemExtensions: schematic.SystemExtensions{ + OfficialExtensions: inputSchematic.Customization.SystemExtensions.OfficialExtensions, + }, + }, + } + + plainSchematicID, err := plainSchematic.ID() + if err != nil { + return EnsuredSchematic{}, fmt.Errorf("failed to generate plain schematic ID: %w", err) + } + + if plainSchematicID != fullSchematicID { + plainSchematicID, err = cli.ensureSingleSchematic(ctx, plainSchematic) + if err != nil { + return EnsuredSchematic{}, fmt.Errorf("failed to ensure plain schematic: %w", err) + } + } + + return EnsuredSchematic{ + FullID: fullSchematicID, + PlainID: plainSchematicID, + }, nil +} + +func (cli *Client) ensureSingleSchematic(ctx context.Context, schematic schematic.Schematic) (string, error) { + schematicID, err := schematic.ID() + if err != nil { + return "", fmt.Errorf("failed to generate schematic ID: %w", err) + } + + schematicResource := omni.NewSchematic( + resources.DefaultNamespace, schematicID, + ) + + res, err := safe.StateGetByID[*omni.Schematic](ctx, cli.state, schematicID) + if err != nil && !state.IsNotFoundError(err) { + return "", err + } + + if res != nil { + return schematicID, nil + } + + callCtx, cancel := context.WithTimeout(ctx, 10*time.Second) + defer cancel() + + if _, err = cli.SchematicCreate(callCtx, schematic); err != nil { + return "", fmt.Errorf("failed to create schematic: %w", err) + } + + ctx = actor.MarkContextAsInternalActor(ctx) + + if err = cli.state.Create(ctx, schematicResource); err != nil && !state.IsConflictError(err) { + return "", err + } + + return schematicID, nil +} diff --git a/internal/backend/imagefactory/imagefactory.go b/internal/backend/imagefactory/imagefactory.go new file mode 100644 index 00000000..44d02a8f --- /dev/null +++ b/internal/backend/imagefactory/imagefactory.go @@ -0,0 +1,7 @@ +// Copyright (c) 2024 Sidero Labs, Inc. +// +// Use of this software is governed by the Business Source License +// included in the LICENSE file. + +// Package imagefactory implements image factory operations the use cases of Omni. +package imagefactory diff --git a/internal/backend/runtime/omni/controllers/omni/cluster_machine_config_status.go b/internal/backend/runtime/omni/controllers/omni/cluster_machine_config_status.go index cce68882..6b291cf0 100644 --- a/internal/backend/runtime/omni/controllers/omni/cluster_machine_config_status.go +++ b/internal/backend/runtime/omni/controllers/omni/cluster_machine_config_status.go @@ -336,7 +336,7 @@ func (h *clusterMachineConfigStatusControllerHandler) syncTalosVersionAndSchemat return false, err } - actualSchematic, err := getSchematic(ctx, c) + schematicInfo, err := talosutils.GetSchematicInfo(ctx, c) if err != nil { if !errors.Is(err, talosutils.ErrInvalidSchematic) { return false, err @@ -345,12 +345,11 @@ func (h *clusterMachineConfigStatusControllerHandler) syncTalosVersionAndSchemat // compatibility code for the machines running extensions installed bypassing image factory // make schematic play no role in the checks expectedSchematic = "" - actualSchematic = "" } - if actualVersion == expectedVersion && actualSchematic == expectedSchematic { + if actualVersion == expectedVersion && schematicInfo.Equal(expectedSchematic) { configStatus.TypedSpec().Value.TalosVersion = actualVersion - configStatus.TypedSpec().Value.SchematicId = actualSchematic + configStatus.TypedSpec().Value.SchematicId = expectedSchematic return true, nil } @@ -363,7 +362,7 @@ func (h *clusterMachineConfigStatusControllerHandler) syncTalosVersionAndSchemat h.logger.Info("upgrading the machine", zap.String("from_version", actualVersion), zap.String("to_version", expectedVersion), - zap.String("from_schematic", actualSchematic), + zap.String("from_schematic", schematicInfo.ID), zap.String("to_schematic", expectedSchematic), zap.String("image", image), zap.String("machine", machineConfig.Metadata().ID())) @@ -726,7 +725,3 @@ func getVersion(ctx context.Context, c *client.Client) (string, error) { return "", errors.New("failed to get Talos version on the machine") } - -func getSchematic(ctx context.Context, c *client.Client) (string, error) { - return talosutils.GetSchematicID(ctx, c) -} diff --git a/internal/backend/runtime/omni/controllers/omni/internal/talos/schematic.go b/internal/backend/runtime/omni/controllers/omni/internal/talos/schematic.go index d73f9a9c..0019f5b8 100644 --- a/internal/backend/runtime/omni/controllers/omni/internal/talos/schematic.go +++ b/internal/backend/runtime/omni/controllers/omni/internal/talos/schematic.go @@ -8,8 +8,8 @@ package talos import ( "context" "fmt" + "strings" - "github.com/cosi-project/runtime/pkg/resource" "github.com/cosi-project/runtime/pkg/safe" "github.com/siderolabs/image-factory/pkg/constants" "github.com/siderolabs/image-factory/pkg/schematic" @@ -20,30 +20,68 @@ import ( // ErrInvalidSchematic means that the machine has extensions installed bypassing the image factory. var ErrInvalidSchematic = fmt.Errorf("invalid schematic") -// GetSchematicID calculates schematic using Talos client: reads extensions list and looks up schematic meta -// extension, or calculates vanilla schematic ID if there's none. -func GetSchematicID(ctx context.Context, c *client.Client) (string, error) { - extensions := map[resource.ID]*runtime.ExtensionStatus{} +// SchematicInfo contains the information about the schematic - the plain schematic ID and the extensions. +type SchematicInfo struct { + ID string + FullID string + Extensions []string +} + +// Equal compares schematic id with both extensions ID and Full ID. +func (si SchematicInfo) Equal(id string) bool { + return si.ID == id || si.FullID == id +} + +// GetSchematicInfo uses Talos API to list all the schematics, and computes the plain schematic ID, +// taking only the extensions into account - ignoring everything else, e.g., the kernel command line args or meta values. +func GetSchematicInfo(ctx context.Context, c *client.Client) (SchematicInfo, error) { + const officialExtensionPrefix = "siderolabs/" items, err := safe.StateListAll[*runtime.ExtensionStatus](ctx, c.COSI) if err != nil { - return "", err + return SchematicInfo{}, fmt.Errorf("failed to list extensions: %w", err) } + var ( + extensions []string + schematicID string + ) + items.ForEach(func(status *runtime.ExtensionStatus) { - extensions[status.TypedSpec().Metadata.Name] = status + name := status.TypedSpec().Metadata.Name + if name == constants.SchematicIDExtensionName { // skip the meta extension + schematicID = status.TypedSpec().Metadata.Version + + return + } + + if !strings.HasPrefix(name, officialExtensionPrefix) { + name = officialExtensionPrefix + name + } + + extensions = append(extensions, name) }) - schematicExtension, ok := extensions[constants.SchematicIDExtensionName] + if schematicID == "" && len(extensions) > 0 { + return SchematicInfo{}, ErrInvalidSchematic + } - if !ok && len(extensions) > 0 { - return "", ErrInvalidSchematic + extensionsSchematic := schematic.Schematic{ + Customization: schematic.Customization{ + SystemExtensions: schematic.SystemExtensions{ + OfficialExtensions: extensions, + }, + }, } - // default schematic - if !ok { - return (&schematic.Schematic{}).ID() + id, err := extensionsSchematic.ID() + if err != nil { + return SchematicInfo{}, fmt.Errorf("failed to calculate extensions schematic ID: %w", err) } - return schematicExtension.TypedSpec().Metadata.Version, nil + return SchematicInfo{ + ID: id, + FullID: schematicID, + Extensions: extensions, + }, nil } diff --git a/internal/backend/runtime/omni/controllers/omni/internal/task/machine/poll.go b/internal/backend/runtime/omni/controllers/omni/internal/task/machine/poll.go index f9dd5045..9da860d9 100644 --- a/internal/backend/runtime/omni/controllers/omni/internal/task/machine/poll.go +++ b/internal/backend/runtime/omni/controllers/omni/internal/task/machine/poll.go @@ -333,7 +333,7 @@ func pollExtensions(ctx context.Context, c *client.Client, info *Info) error { var err error - machineSchematic.Id, err = talos.GetSchematicID(ctx, c) + schematicInfo, err := talos.GetSchematicInfo(ctx, c) if err != nil { if errors.Is(err, talos.ErrInvalidSchematic) { machineSchematic.Invalid = true @@ -344,6 +344,9 @@ func pollExtensions(ctx context.Context, c *client.Client, info *Info) error { return err } + machineSchematic.Id = schematicInfo.ID + machineSchematic.Extensions = schematicInfo.Extensions + return nil } diff --git a/internal/backend/runtime/omni/controllers/omni/machine_status.go b/internal/backend/runtime/omni/controllers/omni/machine_status.go index 0c1ad56b..efd91af5 100644 --- a/internal/backend/runtime/omni/controllers/omni/machine_status.go +++ b/internal/backend/runtime/omni/controllers/omni/machine_status.go @@ -14,20 +14,28 @@ import ( "github.com/cosi-project/runtime/pkg/resource/kvutils" "github.com/cosi-project/runtime/pkg/safe" cosistate "github.com/cosi-project/runtime/pkg/state" + "github.com/siderolabs/image-factory/pkg/schematic" machineapi "github.com/siderolabs/talos/pkg/machinery/api/machine" "go.uber.org/zap" "github.com/siderolabs/omni/client/api/omni/specs" "github.com/siderolabs/omni/client/pkg/omni/resources" "github.com/siderolabs/omni/client/pkg/omni/resources/omni" + "github.com/siderolabs/omni/internal/backend/imagefactory" "github.com/siderolabs/omni/internal/backend/runtime/omni/controllers/helpers" "github.com/siderolabs/omni/internal/backend/runtime/omni/controllers/omni/internal/task" "github.com/siderolabs/omni/internal/backend/runtime/omni/controllers/omni/internal/task/machine" ) +// SchematicEnsurer ensures that the given schematic exists in the image factory. +type SchematicEnsurer interface { + EnsureSchematic(ctx context.Context, inputSchematic schematic.Schematic) (imagefactory.EnsuredSchematic, error) +} + // MachineStatusController manages omni.MachineStatuses based on information from Talos API. type MachineStatusController struct { - runner *task.Runner[machine.InfoChan, machine.CollectTaskSpec] + runner *task.Runner[machine.InfoChan, machine.CollectTaskSpec] + ImageFactoryClient SchematicEnsurer } // Name implements controller.Controller interface. @@ -412,5 +420,19 @@ func (ctrl *MachineStatusController) handleNotification(ctx context.Context, r c return fmt.Errorf("error modifying resource: %w", err) } + if event.Schematic != nil && event.Schematic.Id != "" { + machineSchematic := schematic.Schematic{ + Customization: schematic.Customization{ + SystemExtensions: schematic.SystemExtensions{ + OfficialExtensions: event.Schematic.Extensions, + }, + }, + } + + if _, err := ctrl.ImageFactoryClient.EnsureSchematic(ctx, machineSchematic); err != nil { + return fmt.Errorf("error ensuring schematic: %w", err) + } + } + return nil } diff --git a/internal/backend/runtime/omni/controllers/omni/machine_status_link_test.go b/internal/backend/runtime/omni/controllers/omni/machine_status_link_test.go index ad855505..1b678f30 100644 --- a/internal/backend/runtime/omni/controllers/omni/machine_status_link_test.go +++ b/internal/backend/runtime/omni/controllers/omni/machine_status_link_test.go @@ -34,7 +34,9 @@ func (suite *MachineStatusLinkSuite) SetupTest() { suite.deltaCh = make(chan siderolinkmanager.LinkCounterDeltas) - suite.Require().NoError(suite.runtime.RegisterController(&omnictrl.MachineStatusController{})) + suite.Require().NoError(suite.runtime.RegisterController(&omnictrl.MachineStatusController{ + ImageFactoryClient: &imageFactoryClientMock{}, + })) suite.Require().NoError(suite.runtime.RegisterController(omnictrl.NewMachineStatusLinkController(suite.deltaCh))) } diff --git a/internal/backend/runtime/omni/controllers/omni/machine_status_test.go b/internal/backend/runtime/omni/controllers/omni/machine_status_test.go index 18b6941f..fc5170e9 100644 --- a/internal/backend/runtime/omni/controllers/omni/machine_status_test.go +++ b/internal/backend/runtime/omni/controllers/omni/machine_status_test.go @@ -25,9 +25,37 @@ import ( "github.com/siderolabs/omni/client/pkg/meta" "github.com/siderolabs/omni/client/pkg/omni/resources" "github.com/siderolabs/omni/client/pkg/omni/resources/omni" + "github.com/siderolabs/omni/internal/backend/imagefactory" omnictrl "github.com/siderolabs/omni/internal/backend/runtime/omni/controllers/omni" ) +type imageFactoryClientMock struct{} + +func (i *imageFactoryClientMock) EnsureSchematic(_ context.Context, sch schematic.Schematic) (imagefactory.EnsuredSchematic, error) { + fullID, err := sch.ID() + if err != nil { + return imagefactory.EnsuredSchematic{}, err + } + + plainSchematic := schematic.Schematic{ + Customization: schematic.Customization{ + SystemExtensions: schematic.SystemExtensions{ + OfficialExtensions: sch.Customization.SystemExtensions.OfficialExtensions, + }, + }, + } + + plainID, err := plainSchematic.ID() + if err != nil { + return imagefactory.EnsuredSchematic{}, err + } + + return imagefactory.EnsuredSchematic{ + FullID: fullID, + PlainID: plainID, + }, nil +} + type MachineStatusSuite struct { OmniSuite } @@ -35,7 +63,9 @@ type MachineStatusSuite struct { func (suite *MachineStatusSuite) setup() { suite.startRuntime() - suite.Require().NoError(suite.runtime.RegisterController(&omnictrl.MachineStatusController{})) + suite.Require().NoError(suite.runtime.RegisterController(&omnictrl.MachineStatusController{ + ImageFactoryClient: &imageFactoryClientMock{}, + })) } const testID = "testID" @@ -247,18 +277,41 @@ func (suite *MachineStatusSuite) TestMachineSchematic() { extensions []*runtime.ExtensionStatusSpec }{ { - name: "just schematic id", + name: "extensions", extensions: []*runtime.ExtensionStatusSpec{ { Metadata: extensions.Metadata{ - Name: "schematic", - Version: "1234", - Description: constants.SchematicIDExtensionName, + Name: "gvisor", + Description: "0", + }, + }, + { + Metadata: extensions.Metadata{ + Name: "hello-world-service", + Description: "1", + }, + }, + { + Metadata: extensions.Metadata{ + Name: "mdadm", + Description: "2", + }, + }, + { + Metadata: extensions.Metadata{ + Name: constants.SchematicIDExtensionName, + Description: "3", + Version: "1", }, }, }, expected: &specs.MachineStatusSpec_Schematic{ - Id: "1234", + Id: "7d79f1ce28d7e6c099bc89ccf02238fb574165eb4834c2abf2a61eab998d4dc6", + Extensions: []string{ + "siderolabs/gvisor", + "siderolabs/hello-world-service", + "siderolabs/mdadm", + }, }, }, { diff --git a/internal/backend/runtime/omni/controllers/omni/talos_extensions.go b/internal/backend/runtime/omni/controllers/omni/talos_extensions.go index d3d74337..cbd211f7 100644 --- a/internal/backend/runtime/omni/controllers/omni/talos_extensions.go +++ b/internal/backend/runtime/omni/controllers/omni/talos_extensions.go @@ -15,13 +15,12 @@ import ( "github.com/cosi-project/runtime/pkg/controller" "github.com/cosi-project/runtime/pkg/controller/generic/qtransform" "github.com/siderolabs/gen/xerrors" - "github.com/siderolabs/image-factory/pkg/client" "go.uber.org/zap" "github.com/siderolabs/omni/client/api/omni/specs" "github.com/siderolabs/omni/client/pkg/omni/resources" "github.com/siderolabs/omni/client/pkg/omni/resources/omni" - "github.com/siderolabs/omni/internal/pkg/config" + "github.com/siderolabs/omni/internal/backend/imagefactory" ) // TalosExtensionsController creates omni.TalosExtensions for each omni.TalosVersion. @@ -30,7 +29,7 @@ import ( type TalosExtensionsController = qtransform.QController[*omni.TalosVersion, *omni.TalosExtensions] // NewTalosExtensionsController instantiates the TalosExtensions controller. -func NewTalosExtensionsController() *TalosExtensionsController { +func NewTalosExtensionsController(imageFactoryClient *imagefactory.Client) *TalosExtensionsController { return qtransform.NewQController( qtransform.Settings[*omni.TalosVersion, *omni.TalosExtensions]{ Name: "TalosExtensionsController", @@ -41,12 +40,10 @@ func NewTalosExtensionsController() *TalosExtensionsController { return omni.NewTalosVersion(resources.DefaultNamespace, r.Metadata().ID()) }, TransformFunc: func(ctx context.Context, _ controller.Reader, _ *zap.Logger, version *omni.TalosVersion, extensionsResource *omni.TalosExtensions) error { - factoryClient, err := client.New(config.Config.ImageFactoryBaseURL) - if err != nil { - return fmt.Errorf("failed to get image factory client %w", err) - } + ctx, cancel := context.WithTimeout(ctx, time.Second*10) + defer cancel() - versions, err := factoryClient.Versions(ctx) + versions, err := imageFactoryClient.Versions(ctx) if err != nil { return fmt.Errorf("failed to get existing image factory Talos versions %w", err) } @@ -58,7 +55,7 @@ func NewTalosExtensionsController() *TalosExtensionsController { return xerrors.NewTaggedf[qtransform.SkipReconcileTag]("version %q is not registered in the image factory", version.Metadata().ID()) } - extensionsVersions, err := factoryClient.ExtensionsVersions(ctx, version.TypedSpec().Value.Version) + extensionsVersions, err := imageFactoryClient.ExtensionsVersions(ctx, version.TypedSpec().Value.Version) if err != nil { return controller.NewRequeueErrorf(time.Minute*5, "failed to get extension versions from the image factory %w", err) } diff --git a/internal/backend/runtime/omni/controllers/omni/talos_extensions_test.go b/internal/backend/runtime/omni/controllers/omni/talos_extensions_test.go index 06792dba..f378dc7d 100644 --- a/internal/backend/runtime/omni/controllers/omni/talos_extensions_test.go +++ b/internal/backend/runtime/omni/controllers/omni/talos_extensions_test.go @@ -25,8 +25,8 @@ import ( "github.com/siderolabs/omni/client/pkg/omni/resources" "github.com/siderolabs/omni/client/pkg/omni/resources/omni" + "github.com/siderolabs/omni/internal/backend/imagefactory" omnictrl "github.com/siderolabs/omni/internal/backend/runtime/omni/controllers/omni" - "github.com/siderolabs/omni/internal/pkg/config" ) //nolint:govet @@ -155,11 +155,12 @@ func (suite *TalosExtensionsSuite) TestReconcile() { factory.eg.Wait() //nolint:errcheck }() - config.Config.ImageFactoryBaseURL = factory.address + imageFactoryClient, err := imagefactory.NewClient(suite.state, factory.address) + suite.Require().NoError(err) suite.startRuntime() - suite.Require().NoError(suite.runtime.RegisterQController(omnictrl.NewTalosExtensionsController())) + suite.Require().NoError(suite.runtime.RegisterQController(omnictrl.NewTalosExtensionsController(imageFactoryClient))) versions := []string{ "0.14.0", "1.6.0", "200.0.0", diff --git a/internal/backend/runtime/omni/controllers/omni/talos_upgrade_status.go b/internal/backend/runtime/omni/controllers/omni/talos_upgrade_status.go index 0fb47993..0aeed724 100644 --- a/internal/backend/runtime/omni/controllers/omni/talos_upgrade_status.go +++ b/internal/backend/runtime/omni/controllers/omni/talos_upgrade_status.go @@ -303,6 +303,7 @@ func reconcileTalosUpdateStatus(ctx context.Context, r controller.ReaderWriter, var clusterMachineTalosVersion *omni.ClusterMachineTalosVersion + // if we end up creating the ClusterMachineTalosVersion here, schematic won't be outdated, as we are setting it to the desired schematic ID clusterMachineTalosVersion, err = getOrCreateResource(ctx, r, machine, talosVersion, schematicID) if err != nil { return err diff --git a/internal/backend/runtime/omni/omni.go b/internal/backend/runtime/omni/omni.go index 5e5d2474..f73ed6c0 100644 --- a/internal/backend/runtime/omni/omni.go +++ b/internal/backend/runtime/omni/omni.go @@ -32,6 +32,7 @@ import ( virtualres "github.com/siderolabs/omni/client/pkg/omni/resources/virtual" pkgruntime "github.com/siderolabs/omni/client/pkg/runtime" "github.com/siderolabs/omni/internal/backend/dns" + "github.com/siderolabs/omni/internal/backend/imagefactory" "github.com/siderolabs/omni/internal/backend/resourcelogger" "github.com/siderolabs/omni/internal/backend/runtime" "github.com/siderolabs/omni/internal/backend/runtime/cosi" @@ -73,7 +74,7 @@ type Runtime struct { // //nolint:maintidx func New(talosClientFactory *talos.ClientFactory, dnsService *dns.Service, workloadProxyServiceRegistry *workloadproxy.ServiceRegistry, - resourceLogger *resourcelogger.Logger, linkCounterDeltaCh <-chan siderolink.LinkCounterDeltas, resourceState state.State, + resourceLogger *resourcelogger.Logger, imageFactoryClient *imagefactory.Client, linkCounterDeltaCh <-chan siderolink.LinkCounterDeltas, resourceState state.State, virtualState *virtual.State, metricsRegistry prometheus.Registerer, logger *zap.Logger, ) (*Runtime, error) { var opts []options.Option @@ -168,7 +169,9 @@ func New(talosClientFactory *talos.ClientFactory, dnsService *dns.Service, workl &omnictrl.LoadBalancerController{}, &omnictrl.MachineSetNodeController{}, &omnictrl.MachineSetDestroyStatusController{}, - &omnictrl.MachineStatusController{}, + &omnictrl.MachineStatusController{ + ImageFactoryClient: imageFactoryClient, + }, omnictrl.NewMachineCleanupController(), omnictrl.NewMachineStatusLinkController(linkCounterDeltaCh), &omnictrl.MachineStatusMetricsController{}, @@ -211,7 +214,7 @@ func New(talosClientFactory *talos.ClientFactory, dnsService *dns.Service, workl omnictrl.NewRedactedClusterMachineConfigController(), omnictrl.NewSecretsController(storeFactory), omnictrl.NewTalosConfigController(constants.CertificateValidityTime), - omnictrl.NewTalosExtensionsController(), + omnictrl.NewTalosExtensionsController(imageFactoryClient), omnictrl.NewTalosUpgradeStatusController(), } diff --git a/internal/backend/runtime/omni/omni_test.go b/internal/backend/runtime/omni/omni_test.go index 222c4232..9f1acb81 100644 --- a/internal/backend/runtime/omni/omni_test.go +++ b/internal/backend/runtime/omni/omni_test.go @@ -86,7 +86,7 @@ func (suite *OmniRuntimeSuite) SetupTest() { workloadProxyServiceRegistry, err := workloadproxy.NewServiceRegistry(resourceState, logger) suite.Require().NoError(err) - suite.runtime, err = omniruntime.New(clientFactory, dnsService, workloadProxyServiceRegistry, nil, nil, + suite.runtime, err = omniruntime.New(clientFactory, dnsService, workloadProxyServiceRegistry, nil, nil, nil, resourceState, nil, prometheus.NewRegistry(), logger) suite.Require().NoError(err) diff --git a/internal/backend/server.go b/internal/backend/server.go index ffb8636b..8d7c1636 100644 --- a/internal/backend/server.go +++ b/internal/backend/server.go @@ -59,6 +59,7 @@ import ( grpcomni "github.com/siderolabs/omni/internal/backend/grpc" "github.com/siderolabs/omni/internal/backend/grpc/router" "github.com/siderolabs/omni/internal/backend/health" + "github.com/siderolabs/omni/internal/backend/imagefactory" "github.com/siderolabs/omni/internal/backend/k8sproxy" "github.com/siderolabs/omni/internal/backend/logging" "github.com/siderolabs/omni/internal/backend/monitoring" @@ -94,6 +95,7 @@ type Server struct { authConfig *authres.Config dnsService *dns.Service workloadProxyServiceRegistry *workloadproxy.ServiceRegistry + imageFactoryClient *imagefactory.Client linkCounterDeltaCh chan<- siderolink.LinkCounterDeltas @@ -111,6 +113,7 @@ func NewServer( bindAddress, metricsBindAddress, k8sProxyBindAddress, pprofBindAddress string, dnsService *dns.Service, workloadProxyServiceRegistry *workloadproxy.ServiceRegistry, + imageFactoryClient *imagefactory.Client, linkCounterDeltaCh chan<- siderolink.LinkCounterDeltas, omniRuntime *omni.Runtime, talosRuntime *talos.Runtime, @@ -127,6 +130,7 @@ func NewServer( authConfig: authConfig, dnsService: dnsService, workloadProxyServiceRegistry: workloadProxyServiceRegistry, + imageFactoryClient: imageFactoryClient, linkCounterDeltaCh: linkCounterDeltaCh, proxyServer: proxyServer, bindAddress: bindAddress, @@ -202,7 +206,7 @@ func (s *Server) Run(ctx context.Context) error { return err } - serviceServers, err := grpcomni.MakeServiceServers(runtimeState, s.logHandler, oidcProvider, oidcStorage, s.dnsService, s.logger) + serviceServers, err := grpcomni.MakeServiceServers(runtimeState, s.logHandler, oidcProvider, oidcStorage, s.dnsService, s.imageFactoryClient, s.logger) if err != nil { return err }