Merge pull request #3154 from hzhou97/add_order_output

Ctl command `status certificate`: Add order output
This commit is contained in:
jetstack-bot 2020-08-12 14:13:59 +01:00 committed by GitHub
commit e1791b0b0e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 183 additions and 25 deletions

View File

@ -10,6 +10,7 @@ go_library(
visibility = ["//visibility:public"],
deps = [
"//cmd/ctl/pkg/status/util:go_default_library",
"//pkg/apis/acme/v1beta1:go_default_library",
"//pkg/apis/certmanager/v1alpha2:go_default_library",
"//pkg/client/clientset/versioned:go_default_library",
"//pkg/ctl:go_default_library",

View File

@ -33,6 +33,7 @@ import (
"k8s.io/kubectl/pkg/util/i18n"
"k8s.io/kubectl/pkg/util/templates"
cmacme "github.com/jetstack/cert-manager/pkg/apis/acme/v1beta1"
cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
cmclient "github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
"github.com/jetstack/cert-manager/pkg/ctl"
@ -41,7 +42,7 @@ import (
var (
long = templates.LongDesc(i18n.T(`
Get details about the current status of a cert-manager Certificate resource, including information on related resources like CertificateRequest.`))
Get details about the current status of a cert-manager Certificate resource, including information on related resources like CertificateRequest or Order.`))
example = templates.Examples(i18n.T(`
# Query status of Certificate with name 'my-crt' in namespace 'my-namespace'
@ -149,8 +150,7 @@ func (o *Options) Run(args []string) error {
req, reqErr := findMatchingCR(o.CMClient, ctx, crt)
if reqErr != nil {
reqErr = fmt.Errorf("error when finding CertificateRequest: %w\n", reqErr)
}
if req == nil {
} else if req == nil {
reqErr = errors.New("No CertificateRequest found for this Certificate\n")
}
@ -195,6 +195,19 @@ func (o *Options) Run(args []string) error {
status = status.withClusterIssuer(clusterIssuer, issuerErr)
}
// Nothing to output about Order and Challenge if no CR
if req != nil {
// Get Order
order, orderErr := findMatchingOrder(o.CMClient, ctx, req)
if orderErr != nil {
orderErr = fmt.Errorf("error when finding Order: %w\n", orderErr)
} else if order == nil {
orderErr = errors.New("No Order found for this Certificate\n")
}
status.withOrder(order, orderErr)
}
fmt.Fprintf(o.Out, status.String())
return nil
@ -253,3 +266,29 @@ func findMatchingCR(cmClient cmclient.Interface, ctx context.Context, crt *cmapi
return nil, errors.New("found multiple certificate requests with expected revision and owner")
}
}
// findMatchingOrder tries to find an Order that is owned by req.
// If none found returns nil
// If one found returns the Order
// If multiple found or error occurs when listing Orders, returns error
func findMatchingOrder(cmClient cmclient.Interface, ctx context.Context, req *cmapi.CertificateRequest) (*cmacme.Order, error) {
orders, err := cmClient.AcmeV1beta1().Orders(req.Namespace).List(ctx, metav1.ListOptions{})
if err != nil {
return nil, err
}
possibleMatches := []*cmacme.Order{}
for _, order := range orders.Items {
if predicate.ResourceOwnedBy(req)(&order) {
possibleMatches = append(possibleMatches, order.DeepCopy())
}
}
if len(possibleMatches) < 1 {
return nil, nil
} else if len(possibleMatches) == 1 {
return possibleMatches[0], nil
} else {
return nil, fmt.Errorf("found multiple orders owned by CertificateRequest %s", req.Name)
}
}

View File

@ -29,6 +29,7 @@ import (
"k8s.io/kubectl/pkg/describe"
"github.com/jetstack/cert-manager/cmd/ctl/pkg/status/util"
cmacme "github.com/jetstack/cert-manager/pkg/apis/acme/v1beta1"
cmapiv1alpha2 "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
"github.com/jetstack/cert-manager/pkg/util/pki"
)
@ -58,6 +59,8 @@ type CertificateStatus struct {
SecretStatus *SecretStatus
CRStatus *CRStatus
OrderStatus *OrderStatus
}
type IssuerStatus struct {
@ -70,6 +73,9 @@ type IssuerStatus struct {
Kind string
// Conditions of Issuer/ClusterIssuer resource
Conditions []cmapiv1alpha2.IssuerCondition
// boolean indicating if Issuer/ClusterIssuer is a ACME Issuer/ClusterIssuer
// Defaults to false even when Error is not nil
IsACME bool
}
type SecretStatus struct {
@ -114,6 +120,22 @@ type CRStatus struct {
Events *v1.EventList
}
type OrderStatus struct {
// If Error is not nil, there was a problem getting the status of the Order resource,
// so the rest of the fields is unusable
Error error
// Name of the Order resource
Name string
// State of Order resource
State cmacme.State
// Reason why the Order resource is in its State
Reason string
// What authorizations must be completed to validate the DNS names specified on the Order
Authorizations []cmacme.ACMEAuthorization
// Time the Order failed
FailureTime *metav1.Time
}
func newCertificateStatusFromCert(crt *cmapiv1alpha2.Certificate) *CertificateStatus {
if crt == nil {
return nil
@ -137,7 +159,8 @@ func (status *CertificateStatus) withIssuer(issuer *cmapiv1alpha2.Issuer, err er
if issuer == nil {
return status
}
status.IssuerStatus = &IssuerStatus{Name: issuer.Name, Kind: "Issuer", Conditions: issuer.Status.Conditions}
status.IssuerStatus = &IssuerStatus{Name: issuer.Name, Kind: "Issuer",
Conditions: issuer.Status.Conditions, IsACME: issuer.Spec.ACME != nil}
return status
}
@ -149,7 +172,8 @@ func (status *CertificateStatus) withClusterIssuer(clusterIssuer *cmapiv1alpha2.
if clusterIssuer == nil {
return status
}
status.IssuerStatus = &IssuerStatus{Name: clusterIssuer.Name, Kind: "ClusterIssuer", Conditions: clusterIssuer.Status.Conditions}
status.IssuerStatus = &IssuerStatus{Name: clusterIssuer.Name, Kind: "ClusterIssuer",
Conditions: clusterIssuer.Status.Conditions, IsACME: clusterIssuer.Spec.ACME != nil}
return status
}
@ -192,8 +216,22 @@ func (status *CertificateStatus) withCR(req *cmapiv1alpha2.CertificateRequest, e
if req == nil {
return status
}
status.Events = events
status.CRStatus = &CRStatus{Name: req.Name, Namespace: req.Namespace, Conditions: req.Status.Conditions}
status.CRStatus = &CRStatus{Name: req.Name, Namespace: req.Namespace, Conditions: req.Status.Conditions, Events: events}
return status
}
func (status *CertificateStatus) withOrder(order *cmacme.Order, err error) *CertificateStatus {
if err != nil {
status.OrderStatus = &OrderStatus{Error: err}
return status
}
if order == nil {
return status
}
status.OrderStatus = &OrderStatus{Name: order.Name, State: order.Status.State,
Reason: order.Status.Reason, Authorizations: order.Status.Authorizations,
FailureTime: order.Status.FailureTime}
return status
}
@ -224,8 +262,6 @@ func (status *CertificateStatus) String() string {
output += buf.String()
buf.Reset()
if status.IssuerStatus == nil {
}
output += status.IssuerStatus.String()
output += status.SecretStatus.String()
@ -235,6 +271,11 @@ func (status *CertificateStatus) String() string {
output += status.CRStatus.String()
// Do not print anything about Order if not ACME Issuer to avoid confusion
if status.OrderStatus != nil && status.IssuerStatus.IsACME {
output += status.OrderStatus.String()
}
return output
}
@ -367,8 +408,37 @@ func (crStatus *CRStatus) String() string {
prefixWriter := describe.NewPrefixWriter(tabWriter)
util.DescribeEvents(crStatus.Events, prefixWriter, 1)
tabWriter.Flush()
fmt.Println(buf.Bytes())
infos += buf.String()
buf.Reset()
return infos
}
// String returns the information about the status of a CR as a string to be printed as output
func (orderStatus *OrderStatus) String() string {
if orderStatus.Error != nil {
return orderStatus.Error.Error()
}
output := "Order:\n"
output += fmt.Sprintf(" Name: %s\n", orderStatus.Name)
output += fmt.Sprintf(" State: %s, Reason: %s\n", orderStatus.State, orderStatus.Reason)
authString := ""
for _, auth := range orderStatus.Authorizations {
wildcardString := "nil (bool pointer not set)"
if auth.Wildcard != nil {
wildcardString = fmt.Sprintf("%t", *auth.Wildcard)
}
authString += fmt.Sprintf(" URL: %s, Identifier: %s, Initial State: %s, Wildcard: %s\n", auth.URL, auth.Identifier, auth.InitialState, wildcardString)
}
if authString == "" {
output += " No Authorizations for this Order\n"
} else {
output += " Authorizations:\n"
output += authString
}
if orderStatus.FailureTime != nil {
output += fmt.Sprintf(" FailureTime: %s\n", formatTimeString(orderStatus.FailureTime))
}
return output
}

View File

@ -15,6 +15,7 @@ go_test(
"//cmd/ctl/pkg/renew:go_default_library",
"//cmd/ctl/pkg/status/certificate:go_default_library",
"//pkg/api/util:go_default_library",
"//pkg/apis/acme/v1alpha2:go_default_library",
"//pkg/apis/certmanager/v1alpha2:go_default_library",
"//pkg/apis/meta/v1:go_default_library",
"//pkg/client/clientset/versioned:go_default_library",

View File

@ -31,6 +31,7 @@ import (
statuscertcmd "github.com/jetstack/cert-manager/cmd/ctl/pkg/status/certificate"
apiutil "github.com/jetstack/cert-manager/pkg/api/util"
cmacme "github.com/jetstack/cert-manager/pkg/apis/acme/v1alpha2"
cmapi "github.com/jetstack/cert-manager/pkg/apis/certmanager/v1alpha2"
cmmeta "github.com/jetstack/cert-manager/pkg/apis/meta/v1"
"github.com/jetstack/cert-manager/pkg/client/clientset/versioned"
@ -101,6 +102,7 @@ MA6koCR/K23HZfML8vT6lcHvQJp9XXaHRIe9NX/M/2f6VpfO7JjKWLou5k5a
issuer *cmapi.Issuer
clusterIssuer *cmapi.ClusterIssuer
secret *v1.Secret
order *cmacme.Order
expErr bool
expOutput string
@ -117,7 +119,7 @@ MA6koCR/K23HZfML8vT6lcHvQJp9XXaHRIe9NX/M/2f6VpfO7JjKWLou5k5a
inputNamespace: ns1,
clusterIssuer: gen.ClusterIssuer("letsencrypt-prod"),
expErr: false,
expOutput: `Name: testcrt-1
expOutput: `^Name: testcrt-1
Namespace: testns-1
Created at: ([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([\+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))
Conditions:
@ -134,7 +136,7 @@ error when finding Secret "example-tls": secrets "example-tls" not found
Not Before: <none>
Not After: 2020-09-16T09:26:18Z
Renewal Time: <none>
No CertificateRequest found for this Certificate`,
No CertificateRequest found for this Certificate$`,
},
"certificate issued and renewal in progress with Issuer": {
certificate: gen.Certificate(crt2Name,
@ -152,12 +154,17 @@ No CertificateRequest found for this Certificate`,
gen.SetCertificateRequestCSR([]byte("dummyCSR"))),
reqStatus: &cmapi.CertificateRequestStatus{Conditions: []cmapi.CertificateRequestCondition{reqNotReadyCond}},
issuer: gen.Issuer("letsencrypt-prod",
gen.SetIssuerNamespace(ns1)),
gen.SetIssuerNamespace(ns1),
gen.SetIssuerACME(cmacme.ACMEIssuer{})),
secret: gen.Secret("existing-tls-secret",
gen.SetSecretNamespace(ns1),
gen.SetSecretData(map[string][]byte{"tls.crt": tlsCrt})),
order: gen.Order("example-order",
gen.SetOrderNamespace(ns1),
gen.SetOrderCsr([]byte("dummyCSR")),
gen.SetOrderDNSNames("www.example.com")),
expErr: false,
expOutput: `Name: testcrt-2
expOutput: `^Name: testcrt-2
Namespace: testns-1
Created at: ([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([\+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))
Conditions:
@ -191,7 +198,11 @@ CertificateRequest:
Namespace: testns-1
Conditions:
Ready: False, Reason: Pending, Message: Waiting on certificate issuance from order default/example-order: "pending"
Events: <none>`,
Events: <none>
Order:
Name: example-order
State: , Reason:
No Authorizations for this Order$`,
},
"certificate issued and renewal in progress without Issuer": {
certificate: gen.Certificate(crt3Name,
@ -210,7 +221,7 @@ CertificateRequest:
reqStatus: &cmapi.CertificateRequestStatus{Conditions: []cmapi.CertificateRequestCondition{reqNotReadyCond}},
issuer: nil,
expErr: false,
expOutput: `Name: testcrt-3
expOutput: `^Name: testcrt-3
Namespace: testns-1
Created at: ([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([\+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))
Conditions:
@ -229,7 +240,7 @@ CertificateRequest:
Namespace: testns-1
Conditions:
Ready: False, Reason: Pending, Message: Waiting on certificate issuance from order default/example-order: "pending"
Events: <none>`,
Events: <none>$`,
},
"certificate issued and renewal in progress without ClusterIssuer": {
certificate: gen.Certificate(crt4Name,
@ -248,7 +259,7 @@ CertificateRequest:
reqStatus: &cmapi.CertificateRequestStatus{Conditions: []cmapi.CertificateRequestCondition{reqNotReadyCond}},
issuer: nil,
expErr: false,
expOutput: `Name: testcrt-4
expOutput: `^Name: testcrt-4
Namespace: testns-1
Created at: ([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\.[0-9]+)?(([Zz])|([\+|\-]([01][0-9]|2[0-3]):[0-5][0-9]))
Conditions:
@ -267,7 +278,7 @@ CertificateRequest:
Namespace: testns-1
Conditions:
Ready: False, Reason: Pending, Message: Waiting on certificate issuance from order default/example-order: "pending"
Events: <none>`,
Events: <none>$`,
},
}
@ -283,6 +294,7 @@ CertificateRequest:
t.Fatal(err)
}
// Set up related resources
if test.req != nil {
err = createCROwnedByCrt(t, cmCl, ctx, crt, test.req, test.reqStatus)
if err != nil {
@ -310,6 +322,17 @@ CertificateRequest:
}
}
if test.order != nil {
createdReq, err := cmCl.CertmanagerV1alpha2().CertificateRequests(test.req.Namespace).Get(ctx, test.req.Name, metav1.GetOptions{})
if err != nil {
t.Fatal(err)
}
err = createOrderOwnedByCR(t, cmCl, ctx, createdReq, test.order)
if err != nil {
t.Fatal(err)
}
}
// Options to run status command
streams, _, outBuf, _ := genericclioptions.NewTestIOStreams()
opts := &statuscertcmd.Options{
@ -337,10 +360,10 @@ CertificateRequest:
if err != nil {
t.Error(err)
}
dmp := diffmatchpatch.New()
if !match {
dmp := diffmatchpatch.New()
diffs := dmp.DiffMain(strings.TrimSpace(test.expOutput), strings.TrimSpace(outBuf.String()), false)
t.Errorf("got unexpected ouput, diff (ignoring the regex for creation time):\n%s\n\n expected: %s\n\n got: %s", dmp.DiffPrettyText(diffs), test.expOutput, outBuf.String())
t.Errorf("got unexpected output, diff (ignoring line anchors ^ and $ and regex for creation time):\n%s\n\n expected: \n%s\n\n got: \n%s", dmp.DiffPrettyText(diffs), test.expOutput, outBuf.String())
}
})
}
@ -360,6 +383,7 @@ func setCertificateStatus(cmCl versioned.Interface, crt *cmapi.Certificate,
func createCROwnedByCrt(t *testing.T, cmCl versioned.Interface, ctx context.Context, crt *cmapi.Certificate,
req *cmapi.CertificateRequest, reqStatus *cmapi.CertificateRequestStatus) error {
req, err := cmCl.CertmanagerV1alpha2().CertificateRequests(crt.Namespace).Create(ctx, req, metav1.CreateOptions{})
if err != nil {
return err
@ -373,10 +397,27 @@ func createCROwnedByCrt(t *testing.T, cmCl versioned.Interface, ctx context.Cont
if reqStatus != nil {
req.Status.Conditions = reqStatus.Conditions
}
req, err = cmCl.CertmanagerV1alpha2().CertificateRequests(crt.Namespace).UpdateStatus(ctx, req, metav1.UpdateOptions{})
if err != nil {
t.Errorf("Update Err: %v", err)
req, err = cmCl.CertmanagerV1alpha2().CertificateRequests(crt.Namespace).UpdateStatus(ctx, req, metav1.UpdateOptions{})
if err != nil {
t.Errorf("Update Err: %v", err)
}
}
return nil
}
func createOrderOwnedByCR(t *testing.T, cmCl versioned.Interface, ctx context.Context,
req *cmapi.CertificateRequest, order *cmacme.Order) error {
order, err := cmCl.AcmeV1alpha2().Orders(req.Namespace).Create(ctx, order, metav1.CreateOptions{})
if err != nil {
return err
}
order.OwnerReferences = append(order.OwnerReferences, *metav1.NewControllerRef(req, cmapi.SchemeGroupVersion.WithKind("CertificateRequest")))
order, err = cmCl.AcmeV1alpha2().Orders(req.Namespace).Update(ctx, order, metav1.UpdateOptions{})
if err != nil {
t.Errorf("Update Err: %v", err)
}
return nil
}

View File

@ -95,3 +95,9 @@ func SetOrderNamespace(namespace string) OrderModifier {
order.ObjectMeta.Namespace = namespace
}
}
func SetOrderCsr(csr []byte) OrderModifier {
return func(order *cmacme.Order) {
order.Spec.CSR = csr
}
}