diff --git a/pkg/generated/openapi/zz_generated.openapi.go b/pkg/generated/openapi/zz_generated.openapi.go index 5f81ede91b1..4c2341f554a 100644 --- a/pkg/generated/openapi/zz_generated.openapi.go +++ b/pkg/generated/openapi/zz_generated.openapi.go @@ -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{ diff --git a/pkg/kubelet/apis/config/scheme/testdata/ImagePullIntent/after/v1beta1.yaml b/pkg/kubelet/apis/config/scheme/testdata/ImagePullIntent/after/v1beta1.yaml new file mode 100644 index 00000000000..5621227a099 --- /dev/null +++ b/pkg/kubelet/apis/config/scheme/testdata/ImagePullIntent/after/v1beta1.yaml @@ -0,0 +1,3 @@ +apiVersion: kubelet.config.k8s.io/v1beta1 +image: "" +kind: ImagePullIntent diff --git a/pkg/kubelet/apis/config/scheme/testdata/ImagePullIntent/before/v1beta1.yaml b/pkg/kubelet/apis/config/scheme/testdata/ImagePullIntent/before/v1beta1.yaml new file mode 100644 index 00000000000..c2b5d8ce555 --- /dev/null +++ b/pkg/kubelet/apis/config/scheme/testdata/ImagePullIntent/before/v1beta1.yaml @@ -0,0 +1,2 @@ +kind: ImagePullIntent +apiVersion: kubelet.config.k8s.io/v1beta1 \ No newline at end of file diff --git a/pkg/kubelet/apis/config/scheme/testdata/ImagePullIntent/roundtrip/default/v1beta1.yaml b/pkg/kubelet/apis/config/scheme/testdata/ImagePullIntent/roundtrip/default/v1beta1.yaml new file mode 100644 index 00000000000..5621227a099 --- /dev/null +++ b/pkg/kubelet/apis/config/scheme/testdata/ImagePullIntent/roundtrip/default/v1beta1.yaml @@ -0,0 +1,3 @@ +apiVersion: kubelet.config.k8s.io/v1beta1 +image: "" +kind: ImagePullIntent diff --git a/pkg/kubelet/apis/config/scheme/testdata/ImagePulledRecord/after/v1beta1.yaml b/pkg/kubelet/apis/config/scheme/testdata/ImagePulledRecord/after/v1beta1.yaml new file mode 100644 index 00000000000..6e13c182686 --- /dev/null +++ b/pkg/kubelet/apis/config/scheme/testdata/ImagePulledRecord/after/v1beta1.yaml @@ -0,0 +1,4 @@ +apiVersion: kubelet.config.k8s.io/v1beta1 +imageRef: "" +kind: ImagePulledRecord +lastUpdatedTime: null diff --git a/pkg/kubelet/apis/config/scheme/testdata/ImagePulledRecord/before/v1beta1.yaml b/pkg/kubelet/apis/config/scheme/testdata/ImagePulledRecord/before/v1beta1.yaml new file mode 100644 index 00000000000..a51bb9f9b05 --- /dev/null +++ b/pkg/kubelet/apis/config/scheme/testdata/ImagePulledRecord/before/v1beta1.yaml @@ -0,0 +1,2 @@ +kind: ImagePulledRecord +apiVersion: kubelet.config.k8s.io/v1beta1 \ No newline at end of file diff --git a/pkg/kubelet/apis/config/scheme/testdata/ImagePulledRecord/roundtrip/default/v1beta1.yaml b/pkg/kubelet/apis/config/scheme/testdata/ImagePulledRecord/roundtrip/default/v1beta1.yaml new file mode 100644 index 00000000000..6e13c182686 --- /dev/null +++ b/pkg/kubelet/apis/config/scheme/testdata/ImagePulledRecord/roundtrip/default/v1beta1.yaml @@ -0,0 +1,4 @@ +apiVersion: kubelet.config.k8s.io/v1beta1 +imageRef: "" +kind: ImagePulledRecord +lastUpdatedTime: null diff --git a/pkg/kubelet/apis/config/types.go b/pkg/kubelet/apis/config/types.go index 1260dddc5b5..6ab19ac1c66 100644 --- a/pkg/kubelet/apis/config/types.go +++ b/pkg/kubelet/apis/config/types.go @@ -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 diff --git a/pkg/kubelet/apis/config/v1beta1/zz_generated.conversion.go b/pkg/kubelet/apis/config/v1beta1/zz_generated.conversion.go index 917d40a5e84..96c6d7df9c8 100644 --- a/pkg/kubelet/apis/config/v1beta1/zz_generated.conversion.go +++ b/pkg/kubelet/apis/config/v1beta1/zz_generated.conversion.go @@ -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 diff --git a/pkg/kubelet/images/pullmanager/fs_pullrecords.go b/pkg/kubelet/images/pullmanager/fs_pullrecords.go index de325723658..3e1a249430c 100644 --- a/pkg/kubelet/images/pullmanager/fs_pullrecords.go +++ b/pkg/kubelet/images/pullmanager/fs_pullrecords.go @@ -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 } diff --git a/pkg/kubelet/images/pullmanager/fs_pullrecords_test.go b/pkg/kubelet/images/pullmanager/fs_pullrecords_test.go new file mode 100644 index 00000000000..ac7cb387d8e --- /dev/null +++ b/pkg/kubelet/images/pullmanager/fs_pullrecords_test.go @@ -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) + } +} diff --git a/pkg/kubelet/images/pullmanager/image_pull_manager_test.go b/pkg/kubelet/images/pullmanager/image_pull_manager_test.go index 96821cf11c0..c61eb9f5530 100644 --- a/pkg/kubelet/images/pullmanager/image_pull_manager_test.go +++ b/pkg/kubelet/images/pullmanager/image_pull_manager_test.go @@ -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 { diff --git a/pkg/kubelet/images/pullmanager/metrics_test.go b/pkg/kubelet/images/pullmanager/metrics_test.go index e9fe86f7e2d..51593292e84 100644 --- a/pkg/kubelet/images/pullmanager/metrics_test.go +++ b/pkg/kubelet/images/pullmanager/metrics_test.go @@ -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", diff --git a/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd b/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd index e4c408f6aea..6d62dc3e911 100644 --- a/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd +++ b/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-38a8906435c4dd5f4258899d46621bfd8eea3ad6ff494ee3c2f17ef0321625bd @@ -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":""}]}}} \ No newline at end of file +{"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":""}]}}} \ No newline at end of file diff --git a/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-7045bdd498b0e4b3963d50f906793ffbd68dc98686ec46c5b499a0a640a560b2 b/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-7045bdd498b0e4b3963d50f906793ffbd68dc98686ec46c5b499a0a640a560b2 new file mode 100644 index 00000000000..873c75ae4f8 --- /dev/null +++ b/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-7045bdd498b0e4b3963d50f906793ffbd68dc98686ec46c5b499a0a640a560b2 @@ -0,0 +1 @@ +{"kind":"ImagePulledRecord","apiVersion":"kubelet.config.k8s.io/v1alpha1","lastUpdatedTime":"2024-10-21T12:26:40Z","imageRef":"testemptycredmapping-alpha"} diff --git a/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-a2eace2182b24cdbbb730798e47b10709b9ef5e0f0c1624a3bc06c8ca987727a b/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-a2eace2182b24cdbbb730798e47b10709b9ef5e0f0c1624a3bc06c8ca987727a index 658ad7f6d18..ae286ee45e1 100644 --- a/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-a2eace2182b24cdbbb730798e47b10709b9ef5e0f0c1624a3bc06c8ca987727a +++ b/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-a2eace2182b24cdbbb730798e47b10709b9ef5e0f0c1624a3bc06c8ca987727a @@ -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}}} \ No newline at end of file +{"kind":"ImagePulledRecord","apiVersion":"kubelet.config.k8s.io/v1beta1","lastUpdatedTime":"2024-10-21T12:26:40Z","imageRef":"testimage-anonpull","credentialMapping":{"docker.io/testing/test":{"nodePodsAccessible":true}}} \ No newline at end of file diff --git a/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-b3c0cc4278800b03a308ceb2611161430df571ca733122f0a40ac8b9792a9064 b/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-b3c0cc4278800b03a308ceb2611161430df571ca733122f0a40ac8b9792a9064 index c80e23d50c5..58ca592e3be 100644 --- a/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-b3c0cc4278800b03a308ceb2611161430df571ca733122f0a40ac8b9792a9064 +++ b/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-b3c0cc4278800b03a308ceb2611161430df571ca733122f0a40ac8b9792a9064 @@ -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"}]}}} \ No newline at end of file +{"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"}]}}} \ No newline at end of file diff --git a/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-dd1dd8dc6a329b7e1c2e43951302b23a047cb20fd3e10090e4ce7e8396035405 b/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-dd1dd8dc6a329b7e1c2e43951302b23a047cb20fd3e10090e4ce7e8396035405 new file mode 100644 index 00000000000..877e8679b92 --- /dev/null +++ b/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-dd1dd8dc6a329b7e1c2e43951302b23a047cb20fd3e10090e4ce7e8396035405 @@ -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}}} diff --git a/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-f8778b6393eaf39315e767a58cbeacf2c4b270d94b4d6926ee993d9e49444991 b/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-f8778b6393eaf39315e767a58cbeacf2c4b270d94b4d6926ee993d9e49444991 index b79d714f851..e819735a4d0 100644 --- a/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-f8778b6393eaf39315e767a58cbeacf2c4b270d94b4d6926ee993d9e49444991 +++ b/pkg/kubelet/images/pullmanager/testdata/pulled/sha256-f8778b6393eaf39315e767a58cbeacf2c4b270d94b4d6926ee993d9e49444991 @@ -1 +1 @@ -{"kind":"ImagePulledRecord","apiVersion":"kubelet.config.k8s.io/v1alpha1","lastUpdatedTime":"2024-10-21T12:26:40Z","imageRef":"testemptycredmapping"} \ No newline at end of file +{"kind":"ImagePulledRecord","apiVersion":"kubelet.config.k8s.io/v1beta1","lastUpdatedTime":"2024-10-21T12:26:40Z","imageRef":"testemptycredmapping"} \ No newline at end of file diff --git a/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-6da3dced7eccc7ad0189517c31ec2235b50f730449d738d91525721f7e027fd4 b/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-6da3dced7eccc7ad0189517c31ec2235b50f730449d738d91525721f7e027fd4 new file mode 100644 index 00000000000..ed9847818e0 --- /dev/null +++ b/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-6da3dced7eccc7ad0189517c31ec2235b50f730449d738d91525721f7e027fd4 @@ -0,0 +1 @@ +{"kind":"ImagePullIntent","apiVersion":"kubelet.config.k8s.io/v1alpha1","image":"docker.io/testing/test-alpha:latest"} diff --git a/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-aef2af226629a35d5f3ef0fdbb29fdbebf038d0acd8850590e8c48e1e283aa56 b/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-aef2af226629a35d5f3ef0fdbb29fdbebf038d0acd8850590e8c48e1e283aa56 index 8096c438df0..5360a8f9ef5 100644 --- a/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-aef2af226629a35d5f3ef0fdbb29fdbebf038d0acd8850590e8c48e1e283aa56 +++ b/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-aef2af226629a35d5f3ef0fdbb29fdbebf038d0acd8850590e8c48e1e283aa56 @@ -1 +1 @@ -{"kind":"ImagePullIntent","apiVersion":"kubelet.config.k8s.io/v1alpha1","image":"docker.io/testing/test:latest"} \ No newline at end of file +{"kind":"ImagePullIntent","apiVersion":"kubelet.config.k8s.io/v1beta1","image":"docker.io/testing/test:latest"} \ No newline at end of file diff --git a/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-b6e1482bb6cb030924530b918e609fff30bbb07ab6845451d89deb823c3f72a9 b/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-b6e1482bb6cb030924530b918e609fff30bbb07ab6845451d89deb823c3f72a9 new file mode 100644 index 00000000000..aad62c5955e --- /dev/null +++ b/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-b6e1482bb6cb030924530b918e609fff30bbb07ab6845451d89deb823c3f72a9 @@ -0,0 +1 @@ +{"kind":"ImagePullIntent","apiVersion":"kubelet.config.k8s.io/v1alpha1","image":"repo.repo/test/alphatest:v1"} diff --git a/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-ee81caca15454863449fb55a1d942904d56d5ed9f9b20a7cb3453944ea2c7e11 b/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-ee81caca15454863449fb55a1d942904d56d5ed9f9b20a7cb3453944ea2c7e11 index 5f5989df822..c4b4ce49ef4 100644 --- a/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-ee81caca15454863449fb55a1d942904d56d5ed9f9b20a7cb3453944ea2c7e11 +++ b/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-ee81caca15454863449fb55a1d942904d56d5ed9f9b20a7cb3453944ea2c7e11 @@ -1 +1 @@ -{"kind":"ImagePullIntent","apiVersion":"kubelet.config.k8s.io/v1alpha1","image":"repo.repo/test/test:v1"} \ No newline at end of file +{"kind":"ImagePullIntent","apiVersion":"kubelet.config.k8s.io/v1beta1","image":"repo.repo/test/test:v1"} \ No newline at end of file diff --git a/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-f24acc752be18b93b0504c86312bbaf482c9efb0c45e925bbccb0a591cebd7af b/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-f24acc752be18b93b0504c86312bbaf482c9efb0c45e925bbccb0a591cebd7af index 46a6ccde690..5efd2abd0d8 100644 --- a/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-f24acc752be18b93b0504c86312bbaf482c9efb0c45e925bbccb0a591cebd7af +++ b/pkg/kubelet/images/pullmanager/testdata/pulling/sha256-f24acc752be18b93b0504c86312bbaf482c9efb0c45e925bbccb0a591cebd7af @@ -1 +1 @@ -{"kind":"ImagePullIntent","apiVersion":"kubelet.config.k8s.io/v1alpha1","image":"docker.io/testing/test:something"} \ No newline at end of file +{"kind":"ImagePullIntent","apiVersion":"kubelet.config.k8s.io/v1beta1","image":"docker.io/testing/test:something"} \ No newline at end of file diff --git a/staging/src/k8s.io/kubelet/config/v1alpha1/types.go b/staging/src/k8s.io/kubelet/config/v1alpha1/types.go index d999e116e5d..a965958472d 100644 --- a/staging/src/k8s.io/kubelet/config/v1alpha1/types.go +++ b/staging/src/k8s.io/kubelet/config/v1alpha1/types.go @@ -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. diff --git a/staging/src/k8s.io/kubelet/config/v1beta1/register.go b/staging/src/k8s.io/kubelet/config/v1beta1/register.go index 22bdfbb4cba..c76a568e131 100644 --- a/staging/src/k8s.io/kubelet/config/v1beta1/register.go +++ b/staging/src/k8s.io/kubelet/config/v1beta1/register.go @@ -40,6 +40,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { &KubeletConfiguration{}, &SerializedNodeConfigSource{}, &CredentialProviderConfig{}, + &ImagePullIntent{}, + &ImagePulledRecord{}, ) return nil } diff --git a/staging/src/k8s.io/kubelet/config/v1beta1/types.go b/staging/src/k8s.io/kubelet/config/v1beta1/types.go index 1794d8eb537..a7c4e21829c 100644 --- a/staging/src/k8s.io/kubelet/config/v1beta1/types.go +++ b/staging/src/k8s.io/kubelet/config/v1beta1/types.go @@ -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"` +} diff --git a/staging/src/k8s.io/kubelet/config/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/kubelet/config/v1beta1/zz_generated.deepcopy.go index ac0ba7096bc..6a908018187 100644 --- a/staging/src/k8s.io/kubelet/config/v1beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/kubelet/config/v1beta1/zz_generated.deepcopy.go @@ -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 diff --git a/staging/src/k8s.io/kubelet/config/v1beta1/zz_generated.model_name.go b/staging/src/k8s.io/kubelet/config/v1beta1/zz_generated.model_name.go index 2ad7f1bc816..140a9ab201f 100644 --- a/staging/src/k8s.io/kubelet/config/v1beta1/zz_generated.model_name.go +++ b/staging/src/k8s.io/kubelet/config/v1beta1/zz_generated.model_name.go @@ -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"