Merge pull request #4226 from inteon/simple_kubectl_check_version
add 'kubectl cert-manager version'
This commit is contained in:
commit
d0f4c82baf
@ -67,7 +67,7 @@ kubectl cert-manager is a CLI tool manage and configure cert-manager resources f
|
||||
}
|
||||
|
||||
ioStreams := genericclioptions.IOStreams{In: in, Out: out, ErrOut: err}
|
||||
cmds.AddCommand(version.NewCmdVersion(ctx, ioStreams))
|
||||
cmds.AddCommand(version.NewCmdVersion(ctx, ioStreams, factory))
|
||||
cmds.AddCommand(convert.NewCmdConvert(ctx, ioStreams))
|
||||
cmds.AddCommand(create.NewCmdCreate(ctx, ioStreams, factory))
|
||||
cmds.AddCommand(renew.NewCmdRenew(ctx, ioStreams, factory))
|
||||
|
||||
@ -56,8 +56,7 @@ const (
|
||||
defaultCertManagerNamespace = "cert-manager"
|
||||
)
|
||||
|
||||
const installDesc = `
|
||||
This command installs cert-manager. It uses the Helm libraries to do so.
|
||||
const installDesc = `This command installs cert-manager. It uses the Helm libraries to do so.
|
||||
|
||||
The latest published cert-manager chart in the "https://charts.jetstack.io" repo is used.
|
||||
Most of the features supported by 'helm install' are also supported by this command.
|
||||
|
||||
@ -7,9 +7,11 @@ go_library(
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/util:go_default_library",
|
||||
"//pkg/util/versionchecker:go_default_library",
|
||||
"@com_github_spf13_cobra//:go_default_library",
|
||||
"@io_k8s_cli_runtime//pkg/genericclioptions:go_default_library",
|
||||
"@io_k8s_kubectl//pkg/cmd/util:go_default_library",
|
||||
"@io_k8s_kubectl//pkg/scheme:go_default_library",
|
||||
"@io_k8s_sigs_yaml//:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
@ -25,19 +25,32 @@ import (
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"sigs.k8s.io/yaml"
|
||||
|
||||
"github.com/jetstack/cert-manager/pkg/util"
|
||||
"github.com/jetstack/cert-manager/pkg/util/versionchecker"
|
||||
)
|
||||
|
||||
// Version is a struct for version information
|
||||
type Version struct {
|
||||
ClientVersion *util.Version `json:"clientVersion,omitempty"`
|
||||
ServerVersion *versionchecker.Version `json:"serverVersion,omitempty"`
|
||||
}
|
||||
|
||||
// Options is a struct to support version command
|
||||
type Options struct {
|
||||
// If true, don't try to retrieve the installed version
|
||||
ClientOnly bool
|
||||
|
||||
// If true, only prints the version number.
|
||||
Short bool
|
||||
|
||||
// Output is the target output format for the version string. This may be of
|
||||
// value "", "json" or "yaml".
|
||||
Output string
|
||||
|
||||
// If true, prints the version number.
|
||||
Short bool
|
||||
VersionChecker versionchecker.Interface
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
@ -49,23 +62,48 @@ func NewOptions(ioStreams genericclioptions.IOStreams) *Options {
|
||||
}
|
||||
}
|
||||
|
||||
const versionLong = `Print the cert-manager kubectl plugin version and the deployed cert-manager version.
|
||||
|
||||
The kubectl plugin version is embedded in the binary and directly displayed. Determining
|
||||
the the deployed cert-manager version is done by querying the cert-manger resources.
|
||||
First, the tool looks at the labels of the cert-manager CRD resources. Then, it searches
|
||||
for the labels of the resources related the the cert-manager webhook linked in the CRDs.
|
||||
It also tries to derive the version from the docker image tag of that webhook service.
|
||||
After gathering all this version information, the tool checks if all versions are the same
|
||||
and returns that version. If no version information is found or the found versions differ,
|
||||
an error will be displayed.
|
||||
|
||||
The '--client' flag can be used to disable the logic that tries to determine the installed
|
||||
cert-manager version.
|
||||
|
||||
Some example uses:
|
||||
$ kubectl cert-manager version
|
||||
or
|
||||
$ kubectl cert-manager version --client
|
||||
or
|
||||
$ kubectl cert-manager version --short
|
||||
or
|
||||
$ kubectl cert-manager version -o yaml
|
||||
`
|
||||
|
||||
// NewCmdVersion returns a cobra command for fetching versions
|
||||
func NewCmdVersion(ctx context.Context, ioStreams genericclioptions.IOStreams) *cobra.Command {
|
||||
func NewCmdVersion(ctx context.Context, ioStreams genericclioptions.IOStreams, factory cmdutil.Factory) *cobra.Command {
|
||||
o := NewOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the kubectl cert-manager version",
|
||||
Long: "Print the kubectl cert-manager version",
|
||||
Short: "Print the cert-manager kubectl plugin version and the deployed cert-manager version",
|
||||
Long: versionLong,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmdutil.CheckErr(o.Validate())
|
||||
cmdutil.CheckErr(o.Run())
|
||||
cmdutil.CheckErr(o.Complete(factory))
|
||||
cmdutil.CheckErr(o.Run(ctx))
|
||||
},
|
||||
}
|
||||
|
||||
cmd.Flags().StringVarP(&o.Output, "output", "o", "", "One of '', 'yaml' or 'json'.")
|
||||
cmd.Flags().BoolVar(&o.Short, "short", false, "If true, print just the version number.")
|
||||
|
||||
cmd.Flags().BoolVar(&o.ClientOnly, "client", o.ClientOnly, "If true, shows client version only (no server required).")
|
||||
cmd.Flags().BoolVar(&o.Short, "short", o.Short, "If true, print just the version number.")
|
||||
cmd.Flags().StringVarP(&o.Output, "output", "o", o.Output, "One of 'yaml' or 'json'.")
|
||||
return cmd
|
||||
}
|
||||
|
||||
@ -79,16 +117,52 @@ func (o *Options) Validate() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Complete takes the command arguments and factory and infers any remaining options.
|
||||
func (o *Options) Complete(factory cmdutil.Factory) error {
|
||||
if o.ClientOnly {
|
||||
return nil
|
||||
}
|
||||
|
||||
restConfig, err := factory.ToRESTConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("cannot create the REST config: %v", err)
|
||||
}
|
||||
|
||||
o.VersionChecker, err = versionchecker.New(restConfig, scheme.Scheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Run executes version command
|
||||
func (o *Options) Run() error {
|
||||
versionInfo := util.VersionInfo()
|
||||
func (o *Options) Run(ctx context.Context) error {
|
||||
var (
|
||||
serverVersion *versionchecker.Version
|
||||
serverErr error
|
||||
versionInfo Version
|
||||
)
|
||||
|
||||
clientVersion := util.VersionInfo()
|
||||
versionInfo.ClientVersion = &clientVersion
|
||||
|
||||
if !o.ClientOnly {
|
||||
serverVersion, serverErr = o.VersionChecker.Version(ctx)
|
||||
versionInfo.ServerVersion = serverVersion
|
||||
}
|
||||
|
||||
switch o.Output {
|
||||
case "":
|
||||
if o.Short {
|
||||
fmt.Fprintf(o.Out, "%s\n", versionInfo.GitVersion)
|
||||
fmt.Fprintf(o.Out, "Client Version: %s\n", clientVersion.GitVersion)
|
||||
if serverVersion != nil {
|
||||
fmt.Fprintf(o.Out, "Server Version: %s\n", serverVersion.Detected)
|
||||
}
|
||||
} else {
|
||||
fmt.Fprintf(o.Out, "%#v\n", versionInfo)
|
||||
fmt.Fprintf(o.Out, "Client Version: %s\n", fmt.Sprintf("%#v", clientVersion))
|
||||
if serverVersion != nil {
|
||||
fmt.Fprintf(o.Out, "Server Version: %s\n", fmt.Sprintf("%#v", serverVersion))
|
||||
}
|
||||
}
|
||||
case "yaml":
|
||||
marshalled, err := yaml.Marshal(&versionInfo)
|
||||
@ -108,5 +182,5 @@ func (o *Options) Run() error {
|
||||
return fmt.Errorf("VersionOptions were not validated: --output=%q should have been rejected", o.Output)
|
||||
}
|
||||
|
||||
return nil
|
||||
return serverErr
|
||||
}
|
||||
|
||||
@ -40,6 +40,7 @@ filegroup(
|
||||
"//pkg/util/pki:all-srcs",
|
||||
"//pkg/util/predicate:all-srcs",
|
||||
"//pkg/util/profiling:all-srcs",
|
||||
"//pkg/util/versionchecker:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
|
||||
67
pkg/util/versionchecker/BUILD.bazel
Normal file
67
pkg/util/versionchecker/BUILD.bazel
Normal file
@ -0,0 +1,67 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"fromcrd.go",
|
||||
"fromlabels.go",
|
||||
"fromservice.go",
|
||||
"versionchecker.go",
|
||||
],
|
||||
importpath = "github.com/jetstack/cert-manager/pkg/util/versionchecker",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@io_k8s_api//core/v1:go_default_library",
|
||||
"@io_k8s_apiextensions_apiserver//pkg/apis/apiextensions/v1:go_default_library",
|
||||
"@io_k8s_apiextensions_apiserver//pkg/apis/apiextensions/v1beta1:go_default_library",
|
||||
"@io_k8s_apimachinery//pkg/api/errors:go_default_library",
|
||||
"@io_k8s_apimachinery//pkg/labels:go_default_library",
|
||||
"@io_k8s_apimachinery//pkg/runtime:go_default_library",
|
||||
"@io_k8s_client_go//rest:go_default_library",
|
||||
"@io_k8s_sigs_controller_runtime//pkg/client:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"getpodfromtemplate_test.go",
|
||||
"versionchecker_test.go",
|
||||
],
|
||||
embed = [":go_default_library"],
|
||||
embedsrcs = ["//pkg/util/versionchecker/testdata:test_manifests.tar"], # keep
|
||||
deps = [
|
||||
"//pkg/util:go_default_library",
|
||||
"@io_k8s_api//apps/v1:go_default_library",
|
||||
"@io_k8s_api//core/v1:go_default_library",
|
||||
"@io_k8s_apiextensions_apiserver//pkg/apis/apiextensions/v1:go_default_library",
|
||||
"@io_k8s_apiextensions_apiserver//pkg/apis/apiextensions/v1beta1:go_default_library",
|
||||
"@io_k8s_apimachinery//pkg/api/meta:go_default_library",
|
||||
"@io_k8s_apimachinery//pkg/api/validation:go_default_library",
|
||||
"@io_k8s_apimachinery//pkg/apis/meta/v1:go_default_library",
|
||||
"@io_k8s_apimachinery//pkg/apis/meta/v1/unstructured:go_default_library",
|
||||
"@io_k8s_apimachinery//pkg/labels:go_default_library",
|
||||
"@io_k8s_apimachinery//pkg/runtime:go_default_library",
|
||||
"@io_k8s_cli_runtime//pkg/resource:go_default_library",
|
||||
"@io_k8s_client_go//kubernetes/scheme:go_default_library",
|
||||
"@io_k8s_sigs_controller_runtime//pkg/client/fake:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/util/versionchecker/testdata:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
91
pkg/util/versionchecker/fromcrd.go
Normal file
91
pkg/util/versionchecker/fromcrd.go
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright 2021 The cert-manager 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 versionchecker
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
func (o *versionChecker) extractVersionFromCrd(ctx context.Context, crdName string) error {
|
||||
crdKey := client.ObjectKey{Name: crdName}
|
||||
|
||||
objv1 := &apiextensionsv1.CustomResourceDefinition{}
|
||||
err := o.client.Get(ctx, crdKey, objv1)
|
||||
if err == nil {
|
||||
if label := extractVersionFromLabels(objv1.Labels); label != "" {
|
||||
o.versionSources["crdLabelVersion"] = label
|
||||
}
|
||||
|
||||
return o.extractVersionFromCrdv1(ctx, objv1)
|
||||
}
|
||||
|
||||
// If error differs from not found, don't continue and return error
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
objv1beta1 := &apiextensionsv1beta1.CustomResourceDefinition{}
|
||||
err = o.client.Get(ctx, crdKey, objv1beta1)
|
||||
if err == nil {
|
||||
if label := extractVersionFromLabels(objv1beta1.Labels); label != "" {
|
||||
o.versionSources["crdLabelVersion"] = label
|
||||
}
|
||||
|
||||
return o.extractVersionFromCrdv1beta1(ctx, objv1beta1)
|
||||
}
|
||||
|
||||
// If error differs from not found, don't continue and return error
|
||||
if !apierrors.IsNotFound(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
return ErrCertManagerCRDsNotFound
|
||||
}
|
||||
|
||||
func (o *versionChecker) extractVersionFromCrdv1(ctx context.Context, crd *apiextensionsv1.CustomResourceDefinition) error {
|
||||
if (crd.Spec.Conversion == nil) ||
|
||||
(crd.Spec.Conversion.Webhook == nil) ||
|
||||
(crd.Spec.Conversion.Webhook.ClientConfig == nil) ||
|
||||
(crd.Spec.Conversion.Webhook.ClientConfig.Service == nil) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return o.extractVersionFromService(
|
||||
ctx,
|
||||
crd.Spec.Conversion.Webhook.ClientConfig.Service.Namespace,
|
||||
crd.Spec.Conversion.Webhook.ClientConfig.Service.Name,
|
||||
)
|
||||
}
|
||||
|
||||
func (o *versionChecker) extractVersionFromCrdv1beta1(ctx context.Context, crd *apiextensionsv1beta1.CustomResourceDefinition) error {
|
||||
if (crd.Spec.Conversion == nil) ||
|
||||
(crd.Spec.Conversion.WebhookClientConfig == nil) ||
|
||||
(crd.Spec.Conversion.WebhookClientConfig.Service == nil) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return o.extractVersionFromService(
|
||||
ctx,
|
||||
crd.Spec.Conversion.WebhookClientConfig.Service.Namespace,
|
||||
crd.Spec.Conversion.WebhookClientConfig.Service.Name,
|
||||
)
|
||||
}
|
||||
45
pkg/util/versionchecker/fromlabels.go
Normal file
45
pkg/util/versionchecker/fromlabels.go
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
Copyright 2021 The cert-manager 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 versionchecker
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
)
|
||||
|
||||
var helmChartVersion = regexp.MustCompile(`-(v(?:\d+)\.(?:\d+)\.(?:\d+)(?:.*))$`)
|
||||
|
||||
func extractVersionFromLabels(crdLabels map[string]string) string {
|
||||
if version, ok := crdLabels["app.kubernetes.io/version"]; ok {
|
||||
return version
|
||||
}
|
||||
|
||||
if chartName, ok := crdLabels["helm.sh/chart"]; ok {
|
||||
version := helmChartVersion.FindStringSubmatch(chartName)
|
||||
if len(version) == 2 {
|
||||
return version[1]
|
||||
}
|
||||
}
|
||||
|
||||
if chartName, ok := crdLabels["chart"]; ok {
|
||||
version := helmChartVersion.FindStringSubmatch(chartName)
|
||||
if len(version) == 2 {
|
||||
return version[1]
|
||||
}
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
70
pkg/util/versionchecker/fromservice.go
Normal file
70
pkg/util/versionchecker/fromservice.go
Normal file
@ -0,0 +1,70 @@
|
||||
/*
|
||||
Copyright 2021 The cert-manager 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 versionchecker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"regexp"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
var imageVersion = regexp.MustCompile(`^quay.io/jetstack/cert-manager-webhook:(v(?:\d+)\.(?:\d+)\.(?:\d+)(?:.*))$`)
|
||||
|
||||
func (o *versionChecker) extractVersionFromService(
|
||||
ctx context.Context,
|
||||
namespace string,
|
||||
serviceName string,
|
||||
) error {
|
||||
service := &corev1.Service{}
|
||||
serviceKey := client.ObjectKey{Namespace: namespace, Name: serviceName}
|
||||
err := o.client.Get(ctx, serviceKey, service)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if label := extractVersionFromLabels(service.Labels); label != "" {
|
||||
o.versionSources["webhookServiceLabelVersion"] = label
|
||||
}
|
||||
|
||||
listOptions := client.MatchingLabelsSelector{
|
||||
Selector: labels.Set(service.Spec.Selector).AsSelector(),
|
||||
}
|
||||
pods := &corev1.PodList{}
|
||||
err = o.client.List(ctx, pods, listOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, pod := range pods.Items {
|
||||
if label := extractVersionFromLabels(pod.Labels); label != "" {
|
||||
o.versionSources["webhookPodLabelVersion"] = label
|
||||
}
|
||||
|
||||
for _, container := range pod.Spec.Containers {
|
||||
version := imageVersion.FindStringSubmatch(container.Image)
|
||||
if len(version) == 2 {
|
||||
o.versionSources["webhookPodImageVersion"] = version[1]
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
91
pkg/util/versionchecker/getpodfromtemplate_test.go
Normal file
91
pkg/util/versionchecker/getpodfromtemplate_test.go
Normal file
@ -0,0 +1,91 @@
|
||||
/*
|
||||
Copyright 2021 The cert-manager 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 versionchecker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/api/validation"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
cmutil "github.com/jetstack/cert-manager/pkg/util"
|
||||
)
|
||||
|
||||
// Based on https://github.com/kubernetes/kubernetes/blob/ca643a4d1f7bfe34773c74f79527be4afd95bf39/pkg/controller/controller_utils.go#L542
|
||||
|
||||
var validatePodName = validation.NameIsDNSSubdomain
|
||||
|
||||
func getPodFromTemplate(template *v1.PodTemplateSpec, parentObject runtime.Object, controllerRef *metav1.OwnerReference) (*v1.Pod, error) {
|
||||
desiredLabels := getPodsLabelSet(template)
|
||||
desiredFinalizers := getPodsFinalizers(template)
|
||||
desiredAnnotations := getPodsAnnotationSet(template)
|
||||
accessor, err := meta.Accessor(parentObject)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("parentObject does not have ObjectMeta, %v", err)
|
||||
}
|
||||
prefix := getPodsPrefix(accessor.GetName())
|
||||
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Labels: desiredLabels,
|
||||
Annotations: desiredAnnotations,
|
||||
GenerateName: prefix,
|
||||
Name: prefix + cmutil.RandStringRunes(5),
|
||||
Finalizers: desiredFinalizers,
|
||||
},
|
||||
}
|
||||
if controllerRef != nil {
|
||||
pod.OwnerReferences = append(pod.OwnerReferences, *controllerRef)
|
||||
}
|
||||
pod.Spec = *template.Spec.DeepCopy()
|
||||
return pod, nil
|
||||
}
|
||||
|
||||
func getPodsLabelSet(template *v1.PodTemplateSpec) labels.Set {
|
||||
desiredLabels := make(labels.Set)
|
||||
for k, v := range template.Labels {
|
||||
desiredLabels[k] = v
|
||||
}
|
||||
return desiredLabels
|
||||
}
|
||||
|
||||
func getPodsFinalizers(template *v1.PodTemplateSpec) []string {
|
||||
desiredFinalizers := make([]string, len(template.Finalizers))
|
||||
copy(desiredFinalizers, template.Finalizers)
|
||||
return desiredFinalizers
|
||||
}
|
||||
|
||||
func getPodsAnnotationSet(template *v1.PodTemplateSpec) labels.Set {
|
||||
desiredAnnotations := make(labels.Set)
|
||||
for k, v := range template.Annotations {
|
||||
desiredAnnotations[k] = v
|
||||
}
|
||||
return desiredAnnotations
|
||||
}
|
||||
|
||||
func getPodsPrefix(controllerName string) string {
|
||||
// use the dash (if the name isn't too long) to make the pod name a bit prettier
|
||||
prefix := fmt.Sprintf("%s-", controllerName)
|
||||
if len(validatePodName(prefix, true)) != 0 {
|
||||
prefix = controllerName
|
||||
}
|
||||
return prefix
|
||||
}
|
||||
1
pkg/util/versionchecker/testdata/.gitignore
vendored
Normal file
1
pkg/util/versionchecker/testdata/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.tar
|
||||
41
pkg/util/versionchecker/testdata/BUILD.bazel
vendored
Normal file
41
pkg/util/versionchecker/testdata/BUILD.bazel
vendored
Normal file
@ -0,0 +1,41 @@
|
||||
# Clone empty version of cert-manager repo and list all tags
|
||||
genrule(
|
||||
name = "git_tags",
|
||||
outs = [":git_tags.txt"],
|
||||
cmd = "git ls-remote -t --refs https://github.com/jetstack/cert-manager.git | awk '{print $$2;}' | sed 's/refs\\/tags\\///' | sed -n '/v1.0.0/,$$p' > $@",
|
||||
)
|
||||
|
||||
genrule(
|
||||
name = "test_manifests",
|
||||
srcs = [
|
||||
"//:version",
|
||||
"//deploy/manifests:cert-manager.yaml",
|
||||
":git_tags.txt",
|
||||
],
|
||||
outs = ["test_manifests.tar"],
|
||||
cmd = """
|
||||
$(location fetch.sh) \
|
||||
$(location //:version) \
|
||||
$(location //deploy/manifests:cert-manager.yaml) \
|
||||
$(location :git_tags.txt) \
|
||||
$(location test_manifests.tar)
|
||||
""",
|
||||
tools = [
|
||||
"fetch.sh",
|
||||
],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
72
pkg/util/versionchecker/testdata/fetch.sh
vendored
Executable file
72
pkg/util/versionchecker/testdata/fetch.sh
vendored
Executable file
@ -0,0 +1,72 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Copyright 2020 The cert-manager 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.
|
||||
|
||||
set -o errexit
|
||||
set -o nounset
|
||||
set -o pipefail
|
||||
|
||||
SCRIPT_ROOT="$( cd "$( dirname "${BASH_SOURCE[0]}" )" > /dev/null && pwd )"
|
||||
|
||||
if [[ $# -eq 4 ]]; then # Running inside bazel
|
||||
echo "Updating generated clients..." >&2
|
||||
elif ! command -v bazel &>/dev/null; then
|
||||
echo "Install bazel at https://bazel.build" >&2
|
||||
exit 1
|
||||
else
|
||||
(
|
||||
set -o xtrace
|
||||
bazel build //pkg/util/versionchecker/testdata:test_manifests.tar
|
||||
cp -f "$(bazel info bazel-bin)/pkg/util/versionchecker/testdata/test_manifests.tar" "$SCRIPT_ROOT"
|
||||
)
|
||||
exit 0
|
||||
fi
|
||||
|
||||
CURRENT_VERSION=$(cat "$1") # $(location //:version)
|
||||
current_version_yaml=$(realpath "$2") # $(location //deploy/manifests:cert-manager.yaml)
|
||||
tags=$(cat "$3") # $(location :git_tags.txt)
|
||||
test_manifests_tar=$(realpath "$4") # $(location test_manifests.tar)
|
||||
|
||||
shift 4
|
||||
|
||||
# copy current version's manifest to current folder (will get included in tar)
|
||||
cp "$current_version_yaml" "$CURRENT_VERSION.yaml"
|
||||
|
||||
manifest_urls=""
|
||||
for tag in $tags
|
||||
do
|
||||
# The "v1.2.0-alpha.1" manifest contains duplicate CRD resources
|
||||
# (2 CRD resources with the same name); don't download this manifest
|
||||
# as it will cause the test to fail when adding the CRD resources
|
||||
# to the fake client
|
||||
if [[ $tag == "v1.2.0-alpha.1" ]]; then
|
||||
continue
|
||||
fi
|
||||
|
||||
manifest_urls+=",$tag"
|
||||
done
|
||||
|
||||
# remove leading "," in string
|
||||
manifest_urls=${manifest_urls#","}
|
||||
|
||||
# download all manifests
|
||||
# --compressed: try gzip compressed download
|
||||
# -s: don't show progress bar
|
||||
# -f: fail if non success code
|
||||
# -L: follow redirects
|
||||
# -o: output to "#1.yaml"
|
||||
curl --compressed -sfLo "#1.yaml" "https://github.com/jetstack/cert-manager/releases/download/{$manifest_urls}/cert-manager.yaml"
|
||||
|
||||
tar -cvf "$test_manifests_tar" *.yaml
|
||||
156
pkg/util/versionchecker/versionchecker.go
Normal file
156
pkg/util/versionchecker/versionchecker.go
Normal file
@ -0,0 +1,156 @@
|
||||
/*
|
||||
Copyright 2021 The cert-manager 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 versionchecker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
|
||||
errors "github.com/pkg/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
)
|
||||
|
||||
const certificatesCertManagerCrdName = "certificates.cert-manager.io"
|
||||
const certificatesCertManagerOldCrdName = "certificates.certmanager.k8s.io"
|
||||
|
||||
var certManagerLabelSelector = map[string]string{
|
||||
"app.kubernetes.io/instance": "cert-manager",
|
||||
}
|
||||
var certManagerOldLabelSelector = map[string]string{
|
||||
"release": "cert-manager",
|
||||
}
|
||||
|
||||
var (
|
||||
ErrCertManagerCRDsNotFound = errors.New("the cert-manager CRDs are not yet installed on the Kubernetes API server")
|
||||
ErrVersionNotDetected = errors.New("could not detect the cert-manager version")
|
||||
ErrMultipleVersionsDetected = errors.New("detect multiple different cert-manager versions")
|
||||
)
|
||||
|
||||
type Version struct {
|
||||
// If all found versions are the same,
|
||||
// this field will contain that version
|
||||
Detected string `json:"detected,omitempty"`
|
||||
|
||||
Sources map[string]string `json:"sources"`
|
||||
}
|
||||
|
||||
func shouldReturn(err error) bool {
|
||||
return (err == nil) || (!errors.Is(err, ErrVersionNotDetected))
|
||||
}
|
||||
|
||||
// Interface is used to check what cert-manager version is installed
|
||||
type Interface interface {
|
||||
Version(context.Context) (*Version, error)
|
||||
}
|
||||
|
||||
type versionChecker struct {
|
||||
client client.Client
|
||||
|
||||
versionSources map[string]string
|
||||
}
|
||||
|
||||
// New returns a cert-manager version checker
|
||||
func New(restcfg *rest.Config, scheme *runtime.Scheme) (Interface, error) {
|
||||
if err := corev1.AddToScheme(scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apiextensionsv1.AddToScheme(scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apiextensionsv1beta1.AddToScheme(scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cl, err := client.New(restcfg, client.Options{
|
||||
Scheme: scheme,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &versionChecker{
|
||||
client: cl,
|
||||
versionSources: map[string]string{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Determine the installed cert-manager version. First, we start by looking for
|
||||
// the "certificates.cert-manager.io" CRD and try to extract the version from that
|
||||
// resource's labels. Then, if it uses a webhook, that webhook service resource's
|
||||
// labels are checked for a label. Lastly the pods linked to the webhook its labels
|
||||
// are checked and the image tag is used to determine the version.
|
||||
// If no "certificates.cert-manager.io" CRD is found, the older
|
||||
// "certificates.certmanager.k8s.io" CRD is tried too.
|
||||
func (o *versionChecker) Version(ctx context.Context) (*Version, error) {
|
||||
// Use the "certificates.cert-manager.io" CRD as a starting point
|
||||
err := o.extractVersionFromCrd(ctx, certificatesCertManagerCrdName)
|
||||
|
||||
if err != nil && errors.Is(err, ErrCertManagerCRDsNotFound) {
|
||||
// Retry using the old CRD name "certificates.certmanager.k8s.io" as
|
||||
// a starting point and overwrite ErrCertManagerCRDsNotFound error
|
||||
err = o.extractVersionFromCrd(ctx, certificatesCertManagerOldCrdName)
|
||||
}
|
||||
|
||||
// From the found versions, now determine if we have found any/
|
||||
// if they are all the same version
|
||||
version, detectionError := o.determineVersion()
|
||||
|
||||
if err != nil && detectionError != nil {
|
||||
// There was an error while determining the version (which is probably
|
||||
// caused by a bad setup/ permission or networking issue) and there also
|
||||
// was an error while trying to reduce the found versions to 1 version
|
||||
// Display both.
|
||||
err = fmt.Errorf("%v: %v", detectionError, err)
|
||||
} else if detectionError != nil {
|
||||
// An error occured while trying to reduce the found versions to 1 version
|
||||
err = detectionError
|
||||
}
|
||||
|
||||
return version, err
|
||||
}
|
||||
|
||||
// Try to determine the version of the cert-manager install based on all found
|
||||
// versions. The function tries to reduce the found versions to 1 correct version.
|
||||
// An error is returned if no sources were found or if multiple different versions
|
||||
// were found.
|
||||
func (o *versionChecker) determineVersion() (*Version, error) {
|
||||
if len(o.versionSources) == 0 {
|
||||
return nil, ErrVersionNotDetected
|
||||
}
|
||||
|
||||
var detectedVersion string
|
||||
for _, version := range o.versionSources {
|
||||
if detectedVersion != "" && version != detectedVersion {
|
||||
// We have found a conflicting version
|
||||
return &Version{
|
||||
Sources: o.versionSources,
|
||||
}, ErrMultipleVersionsDetected
|
||||
}
|
||||
|
||||
detectedVersion = version
|
||||
}
|
||||
|
||||
return &Version{
|
||||
Detected: detectedVersion,
|
||||
Sources: o.versionSources,
|
||||
}, nil
|
||||
}
|
||||
177
pkg/util/versionchecker/versionchecker_test.go
Normal file
177
pkg/util/versionchecker/versionchecker_test.go
Normal file
@ -0,0 +1,177 @@
|
||||
/*
|
||||
Copyright 2021 The cert-manager 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 versionchecker
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"context"
|
||||
"embed"
|
||||
"errors"
|
||||
"io"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/cli-runtime/pkg/resource"
|
||||
kubernetesscheme "k8s.io/client-go/kubernetes/scheme"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
)
|
||||
|
||||
//go:embed testdata/test_manifests.tar
|
||||
var testFiles embed.FS
|
||||
|
||||
func loadManifests() (io.Reader, error, func() (string, error), func()) {
|
||||
data, err := testFiles.Open("testdata/test_manifests.tar")
|
||||
if err != nil {
|
||||
return nil, err, nil, nil
|
||||
}
|
||||
fileReader := tar.NewReader(data)
|
||||
|
||||
return fileReader, nil, func() (string, error) {
|
||||
header, err := fileReader.Next()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSuffix(header.Name, ".yaml"), nil
|
||||
}, func() {
|
||||
if err := data.Close(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func manifestToObject(manifest io.Reader) ([]runtime.Object, error) {
|
||||
obj, err := resource.
|
||||
NewLocalBuilder().
|
||||
Flatten().
|
||||
Unstructured().
|
||||
Stream(manifest, "").
|
||||
Do().
|
||||
Object()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list, ok := obj.(*corev1.List)
|
||||
if !ok {
|
||||
return nil, errors.New("Could not get list")
|
||||
}
|
||||
|
||||
return transformObjects(list.Items)
|
||||
}
|
||||
|
||||
func transformObjects(objects []runtime.RawExtension) ([]runtime.Object, error) {
|
||||
transformedObjects := []runtime.Object{}
|
||||
for _, resource := range objects {
|
||||
var err error
|
||||
gvk := resource.Object.GetObjectKind().GroupVersionKind()
|
||||
|
||||
// Create a pod for a Deployment resource
|
||||
if gvk.Group == "apps" && gvk.Version == "v1" && gvk.Kind == "Deployment" {
|
||||
unstr := resource.Object.(*unstructured.Unstructured)
|
||||
|
||||
var deployment appsv1.Deployment
|
||||
err = runtime.DefaultUnstructuredConverter.FromUnstructured(unstr.Object, &deployment)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
pod, err := getPodFromTemplate(&deployment.Spec.Template, resource.Object, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
transformedObjects = append(transformedObjects, pod)
|
||||
}
|
||||
|
||||
transformedObjects = append(transformedObjects, resource.Object)
|
||||
}
|
||||
|
||||
return transformedObjects, nil
|
||||
}
|
||||
|
||||
func setupFakeVersionChecker(manifest io.Reader) (*versionChecker, error) {
|
||||
scheme := runtime.NewScheme()
|
||||
|
||||
if err := kubernetesscheme.AddToScheme(scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := appsv1.AddToScheme(scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apiextensionsv1.AddToScheme(scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := apiextensionsv1beta1.AddToScheme(scheme); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
objs, err := manifestToObject(manifest)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cl := fake.
|
||||
NewClientBuilder().
|
||||
WithScheme(scheme).
|
||||
WithRuntimeObjects(objs...).
|
||||
Build()
|
||||
|
||||
return &versionChecker{
|
||||
client: cl,
|
||||
versionSources: map[string]string{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestVersionChecker(t *testing.T) {
|
||||
f, err, next, close := loadManifests()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer close()
|
||||
|
||||
for {
|
||||
version, err := next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
t.Run(version, func(t *testing.T) {
|
||||
checker, err := setupFakeVersionChecker(f)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
versionGuess, err := checker.Version(context.TODO())
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if version != versionGuess.Detected {
|
||||
t.Fatalf("wrong -> expected: %s vs detected: %s", version, versionGuess)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user