diff options
| author | KubeEdge Bot <48982446+kubeedge-bot@users.noreply.github.com> | 2024-07-18 15:54:01 +0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-07-18 15:54:01 +0800 |
| commit | 33b9b8e2535a88fb5b2483c0856d78cdcd290681 (patch) | |
| tree | 1439de848d86d7f6210d2a3684a091e73cd5805e | |
| parent | Merge pull request #5650 from JiaweiGithub/feat/dmi_state (diff) | |
| parent | Encapsulate CA and Certificate operations (diff) | |
| download | kubeedge-33b9b8e2535a88fb5b2483c0856d78cdcd290681.tar.gz | |
Merge pull request #5544 from WillardHu/enhance-certs
Encapsulate CA and Certificate operations
48 files changed, 5298 insertions, 914 deletions
diff --git a/cloud/cmd/cloudcore/app/server.go b/cloud/cmd/cloudcore/app/server.go index ca2765b7e..890a8d08a 100644 --- a/cloud/cmd/cloudcore/app/server.go +++ b/cloud/cmd/cloudcore/app/server.go @@ -38,7 +38,6 @@ import ( beehiveContext "github.com/kubeedge/beehive/pkg/core/context" "github.com/kubeedge/kubeedge/cloud/cmd/cloudcore/app/options" "github.com/kubeedge/kubeedge/cloud/pkg/cloudhub" - "github.com/kubeedge/kubeedge/cloud/pkg/cloudhub/servers/httpserver" "github.com/kubeedge/kubeedge/cloud/pkg/cloudstream" "github.com/kubeedge/kubeedge/cloud/pkg/cloudstream/iptables" "github.com/kubeedge/kubeedge/cloud/pkg/common/client" @@ -180,13 +179,15 @@ func registerModules(c *v1alpha1.CloudCoreConfig) { } func NegotiateTunnelPort() (*int, error) { + ctx := context.Background() kubeClient := client.GetKubeClient() - err := httpserver.CreateNamespaceIfNeeded(kubeClient, constants.SystemNamespace) + err := client.CreateNamespaceIfNeeded(ctx, constants.SystemNamespace) if err != nil { return nil, fmt.Errorf("failed to create system namespace: %v", err) } - tunnelPort, err := kubeClient.CoreV1().ConfigMaps(constants.SystemNamespace).Get(context.TODO(), modules.TunnelPort, metav1.GetOptions{}) + tunnelPort, err := kubeClient.CoreV1().ConfigMaps(constants.SystemNamespace). + Get(ctx, modules.TunnelPort, metav1.GetOptions{}) if err != nil && !apierror.IsNotFound(err) { return nil, err @@ -224,8 +225,8 @@ func NegotiateTunnelPort() (*int, error) { tunnelPort.Annotations[modules.TunnelPortRecordAnnotationKey] = string(recordBytes) - _, err = kubeClient.CoreV1().ConfigMaps(constants.SystemNamespace).Update(context.TODO(), tunnelPort, metav1.UpdateOptions{}) - if err != nil { + if _, err := kubeClient.CoreV1().ConfigMaps(constants.SystemNamespace). + Update(ctx, tunnelPort, metav1.UpdateOptions{}); err != nil { return nil, err } @@ -247,7 +248,7 @@ func NegotiateTunnelPort() (*int, error) { return nil, err } - _, err = kubeClient.CoreV1().ConfigMaps(constants.SystemNamespace).Create(context.TODO(), &v1.ConfigMap{ + _, err = kubeClient.CoreV1().ConfigMaps(constants.SystemNamespace).Create(ctx, &v1.ConfigMap{ ObjectMeta: metav1.ObjectMeta{ Name: modules.TunnelPort, Namespace: constants.SystemNamespace, diff --git a/cloud/pkg/cloudhub/cloudhub.go b/cloud/pkg/cloudhub/cloudhub.go index 41e0debfa..e032721e4 100644 --- a/cloud/pkg/cloudhub/cloudhub.go +++ b/cloud/pkg/cloudhub/cloudhub.go @@ -97,13 +97,14 @@ func (ch *cloudHub) Start() { klog.Errorf("unable to sync caches for objectSyncController") os.Exit(1) } + ctx := beehiveContext.GetContext() // start dispatch message from the cloud to edge node go ch.dispatcher.DispatchDownstream() // check whether the certificates exist in the local directory, // and then check whether certificates exist in the secret, generate if they don't exist - if err := httpserver.PrepareAllCerts(); err != nil { + if err := httpserver.PrepareAllCerts(ctx); err != nil { klog.Exit(err) } // TODO: Will improve in the future @@ -111,12 +112,16 @@ func (ch *cloudHub) Start() { close(DoneTLSTunnelCerts) // generate Token - if err := httpserver.GenerateAndRefresh(beehiveContext.GetContext()); err != nil { + if err := httpserver.GenerateAndRefreshToken(ctx); err != nil { klog.Exit(err) } // HttpServer mainly used to issue certificates for the edge - go httpserver.StartHTTPServer() + go func() { + if err := httpserver.StartHTTPServer(); err != nil { + klog.Exit(err) + } + }() servers.StartCloudHub(ch.messageHandler) diff --git a/cloud/pkg/cloudhub/config/config.go b/cloud/pkg/cloudhub/config/config.go index bbe762bad..b4b4bad39 100644 --- a/cloud/pkg/cloudhub/config/config.go +++ b/cloud/pkg/cloudhub/config/config.go @@ -1,13 +1,12 @@ package config import ( - "encoding/pem" - "os" "sync" "k8s.io/klog/v2" "github.com/kubeedge/kubeedge/pkg/apis/componentconfig/cloudcore/v1alpha1" + "github.com/kubeedge/kubeedge/pkg/security/certs" ) var Config Configure @@ -28,24 +27,26 @@ func InitConfigure(hub *v1alpha1.CloudHub) { klog.Exit("AdvertiseAddress must be specified!") } - Config = Configure{ - CloudHub: *hub, - } + Config = Configure{CloudHub: *hub} - ca, err := os.ReadFile(hub.TLSCAFile) - if err == nil { - block, _ := pem.Decode(ca) - ca = block.Bytes - klog.Info("Succeed in loading CA certificate from local directory") - } + var ca, caKey, cert, key []byte - caKey, err := os.ReadFile(hub.TLSCAKeyFile) - if err == nil { - block, _ := pem.Decode(caKey) - caKey = block.Bytes - klog.Info("Succeed in loading CA key from local directory") + if hub.TLSCAFile != "" { + if block, err := certs.ReadPEMFile(hub.TLSCAFile); err == nil { + ca = block.Bytes + klog.Info("succeed in loading CA certificate from local directory") + } else { + klog.Warningf("failed to load the CA certificate file %s, err: %v", hub.TLSCAFile, err) + } + } + if hub.TLSCAKeyFile != "" { + if block, err := certs.ReadPEMFile(hub.TLSCAKeyFile); err == nil { + caKey = block.Bytes + klog.Info("succeed in loading CA key from local directory") + } else { + klog.Warningf("failed to load the CA key file %s, err: %v", hub.TLSCAKeyFile, err) + } } - if ca != nil && caKey != nil { Config.Ca = ca Config.CaKey = caKey @@ -53,19 +54,22 @@ func InitConfigure(hub *v1alpha1.CloudHub) { klog.Exit("Both of ca and caKey should be specified!") } - cert, err := os.ReadFile(hub.TLSCertFile) - if err == nil { - block, _ := pem.Decode(cert) - cert = block.Bytes - klog.Info("Succeed in loading certificate from local directory") + if hub.TLSCertFile != "" { + if block, err := certs.ReadPEMFile(hub.TLSCertFile); err == nil { + cert = block.Bytes + klog.Info("succeed in loading certificate from local directory") + } else { + klog.Warningf("failed to load the certificate file %s, err: %v", hub.TLSCertFile, err) + } } - key, err := os.ReadFile(hub.TLSPrivateKeyFile) - if err == nil { - block, _ := pem.Decode(key) - key = block.Bytes - klog.Info("Succeed in loading private key from local directory") + if hub.TLSPrivateKeyFile != "" { + if block, err := certs.ReadPEMFile(hub.TLSPrivateKeyFile); err == nil { + key = block.Bytes + klog.Info("succeed in loading private key from local directory") + } else { + klog.Warningf("failed to load the private key file %s, err: %v", hub.TLSPrivateKeyFile, err) + } } - if cert != nil && key != nil { Config.Cert = cert Config.Key = key @@ -74,3 +78,21 @@ func InitConfigure(hub *v1alpha1.CloudHub) { } }) } + +func (c *Configure) UpdateCA(ca, caKey []byte) { + if ca != nil { + c.Ca = ca + } + if caKey != nil { + c.CaKey = caKey + } +} + +func (c *Configure) UpdateCerts(cert, key []byte) { + if cert != nil { + c.Cert = cert + } + if key != nil { + c.Key = key + } +} diff --git a/cloud/pkg/cloudhub/config/config_test.go b/cloud/pkg/cloudhub/config/config_test.go new file mode 100644 index 000000000..fed5d60b1 --- /dev/null +++ b/cloud/pkg/cloudhub/config/config_test.go @@ -0,0 +1,25 @@ +package config + +import ( + "reflect" + "testing" +) + +func TestUpdateConfig(t *testing.T) { + Config.UpdateCA([]byte("ca"), nil) + if !reflect.DeepEqual(Config.Ca, []byte("ca")) { + t.Errorf("UpdateCA(): got %v, want %v", Config.Ca, []byte("ca")) + } + Config.UpdateCA(nil, []byte("caKey")) + if !reflect.DeepEqual(Config.CaKey, []byte("caKey")) { + t.Errorf("UpdateCA(): got %v, want %v", Config.CaKey, []byte("caKey")) + } + Config.UpdateCerts([]byte("cert"), nil) + if !reflect.DeepEqual(Config.Cert, []byte("cert")) { + t.Errorf("UpdateCerts(): got %v, want %v", Config.Cert, []byte("cert")) + } + Config.UpdateCerts(nil, []byte("key")) + if !reflect.DeepEqual(Config.Key, []byte("key")) { + t.Errorf("UpdateCerts(): got %v, want %v", Config.Key, []byte("key")) + } +} diff --git a/cloud/pkg/cloudhub/servers/httpserver/certificate/certs.go b/cloud/pkg/cloudhub/servers/httpserver/certificate/certs.go new file mode 100644 index 000000000..27e7faabf --- /dev/null +++ b/cloud/pkg/cloudhub/servers/httpserver/certificate/certs.go @@ -0,0 +1,162 @@ +/* +Copyright 2024 The KubeEdge 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 certificate + +import ( + "crypto/x509" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/emicklei/go-restful" + certutil "k8s.io/client-go/util/cert" + "k8s.io/klog/v2" + + hubconfig "github.com/kubeedge/kubeedge/cloud/pkg/cloudhub/config" + "github.com/kubeedge/kubeedge/cloud/pkg/cloudhub/servers/httpserver/resps" + "github.com/kubeedge/kubeedge/common/constants" + "github.com/kubeedge/kubeedge/common/types" + "github.com/kubeedge/kubeedge/pkg/security/certs" + "github.com/kubeedge/kubeedge/pkg/security/token" +) + +// GetCA returns the caCertDER +func GetCA(_ *restful.Request, response *restful.Response) { + resps.OK(response, hubconfig.Config.Ca) +} + +// EdgeCoreClientCert will verify the certificate of EdgeCore or token then create EdgeCoreCert and return it +func EdgeCoreClientCert(request *restful.Request, response *restful.Response) { + r := request.Request + nodeName := r.Header.Get(types.HeaderNodeName) + + if cert := r.TLS.PeerCertificates; len(cert) > 0 { + if err := verifyCert(cert[0], nodeName); err != nil { + message := fmt.Sprintf("failed to verify the certificate for edgenode: %s, err: %v", nodeName, err) + klog.Error(message) + resps.ErrorMessage(response, http.StatusUnauthorized, message) + return + } + } else { + authorization := r.Header.Get(types.HeaderAuthorization) + if code, err := verifyAuthorization(authorization); err != nil { + klog.Error(err) + resps.Error(response, code, err) + return + } + } + + usagesStr := r.Header.Get(types.HeaderExtKeyUsages) + reader := http.MaxBytesReader(response, r.Body, constants.MaxRespBodyLength) + certBlock, err := signEdgeCert(reader, usagesStr) + if err != nil { + message := fmt.Sprintf("failed to sign certs for edgenode %s, err: %v", nodeName, err) + klog.Error(message) + resps.ErrorMessage(response, http.StatusInternalServerError, message) + return + } + resps.OK(response, certBlock.Bytes) +} + +// verifyCert verifies the edge certificate by CA certificate when edge certificates rotate. +func verifyCert(cert *x509.Certificate, nodeName string) error { + roots := x509.NewCertPool() + ok := roots.AppendCertsFromPEM(pem.EncodeToMemory(&pem.Block{ + Type: certutil.CertificateBlockType, + Bytes: hubconfig.Config.Ca, + })) + if !ok { + return fmt.Errorf("failed to parse root certificate") + } + opts := x509.VerifyOptions{ + Roots: roots, + KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, + } + if _, err := cert.Verify(opts); err != nil { + return fmt.Errorf("failed to verify edge certificate: %v", err) + } + return verifyCertSubject(cert, nodeName) +} + +// verifyCertSubject ... +func verifyCertSubject(cert *x509.Certificate, nodeName string) error { + if cert.Subject.Organization[0] == "KubeEdge" && cert.Subject.CommonName == "kubeedge.io" { + // In order to maintain compatibility with older versions of certificates + // this condition will be removed in KubeEdge v1.18. + return nil + } + commonName := fmt.Sprintf("system:node:%s", nodeName) + if cert.Subject.Organization[0] == "system:nodes" && cert.Subject.CommonName == commonName { + return nil + } + return fmt.Errorf("request node name is not match with the certificate") +} + +// verifyAuthorization verifies the token from EdgeCore CSR +func verifyAuthorization(authorization string) (int, error) { + klog.V(4).Info("authorization token is: ", authorization) + if authorization == "" { + return http.StatusUnauthorized, errors.New("token validation failure, token is empty") + } + bearerToken := strings.Split(authorization, " ") + if len(bearerToken) != 2 { + return http.StatusUnauthorized, errors.New("token validation failure, token cannot be splited") + } + valid, err := token.Verify(bearerToken[1], hubconfig.Config.CaKey) + if err != nil { + return http.StatusUnauthorized, fmt.Errorf("token validation failure, err: %v", err) + } + if !valid { + return http.StatusUnauthorized, errors.New("token validation failure, valid is false") + } + return http.StatusOK, nil +} + +// signEdgeCert signs the CSR from EdgeCore +func signEdgeCert(r io.ReadCloser, usagesStr string) (*pem.Block, error) { + klog.V(4).Infof("receive sign crt request, ExtKeyUsages: %s", usagesStr) + var usages []x509.ExtKeyUsage + if usagesStr == "" { + usages = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth} + } else { + err := json.Unmarshal([]byte(usagesStr), &usages) + if err != nil { + return nil, fmt.Errorf("unmarshal http header ExtKeyUsages fail, err: %v", err) + } + } + payload, err := io.ReadAll(r) + if err != nil { + return nil, fmt.Errorf("fail to read file when signing the cert, err: %v", err) + } + edgeCertSigningDuration := hubconfig.Config.CloudHub.EdgeCertSigningDuration * time.Hour * 24 + h := certs.GetHandler(certs.HandlerTypeX509) + certBlock, err := h.SignCerts(certs.SignCertsOptionsWithCSR( + payload, + hubconfig.Config.Ca, + hubconfig.Config.CaKey, + usages, + edgeCertSigningDuration, + )) + if err != nil { + return nil, fmt.Errorf("fail to signCerts, err: %v", err) + } + return certBlock, nil +} diff --git a/cloud/pkg/cloudhub/servers/httpserver/certificate/certs_test.go b/cloud/pkg/cloudhub/servers/httpserver/certificate/certs_test.go new file mode 100644 index 000000000..b87081e06 --- /dev/null +++ b/cloud/pkg/cloudhub/servers/httpserver/certificate/certs_test.go @@ -0,0 +1,128 @@ +package certificate + +import ( + "crypto/x509" + "encoding/base64" + "encoding/pem" + "net/http" + "testing" + "time" + + "github.com/golang-jwt/jwt" + "github.com/stretchr/testify/require" + + hubconfig "github.com/kubeedge/kubeedge/cloud/pkg/cloudhub/config" +) + +func TestVerifyCert(t *testing.T) { + capem, _ := pem.Decode([]byte(ca)) + hubconfig.Config.Ca = capem.Bytes + + certpem, _ := pem.Decode([]byte(cert)) + certs, err := x509.ParseCertificate(certpem.Bytes) + require.NoError(t, err) + + err = verifyCert(certs, "hw-test1ht6hcsru") + require.NoError(t, err) +} + +func TestVerifyAuthorization(t *testing.T) { + cakeyDer, err := base64.StdEncoding.DecodeString(cakey) + require.NoError(t, err) + hubconfig.Config.CaKey = cakeyDer + + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{ + ExpiresAt: time.Now().Add(-1 * time.Minute).Unix(), + }) + expiredToken, err := token.SignedString(cakeyDer) + require.NoError(t, err) + + token = jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.StandardClaims{ + ExpiresAt: time.Now().Add(1 * time.Minute).Unix(), + }) + passedToken, err := token.SignedString(cakeyDer) + require.NoError(t, err) + + cases := []struct { + name string + token string + wantCode int + containsError string + }{ + { + name: "token empty", + token: "", + wantCode: http.StatusUnauthorized, + containsError: "token validation failure, token is empty", + }, + { + name: "not splited token", + token: "xxxx", + wantCode: http.StatusUnauthorized, + containsError: "token validation failure, token cannot be splited", + }, + { + name: "invalid token", + token: "Bearer xxxx", + wantCode: http.StatusUnauthorized, + containsError: "token validation failure, err:", + }, + { + name: "expired token", + token: "Bearer " + expiredToken, + wantCode: http.StatusUnauthorized, + containsError: "token validation failure, err: Token is expired", + }, + { + name: "passed token", + token: "Bearer " + passedToken, + wantCode: http.StatusOK, + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + code, err := verifyAuthorization(c.token) + require.Equal(t, c.wantCode, code) + if c.containsError != "" { + require.Error(t, err) + require.ErrorContains(t, err, c.containsError) + } else { + require.NoError(t, err) + } + }) + } +} + +const ( + ca = `-----BEGIN CERTIFICATE----- +MIIBejCCAR+gAwIBAgICBAAwCgYIKoZIzj0EAwIwEzERMA8GA1UEAxMIS3ViZUVk +Z2UwIBcNMjQwNTA5MDczNzU2WhgPMjEyNDAxMDYwNzM3NTZaMBMxETAPBgNVBAMT +CEt1YmVFZGdlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq4Rd11aJ/FXEYBE2 +YCUMjRZVpqytxDBq2anuzokPculGaTrSDiRy1IKukPhlg34bq7J6wqkF0cmFUvcT +jtReq6NhMF8wDgYDVR0PAQH/BAQDAgKkMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr +BgEFBQcDAjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvK3f704DC7OOiVbmO +PyKwJAUwQjAKBggqhkjOPQQDAgNJADBGAiEAkOgvZtFy+aYSqsfxIVMXxScsGilA +P1Iiy/r5PerqODcCIQCH+qeEuxIgZzUAD/Wm6xameEmyn/mO4su/4UE6APNZFQ== +-----END CERTIFICATE-----` + + cakey = `MHcCAQEEIJQgy45Hw91mXm3pRXwxwDg4BgR4DY1UvHlzm/JXr9K6oAoGCCqGSM49AwEHoUQDQgAEq4Rd11aJ/FXEYBE2YCUMjRZVpqytxDBq2anuzokPculGaTrSDiRy1IKukPhlg34bq7J6wqkF0cmFUvcTjtReqw==` + + cert = `-----BEGIN CERTIFICATE----- +MIIBjjCCATWgAwIBAgIIWugtLvecOyUwCgYIKoZIzj0EAwIwEzERMA8GA1UEAxMI +S3ViZUVkZ2UwHhcNMjQwNTA5MDc0MjQ4WhcNMjUwNTA5MDc0MjQ4WjA+MRUwEwYD +VQQKEwxzeXN0ZW06bm9kZXMxJTAjBgNVBAMTHHN5c3RlbTpub2RlOmh3LXRlc3Qx +aHQ2aGNzcnUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARH0RepNVRX5U5lXcTJ +XJM/3PgoaDbxEeiS4RZq2Vz86fESc1KmFL+dUyvaqQ4BBIq3FOfirkwUYuhtiSXr +UyiRo0gwRjAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYD +VR0jBBgwFoAU7yt3+9OAwuzjolW5jj8isCQFMEIwCgYIKoZIzj0EAwIDRwAwRAIg +fswApb3FlJZXRw5aSIvls+uqR1ryfczy4fuzL/Y2i4MCIFyyV0t9Ts9uMHHx8R2+ +6oFBzFcJvH65edh9/eH8rUy8 +-----END CERTIFICATE-----` + + certKey = `-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJOhw4Hxmkq6E9YTwj/y+L+bl7nUgxNKcz1dcgiPlm4WoAoGCCqGSM49 +AwEHoUQDQgAER9EXqTVUV+VOZV3EyVyTP9z4KGg28RHokuEWatlc/OnxEnNSphS/ +nVMr2qkOAQSKtxTn4q5MFGLobYkl61MokQ== +-----END EC PRIVATE KEY-----` +) diff --git a/cloud/pkg/cloudhub/servers/httpserver/certutil.go b/cloud/pkg/cloudhub/servers/httpserver/certutil.go deleted file mode 100644 index 29010239b..000000000 --- a/cloud/pkg/cloudhub/servers/httpserver/certutil.go +++ /dev/null @@ -1,128 +0,0 @@ -package httpserver - -import ( - "crypto" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" - "crypto/x509" - "crypto/x509/pkix" - "errors" - "fmt" - "math" - "math/big" - "time" - - certutil "k8s.io/client-go/util/cert" - - hubconfig "github.com/kubeedge/kubeedge/cloud/pkg/cloudhub/config" - "github.com/kubeedge/kubeedge/common/constants" -) - -const validalityPeriod time.Duration = 365 * 100 - -// NewCertificateAuthorityDer returns certDer and key -func NewCertificateAuthorityDer() ([]byte, crypto.Signer, error) { - caKey, err := NewPrivateKey() - if err != nil { - return nil, nil, err - } - certDER, err := NewSelfSignedCACertDERBytes(caKey) - if err != nil { - return nil, nil, err - } - return certDER, caKey, nil -} - -// NewPrivateKey creates an ECDSA private key -func NewPrivateKey() (crypto.Signer, error) { - return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) -} - -// NewSelfSignedCACertDERBytes creates a CA certificate -func NewSelfSignedCACertDERBytes(key crypto.Signer) ([]byte, error) { - tmpl := x509.Certificate{ - SerialNumber: big.NewInt(1024), - Subject: pkix.Name{ - CommonName: constants.ProjectName, - }, - NotBefore: time.Now().UTC(), - NotAfter: time.Now().Add(time.Hour * 24 * 365 * 100), - - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, - ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, - BasicConstraintsValid: true, - IsCA: true, - } - - caDERBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, key.Public(), key) - if err != nil { - return nil, err - } - return caDERBytes, nil -} - -// NewCloudCoreCertDERandKey creates certificate and key for CloudCore -func NewCloudCoreCertDERandKey(cfg *certutil.Config) ([]byte, []byte, error) { - serverKey, err := NewPrivateKey() - if err != nil { - return nil, nil, fmt.Errorf("failed to generate a privateKey, err: %v", err) - } - - keyDER, err := x509.MarshalECPrivateKey(serverKey.(*ecdsa.PrivateKey)) - if err != nil { - return nil, nil, fmt.Errorf("failed to convert an EC private key to SEC 1, ASN.1 DER form, err: %v", err) - } - - // get ca from config - ca := hubconfig.Config.Ca - caCert, err := x509.ParseCertificate(ca) - if err != nil { - return nil, nil, fmt.Errorf("failed to parse a caCert from the given ASN.1 DER data, err: %v", err) - } - - caKeyDER := hubconfig.Config.CaKey - caKey, err := x509.ParseECPrivateKey(caKeyDER) - if err != nil { - return nil, nil, fmt.Errorf("failed to parse ECPrivateKey, err: %v", err) - } - - certDER, err := NewCertFromCa(cfg, caCert, serverKey.Public(), caKey, validalityPeriod) - if err != nil { - return nil, nil, fmt.Errorf("failed to generate a certificate using the given CA certificate and key, err: %v", err) - } - return certDER, keyDER, nil -} - -// NewCertFromCa creates a signed certificate using the given CA certificate and key -func NewCertFromCa(cfg *certutil.Config, caCert *x509.Certificate, serverKey crypto.PublicKey, caKey crypto.Signer, validalityPeriod time.Duration) ([]byte, error) { - serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64)) - if err != nil { - return nil, err - } - if len(cfg.CommonName) == 0 { - return nil, errors.New("must specify a CommonName") - } - if len(cfg.Usages) == 0 { - return nil, errors.New("must specify at least one ExtKeyUsage") - } - - certTmpl := x509.Certificate{ - Subject: pkix.Name{ - CommonName: cfg.CommonName, - Organization: cfg.Organization, - }, - DNSNames: cfg.AltNames.DNSNames, - IPAddresses: cfg.AltNames.IPs, - SerialNumber: serial, - NotBefore: time.Now().UTC(), - NotAfter: time.Now().Add(time.Hour * 24 * validalityPeriod), - KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, - ExtKeyUsage: cfg.Usages, - } - certDERBytes, err := x509.CreateCertificate(rand.Reader, &certTmpl, caCert, serverKey, caKey) - if err != nil { - return nil, err - } - return certDERBytes, nil -} diff --git a/cloud/pkg/cloudhub/servers/httpserver/report_task_status.go b/cloud/pkg/cloudhub/servers/httpserver/nodetask/report_status.go index 784470502..f5fd7614d 100644 --- a/cloud/pkg/cloudhub/servers/httpserver/report_task_status.go +++ b/cloud/pkg/cloudhub/servers/httpserver/nodetask/report_status.go @@ -5,7 +5,7 @@ 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 + 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, @@ -13,8 +13,7 @@ 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 httpserver +package nodetask import ( "encoding/json" @@ -36,8 +35,8 @@ const ( millionByte = int64(3 * 1024 * 1024) ) -// reportTaskStatus report the status of task -func reportTaskStatus(request *restful.Request, response *restful.Response) { +// ReportStatus report the status of task +func ReportStatus(request *restful.Request, response *restful.Response) { resp := types.NodeTaskResponse{} taskID := request.PathParameter("taskID") taskType := request.PathParameter("taskType") diff --git a/cloud/pkg/cloudhub/servers/httpserver/upgrade.go b/cloud/pkg/cloudhub/servers/httpserver/nodetask/upgrade.go index 2dc343714..f0f44e7aa 100644 --- a/cloud/pkg/cloudhub/servers/httpserver/upgrade.go +++ b/cloud/pkg/cloudhub/servers/httpserver/nodetask/upgrade.go @@ -3,15 +3,16 @@ Copyright 2022 The KubeEdge 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 + + 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 httpserver +package nodetask import ( "encoding/json" @@ -37,8 +38,8 @@ const ( UpgradeFailedRollbackFailed = "upgrade_failed_rollback_failed" ) -// upgradeEdge upgrade the edgecore version -func upgradeEdge(request *restful.Request, response *restful.Response) { +// UpgradeEdge upgrade the edgecore version +func UpgradeEdge(request *restful.Request, response *restful.Response) { resp := commontypes.NodeUpgradeJobResponse{} taskID := resp.UpgradeID diff --git a/cloud/pkg/cloudhub/servers/httpserver/pre_server.go b/cloud/pkg/cloudhub/servers/httpserver/pre_server.go new file mode 100644 index 000000000..cf6641a86 --- /dev/null +++ b/cloud/pkg/cloudhub/servers/httpserver/pre_server.go @@ -0,0 +1,233 @@ +/* +Copyright 2024 The KubeEdge 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 httpserver + +import ( + "context" + "crypto/x509" + "fmt" + "net" + "time" + + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + certutil "k8s.io/client-go/util/cert" + "k8s.io/klog/v2" + + hubconfig "github.com/kubeedge/kubeedge/cloud/pkg/cloudhub/config" + "github.com/kubeedge/kubeedge/cloud/pkg/common/client" + "github.com/kubeedge/kubeedge/common/constants" + "github.com/kubeedge/kubeedge/pkg/security/certs" + "github.com/kubeedge/kubeedge/pkg/security/token" +) + +const ( + TokenSecretName string = "tokensecret" + TokenDataName string = "tokendata" + CaSecretName string = "casecret" + CloudCoreSecretName string = "cloudcoresecret" + CaDataName string = "cadata" + CaKeyDataName string = "cakeydata" + CloudCoreCertName string = "cloudcoredata" + CloudCoreKeyDataName string = "cloudcorekeydata" +) + +// PrepareAllCerts check whether the certificates exist in the local directory, +// and then check whether certificates exist in the secret, generate if they don't exist +func PrepareAllCerts(ctx context.Context) error { + if err := createCAToSecret(ctx); err != nil { + return err + } + return createCertsToSecret(ctx) +} + +func createCAToSecret(ctx context.Context) error { + var caDER, keyDER []byte + // Check whether the ca exists in the local directory + if hubconfig.Config.Ca == nil && hubconfig.Config.CaKey == nil { + klog.Info("Ca and CaKey don't exist in local directory, and will read from the secret") + + // Check whether the ca exists in the secret + caSecret, err := client.GetSecret(ctx, CaSecretName, constants.SystemNamespace) + if err != nil { + klog.Info("Ca and CaKey don't exist in the secret, and will be created by CloudCore") + h := certs.GetCAHandler(certs.CAHandlerTypeX509) + pk, err := h.GenPrivateKey() + if err != nil { + return err + } + + caPem, err := h.NewSelfSigned(pk) + if err != nil { + return fmt.Errorf("failed to create Certificate Authority, error: %v", err) + } + caDER = caPem.Bytes + keyDER = pk.DER() + } else { + caDER = caSecret.Data[CaDataName] + keyDER = caSecret.Data[CaKeyDataName] + } + + hubconfig.Config.UpdateCA(caDER, keyDER) + } else { + // HubConfig has been initialized + caDER = hubconfig.Config.Ca + keyDER = hubconfig.Config.CaKey + } + + if err := client.SaveSecret(ctx, createCaSecret(caDER, keyDER), constants.SystemNamespace); err != nil { + return fmt.Errorf("failed to create ca to secrets, error: %v", err) + } + + return nil +} + +func createCertsToSecret(ctx context.Context) error { + const year100 = time.Hour * 24 * 364 * 100 + var certDER, keyDER []byte + + // Check whether the CloudCore certificates exist in the local directory + if hubconfig.Config.Key == nil && hubconfig.Config.Cert == nil { + klog.Infof("CloudCoreCert and key don't exist in local directory, and will read from the secret") + + // Check whether the CloudCore certificates exist in the secret + cloudSecret, err := client.GetSecret(ctx, CloudCoreSecretName, constants.SystemNamespace) + if err != nil { + klog.Info("CloudCoreCert and key don't exist in the secret, and will be signed by CA") + + ips := make([]net.IP, 0, len(hubconfig.Config.AdvertiseAddress)) + for _, addr := range hubconfig.Config.AdvertiseAddress { + ips = append(ips, net.ParseIP(addr)) + } + h := certs.GetHandler(certs.HandlerTypeX509) + + keywrap, err := h.GenPrivateKey() + if err != nil { + return fmt.Errorf("faield to generate the private key, err: %v", err) + } + key, err := keywrap.Signer() + if err != nil { + return fmt.Errorf("failed parse the priavte key, err: %v", err) + } + + opts := certs.SignCertsOptionsWithCA(certutil.Config{ + CommonName: constants.ProjectName, + Organization: []string{constants.ProjectName}, + Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + AltNames: certutil.AltNames{ + DNSNames: hubconfig.Config.DNSNames, + IPs: ips, + }, + }, hubconfig.Config.Ca, hubconfig.Config.CaKey, key.Public(), year100) + certPEM, err := h.SignCerts(opts) + if err != nil { + return fmt.Errorf("failed to sign the certificate, err: %v", err) + } + keyDER = keywrap.DER() + certDER = certPEM.Bytes + } else { + certDER = cloudSecret.Data[CloudCoreCertName] + keyDER = cloudSecret.Data[CloudCoreKeyDataName] + } + + hubconfig.Config.UpdateCerts(certDER, keyDER) + } else { + // HubConfig has been initialized + certDER = hubconfig.Config.Cert + keyDER = hubconfig.Config.Key + } + + if err := client.SaveSecret(ctx, createCloudCoreSecret(certDER, keyDER), constants.SystemNamespace); err != nil { + return fmt.Errorf("failed to save CloudCore cert and key to secret, error: %v", err) + } + + return nil +} + +// GenerateAndRefreshToken creates a token and save it to secret, then craete a timer to refresh the token. +func GenerateAndRefreshToken(ctx context.Context) error { + caHashToken, err := token.Create(hubconfig.Config.Ca, hubconfig.Config.CaKey, + hubconfig.Config.CloudHub.TokenRefreshDuration) + if err != nil { + return fmt.Errorf("failed to generate the token for edgecore register, err: %v", err) + } + // save caHashAndToken to secret + if err := client.SaveSecret(ctx, createTokenSecret([]byte(caHashToken)), constants.SystemNamespace); err != nil { + return fmt.Errorf("failed to create tokenSecret, err: %v", err) + } + + t := time.NewTicker(time.Hour * hubconfig.Config.CloudHub.TokenRefreshDuration) + go func() { + for { + select { + case <-t.C: + caHashToken, err = token.Create(hubconfig.Config.Ca, hubconfig.Config.CaKey, + hubconfig.Config.CloudHub.TokenRefreshDuration) + if err != nil { + klog.Errorf("failed to refresh the token for edgecore register, err: %v", err) + } + case <-ctx.Done(): + break + } + } + }() + klog.Info("Succeed to creating token") + return nil +} + +func createTokenSecret(caHashAndToken []byte) *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: TokenSecretName, + Namespace: constants.SystemNamespace, + }, + Data: map[string][]byte{ + TokenDataName: caHashAndToken, + }, + StringData: map[string]string{}, + Type: "Opaque", + } +} + +func createCaSecret(certDER, key []byte) *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: CaSecretName, + Namespace: constants.SystemNamespace, + }, + Data: map[string][]byte{ + CaDataName: certDER, + CaKeyDataName: key, + }, + StringData: map[string]string{}, + Type: "Opaque", + } +} + +func createCloudCoreSecret(certDER, key []byte) *corev1.Secret { + return &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: CloudCoreSecretName, + Namespace: constants.SystemNamespace, + }, + Data: map[string][]byte{ + CloudCoreCertName: certDER, + CloudCoreKeyDataName: key, + }, + StringData: map[string]string{}, + Type: "Opaque", + } +} diff --git a/cloud/pkg/cloudhub/servers/httpserver/resps/fake.go b/cloud/pkg/cloudhub/servers/httpserver/resps/fake.go new file mode 100644 index 000000000..3b219a1fd --- /dev/null +++ b/cloud/pkg/cloudhub/servers/httpserver/resps/fake.go @@ -0,0 +1,21 @@ +package resps + +import "net/http" + +type FakeResponseWriter struct { + payload []byte + code int +} + +func (f FakeResponseWriter) Header() http.Header { + return make(map[string][]string) +} + +func (f *FakeResponseWriter) Write(payload []byte) (int, error) { + f.payload = payload + return -1, nil +} + +func (f *FakeResponseWriter) WriteHeader(code int) { + f.code = code +} diff --git a/cloud/pkg/cloudhub/servers/httpserver/resps/response.go b/cloud/pkg/cloudhub/servers/httpserver/resps/response.go new file mode 100644 index 000000000..af4a3e3f9 --- /dev/null +++ b/cloud/pkg/cloudhub/servers/httpserver/resps/response.go @@ -0,0 +1,28 @@ +package resps + +import ( + "net/http" + + "k8s.io/klog/v2" +) + +func Error(w http.ResponseWriter, code int, err error) { + ErrorMessage(w, code, err.Error()) +} + +func ErrorMessage(w http.ResponseWriter, code int, msg string) { + if code == 0 { + code = http.StatusInternalServerError + } + w.WriteHeader(code) + if _, err := w.Write([]byte(msg)); err != nil { + klog.Errorf("failed to write a error messge to the response, err: %v", err) + } +} + +func OK(w http.ResponseWriter, body []byte) { + w.WriteHeader(http.StatusOK) + if _, err := w.Write(body); err != nil { + klog.Errorf("failed to write a payload to the response, err: %v", err) + } +} diff --git a/cloud/pkg/cloudhub/servers/httpserver/resps/response_test.go b/cloud/pkg/cloudhub/servers/httpserver/resps/response_test.go new file mode 100644 index 000000000..b22e01593 --- /dev/null +++ b/cloud/pkg/cloudhub/servers/httpserver/resps/response_test.go @@ -0,0 +1,69 @@ +package resps + +import ( + "errors" + "net/http" + "reflect" + "testing" +) + +func TestErrorMessage(t *testing.T) { + cases := []struct { + name string + code int + message string + err error + }{ + { + name: "default error code", + message: "test message", + }, + { + name: "specified error code", + code: http.StatusBadRequest, + message: "bad request", + }, + { + name: "specified error", + err: errors.New("new error"), + }, + } + + for _, c := range cases { + t.Run(c.name, func(t *testing.T) { + var wantCode int + if c.code == 0 { + wantCode = http.StatusInternalServerError + } else { + wantCode = c.code + } + w := new(FakeResponseWriter) + var wantMsg string + if c.err != nil { + wantMsg = c.err.Error() + Error(w, c.code, c.err) + } else { + wantMsg = c.message + ErrorMessage(w, c.code, c.message) + } + if w.code != wantCode { + t.Fatalf("want status code is %d, actual is %d", wantCode, w.code) + } + if msg := string(w.payload); msg != wantMsg { + t.Fatalf("want error message is %s, actual is %s", wantMsg, msg) + } + }) + } +} + +func TestOK(t *testing.T) { + payload := []byte("test data") + w := new(FakeResponseWriter) + OK(w, payload) + if w.code != http.StatusOK { + t.Fatalf("want status code is %d, actual is %d", http.StatusOK, w.code) + } + if !reflect.DeepEqual(w.payload, payload) { + t.Fatalf("want error message is %s, actual is %s", string(payload), string(w.payload)) + } +} diff --git a/cloud/pkg/cloudhub/servers/httpserver/secretsutil.go b/cloud/pkg/cloudhub/servers/httpserver/secretsutil.go deleted file mode 100644 index 38026324a..000000000 --- a/cloud/pkg/cloudhub/servers/httpserver/secretsutil.go +++ /dev/null @@ -1,112 +0,0 @@ -package httpserver - -import ( - "context" - "fmt" - - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/errors" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/client-go/kubernetes" - - "github.com/kubeedge/kubeedge/cloud/pkg/common/client" - "github.com/kubeedge/kubeedge/common/constants" -) - -const ( - TokenSecretName string = "tokensecret" - TokenDataName string = "tokendata" - CaSecretName string = "casecret" - CloudCoreSecretName string = "cloudcoresecret" - CaDataName string = "cadata" - CaKeyDataName string = "cakeydata" - CloudCoreCertName string = "cloudcoredata" - CloudCoreKeyDataName string = "cloudcorekeydata" -) - -func GetSecret(secretName string, ns string) (*corev1.Secret, error) { - cli := client.GetKubeClient() - return cli.CoreV1().Secrets(ns).Get(context.Background(), secretName, metav1.GetOptions{}) -} - -// CreateSecret creates a secret -func CreateSecret(secret *corev1.Secret, ns string) error { - cli := client.GetKubeClient() - if err := CreateNamespaceIfNeeded(cli, ns); err != nil { - return fmt.Errorf("failed to create Namespace kubeedge, error: %v", err) - } - if _, err := cli.CoreV1().Secrets(ns).Create(context.Background(), secret, metav1.CreateOptions{}); err != nil { - if errors.IsAlreadyExists(err) { - if _, err := cli.CoreV1().Secrets(ns).Update(context.Background(), secret, metav1.UpdateOptions{}); err != nil { - return fmt.Errorf("failed to update the secret, namespace: %s, name: %s, err: %v", ns, secret.Name, err) - } - } else { - return fmt.Errorf("failed to create the secret, namespace: %s, name: %s, err: %v", ns, secret.Name, err) - } - } - return nil -} - -func CreateTokenSecret(caHashAndToken []byte) error { - token := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: TokenSecretName, - Namespace: constants.SystemNamespace, - }, - Data: map[string][]byte{ - TokenDataName: caHashAndToken, - }, - StringData: map[string]string{}, - Type: "Opaque", - } - return CreateSecret(token, constants.SystemNamespace) -} - -func CreateCaSecret(certDER, key []byte) error { - caSecret := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: CaSecretName, - Namespace: constants.SystemNamespace, - }, - Data: map[string][]byte{ - CaDataName: certDER, - CaKeyDataName: key, - }, - StringData: map[string]string{}, - Type: "Opaque", - } - return CreateSecret(caSecret, constants.SystemNamespace) -} - -func CreateCloudCoreSecret(certDER, key []byte) error { - cloudCoreCert := &corev1.Secret{ - ObjectMeta: metav1.ObjectMeta{ - Name: CloudCoreSecretName, - Namespace: constants.SystemNamespace, - }, - Data: map[string][]byte{ - CloudCoreCertName: certDER, - CloudCoreKeyDataName: key, - }, - StringData: map[string]string{}, - Type: "Opaque", - } - return CreateSecret(cloudCoreCert, constants.SystemNamespace) -} - -func CreateNamespaceIfNeeded(cli kubernetes.Interface, ns string) error { - c := cli.CoreV1() - if _, err := c.Namespaces().Get(context.Background(), ns, metav1.GetOptions{}); err == nil { - return nil - } - newNs := &corev1.Namespace{ - ObjectMeta: metav1.ObjectMeta{ - Name: ns, - }, - } - _, err := c.Namespaces().Create(context.Background(), newNs, metav1.CreateOptions{}) - if err != nil && errors.IsAlreadyExists(err) { - err = nil - } - return err -} diff --git a/cloud/pkg/cloudhub/servers/httpserver/server.go b/cloud/pkg/cloudhub/servers/httpserver/server.go index 8afa1a61a..e01f53644 100644 --- a/cloud/pkg/cloudhub/servers/httpserver/server.go +++ b/cloud/pkg/cloudhub/servers/httpserver/server.go @@ -5,7 +5,7 @@ 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 + 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, @@ -13,50 +13,34 @@ 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 httpserver import ( - "crypto" - "crypto/ecdsa" "crypto/tls" - "crypto/x509" - "crypto/x509/pkix" - "encoding/json" "encoding/pem" "fmt" - "io" "net/http" - "strings" "github.com/emicklei/go-restful" - "github.com/golang-jwt/jwt" certutil "k8s.io/client-go/util/cert" - "k8s.io/klog/v2" hubconfig "github.com/kubeedge/kubeedge/cloud/pkg/cloudhub/config" + certshandler "github.com/kubeedge/kubeedge/cloud/pkg/cloudhub/servers/httpserver/certificate" + nodetaskhandler "github.com/kubeedge/kubeedge/cloud/pkg/cloudhub/servers/httpserver/nodetask" "github.com/kubeedge/kubeedge/common/constants" - "github.com/kubeedge/kubeedge/common/types" - "github.com/kubeedge/kubeedge/pkg/security/token" ) // StartHTTPServer starts the http service -func StartHTTPServer() { +func StartHTTPServer() error { serverContainer := restful.NewContainer() - ws := new(restful.WebService) - ws.Path("/") - ws.Route(ws.GET(constants.DefaultCertURL).To(edgeCoreClientCert)) - ws.Route(ws.GET(constants.DefaultCAURL).To(getCA)) - ws.Route(ws.POST(constants.DefaultNodeUpgradeURL).To(upgradeEdge)) - ws.Route(ws.POST(constants.DefaultTaskStateReportURL).To(reportTaskStatus)) - serverContainer.Add(ws) - + serverContainer.Add(routes()) addr := fmt.Sprintf("%s:%d", hubconfig.Config.HTTPS.Address, hubconfig.Config.HTTPS.Port) - - cert, err := tls.X509KeyPair(pem.EncodeToMemory(&pem.Block{Type: certutil.CertificateBlockType, Bytes: hubconfig.Config.Cert}), pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: hubconfig.Config.Key})) - + cert, err := tls.X509KeyPair( + pem.EncodeToMemory(&pem.Block{Type: certutil.CertificateBlockType, Bytes: hubconfig.Config.Cert}), + pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: hubconfig.Config.Key}), + ) if err != nil { - klog.Exit(err) + return fmt.Errorf("failed to create a x509 tls certificate") } server := &http.Server{ @@ -67,267 +51,15 @@ func StartHTTPServer() { ClientAuth: tls.RequestClientCert, }, } - klog.Exit(server.ListenAndServeTLS("", "")) -} - -// getCA returns the caCertDER -func getCA(_ *restful.Request, response *restful.Response) { - caCertDER := hubconfig.Config.Ca - if _, err := response.Write(caCertDER); err != nil { - klog.Errorf("failed to write caCertDER, err: %v", err) - } -} - -// EncodeCertPEM returns PEM-encoded certificate data -func EncodeCertPEM(cert *x509.Certificate) []byte { - block := pem.Block{ - Type: certutil.CertificateBlockType, - Bytes: cert.Raw, - } - return pem.EncodeToMemory(&block) -} - -// edgeCoreClientCert will verify the certificate of EdgeCore or token then create EdgeCoreCert and return it -func edgeCoreClientCert(request *restful.Request, response *restful.Response) { - nodeName := request.Request.Header.Get(types.NodeNameKey) - if cert := request.Request.TLS.PeerCertificates; len(cert) > 0 { - if err := verifyCert(cert[0], nodeName); err != nil { - klog.Errorf("failed to sign the certificate for edgenode: %s, failed to verify the certificate", nodeName) - response.WriteHeader(http.StatusUnauthorized) - if _, err := response.Write([]byte(err.Error())); err != nil { - klog.Errorf("failed to write response, err: %v", err) - } - } else { - signEdgeCert(response, request.Request) - } - return - } - if verifyAuthorization(response, request.Request) { - signEdgeCert(response, request.Request) - } else { - klog.Errorf("failed to sign the certificate for edgenode: %s, invalid token", nodeName) - } -} - -// verifyCert verifies the edge certificate by CA certificate when edge certificates rotate. -func verifyCert(cert *x509.Certificate, nodeName string) error { - roots := x509.NewCertPool() - ok := roots.AppendCertsFromPEM(pem.EncodeToMemory(&pem.Block{Type: certutil.CertificateBlockType, Bytes: hubconfig.Config.Ca})) - if !ok { - return fmt.Errorf("failed to parse root certificate") - } - opts := x509.VerifyOptions{ - Roots: roots, - KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, - } - if _, err := cert.Verify(opts); err != nil { - return fmt.Errorf("failed to verify edge certificate: %v", err) - } - return verifyCertSubject(cert, nodeName) -} - -func verifyCertSubject(cert *x509.Certificate, nodeName string) error { - if cert.Subject.Organization[0] == "KubeEdge" && cert.Subject.CommonName == "kubeedge.io" { - // In order to maintain compatibility with older versions of certificates - // this condition will be removed in KubeEdge v1.18. - return nil - } - commonName := fmt.Sprintf("system:node:%s", nodeName) - if cert.Subject.Organization[0] == "system:nodes" && cert.Subject.CommonName == commonName { - return nil - } - return fmt.Errorf("request node name is not match with the certificate") -} - -// verifyAuthorization verifies the token from EdgeCore CSR -func verifyAuthorization(w http.ResponseWriter, r *http.Request) bool { - authorizationHeader := r.Header.Get("authorization") - if authorizationHeader == "" { - klog.Warning("token validation failure, token is empty") - w.WriteHeader(http.StatusUnauthorized) - if _, err := w.Write([]byte("Invalid authorization token")); err != nil { - klog.Errorf("failed to write http response, err: %v", err) - } - return false - } - bearerToken := strings.Split(authorizationHeader, " ") - if len(bearerToken) != 2 { - klog.Warning("token validation failure, token cannot be splited") - w.WriteHeader(http.StatusUnauthorized) - if _, err := w.Write([]byte("Invalid authorization token")); err != nil { - klog.Errorf("failed to write http response, err: %v", err) - } - return false - } - valid, err := token.Verify(bearerToken[1], hubconfig.Config.CaKey) - if err != nil { - klog.Warning("token validation failure, ", err.Error()) - if err == jwt.ErrSignatureInvalid { - w.WriteHeader(http.StatusUnauthorized) - if _, err := w.Write([]byte("Invalid authorization token")); err != nil { - klog.Errorf("Write body error %v", err) - } - return false - } - w.WriteHeader(http.StatusBadRequest) - if _, err := w.Write([]byte("Invalid authorization token")); err != nil { - klog.Errorf("Write body error %v", err) - } - return false - } - if !valid { - klog.Warning("token validation failure, valid is false") - w.WriteHeader(http.StatusUnauthorized) - if _, err := w.Write([]byte("Invalid authorization token")); err != nil { - klog.Errorf("Write body error %v", err) - } - return false - } - return true + return server.ListenAndServeTLS("", "") } -// signEdgeCert signs the CSR from EdgeCore -func signEdgeCert(w http.ResponseWriter, r *http.Request) { - r.Body = http.MaxBytesReader(w, r.Body, constants.MaxRespBodyLength) - csrContent, err := io.ReadAll(r.Body) - if err != nil { - klog.Errorf("fail to read file when signing the cert for edgenode:%s! error:%v", r.Header.Get(types.NodeNameKey), err) - return - } - csr, err := x509.ParseCertificateRequest(csrContent) - if err != nil { - klog.Errorf("fail to ParseCertificateRequest of edgenode: %s! error:%v", r.Header.Get(types.NodeNameKey), err) - return - } - usagesStr := r.Header.Get("ExtKeyUsages") - var usages []x509.ExtKeyUsage - if usagesStr == "" { - usages = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth} - } else { - err := json.Unmarshal([]byte(usagesStr), &usages) - if err != nil { - klog.Errorf("unmarshal http header ExtKeyUsages fail, err: %v", err) - return - } - } - klog.V(4).Infof("receive sign crt request, ExtKeyUsages: %v", usages) - clientCertDER, err := signCerts(csr.Subject, csr.PublicKey, usages) - if err != nil { - klog.Errorf("fail to signCerts for edgenode:%s! error:%v", r.Header.Get(types.NodeNameKey), err) - return - } - - if _, err := w.Write(clientCertDER); err != nil { - klog.Errorf("write error %v", err) - } -} - -// signCerts will create a certificate for EdgeCore -func signCerts(subInfo pkix.Name, pbKey crypto.PublicKey, usages []x509.ExtKeyUsage) ([]byte, error) { - cfgs := &certutil.Config{ - CommonName: subInfo.CommonName, - Organization: subInfo.Organization, - Usages: usages, - } - clientKey := pbKey - - ca := hubconfig.Config.Ca - caCert, err := x509.ParseCertificate(ca) - if err != nil { - return nil, fmt.Errorf("unable to ParseCertificate: %v", err) - } - - var caKey crypto.Signer - caKeyDER := hubconfig.Config.CaKey - caKey, err = x509.ParseECPrivateKey(caKeyDER) - if err != nil { - caKey, err = x509.ParsePKCS1PrivateKey(caKeyDER) - if err != nil { - return nil, fmt.Errorf("unable to Parse PrivateKey: %v", err) - } - } - - edgeCertSigningDuration := hubconfig.Config.CloudHub.EdgeCertSigningDuration - certDER, err := NewCertFromCa(cfgs, caCert, clientKey, caKey, edgeCertSigningDuration) //crypto.Signer(caKey) - if err != nil { - return nil, fmt.Errorf("unable to NewCertFromCa: %v", err) - } - - return certDER, err -} - -// PrepareAllCerts check whether the certificates exist in the local directory, -// and then check whether certificates exist in the secret, generate if they don't exist -func PrepareAllCerts() error { - // Check whether the ca exists in the local directory - if hubconfig.Config.Ca == nil && hubconfig.Config.CaKey == nil { - var caDER, caKeyDER []byte - klog.Info("Ca and CaKey don't exist in local directory, and will read from the secret") - // Check whether the ca exists in the secret - caSecret, err := GetSecret(CaSecretName, constants.SystemNamespace) - if err != nil { - klog.Info("Ca and CaKey don't exist in the secret, and will be created by CloudCore") - var caKey crypto.Signer - caDER, caKey, err = NewCertificateAuthorityDer() - if err != nil { - klog.Errorf("failed to create Certificate Authority, error: %v", err) - return err - } - - caKeyDER, err = x509.MarshalECPrivateKey(caKey.(*ecdsa.PrivateKey)) - if err != nil { - klog.Errorf("failed to convert an EC private key to SEC 1, ASN.1 DER form, error: %v", err) - return err - } - - err = CreateCaSecret(caDER, caKeyDER) - if err != nil { - klog.Errorf("failed to create ca to secrets, error: %v", err) - return err - } - } else { - caDER = caSecret.Data[CaDataName] - caKeyDER = caSecret.Data[CaKeyDataName] - } - UpdateConfig(caDER, caKeyDER, nil, nil) - } else { - // HubConfig has been initialized - if err := CreateCaSecret(hubconfig.Config.Ca, hubconfig.Config.CaKey); err != nil { - klog.Errorf("failed to save ca and key to the secret, error: %v", err) - return err - } - } - - // Check whether the CloudCore certificates exist in the local directory - if hubconfig.Config.Key == nil && hubconfig.Config.Cert == nil { - klog.Infof("CloudCoreCert and key don't exist in local directory, and will read from the secret") - // Check whether the CloudCore certificates exist in the secret - var certDER, keyDER []byte - cloudSecret, err := GetSecret(CloudCoreSecretName, constants.SystemNamespace) - if err != nil { - klog.Info("CloudCoreCert and key don't exist in the secret, and will be signed by CA") - certDER, keyDER, err = SignCerts() - if err != nil { - klog.Errorf("failed to sign a certificate, error: %v", err) - return err - } - - err = CreateCloudCoreSecret(certDER, keyDER) - if err != nil { - klog.Errorf("failed to save CloudCore cert and key to secret, error: %v", err) - return err - } - } else { - certDER = cloudSecret.Data[CloudCoreCertName] - keyDER = cloudSecret.Data[CloudCoreKeyDataName] - } - UpdateConfig(nil, nil, certDER, keyDER) - } else { - // HubConfig has been initialized - if err := CreateCloudCoreSecret(hubconfig.Config.Cert, hubconfig.Config.Key); err != nil { - klog.Errorf("failed to save CloudCore cert to secret, error: %v", err) - return err - } - } - return nil +func routes() *restful.WebService { + ws := new(restful.WebService) + ws.Path("/") + ws.Route(ws.GET(constants.DefaultCertURL).To(certshandler.EdgeCoreClientCert)) + ws.Route(ws.GET(constants.DefaultCAURL).To(certshandler.GetCA)) + ws.Route(ws.POST(constants.DefaultNodeUpgradeURL).To(nodetaskhandler.UpgradeEdge)) + ws.Route(ws.POST(constants.DefaultTaskStateReportURL).To(nodetaskhandler.ReportStatus)) + return ws } diff --git a/cloud/pkg/cloudhub/servers/httpserver/signcerts.go b/cloud/pkg/cloudhub/servers/httpserver/signcerts.go deleted file mode 100644 index 93d309eac..000000000 --- a/cloud/pkg/cloudhub/servers/httpserver/signcerts.go +++ /dev/null @@ -1,90 +0,0 @@ -/* -Copyright 2020 The KubeEdge 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 httpserver - -import ( - "context" - "crypto/x509" - "fmt" - "net" - "time" - - certutil "k8s.io/client-go/util/cert" - "k8s.io/klog/v2" - - hubconfig "github.com/kubeedge/kubeedge/cloud/pkg/cloudhub/config" - "github.com/kubeedge/kubeedge/common/constants" - "github.com/kubeedge/kubeedge/pkg/security/token" -) - -// SignCerts creates server's certificate and key -func SignCerts() ([]byte, []byte, error) { - cfg := &certutil.Config{ - CommonName: constants.ProjectName, - Organization: []string{constants.ProjectName}, - Usages: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, - AltNames: certutil.AltNames{ - DNSNames: hubconfig.Config.DNSNames, - IPs: getIps(hubconfig.Config.AdvertiseAddress), - }, - } - - certDER, keyDER, err := NewCloudCoreCertDERandKey(cfg) - if err != nil { - return nil, nil, err - } - - return certDER, keyDER, nil -} - -func getIps(advertiseAddress []string) (Ips []net.IP) { - for _, addr := range advertiseAddress { - Ips = append(Ips, net.ParseIP(addr)) - } - return -} - -// GenerateAndRefresh creates a token and save it to secret, then craete a timer to refresh the token. -func GenerateAndRefresh(ctx context.Context) error { - caHashToken, err := token.Create(hubconfig.Config.Ca, hubconfig.Config.CaKey, - hubconfig.Config.CloudHub.TokenRefreshDuration) - if err != nil { - return fmt.Errorf("failed to generate the token for edgecore register, err: %v", err) - } - // save caHashAndToken to secret - err = CreateTokenSecret([]byte(caHashToken)) - if err != nil { - return fmt.Errorf("failed to create tokenSecret, err: %v", err) - } - - t := time.NewTicker(time.Hour * hubconfig.Config.CloudHub.TokenRefreshDuration) - go func() { - for { - select { - case <-t.C: - caHashToken, err = token.Create(hubconfig.Config.Ca, hubconfig.Config.CaKey, - hubconfig.Config.CloudHub.TokenRefreshDuration) - if err != nil { - klog.Errorf("failed to refresh the token for edgecore register, err: %v", err) - } - case <-ctx.Done(): - break - } - } - }() - klog.Info("Succeed to creating token") - return nil -} diff --git a/cloud/pkg/cloudhub/servers/httpserver/updateconfig.go b/cloud/pkg/cloudhub/servers/httpserver/updateconfig.go deleted file mode 100644 index a7a08c73b..000000000 --- a/cloud/pkg/cloudhub/servers/httpserver/updateconfig.go +++ /dev/null @@ -1,20 +0,0 @@ -package httpserver - -import ( - hubconfig "github.com/kubeedge/kubeedge/cloud/pkg/cloudhub/config" -) - -func UpdateConfig(ca, caKey, cert, key []byte) { - if ca != nil { - hubconfig.Config.Ca = ca - } - if caKey != nil { - hubconfig.Config.CaKey = caKey - } - if cert != nil { - hubconfig.Config.Cert = cert - } - if key != nil { - hubconfig.Config.Key = key - } -} diff --git a/cloud/pkg/cloudhub/servers/httpserver/updateconfig_test.go b/cloud/pkg/cloudhub/servers/httpserver/updateconfig_test.go deleted file mode 100644 index 3c5d83b0a..000000000 --- a/cloud/pkg/cloudhub/servers/httpserver/updateconfig_test.go +++ /dev/null @@ -1,27 +0,0 @@ -package httpserver - -import ( - "reflect" - "testing" - - "github.com/kubeedge/kubeedge/cloud/pkg/cloudhub/config" -) - -func TestUpdateConfig(t *testing.T) { - UpdateConfig([]byte("ca"), nil, nil, nil) - if !reflect.DeepEqual(config.Config.Ca, []byte("ca")) { - t.Errorf("UpdateConfig(): got %v, want %v", config.Config.Ca, []byte("ca")) - } - UpdateConfig(nil, []byte("caKey"), nil, nil) - if !reflect.DeepEqual(config.Config.CaKey, []byte("caKey")) { - t.Errorf("UpdateConfig(): got %v, want %v", config.Config.CaKey, []byte("caKey")) - } - UpdateConfig(nil, nil, []byte("cert"), nil) - if !reflect.DeepEqual(config.Config.Cert, []byte("cert")) { - t.Errorf("UpdateConfig(): got %v, want %v", config.Config.Cert, []byte("cert")) - } - UpdateConfig(nil, nil, nil, []byte("key")) - if !reflect.DeepEqual(config.Config.Key, []byte("key")) { - t.Errorf("UpdateConfig(): got %v, want %v", config.Config.Key, []byte("key")) - } -} diff --git a/cloud/pkg/common/client/client.go b/cloud/pkg/common/client/client.go index 19279ceaf..4d1d8c305 100644 --- a/cloud/pkg/common/client/client.go +++ b/cloud/pkg/common/client/client.go @@ -5,7 +5,7 @@ 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 + 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, @@ -13,7 +13,6 @@ 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 client import ( diff --git a/cloud/pkg/common/client/namespace.go b/cloud/pkg/common/client/namespace.go new file mode 100644 index 000000000..8626c96e2 --- /dev/null +++ b/cloud/pkg/common/client/namespace.go @@ -0,0 +1,42 @@ +/* +Copyright 2024 The KubeEdge 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 client + +import ( + "context" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func CreateNamespaceIfNeeded(ctx context.Context, ns string) error { + cli := GetKubeClient() + c := cli.CoreV1() + if _, err := c.Namespaces().Get(ctx, ns, metav1.GetOptions{}); err == nil { + return nil + } + newNs := &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{ + Name: ns, + }, + } + _, err := c.Namespaces().Create(ctx, newNs, metav1.CreateOptions{}) + if err != nil && errors.IsAlreadyExists(err) { + err = nil + } + return err +} diff --git a/cloud/pkg/common/client/secret.go b/cloud/pkg/common/client/secret.go new file mode 100644 index 000000000..4e572f043 --- /dev/null +++ b/cloud/pkg/common/client/secret.go @@ -0,0 +1,49 @@ +/* +Copyright 2024 The KubeEdge 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 client + +import ( + "context" + "fmt" + + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/errors" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// GetSecret ... +func GetSecret(ctx context.Context, secretName string, ns string) (*corev1.Secret, error) { + cli := GetKubeClient() + return cli.CoreV1().Secrets(ns).Get(ctx, secretName, metav1.GetOptions{}) +} + +// saveSecret creates a secret when it does not exist, otherwise updates it. +func SaveSecret(ctx context.Context, secret *corev1.Secret, ns string) error { + cli := GetKubeClient() + if err := CreateNamespaceIfNeeded(ctx, ns); err != nil { + return fmt.Errorf("failed to create Namespace kubeedge, error: %v", err) + } + if _, err := cli.CoreV1().Secrets(ns).Create(ctx, secret, metav1.CreateOptions{}); err != nil { + if errors.IsAlreadyExists(err) { + if _, err := cli.CoreV1().Secrets(ns).Update(ctx, secret, metav1.UpdateOptions{}); err != nil { + return fmt.Errorf("failed to update the secret, namespace: %s, name: %s, err: %v", ns, secret.Name, err) + } + } else { + return fmt.Errorf("failed to create the secret, namespace: %s, name: %s, err: %v", ns, secret.Name, err) + } + } + return nil +} diff --git a/common/types/http.go b/common/types/http.go index c001b44ac..e1faa7f6c 100644 --- a/common/types/http.go +++ b/common/types/http.go @@ -18,6 +18,7 @@ type HTTPResponse struct { } const ( - AuthorizationKey = "Authorization" - NodeNameKey = "NodeName" + HeaderAuthorization = "Authorization" + HeaderNodeName = "NodeName" + HeaderExtKeyUsages = "ExtKeyUsages" ) diff --git a/edge/pkg/edgehub/certificate/certmanager.go b/edge/pkg/edgehub/certificate/certmanager.go index 21f0abaa8..c1130a7a2 100644 --- a/edge/pkg/edgehub/certificate/certmanager.go +++ b/edge/pkg/edgehub/certificate/certmanager.go @@ -2,9 +2,6 @@ package certificate import ( "bytes" - "crypto/ecdsa" - "crypto/elliptic" - "crypto/rand" "crypto/tls" "crypto/x509" "crypto/x509/pkix" @@ -18,12 +15,14 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/util/cert" + certutil "k8s.io/client-go/util/cert" + "k8s.io/client-go/util/keyutil" "k8s.io/klog/v2" "github.com/kubeedge/kubeedge/common/constants" - "github.com/kubeedge/kubeedge/edge/pkg/edgehub/common/certutil" "github.com/kubeedge/kubeedge/edge/pkg/edgehub/common/http" "github.com/kubeedge/kubeedge/pkg/apis/componentconfig/edgecore/v1alpha2" + "github.com/kubeedge/kubeedge/pkg/security/certs" "github.com/kubeedge/kubeedge/pkg/security/token" ) @@ -44,7 +43,6 @@ var CleanupTokenChan = make(chan struct{}, 1) type CertManager struct { RotateCertificates bool NodeName string - CR *x509.CertificateRequest caFile string certFile string @@ -61,20 +59,10 @@ type CertManager struct { // NewCertManager creates a CertManager for edge certificate management according to EdgeHub config func NewCertManager(edgehub v1alpha2.EdgeHub, nodename string) CertManager { - certReq := &x509.CertificateRequest{ - Subject: pkix.Name{ - Country: []string{"CN"}, - Organization: []string{"system:nodes"}, - Locality: []string{"Hangzhou"}, - Province: []string{"Zhejiang"}, - CommonName: fmt.Sprintf("system:node:%s", nodename), - }, - } return CertManager{ RotateCertificates: edgehub.RotateCertificates, NodeName: nodename, token: edgehub.Token, - CR: certReq, caFile: edgehub.TLSCAFile, certFile: edgehub.TLSCertFile, keyFile: edgehub.TLSPrivateKeyFile, @@ -87,11 +75,10 @@ func NewCertManager(edgehub v1alpha2.EdgeHub, nodename string) CertManager { // Start starts the CertManager func (cm *CertManager) Start() { - _, err := cm.getCurrent() - if err != nil { - err = cm.applyCerts() - if err != nil { - klog.Exitf("Error: %v", err) + if _, err := cm.getCurrent(); err != nil { + klog.Infof("unable to get the current edge certs, reason: %v", err) + if err = cm.applyCerts(); err != nil { + klog.Exitf("failed to apply the edge certs, err: %v", err) } // inform to cleanup token in configuration edgecore.yaml CleanupTokenChan <- struct{}{} @@ -129,28 +116,23 @@ func (cm *CertManager) applyCerts() error { } // save the ca.crt to file - ca, err := x509.ParseCertificate(cacert) + caPem, err := certs.WriteDERToPEMFile(cm.caFile, cert.CertificateBlockType, cacert) if err != nil { - return fmt.Errorf("failed to parse the CA certificate, error: %v", err) - } - - if err = certutil.WriteCert(cm.caFile, ca); err != nil { return fmt.Errorf("failed to save the CA certificate to file: %s, error: %v", cm.caFile, err) } - - // get the edge.crt - caPem := pem.EncodeToMemory(&pem.Block{Bytes: cacert, Type: cert.CertificateBlockType}) - pk, edgeCert, err := cm.GetEdgeCert(cm.certURL, caPem, tls.Certificate{}, realToken) + certDER, keyDER, err := cm.GetEdgeCert(cm.certURL, pem.EncodeToMemory(caPem), tls.Certificate{}, realToken) if err != nil { return fmt.Errorf("failed to get edge certificate from the cloudcore, error: %v", err) } - // save the edge.crt to the file - crt, _ := x509.ParseCertificate(edgeCert) - if err = certutil.WriteKeyAndCert(cm.keyFile, cm.certFile, pk, crt); err != nil { - return fmt.Errorf("failed to save the edge key and certificate to file: %s, error: %v", cm.certFile, err) + if _, err := certs.WriteDERToPEMFile(cm.certFile, + certutil.CertificateBlockType, certDER); err != nil { + return fmt.Errorf("failed to save the certificate file %s, err: %v", cm.certFile, err) + } + if _, err := certs.WriteDERToPEMFile(cm.keyFile, + keyutil.ECPrivateKeyBlockType, keyDER); err != nil { + return fmt.Errorf("failed to save the certificate key file %s, err: %v", cm.keyFile, err) } - return nil } @@ -212,19 +194,19 @@ func (cm *CertManager) rotateCert() (bool, error) { klog.Errorf("failed to get CA certificate locally:%v", err) return false, nil } - pk, edgecert, err := cm.GetEdgeCert(cm.certURL, caPem, *tlsCert, "") + certDER, keyDER, err := cm.GetEdgeCert(cm.certURL, caPem, *tlsCert, "") if err != nil { klog.Errorf("failed to get edge certificate from CloudCore:%v", err) return false, nil } - // save the edge.crt to the file - cert, err := x509.ParseCertificate(edgecert) - if err != nil { - klog.Errorf("failed to parse edge certificate:%v", err) + if _, err := certs.WriteDERToPEMFile(cm.certFile, + certutil.CertificateBlockType, certDER); err != nil { + klog.Errorf("failed to save the certificate file %s, err: %v", cm.certFile, err) return false, nil } - if err = certutil.WriteKeyAndCert(cm.keyFile, cm.certFile, pk, cert); err != nil { - klog.Errorf("failed to save edge key and certificate:%v", err) + if _, err := certs.WriteDERToPEMFile(cm.keyFile, + keyutil.ECPrivateKeyBlockType, keyDER); err != nil { + klog.Errorf("failed to save the certificate key file %s, err: %v", cm.keyFile, err) return false, nil } @@ -261,48 +243,46 @@ func GetCACert(url string) ([]byte, error) { } // GetEdgeCert applies for the certificate from cloudcore -func (cm *CertManager) GetEdgeCert(url string, capem []byte, cert tls.Certificate, token string) (*ecdsa.PrivateKey, []byte, error) { - pk, csr, err := cm.getCSR() +func (cm *CertManager) GetEdgeCert(url string, capem []byte, tlscert tls.Certificate, token string, +) ([]byte, []byte, error) { + h := certs.GetHandler(certs.HandlerTypeX509) + pkw, err := h.GenPrivateKey() if err != nil { - return nil, nil, fmt.Errorf("failed to create CSR: %v", err) + return nil, nil, fmt.Errorf("failed to generate a private key of edge cert, err: %v", err) + } + csrPem, err := h.CreateCSR(pkix.Name{ + Country: []string{"CN"}, + Organization: []string{"system:nodes"}, + Locality: []string{"Hangzhou"}, + Province: []string{"Zhejiang"}, + CommonName: fmt.Sprintf("system:node:%s", cm.NodeName), + }, pkw, nil) + if err != nil { + return nil, nil, fmt.Errorf("failed to create a csr of edge cert, err %v", err) } - client, err := http.NewHTTPClientWithCA(capem, cert) + client, err := http.NewHTTPClientWithCA(capem, tlscert) if err != nil { - return nil, nil, fmt.Errorf("failed to create http client:%v", err) + return nil, nil, fmt.Errorf("failed to create a http client, err: %v", err) } - req, err := http.BuildRequest(nethttp.MethodGet, url, bytes.NewReader(csr), token, cm.NodeName) + req, err := http.BuildRequest(nethttp.MethodGet, url, bytes.NewReader(csrPem.Bytes), token, cm.NodeName) if err != nil { - return nil, nil, fmt.Errorf("failed to generate http request:%v", err) + return nil, nil, fmt.Errorf("failed to generate a http request, err: %v", err) } res, err := http.SendRequest(req, client) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("failed to request the cloudcore server, err: %v", err) } defer res.Body.Close() content, err := io.ReadAll(io.LimitReader(res.Body, constants.MaxRespBodyLength)) if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("failed to read response body, err: %v", err) } if res.StatusCode != nethttp.StatusOK { - return nil, nil, fmt.Errorf(string(content)) - } - - return pk, content, nil -} - -func (cm *CertManager) getCSR() (*ecdsa.PrivateKey, []byte, error) { - pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - if err != nil { - return nil, nil, err + return nil, nil, fmt.Errorf("failed to call http, code: %d, message: %s", res.StatusCode, string(content)) } - csr, err := x509.CreateCertificateRequest(rand.Reader, cm.CR, pk) - if err != nil { - return nil, nil, err - } - - return pk, csr, nil + return content, pkw.DER(), nil } diff --git a/edge/pkg/edgehub/certificate/certmanager_test.go b/edge/pkg/edgehub/certificate/certmanager_test.go new file mode 100644 index 000000000..149a8816e --- /dev/null +++ b/edge/pkg/edgehub/certificate/certmanager_test.go @@ -0,0 +1,103 @@ +/* +Copyright 2022 The KubeEdge 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 certificate + +import ( + "crypto/tls" + "net/http" + "testing" + + "github.com/agiledragon/gomonkey" + "github.com/stretchr/testify/require" + + "github.com/kubeedge/kubeedge/common/constants" + commhttp "github.com/kubeedge/kubeedge/edge/pkg/edgehub/common/http" + httpfake "github.com/kubeedge/kubeedge/edge/pkg/edgehub/common/http/fake" +) + +func TestGetCACert(t *testing.T) { + const fakehost = "http://localhost" + + patches := gomonkey.NewPatches() + defer patches.Reset() + + patches.ApplyFunc(commhttp.SendRequest, + func(_ *http.Request, _ *http.Client) (*http.Response, error) { + return &http.Response{Body: httpfake.NewFakeBodyReader([]byte{})}, nil + }) + + _, err := GetCACert(fakehost + "/ca.crt") + require.NoError(t, err) +} + +func TestGetEdgeCert(t *testing.T) { + const ( + fakehost = "http://localhost" + + capem = `-----BEGIN CERTIFICATE----- +MIIBejCCAR+gAwIBAgICBAAwCgYIKoZIzj0EAwIwEzERMA8GA1UEAxMIS3ViZUVk +Z2UwIBcNMjQwNTA5MDczNzU2WhgPMjEyNDAxMDYwNzM3NTZaMBMxETAPBgNVBAMT +CEt1YmVFZGdlMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEq4Rd11aJ/FXEYBE2 +YCUMjRZVpqytxDBq2anuzokPculGaTrSDiRy1IKukPhlg34bq7J6wqkF0cmFUvcT +jtReq6NhMF8wDgYDVR0PAQH/BAQDAgKkMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggr +BgEFBQcDAjAPBgNVHRMBAf8EBTADAQH/MB0GA1UdDgQWBBTvK3f704DC7OOiVbmO +PyKwJAUwQjAKBggqhkjOPQQDAgNJADBGAiEAkOgvZtFy+aYSqsfxIVMXxScsGilA +P1Iiy/r5PerqODcCIQCH+qeEuxIgZzUAD/Wm6xameEmyn/mO4su/4UE6APNZFQ== +-----END CERTIFICATE-----` + ) + + t.Run("request failed", func(t *testing.T) { + patches := gomonkey.NewPatches() + defer patches.Reset() + + patches.ApplyFunc(commhttp.SendRequest, + func(_ *http.Request, _ *http.Client) (*http.Response, error) { + return &http.Response{ + StatusCode: http.StatusInternalServerError, + Body: httpfake.NewFakeBodyReader([]byte("test error")), + }, nil + }) + + cm := &CertManager{} + tls, err := tls.LoadX509KeyPair("testdata/server.crt", "testdata/server.key") + require.NoError(t, err) + + _, _, err = cm.GetEdgeCert(fakehost+constants.DefaultCAURL, []byte(capem), tls, "") + require.Error(t, err) + require.ErrorContains(t, err, "failed to call http, code: 500, message: test error") + }) + + t.Run("request successful", func(t *testing.T) { + patches := gomonkey.NewPatches() + defer patches.Reset() + + patches.ApplyFunc(commhttp.SendRequest, + func(_ *http.Request, _ *http.Client) (*http.Response, error) { + return &http.Response{ + StatusCode: http.StatusOK, + Body: httpfake.NewFakeBodyReader([]byte("test cert...")), + }, nil + }) + + cm := &CertManager{} + tls, err := tls.LoadX509KeyPair("testdata/server.crt", "testdata/server.key") + require.NoError(t, err) + + _, _, err = cm.GetEdgeCert(fakehost+constants.DefaultCAURL, []byte(capem), tls, "") + require.NoError(t, err) + }) +} diff --git a/edge/pkg/edgehub/certificate/testdata/server.crt b/edge/pkg/edgehub/certificate/testdata/server.crt new file mode 100644 index 000000000..59ae73e54 --- /dev/null +++ b/edge/pkg/edgehub/certificate/testdata/server.crt @@ -0,0 +1,11 @@ +-----BEGIN CERTIFICATE----- +MIIBjjCCATWgAwIBAgIIWugtLvecOyUwCgYIKoZIzj0EAwIwEzERMA8GA1UEAxMI +S3ViZUVkZ2UwHhcNMjQwNTA5MDc0MjQ4WhcNMjUwNTA5MDc0MjQ4WjA+MRUwEwYD +VQQKEwxzeXN0ZW06bm9kZXMxJTAjBgNVBAMTHHN5c3RlbTpub2RlOmh3LXRlc3Qx +aHQ2aGNzcnUwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAARH0RepNVRX5U5lXcTJ +XJM/3PgoaDbxEeiS4RZq2Vz86fESc1KmFL+dUyvaqQ4BBIq3FOfirkwUYuhtiSXr +UyiRo0gwRjAOBgNVHQ8BAf8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwIwHwYD +VR0jBBgwFoAU7yt3+9OAwuzjolW5jj8isCQFMEIwCgYIKoZIzj0EAwIDRwAwRAIg +fswApb3FlJZXRw5aSIvls+uqR1ryfczy4fuzL/Y2i4MCIFyyV0t9Ts9uMHHx8R2+ +6oFBzFcJvH65edh9/eH8rUy8 +-----END CERTIFICATE-----
\ No newline at end of file diff --git a/edge/pkg/edgehub/certificate/testdata/server.key b/edge/pkg/edgehub/certificate/testdata/server.key new file mode 100644 index 000000000..d36152ca7 --- /dev/null +++ b/edge/pkg/edgehub/certificate/testdata/server.key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIJOhw4Hxmkq6E9YTwj/y+L+bl7nUgxNKcz1dcgiPlm4WoAoGCCqGSM49 +AwEHoUQDQgAER9EXqTVUV+VOZV3EyVyTP9z4KGg28RHokuEWatlc/OnxEnNSphS/ +nVMr2qkOAQSKtxTn4q5MFGLobYkl61MokQ== +-----END EC PRIVATE KEY-----
\ No newline at end of file diff --git a/edge/pkg/edgehub/common/certutil/certfile.go b/edge/pkg/edgehub/common/certutil/certfile.go deleted file mode 100644 index 74ed33db2..000000000 --- a/edge/pkg/edgehub/common/certutil/certfile.go +++ /dev/null @@ -1,62 +0,0 @@ -package certutil - -import ( - "crypto" - "crypto/x509" - "encoding/pem" - "fmt" - - certutil "k8s.io/client-go/util/cert" - "k8s.io/client-go/util/keyutil" -) - -func WriteKeyAndCert(keyFile string, certFile string, key crypto.Signer, cert *x509.Certificate) error { - err := WriteKey(keyFile, key) - if err != nil { - return err - } - err = WriteCert(certFile, cert) - if err != nil { - return err - } - return nil -} - -// WriteKey stores the given key at the given location -func WriteKey(pkiPath string, key crypto.Signer) error { - if key == nil { - return fmt.Errorf("private key cannot be nil when writing to file") - } - - encoded, err := keyutil.MarshalPrivateKeyToPEM(key) - if err != nil { - return fmt.Errorf("unable to marshal private key to PEM: %w", err) - } - if err := keyutil.WriteKey(pkiPath, encoded); err != nil { - return fmt.Errorf("unable to write private key to file %s: %w", pkiPath, err) - } - - return nil -} - -// WriteCert stores the given certificate at the given location -func WriteCert(certPath string, cert *x509.Certificate) error { - if cert == nil { - return fmt.Errorf("certificate cannot be nil when writing to file") - } - - if err := certutil.WriteCert(certPath, EncodeCertPEM(cert)); err != nil { - return fmt.Errorf("unable to write certificate to file %s: %w", certPath, err) - } - - return nil -} - -// EncodeCertPEM returns PEM-encoded certificate data -func EncodeCertPEM(cert *x509.Certificate) []byte { - block := pem.Block{ - Type: certutil.CertificateBlockType, - Bytes: cert.Raw, - } - return pem.EncodeToMemory(&block) -} diff --git a/edge/pkg/edgehub/common/certutil/certfile_test.go b/edge/pkg/edgehub/common/certutil/certfile_test.go deleted file mode 100644 index 4b73707da..000000000 --- a/edge/pkg/edgehub/common/certutil/certfile_test.go +++ /dev/null @@ -1,67 +0,0 @@ -package certutil - -import ( - "crypto" - "crypto/rand" - "crypto/rsa" - "crypto/x509" - "fmt" - "testing" -) - -func TestWriteKeyAndCert(t *testing.T) { - key, err := rsa.GenerateKey(rand.Reader, 1024) - if err != nil { - t.Error(err) - return - } - type args struct { - keyFile string - certFile string - key crypto.Signer - cert *x509.Certificate - } - tests := []struct { - name string - args args - wantErr error - }{ - { - name: "TestWriteKeyAndCert(): Case 1: Test without key", - args: args{ - keyFile: "/tmp/key.pem", - certFile: "/tmp/crt.pem", - key: nil, - cert: nil, - }, - wantErr: fmt.Errorf("private key cannot be nil when writing to file"), - }, - { - name: "TestWriteKeyAndCert(): Case 2: Test without cert", - args: args{ - keyFile: "/tmp/key.pem", - certFile: "/tmp/crt.pem", - key: key, - cert: nil, - }, - wantErr: fmt.Errorf("certificate cannot be nil when writing to file"), - }, - { - name: "TestWriteKeyAndCert(): Case 1: Test with key and cert", - args: args{ - keyFile: "/tmp/key.pem", - certFile: "/tmp/crt.pem", - key: key, - cert: &x509.Certificate{}, - }, - wantErr: nil, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if err := WriteKeyAndCert(tt.args.keyFile, tt.args.certFile, tt.args.key, tt.args.cert); err != nil && err.Error() != tt.wantErr.Error() { - t.Errorf("WriteKeyAndCert() error = %v, wantErr %v", err, tt.wantErr) - } - }) - } -} diff --git a/edge/pkg/edgehub/common/http/fake/http.go b/edge/pkg/edgehub/common/http/fake/http.go new file mode 100644 index 000000000..96ab99fd2 --- /dev/null +++ b/edge/pkg/edgehub/common/http/fake/http.go @@ -0,0 +1,17 @@ +package fake + +import "bytes" + +type FakeBodyReader struct { + *bytes.Reader +} + +func NewFakeBodyReader(bff []byte) *FakeBodyReader { + return &FakeBodyReader{ + Reader: bytes.NewReader(bff), + } +} + +func (FakeBodyReader) Close() error { + return nil +} diff --git a/edge/pkg/edgehub/common/http/http.go b/edge/pkg/edgehub/common/http/http.go index 4f8315bc0..3980b4521 100644 --- a/edge/pkg/edgehub/common/http/http.go +++ b/edge/pkg/edgehub/common/http/http.go @@ -99,10 +99,10 @@ func BuildRequest(method string, urlStr string, body io.Reader, token string, no } if token != "" { bearerToken := "Bearer " + token - req.Header.Add("Authorization", bearerToken) + req.Header.Add(types.HeaderAuthorization, bearerToken) } if nodeName != "" { - req.Header.Add(types.NodeNameKey, nodeName) + req.Header.Add(types.HeaderNodeName, nodeName) } return req, nil } diff --git a/edge/pkg/metamanager/metaserver/agent/agent_test.go b/edge/pkg/metamanager/metaserver/agent/agent_test.go index 6e86658c7..231580add 100644 --- a/edge/pkg/metamanager/metaserver/agent/agent_test.go +++ b/edge/pkg/metamanager/metaserver/agent/agent_test.go @@ -51,7 +51,7 @@ func TestApplicationGC(t *testing.T) { Resource: "nodes", } ctx := apirequest.WithRequestInfo(context.Background(), requestInfo) - ctx = context.WithValue(ctx, commontypes.AuthorizationKey, "Bearer xxxx") + ctx = context.WithValue(ctx, commontypes.HeaderAuthorization, "Bearer xxxx") connect.SetConnected(true) @@ -61,6 +61,7 @@ require ( github.com/onsi/ginkgo/v2 v2.13.0 github.com/opencontainers/selinux v1.11.0 github.com/pkg/errors v0.9.1 + github.com/stretchr/testify v1.9.0 github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852 go.opentelemetry.io/otel/trace v1.22.0 golang.org/x/sys v0.19.0 @@ -218,7 +219,6 @@ require ( github.com/sirupsen/logrus v1.9.0 // indirect github.com/spf13/cast v1.5.0 // indirect github.com/stoewer/go-strcase v1.2.0 // indirect - github.com/stretchr/testify v1.9.0 github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 // indirect github.com/tklauser/go-sysconf v0.3.11 // indirect github.com/tklauser/numcpus v0.6.0 // indirect diff --git a/pkg/security/certs/factory.go b/pkg/security/certs/factory.go new file mode 100644 index 000000000..a61369366 --- /dev/null +++ b/pkg/security/certs/factory.go @@ -0,0 +1,41 @@ +/* +Copyright 2024 The KubeEdge 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 certs + +const ( + CAHandlerTypeX509 = "x509" + + HandlerTypeX509 = "x509" +) + +type CAHandlerType string +type HanndlerType string + +func GetCAHandler(t CAHandlerType) CAHandler { + switch t { + case CAHandlerTypeX509: + return &x509CAHandler{} + } + return nil +} + +func GetHandler(t HanndlerType) Handler { + switch t { + case HandlerTypeX509: + return &x509CertsHandler{} + } + return nil +} diff --git a/pkg/security/certs/io.go b/pkg/security/certs/io.go new file mode 100644 index 000000000..4d598d01a --- /dev/null +++ b/pkg/security/certs/io.go @@ -0,0 +1,45 @@ +/* +Copyright 2024 The KubeEdge 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 certs + +import ( + "encoding/pem" + "fmt" + "os" + "path/filepath" +) + +func ReadPEMFile(file string) (*pem.Block, error) { + bff, err := os.ReadFile(file) + if err != nil { + return nil, err + } + p, _ := pem.Decode(bff) + return p, nil +} + +func WriteDERToPEMFile(file, t string, der []byte) (*pem.Block, error) { + dir := filepath.Dir(file) + if err := os.MkdirAll(dir, 0722); err != nil { + return nil, fmt.Errorf("failed to create dir %s, err: %v", dir, err) + } + block := &pem.Block{Type: t, Bytes: der} + bff := pem.EncodeToMemory(block) + if err := os.WriteFile(file, bff, 0622); err != nil { + return nil, fmt.Errorf("failed to write file %s, err: %v", file, err) + } + return block, nil +} diff --git a/pkg/security/certs/io_test.go b/pkg/security/certs/io_test.go new file mode 100644 index 000000000..c0b335f0b --- /dev/null +++ b/pkg/security/certs/io_test.go @@ -0,0 +1,24 @@ +package certs + +import ( + "os" + "testing" +) + +func TestReadWrite(t *testing.T) { + file := "./testdata/ca/ca.key" + if _, err := WriteDERToPEMFile(file, "test data", []byte("test")); err != nil { + t.Fatal(err) + } + if block, err := ReadPEMFile(file); err != nil { + t.Fatal(err) + } else { + if block.Type != "test data" { + t.Fatalf("want block type '%s', actual '%s'", "test data", block.Type) + } + } + // Clean + if err := os.RemoveAll("testdata"); err != nil { + t.Fatalf("failed to clean testdata, err: %v", err) + } +} diff --git a/pkg/security/certs/types.go b/pkg/security/certs/types.go new file mode 100644 index 000000000..13d88dda1 --- /dev/null +++ b/pkg/security/certs/types.go @@ -0,0 +1,92 @@ +/* +Copyright 2024 The KubeEdge 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 certs + +import ( + "crypto" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "time" + + certutil "k8s.io/client-go/util/cert" +) + +type PrivateKeyWrap interface { + Signer() (crypto.Signer, error) + DER() []byte + PEM() []byte +} + +type CAHandler interface { + // GenPrivateKey create a private key + GenPrivateKey() (PrivateKeyWrap, error) + + // New creates CA certificate, returns a pem block. + NewSelfSigned(key PrivateKeyWrap) (*pem.Block, error) +} + +type Handler interface { + // GenPrivateKey create a private key + GenPrivateKey() (PrivateKeyWrap, error) + + // CreateCSR create a certificate request, returns a pem block. + CreateCSR(sub pkix.Name, pkw PrivateKeyWrap, alt *certutil.AltNames) (*pem.Block, error) + + // SignCerts creates a certificate, returns a pem block. + SignCerts(opts SignCertsOptions) (*pem.Block, error) +} + +type SignCertsOptions struct { + cfg certutil.Config + caDER []byte + caKeyDER []byte + csrDER []byte + publicKey any + expiration time.Duration +} + +func SignCertsOptionsWithCA(cfg certutil.Config, caDER, caKeyDER []byte, publicKey any, expiration time.Duration) SignCertsOptions { + return SignCertsOptions{ + cfg: cfg, + caDER: caDER, + caKeyDER: caKeyDER, + publicKey: publicKey, + expiration: expiration, + } +} + +func SignCertsOptionsWithCSR(csrDER, caDER, caKeyDER []byte, usages []x509.ExtKeyUsage, expiration time.Duration) SignCertsOptions { + return SignCertsOptions{ + csrDER: csrDER, + caDER: caDER, + caKeyDER: caKeyDER, + cfg: certutil.Config{ + Usages: usages, + }, + expiration: expiration, + } +} + +func SignCertsOptionsWithK8sCSR(csrDER []byte, usages []x509.ExtKeyUsage, expiration time.Duration) SignCertsOptions { + return SignCertsOptions{ + csrDER: csrDER, + cfg: certutil.Config{ + Usages: usages, + }, + expiration: expiration, + } +} diff --git a/pkg/security/certs/x509_ca.go b/pkg/security/certs/x509_ca.go new file mode 100644 index 000000000..1b16ca70d --- /dev/null +++ b/pkg/security/certs/x509_ca.go @@ -0,0 +1,75 @@ +/* +Copyright 2024 The KubeEdge 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 certs + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "math/big" + "time" + + certutil "k8s.io/client-go/util/cert" + + "github.com/kubeedge/kubeedge/common/constants" +) + +type x509CAHandler struct{} + +// check implements CAHandler +var _ CAHandler = (*x509CAHandler)(nil) + +func (h x509CAHandler) GenPrivateKey() (PrivateKeyWrap, error) { + pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, fmt.Errorf("failed to generate self signed CA private key, err: %v", err) + } + keyDER, err := x509.MarshalECPrivateKey(pk) + if err != nil { + return nil, fmt.Errorf("failed to convert an EC private key to SEC 1, ASN.1 DER form, err: %v", err) + } + return &x509PrivateKeyWrap{der: keyDER}, nil +} + +func (h x509CAHandler) NewSelfSigned(key PrivateKeyWrap) (*pem.Block, error) { + const year100 = time.Hour * 24 * 364 * 100 + + tmpl := x509.Certificate{ + SerialNumber: big.NewInt(1024), + Subject: pkix.Name{ + CommonName: constants.ProjectName, + }, + NotBefore: time.Now().UTC(), + NotAfter: time.Now().Add(year100), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + BasicConstraintsValid: true, + IsCA: true, + } + pk, err := key.Signer() + if err != nil { + return nil, fmt.Errorf("failed parse CA key der to private key, err: %v", err) + } + caDER, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, pk.Public(), pk) + if err != nil { + return nil, fmt.Errorf("failed to generate self signed CA cert, err: %v", err) + } + return &pem.Block{Type: certutil.CertificateBlockType, Bytes: caDER}, nil +} diff --git a/pkg/security/certs/x509_ca_certs_test.go b/pkg/security/certs/x509_ca_certs_test.go new file mode 100644 index 000000000..43638762b --- /dev/null +++ b/pkg/security/certs/x509_ca_certs_test.go @@ -0,0 +1,44 @@ +package certs + +import ( + "crypto/x509" + "crypto/x509/pkix" + "testing" + "time" +) + +func TestSignX509Certs(t *testing.T) { + cah := new(x509CAHandler) + certh := new(x509CertsHandler) + + capkw, err := cah.GenPrivateKey() + if err != nil { + t.Fatal(err) + } + cablock, err := cah.NewSelfSigned(capkw) + if err != nil { + t.Fatal(err) + } + + certpkw, err := certh.GenPrivateKey() + if err != nil { + t.Fatal(err) + } + csrblock, err := certh.CreateCSR(pkix.Name{ + Country: []string{"CN"}, + Organization: []string{"system:nodes"}, + Locality: []string{"Hangzhou"}, + Province: []string{"Zhejiang"}, + CommonName: "test-node", + }, certpkw, nil) + + opts := SignCertsOptionsWithCSR(csrblock.Bytes, cablock.Bytes, capkw.DER(), + []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, 24*time.Hour) + certblock, err := certh.SignCerts(opts) + if err != nil { + t.Fatal(err) + } + if len(certblock.Bytes) == 0 { + t.Fatal("cert bytes cannot be empty") + } +} diff --git a/pkg/security/certs/x509_certs.go b/pkg/security/certs/x509_certs.go new file mode 100644 index 000000000..a454a0daa --- /dev/null +++ b/pkg/security/certs/x509_certs.go @@ -0,0 +1,124 @@ +/* +Copyright 2024 The KubeEdge 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 certs + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "errors" + "fmt" + "math" + "math/big" + "time" + + certutil "k8s.io/client-go/util/cert" +) + +type x509CertsHandler struct{} + +// check implements Handler +var _ Handler = (*x509CertsHandler)(nil) + +func (h x509CertsHandler) GenPrivateKey() (PrivateKeyWrap, error) { + pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return nil, fmt.Errorf("failed to generate certificate private key, err: %v", err) + } + keyDER, err := x509.MarshalECPrivateKey(pk) + if err != nil { + return nil, fmt.Errorf("failed to convert an EC private key to SEC 1, ASN.1 DER form, err: %v", err) + } + return &x509PrivateKeyWrap{der: keyDER}, nil +} + +func (h x509CertsHandler) CreateCSR(sub pkix.Name, pkw PrivateKeyWrap, alt *certutil.AltNames) (*pem.Block, error) { + tpl := x509.CertificateRequest{ + Subject: sub, + } + if alt != nil { + tpl.DNSNames = alt.DNSNames + tpl.IPAddresses = alt.IPs + } + pk, err := pkw.Signer() + if err != nil { + return nil, fmt.Errorf("faild to parse the private key der to Signer, err: %v", err) + } + csrDER, err := x509.CreateCertificateRequest(rand.Reader, &tpl, pk) + if err != nil { + return nil, fmt.Errorf("faild to create x509 certificate request, err %v", err) + } + return &pem.Block{Type: certutil.CertificateRequestBlockType, Bytes: csrDER}, nil +} + +func (h x509CertsHandler) SignCerts(opts SignCertsOptions) (*pem.Block, error) { + pubkey := opts.publicKey + if opts.csrDER != nil { + csr, err := x509.ParseCertificateRequest(opts.csrDER) + if err != nil { + return nil, fmt.Errorf("failed to parse csr, err: %v", err) + } + opts.cfg.CommonName = csr.Subject.CommonName + opts.cfg.Organization = csr.Subject.Organization + opts.cfg.AltNames.DNSNames = csr.DNSNames + opts.cfg.AltNames.IPs = csr.IPAddresses + pubkey = csr.PublicKey + } + if len(opts.cfg.CommonName) == 0 { + return nil, errors.New("must specify a CommonName") + } + if len(opts.cfg.Usages) == 0 { + return nil, errors.New("must specify at least one ExtKeyUsage") + } + + serial, err := rand.Int(rand.Reader, new(big.Int).SetInt64(math.MaxInt64)) + if err != nil { + return nil, fmt.Errorf("failed to generate serial number, err: %v", err) + } + + caKey, err := x509PrivateKeyWrap{der: opts.caKeyDER}.Signer() + if err != nil { + return nil, fmt.Errorf("failed to parse CA private key, err: %v", err) + } + + ca, err := x509.ParseCertificate(opts.caDER) + if err != nil { + return nil, fmt.Errorf("failed to parse CA, err: %v", err) + } + + certTmpl := x509.Certificate{ + Subject: pkix.Name{ + CommonName: opts.cfg.CommonName, + Organization: opts.cfg.Organization, + }, + DNSNames: opts.cfg.AltNames.DNSNames, + IPAddresses: opts.cfg.AltNames.IPs, + SerialNumber: serial, + NotBefore: time.Now().UTC(), + NotAfter: time.Now().Add(opts.expiration), + KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, + ExtKeyUsage: opts.cfg.Usages, + } + certDER, err := x509.CreateCertificate(rand.Reader, &certTmpl, ca, pubkey, caKey) + if err != nil { + return nil, fmt.Errorf("failed to create certificate, err: %v", err) + } + + return &pem.Block{Type: certutil.CertificateBlockType, Bytes: certDER}, nil +} diff --git a/pkg/security/certs/x509_types.go b/pkg/security/certs/x509_types.go new file mode 100644 index 000000000..524c25342 --- /dev/null +++ b/pkg/security/certs/x509_types.go @@ -0,0 +1,44 @@ +/* +Copyright 2024 The KubeEdge 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 certs + +import ( + "crypto" + "crypto/x509" + "encoding/pem" + + "k8s.io/client-go/util/keyutil" +) + +type x509PrivateKeyWrap struct { + der []byte +} + +func (k x509PrivateKeyWrap) Signer() (crypto.Signer, error) { + return x509.ParseECPrivateKey(k.der) +} + +func (k x509PrivateKeyWrap) DER() []byte { + return k.der +} + +func (k x509PrivateKeyWrap) PEM() []byte { + privateKeyPemBlock := &pem.Block{ + Type: keyutil.ECPrivateKeyBlockType, + Bytes: k.der, + } + return pem.EncodeToMemory(privateKeyPemBlock) +} diff --git a/vendor/github.com/stretchr/testify/require/doc.go b/vendor/github.com/stretchr/testify/require/doc.go new file mode 100644 index 000000000..968434724 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/doc.go @@ -0,0 +1,29 @@ +// Package require implements the same assertions as the `assert` package but +// stops test execution when a test fails. +// +// # Example Usage +// +// The following is a complete example using require in a standard test function: +// +// import ( +// "testing" +// "github.com/stretchr/testify/require" +// ) +// +// func TestSomething(t *testing.T) { +// +// var a string = "Hello" +// var b string = "Hello" +// +// require.Equal(t, a, b, "The two words should be the same.") +// +// } +// +// # Assertions +// +// The `require` package have same global functions as in the `assert` package, +// but instead of returning a boolean result they call `t.FailNow()`. +// +// Every assertion function also takes an optional string message as the final argument, +// allowing custom error messages to be appended to the message the assertion method outputs. +package require diff --git a/vendor/github.com/stretchr/testify/require/forward_requirements.go b/vendor/github.com/stretchr/testify/require/forward_requirements.go new file mode 100644 index 000000000..1dcb2338c --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/forward_requirements.go @@ -0,0 +1,16 @@ +package require + +// Assertions provides assertion methods around the +// TestingT interface. +type Assertions struct { + t TestingT +} + +// New makes a new Assertions object for the specified TestingT. +func New(t TestingT) *Assertions { + return &Assertions{ + t: t, + } +} + +//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require_forward.go.tmpl -include-format-funcs" diff --git a/vendor/github.com/stretchr/testify/require/require.go b/vendor/github.com/stretchr/testify/require/require.go new file mode 100644 index 000000000..506a82f80 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require.go @@ -0,0 +1,2060 @@ +// Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT. + +package require + +import ( + assert "github.com/stretchr/testify/assert" + http "net/http" + url "net/url" + time "time" +) + +// Condition uses a Comparison to assert a complex condition. +func Condition(t TestingT, comp assert.Comparison, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Condition(t, comp, msgAndArgs...) { + return + } + t.FailNow() +} + +// Conditionf uses a Comparison to assert a complex condition. +func Conditionf(t TestingT, comp assert.Comparison, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Conditionf(t, comp, msg, args...) { + return + } + t.FailNow() +} + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// assert.Contains(t, "Hello World", "World") +// assert.Contains(t, ["Hello", "World"], "World") +// assert.Contains(t, {"Hello": "World"}, "Hello") +func Contains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Contains(t, s, contains, msgAndArgs...) { + return + } + t.FailNow() +} + +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// assert.Containsf(t, "Hello World", "World", "error message %s", "formatted") +// assert.Containsf(t, ["Hello", "World"], "World", "error message %s", "formatted") +// assert.Containsf(t, {"Hello": "World"}, "Hello", "error message %s", "formatted") +func Containsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Containsf(t, s, contains, msg, args...) { + return + } + t.FailNow() +} + +// DirExists checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func DirExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.DirExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// DirExistsf checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func DirExistsf(t TestingT, path string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.DirExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatch(t, [1, 3, 2, 3], [1, 3, 3, 2]) +func ElementsMatch(t TestingT, listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ElementsMatch(t, listA, listB, msgAndArgs...) { + return + } + t.FailNow() +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// assert.ElementsMatchf(t, [1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func ElementsMatchf(t TestingT, listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ElementsMatchf(t, listA, listB, msg, args...) { + return + } + t.FailNow() +} + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Empty(t, obj) +func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Empty(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// assert.Emptyf(t, obj, "error message %s", "formatted") +func Emptyf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Emptyf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// Equal asserts that two objects are equal. +// +// assert.Equal(t, 123, 123) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func Equal(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Equal(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// assert.EqualError(t, err, expectedErrorString) +func EqualError(t TestingT, theError error, errString string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualError(t, theError, errString, msgAndArgs...) { + return + } + t.FailNow() +} + +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// assert.EqualErrorf(t, err, expectedErrorString, "error message %s", "formatted") +func EqualErrorf(t TestingT, theError error, errString string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualErrorf(t, theError, errString, msg, args...) { + return + } + t.FailNow() +} + +// EqualExportedValues asserts that the types of two objects are equal and their public +// fields are also equal. This is useful for comparing structs that have private fields +// that could potentially differ. +// +// type S struct { +// Exported int +// notExported int +// } +// assert.EqualExportedValues(t, S{1, 2}, S{1, 3}) => true +// assert.EqualExportedValues(t, S{1, 2}, S{2, 3}) => false +func EqualExportedValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualExportedValues(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// EqualExportedValuesf asserts that the types of two objects are equal and their public +// fields are also equal. This is useful for comparing structs that have private fields +// that could potentially differ. +// +// type S struct { +// Exported int +// notExported int +// } +// assert.EqualExportedValuesf(t, S{1, 2}, S{1, 3}, "error message %s", "formatted") => true +// assert.EqualExportedValuesf(t, S{1, 2}, S{2, 3}, "error message %s", "formatted") => false +func EqualExportedValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualExportedValuesf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// EqualValues asserts that two objects are equal or convertible to the same types +// and equal. +// +// assert.EqualValues(t, uint32(123), int32(123)) +func EqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualValues(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// EqualValuesf asserts that two objects are equal or convertible to the same types +// and equal. +// +// assert.EqualValuesf(t, uint32(123), int32(123), "error message %s", "formatted") +func EqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EqualValuesf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Equalf asserts that two objects are equal. +// +// assert.Equalf(t, 123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func Equalf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Equalf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Error(t, err) { +// assert.Equal(t, expectedError, err) +// } +func Error(t TestingT, err error, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Error(t, err, msgAndArgs...) { + return + } + t.FailNow() +} + +// ErrorAs asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. +// This is a wrapper for errors.As. +func ErrorAs(t TestingT, err error, target interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorAs(t, err, target, msgAndArgs...) { + return + } + t.FailNow() +} + +// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. +// This is a wrapper for errors.As. +func ErrorAsf(t TestingT, err error, target interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorAsf(t, err, target, msg, args...) { + return + } + t.FailNow() +} + +// ErrorContains asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// assert.ErrorContains(t, err, expectedErrorSubString) +func ErrorContains(t TestingT, theError error, contains string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorContains(t, theError, contains, msgAndArgs...) { + return + } + t.FailNow() +} + +// ErrorContainsf asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// assert.ErrorContainsf(t, err, expectedErrorSubString, "error message %s", "formatted") +func ErrorContainsf(t TestingT, theError error, contains string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorContainsf(t, theError, contains, msg, args...) { + return + } + t.FailNow() +} + +// ErrorIs asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func ErrorIs(t TestingT, err error, target error, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorIs(t, err, target, msgAndArgs...) { + return + } + t.FailNow() +} + +// ErrorIsf asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func ErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.ErrorIsf(t, err, target, msg, args...) { + return + } + t.FailNow() +} + +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if assert.Errorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func Errorf(t TestingT, err error, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Errorf(t, err, msg, args...) { + return + } + t.FailNow() +} + +// Eventually asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// assert.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond) +func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Eventually(t, condition, waitFor, tick, msgAndArgs...) { + return + } + t.FailNow() +} + +// EventuallyWithT asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. In contrast to Eventually, +// it supplies a CollectT to the condition function, so that the condition +// function can use the CollectT to call other assertions. +// The condition is considered "met" if no errors are raised in a tick. +// The supplied CollectT collects all errors from one tick (if there are any). +// If the condition is not met before waitFor, the collected errors of +// the last tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// assert.EventuallyWithT(t, func(c *assert.CollectT) { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +func EventuallyWithT(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EventuallyWithT(t, condition, waitFor, tick, msgAndArgs...) { + return + } + t.FailNow() +} + +// EventuallyWithTf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. In contrast to Eventually, +// it supplies a CollectT to the condition function, so that the condition +// function can use the CollectT to call other assertions. +// The condition is considered "met" if no errors are raised in a tick. +// The supplied CollectT collects all errors from one tick (if there are any). +// If the condition is not met before waitFor, the collected errors of +// the last tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// assert.EventuallyWithTf(t, func(c *assert.CollectT, "error message %s", "formatted") { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +func EventuallyWithTf(t TestingT, condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.EventuallyWithTf(t, condition, waitFor, tick, msg, args...) { + return + } + t.FailNow() +} + +// Eventuallyf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Eventuallyf(t, condition, waitFor, tick, msg, args...) { + return + } + t.FailNow() +} + +// Exactly asserts that two objects are equal in value and type. +// +// assert.Exactly(t, int32(123), int64(123)) +func Exactly(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Exactly(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// Exactlyf asserts that two objects are equal in value and type. +// +// assert.Exactlyf(t, int32(123), int64(123), "error message %s", "formatted") +func Exactlyf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Exactlyf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Fail reports a failure through +func Fail(t TestingT, failureMessage string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Fail(t, failureMessage, msgAndArgs...) { + return + } + t.FailNow() +} + +// FailNow fails test +func FailNow(t TestingT, failureMessage string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FailNow(t, failureMessage, msgAndArgs...) { + return + } + t.FailNow() +} + +// FailNowf fails test +func FailNowf(t TestingT, failureMessage string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FailNowf(t, failureMessage, msg, args...) { + return + } + t.FailNow() +} + +// Failf reports a failure through +func Failf(t TestingT, failureMessage string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Failf(t, failureMessage, msg, args...) { + return + } + t.FailNow() +} + +// False asserts that the specified value is false. +// +// assert.False(t, myBool) +func False(t TestingT, value bool, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.False(t, value, msgAndArgs...) { + return + } + t.FailNow() +} + +// Falsef asserts that the specified value is false. +// +// assert.Falsef(t, myBool, "error message %s", "formatted") +func Falsef(t TestingT, value bool, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Falsef(t, value, msg, args...) { + return + } + t.FailNow() +} + +// FileExists checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func FileExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FileExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// FileExistsf checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func FileExistsf(t TestingT, path string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.FileExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// Greater asserts that the first element is greater than the second +// +// assert.Greater(t, 2, 1) +// assert.Greater(t, float64(2), float64(1)) +// assert.Greater(t, "b", "a") +func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Greater(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// GreaterOrEqual asserts that the first element is greater than or equal to the second +// +// assert.GreaterOrEqual(t, 2, 1) +// assert.GreaterOrEqual(t, 2, 2) +// assert.GreaterOrEqual(t, "b", "a") +// assert.GreaterOrEqual(t, "b", "b") +func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.GreaterOrEqual(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// GreaterOrEqualf asserts that the first element is greater than or equal to the second +// +// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted") +// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted") +// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted") +// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted") +func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.GreaterOrEqualf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// Greaterf asserts that the first element is greater than the second +// +// assert.Greaterf(t, 2, 1, "error message %s", "formatted") +// assert.Greaterf(t, float64(2), float64(1), "error message %s", "formatted") +// assert.Greaterf(t, "b", "a", "error message %s", "formatted") +func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Greaterf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// assert.HTTPBodyContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyContains(t, handler, method, url, values, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// assert.HTTPBodyContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyContainsf(t, handler, method, url, values, str, msg, args...) { + return + } + t.FailNow() +} + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// assert.HTTPBodyNotContains(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyNotContains(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyNotContains(t, handler, method, url, values, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// assert.HTTPBodyNotContainsf(t, myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPBodyNotContainsf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPBodyNotContainsf(t, handler, method, url, values, str, msg, args...) { + return + } + t.FailNow() +} + +// HTTPError asserts that a specified handler returns an error status code. +// +// assert.HTTPError(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPError(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPError(t, handler, method, url, values, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// assert.HTTPErrorf(t, myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPErrorf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPErrorf(t, handler, method, url, values, msg, args...) { + return + } + t.FailNow() +} + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// assert.HTTPRedirect(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPRedirect(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPRedirect(t, handler, method, url, values, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// assert.HTTPRedirectf(t, myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPRedirectf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPRedirectf(t, handler, method, url, values, msg, args...) { + return + } + t.FailNow() +} + +// HTTPStatusCode asserts that a specified handler returns a specified status code. +// +// assert.HTTPStatusCode(t, myHandler, "GET", "/notImplemented", nil, 501) +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPStatusCode(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPStatusCode(t, handler, method, url, values, statuscode, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPStatusCodef asserts that a specified handler returns a specified status code. +// +// assert.HTTPStatusCodef(t, myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPStatusCodef(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPStatusCodef(t, handler, method, url, values, statuscode, msg, args...) { + return + } + t.FailNow() +} + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// assert.HTTPSuccess(t, myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPSuccess(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPSuccess(t, handler, method, url, values, msgAndArgs...) { + return + } + t.FailNow() +} + +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// assert.HTTPSuccessf(t, myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func HTTPSuccessf(t TestingT, handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.HTTPSuccessf(t, handler, method, url, values, msg, args...) { + return + } + t.FailNow() +} + +// Implements asserts that an object is implemented by the specified interface. +// +// assert.Implements(t, (*MyInterface)(nil), new(MyObject)) +func Implements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Implements(t, interfaceObject, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// Implementsf asserts that an object is implemented by the specified interface. +// +// assert.Implementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +func Implementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Implementsf(t, interfaceObject, object, msg, args...) { + return + } + t.FailNow() +} + +// InDelta asserts that the two numerals are within delta of each other. +// +// assert.InDelta(t, math.Pi, 22/7.0, 0.01) +func InDelta(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDelta(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValues(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaMapValues(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaMapValuesf(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func InDeltaSlice(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaSlice(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func InDeltaSlicef(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaSlicef(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// InDeltaf asserts that the two numerals are within delta of each other. +// +// assert.InDeltaf(t, math.Pi, 22/7.0, 0.01, "error message %s", "formatted") +func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InDeltaf(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +func InEpsilon(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilon(t, expected, actual, epsilon, msgAndArgs...) { + return + } + t.FailNow() +} + +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlice(t TestingT, expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilonSlice(t, expected, actual, epsilon, msgAndArgs...) { + return + } + t.FailNow() +} + +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilonSlicef(t, expected, actual, epsilon, msg, args...) { + return + } + t.FailNow() +} + +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InEpsilonf(t, expected, actual, epsilon, msg, args...) { + return + } + t.FailNow() +} + +// IsDecreasing asserts that the collection is decreasing +// +// assert.IsDecreasing(t, []int{2, 1, 0}) +// assert.IsDecreasing(t, []float{2, 1}) +// assert.IsDecreasing(t, []string{"b", "a"}) +func IsDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsDecreasing(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// IsDecreasingf asserts that the collection is decreasing +// +// assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted") +// assert.IsDecreasingf(t, []float{2, 1}, "error message %s", "formatted") +// assert.IsDecreasingf(t, []string{"b", "a"}, "error message %s", "formatted") +func IsDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsDecreasingf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// IsIncreasing asserts that the collection is increasing +// +// assert.IsIncreasing(t, []int{1, 2, 3}) +// assert.IsIncreasing(t, []float{1, 2}) +// assert.IsIncreasing(t, []string{"a", "b"}) +func IsIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsIncreasing(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// IsIncreasingf asserts that the collection is increasing +// +// assert.IsIncreasingf(t, []int{1, 2, 3}, "error message %s", "formatted") +// assert.IsIncreasingf(t, []float{1, 2}, "error message %s", "formatted") +// assert.IsIncreasingf(t, []string{"a", "b"}, "error message %s", "formatted") +func IsIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsIncreasingf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// IsNonDecreasing asserts that the collection is not decreasing +// +// assert.IsNonDecreasing(t, []int{1, 1, 2}) +// assert.IsNonDecreasing(t, []float{1, 2}) +// assert.IsNonDecreasing(t, []string{"a", "b"}) +func IsNonDecreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsNonDecreasing(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// IsNonDecreasingf asserts that the collection is not decreasing +// +// assert.IsNonDecreasingf(t, []int{1, 1, 2}, "error message %s", "formatted") +// assert.IsNonDecreasingf(t, []float{1, 2}, "error message %s", "formatted") +// assert.IsNonDecreasingf(t, []string{"a", "b"}, "error message %s", "formatted") +func IsNonDecreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsNonDecreasingf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// IsNonIncreasing asserts that the collection is not increasing +// +// assert.IsNonIncreasing(t, []int{2, 1, 1}) +// assert.IsNonIncreasing(t, []float{2, 1}) +// assert.IsNonIncreasing(t, []string{"b", "a"}) +func IsNonIncreasing(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsNonIncreasing(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// IsNonIncreasingf asserts that the collection is not increasing +// +// assert.IsNonIncreasingf(t, []int{2, 1, 1}, "error message %s", "formatted") +// assert.IsNonIncreasingf(t, []float{2, 1}, "error message %s", "formatted") +// assert.IsNonIncreasingf(t, []string{"b", "a"}, "error message %s", "formatted") +func IsNonIncreasingf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsNonIncreasingf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// IsType asserts that the specified objects are of the same type. +func IsType(t TestingT, expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsType(t, expectedType, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// IsTypef asserts that the specified objects are of the same type. +func IsTypef(t TestingT, expectedType interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.IsTypef(t, expectedType, object, msg, args...) { + return + } + t.FailNow() +} + +// JSONEq asserts that two JSON strings are equivalent. +// +// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.JSONEq(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// JSONEqf asserts that two JSON strings are equivalent. +// +// assert.JSONEqf(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func JSONEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.JSONEqf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// assert.Len(t, mySlice, 3) +func Len(t TestingT, object interface{}, length int, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Len(t, object, length, msgAndArgs...) { + return + } + t.FailNow() +} + +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// assert.Lenf(t, mySlice, 3, "error message %s", "formatted") +func Lenf(t TestingT, object interface{}, length int, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Lenf(t, object, length, msg, args...) { + return + } + t.FailNow() +} + +// Less asserts that the first element is less than the second +// +// assert.Less(t, 1, 2) +// assert.Less(t, float64(1), float64(2)) +// assert.Less(t, "a", "b") +func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Less(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// LessOrEqual asserts that the first element is less than or equal to the second +// +// assert.LessOrEqual(t, 1, 2) +// assert.LessOrEqual(t, 2, 2) +// assert.LessOrEqual(t, "a", "b") +// assert.LessOrEqual(t, "b", "b") +func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.LessOrEqual(t, e1, e2, msgAndArgs...) { + return + } + t.FailNow() +} + +// LessOrEqualf asserts that the first element is less than or equal to the second +// +// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted") +// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted") +// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted") +// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted") +func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.LessOrEqualf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// Lessf asserts that the first element is less than the second +// +// assert.Lessf(t, 1, 2, "error message %s", "formatted") +// assert.Lessf(t, float64(1), float64(2), "error message %s", "formatted") +// assert.Lessf(t, "a", "b", "error message %s", "formatted") +func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Lessf(t, e1, e2, msg, args...) { + return + } + t.FailNow() +} + +// Negative asserts that the specified element is negative +// +// assert.Negative(t, -1) +// assert.Negative(t, -1.23) +func Negative(t TestingT, e interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Negative(t, e, msgAndArgs...) { + return + } + t.FailNow() +} + +// Negativef asserts that the specified element is negative +// +// assert.Negativef(t, -1, "error message %s", "formatted") +// assert.Negativef(t, -1.23, "error message %s", "formatted") +func Negativef(t TestingT, e interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Negativef(t, e, msg, args...) { + return + } + t.FailNow() +} + +// Never asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// assert.Never(t, func() bool { return false; }, time.Second, 10*time.Millisecond) +func Never(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Never(t, condition, waitFor, tick, msgAndArgs...) { + return + } + t.FailNow() +} + +// Neverf asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// assert.Neverf(t, func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func Neverf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Neverf(t, condition, waitFor, tick, msg, args...) { + return + } + t.FailNow() +} + +// Nil asserts that the specified object is nil. +// +// assert.Nil(t, err) +func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Nil(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// Nilf asserts that the specified object is nil. +// +// assert.Nilf(t, err, "error message %s", "formatted") +func Nilf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Nilf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// NoDirExists checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func NoDirExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoDirExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// NoDirExistsf checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func NoDirExistsf(t TestingT, path string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoDirExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if assert.NoError(t, err) { +// assert.Equal(t, expectedObj, actualObj) +// } +func NoError(t TestingT, err error, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoError(t, err, msgAndArgs...) { + return + } + t.FailNow() +} + +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if assert.NoErrorf(t, err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } +func NoErrorf(t TestingT, err error, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoErrorf(t, err, msg, args...) { + return + } + t.FailNow() +} + +// NoFileExists checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func NoFileExists(t TestingT, path string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoFileExists(t, path, msgAndArgs...) { + return + } + t.FailNow() +} + +// NoFileExistsf checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func NoFileExistsf(t TestingT, path string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NoFileExistsf(t, path, msg, args...) { + return + } + t.FailNow() +} + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// assert.NotContains(t, "Hello World", "Earth") +// assert.NotContains(t, ["Hello", "World"], "Earth") +// assert.NotContains(t, {"Hello": "World"}, "Earth") +func NotContains(t TestingT, s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotContains(t, s, contains, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// assert.NotContainsf(t, "Hello World", "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, ["Hello", "World"], "Earth", "error message %s", "formatted") +// assert.NotContainsf(t, {"Hello": "World"}, "Earth", "error message %s", "formatted") +func NotContainsf(t TestingT, s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotContainsf(t, s, contains, msg, args...) { + return + } + t.FailNow() +} + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmpty(t, obj) { +// assert.Equal(t, "two", obj[1]) +// } +func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEmpty(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if assert.NotEmptyf(t, obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func NotEmptyf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEmptyf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// NotEqual asserts that the specified values are NOT equal. +// +// assert.NotEqual(t, obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqual(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqual(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotEqualValues asserts that two objects are not equal even when converted to the same type +// +// assert.NotEqualValues(t, obj1, obj2) +func NotEqualValues(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqualValues(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotEqualValuesf asserts that two objects are not equal even when converted to the same type +// +// assert.NotEqualValuesf(t, obj1, obj2, "error message %s", "formatted") +func NotEqualValuesf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqualValuesf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// NotEqualf asserts that the specified values are NOT equal. +// +// assert.NotEqualf(t, obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func NotEqualf(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotEqualf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// NotErrorIs asserts that at none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func NotErrorIs(t TestingT, err error, target error, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotErrorIs(t, err, target, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotErrorIsf asserts that at none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func NotErrorIsf(t TestingT, err error, target error, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotErrorIsf(t, err, target, msg, args...) { + return + } + t.FailNow() +} + +// NotImplements asserts that an object does not implement the specified interface. +// +// assert.NotImplements(t, (*MyInterface)(nil), new(MyObject)) +func NotImplements(t TestingT, interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotImplements(t, interfaceObject, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotImplementsf asserts that an object does not implement the specified interface. +// +// assert.NotImplementsf(t, (*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +func NotImplementsf(t TestingT, interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotImplementsf(t, interfaceObject, object, msg, args...) { + return + } + t.FailNow() +} + +// NotNil asserts that the specified object is not nil. +// +// assert.NotNil(t, err) +func NotNil(t TestingT, object interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotNil(t, object, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotNilf asserts that the specified object is not nil. +// +// assert.NotNilf(t, err, "error message %s", "formatted") +func NotNilf(t TestingT, object interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotNilf(t, object, msg, args...) { + return + } + t.FailNow() +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// assert.NotPanics(t, func(){ RemainCalm() }) +func NotPanics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotPanics(t, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// assert.NotPanicsf(t, func(){ RemainCalm() }, "error message %s", "formatted") +func NotPanicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotPanicsf(t, f, msg, args...) { + return + } + t.FailNow() +} + +// NotRegexp asserts that a specified regexp does not match a string. +// +// assert.NotRegexp(t, regexp.MustCompile("starts"), "it's starting") +// assert.NotRegexp(t, "^start", "it's not starting") +func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotRegexp(t, rx, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotRegexpf asserts that a specified regexp does not match a string. +// +// assert.NotRegexpf(t, regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") +// assert.NotRegexpf(t, "^start", "it's not starting", "error message %s", "formatted") +func NotRegexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotRegexpf(t, rx, str, msg, args...) { + return + } + t.FailNow() +} + +// NotSame asserts that two pointers do not reference the same object. +// +// assert.NotSame(t, ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func NotSame(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSame(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotSamef asserts that two pointers do not reference the same object. +// +// assert.NotSamef(t, ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func NotSamef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSamef(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// NotSubset asserts that the specified list(array, slice...) or map does NOT +// contain all elements given in the specified subset list(array, slice...) or +// map. +// +// assert.NotSubset(t, [1, 3, 4], [1, 2]) +// assert.NotSubset(t, {"x": 1, "y": 2}, {"z": 3}) +func NotSubset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSubset(t, list, subset, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotSubsetf asserts that the specified list(array, slice...) or map does NOT +// contain all elements given in the specified subset list(array, slice...) or +// map. +// +// assert.NotSubsetf(t, [1, 3, 4], [1, 2], "error message %s", "formatted") +// assert.NotSubsetf(t, {"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted") +func NotSubsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotSubsetf(t, list, subset, msg, args...) { + return + } + t.FailNow() +} + +// NotZero asserts that i is not the zero value for its type. +func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotZero(t, i, msgAndArgs...) { + return + } + t.FailNow() +} + +// NotZerof asserts that i is not the zero value for its type. +func NotZerof(t TestingT, i interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.NotZerof(t, i, msg, args...) { + return + } + t.FailNow() +} + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panics(t, func(){ GoCrazy() }) +func Panics(t TestingT, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Panics(t, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// PanicsWithError asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// assert.PanicsWithError(t, "crazy error", func(){ GoCrazy() }) +func PanicsWithError(t TestingT, errString string, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithError(t, errString, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// assert.PanicsWithErrorf(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func PanicsWithErrorf(t TestingT, errString string, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithErrorf(t, errString, f, msg, args...) { + return + } + t.FailNow() +} + +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// assert.PanicsWithValue(t, "crazy error", func(){ GoCrazy() }) +func PanicsWithValue(t TestingT, expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithValue(t, expected, f, msgAndArgs...) { + return + } + t.FailNow() +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// assert.PanicsWithValuef(t, "crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func PanicsWithValuef(t TestingT, expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.PanicsWithValuef(t, expected, f, msg, args...) { + return + } + t.FailNow() +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// assert.Panicsf(t, func(){ GoCrazy() }, "error message %s", "formatted") +func Panicsf(t TestingT, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Panicsf(t, f, msg, args...) { + return + } + t.FailNow() +} + +// Positive asserts that the specified element is positive +// +// assert.Positive(t, 1) +// assert.Positive(t, 1.23) +func Positive(t TestingT, e interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Positive(t, e, msgAndArgs...) { + return + } + t.FailNow() +} + +// Positivef asserts that the specified element is positive +// +// assert.Positivef(t, 1, "error message %s", "formatted") +// assert.Positivef(t, 1.23, "error message %s", "formatted") +func Positivef(t TestingT, e interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Positivef(t, e, msg, args...) { + return + } + t.FailNow() +} + +// Regexp asserts that a specified regexp matches a string. +// +// assert.Regexp(t, regexp.MustCompile("start"), "it's starting") +// assert.Regexp(t, "start...$", "it's not starting") +func Regexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Regexp(t, rx, str, msgAndArgs...) { + return + } + t.FailNow() +} + +// Regexpf asserts that a specified regexp matches a string. +// +// assert.Regexpf(t, regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") +// assert.Regexpf(t, "start...$", "it's not starting", "error message %s", "formatted") +func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Regexpf(t, rx, str, msg, args...) { + return + } + t.FailNow() +} + +// Same asserts that two pointers reference the same object. +// +// assert.Same(t, ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func Same(t TestingT, expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Same(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// Samef asserts that two pointers reference the same object. +// +// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func Samef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Samef(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Subset asserts that the specified list(array, slice...) or map contains all +// elements given in the specified subset list(array, slice...) or map. +// +// assert.Subset(t, [1, 2, 3], [1, 2]) +// assert.Subset(t, {"x": 1, "y": 2}, {"x": 1}) +func Subset(t TestingT, list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Subset(t, list, subset, msgAndArgs...) { + return + } + t.FailNow() +} + +// Subsetf asserts that the specified list(array, slice...) or map contains all +// elements given in the specified subset list(array, slice...) or map. +// +// assert.Subsetf(t, [1, 2, 3], [1, 2], "error message %s", "formatted") +// assert.Subsetf(t, {"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted") +func Subsetf(t TestingT, list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Subsetf(t, list, subset, msg, args...) { + return + } + t.FailNow() +} + +// True asserts that the specified value is true. +// +// assert.True(t, myBool) +func True(t TestingT, value bool, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.True(t, value, msgAndArgs...) { + return + } + t.FailNow() +} + +// Truef asserts that the specified value is true. +// +// assert.Truef(t, myBool, "error message %s", "formatted") +func Truef(t TestingT, value bool, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Truef(t, value, msg, args...) { + return + } + t.FailNow() +} + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// assert.WithinDuration(t, time.Now(), time.Now(), 10*time.Second) +func WithinDuration(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.WithinDuration(t, expected, actual, delta, msgAndArgs...) { + return + } + t.FailNow() +} + +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// assert.WithinDurationf(t, time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func WithinDurationf(t TestingT, expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.WithinDurationf(t, expected, actual, delta, msg, args...) { + return + } + t.FailNow() +} + +// WithinRange asserts that a time is within a time range (inclusive). +// +// assert.WithinRange(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) +func WithinRange(t TestingT, actual time.Time, start time.Time, end time.Time, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.WithinRange(t, actual, start, end, msgAndArgs...) { + return + } + t.FailNow() +} + +// WithinRangef asserts that a time is within a time range (inclusive). +// +// assert.WithinRangef(t, time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") +func WithinRangef(t TestingT, actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.WithinRangef(t, actual, start, end, msg, args...) { + return + } + t.FailNow() +} + +// YAMLEq asserts that two YAML strings are equivalent. +func YAMLEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.YAMLEq(t, expected, actual, msgAndArgs...) { + return + } + t.FailNow() +} + +// YAMLEqf asserts that two YAML strings are equivalent. +func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.YAMLEqf(t, expected, actual, msg, args...) { + return + } + t.FailNow() +} + +// Zero asserts that i is the zero value for its type. +func Zero(t TestingT, i interface{}, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Zero(t, i, msgAndArgs...) { + return + } + t.FailNow() +} + +// Zerof asserts that i is the zero value for its type. +func Zerof(t TestingT, i interface{}, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.Zerof(t, i, msg, args...) { + return + } + t.FailNow() +} diff --git a/vendor/github.com/stretchr/testify/require/require.go.tmpl b/vendor/github.com/stretchr/testify/require/require.go.tmpl new file mode 100644 index 000000000..55e42ddeb --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require.go.tmpl @@ -0,0 +1,6 @@ +{{.Comment}} +func {{.DocInfo.Name}}(t TestingT, {{.Params}}) { + if h, ok := t.(tHelper); ok { h.Helper() } + if assert.{{.DocInfo.Name}}(t, {{.ForwardedParams}}) { return } + t.FailNow() +} diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go b/vendor/github.com/stretchr/testify/require/require_forward.go new file mode 100644 index 000000000..eee8310a5 --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require_forward.go @@ -0,0 +1,1622 @@ +// Code generated with github.com/stretchr/testify/_codegen; DO NOT EDIT. + +package require + +import ( + assert "github.com/stretchr/testify/assert" + http "net/http" + url "net/url" + time "time" +) + +// Condition uses a Comparison to assert a complex condition. +func (a *Assertions) Condition(comp assert.Comparison, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Condition(a.t, comp, msgAndArgs...) +} + +// Conditionf uses a Comparison to assert a complex condition. +func (a *Assertions) Conditionf(comp assert.Comparison, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Conditionf(a.t, comp, msg, args...) +} + +// Contains asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Contains("Hello World", "World") +// a.Contains(["Hello", "World"], "World") +// a.Contains({"Hello": "World"}, "Hello") +func (a *Assertions) Contains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Contains(a.t, s, contains, msgAndArgs...) +} + +// Containsf asserts that the specified string, list(array, slice...) or map contains the +// specified substring or element. +// +// a.Containsf("Hello World", "World", "error message %s", "formatted") +// a.Containsf(["Hello", "World"], "World", "error message %s", "formatted") +// a.Containsf({"Hello": "World"}, "Hello", "error message %s", "formatted") +func (a *Assertions) Containsf(s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Containsf(a.t, s, contains, msg, args...) +} + +// DirExists checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + DirExists(a.t, path, msgAndArgs...) +} + +// DirExistsf checks whether a directory exists in the given path. It also fails +// if the path is a file rather a directory or there is an error checking whether it exists. +func (a *Assertions) DirExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + DirExistsf(a.t, path, msg, args...) +} + +// ElementsMatch asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatch([1, 3, 2, 3], [1, 3, 3, 2]) +func (a *Assertions) ElementsMatch(listA interface{}, listB interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ElementsMatch(a.t, listA, listB, msgAndArgs...) +} + +// ElementsMatchf asserts that the specified listA(array, slice...) is equal to specified +// listB(array, slice...) ignoring the order of the elements. If there are duplicate elements, +// the number of appearances of each of them in both lists should match. +// +// a.ElementsMatchf([1, 3, 2, 3], [1, 3, 3, 2], "error message %s", "formatted") +func (a *Assertions) ElementsMatchf(listA interface{}, listB interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ElementsMatchf(a.t, listA, listB, msg, args...) +} + +// Empty asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Empty(obj) +func (a *Assertions) Empty(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Empty(a.t, object, msgAndArgs...) +} + +// Emptyf asserts that the specified object is empty. I.e. nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// a.Emptyf(obj, "error message %s", "formatted") +func (a *Assertions) Emptyf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Emptyf(a.t, object, msg, args...) +} + +// Equal asserts that two objects are equal. +// +// a.Equal(123, 123) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func (a *Assertions) Equal(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Equal(a.t, expected, actual, msgAndArgs...) +} + +// EqualError asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// a.EqualError(err, expectedErrorString) +func (a *Assertions) EqualError(theError error, errString string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualError(a.t, theError, errString, msgAndArgs...) +} + +// EqualErrorf asserts that a function returned an error (i.e. not `nil`) +// and that it is equal to the provided error. +// +// actualObj, err := SomeFunction() +// a.EqualErrorf(err, expectedErrorString, "error message %s", "formatted") +func (a *Assertions) EqualErrorf(theError error, errString string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualErrorf(a.t, theError, errString, msg, args...) +} + +// EqualExportedValues asserts that the types of two objects are equal and their public +// fields are also equal. This is useful for comparing structs that have private fields +// that could potentially differ. +// +// type S struct { +// Exported int +// notExported int +// } +// a.EqualExportedValues(S{1, 2}, S{1, 3}) => true +// a.EqualExportedValues(S{1, 2}, S{2, 3}) => false +func (a *Assertions) EqualExportedValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualExportedValues(a.t, expected, actual, msgAndArgs...) +} + +// EqualExportedValuesf asserts that the types of two objects are equal and their public +// fields are also equal. This is useful for comparing structs that have private fields +// that could potentially differ. +// +// type S struct { +// Exported int +// notExported int +// } +// a.EqualExportedValuesf(S{1, 2}, S{1, 3}, "error message %s", "formatted") => true +// a.EqualExportedValuesf(S{1, 2}, S{2, 3}, "error message %s", "formatted") => false +func (a *Assertions) EqualExportedValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualExportedValuesf(a.t, expected, actual, msg, args...) +} + +// EqualValues asserts that two objects are equal or convertible to the same types +// and equal. +// +// a.EqualValues(uint32(123), int32(123)) +func (a *Assertions) EqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualValues(a.t, expected, actual, msgAndArgs...) +} + +// EqualValuesf asserts that two objects are equal or convertible to the same types +// and equal. +// +// a.EqualValuesf(uint32(123), int32(123), "error message %s", "formatted") +func (a *Assertions) EqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EqualValuesf(a.t, expected, actual, msg, args...) +} + +// Equalf asserts that two objects are equal. +// +// a.Equalf(123, 123, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). Function equality +// cannot be determined and will always fail. +func (a *Assertions) Equalf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Equalf(a.t, expected, actual, msg, args...) +} + +// Error asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Error(err) { +// assert.Equal(t, expectedError, err) +// } +func (a *Assertions) Error(err error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Error(a.t, err, msgAndArgs...) +} + +// ErrorAs asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. +// This is a wrapper for errors.As. +func (a *Assertions) ErrorAs(err error, target interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorAs(a.t, err, target, msgAndArgs...) +} + +// ErrorAsf asserts that at least one of the errors in err's chain matches target, and if so, sets target to that error value. +// This is a wrapper for errors.As. +func (a *Assertions) ErrorAsf(err error, target interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorAsf(a.t, err, target, msg, args...) +} + +// ErrorContains asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// a.ErrorContains(err, expectedErrorSubString) +func (a *Assertions) ErrorContains(theError error, contains string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorContains(a.t, theError, contains, msgAndArgs...) +} + +// ErrorContainsf asserts that a function returned an error (i.e. not `nil`) +// and that the error contains the specified substring. +// +// actualObj, err := SomeFunction() +// a.ErrorContainsf(err, expectedErrorSubString, "error message %s", "formatted") +func (a *Assertions) ErrorContainsf(theError error, contains string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorContainsf(a.t, theError, contains, msg, args...) +} + +// ErrorIs asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) ErrorIs(err error, target error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorIs(a.t, err, target, msgAndArgs...) +} + +// ErrorIsf asserts that at least one of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) ErrorIsf(err error, target error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + ErrorIsf(a.t, err, target, msg, args...) +} + +// Errorf asserts that a function returned an error (i.e. not `nil`). +// +// actualObj, err := SomeFunction() +// if a.Errorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedErrorf, err) +// } +func (a *Assertions) Errorf(err error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Errorf(a.t, err, msg, args...) +} + +// Eventually asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// a.Eventually(func() bool { return true; }, time.Second, 10*time.Millisecond) +func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Eventually(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// EventuallyWithT asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. In contrast to Eventually, +// it supplies a CollectT to the condition function, so that the condition +// function can use the CollectT to call other assertions. +// The condition is considered "met" if no errors are raised in a tick. +// The supplied CollectT collects all errors from one tick (if there are any). +// If the condition is not met before waitFor, the collected errors of +// the last tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// a.EventuallyWithT(func(c *assert.CollectT) { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +func (a *Assertions) EventuallyWithT(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EventuallyWithT(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// EventuallyWithTf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. In contrast to Eventually, +// it supplies a CollectT to the condition function, so that the condition +// function can use the CollectT to call other assertions. +// The condition is considered "met" if no errors are raised in a tick. +// The supplied CollectT collects all errors from one tick (if there are any). +// If the condition is not met before waitFor, the collected errors of +// the last tick are copied to t. +// +// externalValue := false +// go func() { +// time.Sleep(8*time.Second) +// externalValue = true +// }() +// a.EventuallyWithTf(func(c *assert.CollectT, "error message %s", "formatted") { +// // add assertions as needed; any assertion failure will fail the current tick +// assert.True(c, externalValue, "expected 'externalValue' to be true") +// }, 1*time.Second, 10*time.Second, "external state has not changed to 'true'; still false") +func (a *Assertions) EventuallyWithTf(condition func(collect *assert.CollectT), waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + EventuallyWithTf(a.t, condition, waitFor, tick, msg, args...) +} + +// Eventuallyf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// a.Eventuallyf(func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func (a *Assertions) Eventuallyf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Eventuallyf(a.t, condition, waitFor, tick, msg, args...) +} + +// Exactly asserts that two objects are equal in value and type. +// +// a.Exactly(int32(123), int64(123)) +func (a *Assertions) Exactly(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Exactly(a.t, expected, actual, msgAndArgs...) +} + +// Exactlyf asserts that two objects are equal in value and type. +// +// a.Exactlyf(int32(123), int64(123), "error message %s", "formatted") +func (a *Assertions) Exactlyf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Exactlyf(a.t, expected, actual, msg, args...) +} + +// Fail reports a failure through +func (a *Assertions) Fail(failureMessage string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Fail(a.t, failureMessage, msgAndArgs...) +} + +// FailNow fails test +func (a *Assertions) FailNow(failureMessage string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FailNow(a.t, failureMessage, msgAndArgs...) +} + +// FailNowf fails test +func (a *Assertions) FailNowf(failureMessage string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FailNowf(a.t, failureMessage, msg, args...) +} + +// Failf reports a failure through +func (a *Assertions) Failf(failureMessage string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Failf(a.t, failureMessage, msg, args...) +} + +// False asserts that the specified value is false. +// +// a.False(myBool) +func (a *Assertions) False(value bool, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + False(a.t, value, msgAndArgs...) +} + +// Falsef asserts that the specified value is false. +// +// a.Falsef(myBool, "error message %s", "formatted") +func (a *Assertions) Falsef(value bool, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Falsef(a.t, value, msg, args...) +} + +// FileExists checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FileExists(a.t, path, msgAndArgs...) +} + +// FileExistsf checks whether a file exists in the given path. It also fails if +// the path points to a directory or there is an error when trying to check the file. +func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + FileExistsf(a.t, path, msg, args...) +} + +// Greater asserts that the first element is greater than the second +// +// a.Greater(2, 1) +// a.Greater(float64(2), float64(1)) +// a.Greater("b", "a") +func (a *Assertions) Greater(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Greater(a.t, e1, e2, msgAndArgs...) +} + +// GreaterOrEqual asserts that the first element is greater than or equal to the second +// +// a.GreaterOrEqual(2, 1) +// a.GreaterOrEqual(2, 2) +// a.GreaterOrEqual("b", "a") +// a.GreaterOrEqual("b", "b") +func (a *Assertions) GreaterOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + GreaterOrEqual(a.t, e1, e2, msgAndArgs...) +} + +// GreaterOrEqualf asserts that the first element is greater than or equal to the second +// +// a.GreaterOrEqualf(2, 1, "error message %s", "formatted") +// a.GreaterOrEqualf(2, 2, "error message %s", "formatted") +// a.GreaterOrEqualf("b", "a", "error message %s", "formatted") +// a.GreaterOrEqualf("b", "b", "error message %s", "formatted") +func (a *Assertions) GreaterOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + GreaterOrEqualf(a.t, e1, e2, msg, args...) +} + +// Greaterf asserts that the first element is greater than the second +// +// a.Greaterf(2, 1, "error message %s", "formatted") +// a.Greaterf(float64(2), float64(1), "error message %s", "formatted") +// a.Greaterf("b", "a", "error message %s", "formatted") +func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Greaterf(a.t, e1, e2, msg, args...) +} + +// HTTPBodyContains asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyContains(a.t, handler, method, url, values, str, msgAndArgs...) +} + +// HTTPBodyContainsf asserts that a specified handler returns a +// body that contains a string. +// +// a.HTTPBodyContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyContainsf(a.t, handler, method, url, values, str, msg, args...) +} + +// HTTPBodyNotContains asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContains(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContains(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyNotContains(a.t, handler, method, url, values, str, msgAndArgs...) +} + +// HTTPBodyNotContainsf asserts that a specified handler returns a +// body that does not contain a string. +// +// a.HTTPBodyNotContainsf(myHandler, "GET", "www.google.com", nil, "I'm Feeling Lucky", "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPBodyNotContainsf(handler http.HandlerFunc, method string, url string, values url.Values, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPBodyNotContainsf(a.t, handler, method, url, values, str, msg, args...) +} + +// HTTPError asserts that a specified handler returns an error status code. +// +// a.HTTPError(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPError(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPError(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPErrorf asserts that a specified handler returns an error status code. +// +// a.HTTPErrorf(myHandler, "POST", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPErrorf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPErrorf(a.t, handler, method, url, values, msg, args...) +} + +// HTTPRedirect asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirect(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPRedirect(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPRedirect(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPRedirectf asserts that a specified handler returns a redirect status code. +// +// a.HTTPRedirectf(myHandler, "GET", "/a/b/c", url.Values{"a": []string{"b", "c"}} +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPRedirectf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPRedirectf(a.t, handler, method, url, values, msg, args...) +} + +// HTTPStatusCode asserts that a specified handler returns a specified status code. +// +// a.HTTPStatusCode(myHandler, "GET", "/notImplemented", nil, 501) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPStatusCode(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPStatusCode(a.t, handler, method, url, values, statuscode, msgAndArgs...) +} + +// HTTPStatusCodef asserts that a specified handler returns a specified status code. +// +// a.HTTPStatusCodef(myHandler, "GET", "/notImplemented", nil, 501, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPStatusCodef(handler http.HandlerFunc, method string, url string, values url.Values, statuscode int, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPStatusCodef(a.t, handler, method, url, values, statuscode, msg, args...) +} + +// HTTPSuccess asserts that a specified handler returns a success status code. +// +// a.HTTPSuccess(myHandler, "POST", "http://www.google.com", nil) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccess(handler http.HandlerFunc, method string, url string, values url.Values, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPSuccess(a.t, handler, method, url, values, msgAndArgs...) +} + +// HTTPSuccessf asserts that a specified handler returns a success status code. +// +// a.HTTPSuccessf(myHandler, "POST", "http://www.google.com", nil, "error message %s", "formatted") +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) HTTPSuccessf(handler http.HandlerFunc, method string, url string, values url.Values, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + HTTPSuccessf(a.t, handler, method, url, values, msg, args...) +} + +// Implements asserts that an object is implemented by the specified interface. +// +// a.Implements((*MyInterface)(nil), new(MyObject)) +func (a *Assertions) Implements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Implements(a.t, interfaceObject, object, msgAndArgs...) +} + +// Implementsf asserts that an object is implemented by the specified interface. +// +// a.Implementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +func (a *Assertions) Implementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Implementsf(a.t, interfaceObject, object, msg, args...) +} + +// InDelta asserts that the two numerals are within delta of each other. +// +// a.InDelta(math.Pi, 22/7.0, 0.01) +func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDelta(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaMapValues(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaMapValuesf(a.t, expected, actual, delta, msg, args...) +} + +// InDeltaSlice is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlice(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaSlice(a.t, expected, actual, delta, msgAndArgs...) +} + +// InDeltaSlicef is the same as InDelta, except it compares two slices. +func (a *Assertions) InDeltaSlicef(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaSlicef(a.t, expected, actual, delta, msg, args...) +} + +// InDeltaf asserts that the two numerals are within delta of each other. +// +// a.InDeltaf(math.Pi, 22/7.0, 0.01, "error message %s", "formatted") +func (a *Assertions) InDeltaf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InDeltaf(a.t, expected, actual, delta, msg, args...) +} + +// InEpsilon asserts that expected and actual have a relative error less than epsilon +func (a *Assertions) InEpsilon(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilon(a.t, expected, actual, epsilon, msgAndArgs...) +} + +// InEpsilonSlice is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlice(expected interface{}, actual interface{}, epsilon float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonSlice(a.t, expected, actual, epsilon, msgAndArgs...) +} + +// InEpsilonSlicef is the same as InEpsilon, except it compares each value from two slices. +func (a *Assertions) InEpsilonSlicef(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonSlicef(a.t, expected, actual, epsilon, msg, args...) +} + +// InEpsilonf asserts that expected and actual have a relative error less than epsilon +func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilon float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InEpsilonf(a.t, expected, actual, epsilon, msg, args...) +} + +// IsDecreasing asserts that the collection is decreasing +// +// a.IsDecreasing([]int{2, 1, 0}) +// a.IsDecreasing([]float{2, 1}) +// a.IsDecreasing([]string{"b", "a"}) +func (a *Assertions) IsDecreasing(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsDecreasing(a.t, object, msgAndArgs...) +} + +// IsDecreasingf asserts that the collection is decreasing +// +// a.IsDecreasingf([]int{2, 1, 0}, "error message %s", "formatted") +// a.IsDecreasingf([]float{2, 1}, "error message %s", "formatted") +// a.IsDecreasingf([]string{"b", "a"}, "error message %s", "formatted") +func (a *Assertions) IsDecreasingf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsDecreasingf(a.t, object, msg, args...) +} + +// IsIncreasing asserts that the collection is increasing +// +// a.IsIncreasing([]int{1, 2, 3}) +// a.IsIncreasing([]float{1, 2}) +// a.IsIncreasing([]string{"a", "b"}) +func (a *Assertions) IsIncreasing(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsIncreasing(a.t, object, msgAndArgs...) +} + +// IsIncreasingf asserts that the collection is increasing +// +// a.IsIncreasingf([]int{1, 2, 3}, "error message %s", "formatted") +// a.IsIncreasingf([]float{1, 2}, "error message %s", "formatted") +// a.IsIncreasingf([]string{"a", "b"}, "error message %s", "formatted") +func (a *Assertions) IsIncreasingf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsIncreasingf(a.t, object, msg, args...) +} + +// IsNonDecreasing asserts that the collection is not decreasing +// +// a.IsNonDecreasing([]int{1, 1, 2}) +// a.IsNonDecreasing([]float{1, 2}) +// a.IsNonDecreasing([]string{"a", "b"}) +func (a *Assertions) IsNonDecreasing(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsNonDecreasing(a.t, object, msgAndArgs...) +} + +// IsNonDecreasingf asserts that the collection is not decreasing +// +// a.IsNonDecreasingf([]int{1, 1, 2}, "error message %s", "formatted") +// a.IsNonDecreasingf([]float{1, 2}, "error message %s", "formatted") +// a.IsNonDecreasingf([]string{"a", "b"}, "error message %s", "formatted") +func (a *Assertions) IsNonDecreasingf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsNonDecreasingf(a.t, object, msg, args...) +} + +// IsNonIncreasing asserts that the collection is not increasing +// +// a.IsNonIncreasing([]int{2, 1, 1}) +// a.IsNonIncreasing([]float{2, 1}) +// a.IsNonIncreasing([]string{"b", "a"}) +func (a *Assertions) IsNonIncreasing(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsNonIncreasing(a.t, object, msgAndArgs...) +} + +// IsNonIncreasingf asserts that the collection is not increasing +// +// a.IsNonIncreasingf([]int{2, 1, 1}, "error message %s", "formatted") +// a.IsNonIncreasingf([]float{2, 1}, "error message %s", "formatted") +// a.IsNonIncreasingf([]string{"b", "a"}, "error message %s", "formatted") +func (a *Assertions) IsNonIncreasingf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsNonIncreasingf(a.t, object, msg, args...) +} + +// IsType asserts that the specified objects are of the same type. +func (a *Assertions) IsType(expectedType interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsType(a.t, expectedType, object, msgAndArgs...) +} + +// IsTypef asserts that the specified objects are of the same type. +func (a *Assertions) IsTypef(expectedType interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + IsTypef(a.t, expectedType, object, msg, args...) +} + +// JSONEq asserts that two JSON strings are equivalent. +// +// a.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + JSONEq(a.t, expected, actual, msgAndArgs...) +} + +// JSONEqf asserts that two JSON strings are equivalent. +// +// a.JSONEqf(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, "error message %s", "formatted") +func (a *Assertions) JSONEqf(expected string, actual string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + JSONEqf(a.t, expected, actual, msg, args...) +} + +// Len asserts that the specified object has specific length. +// Len also fails if the object has a type that len() not accept. +// +// a.Len(mySlice, 3) +func (a *Assertions) Len(object interface{}, length int, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Len(a.t, object, length, msgAndArgs...) +} + +// Lenf asserts that the specified object has specific length. +// Lenf also fails if the object has a type that len() not accept. +// +// a.Lenf(mySlice, 3, "error message %s", "formatted") +func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Lenf(a.t, object, length, msg, args...) +} + +// Less asserts that the first element is less than the second +// +// a.Less(1, 2) +// a.Less(float64(1), float64(2)) +// a.Less("a", "b") +func (a *Assertions) Less(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Less(a.t, e1, e2, msgAndArgs...) +} + +// LessOrEqual asserts that the first element is less than or equal to the second +// +// a.LessOrEqual(1, 2) +// a.LessOrEqual(2, 2) +// a.LessOrEqual("a", "b") +// a.LessOrEqual("b", "b") +func (a *Assertions) LessOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + LessOrEqual(a.t, e1, e2, msgAndArgs...) +} + +// LessOrEqualf asserts that the first element is less than or equal to the second +// +// a.LessOrEqualf(1, 2, "error message %s", "formatted") +// a.LessOrEqualf(2, 2, "error message %s", "formatted") +// a.LessOrEqualf("a", "b", "error message %s", "formatted") +// a.LessOrEqualf("b", "b", "error message %s", "formatted") +func (a *Assertions) LessOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + LessOrEqualf(a.t, e1, e2, msg, args...) +} + +// Lessf asserts that the first element is less than the second +// +// a.Lessf(1, 2, "error message %s", "formatted") +// a.Lessf(float64(1), float64(2), "error message %s", "formatted") +// a.Lessf("a", "b", "error message %s", "formatted") +func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Lessf(a.t, e1, e2, msg, args...) +} + +// Negative asserts that the specified element is negative +// +// a.Negative(-1) +// a.Negative(-1.23) +func (a *Assertions) Negative(e interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Negative(a.t, e, msgAndArgs...) +} + +// Negativef asserts that the specified element is negative +// +// a.Negativef(-1, "error message %s", "formatted") +// a.Negativef(-1.23, "error message %s", "formatted") +func (a *Assertions) Negativef(e interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Negativef(a.t, e, msg, args...) +} + +// Never asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// a.Never(func() bool { return false; }, time.Second, 10*time.Millisecond) +func (a *Assertions) Never(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Never(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// Neverf asserts that the given condition doesn't satisfy in waitFor time, +// periodically checking the target function each tick. +// +// a.Neverf(func() bool { return false; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func (a *Assertions) Neverf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Neverf(a.t, condition, waitFor, tick, msg, args...) +} + +// Nil asserts that the specified object is nil. +// +// a.Nil(err) +func (a *Assertions) Nil(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Nil(a.t, object, msgAndArgs...) +} + +// Nilf asserts that the specified object is nil. +// +// a.Nilf(err, "error message %s", "formatted") +func (a *Assertions) Nilf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Nilf(a.t, object, msg, args...) +} + +// NoDirExists checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func (a *Assertions) NoDirExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoDirExists(a.t, path, msgAndArgs...) +} + +// NoDirExistsf checks whether a directory does not exist in the given path. +// It fails if the path points to an existing _directory_ only. +func (a *Assertions) NoDirExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoDirExistsf(a.t, path, msg, args...) +} + +// NoError asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoError(err) { +// assert.Equal(t, expectedObj, actualObj) +// } +func (a *Assertions) NoError(err error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoError(a.t, err, msgAndArgs...) +} + +// NoErrorf asserts that a function returned no error (i.e. `nil`). +// +// actualObj, err := SomeFunction() +// if a.NoErrorf(err, "error message %s", "formatted") { +// assert.Equal(t, expectedObj, actualObj) +// } +func (a *Assertions) NoErrorf(err error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoErrorf(a.t, err, msg, args...) +} + +// NoFileExists checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func (a *Assertions) NoFileExists(path string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoFileExists(a.t, path, msgAndArgs...) +} + +// NoFileExistsf checks whether a file does not exist in a given path. It fails +// if the path points to an existing _file_ only. +func (a *Assertions) NoFileExistsf(path string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NoFileExistsf(a.t, path, msg, args...) +} + +// NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContains("Hello World", "Earth") +// a.NotContains(["Hello", "World"], "Earth") +// a.NotContains({"Hello": "World"}, "Earth") +func (a *Assertions) NotContains(s interface{}, contains interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotContains(a.t, s, contains, msgAndArgs...) +} + +// NotContainsf asserts that the specified string, list(array, slice...) or map does NOT contain the +// specified substring or element. +// +// a.NotContainsf("Hello World", "Earth", "error message %s", "formatted") +// a.NotContainsf(["Hello", "World"], "Earth", "error message %s", "formatted") +// a.NotContainsf({"Hello": "World"}, "Earth", "error message %s", "formatted") +func (a *Assertions) NotContainsf(s interface{}, contains interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotContainsf(a.t, s, contains, msg, args...) +} + +// NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmpty(obj) { +// assert.Equal(t, "two", obj[1]) +// } +func (a *Assertions) NotEmpty(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEmpty(a.t, object, msgAndArgs...) +} + +// NotEmptyf asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either +// a slice or a channel with len == 0. +// +// if a.NotEmptyf(obj, "error message %s", "formatted") { +// assert.Equal(t, "two", obj[1]) +// } +func (a *Assertions) NotEmptyf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEmptyf(a.t, object, msg, args...) +} + +// NotEqual asserts that the specified values are NOT equal. +// +// a.NotEqual(obj1, obj2) +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func (a *Assertions) NotEqual(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqual(a.t, expected, actual, msgAndArgs...) +} + +// NotEqualValues asserts that two objects are not equal even when converted to the same type +// +// a.NotEqualValues(obj1, obj2) +func (a *Assertions) NotEqualValues(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqualValues(a.t, expected, actual, msgAndArgs...) +} + +// NotEqualValuesf asserts that two objects are not equal even when converted to the same type +// +// a.NotEqualValuesf(obj1, obj2, "error message %s", "formatted") +func (a *Assertions) NotEqualValuesf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqualValuesf(a.t, expected, actual, msg, args...) +} + +// NotEqualf asserts that the specified values are NOT equal. +// +// a.NotEqualf(obj1, obj2, "error message %s", "formatted") +// +// Pointer variable equality is determined based on the equality of the +// referenced values (as opposed to the memory addresses). +func (a *Assertions) NotEqualf(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotEqualf(a.t, expected, actual, msg, args...) +} + +// NotErrorIs asserts that at none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) NotErrorIs(err error, target error, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotErrorIs(a.t, err, target, msgAndArgs...) +} + +// NotErrorIsf asserts that at none of the errors in err's chain matches target. +// This is a wrapper for errors.Is. +func (a *Assertions) NotErrorIsf(err error, target error, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotErrorIsf(a.t, err, target, msg, args...) +} + +// NotImplements asserts that an object does not implement the specified interface. +// +// a.NotImplements((*MyInterface)(nil), new(MyObject)) +func (a *Assertions) NotImplements(interfaceObject interface{}, object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotImplements(a.t, interfaceObject, object, msgAndArgs...) +} + +// NotImplementsf asserts that an object does not implement the specified interface. +// +// a.NotImplementsf((*MyInterface)(nil), new(MyObject), "error message %s", "formatted") +func (a *Assertions) NotImplementsf(interfaceObject interface{}, object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotImplementsf(a.t, interfaceObject, object, msg, args...) +} + +// NotNil asserts that the specified object is not nil. +// +// a.NotNil(err) +func (a *Assertions) NotNil(object interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotNil(a.t, object, msgAndArgs...) +} + +// NotNilf asserts that the specified object is not nil. +// +// a.NotNilf(err, "error message %s", "formatted") +func (a *Assertions) NotNilf(object interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotNilf(a.t, object, msg, args...) +} + +// NotPanics asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanics(func(){ RemainCalm() }) +func (a *Assertions) NotPanics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotPanics(a.t, f, msgAndArgs...) +} + +// NotPanicsf asserts that the code inside the specified PanicTestFunc does NOT panic. +// +// a.NotPanicsf(func(){ RemainCalm() }, "error message %s", "formatted") +func (a *Assertions) NotPanicsf(f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotPanicsf(a.t, f, msg, args...) +} + +// NotRegexp asserts that a specified regexp does not match a string. +// +// a.NotRegexp(regexp.MustCompile("starts"), "it's starting") +// a.NotRegexp("^start", "it's not starting") +func (a *Assertions) NotRegexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotRegexp(a.t, rx, str, msgAndArgs...) +} + +// NotRegexpf asserts that a specified regexp does not match a string. +// +// a.NotRegexpf(regexp.MustCompile("starts"), "it's starting", "error message %s", "formatted") +// a.NotRegexpf("^start", "it's not starting", "error message %s", "formatted") +func (a *Assertions) NotRegexpf(rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotRegexpf(a.t, rx, str, msg, args...) +} + +// NotSame asserts that two pointers do not reference the same object. +// +// a.NotSame(ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) NotSame(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSame(a.t, expected, actual, msgAndArgs...) +} + +// NotSamef asserts that two pointers do not reference the same object. +// +// a.NotSamef(ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) NotSamef(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSamef(a.t, expected, actual, msg, args...) +} + +// NotSubset asserts that the specified list(array, slice...) or map does NOT +// contain all elements given in the specified subset list(array, slice...) or +// map. +// +// a.NotSubset([1, 3, 4], [1, 2]) +// a.NotSubset({"x": 1, "y": 2}, {"z": 3}) +func (a *Assertions) NotSubset(list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSubset(a.t, list, subset, msgAndArgs...) +} + +// NotSubsetf asserts that the specified list(array, slice...) or map does NOT +// contain all elements given in the specified subset list(array, slice...) or +// map. +// +// a.NotSubsetf([1, 3, 4], [1, 2], "error message %s", "formatted") +// a.NotSubsetf({"x": 1, "y": 2}, {"z": 3}, "error message %s", "formatted") +func (a *Assertions) NotSubsetf(list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotSubsetf(a.t, list, subset, msg, args...) +} + +// NotZero asserts that i is not the zero value for its type. +func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotZero(a.t, i, msgAndArgs...) +} + +// NotZerof asserts that i is not the zero value for its type. +func (a *Assertions) NotZerof(i interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + NotZerof(a.t, i, msg, args...) +} + +// Panics asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panics(func(){ GoCrazy() }) +func (a *Assertions) Panics(f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Panics(a.t, f, msgAndArgs...) +} + +// PanicsWithError asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// a.PanicsWithError("crazy error", func(){ GoCrazy() }) +func (a *Assertions) PanicsWithError(errString string, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithError(a.t, errString, f, msgAndArgs...) +} + +// PanicsWithErrorf asserts that the code inside the specified PanicTestFunc +// panics, and that the recovered panic value is an error that satisfies the +// EqualError comparison. +// +// a.PanicsWithErrorf("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) PanicsWithErrorf(errString string, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithErrorf(a.t, errString, f, msg, args...) +} + +// PanicsWithValue asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValue("crazy error", func(){ GoCrazy() }) +func (a *Assertions) PanicsWithValue(expected interface{}, f assert.PanicTestFunc, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithValue(a.t, expected, f, msgAndArgs...) +} + +// PanicsWithValuef asserts that the code inside the specified PanicTestFunc panics, and that +// the recovered panic value equals the expected panic value. +// +// a.PanicsWithValuef("crazy error", func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) PanicsWithValuef(expected interface{}, f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + PanicsWithValuef(a.t, expected, f, msg, args...) +} + +// Panicsf asserts that the code inside the specified PanicTestFunc panics. +// +// a.Panicsf(func(){ GoCrazy() }, "error message %s", "formatted") +func (a *Assertions) Panicsf(f assert.PanicTestFunc, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Panicsf(a.t, f, msg, args...) +} + +// Positive asserts that the specified element is positive +// +// a.Positive(1) +// a.Positive(1.23) +func (a *Assertions) Positive(e interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Positive(a.t, e, msgAndArgs...) +} + +// Positivef asserts that the specified element is positive +// +// a.Positivef(1, "error message %s", "formatted") +// a.Positivef(1.23, "error message %s", "formatted") +func (a *Assertions) Positivef(e interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Positivef(a.t, e, msg, args...) +} + +// Regexp asserts that a specified regexp matches a string. +// +// a.Regexp(regexp.MustCompile("start"), "it's starting") +// a.Regexp("start...$", "it's not starting") +func (a *Assertions) Regexp(rx interface{}, str interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Regexp(a.t, rx, str, msgAndArgs...) +} + +// Regexpf asserts that a specified regexp matches a string. +// +// a.Regexpf(regexp.MustCompile("start"), "it's starting", "error message %s", "formatted") +// a.Regexpf("start...$", "it's not starting", "error message %s", "formatted") +func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Regexpf(a.t, rx, str, msg, args...) +} + +// Same asserts that two pointers reference the same object. +// +// a.Same(ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) Same(expected interface{}, actual interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Same(a.t, expected, actual, msgAndArgs...) +} + +// Samef asserts that two pointers reference the same object. +// +// a.Samef(ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) Samef(expected interface{}, actual interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Samef(a.t, expected, actual, msg, args...) +} + +// Subset asserts that the specified list(array, slice...) or map contains all +// elements given in the specified subset list(array, slice...) or map. +// +// a.Subset([1, 2, 3], [1, 2]) +// a.Subset({"x": 1, "y": 2}, {"x": 1}) +func (a *Assertions) Subset(list interface{}, subset interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Subset(a.t, list, subset, msgAndArgs...) +} + +// Subsetf asserts that the specified list(array, slice...) or map contains all +// elements given in the specified subset list(array, slice...) or map. +// +// a.Subsetf([1, 2, 3], [1, 2], "error message %s", "formatted") +// a.Subsetf({"x": 1, "y": 2}, {"x": 1}, "error message %s", "formatted") +func (a *Assertions) Subsetf(list interface{}, subset interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Subsetf(a.t, list, subset, msg, args...) +} + +// True asserts that the specified value is true. +// +// a.True(myBool) +func (a *Assertions) True(value bool, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + True(a.t, value, msgAndArgs...) +} + +// Truef asserts that the specified value is true. +// +// a.Truef(myBool, "error message %s", "formatted") +func (a *Assertions) Truef(value bool, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Truef(a.t, value, msg, args...) +} + +// WithinDuration asserts that the two times are within duration delta of each other. +// +// a.WithinDuration(time.Now(), time.Now(), 10*time.Second) +func (a *Assertions) WithinDuration(expected time.Time, actual time.Time, delta time.Duration, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinDuration(a.t, expected, actual, delta, msgAndArgs...) +} + +// WithinDurationf asserts that the two times are within duration delta of each other. +// +// a.WithinDurationf(time.Now(), time.Now(), 10*time.Second, "error message %s", "formatted") +func (a *Assertions) WithinDurationf(expected time.Time, actual time.Time, delta time.Duration, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinDurationf(a.t, expected, actual, delta, msg, args...) +} + +// WithinRange asserts that a time is within a time range (inclusive). +// +// a.WithinRange(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second)) +func (a *Assertions) WithinRange(actual time.Time, start time.Time, end time.Time, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinRange(a.t, actual, start, end, msgAndArgs...) +} + +// WithinRangef asserts that a time is within a time range (inclusive). +// +// a.WithinRangef(time.Now(), time.Now().Add(-time.Second), time.Now().Add(time.Second), "error message %s", "formatted") +func (a *Assertions) WithinRangef(actual time.Time, start time.Time, end time.Time, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + WithinRangef(a.t, actual, start, end, msg, args...) +} + +// YAMLEq asserts that two YAML strings are equivalent. +func (a *Assertions) YAMLEq(expected string, actual string, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + YAMLEq(a.t, expected, actual, msgAndArgs...) +} + +// YAMLEqf asserts that two YAML strings are equivalent. +func (a *Assertions) YAMLEqf(expected string, actual string, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + YAMLEqf(a.t, expected, actual, msg, args...) +} + +// Zero asserts that i is the zero value for its type. +func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Zero(a.t, i, msgAndArgs...) +} + +// Zerof asserts that i is the zero value for its type. +func (a *Assertions) Zerof(i interface{}, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + Zerof(a.t, i, msg, args...) +} diff --git a/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl b/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl new file mode 100644 index 000000000..54124df1d --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/require_forward.go.tmpl @@ -0,0 +1,5 @@ +{{.CommentWithoutT "a"}} +func (a *Assertions) {{.DocInfo.Name}}({{.Params}}) { + if h, ok := a.t.(tHelper); ok { h.Helper() } + {{.DocInfo.Name}}(a.t, {{.ForwardedParams}}) +} diff --git a/vendor/github.com/stretchr/testify/require/requirements.go b/vendor/github.com/stretchr/testify/require/requirements.go new file mode 100644 index 000000000..91772dfeb --- /dev/null +++ b/vendor/github.com/stretchr/testify/require/requirements.go @@ -0,0 +1,29 @@ +package require + +// TestingT is an interface wrapper around *testing.T +type TestingT interface { + Errorf(format string, args ...interface{}) + FailNow() +} + +type tHelper interface { + Helper() +} + +// ComparisonAssertionFunc is a common function prototype when comparing two values. Can be useful +// for table driven tests. +type ComparisonAssertionFunc func(TestingT, interface{}, interface{}, ...interface{}) + +// ValueAssertionFunc is a common function prototype when validating a single value. Can be useful +// for table driven tests. +type ValueAssertionFunc func(TestingT, interface{}, ...interface{}) + +// BoolAssertionFunc is a common function prototype when validating a bool value. Can be useful +// for table driven tests. +type BoolAssertionFunc func(TestingT, bool, ...interface{}) + +// ErrorAssertionFunc is a common function prototype when validating an error value. Can be useful +// for table driven tests. +type ErrorAssertionFunc func(TestingT, error, ...interface{}) + +//go:generate sh -c "cd ../_codegen && go build && cd - && ../_codegen/_codegen -output-package=require -template=require.go.tmpl -include-format-funcs" diff --git a/vendor/modules.txt b/vendor/modules.txt index d18004919..96f0a3132 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -889,6 +889,7 @@ github.com/stoewer/go-strcase # github.com/stretchr/testify v1.9.0 ## explicit; go 1.17 github.com/stretchr/testify/assert +github.com/stretchr/testify/require # github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635 ## explicit github.com/syndtr/gocapability/capability |
