From 1f625e247d14bd4305ec1cdbfb702f5bd19e443e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 01:32:35 +0000 Subject: [PATCH 1/2] Initial plan From 9e2f6eb78d5c6c1ab3c7012c721fb1942ad1cfde Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 5 Mar 2026 01:41:04 +0000 Subject: [PATCH 2/2] Install crictl on the host as part of CRI binaries download Co-authored-by: bcho <1975118+bcho@users.noreply.github.com> --- components/cri/action.pb.go | 133 ++++++++++++++++++++++++--- components/cri/action.proto | 3 + components/cri/v20260301/download.go | 60 ++++++++++++ go.mod | 4 +- pkg/bootstrapper/components.go | 4 + pkg/config/defaults.go | 1 + pkg/config/structs.go | 7 ++ 7 files changed, 195 insertions(+), 17 deletions(-) diff --git a/components/cri/action.pb.go b/components/cri/action.pb.go index cc77a534..59c6e1a6 100644 --- a/components/cri/action.pb.go +++ b/components/cri/action.pb.go @@ -143,6 +143,7 @@ type DownloadCRIBinariesSpec struct { state protoimpl.MessageState `protogen:"opaque.v1"` xxx_hidden_ContainerdVersion *string `protobuf:"bytes,1,opt,name=containerd_version,json=containerdVersion"` xxx_hidden_RuncVersion *string `protobuf:"bytes,2,opt,name=runc_version,json=runcVersion"` + xxx_hidden_CrictlVersion *string `protobuf:"bytes,3,opt,name=crictl_version,json=crictlVersion"` XXX_raceDetectHookData protoimpl.RaceDetectHookData XXX_presence [1]uint32 unknownFields protoimpl.UnknownFields @@ -194,14 +195,29 @@ func (x *DownloadCRIBinariesSpec) GetRuncVersion() string { return "" } +func (x *DownloadCRIBinariesSpec) GetCrictlVersion() string { + if x != nil { + if x.xxx_hidden_CrictlVersion != nil { + return *x.xxx_hidden_CrictlVersion + } + return "" + } + return "" +} + func (x *DownloadCRIBinariesSpec) SetContainerdVersion(v string) { x.xxx_hidden_ContainerdVersion = &v - protoimpl.X.SetPresent(&(x.XXX_presence[0]), 0, 2) + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 0, 3) } func (x *DownloadCRIBinariesSpec) SetRuncVersion(v string) { x.xxx_hidden_RuncVersion = &v - protoimpl.X.SetPresent(&(x.XXX_presence[0]), 1, 2) + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 1, 3) +} + +func (x *DownloadCRIBinariesSpec) SetCrictlVersion(v string) { + x.xxx_hidden_CrictlVersion = &v + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 2, 3) } func (x *DownloadCRIBinariesSpec) HasContainerdVersion() bool { @@ -218,6 +234,13 @@ func (x *DownloadCRIBinariesSpec) HasRuncVersion() bool { return protoimpl.X.Present(&(x.XXX_presence[0]), 1) } +func (x *DownloadCRIBinariesSpec) HasCrictlVersion() bool { + if x == nil { + return false + } + return protoimpl.X.Present(&(x.XXX_presence[0]), 2) +} + func (x *DownloadCRIBinariesSpec) ClearContainerdVersion() { protoimpl.X.ClearPresent(&(x.XXX_presence[0]), 0) x.xxx_hidden_ContainerdVersion = nil @@ -228,11 +251,17 @@ func (x *DownloadCRIBinariesSpec) ClearRuncVersion() { x.xxx_hidden_RuncVersion = nil } +func (x *DownloadCRIBinariesSpec) ClearCrictlVersion() { + protoimpl.X.ClearPresent(&(x.XXX_presence[0]), 2) + x.xxx_hidden_CrictlVersion = nil +} + type DownloadCRIBinariesSpec_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. ContainerdVersion *string RuncVersion *string + CrictlVersion *string } func (b0 DownloadCRIBinariesSpec_builder) Build() *DownloadCRIBinariesSpec { @@ -240,13 +269,17 @@ func (b0 DownloadCRIBinariesSpec_builder) Build() *DownloadCRIBinariesSpec { b, x := &b0, m0 _, _ = b, x if b.ContainerdVersion != nil { - protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 0, 2) + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 0, 3) x.xxx_hidden_ContainerdVersion = b.ContainerdVersion } if b.RuncVersion != nil { - protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 1, 2) + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 1, 3) x.xxx_hidden_RuncVersion = b.RuncVersion } + if b.CrictlVersion != nil { + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 2, 3) + x.xxx_hidden_CrictlVersion = b.CrictlVersion + } return m0 } @@ -256,6 +289,8 @@ type DownloadCRIBinariesStatus struct { xxx_hidden_ContainerdPath *string `protobuf:"bytes,2,opt,name=containerd_path,json=containerdPath"` xxx_hidden_RuncDownloadUrl *string `protobuf:"bytes,3,opt,name=runc_download_url,json=runcDownloadUrl"` xxx_hidden_RuncPath *string `protobuf:"bytes,4,opt,name=runc_path,json=runcPath"` + xxx_hidden_CrictlDownloadUrl *string `protobuf:"bytes,5,opt,name=crictl_download_url,json=crictlDownloadUrl"` + xxx_hidden_CrictlPath *string `protobuf:"bytes,6,opt,name=crictl_path,json=crictlPath"` XXX_raceDetectHookData protoimpl.RaceDetectHookData XXX_presence [1]uint32 unknownFields protoimpl.UnknownFields @@ -327,24 +362,54 @@ func (x *DownloadCRIBinariesStatus) GetRuncPath() string { return "" } +func (x *DownloadCRIBinariesStatus) GetCrictlDownloadUrl() string { + if x != nil { + if x.xxx_hidden_CrictlDownloadUrl != nil { + return *x.xxx_hidden_CrictlDownloadUrl + } + return "" + } + return "" +} + +func (x *DownloadCRIBinariesStatus) GetCrictlPath() string { + if x != nil { + if x.xxx_hidden_CrictlPath != nil { + return *x.xxx_hidden_CrictlPath + } + return "" + } + return "" +} + func (x *DownloadCRIBinariesStatus) SetContainerdDownloadUrl(v string) { x.xxx_hidden_ContainerdDownloadUrl = &v - protoimpl.X.SetPresent(&(x.XXX_presence[0]), 0, 4) + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 0, 6) } func (x *DownloadCRIBinariesStatus) SetContainerdPath(v string) { x.xxx_hidden_ContainerdPath = &v - protoimpl.X.SetPresent(&(x.XXX_presence[0]), 1, 4) + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 1, 6) } func (x *DownloadCRIBinariesStatus) SetRuncDownloadUrl(v string) { x.xxx_hidden_RuncDownloadUrl = &v - protoimpl.X.SetPresent(&(x.XXX_presence[0]), 2, 4) + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 2, 6) } func (x *DownloadCRIBinariesStatus) SetRuncPath(v string) { x.xxx_hidden_RuncPath = &v - protoimpl.X.SetPresent(&(x.XXX_presence[0]), 3, 4) + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 3, 6) +} + +func (x *DownloadCRIBinariesStatus) SetCrictlDownloadUrl(v string) { + x.xxx_hidden_CrictlDownloadUrl = &v + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 4, 6) +} + +func (x *DownloadCRIBinariesStatus) SetCrictlPath(v string) { + x.xxx_hidden_CrictlPath = &v + protoimpl.X.SetPresent(&(x.XXX_presence[0]), 5, 6) } func (x *DownloadCRIBinariesStatus) HasContainerdDownloadUrl() bool { @@ -375,6 +440,20 @@ func (x *DownloadCRIBinariesStatus) HasRuncPath() bool { return protoimpl.X.Present(&(x.XXX_presence[0]), 3) } +func (x *DownloadCRIBinariesStatus) HasCrictlDownloadUrl() bool { + if x == nil { + return false + } + return protoimpl.X.Present(&(x.XXX_presence[0]), 4) +} + +func (x *DownloadCRIBinariesStatus) HasCrictlPath() bool { + if x == nil { + return false + } + return protoimpl.X.Present(&(x.XXX_presence[0]), 5) +} + func (x *DownloadCRIBinariesStatus) ClearContainerdDownloadUrl() { protoimpl.X.ClearPresent(&(x.XXX_presence[0]), 0) x.xxx_hidden_ContainerdDownloadUrl = nil @@ -395,6 +474,16 @@ func (x *DownloadCRIBinariesStatus) ClearRuncPath() { x.xxx_hidden_RuncPath = nil } +func (x *DownloadCRIBinariesStatus) ClearCrictlDownloadUrl() { + protoimpl.X.ClearPresent(&(x.XXX_presence[0]), 4) + x.xxx_hidden_CrictlDownloadUrl = nil +} + +func (x *DownloadCRIBinariesStatus) ClearCrictlPath() { + protoimpl.X.ClearPresent(&(x.XXX_presence[0]), 5) + x.xxx_hidden_CrictlPath = nil +} + type DownloadCRIBinariesStatus_builder struct { _ [0]func() // Prevents comparability and use of unkeyed literals for the builder. @@ -402,6 +491,8 @@ type DownloadCRIBinariesStatus_builder struct { ContainerdPath *string RuncDownloadUrl *string RuncPath *string + CrictlDownloadUrl *string + CrictlPath *string } func (b0 DownloadCRIBinariesStatus_builder) Build() *DownloadCRIBinariesStatus { @@ -409,21 +500,29 @@ func (b0 DownloadCRIBinariesStatus_builder) Build() *DownloadCRIBinariesStatus { b, x := &b0, m0 _, _ = b, x if b.ContainerdDownloadUrl != nil { - protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 0, 4) + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 0, 6) x.xxx_hidden_ContainerdDownloadUrl = b.ContainerdDownloadUrl } if b.ContainerdPath != nil { - protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 1, 4) + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 1, 6) x.xxx_hidden_ContainerdPath = b.ContainerdPath } if b.RuncDownloadUrl != nil { - protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 2, 4) + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 2, 6) x.xxx_hidden_RuncDownloadUrl = b.RuncDownloadUrl } if b.RuncPath != nil { - protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 3, 4) + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 3, 6) x.xxx_hidden_RuncPath = b.RuncPath } + if b.CrictlDownloadUrl != nil { + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 4, 6) + x.xxx_hidden_CrictlDownloadUrl = b.CrictlDownloadUrl + } + if b.CrictlPath != nil { + protoimpl.X.SetPresentNonAtomic(&(x.XXX_presence[0]), 5, 6) + x.xxx_hidden_CrictlPath = b.CrictlPath + } return m0 } @@ -1157,15 +1256,19 @@ const file_components_cri_action_proto_rawDesc = "" + "\x13DownloadCRIBinaries\x12=\n" + "\bmetadata\x18\x01 \x01(\v2!.aks.flex.components.api.MetadataR\bmetadata\x12D\n" + "\x04spec\x18\x02 \x01(\v20.aks.flex.components.cri.DownloadCRIBinariesSpecR\x04spec\x12J\n" + - "\x06status\x18\x03 \x01(\v22.aks.flex.components.cri.DownloadCRIBinariesStatusR\x06status\"k\n" + + "\x06status\x18\x03 \x01(\v22.aks.flex.components.cri.DownloadCRIBinariesStatusR\x06status\"\x92\x01\n" + "\x17DownloadCRIBinariesSpec\x12-\n" + "\x12containerd_version\x18\x01 \x01(\tR\x11containerdVersion\x12!\n" + - "\frunc_version\x18\x02 \x01(\tR\vruncVersion\"\xc5\x01\n" + + "\frunc_version\x18\x02 \x01(\tR\vruncVersion\x12%\n" + + "\x0ecrictl_version\x18\x03 \x01(\tR\rcrictlVersion\"\x96\x02\n" + "\x19DownloadCRIBinariesStatus\x126\n" + "\x17containerd_download_url\x18\x01 \x01(\tR\x15containerdDownloadUrl\x12'\n" + "\x0fcontainerd_path\x18\x02 \x01(\tR\x0econtainerdPath\x12*\n" + "\x11runc_download_url\x18\x03 \x01(\tR\x0fruncDownloadUrl\x12\x1b\n" + - "\trunc_path\x18\x04 \x01(\tR\bruncPath\"\xef\x01\n" + + "\trunc_path\x18\x04 \x01(\tR\bruncPath\x12.\n" + + "\x13crictl_download_url\x18\x05 \x01(\tR\x11crictlDownloadUrl\x12\x1f\n" + + "\vcrictl_path\x18\x06 \x01(\tR\n" + + "crictlPath\"\xef\x01\n" + "\x16StartContainerdService\x12=\n" + "\bmetadata\x18\x01 \x01(\v2!.aks.flex.components.api.MetadataR\bmetadata\x12G\n" + "\x04spec\x18\x02 \x01(\v23.aks.flex.components.cri.StartContainerdServiceSpecR\x04spec\x12M\n" + diff --git a/components/cri/action.proto b/components/cri/action.proto index 14d84709..eb251d53 100644 --- a/components/cri/action.proto +++ b/components/cri/action.proto @@ -17,6 +17,7 @@ message DownloadCRIBinaries { message DownloadCRIBinariesSpec { string containerd_version = 1; string runc_version = 2; + string crictl_version = 3; } message DownloadCRIBinariesStatus { @@ -24,6 +25,8 @@ message DownloadCRIBinariesStatus { string containerd_path = 2; string runc_download_url = 3; string runc_path = 4; + string crictl_download_url = 5; + string crictl_path = 6; } message StartContainerdService { diff --git a/components/cri/v20260301/download.go b/components/cri/v20260301/download.go index 8d8b8e48..6b867072 100644 --- a/components/cri/v20260301/download.go +++ b/components/cri/v20260301/download.go @@ -29,11 +29,16 @@ const ( // runc download URL template: version, arch defaultRuncURLTemplate = "https://github.com/opencontainers/runc/releases/download/v%s/runc.%s" + + // crictl download URL template: version, version, arch + // FIXME: confirm correct download endpoint + defaultCrictlURLTemplate = "https://github.com/kubernetes-sigs/cri-tools/releases/download/v%s/crictl-v%s-linux-%s.tar.gz" ) var ( containerdBinPath = filepath.Join(config.DefaultBinaryPath, "containerd") runcBinPath = filepath.Join(config.DefaultBinaryPath, "runc") + crictlBinPath = filepath.Join(config.DefaultBinaryPath, "crictl") // containerdBinaries lists all binaries included in containerd releases. containerdBinaries = []string{ @@ -76,12 +81,15 @@ func (d *downloadCRIBinariesAction) ApplyAction( containerdURL := constructContainerdDownloadURL(spec.GetContainerdVersion()) runcURL := constructRuncDownloadURL(spec.GetRuncVersion()) + crictlURL := constructCrictlDownloadURL(spec.GetCrictlVersion()) st := cri.DownloadCRIBinariesStatus_builder{ ContainerdDownloadUrl: to.Ptr(containerdURL), ContainerdPath: to.Ptr(containerdBinPath), RuncDownloadUrl: to.Ptr(runcURL), RuncPath: to.Ptr(runcBinPath), + CrictlDownloadUrl: to.Ptr(crictlURL), + CrictlPath: to.Ptr(crictlBinPath), } if !containerdVersionMatch(spec.GetContainerdVersion()) { @@ -96,6 +104,12 @@ func (d *downloadCRIBinariesAction) ApplyAction( } } + if !crictlVersionMatch(spec.GetCrictlVersion()) { + if err := d.downloadCrictl(ctx, crictlURL); err != nil { + return nil, err + } + } + config.SetStatus(st.Build()) item, err := anypb.New(config) @@ -140,6 +154,32 @@ func (d *downloadCRIBinariesAction) downloadRunc(ctx context.Context, downloadUR return nil } +// downloadCrictl downloads and extracts the crictl binary from a tar.gz archive. +func (d *downloadCRIBinariesAction) downloadCrictl(ctx context.Context, downloadURL string) error { + installed := false + for tarFile, err := range utilio.DecompressTarGzFromRemote(ctx, downloadURL) { + if err != nil { + return status.Errorf(codes.Internal, "decompress crictl tar: %s", err) + } + + if tarFile.Name != "crictl" { + continue + } + + if err := utilio.InstallFile(crictlBinPath, tarFile.Body, 0755); err != nil { + return status.Errorf(codes.Internal, "install crictl: %s", err) + } + installed = true + break + } + + if !installed { + return status.Errorf(codes.Internal, "crictl binary not found in archive %s", downloadURL) + } + + return nil +} + // containerdVersionMatch checks if the installed containerd version matches the expected version. func containerdVersionMatch(expectedVersion string) bool { for _, binary := range containerdBinaries { @@ -171,6 +211,20 @@ func runcVersionMatch(expectedVersion string) bool { return strings.Contains(string(output), expectedVersion) // FIXME: this is not a robust way } +// crictlVersionMatch checks if the installed crictl version matches the expected version. +func crictlVersionMatch(expectedVersion string) bool { + if !utilio.IsExecutable(crictlBinPath) { + return false + } + + output, err := utilexec.New().Command(crictlBinPath, "--version").Output() + if err != nil { + return false + } + + return strings.Contains(string(output), expectedVersion) // FIXME: this is not a robust way +} + // constructContainerdDownloadURL builds the download URL for the given containerd version. func constructContainerdDownloadURL(version string) string { arch := utilhost.GetArch() @@ -182,3 +236,9 @@ func constructRuncDownloadURL(version string) string { arch := utilhost.GetArch() return fmt.Sprintf(defaultRuncURLTemplate, version, arch) } + +// constructCrictlDownloadURL builds the download URL for the given crictl version. +func constructCrictlDownloadURL(version string) string { + arch := utilhost.GetArch() + return fmt.Sprintf(defaultCrictlURLTemplate, version, version, arch) +} diff --git a/go.mod b/go.mod index 73ad4cff..f0acdaf3 100644 --- a/go.mod +++ b/go.mod @@ -20,10 +20,12 @@ require ( github.com/spf13/viper v1.21.0 google.golang.org/grpc v1.72.3 google.golang.org/protobuf v1.36.8 + k8s.io/api v0.35.0 k8s.io/apimachinery v0.35.0 k8s.io/client-go v0.35.0 k8s.io/utils v0.0.0-20251002143259-bc988d571ff4 sigs.k8s.io/cluster-api v1.12.2 + sigs.k8s.io/yaml v1.6.0 ) require ( @@ -89,7 +91,6 @@ require ( google.golang.org/genproto/googleapis/rpc v0.0.0-20250303144028-a0af3efb3deb // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/api v0.35.0 // indirect k8s.io/apiextensions-apiserver v0.34.3 // indirect k8s.io/cluster-bootstrap v0.34.2 // indirect k8s.io/component-base v0.34.3 // indirect @@ -99,5 +100,4 @@ require ( sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect sigs.k8s.io/randfill v1.0.0 // indirect sigs.k8s.io/structured-merge-diff/v6 v6.3.0 // indirect - sigs.k8s.io/yaml v1.6.0 // indirect ) diff --git a/pkg/bootstrapper/components.go b/pkg/bootstrapper/components.go index 418af61e..6bd35c02 100644 --- a/pkg/bootstrapper/components.go +++ b/pkg/bootstrapper/components.go @@ -104,6 +104,10 @@ var downloadCRIBinaries resolveActionFunc[*cri.DownloadCRIBinaries] = func( cfg.Runc.Version, config.DefaultRunCVersion, ), + CrictlVersion: ptrWithDefault( + cfg.Crictl.Version, + config.DefaultCrictlVersion, + ), }.Build() return cri.DownloadCRIBinaries_builder{ diff --git a/pkg/config/defaults.go b/pkg/config/defaults.go index 798a228c..93529db4 100644 --- a/pkg/config/defaults.go +++ b/pkg/config/defaults.go @@ -17,6 +17,7 @@ const ( DefaultNPDVersion = "v1.35.1" DefaultRunCVersion = "1.1.12" DefaultContainerdVersion = "2.0.4" // FIXME: confirm if we still want containerd 1.x + DefaultCrictlVersion = "1.33.0" // FIXME: confirm correct version and download endpoint KubeletKubeconfigPath = "/var/lib/kubelet/kubeconfig" KubeletBootstrapKubeconfigPath = "/var/lib/kubelet/bootstrap-kubeconfig" diff --git a/pkg/config/structs.go b/pkg/config/structs.go index a17e1f8b..3a402c0d 100644 --- a/pkg/config/structs.go +++ b/pkg/config/structs.go @@ -11,6 +11,7 @@ type Config struct { Kubernetes KubernetesConfig `json:"kubernetes"` CNI CNIConfig `json:"cni"` Runc RuncConfig `json:"runc"` + Crictl CrictlConfig `json:"crictl"` Node NodeConfig `json:"node"` Paths PathsConfig `json:"paths"` Npd NPDConfig `json:"npd"` @@ -90,6 +91,12 @@ type RuncConfig struct { URL string `json:"url"` } +// CrictlConfig holds configuration settings for the crictl CLI tool. +type CrictlConfig struct { + Version string `json:"version"` + URL string `json:"url"` +} + // ContainerdConfig holds configuration settings for the containerd runtime. type ContainerdConfig struct { Version string `json:"version"`