Merge pull request #132579 from stlaz/ensure-secret-images-beta

KEP-2535: move objects to beta, add storage version migration to filesystem cache
This commit is contained in:
Kubernetes Prow Robot
2025-11-10 18:18:54 -08:00
committed by GitHub
29 changed files with 963 additions and 37 deletions

View File

@@ -1471,6 +1471,11 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
kubeletconfigv1beta1.CredentialProvider{}.OpenAPIModelName(): schema_k8sio_kubelet_config_v1beta1_CredentialProvider(ref),
kubeletconfigv1beta1.CredentialProviderConfig{}.OpenAPIModelName(): schema_k8sio_kubelet_config_v1beta1_CredentialProviderConfig(ref),
kubeletconfigv1beta1.ExecEnvVar{}.OpenAPIModelName(): schema_k8sio_kubelet_config_v1beta1_ExecEnvVar(ref),
kubeletconfigv1beta1.ImagePullCredentials{}.OpenAPIModelName(): schema_k8sio_kubelet_config_v1beta1_ImagePullCredentials(ref),
kubeletconfigv1beta1.ImagePullIntent{}.OpenAPIModelName(): schema_k8sio_kubelet_config_v1beta1_ImagePullIntent(ref),
kubeletconfigv1beta1.ImagePullSecret{}.OpenAPIModelName(): schema_k8sio_kubelet_config_v1beta1_ImagePullSecret(ref),
kubeletconfigv1beta1.ImagePullServiceAccount{}.OpenAPIModelName(): schema_k8sio_kubelet_config_v1beta1_ImagePullServiceAccount(ref),
kubeletconfigv1beta1.ImagePulledRecord{}.OpenAPIModelName(): schema_k8sio_kubelet_config_v1beta1_ImagePulledRecord(ref),
kubeletconfigv1beta1.KubeletAnonymousAuthentication{}.OpenAPIModelName(): schema_k8sio_kubelet_config_v1beta1_KubeletAnonymousAuthentication(ref),
kubeletconfigv1beta1.KubeletAuthentication{}.OpenAPIModelName(): schema_k8sio_kubelet_config_v1beta1_KubeletAuthentication(ref),
kubeletconfigv1beta1.KubeletAuthorization{}.OpenAPIModelName(): schema_k8sio_kubelet_config_v1beta1_KubeletAuthorization(ref),
@@ -71414,6 +71419,239 @@ func schema_k8sio_kubelet_config_v1beta1_ExecEnvVar(ref common.ReferenceCallback
}
}
func schema_k8sio_kubelet_config_v1beta1_ImagePullCredentials(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "ImagePullCredentials describe credentials that can be used to pull an image.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kubernetesSecrets": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "set",
},
},
SchemaProps: spec.SchemaProps{
Description: "KubernetesSecretCoordinates is an index of coordinates of all the kubernetes secrets that were used to pull the image.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref(kubeletconfigv1beta1.ImagePullSecret{}.OpenAPIModelName()),
},
},
},
},
},
"kubernetesServiceAccounts": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "set",
},
},
SchemaProps: spec.SchemaProps{
Description: "KubernetesServiceAccounts is an index of coordinates of all the kubernetes service accounts that were used to pull the image.",
Type: []string{"array"},
Items: &spec.SchemaOrArray{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref(kubeletconfigv1beta1.ImagePullServiceAccount{}.OpenAPIModelName()),
},
},
},
},
},
"nodePodsAccessible": {
SchemaProps: spec.SchemaProps{
Description: "NodePodsAccessible is a flag denoting the pull credentials are accessible by all the pods on the node, or that no credentials are needed for the pull.\n\nIf true, it is mutually exclusive with the `kubernetesSecrets` field.",
Type: []string{"boolean"},
Format: "",
},
},
},
},
},
Dependencies: []string{
kubeletconfigv1beta1.ImagePullSecret{}.OpenAPIModelName(), kubeletconfigv1beta1.ImagePullServiceAccount{}.OpenAPIModelName()},
}
}
func schema_k8sio_kubelet_config_v1beta1_ImagePullIntent(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "ImagePullIntent is a record of the kubelet attempting to pull an image.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
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{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
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{"string"},
Format: "",
},
},
"image": {
SchemaProps: spec.SchemaProps{
Description: "Image is the image spec from a Container's `image` field. The filename is a SHA-256 hash of this value. This is to avoid filename-unsafe characters like ':' and '/'.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"image"},
},
},
}
}
func schema_k8sio_kubelet_config_v1beta1_ImagePullSecret(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "ImagePullSecret is a representation of a Kubernetes secret object coordinates along with a credential hash of the pull secret credentials this object contains.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"uid": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"namespace": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"name": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"credentialHash": {
SchemaProps: spec.SchemaProps{
Description: "CredentialHash is a SHA-256 retrieved by hashing the image pull credentials content of the secret specified by the UID/Namespace/Name coordinates.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"uid", "namespace", "name", "credentialHash"},
},
},
}
}
func schema_k8sio_kubelet_config_v1beta1_ImagePullServiceAccount(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "ImagePullServiceAccount is a representation of a Kubernetes service account object coordinates for which the kubelet sent service account token to the credential provider plugin for image pull credentials.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"uid": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"namespace": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
"name": {
SchemaProps: spec.SchemaProps{
Default: "",
Type: []string{"string"},
Format: "",
},
},
},
Required: []string{"uid", "namespace", "name"},
},
},
}
}
func schema_k8sio_kubelet_config_v1beta1_ImagePulledRecord(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{
SchemaProps: spec.SchemaProps{
Description: "ImagePullRecord is a record of an image that was pulled by the kubelet.\n\nIf there are no records in the `kubernetesSecrets` field and both `nodeWideCredentials` and `anonymous` are `false`, credentials must be re-checked the next time an image represented by this record is being requested.",
Type: []string{"object"},
Properties: map[string]spec.Schema{
"kind": {
SchemaProps: spec.SchemaProps{
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{"string"},
Format: "",
},
},
"apiVersion": {
SchemaProps: spec.SchemaProps{
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{"string"},
Format: "",
},
},
"lastUpdatedTime": {
SchemaProps: spec.SchemaProps{
Description: "LastUpdatedTime is the time of the last update to this record",
Ref: ref(metav1.Time{}.OpenAPIModelName()),
},
},
"imageRef": {
SchemaProps: spec.SchemaProps{
Description: "ImageRef is a reference to the image represented by this file as received from the CRI. The filename is a SHA-256 hash of this value. This is to avoid filename-unsafe characters like ':' and '/'.",
Default: "",
Type: []string{"string"},
Format: "",
},
},
"credentialMapping": {
SchemaProps: spec.SchemaProps{
Description: "CredentialMapping maps `image` to the set of credentials that it was previously pulled with. `image` in this case is the content of a pod's container `image` field that's got its tag/digest removed.\n\nExample:\n Container requests the `hello-world:latest@sha256:91fb4b041da273d5a3273b6d587d62d518300a6ad268b28628f74997b93171b2` image:\n \"credentialMapping\": {\n \"hello-world\": { \"nodePodsAccessible\": true }\n }",
Type: []string{"object"},
AdditionalProperties: &spec.SchemaOrBool{
Allows: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: map[string]interface{}{},
Ref: ref(kubeletconfigv1beta1.ImagePullCredentials{}.OpenAPIModelName()),
},
},
},
},
},
},
Required: []string{"lastUpdatedTime", "imageRef"},
},
},
Dependencies: []string{
metav1.Time{}.OpenAPIModelName(), kubeletconfigv1beta1.ImagePullCredentials{}.OpenAPIModelName()},
}
}
func schema_k8sio_kubelet_config_v1beta1_KubeletAnonymousAuthentication(ref common.ReferenceCallback) common.OpenAPIDefinition {
return common.OpenAPIDefinition{
Schema: spec.Schema{

View File

@@ -0,0 +1,3 @@
apiVersion: kubelet.config.k8s.io/v1beta1
image: ""
kind: ImagePullIntent

View File

@@ -0,0 +1,2 @@
kind: ImagePullIntent
apiVersion: kubelet.config.k8s.io/v1beta1

View File

@@ -0,0 +1,3 @@
apiVersion: kubelet.config.k8s.io/v1beta1
image: ""
kind: ImagePullIntent

View File

@@ -0,0 +1,4 @@
apiVersion: kubelet.config.k8s.io/v1beta1
imageRef: ""
kind: ImagePulledRecord
lastUpdatedTime: null

View File

@@ -0,0 +1,2 @@
kind: ImagePulledRecord
apiVersion: kubelet.config.k8s.io/v1beta1

View File

@@ -0,0 +1,4 @@
apiVersion: kubelet.config.k8s.io/v1beta1
imageRef: ""
kind: ImagePulledRecord
lastUpdatedTime: null

View File

@@ -887,7 +887,7 @@ type ImagePulledRecord struct {
// ImagePullCredentials describe credentials that can be used to pull an image.
type ImagePullCredentials struct {
// KuberneteSecretCoordinates is an index of coordinates of all the kubernetes
// KubernetesSecretCoordinates is an index of coordinates of all the kubernetes
// secrets that were used to pull the image.
// +optional
KubernetesSecrets []ImagePullSecret

View File

@@ -75,6 +75,56 @@ func RegisterConversions(s *runtime.Scheme) error {
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*configv1beta1.ImagePullCredentials)(nil), (*config.ImagePullCredentials)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta1_ImagePullCredentials_To_config_ImagePullCredentials(a.(*configv1beta1.ImagePullCredentials), b.(*config.ImagePullCredentials), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.ImagePullCredentials)(nil), (*configv1beta1.ImagePullCredentials)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_ImagePullCredentials_To_v1beta1_ImagePullCredentials(a.(*config.ImagePullCredentials), b.(*configv1beta1.ImagePullCredentials), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*configv1beta1.ImagePullIntent)(nil), (*config.ImagePullIntent)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta1_ImagePullIntent_To_config_ImagePullIntent(a.(*configv1beta1.ImagePullIntent), b.(*config.ImagePullIntent), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.ImagePullIntent)(nil), (*configv1beta1.ImagePullIntent)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_ImagePullIntent_To_v1beta1_ImagePullIntent(a.(*config.ImagePullIntent), b.(*configv1beta1.ImagePullIntent), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*configv1beta1.ImagePullSecret)(nil), (*config.ImagePullSecret)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta1_ImagePullSecret_To_config_ImagePullSecret(a.(*configv1beta1.ImagePullSecret), b.(*config.ImagePullSecret), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.ImagePullSecret)(nil), (*configv1beta1.ImagePullSecret)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_ImagePullSecret_To_v1beta1_ImagePullSecret(a.(*config.ImagePullSecret), b.(*configv1beta1.ImagePullSecret), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*configv1beta1.ImagePullServiceAccount)(nil), (*config.ImagePullServiceAccount)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta1_ImagePullServiceAccount_To_config_ImagePullServiceAccount(a.(*configv1beta1.ImagePullServiceAccount), b.(*config.ImagePullServiceAccount), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.ImagePullServiceAccount)(nil), (*configv1beta1.ImagePullServiceAccount)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_ImagePullServiceAccount_To_v1beta1_ImagePullServiceAccount(a.(*config.ImagePullServiceAccount), b.(*configv1beta1.ImagePullServiceAccount), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*configv1beta1.ImagePulledRecord)(nil), (*config.ImagePulledRecord)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta1_ImagePulledRecord_To_config_ImagePulledRecord(a.(*configv1beta1.ImagePulledRecord), b.(*config.ImagePulledRecord), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*config.ImagePulledRecord)(nil), (*configv1beta1.ImagePulledRecord)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_config_ImagePulledRecord_To_v1beta1_ImagePulledRecord(a.(*config.ImagePulledRecord), b.(*configv1beta1.ImagePulledRecord), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*configv1beta1.KubeletAnonymousAuthentication)(nil), (*config.KubeletAnonymousAuthentication)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1beta1_KubeletAnonymousAuthentication_To_config_KubeletAnonymousAuthentication(a.(*configv1beta1.KubeletAnonymousAuthentication), b.(*config.KubeletAnonymousAuthentication), scope)
}); err != nil {
@@ -311,6 +361,124 @@ func Convert_config_ExecEnvVar_To_v1beta1_ExecEnvVar(in *config.ExecEnvVar, out
return autoConvert_config_ExecEnvVar_To_v1beta1_ExecEnvVar(in, out, s)
}
func autoConvert_v1beta1_ImagePullCredentials_To_config_ImagePullCredentials(in *configv1beta1.ImagePullCredentials, out *config.ImagePullCredentials, s conversion.Scope) error {
out.KubernetesSecrets = *(*[]config.ImagePullSecret)(unsafe.Pointer(&in.KubernetesSecrets))
out.KubernetesServiceAccounts = *(*[]config.ImagePullServiceAccount)(unsafe.Pointer(&in.KubernetesServiceAccounts))
out.NodePodsAccessible = in.NodePodsAccessible
return nil
}
// Convert_v1beta1_ImagePullCredentials_To_config_ImagePullCredentials is an autogenerated conversion function.
func Convert_v1beta1_ImagePullCredentials_To_config_ImagePullCredentials(in *configv1beta1.ImagePullCredentials, out *config.ImagePullCredentials, s conversion.Scope) error {
return autoConvert_v1beta1_ImagePullCredentials_To_config_ImagePullCredentials(in, out, s)
}
func autoConvert_config_ImagePullCredentials_To_v1beta1_ImagePullCredentials(in *config.ImagePullCredentials, out *configv1beta1.ImagePullCredentials, s conversion.Scope) error {
out.KubernetesSecrets = *(*[]configv1beta1.ImagePullSecret)(unsafe.Pointer(&in.KubernetesSecrets))
out.KubernetesServiceAccounts = *(*[]configv1beta1.ImagePullServiceAccount)(unsafe.Pointer(&in.KubernetesServiceAccounts))
out.NodePodsAccessible = in.NodePodsAccessible
return nil
}
// Convert_config_ImagePullCredentials_To_v1beta1_ImagePullCredentials is an autogenerated conversion function.
func Convert_config_ImagePullCredentials_To_v1beta1_ImagePullCredentials(in *config.ImagePullCredentials, out *configv1beta1.ImagePullCredentials, s conversion.Scope) error {
return autoConvert_config_ImagePullCredentials_To_v1beta1_ImagePullCredentials(in, out, s)
}
func autoConvert_v1beta1_ImagePullIntent_To_config_ImagePullIntent(in *configv1beta1.ImagePullIntent, out *config.ImagePullIntent, s conversion.Scope) error {
out.Image = in.Image
return nil
}
// Convert_v1beta1_ImagePullIntent_To_config_ImagePullIntent is an autogenerated conversion function.
func Convert_v1beta1_ImagePullIntent_To_config_ImagePullIntent(in *configv1beta1.ImagePullIntent, out *config.ImagePullIntent, s conversion.Scope) error {
return autoConvert_v1beta1_ImagePullIntent_To_config_ImagePullIntent(in, out, s)
}
func autoConvert_config_ImagePullIntent_To_v1beta1_ImagePullIntent(in *config.ImagePullIntent, out *configv1beta1.ImagePullIntent, s conversion.Scope) error {
out.Image = in.Image
return nil
}
// Convert_config_ImagePullIntent_To_v1beta1_ImagePullIntent is an autogenerated conversion function.
func Convert_config_ImagePullIntent_To_v1beta1_ImagePullIntent(in *config.ImagePullIntent, out *configv1beta1.ImagePullIntent, s conversion.Scope) error {
return autoConvert_config_ImagePullIntent_To_v1beta1_ImagePullIntent(in, out, s)
}
func autoConvert_v1beta1_ImagePullSecret_To_config_ImagePullSecret(in *configv1beta1.ImagePullSecret, out *config.ImagePullSecret, s conversion.Scope) error {
out.UID = in.UID
out.Namespace = in.Namespace
out.Name = in.Name
out.CredentialHash = in.CredentialHash
return nil
}
// Convert_v1beta1_ImagePullSecret_To_config_ImagePullSecret is an autogenerated conversion function.
func Convert_v1beta1_ImagePullSecret_To_config_ImagePullSecret(in *configv1beta1.ImagePullSecret, out *config.ImagePullSecret, s conversion.Scope) error {
return autoConvert_v1beta1_ImagePullSecret_To_config_ImagePullSecret(in, out, s)
}
func autoConvert_config_ImagePullSecret_To_v1beta1_ImagePullSecret(in *config.ImagePullSecret, out *configv1beta1.ImagePullSecret, s conversion.Scope) error {
out.UID = in.UID
out.Namespace = in.Namespace
out.Name = in.Name
out.CredentialHash = in.CredentialHash
return nil
}
// Convert_config_ImagePullSecret_To_v1beta1_ImagePullSecret is an autogenerated conversion function.
func Convert_config_ImagePullSecret_To_v1beta1_ImagePullSecret(in *config.ImagePullSecret, out *configv1beta1.ImagePullSecret, s conversion.Scope) error {
return autoConvert_config_ImagePullSecret_To_v1beta1_ImagePullSecret(in, out, s)
}
func autoConvert_v1beta1_ImagePullServiceAccount_To_config_ImagePullServiceAccount(in *configv1beta1.ImagePullServiceAccount, out *config.ImagePullServiceAccount, s conversion.Scope) error {
out.UID = in.UID
out.Namespace = in.Namespace
out.Name = in.Name
return nil
}
// Convert_v1beta1_ImagePullServiceAccount_To_config_ImagePullServiceAccount is an autogenerated conversion function.
func Convert_v1beta1_ImagePullServiceAccount_To_config_ImagePullServiceAccount(in *configv1beta1.ImagePullServiceAccount, out *config.ImagePullServiceAccount, s conversion.Scope) error {
return autoConvert_v1beta1_ImagePullServiceAccount_To_config_ImagePullServiceAccount(in, out, s)
}
func autoConvert_config_ImagePullServiceAccount_To_v1beta1_ImagePullServiceAccount(in *config.ImagePullServiceAccount, out *configv1beta1.ImagePullServiceAccount, s conversion.Scope) error {
out.UID = in.UID
out.Namespace = in.Namespace
out.Name = in.Name
return nil
}
// Convert_config_ImagePullServiceAccount_To_v1beta1_ImagePullServiceAccount is an autogenerated conversion function.
func Convert_config_ImagePullServiceAccount_To_v1beta1_ImagePullServiceAccount(in *config.ImagePullServiceAccount, out *configv1beta1.ImagePullServiceAccount, s conversion.Scope) error {
return autoConvert_config_ImagePullServiceAccount_To_v1beta1_ImagePullServiceAccount(in, out, s)
}
func autoConvert_v1beta1_ImagePulledRecord_To_config_ImagePulledRecord(in *configv1beta1.ImagePulledRecord, out *config.ImagePulledRecord, s conversion.Scope) error {
out.LastUpdatedTime = in.LastUpdatedTime
out.ImageRef = in.ImageRef
out.CredentialMapping = *(*map[string]config.ImagePullCredentials)(unsafe.Pointer(&in.CredentialMapping))
return nil
}
// Convert_v1beta1_ImagePulledRecord_To_config_ImagePulledRecord is an autogenerated conversion function.
func Convert_v1beta1_ImagePulledRecord_To_config_ImagePulledRecord(in *configv1beta1.ImagePulledRecord, out *config.ImagePulledRecord, s conversion.Scope) error {
return autoConvert_v1beta1_ImagePulledRecord_To_config_ImagePulledRecord(in, out, s)
}
func autoConvert_config_ImagePulledRecord_To_v1beta1_ImagePulledRecord(in *config.ImagePulledRecord, out *configv1beta1.ImagePulledRecord, s conversion.Scope) error {
out.LastUpdatedTime = in.LastUpdatedTime
out.ImageRef = in.ImageRef
out.CredentialMapping = *(*map[string]configv1beta1.ImagePullCredentials)(unsafe.Pointer(&in.CredentialMapping))
return nil
}
// Convert_config_ImagePulledRecord_To_v1beta1_ImagePulledRecord is an autogenerated conversion function.
func Convert_config_ImagePulledRecord_To_v1beta1_ImagePulledRecord(in *config.ImagePulledRecord, out *configv1beta1.ImagePulledRecord, s conversion.Scope) error {
return autoConvert_config_ImagePulledRecord_To_v1beta1_ImagePulledRecord(in, out, s)
}
func autoConvert_v1beta1_KubeletAnonymousAuthentication_To_config_KubeletAnonymousAuthentication(in *configv1beta1.KubeletAnonymousAuthentication, out *config.KubeletAnonymousAuthentication, s conversion.Scope) error {
if err := v1.Convert_Pointer_bool_To_bool(&in.Enabled, &out.Enabled, s); err != nil {
return err

View File

@@ -30,9 +30,11 @@ import (
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/serializer"
utilerrors "k8s.io/apimachinery/pkg/util/errors"
kubeletconfigv1alpha1 "k8s.io/kubelet/config/v1alpha1"
"k8s.io/klog/v2"
kubeletconfigv1beta1 "k8s.io/kubelet/config/v1beta1"
kubeletconfiginternal "k8s.io/kubernetes/pkg/kubelet/apis/config"
kubeletconfigvint1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/config/v1alpha1"
kubeletconfigint1alpha1 "k8s.io/kubernetes/pkg/kubelet/apis/config/v1alpha1"
kubeletconfigint1beta1 "k8s.io/kubernetes/pkg/kubelet/apis/config/v1beta1"
)
const (
@@ -40,6 +42,8 @@ const (
tmpFilesSuffix = ".tmp"
)
var encodeVersion = kubeletconfigv1beta1.SchemeGroupVersion
var _ PullRecordsAccessor = &fsPullRecordsAccessor{}
// fsPullRecordsAccessor uses the filesystem to read/write ImagePullIntent/ImagePulledRecord
@@ -76,9 +80,46 @@ func NewFSPullRecordsAccessor(kubeletDir string) (PullRecordsAccessor, error) {
return nil, err
}
accessor.recordsVersionMigration()
return NewMeteringRecordsAccessor(accessor, fsPullIntentsSize, fsPulledRecordsSize), nil
}
func (f *fsPullRecordsAccessor) recordsVersionMigration() {
err := processDirFiles(f.pullingDir,
func(filePath string, fileContent []byte) error {
intent, isCurrentVersion, err := decodeIntent(f.decoder, fileContent)
if err != nil {
return fmt.Errorf("failed to decode ImagePullIntent from file %q: %w", filePath, err)
}
if !isCurrentVersion {
if err := f.WriteImagePullIntent(intent.Image); err != nil {
return fmt.Errorf("failed to migrate ImagePullIntent for image %q to the current version: %w", intent.Image, err)
}
}
return nil
})
if err != nil {
klog.ErrorS(err, "Error migrating image pull intents")
}
err = processDirFiles(f.pulledDir,
func(filePath string, fileContent []byte) error {
pullRecord, isCurrentVersion, err := decodePulledRecord(f.decoder, fileContent)
if err != nil {
return fmt.Errorf("failed to decode ImagePulledRecord from file %q: %w", filePath, err)
}
if !isCurrentVersion {
if err := f.WriteImagePulledRecord(pullRecord); err != nil {
return fmt.Errorf("failed to migrate ImagePulledRecord for image ref %q to the current version: %w", pullRecord.ImageRef, err)
}
}
return nil
})
if err != nil {
klog.ErrorS(err, "Error migrating image pulled records")
}
}
func (f *fsPullRecordsAccessor) WriteImagePullIntent(image string) error {
intent := kubeletconfiginternal.ImagePullIntent{
Image: image,
@@ -97,7 +138,7 @@ func (f *fsPullRecordsAccessor) ListImagePullIntents() ([]*kubeletconfiginternal
// walk the pulling directory for any pull intent records
err := processDirFiles(f.pullingDir,
func(filePath string, fileContent []byte) error {
intent, err := decodeIntent(f.decoder, fileContent)
intent, _, err := decodeIntent(f.decoder, fileContent)
if err != nil {
return fmt.Errorf("failed to deserialize content of file %q into ImagePullIntent: %w", filePath, err)
}
@@ -117,7 +158,7 @@ func (f *fsPullRecordsAccessor) ImagePullIntentExists(image string) (bool, error
return false, err
}
intent, err := decodeIntent(f.decoder, intentBytes)
intent, _, err := decodeIntent(f.decoder, intentBytes)
if err != nil {
return false, err
}
@@ -141,7 +182,7 @@ func (f *fsPullRecordsAccessor) GetImagePulledRecord(imageRef string) (*kubeletc
return nil, false, err
}
pulledRecord, err := decodePulledRecord(f.decoder, recordBytes)
pulledRecord, _, err := decodePulledRecord(f.decoder, recordBytes)
if err != nil {
return nil, true, err
}
@@ -155,7 +196,7 @@ func (f *fsPullRecordsAccessor) ListImagePulledRecords() ([]*kubeletconfigintern
var pullRecords []*kubeletconfiginternal.ImagePulledRecord
err := processDirFiles(f.pulledDir,
func(filePath string, fileContent []byte) error {
pullRecord, err := decodePulledRecord(f.decoder, fileContent)
pullRecord, _, err := decodePulledRecord(f.decoder, fileContent)
if err != nil {
return fmt.Errorf("failed to deserialize content of file %q into ImagePulledRecord: %w", filePath, err)
}
@@ -311,10 +352,21 @@ func processDirFiles(dirName string, fileAction func(filePath string, fileConten
// createKubeletCOnfigSchemeEncoderDecoder creates strict-encoding encoder and
// decoder for the internal and alpha kubelet config APIs.
func createKubeletConfigSchemeEncoderDecoder() (runtime.Encoder, runtime.Decoder, error) {
codecs, info, err := getKubeletConfigSerializerInfo()
if err != nil {
return nil, nil, err
}
return codecs.EncoderForVersion(info.Serializer, encodeVersion), codecs.UniversalDecoder(), nil
}
func getKubeletConfigSerializerInfo() (*serializer.CodecFactory, *runtime.SerializerInfo, error) {
const mediaType = runtime.ContentTypeJSON
scheme := runtime.NewScheme()
if err := kubeletconfigvint1alpha1.AddToScheme(scheme); err != nil {
if err := kubeletconfigint1beta1.AddToScheme(scheme); err != nil {
return nil, nil, err
}
if err := kubeletconfigint1alpha1.AddToScheme(scheme); err != nil {
return nil, nil, err
}
if err := kubeletconfiginternal.AddToScheme(scheme); err != nil {
@@ -328,33 +380,45 @@ func createKubeletConfigSchemeEncoderDecoder() (runtime.Encoder, runtime.Decoder
if !ok {
return nil, nil, fmt.Errorf("unable to locate encoder -- %q is not a supported media type", mediaType)
}
return codecs.EncoderForVersion(info.Serializer, kubeletconfigv1alpha1.SchemeGroupVersion), codecs.UniversalDecoder(), nil
return &codecs, &info, nil
}
func decodeIntent(d runtime.Decoder, objBytes []byte) (*kubeletconfiginternal.ImagePullIntent, error) {
obj, _, err := d.Decode(objBytes, nil, nil)
// decodeIntent decodes the image pull intent and converts it to the internal API version.
// The decoder must be aware of the schemas available for the kubelet config API.
//
// The returned object is an internal representation of the image pull intent and a bool
// that indicates whether the image pull intent appears in the latest known version of the kubelet config API.
func decodeIntent(d runtime.Decoder, objBytes []byte) (*kubeletconfiginternal.ImagePullIntent, bool, error) {
obj, gvk, err := d.Decode(objBytes, nil, nil)
if err != nil {
return nil, err
return nil, false, err
}
intentObj, ok := obj.(*kubeletconfiginternal.ImagePullIntent)
if !ok {
return nil, fmt.Errorf("failed to convert object to *ImagePullIntent: %T", obj)
return nil, false, fmt.Errorf("failed to convert object to *ImagePullIntent: %T", obj)
}
return intentObj, nil
isLatestVersion := gvk.Version == encodeVersion.Version
return intentObj, isLatestVersion, nil
}
func decodePulledRecord(d runtime.Decoder, objBytes []byte) (*kubeletconfiginternal.ImagePulledRecord, error) {
obj, _, err := d.Decode(objBytes, nil, nil)
// decodePulledRecord decodes the pulled record and converts it to the internal API version.
// The decoder must be aware of the schemas available for the kubelet config API.
//
// The returned object is an internal representation of the pulled record and a bool
// that indicates whether the record appears in the latest known version of the kubelet config API.
func decodePulledRecord(d runtime.Decoder, objBytes []byte) (*kubeletconfiginternal.ImagePulledRecord, bool, error) {
obj, gvk, err := d.Decode(objBytes, nil, nil)
if err != nil {
return nil, err
return nil, false, err
}
pulledRecord, ok := obj.(*kubeletconfiginternal.ImagePulledRecord)
if !ok {
return nil, fmt.Errorf("failed to convert object to *ImagePulledRecord: %T", obj)
return nil, false, fmt.Errorf("failed to convert object to *ImagePulledRecord: %T", obj)
}
return pulledRecord, nil
isLatestVersion := gvk.Version == encodeVersion.Version
return pulledRecord, isLatestVersion, nil
}

View File

@@ -0,0 +1,201 @@
/*
Copyright 2025 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package pullmanager
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
)
func TestNewFSPullRecordsAccessor(t *testing.T) {
tests := []struct {
name string
initRoot bool
initIntents []string
initPulled []string
modIntent func(string) string
modPulledRecord func(string) string
wantErr bool // TODO: do I ever want an error?
}{
{
name: "kubelet root dir does not exist",
initRoot: false,
},
{
name: "kubelet root dir exists",
initRoot: true,
},
{
name: "only alpha intents",
initRoot: true,
initIntents: []string{
"sha256-6da3dced7eccc7ad0189517c31ec2235b50f730449d738d91525721f7e027fd4",
"sha256-b6e1482bb6cb030924530b918e609fff30bbb07ab6845451d89deb823c3f72a9",
},
modIntent: func(intent string) string {
return strings.Replace(intent, "kubelet.config.k8s.io/v1alpha1", "kubelet.config.k8s.io/v1beta1", 1)
},
},
{
name: "alpha and beta intents mixed",
initRoot: true,
initIntents: []string{
"sha256-aef2af226629a35d5f3ef0fdbb29fdbebf038d0acd8850590e8c48e1e283aa56",
"sha256-6da3dced7eccc7ad0189517c31ec2235b50f730449d738d91525721f7e027fd4",
"sha256-ee81caca15454863449fb55a1d942904d56d5ed9f9b20a7cb3453944ea2c7e11",
"sha256-b6e1482bb6cb030924530b918e609fff30bbb07ab6845451d89deb823c3f72a9",
},
modIntent: func(intent string) string {
return strings.Replace(intent, "kubelet.config.k8s.io/v1alpha1", "kubelet.config.k8s.io/v1beta1", 1)
},
},
{
name: "only beta intents",
initRoot: true,
initIntents: []string{
"sha256-aef2af226629a35d5f3ef0fdbb29fdbebf038d0acd8850590e8c48e1e283aa56",
"sha256-ee81caca15454863449fb55a1d942904d56d5ed9f9b20a7cb3453944ea2c7e11",
"sha256-f24acc752be18b93b0504c86312bbaf482c9efb0c45e925bbccb0a591cebd7af",
},
},
{
name: "only alpha pulled records",
initRoot: true,
initPulled: []string{
"sha256-7045bdd498b0e4b3963d50f906793ffbd68dc98686ec46c5b499a0a640a560b2",
"sha256-dd1dd8dc6a329b7e1c2e43951302b23a047cb20fd3e10090e4ce7e8396035405",
},
modPulledRecord: func(intent string) string {
return strings.Replace(intent, "kubelet.config.k8s.io/v1alpha1", "kubelet.config.k8s.io/v1beta1", 1)
},
},
{
name: "alpha and beta pulled records mixed",
initRoot: true,
initPulled: []string{
"sha256-7045bdd498b0e4b3963d50f906793ffbd68dc98686ec46c5b499a0a640a560b2",
"sha256-38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd",
"sha256-dd1dd8dc6a329b7e1c2e43951302b23a047cb20fd3e10090e4ce7e8396035405",
"sha256-a2eace2182b24cdbbb730798e47b10709b9ef5e0f0c1624a3bc06c8ca987727a",
"sha256-f8778b6393eaf39315e767a58cbeacf2c4b270d94b4d6926ee993d9e49444991",
},
modPulledRecord: func(intent string) string {
return strings.Replace(intent, "kubelet.config.k8s.io/v1alpha1", "kubelet.config.k8s.io/v1beta1", 1)
},
},
{
name: "only beta pulled records",
initRoot: true,
initPulled: []string{
"sha256-38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd",
"sha256-a2eace2182b24cdbbb730798e47b10709b9ef5e0f0c1624a3bc06c8ca987727a",
"sha256-f8778b6393eaf39315e767a58cbeacf2c4b270d94b4d6926ee993d9e49444991",
},
},
{
name: "everything altogether",
initRoot: true,
initIntents: []string{
"sha256-aef2af226629a35d5f3ef0fdbb29fdbebf038d0acd8850590e8c48e1e283aa56",
"sha256-6da3dced7eccc7ad0189517c31ec2235b50f730449d738d91525721f7e027fd4",
"sha256-ee81caca15454863449fb55a1d942904d56d5ed9f9b20a7cb3453944ea2c7e11",
"sha256-b6e1482bb6cb030924530b918e609fff30bbb07ab6845451d89deb823c3f72a9",
},
modIntent: func(intent string) string {
return strings.Replace(intent, "kubelet.config.k8s.io/v1alpha1", "kubelet.config.k8s.io/v1beta1", 1)
},
initPulled: []string{
"sha256-7045bdd498b0e4b3963d50f906793ffbd68dc98686ec46c5b499a0a640a560b2",
"sha256-38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd",
"sha256-dd1dd8dc6a329b7e1c2e43951302b23a047cb20fd3e10090e4ce7e8396035405",
"sha256-a2eace2182b24cdbbb730798e47b10709b9ef5e0f0c1624a3bc06c8ca987727a",
"sha256-f8778b6393eaf39315e767a58cbeacf2c4b270d94b4d6926ee993d9e49444991",
},
modPulledRecord: func(intent string) string {
return strings.Replace(intent, "kubelet.config.k8s.io/v1alpha1", "kubelet.config.k8s.io/v1beta1", 1)
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
kubeletDir := t.TempDir()
if !tt.initRoot {
kubeletDir = filepath.Join(kubeletDir, "does-not-exist", "level2dir")
}
intentsDir := filepath.Join(kubeletDir, "image_manager", "pulling")
pulledRecordsDir := filepath.Join(kubeletDir, "image_manager", "pulled")
expectedIntents := teeTestData(t, intentsDir, "pulling", tt.initIntents)
if tt.modIntent != nil {
for i := range expectedIntents {
expectedIntents[i] = tt.modIntent(expectedIntents[i])
}
}
pulledRecordBytes := teeTestData(t, pulledRecordsDir, "pulled", tt.initPulled)
if tt.modPulledRecord != nil {
for i := range pulledRecordBytes {
pulledRecordBytes[i] = tt.modPulledRecord(pulledRecordBytes[i])
}
}
_, err := NewFSPullRecordsAccessor(kubeletDir)
if (err != nil) != tt.wantErr {
t.Errorf("NewFSPullRecordsAccessor() error = %v, wantErr %v", err, tt.wantErr)
return
}
for _, dir := range []string{
"image_manager",
filepath.Join("image_manager", "pulling"),
filepath.Join("image_manager", "pulled"),
} {
if info, err := os.Stat(filepath.Join(kubeletDir, dir)); err != nil {
t.Errorf("error encountered accessing %q: %v", dir, err)
} else if !info.IsDir() {
t.Errorf("%q is not a directory", dir)
}
}
for intent, expectedContent := range expectedIntents {
filePath := filepath.Join(intentsDir, intent)
testFileContentMatch(t, filePath, expectedContent)
}
for pulled, expectedContent := range pulledRecordBytes {
filePath := filepath.Join(pulledRecordsDir, pulled)
testFileContentMatch(t, filePath, expectedContent)
}
})
}
}
func testFileContentMatch(t *testing.T, filePath string, expectedContent string) {
t.Helper()
fileContent, err := os.ReadFile(filePath)
if err != nil {
t.Fatalf("failed to read file %q: %v", filePath, err)
}
if diff := cmp.Diff(expectedContent, string(fileContent)); diff != "" {
t.Errorf("file contents of %q do not match the expectation: %s", filePath, diff)
}
}

View File

@@ -758,8 +758,8 @@ func TestFileBasedImagePullManager_MustAttemptImagePull(t *testing.T) {
pullingDir := filepath.Join(testDir, "pulling")
pulledDir := filepath.Join(testDir, "pulled")
copyTestData(t, pullingDir, "pulling", tt.pullingFiles)
copyTestData(t, pulledDir, "pulled", tt.pulledFiles)
_ = teeTestData(t, pullingDir, "pulling", tt.pullingFiles)
_ = teeTestData(t, pulledDir, "pulled", tt.pulledFiles)
fsRecordAccessor := &testWriteCountingFSPullRecordsAccessor{
fsPullRecordsAccessor: fsPullRecordsAccessor{
@@ -1157,8 +1157,8 @@ func TestFileBasedImagePullManager_RecordImagePulled(t *testing.T) {
pullingDir := filepath.Join(testDir, "pulling")
pulledDir := filepath.Join(testDir, "pulled")
copyTestData(t, pullingDir, "pulling", tt.existingPulling)
copyTestData(t, pulledDir, "pulled", tt.existingPulled)
_ = teeTestData(t, pullingDir, "pulling", tt.existingPulling)
_ = teeTestData(t, pulledDir, "pulled", tt.existingPulled)
fsRecordAccessor := &fsPullRecordsAccessor{
pullingDir: pullingDir,
@@ -1193,7 +1193,7 @@ func TestFileBasedImagePullManager_RecordImagePulled(t *testing.T) {
t.Fatalf("failed to read the expected image pulled record: %v", err)
}
got, err := decodePulledRecord(decoder, pulledBytes)
got, _, err := decodePulledRecord(decoder, pulledBytes)
if err != nil {
t.Fatalf("failed to deserialize the image pulled record: %v", err)
}
@@ -1332,8 +1332,8 @@ func TestFileBasedImagePullManager_initialize(t *testing.T) {
t.Fatal(err)
}
copyTestData(t, pullingDir, "pulling", tt.existingIntents)
copyTestData(t, pulledDir, "pulled", tt.existingPulledRecords)
_ = teeTestData(t, pullingDir, "pulling", tt.existingIntents)
_ = teeTestData(t, pulledDir, "pulled", tt.existingPulledRecords)
fsRecordAccessor := &fsPullRecordsAccessor{
pullingDir: pullingDir,
@@ -1449,7 +1449,7 @@ func TestFileBasedImagePullManager_PruneUnknownRecords(t *testing.T) {
t.Fatalf("failed to create testing dir %q: %v", pulledDir, err)
}
copyTestData(t, pulledDir, "pulled", tt.pulledFiles)
_ = teeTestData(t, pulledDir, "pulled", tt.pulledFiles)
fsRecordAccessor := &fsPullRecordsAccessor{
pulledDir: pulledDir,
@@ -1487,7 +1487,9 @@ func TestFileBasedImagePullManager_PruneUnknownRecords(t *testing.T) {
}
}
func copyTestData(t *testing.T, dstDir string, testdataDir string, src []string) {
func teeTestData(t *testing.T, dstDir string, testdataDir string, src []string) map[string]string {
t.Helper()
ret := make(map[string]string, len(src))
for _, f := range src {
testBytes, err := os.ReadFile(filepath.Join("testdata", testdataDir, f))
if err != nil {
@@ -1496,7 +1498,9 @@ func copyTestData(t *testing.T, dstDir string, testdataDir string, src []string)
if err := writeFile(dstDir, f, testBytes); err != nil {
t.Fatalf("failed to write test data: %v", err)
}
ret[f] = string(testBytes)
}
return ret
}
func withImageRecord(r *kubeletconfiginternal.ImagePulledRecord, image string, record kubeletconfiginternal.ImagePullCredentials) *kubeletconfiginternal.ImagePulledRecord {

View File

@@ -193,7 +193,7 @@ func TestMustAttemptPullMetrics(t *testing.T) {
)
require.NoError(t, err)
copyTestData(t, pulledDir, "pulled", []string{
teeTestData(t, pulledDir, "pulled", []string{
"sha256-e766e4624f9bc4d3847d6c5b470d9d5362cc8d0c250bd9572daf95278e044263",
"sha256-a2eace2182b24cdbbb730798e47b10709b9ef5e0f0c1624a3bc06c8ca987727a",
"sha256-f4058727984875eb66ddbf289f7013096d4cbaa167e591fbcb2e447e36391c1f",

View File

@@ -1 +1 @@
{"kind":"ImagePulledRecord","apiVersion":"kubelet.config.k8s.io/v1alpha1","lastUpdatedTime":"2024-10-21T12:26:40Z","imageRef":"test-brokenhash","credentialMapping":{"docker.io/testing/test":{"kubernetesSecrets":[{"uid":"testsecretuid","namespace":"default","name":"pull-secret","credentialHash":""}]}}}
{"kind":"ImagePulledRecord","apiVersion":"kubelet.config.k8s.io/v1beta1","lastUpdatedTime":"2024-10-21T12:26:40Z","imageRef":"test-brokenhash","credentialMapping":{"docker.io/testing/test":{"kubernetesSecrets":[{"uid":"testsecretuid","namespace":"default","name":"pull-secret","credentialHash":""}]}}}

View File

@@ -0,0 +1 @@
{"kind":"ImagePulledRecord","apiVersion":"kubelet.config.k8s.io/v1alpha1","lastUpdatedTime":"2024-10-21T12:26:40Z","imageRef":"testemptycredmapping-alpha"}

View File

@@ -1 +1 @@
{"kind":"ImagePulledRecord","apiVersion":"kubelet.config.k8s.io/v1alpha1","lastUpdatedTime":"2024-10-21T12:26:40Z","imageRef":"testimage-anonpull","credentialMapping":{"docker.io/testing/test":{"nodePodsAccessible":true}}}
{"kind":"ImagePulledRecord","apiVersion":"kubelet.config.k8s.io/v1beta1","lastUpdatedTime":"2024-10-21T12:26:40Z","imageRef":"testimage-anonpull","credentialMapping":{"docker.io/testing/test":{"nodePodsAccessible":true}}}

View File

@@ -1 +1 @@
{"kind":"ImagePulledRecord","apiVersion":"kubelet.config.k8s.io/v1alpha1","lastUpdatedTime":"2024-10-21T12:26:40Z","imageRef":"testimageref","credentialMapping":{"docker.io/testing/test":{"kubernetesSecrets":[{"uid":"testsecretuid","namespace":"default","name":"pull-secret","credentialHash":"testsecrethash"}]}}}
{"kind":"ImagePulledRecord","apiVersion":"kubelet.config.k8s.io/v1beta1","lastUpdatedTime":"2024-10-21T12:26:40Z","imageRef":"testimageref","credentialMapping":{"docker.io/testing/test":{"kubernetesSecrets":[{"uid":"testsecretuid","namespace":"default","name":"pull-secret","credentialHash":"testsecrethash"}]}}}

View File

@@ -0,0 +1 @@
{"kind":"ImagePulledRecord","apiVersion":"kubelet.config.k8s.io/v1alpha1","lastUpdatedTime":"2025-06-21T12:26:40Z","imageRef":"testimage-anonpull-alpha","credentialMapping":{"docker.io/testing/test":{"nodePodsAccessible":true}}}

View File

@@ -1 +1 @@
{"kind":"ImagePulledRecord","apiVersion":"kubelet.config.k8s.io/v1alpha1","lastUpdatedTime":"2024-10-21T12:26:40Z","imageRef":"testemptycredmapping"}
{"kind":"ImagePulledRecord","apiVersion":"kubelet.config.k8s.io/v1beta1","lastUpdatedTime":"2024-10-21T12:26:40Z","imageRef":"testemptycredmapping"}

View File

@@ -0,0 +1 @@
{"kind":"ImagePullIntent","apiVersion":"kubelet.config.k8s.io/v1alpha1","image":"docker.io/testing/test-alpha:latest"}

View File

@@ -1 +1 @@
{"kind":"ImagePullIntent","apiVersion":"kubelet.config.k8s.io/v1alpha1","image":"docker.io/testing/test:latest"}
{"kind":"ImagePullIntent","apiVersion":"kubelet.config.k8s.io/v1beta1","image":"docker.io/testing/test:latest"}

View File

@@ -0,0 +1 @@
{"kind":"ImagePullIntent","apiVersion":"kubelet.config.k8s.io/v1alpha1","image":"repo.repo/test/alphatest:v1"}

View File

@@ -1 +1 @@
{"kind":"ImagePullIntent","apiVersion":"kubelet.config.k8s.io/v1alpha1","image":"repo.repo/test/test:v1"}
{"kind":"ImagePullIntent","apiVersion":"kubelet.config.k8s.io/v1beta1","image":"repo.repo/test/test:v1"}

View File

@@ -1 +1 @@
{"kind":"ImagePullIntent","apiVersion":"kubelet.config.k8s.io/v1alpha1","image":"docker.io/testing/test:something"}
{"kind":"ImagePullIntent","apiVersion":"kubelet.config.k8s.io/v1beta1","image":"docker.io/testing/test:something"}

View File

@@ -147,7 +147,7 @@ type ImagePullCredentials struct {
// secrets that were used to pull the image.
// +optional
// +listType=set
KubernetesSecrets []ImagePullSecret `json:"kubernetesSecrets"`
KubernetesSecrets []ImagePullSecret `json:"kubernetesSecrets,omitempty"`
// KubernetesServiceAccounts is an index of coordinates of all the kubernetes
// service accounts that were used to pull the image.

View File

@@ -40,6 +40,8 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&KubeletConfiguration{},
&SerializedNodeConfigSource{},
&CredentialProviderConfig{},
&ImagePullIntent{},
&ImagePulledRecord{},
)
return nil
}

View File

@@ -1135,3 +1135,89 @@ type UserNamespaces struct {
// +optional
IDsPerPod *int64 `json:"idsPerPod,omitempty"`
}
// ImagePullIntent is a record of the kubelet attempting to pull an image.
//
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ImagePullIntent struct {
metav1.TypeMeta `json:",inline"`
// Image is the image spec from a Container's `image` field.
// The filename is a SHA-256 hash of this value. This is to avoid filename-unsafe
// characters like ':' and '/'.
Image string `json:"image"`
}
// ImagePullRecord is a record of an image that was pulled by the kubelet.
//
// If there are no records in the `kubernetesSecrets` field and both `nodeWideCredentials`
// and `anonymous` are `false`, credentials must be re-checked the next time an
// image represented by this record is being requested.
//
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type ImagePulledRecord struct {
metav1.TypeMeta `json:",inline"`
// LastUpdatedTime is the time of the last update to this record
LastUpdatedTime metav1.Time `json:"lastUpdatedTime"`
// ImageRef is a reference to the image represented by this file as received
// from the CRI.
// The filename is a SHA-256 hash of this value. This is to avoid filename-unsafe
// characters like ':' and '/'.
ImageRef string `json:"imageRef"`
// CredentialMapping maps `image` to the set of credentials that it was
// previously pulled with.
// `image` in this case is the content of a pod's container `image` field that's
// got its tag/digest removed.
//
// Example:
// Container requests the `hello-world:latest@sha256:91fb4b041da273d5a3273b6d587d62d518300a6ad268b28628f74997b93171b2` image:
// "credentialMapping": {
// "hello-world": { "nodePodsAccessible": true }
// }
CredentialMapping map[string]ImagePullCredentials `json:"credentialMapping,omitempty"`
}
// ImagePullCredentials describe credentials that can be used to pull an image.
type ImagePullCredentials struct {
// KubernetesSecretCoordinates is an index of coordinates of all the kubernetes
// secrets that were used to pull the image.
// +optional
// +listType=set
KubernetesSecrets []ImagePullSecret `json:"kubernetesSecrets,omitempty"`
// KubernetesServiceAccounts is an index of coordinates of all the kubernetes
// service accounts that were used to pull the image.
// +optional
// +listType=set
KubernetesServiceAccounts []ImagePullServiceAccount `json:"kubernetesServiceAccounts,omitempty"`
// NodePodsAccessible is a flag denoting the pull credentials are accessible
// by all the pods on the node, or that no credentials are needed for the pull.
//
// If true, it is mutually exclusive with the `kubernetesSecrets` field.
// +optional
NodePodsAccessible bool `json:"nodePodsAccessible,omitempty"`
}
// ImagePullSecret is a representation of a Kubernetes secret object coordinates along
// with a credential hash of the pull secret credentials this object contains.
type ImagePullSecret struct {
UID string `json:"uid"`
Namespace string `json:"namespace"`
Name string `json:"name"`
// CredentialHash is a SHA-256 retrieved by hashing the image pull credentials
// content of the secret specified by the UID/Namespace/Name coordinates.
CredentialHash string `json:"credentialHash"`
}
// ImagePullServiceAccount is a representation of a Kubernetes service account object coordinates
// for which the kubelet sent service account token to the credential provider plugin for image pull credentials.
type ImagePullServiceAccount struct {
UID string `json:"uid"`
Namespace string `json:"namespace"`
Name string `json:"name"`
}

View File

@@ -133,6 +133,122 @@ func (in *ExecEnvVar) DeepCopy() *ExecEnvVar {
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ImagePullCredentials) DeepCopyInto(out *ImagePullCredentials) {
*out = *in
if in.KubernetesSecrets != nil {
in, out := &in.KubernetesSecrets, &out.KubernetesSecrets
*out = make([]ImagePullSecret, len(*in))
copy(*out, *in)
}
if in.KubernetesServiceAccounts != nil {
in, out := &in.KubernetesServiceAccounts, &out.KubernetesServiceAccounts
*out = make([]ImagePullServiceAccount, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImagePullCredentials.
func (in *ImagePullCredentials) DeepCopy() *ImagePullCredentials {
if in == nil {
return nil
}
out := new(ImagePullCredentials)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ImagePullIntent) DeepCopyInto(out *ImagePullIntent) {
*out = *in
out.TypeMeta = in.TypeMeta
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImagePullIntent.
func (in *ImagePullIntent) DeepCopy() *ImagePullIntent {
if in == nil {
return nil
}
out := new(ImagePullIntent)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ImagePullIntent) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ImagePullSecret) DeepCopyInto(out *ImagePullSecret) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImagePullSecret.
func (in *ImagePullSecret) DeepCopy() *ImagePullSecret {
if in == nil {
return nil
}
out := new(ImagePullSecret)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ImagePullServiceAccount) DeepCopyInto(out *ImagePullServiceAccount) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImagePullServiceAccount.
func (in *ImagePullServiceAccount) DeepCopy() *ImagePullServiceAccount {
if in == nil {
return nil
}
out := new(ImagePullServiceAccount)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ImagePulledRecord) DeepCopyInto(out *ImagePulledRecord) {
*out = *in
out.TypeMeta = in.TypeMeta
in.LastUpdatedTime.DeepCopyInto(&out.LastUpdatedTime)
if in.CredentialMapping != nil {
in, out := &in.CredentialMapping, &out.CredentialMapping
*out = make(map[string]ImagePullCredentials, len(*in))
for key, val := range *in {
(*out)[key] = *val.DeepCopy()
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ImagePulledRecord.
func (in *ImagePulledRecord) DeepCopy() *ImagePulledRecord {
if in == nil {
return nil
}
out := new(ImagePulledRecord)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ImagePulledRecord) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *KubeletAnonymousAuthentication) DeepCopyInto(out *KubeletAnonymousAuthentication) {
*out = *in

View File

@@ -41,6 +41,31 @@ func (in ExecEnvVar) OpenAPIModelName() string {
return "io.k8s.kubelet.config.v1beta1.ExecEnvVar"
}
// OpenAPIModelName returns the OpenAPI model name for this type.
func (in ImagePullCredentials) OpenAPIModelName() string {
return "io.k8s.kubelet.config.v1beta1.ImagePullCredentials"
}
// OpenAPIModelName returns the OpenAPI model name for this type.
func (in ImagePullIntent) OpenAPIModelName() string {
return "io.k8s.kubelet.config.v1beta1.ImagePullIntent"
}
// OpenAPIModelName returns the OpenAPI model name for this type.
func (in ImagePullSecret) OpenAPIModelName() string {
return "io.k8s.kubelet.config.v1beta1.ImagePullSecret"
}
// OpenAPIModelName returns the OpenAPI model name for this type.
func (in ImagePullServiceAccount) OpenAPIModelName() string {
return "io.k8s.kubelet.config.v1beta1.ImagePullServiceAccount"
}
// OpenAPIModelName returns the OpenAPI model name for this type.
func (in ImagePulledRecord) OpenAPIModelName() string {
return "io.k8s.kubelet.config.v1beta1.ImagePulledRecord"
}
// OpenAPIModelName returns the OpenAPI model name for this type.
func (in KubeletAnonymousAuthentication) OpenAPIModelName() string {
return "io.k8s.kubelet.config.v1beta1.KubeletAnonymousAuthentication"