mirror of
https://github.com/wahyd4/cert-manager.git
synced 2026-08-09 05:06:38 +10:00
Merge pull request #4205 from inteon/kubectl_check_api
Add kubectl 'cert-manager check api' command
This commit is contained in:
@@ -34,6 +34,7 @@ filegroup(
|
||||
":package-srcs",
|
||||
"//cmd/ctl/cmd:all-srcs",
|
||||
"//cmd/ctl/pkg/approve:all-srcs",
|
||||
"//cmd/ctl/pkg/check:all-srcs",
|
||||
"//cmd/ctl/pkg/convert:all-srcs",
|
||||
"//cmd/ctl/pkg/create:all-srcs",
|
||||
"//cmd/ctl/pkg/deny:all-srcs",
|
||||
|
||||
@@ -7,6 +7,7 @@ go_library(
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//cmd/ctl/pkg/approve:go_default_library",
|
||||
"//cmd/ctl/pkg/check:go_default_library",
|
||||
"//cmd/ctl/pkg/convert:go_default_library",
|
||||
"//cmd/ctl/pkg/create:go_default_library",
|
||||
"//cmd/ctl/pkg/deny:go_default_library",
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
|
||||
"github.com/jetstack/cert-manager/cmd/ctl/pkg/approve"
|
||||
"github.com/jetstack/cert-manager/cmd/ctl/pkg/check"
|
||||
"github.com/jetstack/cert-manager/cmd/ctl/pkg/convert"
|
||||
"github.com/jetstack/cert-manager/cmd/ctl/pkg/create"
|
||||
"github.com/jetstack/cert-manager/cmd/ctl/pkg/deny"
|
||||
@@ -74,6 +75,7 @@ kubectl cert-manager is a CLI tool manage and configure cert-manager resources f
|
||||
cmds.AddCommand(inspect.NewCmdInspect(ctx, ioStreams, factory))
|
||||
cmds.AddCommand(approve.NewCmdApprove(ctx, ioStreams, factory))
|
||||
cmds.AddCommand(deny.NewCmdDeny(ctx, ioStreams, factory))
|
||||
cmds.AddCommand(check.NewCmdCheck(ctx, ioStreams, factory))
|
||||
|
||||
// Experimental features
|
||||
cmds.AddCommand(experimental.NewCmdExperimental(ctx, ioStreams, factory))
|
||||
|
||||
@@ -33,5 +33,6 @@ func main() {
|
||||
|
||||
if err := cmd.Execute(); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["check.go"],
|
||||
importpath = "github.com/jetstack/cert-manager/cmd/ctl/pkg/check",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//cmd/ctl/pkg/check/api: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",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//cmd/ctl/pkg/check/api:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@@ -0,0 +1,32 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["api.go"],
|
||||
importpath = "github.com/jetstack/cert-manager/cmd/ctl/pkg/check/api",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/util/cmapichecker:go_default_library",
|
||||
"@com_github_spf13_cobra//:go_default_library",
|
||||
"@io_k8s_apimachinery//pkg/util/wait: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_kubectl//pkg/util/i18n:go_default_library",
|
||||
"@io_k8s_kubectl//pkg/util/templates:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
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 api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
"k8s.io/kubectl/pkg/scheme"
|
||||
"k8s.io/kubectl/pkg/util/i18n"
|
||||
"k8s.io/kubectl/pkg/util/templates"
|
||||
|
||||
"github.com/jetstack/cert-manager/pkg/util/cmapichecker"
|
||||
)
|
||||
|
||||
// Options is a struct to support check api command
|
||||
type Options struct {
|
||||
// APIChecker is used to check that the cert-manager CRDs have been installed on the K8S
|
||||
// API server and that the cert-manager webhooks are all working
|
||||
APIChecker cmapichecker.Interface
|
||||
|
||||
// Time before timeout when waiting
|
||||
Wait time.Duration
|
||||
|
||||
// Time between checks when waiting
|
||||
Interval time.Duration
|
||||
|
||||
// Namespace that is used to dry-run create the certificate resource in
|
||||
Namespace string
|
||||
|
||||
// Print details regarding encountered errors
|
||||
Verbose bool
|
||||
|
||||
genericclioptions.IOStreams
|
||||
}
|
||||
|
||||
var checkApiDesc = templates.LongDesc(i18n.T(`
|
||||
This check attempts to perform a dry-run create of a cert-manager *v1alpha2*
|
||||
Certificate resource in order to verify that CRDs are installed and all the
|
||||
required webhooks are reachable by the K8S API server.
|
||||
We use v1alpha2 API to ensure that the API server has also connected to the
|
||||
cert-manager conversion webhook.`))
|
||||
|
||||
// NewOptions returns initialized Options
|
||||
func NewOptions(ioStreams genericclioptions.IOStreams) *Options {
|
||||
return &Options{
|
||||
IOStreams: ioStreams,
|
||||
}
|
||||
}
|
||||
|
||||
// Complete takes the command arguments and factory and infers any remaining options.
|
||||
func (o *Options) Complete(factory cmdutil.Factory) error {
|
||||
var err error
|
||||
|
||||
o.Namespace, _, err = factory.ToRawKubeConfigLoader().Namespace()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error: cannot get the namespace: %v", err)
|
||||
}
|
||||
|
||||
restConfig, err := factory.ToRESTConfig()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error: cannot create the REST config: %v", err)
|
||||
}
|
||||
|
||||
// We pass the scheme that is used in the RESTConfig's NegotiatedSerializer,
|
||||
// this makes sure that the cmapi is also added to NegotiatedSerializer's scheme
|
||||
// see: https://github.com/jetstack/cert-manager/pull/4205#discussion_r668660271
|
||||
o.APIChecker, err = cmapichecker.New(restConfig, scheme.Scheme, o.Namespace)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewCmdCheckApi returns a cobra command for checking creating cert-manager resources against the K8S API server
|
||||
func NewCmdCheckApi(ctx context.Context, ioStreams genericclioptions.IOStreams, factory cmdutil.Factory) *cobra.Command {
|
||||
o := NewOptions(ioStreams)
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "api",
|
||||
Short: "Check if the cert-manager API is ready",
|
||||
Long: checkApiDesc,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := o.Complete(factory); err != nil {
|
||||
return err
|
||||
}
|
||||
o.Run(ctx)
|
||||
return nil
|
||||
},
|
||||
SilenceUsage: true,
|
||||
SilenceErrors: true,
|
||||
}
|
||||
cmd.Flags().DurationVar(&o.Wait, "wait", 0, "Wait until the cert-manager API is ready (default 0s)")
|
||||
cmd.Flags().DurationVar(&o.Interval, "interval", 5*time.Second, "Time between checks when waiting, must include unit, e.g. 1m or 10m")
|
||||
cmd.Flags().BoolVarP(&o.Verbose, "verbose", "v", false, "Print detailed error messages")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
// Run executes check api command
|
||||
func (o *Options) Run(ctx context.Context) {
|
||||
if !o.Verbose {
|
||||
log.SetFlags(0) // Disable prefixing logs with timestamps.
|
||||
}
|
||||
log.SetOutput(o.ErrOut) // Log all intermediate errors to stderr
|
||||
|
||||
pollContext, cancel := context.WithTimeout(ctx, o.Wait)
|
||||
defer cancel()
|
||||
|
||||
pollErr := wait.PollImmediateUntil(o.Interval, func() (done bool, err error) {
|
||||
if err := o.APIChecker.Check(ctx); err != nil {
|
||||
if !o.Verbose && errors.Unwrap(err) != nil {
|
||||
err = errors.Unwrap(err)
|
||||
}
|
||||
|
||||
log.Printf("Not ready: %v", err)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}, pollContext.Done())
|
||||
|
||||
log.SetOutput(o.Out) // Log conclusion to stdout
|
||||
|
||||
if pollErr != nil {
|
||||
if errors.Is(pollContext.Err(), context.DeadlineExceeded) && o.Wait > 0 {
|
||||
log.Printf("Timed out after %s", o.Wait)
|
||||
}
|
||||
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
log.Printf("The cert-manager API is ready")
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
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 check
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
"k8s.io/cli-runtime/pkg/genericclioptions"
|
||||
cmdutil "k8s.io/kubectl/pkg/cmd/util"
|
||||
|
||||
"github.com/jetstack/cert-manager/cmd/ctl/pkg/check/api"
|
||||
)
|
||||
|
||||
func NewCmdCheck(ctx context.Context, ioStreams genericclioptions.IOStreams, factory cmdutil.Factory) *cobra.Command {
|
||||
cmds := NewCmdCreateBare()
|
||||
cmds.AddCommand(api.NewCmdCheckApi(ctx, ioStreams, factory))
|
||||
|
||||
return cmds
|
||||
}
|
||||
|
||||
// Create a bare Create Command, without any subcommands
|
||||
func NewCmdCreateBare() *cobra.Command {
|
||||
return &cobra.Command{
|
||||
Use: "check",
|
||||
Short: "Check cert-manager components",
|
||||
Long: `Check cert-manager components`,
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ SCRIPT_ROOT=$(dirname "${BASH_SOURCE}")
|
||||
|
||||
# Require kubectl & helm available on PATH
|
||||
check_tool kubectl
|
||||
check_tool kubectl-cert_manager
|
||||
check_tool helm
|
||||
|
||||
# Use the current timestamp as the APP_VERSION so a rolling update will be
|
||||
@@ -52,6 +53,9 @@ kubectl get namespace "${NAMESPACE}" || kubectl create namespace "${NAMESPACE}"
|
||||
# Build the Helm chart package .tgz
|
||||
bazel build //deploy/charts/cert-manager
|
||||
|
||||
# Pre-compile the kubectl plugin, so it can quickly check the api status
|
||||
bazel build //hack/bin:kubectl-cert_manager
|
||||
|
||||
# Upgrade or install cert-manager
|
||||
helm upgrade \
|
||||
--install \
|
||||
@@ -65,3 +69,5 @@ helm upgrade \
|
||||
--set "extraArgs={--dns01-recursive-nameservers=${SERVICE_IP_PREFIX}.16:53,--dns01-recursive-nameservers-only=true,--controllers=*\,gateway-shim}" \
|
||||
"$RELEASE_NAME" \
|
||||
"$REPO_ROOT/bazel-bin/deploy/charts/cert-manager/cert-manager.tgz"
|
||||
|
||||
kubectl cert-manager check api --wait=1m -v
|
||||
|
||||
@@ -33,6 +33,7 @@ filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//pkg/util/cmapichecker:all-srcs",
|
||||
"//pkg/util/cmd:all-srcs",
|
||||
"//pkg/util/coverage:all-srcs",
|
||||
"//pkg/util/errors:all-srcs",
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["cmapichecker.go"],
|
||||
importpath = "github.com/jetstack/cert-manager/pkg/util/cmapichecker",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/apis/certmanager/v1alpha2:go_default_library",
|
||||
"//pkg/apis/meta/v1:go_default_library",
|
||||
"@com_github_pkg_errors//:go_default_library",
|
||||
"@io_k8s_apimachinery//pkg/apis/meta/v1: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",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["cmapichecker_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//pkg/apis/certmanager/v1alpha2:go_default_library",
|
||||
"@io_k8s_apimachinery//pkg/runtime:go_default_library",
|
||||
"@io_k8s_sigs_controller_runtime//pkg/client:go_default_library",
|
||||
"@io_k8s_sigs_controller_runtime//pkg/client/fake:go_default_library",
|
||||
],
|
||||
)
|
||||
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
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 cmapichecker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
errors "github.com/pkg/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/rest"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
|
||||
// Use v1alpha2 API to ensure that the API server has also connected to the
|
||||
// cert-manager conversion webhook.
|
||||
// TODO(wallrj): Only change this when the old deprecated APIs are removed,
|
||||
// at which point the conversion webhook may be removed anyway.
|
||||
cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
|
||||
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCertManagerCRDsNotFound = errors.New("the cert-manager CRDs are not yet installed on the Kubernetes API server")
|
||||
ErrWebhookServiceFailure = errors.New("the cert-manager webhook service is not created yet")
|
||||
ErrWebhookDeploymentFailure = errors.New("the cert-manager webhook deployment is not ready yet")
|
||||
ErrWebhookCertificateFailure = errors.New("the cert-manager webhook CA bundle is not injected yet")
|
||||
)
|
||||
|
||||
const (
|
||||
crdsMappingError = `error finding the scope of the object: failed to get restmapping: no matches for kind "Certificate" in group "cert-manager.io"`
|
||||
crdsNotFoundError = `the server could not find the requested resource (post certificates.cert-manager.io)`
|
||||
)
|
||||
|
||||
var (
|
||||
regexErrCertManagerCRDsNotFound = regexp.MustCompile(`^(` + regexp.QuoteMeta(crdsMappingError) + `|` + regexp.QuoteMeta(crdsNotFoundError) + `)$`)
|
||||
regexErrWebhookServiceFailure = regexp.MustCompile(`Post "(.*)": service "(.*)-webhook" not found`)
|
||||
regexErrWebhookDeploymentFailure = regexp.MustCompile(`Post "(.*)": (.*): connect: connection refused`)
|
||||
regexErrWebhookCertificateFailure = regexp.MustCompile(`Post "(.*)": x509: certificate signed by unknown authority`)
|
||||
)
|
||||
|
||||
// Interface is used to check that the cert-manager CRDs have been installed and are usable.
|
||||
type Interface interface {
|
||||
Check(context.Context) error
|
||||
}
|
||||
|
||||
type cmapiChecker struct {
|
||||
client client.Client
|
||||
}
|
||||
|
||||
// New returns a cert-manager API checker
|
||||
func New(restcfg *rest.Config, scheme *runtime.Scheme, namespace string) (Interface, error) {
|
||||
if err := cmapi.AddToScheme(scheme); err != nil {
|
||||
return nil, errors.Wrap(err, "while configuring scheme")
|
||||
}
|
||||
|
||||
cl, err := client.New(restcfg, client.Options{
|
||||
Scheme: scheme,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "while creating client")
|
||||
}
|
||||
|
||||
return &cmapiChecker{
|
||||
client: client.NewNamespacedClient(client.NewDryRunClient(cl), namespace),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Check attempts to perform a dry-run create of a cert-manager *v1alpha2*
|
||||
// Certificate resource in order to verify that CRDs are installed and all the
|
||||
// required webhooks are reachable by the K8S API server.
|
||||
// We use v1alpha2 API to ensure that the API server has also connected to the
|
||||
// cert-manager conversion webhook.
|
||||
func (o *cmapiChecker) Check(ctx context.Context) error {
|
||||
cert := &cmapi.Certificate{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
GenerateName: "cmapichecker-",
|
||||
},
|
||||
Spec: cmapi.CertificateSpec{
|
||||
DNSNames: []string{"cmapichecker.example"},
|
||||
SecretName: "cmapichecker",
|
||||
IssuerRef: cmmeta.ObjectReference{
|
||||
Name: "cmapichecker",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
if err := o.client.Create(ctx, cert); err != nil {
|
||||
return &ApiCheckError{
|
||||
SimpleError: translateToSimpleError(err),
|
||||
UnderlyingError: err,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ApiCheckError struct {
|
||||
SimpleError error
|
||||
UnderlyingError error
|
||||
}
|
||||
|
||||
func (e *ApiCheckError) Error() string {
|
||||
// If no simple error exists, print underlying error
|
||||
if e.SimpleError == nil {
|
||||
return e.UnderlyingError.Error()
|
||||
}
|
||||
return fmt.Sprintf("%v (%v)", e.SimpleError.Error(), e.UnderlyingError.Error())
|
||||
}
|
||||
|
||||
// If no simple error exists, this function will return nil
|
||||
// which indicates that the error is not unwrappable
|
||||
func (e *ApiCheckError) Unwrap() error {
|
||||
return e.SimpleError
|
||||
}
|
||||
|
||||
// This translateToSimpleError function detects errors based on the error message.
|
||||
// It tries to map these error messages to a better understandable error message that
|
||||
// explains what is wrong. If it cannot create a simple error, it will return nil.
|
||||
// ErrCertManagerCRDsNotFound:
|
||||
// - error finding the scope of the object: failed to get restmapping: no matches for kind "Certificate" in group "cert-manager.io"
|
||||
// ErrWebhookServiceFailure:
|
||||
// - Internal error occurred: failed calling webhook "webhook.cert-manager.io": Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": service "cert-manager-webhook" not found
|
||||
// - conversion webhook for cert-manager.io/v1alpha2, Kind=Certificate failed: Post "https://cert-manager-webhook.cert-manager.svc:443/convert?timeout=30s": service "cert-manager-webhook" not found
|
||||
// ErrWebhookDeploymentFailure:
|
||||
// - Internal error occurred: failed calling webhook "webhook.cert-manager.io": Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": dial tcp 10.96.38.90:443: connect: connection refused
|
||||
// - conversion webhook for cert-manager.io/v1alpha2, Kind=Certificate failed: Post "https://cert-manager-webhook.cert-manager.svc:443/convert?timeout=30s": dial tcp 10.96.38.90:443: connect: connection refused
|
||||
// ErrWebhookCertificateFailure:
|
||||
// - Internal error occurred: failed calling webhook "webhook.cert-manager.io": Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": x509: certificate signed by unknown authority (possibly because of "x509: ECDSA verification failure" while trying to verify candidate authority certificate "cert-manager-webhook-ca")
|
||||
// - conversion webhook for cert-manager.io/v1alpha2, Kind=Certificate failed: Post "https://cert-manager-webhook.cert-manager.svc:443/convert?timeout=30s": x509: certificate signed by unknown authority
|
||||
func translateToSimpleError(err error) error {
|
||||
s := err.Error()
|
||||
|
||||
if regexErrCertManagerCRDsNotFound.MatchString(s) {
|
||||
return ErrCertManagerCRDsNotFound
|
||||
} else if regexErrWebhookServiceFailure.MatchString(s) {
|
||||
return ErrWebhookServiceFailure
|
||||
} else if regexErrWebhookDeploymentFailure.MatchString(s) {
|
||||
return ErrWebhookDeploymentFailure
|
||||
} else if regexErrWebhookCertificateFailure.MatchString(s) {
|
||||
return ErrWebhookCertificateFailure
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
/*
|
||||
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 cmapichecker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client"
|
||||
"sigs.k8s.io/controller-runtime/pkg/client/fake"
|
||||
|
||||
cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
|
||||
)
|
||||
|
||||
type fakeErrorClient struct {
|
||||
client.Client
|
||||
|
||||
createError error
|
||||
}
|
||||
|
||||
func (cl *fakeErrorClient) Create(ctx context.Context, obj client.Object, opts ...client.CreateOption) error {
|
||||
if cl.createError != nil {
|
||||
return cl.createError
|
||||
}
|
||||
|
||||
return cl.Client.Create(ctx, obj, opts...)
|
||||
}
|
||||
|
||||
func newFakeCmapiChecker() (*fakeErrorClient, Interface, error) {
|
||||
scheme := runtime.NewScheme()
|
||||
if err := cmapi.AddToScheme(scheme); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
cl := fake.NewClientBuilder().WithScheme(scheme).Build()
|
||||
errorClient := &fakeErrorClient{
|
||||
Client: cl,
|
||||
createError: nil,
|
||||
}
|
||||
|
||||
return errorClient, &cmapiChecker{
|
||||
client: errorClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
const (
|
||||
errCertManagerCRDsMapping = `error finding the scope of the object: failed to get restmapping: no matches for kind "Certificate" in group "cert-manager.io"`
|
||||
errCertManagerCRDsNotFound = `the server could not find the requested resource (post certificates.cert-manager.io)`
|
||||
|
||||
errMutatingWebhookServiceFailure = `Internal error occurred: failed calling webhook "webhook.cert-manager.io": Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": service "cert-manager-webhook" not found`
|
||||
errMutatingWebhookDeploymentFailure = `Internal error occurred: failed calling webhook "webhook.cert-manager.io": Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": dial tcp 10.96.38.90:443: connect: connection refused`
|
||||
errMutatingWebhookCertificateFailure = `Internal error occurred: failed calling webhook "webhook.cert-manager.io": Post "https://cert-manager-webhook.cert-manager.svc:443/mutate?timeout=10s": x509: certificate signed by unknown authority (possibly because of "x509: ECDSA verification failure" while trying to verify candidate authority certificate "cert-manager-webhook-ca"`
|
||||
|
||||
errConversionWebhookServiceFailure = `conversion webhook for cert-manager.io/v1alpha2, Kind=Certificate failed: Post "https://cert-manager-webhook.cert-manager.svc:443/convert?timeout=30s": service "cert-manager-webhook" not found`
|
||||
errConversionWebhookDeploymentFailure = `conversion webhook for cert-manager.io/v1alpha2, Kind=Certificate failed: Post "https://cert-manager-webhook.cert-manager.svc:443/convert?timeout=30s": dial tcp 10.96.38.90:443: connect: connection refused`
|
||||
errConversionWebhookCertificateFailure = `conversion webhook for cert-manager.io/v1alpha2, Kind=Certificate failed: Post "https://cert-manager-webhook.cert-manager.svc:443/convert?timeout=30s": x509: certificate signed by unknown authority`
|
||||
)
|
||||
|
||||
func TestCmapiChecker(t *testing.T) {
|
||||
tests := map[string]testT{
|
||||
"check API without errors": {
|
||||
createError: nil,
|
||||
|
||||
expectedSimpleError: "",
|
||||
expectedVerboseError: "",
|
||||
},
|
||||
"check API without CRDs installed 1": {
|
||||
createError: errors.New(errCertManagerCRDsMapping),
|
||||
|
||||
expectedSimpleError: ErrCertManagerCRDsNotFound.Error(),
|
||||
expectedVerboseError: fmt.Sprintf("%s (%s)", ErrCertManagerCRDsNotFound.Error(), errCertManagerCRDsMapping),
|
||||
},
|
||||
"check API without CRDs installed 2": {
|
||||
createError: errors.New(errCertManagerCRDsNotFound),
|
||||
|
||||
expectedSimpleError: ErrCertManagerCRDsNotFound.Error(),
|
||||
expectedVerboseError: fmt.Sprintf("%s (%s)", ErrCertManagerCRDsNotFound.Error(), errCertManagerCRDsNotFound),
|
||||
},
|
||||
|
||||
"check API with mutating webhook service not ready": {
|
||||
createError: errors.New(errMutatingWebhookServiceFailure),
|
||||
|
||||
expectedSimpleError: ErrWebhookServiceFailure.Error(),
|
||||
expectedVerboseError: fmt.Sprintf("%s (%s)", ErrWebhookServiceFailure.Error(), errMutatingWebhookServiceFailure),
|
||||
},
|
||||
"check API with conversion webhook service not ready": {
|
||||
createError: errors.New(errConversionWebhookServiceFailure),
|
||||
|
||||
expectedSimpleError: ErrWebhookServiceFailure.Error(),
|
||||
expectedVerboseError: fmt.Sprintf("%s (%s)", ErrWebhookServiceFailure.Error(), errConversionWebhookServiceFailure),
|
||||
},
|
||||
|
||||
"check API with mutating webhook pod not accepting connections": {
|
||||
createError: errors.New(errMutatingWebhookDeploymentFailure),
|
||||
|
||||
expectedSimpleError: ErrWebhookDeploymentFailure.Error(),
|
||||
expectedVerboseError: fmt.Sprintf("%s (%s)", ErrWebhookDeploymentFailure.Error(), errMutatingWebhookDeploymentFailure),
|
||||
},
|
||||
"check API with conversion webhook pod not accepting connections": {
|
||||
createError: errors.New(errConversionWebhookDeploymentFailure),
|
||||
|
||||
expectedSimpleError: ErrWebhookDeploymentFailure.Error(),
|
||||
expectedVerboseError: fmt.Sprintf("%s (%s)", ErrWebhookDeploymentFailure.Error(), errConversionWebhookDeploymentFailure),
|
||||
},
|
||||
|
||||
"check API with webhook certificate not updated in mutation webhook resource definitions": {
|
||||
createError: errors.New(errMutatingWebhookCertificateFailure),
|
||||
|
||||
expectedSimpleError: ErrWebhookCertificateFailure.Error(),
|
||||
expectedVerboseError: fmt.Sprintf("%s (%s)", ErrWebhookCertificateFailure.Error(), errMutatingWebhookCertificateFailure),
|
||||
},
|
||||
"check API with webhook certificate not updated in conversion webhook resource definitions": {
|
||||
createError: errors.New(errConversionWebhookCertificateFailure),
|
||||
|
||||
expectedSimpleError: ErrWebhookCertificateFailure.Error(),
|
||||
expectedVerboseError: fmt.Sprintf("%s (%s)", ErrWebhookCertificateFailure.Error(), errConversionWebhookCertificateFailure),
|
||||
},
|
||||
"unexpected error": {
|
||||
createError: errors.New("unexpected error"),
|
||||
|
||||
expectedSimpleError: "",
|
||||
expectedVerboseError: "unexpected error",
|
||||
},
|
||||
}
|
||||
|
||||
for n, test := range tests {
|
||||
t.Run(n, func(t *testing.T) {
|
||||
runTest(t, test)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type testT struct {
|
||||
createError error
|
||||
|
||||
expectedSimpleError string
|
||||
expectedVerboseError string
|
||||
}
|
||||
|
||||
func runTest(t *testing.T, test testT) {
|
||||
errorClient, checker, err := newFakeCmapiChecker()
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
errorClient.createError = test.createError
|
||||
|
||||
var unwrappedErr error
|
||||
err = checker.Check(context.TODO())
|
||||
if err != nil {
|
||||
if err.Error() != test.expectedVerboseError {
|
||||
t.Errorf("error differs from expected error:\n%s\n vs \n%s", err.Error(), test.expectedVerboseError)
|
||||
}
|
||||
|
||||
unwrappedErr = errors.Unwrap(err)
|
||||
} else {
|
||||
if test.expectedVerboseError != "" {
|
||||
t.Errorf("expected error did not occure:\n%s", test.expectedVerboseError)
|
||||
}
|
||||
}
|
||||
|
||||
if unwrappedErr != nil {
|
||||
if unwrappedErr.Error() != test.expectedSimpleError {
|
||||
t.Errorf("simple error differs from expected error:\n%s\n vs \n%s", unwrappedErr.Error(), test.expectedSimpleError)
|
||||
}
|
||||
} else {
|
||||
if test.expectedSimpleError != "" {
|
||||
t.Errorf("expected simple error did not occure:\n%s", test.expectedSimpleError)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user