Merge pull request #123372 from carlory/fix-kubeadm-494

kubeadm certs check-expiration support json/yaml output
This commit is contained in:
Kubernetes Prow Robot 2024-02-22 01:07:52 -08:00 committed by GitHub
commit ec58e1f2fc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 1185 additions and 85 deletions

View File

@ -39,6 +39,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
&BootstrapToken{},
&Images{},
&UpgradePlan{},
&CertificateExpirationInfo{},
)
return nil
}

View File

@ -25,6 +25,7 @@ import (
"k8s.io/kubernetes/cmd/kubeadm/app/apis/output"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha2"
"k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha3"
)
// Scheme is the runtime.Scheme to which all kubeadm api types are registered.
@ -42,5 +43,6 @@ func init() {
func AddToScheme(scheme *runtime.Scheme) {
utilruntime.Must(output.AddToScheme(scheme))
utilruntime.Must(v1alpha2.AddToScheme(scheme))
utilruntime.Must(scheme.SetVersionPriority(v1alpha2.SchemeGroupVersion))
utilruntime.Must(v1alpha3.AddToScheme(scheme))
utilruntime.Must(scheme.SetVersionPriority(v1alpha3.SchemeGroupVersion, v1alpha2.SchemeGroupVersion))
}

View File

@ -82,3 +82,38 @@ type UpgradePlan struct {
ConfigVersions []ComponentConfigVersionState
}
// Certificate represents information for a certificate or a certificate authority when using the check-expiration command.
type Certificate struct {
// Name of the certificate.
Name string
// ExpirationDate defines certificate expiration date in UTC following the RFC3339 format.
ExpirationDate metav1.Time
// ResidualTimeSeconds represents the duration in seconds relative to the residual time before expiration.
ResidualTimeSeconds int64
// ExternallyManaged defines if the certificate is externally managed.
ExternallyManaged bool
// CAName represents the name of the CA that signed the certificate.
// This field is empty for self-signed, root CA certificates.
CAName string
// Missing represents if the certificate is missing.
Missing bool
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// CertificateExpirationInfo represents information for the output produced by 'kubeadm certs check-expiration'.
type CertificateExpirationInfo struct {
metav1.TypeMeta
// Certificates holds a list of certificates to show expiration information for.
Certificates []Certificate
// CertificateAuthorities holds a list of certificate authorities to show expiration information for.
CertificateAuthorities []Certificate
}

View File

@ -23,4 +23,5 @@ limitations under the License.
// The purpose of the kubeadm structured output is to have a well
// defined versioned output format that other software that uses
// kubeadm for cluster deployments can use and rely on.
// DEPRECATED: this API will be removed in a future release. Please use v1alpha3.
package v1alpha2 // import "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha2"

View File

@ -0,0 +1,30 @@
/*
Copyright 2024 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.
*/
// +groupName=output.kubeadm.k8s.io
// +k8s:deepcopy-gen=package
// +k8s:conversion-gen=k8s.io/kubernetes/cmd/kubeadm/app/apis/output
// Package v1alpha3 defines the v1alpha3 version of the kubeadm data structures
// related to structured output
// The purpose of the kubeadm structured output is to have a well
// defined versioned output format that other software that uses
// kubeadm for cluster deployments can use and rely on.
//
// Changes since v1alpha2:
// - Added support for outputting certificate expiration information for "kubeadm certs check-expiration"
// with the CertificateExpirationInfo structure.
package v1alpha3 // import "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha3"

View File

@ -0,0 +1,55 @@
/*
Copyright 2024 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 v1alpha3
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupName is the group name use in this package
const GroupName = "output.kubeadm.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha3"}
var (
// SchemeBuilder points to a list of functions added to Scheme.
SchemeBuilder runtime.SchemeBuilder
localSchemeBuilder = &SchemeBuilder
// AddToScheme applies all the stored functions to the scheme.
AddToScheme = localSchemeBuilder.AddToScheme
)
func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(addKnownTypes)
}
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&BootstrapToken{},
&Images{},
&UpgradePlan{},
&CertificateExpirationInfo{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}

View File

@ -0,0 +1,119 @@
/*
Copyright 2024 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 v1alpha3
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
bootstraptokenv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/bootstraptoken/v1"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// BootstrapToken represents information for the bootstrap token output produced by kubeadm
type BootstrapToken struct {
metav1.TypeMeta `json:",inline"`
bootstraptokenv1.BootstrapToken
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Images represents information for the output produced by 'kubeadm config images list'
type Images struct {
metav1.TypeMeta `json:",inline"`
Images []string `json:"images"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// ComponentUpgradePlan represents information about upgrade plan for one component
type ComponentUpgradePlan struct {
metav1.TypeMeta
Name string `json:"name"`
CurrentVersion string `json:"currentVersion"`
NewVersion string `json:"newVersion"`
}
// ComponentConfigVersionState describes the current and desired version of a component config
type ComponentConfigVersionState struct {
// Group points to the Kubernetes API group that covers the config
Group string `json:"group"`
// CurrentVersion is the currently active component config version
// NOTE: This can be empty in case the config was not found on the cluster or it was unsupported
// kubeadm generated version
CurrentVersion string `json:"currentVersion"`
// PreferredVersion is the component config version that is currently preferred by kubeadm for use.
// NOTE: As of today, this is the only version supported by kubeadm.
PreferredVersion string `json:"preferredVersion"`
// ManualUpgradeRequired indicates if users need to manually upgrade their component config versions. This happens if
// the CurrentVersion of the config is user supplied (or modified) and no longer supported. Users should upgrade
// their component configs to PreferredVersion or any other supported component config version.
ManualUpgradeRequired bool `json:"manualUpgradeRequired"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// UpgradePlan represents information about upgrade plan for the output
// produced by 'kubeadm upgrade plan'
type UpgradePlan struct {
metav1.TypeMeta
Components []ComponentUpgradePlan `json:"components"`
ConfigVersions []ComponentConfigVersionState `json:"configVersions"`
}
// Certificate represents information for a certificate or a certificate authority when using the check-expiration command.
type Certificate struct {
// Name of the certificate.
Name string `json:"name"`
// ExpirationDate defines certificate expiration date in UTC following the RFC3339 format.
ExpirationDate metav1.Time `json:"expirationDate"`
// ResidualTimeSeconds represents the duration in seconds relative to the residual time before expiration.
ResidualTimeSeconds int64 `json:"residualTime"`
// ExternallyManaged defines if the certificate is externally managed.
ExternallyManaged bool `json:"externallyManaged"`
// CAName represents the name of the CA that signed the certificate.
// This field is empty for self-signed, root CA certificates.
CAName string `json:"caName,omitempty"`
// Missing represents if the certificate is missing.
Missing bool `json:"missing"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// CertificateExpirationInfo represents information for the output produced by 'kubeadm certs check-expiration'.
type CertificateExpirationInfo struct {
metav1.TypeMeta
// Certificates holds a list of certificates to show expiration information for.
Certificates []Certificate `json:"certificates"`
// CertificateAuthorities holds a list of certificate authorities to show expiration information for.
CertificateAuthorities []Certificate `json:"certificateAuthorities"`
}

View File

@ -0,0 +1,274 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by conversion-gen. DO NOT EDIT.
package v1alpha3
import (
unsafe "unsafe"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
output "k8s.io/kubernetes/cmd/kubeadm/app/apis/output"
)
func init() {
localSchemeBuilder.Register(RegisterConversions)
}
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
if err := s.AddGeneratedConversionFunc((*BootstrapToken)(nil), (*output.BootstrapToken)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha3_BootstrapToken_To_output_BootstrapToken(a.(*BootstrapToken), b.(*output.BootstrapToken), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*output.BootstrapToken)(nil), (*BootstrapToken)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_output_BootstrapToken_To_v1alpha3_BootstrapToken(a.(*output.BootstrapToken), b.(*BootstrapToken), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*Certificate)(nil), (*output.Certificate)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha3_Certificate_To_output_Certificate(a.(*Certificate), b.(*output.Certificate), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*output.Certificate)(nil), (*Certificate)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_output_Certificate_To_v1alpha3_Certificate(a.(*output.Certificate), b.(*Certificate), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*CertificateExpirationInfo)(nil), (*output.CertificateExpirationInfo)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha3_CertificateExpirationInfo_To_output_CertificateExpirationInfo(a.(*CertificateExpirationInfo), b.(*output.CertificateExpirationInfo), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*output.CertificateExpirationInfo)(nil), (*CertificateExpirationInfo)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_output_CertificateExpirationInfo_To_v1alpha3_CertificateExpirationInfo(a.(*output.CertificateExpirationInfo), b.(*CertificateExpirationInfo), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*ComponentConfigVersionState)(nil), (*output.ComponentConfigVersionState)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha3_ComponentConfigVersionState_To_output_ComponentConfigVersionState(a.(*ComponentConfigVersionState), b.(*output.ComponentConfigVersionState), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*output.ComponentConfigVersionState)(nil), (*ComponentConfigVersionState)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_output_ComponentConfigVersionState_To_v1alpha3_ComponentConfigVersionState(a.(*output.ComponentConfigVersionState), b.(*ComponentConfigVersionState), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*ComponentUpgradePlan)(nil), (*output.ComponentUpgradePlan)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha3_ComponentUpgradePlan_To_output_ComponentUpgradePlan(a.(*ComponentUpgradePlan), b.(*output.ComponentUpgradePlan), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*output.ComponentUpgradePlan)(nil), (*ComponentUpgradePlan)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_output_ComponentUpgradePlan_To_v1alpha3_ComponentUpgradePlan(a.(*output.ComponentUpgradePlan), b.(*ComponentUpgradePlan), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*Images)(nil), (*output.Images)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha3_Images_To_output_Images(a.(*Images), b.(*output.Images), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*output.Images)(nil), (*Images)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_output_Images_To_v1alpha3_Images(a.(*output.Images), b.(*Images), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*UpgradePlan)(nil), (*output.UpgradePlan)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1alpha3_UpgradePlan_To_output_UpgradePlan(a.(*UpgradePlan), b.(*output.UpgradePlan), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*output.UpgradePlan)(nil), (*UpgradePlan)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_output_UpgradePlan_To_v1alpha3_UpgradePlan(a.(*output.UpgradePlan), b.(*UpgradePlan), scope)
}); err != nil {
return err
}
return nil
}
func autoConvert_v1alpha3_BootstrapToken_To_output_BootstrapToken(in *BootstrapToken, out *output.BootstrapToken, s conversion.Scope) error {
out.BootstrapToken = in.BootstrapToken
return nil
}
// Convert_v1alpha3_BootstrapToken_To_output_BootstrapToken is an autogenerated conversion function.
func Convert_v1alpha3_BootstrapToken_To_output_BootstrapToken(in *BootstrapToken, out *output.BootstrapToken, s conversion.Scope) error {
return autoConvert_v1alpha3_BootstrapToken_To_output_BootstrapToken(in, out, s)
}
func autoConvert_output_BootstrapToken_To_v1alpha3_BootstrapToken(in *output.BootstrapToken, out *BootstrapToken, s conversion.Scope) error {
out.BootstrapToken = in.BootstrapToken
return nil
}
// Convert_output_BootstrapToken_To_v1alpha3_BootstrapToken is an autogenerated conversion function.
func Convert_output_BootstrapToken_To_v1alpha3_BootstrapToken(in *output.BootstrapToken, out *BootstrapToken, s conversion.Scope) error {
return autoConvert_output_BootstrapToken_To_v1alpha3_BootstrapToken(in, out, s)
}
func autoConvert_v1alpha3_Certificate_To_output_Certificate(in *Certificate, out *output.Certificate, s conversion.Scope) error {
out.Name = in.Name
out.ExpirationDate = in.ExpirationDate
out.ResidualTimeSeconds = in.ResidualTimeSeconds
out.ExternallyManaged = in.ExternallyManaged
out.CAName = in.CAName
out.Missing = in.Missing
return nil
}
// Convert_v1alpha3_Certificate_To_output_Certificate is an autogenerated conversion function.
func Convert_v1alpha3_Certificate_To_output_Certificate(in *Certificate, out *output.Certificate, s conversion.Scope) error {
return autoConvert_v1alpha3_Certificate_To_output_Certificate(in, out, s)
}
func autoConvert_output_Certificate_To_v1alpha3_Certificate(in *output.Certificate, out *Certificate, s conversion.Scope) error {
out.Name = in.Name
out.ExpirationDate = in.ExpirationDate
out.ResidualTimeSeconds = in.ResidualTimeSeconds
out.ExternallyManaged = in.ExternallyManaged
out.CAName = in.CAName
out.Missing = in.Missing
return nil
}
// Convert_output_Certificate_To_v1alpha3_Certificate is an autogenerated conversion function.
func Convert_output_Certificate_To_v1alpha3_Certificate(in *output.Certificate, out *Certificate, s conversion.Scope) error {
return autoConvert_output_Certificate_To_v1alpha3_Certificate(in, out, s)
}
func autoConvert_v1alpha3_CertificateExpirationInfo_To_output_CertificateExpirationInfo(in *CertificateExpirationInfo, out *output.CertificateExpirationInfo, s conversion.Scope) error {
out.Certificates = *(*[]output.Certificate)(unsafe.Pointer(&in.Certificates))
out.CertificateAuthorities = *(*[]output.Certificate)(unsafe.Pointer(&in.CertificateAuthorities))
return nil
}
// Convert_v1alpha3_CertificateExpirationInfo_To_output_CertificateExpirationInfo is an autogenerated conversion function.
func Convert_v1alpha3_CertificateExpirationInfo_To_output_CertificateExpirationInfo(in *CertificateExpirationInfo, out *output.CertificateExpirationInfo, s conversion.Scope) error {
return autoConvert_v1alpha3_CertificateExpirationInfo_To_output_CertificateExpirationInfo(in, out, s)
}
func autoConvert_output_CertificateExpirationInfo_To_v1alpha3_CertificateExpirationInfo(in *output.CertificateExpirationInfo, out *CertificateExpirationInfo, s conversion.Scope) error {
out.Certificates = *(*[]Certificate)(unsafe.Pointer(&in.Certificates))
out.CertificateAuthorities = *(*[]Certificate)(unsafe.Pointer(&in.CertificateAuthorities))
return nil
}
// Convert_output_CertificateExpirationInfo_To_v1alpha3_CertificateExpirationInfo is an autogenerated conversion function.
func Convert_output_CertificateExpirationInfo_To_v1alpha3_CertificateExpirationInfo(in *output.CertificateExpirationInfo, out *CertificateExpirationInfo, s conversion.Scope) error {
return autoConvert_output_CertificateExpirationInfo_To_v1alpha3_CertificateExpirationInfo(in, out, s)
}
func autoConvert_v1alpha3_ComponentConfigVersionState_To_output_ComponentConfigVersionState(in *ComponentConfigVersionState, out *output.ComponentConfigVersionState, s conversion.Scope) error {
out.Group = in.Group
out.CurrentVersion = in.CurrentVersion
out.PreferredVersion = in.PreferredVersion
out.ManualUpgradeRequired = in.ManualUpgradeRequired
return nil
}
// Convert_v1alpha3_ComponentConfigVersionState_To_output_ComponentConfigVersionState is an autogenerated conversion function.
func Convert_v1alpha3_ComponentConfigVersionState_To_output_ComponentConfigVersionState(in *ComponentConfigVersionState, out *output.ComponentConfigVersionState, s conversion.Scope) error {
return autoConvert_v1alpha3_ComponentConfigVersionState_To_output_ComponentConfigVersionState(in, out, s)
}
func autoConvert_output_ComponentConfigVersionState_To_v1alpha3_ComponentConfigVersionState(in *output.ComponentConfigVersionState, out *ComponentConfigVersionState, s conversion.Scope) error {
out.Group = in.Group
out.CurrentVersion = in.CurrentVersion
out.PreferredVersion = in.PreferredVersion
out.ManualUpgradeRequired = in.ManualUpgradeRequired
return nil
}
// Convert_output_ComponentConfigVersionState_To_v1alpha3_ComponentConfigVersionState is an autogenerated conversion function.
func Convert_output_ComponentConfigVersionState_To_v1alpha3_ComponentConfigVersionState(in *output.ComponentConfigVersionState, out *ComponentConfigVersionState, s conversion.Scope) error {
return autoConvert_output_ComponentConfigVersionState_To_v1alpha3_ComponentConfigVersionState(in, out, s)
}
func autoConvert_v1alpha3_ComponentUpgradePlan_To_output_ComponentUpgradePlan(in *ComponentUpgradePlan, out *output.ComponentUpgradePlan, s conversion.Scope) error {
out.Name = in.Name
out.CurrentVersion = in.CurrentVersion
out.NewVersion = in.NewVersion
return nil
}
// Convert_v1alpha3_ComponentUpgradePlan_To_output_ComponentUpgradePlan is an autogenerated conversion function.
func Convert_v1alpha3_ComponentUpgradePlan_To_output_ComponentUpgradePlan(in *ComponentUpgradePlan, out *output.ComponentUpgradePlan, s conversion.Scope) error {
return autoConvert_v1alpha3_ComponentUpgradePlan_To_output_ComponentUpgradePlan(in, out, s)
}
func autoConvert_output_ComponentUpgradePlan_To_v1alpha3_ComponentUpgradePlan(in *output.ComponentUpgradePlan, out *ComponentUpgradePlan, s conversion.Scope) error {
out.Name = in.Name
out.CurrentVersion = in.CurrentVersion
out.NewVersion = in.NewVersion
return nil
}
// Convert_output_ComponentUpgradePlan_To_v1alpha3_ComponentUpgradePlan is an autogenerated conversion function.
func Convert_output_ComponentUpgradePlan_To_v1alpha3_ComponentUpgradePlan(in *output.ComponentUpgradePlan, out *ComponentUpgradePlan, s conversion.Scope) error {
return autoConvert_output_ComponentUpgradePlan_To_v1alpha3_ComponentUpgradePlan(in, out, s)
}
func autoConvert_v1alpha3_Images_To_output_Images(in *Images, out *output.Images, s conversion.Scope) error {
out.Images = *(*[]string)(unsafe.Pointer(&in.Images))
return nil
}
// Convert_v1alpha3_Images_To_output_Images is an autogenerated conversion function.
func Convert_v1alpha3_Images_To_output_Images(in *Images, out *output.Images, s conversion.Scope) error {
return autoConvert_v1alpha3_Images_To_output_Images(in, out, s)
}
func autoConvert_output_Images_To_v1alpha3_Images(in *output.Images, out *Images, s conversion.Scope) error {
out.Images = *(*[]string)(unsafe.Pointer(&in.Images))
return nil
}
// Convert_output_Images_To_v1alpha3_Images is an autogenerated conversion function.
func Convert_output_Images_To_v1alpha3_Images(in *output.Images, out *Images, s conversion.Scope) error {
return autoConvert_output_Images_To_v1alpha3_Images(in, out, s)
}
func autoConvert_v1alpha3_UpgradePlan_To_output_UpgradePlan(in *UpgradePlan, out *output.UpgradePlan, s conversion.Scope) error {
out.Components = *(*[]output.ComponentUpgradePlan)(unsafe.Pointer(&in.Components))
out.ConfigVersions = *(*[]output.ComponentConfigVersionState)(unsafe.Pointer(&in.ConfigVersions))
return nil
}
// Convert_v1alpha3_UpgradePlan_To_output_UpgradePlan is an autogenerated conversion function.
func Convert_v1alpha3_UpgradePlan_To_output_UpgradePlan(in *UpgradePlan, out *output.UpgradePlan, s conversion.Scope) error {
return autoConvert_v1alpha3_UpgradePlan_To_output_UpgradePlan(in, out, s)
}
func autoConvert_output_UpgradePlan_To_v1alpha3_UpgradePlan(in *output.UpgradePlan, out *UpgradePlan, s conversion.Scope) error {
out.Components = *(*[]ComponentUpgradePlan)(unsafe.Pointer(&in.Components))
out.ConfigVersions = *(*[]ComponentConfigVersionState)(unsafe.Pointer(&in.ConfigVersions))
return nil
}
// Convert_output_UpgradePlan_To_v1alpha3_UpgradePlan is an autogenerated conversion function.
func Convert_output_UpgradePlan_To_v1alpha3_UpgradePlan(in *output.UpgradePlan, out *UpgradePlan, s conversion.Scope) error {
return autoConvert_output_UpgradePlan_To_v1alpha3_UpgradePlan(in, out, s)
}

View File

@ -0,0 +1,214 @@
//go:build !ignore_autogenerated
// +build !ignore_autogenerated
/*
Copyright 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.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1alpha3
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *BootstrapToken) DeepCopyInto(out *BootstrapToken) {
*out = *in
out.TypeMeta = in.TypeMeta
in.BootstrapToken.DeepCopyInto(&out.BootstrapToken)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BootstrapToken.
func (in *BootstrapToken) DeepCopy() *BootstrapToken {
if in == nil {
return nil
}
out := new(BootstrapToken)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *BootstrapToken) 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 *Certificate) DeepCopyInto(out *Certificate) {
*out = *in
in.ExpirationDate.DeepCopyInto(&out.ExpirationDate)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Certificate.
func (in *Certificate) DeepCopy() *Certificate {
if in == nil {
return nil
}
out := new(Certificate)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CertificateExpirationInfo) DeepCopyInto(out *CertificateExpirationInfo) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.Certificates != nil {
in, out := &in.Certificates, &out.Certificates
*out = make([]Certificate, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.CertificateAuthorities != nil {
in, out := &in.CertificateAuthorities, &out.CertificateAuthorities
*out = make([]Certificate, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateExpirationInfo.
func (in *CertificateExpirationInfo) DeepCopy() *CertificateExpirationInfo {
if in == nil {
return nil
}
out := new(CertificateExpirationInfo)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CertificateExpirationInfo) 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 *ComponentConfigVersionState) DeepCopyInto(out *ComponentConfigVersionState) {
*out = *in
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentConfigVersionState.
func (in *ComponentConfigVersionState) DeepCopy() *ComponentConfigVersionState {
if in == nil {
return nil
}
out := new(ComponentConfigVersionState)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *ComponentUpgradePlan) DeepCopyInto(out *ComponentUpgradePlan) {
*out = *in
out.TypeMeta = in.TypeMeta
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ComponentUpgradePlan.
func (in *ComponentUpgradePlan) DeepCopy() *ComponentUpgradePlan {
if in == nil {
return nil
}
out := new(ComponentUpgradePlan)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *ComponentUpgradePlan) 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 *Images) DeepCopyInto(out *Images) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.Images != nil {
in, out := &in.Images, &out.Images
*out = make([]string, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Images.
func (in *Images) DeepCopy() *Images {
if in == nil {
return nil
}
out := new(Images)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *Images) 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 *UpgradePlan) DeepCopyInto(out *UpgradePlan) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.Components != nil {
in, out := &in.Components, &out.Components
*out = make([]ComponentUpgradePlan, len(*in))
copy(*out, *in)
}
if in.ConfigVersions != nil {
in, out := &in.ConfigVersions, &out.ConfigVersions
*out = make([]ComponentConfigVersionState, len(*in))
copy(*out, *in)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new UpgradePlan.
func (in *UpgradePlan) DeepCopy() *UpgradePlan {
if in == nil {
return nil
}
out := new(UpgradePlan)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *UpgradePlan) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

View File

@ -51,6 +51,62 @@ func (in *BootstrapToken) DeepCopyObject() runtime.Object {
return nil
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *Certificate) DeepCopyInto(out *Certificate) {
*out = *in
in.ExpirationDate.DeepCopyInto(&out.ExpirationDate)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Certificate.
func (in *Certificate) DeepCopy() *Certificate {
if in == nil {
return nil
}
out := new(Certificate)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *CertificateExpirationInfo) DeepCopyInto(out *CertificateExpirationInfo) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.Certificates != nil {
in, out := &in.Certificates, &out.Certificates
*out = make([]Certificate, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
if in.CertificateAuthorities != nil {
in, out := &in.CertificateAuthorities, &out.CertificateAuthorities
*out = make([]Certificate, len(*in))
for i := range *in {
(*in)[i].DeepCopyInto(&(*out)[i])
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new CertificateExpirationInfo.
func (in *CertificateExpirationInfo) DeepCopy() *CertificateExpirationInfo {
if in == nil {
return nil
}
out := new(CertificateExpirationInfo)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *CertificateExpirationInfo) 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 *ComponentConfigVersionState) DeepCopyInto(out *ComponentConfigVersionState) {
*out = *in

View File

@ -20,17 +20,25 @@ import (
"fmt"
"io"
"text/tabwriter"
"time"
"github.com/lithammer/dedent"
"github.com/pkg/errors"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/util/duration"
"k8s.io/cli-runtime/pkg/genericclioptions"
"k8s.io/client-go/kubernetes"
"k8s.io/klog/v2"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmscheme "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/scheme"
kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
outputapischeme "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/scheme"
outputapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha3"
"k8s.io/kubernetes/cmd/kubeadm/app/cmd/options"
cmdutil "k8s.io/kubernetes/cmd/kubeadm/app/cmd/util"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
@ -40,6 +48,7 @@ import (
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
configutil "k8s.io/kubernetes/cmd/kubeadm/app/util/config"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
"k8s.io/kubernetes/cmd/kubeadm/app/util/output"
)
var (
@ -252,7 +261,8 @@ func getRenewSubCommands(out io.Writer, kdir string) []*cobra.Command {
renewalFunc := func(handler *renewal.CertificateRenewHandler) func() error {
return func() error {
// Get cluster configuration (from --config, kubeadm-config ConfigMap, or default as a fallback)
internalcfg, err := getInternalCfg(flags.cfgPath, flags.kubeconfigPath, flags.cfg, out, "renew")
client, _ := kubeconfigutil.ClientSetFromFile(flags.kubeconfigPath)
internalcfg, err := getInternalCfg(flags.cfgPath, client, flags.cfg, &output.TextPrinter{}, "renew")
if err != nil {
return err
}
@ -272,7 +282,8 @@ func getRenewSubCommands(out io.Writer, kdir string) []*cobra.Command {
Long: allLongDesc,
RunE: func(*cobra.Command, []string) error {
// Get cluster configuration (from --config, kubeadm-config ConfigMap, or default as a fallback)
internalcfg, err := getInternalCfg(flags.cfgPath, flags.kubeconfigPath, flags.cfg, out, "renew")
client, _ := kubeconfigutil.ClientSetFromFile(flags.kubeconfigPath)
internalcfg, err := getInternalCfg(flags.cfgPath, client, flags.cfg, &output.TextPrinter{}, "renew")
if err != nil {
return err
}
@ -332,27 +343,24 @@ func renewCert(kdir string, internalcfg *kubeadmapi.InitConfiguration, handler *
return nil
}
func getInternalCfg(cfgPath string, kubeconfigPath string, cfg kubeadmapiv1.ClusterConfiguration, out io.Writer, logPrefix string) (*kubeadmapi.InitConfiguration, error) {
func getInternalCfg(cfgPath string, client kubernetes.Interface, cfg kubeadmapiv1.ClusterConfiguration, printer output.Printer, logPrefix string) (*kubeadmapi.InitConfiguration, error) {
// In case the user is not providing a custom config, try to get current config from the cluster.
// NB. this operation should not block, because we want to allow certificate renewal also in case of not-working clusters
if cfgPath == "" {
client, err := kubeconfigutil.ClientSetFromFile(kubeconfigPath)
if cfgPath == "" && client != nil {
internalcfg, err := configutil.FetchInitConfigurationFromCluster(client, printer, logPrefix, false, false)
if err == nil {
internalcfg, err := configutil.FetchInitConfigurationFromCluster(client, nil, logPrefix, false, false)
if err == nil {
fmt.Println() // add empty line to separate the FetchInitConfigurationFromCluster output from the command output
// certificate renewal or expiration checking doesn't depend on a running cluster, which means the CertificatesDir
// could be set to a value other than the default value or the value fetched from the cluster.
// cfg.CertificatesDir could be empty if the default value is set to empty (not true today).
if len(cfg.CertificatesDir) != 0 {
klog.V(1).Infof("Overriding the cluster certificate directory with the value from command line flag --%s: %s", options.CertificatesDir, cfg.CertificatesDir)
internalcfg.ClusterConfiguration.CertificatesDir = cfg.CertificatesDir
}
return internalcfg, nil
printer.Println() // add empty line to separate the FetchInitConfigurationFromCluster output from the command output
// certificate renewal or expiration checking doesn't depend on a running cluster, which means the CertificatesDir
// could be set to a value other than the default value or the value fetched from the cluster.
// cfg.CertificatesDir could be empty if the default value is set to empty (not true today).
if len(cfg.CertificatesDir) != 0 {
klog.V(1).Infof("Overriding the cluster certificate directory with the value from command line flag --%s: %s", options.CertificatesDir, cfg.CertificatesDir)
internalcfg.ClusterConfiguration.CertificatesDir = cfg.CertificatesDir
}
fmt.Printf("[%s] Error reading configuration from the Cluster. Falling back to default configuration\n\n", logPrefix)
return internalcfg, nil
}
printer.Printf("[%s] Error reading configuration from the Cluster. Falling back to default configuration\n\n", logPrefix)
}
// Read config from --config if provided. Otherwise, use the default configuration
@ -361,6 +369,60 @@ func getInternalCfg(cfgPath string, kubeconfigPath string, cfg kubeadmapiv1.Clus
})
}
// fetchCertificateExpirationInfo returns the certificate expiration info for the given renewal manager
func fetchCertificateExpirationInfo(rm *renewal.Manager) (*outputapiv1alpha3.CertificateExpirationInfo, error) {
info := &outputapiv1alpha3.CertificateExpirationInfo{}
for _, handler := range rm.Certificates() {
if ok, _ := rm.CertificateExists(handler.Name); ok {
e, err := rm.GetCertificateExpirationInfo(handler.Name)
if err != nil {
return nil, err
}
info.Certificates = append(info.Certificates, outputapiv1alpha3.Certificate{
Name: e.Name,
ExpirationDate: metav1.Time{Time: e.ExpirationDate},
ResidualTimeSeconds: int64(e.ResidualTime() / time.Second),
CAName: handler.CAName,
ExternallyManaged: e.ExternallyManaged,
})
} else {
// the certificate does not exist (for any reason)
info.Certificates = append(info.Certificates, outputapiv1alpha3.Certificate{
Name: handler.Name,
Missing: true,
})
}
}
for _, handler := range rm.CAs() {
if ok, _ := rm.CAExists(handler.Name); ok {
e, err := rm.GetCAExpirationInfo(handler.Name)
if err != nil {
return nil, err
}
info.CertificateAuthorities = append(info.CertificateAuthorities, outputapiv1alpha3.Certificate{
Name: e.Name,
ExpirationDate: metav1.Time{Time: e.ExpirationDate},
ResidualTimeSeconds: int64(e.ResidualTime() / time.Second),
ExternallyManaged: e.ExternallyManaged,
})
} else {
// the CA does not exist (for any reason)
info.CertificateAuthorities = append(info.CertificateAuthorities, outputapiv1alpha3.Certificate{
Name: handler.Name,
Missing: true,
})
}
}
return info, nil
}
// clientSetFromFile is a variable that holds the function to create a clientset from a kubeconfig file.
// It is used for testing purposes.
var clientSetFromFile = kubeconfigutil.ClientSetFromFile
// newCmdCertsExpiration creates a new `cert check-expiration` command.
func newCmdCertsExpiration(out io.Writer, kdir string) *cobra.Command {
flags := &expirationFlags{
@ -373,13 +435,21 @@ func newCmdCertsExpiration(out io.Writer, kdir string) *cobra.Command {
// Default values for the cobra help text
kubeadmscheme.Scheme.Default(&flags.cfg)
outputFlags := output.NewOutputFlags(&certTextPrintFlags{}).WithTypeSetter(outputapischeme.Scheme).WithDefaultOutput(output.TextOutput)
cmd := &cobra.Command{
Use: "check-expiration",
Short: "Check certificates expiration for a Kubernetes cluster",
Long: expirationLongDesc,
RunE: func(cmd *cobra.Command, args []string) error {
printer, err := outputFlags.ToPrinter()
if err != nil {
return errors.Wrap(err, "could not construct output printer")
}
// Get cluster configuration (from --config, kubeadm-config ConfigMap, or default as a fallback)
internalcfg, err := getInternalCfg(flags.cfgPath, flags.kubeconfigPath, flags.cfg, out, "check-expiration")
client, _ := clientSetFromFile(flags.kubeconfigPath)
internalcfg, err := getInternalCfg(flags.cfgPath, client, flags.cfg, printer, "check-expiration")
if err != nil {
return err
}
@ -390,73 +460,16 @@ func newCmdCertsExpiration(out io.Writer, kdir string) *cobra.Command {
return err
}
// Get all the certificate expiration info
yesNo := func(b bool) string {
if b {
return "yes"
}
return "no"
info, err := fetchCertificateExpirationInfo(rm)
if err != nil {
return err
}
w := tabwriter.NewWriter(out, 10, 4, 3, ' ', 0)
fmt.Fprintln(w, "CERTIFICATE\tEXPIRES\tRESIDUAL TIME\tCERTIFICATE AUTHORITY\tEXTERNALLY MANAGED")
for _, handler := range rm.Certificates() {
if ok, _ := rm.CertificateExists(handler.Name); ok {
e, err := rm.GetCertificateExpirationInfo(handler.Name)
if err != nil {
return err
}
s := fmt.Sprintf("%s\t%s\t%s\t%s\t%-8v",
e.Name,
e.ExpirationDate.Format("Jan 02, 2006 15:04 MST"),
duration.ShortHumanDuration(e.ResidualTime()),
handler.CAName,
yesNo(e.ExternallyManaged),
)
fmt.Fprintln(w, s)
continue
}
// the certificate does not exist (for any reason)
s := fmt.Sprintf("!MISSING! %s\t\t\t\t",
handler.Name,
)
fmt.Fprintln(w, s)
}
fmt.Fprintln(w)
fmt.Fprintln(w, "CERTIFICATE AUTHORITY\tEXPIRES\tRESIDUAL TIME\tEXTERNALLY MANAGED")
for _, handler := range rm.CAs() {
if ok, _ := rm.CAExists(handler.Name); ok {
e, err := rm.GetCAExpirationInfo(handler.Name)
if err != nil {
return err
}
s := fmt.Sprintf("%s\t%s\t%s\t%-8v",
e.Name,
e.ExpirationDate.Format("Jan 02, 2006 15:04 MST"),
duration.ShortHumanDuration(e.ResidualTime()),
yesNo(e.ExternallyManaged),
)
fmt.Fprintln(w, s)
continue
}
// the CA does not exist (for any reason)
s := fmt.Sprintf("!MISSING! %s\t\t\t",
handler.Name,
)
fmt.Fprintln(w, s)
}
w.Flush()
return nil
return printer.PrintObj(info, out)
},
Args: cobra.NoArgs,
}
addExpirationFlags(cmd, flags)
outputFlags.AddFlags(cmd)
return cmd
}
@ -471,3 +484,72 @@ func addExpirationFlags(cmd *cobra.Command, flags *expirationFlags) {
options.AddCertificateDirFlag(cmd.Flags(), &flags.cfg.CertificatesDir)
options.AddKubeConfigFlag(cmd.Flags(), &flags.kubeconfigPath)
}
// certsTextPrinter prints all certificates in a text form
type certTextPrinter struct {
output.TextPrinter
}
// PrintObj is an implementation of ResourcePrinter.PrintObj for plain text output
func (p *certTextPrinter) PrintObj(obj runtime.Object, writer io.Writer) error {
info, ok := obj.(*outputapiv1alpha3.CertificateExpirationInfo)
if !ok {
return errors.New("unexpected type")
}
yesNo := func(b bool) string {
if b {
return "yes"
}
return "no"
}
tabw := tabwriter.NewWriter(writer, 10, 4, 3, ' ', 0)
fmt.Fprintln(tabw, "CERTIFICATE\tEXPIRES\tRESIDUAL TIME\tCERTIFICATE AUTHORITY\tEXTERNALLY MANAGED")
for _, cert := range info.Certificates {
if cert.Missing {
s := fmt.Sprintf("!MISSING! %s\t\t\t\t", cert.Name)
fmt.Fprintln(tabw, s)
continue
}
s := fmt.Sprintf("%s\t%s\t%s\t%s\t%-8v",
cert.Name,
cert.ExpirationDate.Format("Jan 02, 2006 15:04 MST"),
duration.ShortHumanDuration(time.Duration(cert.ResidualTimeSeconds)*time.Second),
cert.CAName,
yesNo(cert.ExternallyManaged),
)
fmt.Fprintln(tabw, s)
}
fmt.Fprintln(tabw)
fmt.Fprintln(tabw, "CERTIFICATE AUTHORITY\tEXPIRES\tRESIDUAL TIME\tEXTERNALLY MANAGED")
for _, ca := range info.CertificateAuthorities {
if ca.Missing {
s := fmt.Sprintf("!MISSING! %s\t\t\t", ca.Name)
fmt.Fprintln(tabw, s)
continue
}
s := fmt.Sprintf("%s\t%s\t%s\t%-8v",
ca.Name,
ca.ExpirationDate.Format("Jan 02, 2006 15:04 MST"),
duration.ShortHumanDuration(time.Duration(ca.ResidualTimeSeconds)*time.Second),
yesNo(ca.ExternallyManaged),
)
fmt.Fprintln(tabw, s)
}
return tabw.Flush()
}
// certTextPrintFlags provides flags necessary for printing
type certTextPrintFlags struct{}
// ToPrinter returns a kubeadm printer for the text output format
func (tpf *certTextPrintFlags) ToPrinter(outputFormat string) (output.Printer, error) {
if outputFormat == output.TextOutput {
return &certTextPrinter{}, nil
}
return nil, genericclioptions.NoCompatiblePrinterError{OutputFormat: &outputFormat, AllowedFormats: []string{output.TextOutput}}
}

View File

@ -20,8 +20,10 @@ limitations under the License.
package cmd
import (
"bytes"
"crypto"
"crypto/x509"
"encoding/json"
"fmt"
"io"
"os"
@ -30,18 +32,27 @@ import (
"testing"
"time"
"github.com/lithammer/dedent"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/client-go/kubernetes"
fakeclientset "k8s.io/client-go/kubernetes/fake"
clientgotesting "k8s.io/client-go/testing"
"k8s.io/client-go/tools/clientcmd"
kubeadmapi "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm"
kubeadmapiv1 "k8s.io/kubernetes/cmd/kubeadm/app/apis/kubeadm/v1beta3"
outputapiv1alpha3 "k8s.io/kubernetes/cmd/kubeadm/app/apis/output/v1alpha3"
kubeadmconstants "k8s.io/kubernetes/cmd/kubeadm/app/constants"
certsphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/certs"
kubeconfigphase "k8s.io/kubernetes/cmd/kubeadm/app/phases/kubeconfig"
kubeconfigutil "k8s.io/kubernetes/cmd/kubeadm/app/util/kubeconfig"
"k8s.io/kubernetes/cmd/kubeadm/app/util/pkiutil"
testutil "k8s.io/kubernetes/cmd/kubeadm/test"
cmdtestutil "k8s.io/kubernetes/cmd/kubeadm/test/cmd"
@ -482,3 +493,223 @@ kubernetesVersion: %s`,
})
}
}
func TestRunCmdCertsExpiration(t *testing.T) {
kdir := testutil.SetupTempDir(t)
defer func() {
if err := os.RemoveAll(kdir); err != nil {
t.Fatalf("Failed to teardown: %s", err)
}
clientSetFromFile = kubeconfigutil.ClientSetFromFile
}()
cfg := testutil.GetDefaultInternalConfig(t)
cfg.CertificatesDir = kdir
// Generate all the CA
caCerts := map[string]*x509.Certificate{}
caKeys := map[string]crypto.Signer{}
for _, ca := range []*certsphase.KubeadmCert{
certsphase.KubeadmCertRootCA(),
certsphase.KubeadmCertFrontProxyCA(),
certsphase.KubeadmCertEtcdCA(),
} {
caCert, caKey, err := ca.CreateAsCA(cfg)
if err != nil {
t.Fatalf("couldn't write out CA %s: %v", ca.Name, err)
}
caCerts[ca.Name] = caCert
caKeys[ca.Name] = caKey
}
// Generate all the signed certificates
kubeadmCerts := []*certsphase.KubeadmCert{
certsphase.KubeadmCertAPIServer(),
certsphase.KubeadmCertKubeletClient(),
certsphase.KubeadmCertFrontProxyClient(),
certsphase.KubeadmCertEtcdAPIClient(),
certsphase.KubeadmCertEtcdServer(),
certsphase.KubeadmCertEtcdPeer(),
certsphase.KubeadmCertEtcdHealthcheck(),
}
for _, cert := range kubeadmCerts {
caCert := caCerts[cert.CAName]
caKey := caKeys[cert.CAName]
if err := cert.CreateFromCA(cfg, caCert, caKey); err != nil {
t.Fatalf("couldn't write certificate %s: %v", cert.Name, err)
}
}
// Generate all the kubeconfig files with embedded certs
kubeConfigs := []string{
kubeadmconstants.AdminKubeConfigFileName,
kubeadmconstants.SuperAdminKubeConfigFileName,
kubeadmconstants.SchedulerKubeConfigFileName,
kubeadmconstants.ControllerManagerKubeConfigFileName,
}
for _, kubeConfig := range kubeConfigs {
if err := kubeconfigphase.CreateKubeConfigFile(kubeConfig, kdir, cfg); err != nil {
t.Fatalf("couldn't create kubeconfig %q: %v", kubeConfig, err)
}
}
// A minimal kubeadm config with just enough values to avoid triggering
// auto-detection of config values at runtime.
var kubeadmConfig = fmt.Sprintf(`
apiVersion: %[1]s
kind: ClusterConfiguration
certificatesDir: %s
kubernetesVersion: %s`,
kubeadmapiv1.SchemeGroupVersion.String(),
cfg.CertificatesDir,
kubeadmconstants.MinimumControlPlaneVersion.String())
// Write the minimal kubeadm config to a file
customConfigPath := kdir + "/kubeadm.conf"
f, err := os.Create(customConfigPath)
require.NoError(t, err)
_, err = f.Write([]byte(kubeadmConfig))
require.NoError(t, err)
// fakeClientSetFromFile returns a fake clientset with kubeadm config map
var fakeClientSetFromFile = func(_ string) (kubernetes.Interface, error) {
client := fakeclientset.NewSimpleClientset()
client.PrependReactor("get", "configmaps", func(action clientgotesting.Action) (bool, runtime.Object, error) {
getAction := action.(clientgotesting.GetAction)
if getAction.GetNamespace() == metav1.NamespaceSystem && getAction.GetName() == kubeadmconstants.KubeadmConfigConfigMap {
cm := &v1.ConfigMap{
ObjectMeta: metav1.ObjectMeta{
Name: kubeadmconstants.KubeadmConfigConfigMap,
Namespace: metav1.NamespaceSystem,
},
Data: map[string]string{
kubeadmconstants.ClusterConfigurationConfigMapKey: dedent.Dedent(kubeadmConfig),
},
}
return true, cm, nil
}
// Return an empty config map for kubelet and kube-proxy components.
return true, &v1.ConfigMap{}, nil
})
return client, nil
}
// Select a certificate used to simulate a missing certificate file
brokenCertName := kubeadmconstants.APIServerCertAndKeyBaseName
brokenCertPath, _ := pkiutil.PathsForCertAndKey(cfg.CertificatesDir, brokenCertName)
type testCase struct {
name string
config string
output string
brokenCertName string
}
var runTestCase = func(t *testing.T, tc testCase) {
var output bytes.Buffer
cmd := newCmdCertsExpiration(&output, kdir)
args := []string{
fmt.Sprintf("--cert-dir=%s", cfg.CertificatesDir),
}
if tc.config != "" {
args = append(args, fmt.Sprintf("--config=%s", tc.config))
} else {
clientSetFromFile = fakeClientSetFromFile
defer func() {
clientSetFromFile = kubeconfigutil.ClientSetFromFile
}()
}
if tc.output != "" {
args = append(args, fmt.Sprintf("-o=%s", tc.output))
}
cmd.SetArgs(args)
require.NoError(t, cmd.Execute())
switch tc.output {
case "json":
var info outputapiv1alpha3.CertificateExpirationInfo
require.NoError(t, json.Unmarshal(output.Bytes(), &info))
assert.Len(t, info.Certificates, len(kubeadmCerts)+len(kubeConfigs))
assert.Len(t, info.CertificateAuthorities, len(caCerts))
for _, cert := range info.Certificates {
if tc.brokenCertName == cert.Name {
assert.True(t, cert.Missing, "expected certificate to be missing")
} else {
assert.False(t, cert.Missing, "expected certificate to be present")
}
}
default:
outputStr := output.String()
if tc.brokenCertName != "" {
assert.Contains(t, outputStr, "!MISSING!")
} else {
assert.NotContains(t, outputStr, "!MISSING!")
}
var lines []string
for _, line := range strings.SplitAfter(outputStr, "\n") {
if strings.TrimSpace(line) != "" {
lines = append(lines, line)
}
}
// 2 lines for the column headers.
expectedLineCount := len(caCerts) + len(kubeadmCerts) + len(kubeConfigs) + 2
assert.Lenf(t, lines, expectedLineCount, "expected %d non-blank lines in output", expectedLineCount)
}
}
testCases := []testCase{
{
name: "fetch kubeadm config from file and print columns and no missing certs",
config: customConfigPath,
output: "",
},
{
name: "fetch kubeadm config from server and print columns and no missing certs",
output: "",
},
{
name: "fetch kubeadm config from file and print json and no missing certs",
config: customConfigPath,
output: "json",
},
{
name: "fetch kubeadm config from server and print json and no missing certs",
output: "json",
},
// all broken cases must be at the end of the list.
{
name: "fetch kubeadm config from file and print columns and missing certs",
config: customConfigPath,
output: "",
brokenCertName: brokenCertName,
},
{
name: "fetch kubeadm config from server and print columns and missing certs",
output: "",
brokenCertName: brokenCertName,
},
{
name: "fetch kubeadm config from file and print json and missing certs",
config: customConfigPath,
output: "json",
brokenCertName: brokenCertName,
},
{
name: "fetch kubeadm config from server and print json and missing certs",
output: "json",
brokenCertName: brokenCertName,
},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
if tc.brokenCertName != "" {
// remove the file to simulate a missing certificate
_ = os.Remove(brokenCertPath)
}
runTestCase(t, tc)
})
}
}

View File

@ -115,7 +115,7 @@ func runDiff(flags *diffFlags, args []string) error {
SkipCRIDetect: true,
})
} else {
var client *client.Clientset
var client client.Interface
client, err = kubeconfigutil.ClientSetFromFile(flags.kubeConfigPath)
if err != nil {
return errors.Wrapf(err, "couldn't create a Kubernetes client from file %q", flags.kubeConfigPath)

View File

@ -70,7 +70,7 @@ func CreateWithToken(serverURL, clusterName, userName string, caCert []byte, tok
}
// ClientSetFromFile returns a ready-to-use client from a kubeconfig file
func ClientSetFromFile(path string) (*clientset.Clientset, error) {
func ClientSetFromFile(path string) (clientset.Interface, error) {
config, err := clientcmd.LoadFromFile(path)
if err != nil {
return nil, errors.Wrap(err, "failed to load admin kubeconfig")
@ -79,7 +79,7 @@ func ClientSetFromFile(path string) (*clientset.Clientset, error) {
}
// ToClientSet converts a KubeConfig object to a client
func ToClientSet(config *clientcmdapi.Config) (*clientset.Clientset, error) {
func ToClientSet(config *clientcmdapi.Config) (clientset.Interface, error) {
overrides := clientcmd.ConfigOverrides{Timeout: "10s"}
clientConfig, err := clientcmd.NewDefaultClientConfig(*config, &overrides).ClientConfig()
if err != nil {