mirror of
https://github.com/vmware-tanzu/pinniped.git
synced 2026-09-20 06:54:20 +00:00
Merge remote-tracking branch 'upstream/main' into generate-jwk-key
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
#! SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#@ load("@ytt:data", "data")
|
||||
#@ load("@ytt:sha256", "sha256")
|
||||
#@ load("@ytt:yaml", "yaml")
|
||||
|
||||
#@ def dexConfig():
|
||||
issuer: #@ "http://127.0.0.1:" + str(data.values.ports.local) + "/dex"
|
||||
storage:
|
||||
type: sqlite3
|
||||
config:
|
||||
file: ":memory:"
|
||||
web:
|
||||
http: 0.0.0.0:5556
|
||||
oauth2:
|
||||
skipApprovalScreen: true
|
||||
staticClients:
|
||||
- id: pinniped-cli
|
||||
name: 'Pinniped CLI'
|
||||
#! we can't have "public: true" until https://github.com/dexidp/dex/pull/1822 lands in Dex.
|
||||
redirectURIs:
|
||||
- #@ "http://127.0.0.1:" + str(data.values.ports.cli) + "/callback"
|
||||
- #@ "http://[::1]:" + str(data.values.ports.cli) + "/callback"
|
||||
enablePasswordDB: true
|
||||
staticPasswords:
|
||||
- username: "pinny"
|
||||
email: "pinny@example.com"
|
||||
hash: "$2a$10$2b2cU8CPhOTaGrs1HRQuAueS7JTT5ZHsHSzYiFPm1leZck7Mc8T4W" #! bcrypt("password")
|
||||
userID: "061d23d1-fe1e-4777-9ae9-59cd12abeaaa"
|
||||
#@ end
|
||||
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Namespace
|
||||
metadata:
|
||||
name: dex
|
||||
labels:
|
||||
name: dex
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: dex-config
|
||||
namespace: dex
|
||||
labels:
|
||||
app: dex
|
||||
data:
|
||||
config.yaml: #@ yaml.encode(dexConfig())
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: dex
|
||||
namespace: dex
|
||||
labels:
|
||||
app: dex
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app: dex
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app: dex
|
||||
annotations:
|
||||
dexConfigHash: #@ sha256.sum(yaml.encode(dexConfig()))
|
||||
spec:
|
||||
containers:
|
||||
- name: dex
|
||||
image: quay.io/dexidp/dex:v2.10.0
|
||||
imagePullPolicy: IfNotPresent
|
||||
command:
|
||||
- /usr/local/bin/dex
|
||||
- serve
|
||||
- /etc/dex/cfg/config.yaml
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 5556
|
||||
volumeMounts:
|
||||
- name: config
|
||||
mountPath: /etc/dex/cfg
|
||||
volumes:
|
||||
- name: config
|
||||
configMap:
|
||||
name: dex-config
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: dex
|
||||
namespace: dex
|
||||
labels:
|
||||
app: dex
|
||||
spec:
|
||||
type: NodePort
|
||||
selector:
|
||||
app: dex
|
||||
ports:
|
||||
- port: 5556
|
||||
nodePort: #@ data.values.ports.node
|
||||
@@ -0,0 +1,17 @@
|
||||
#! Copyright 2020 the Pinniped contributors. All Rights Reserved.
|
||||
#! SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
#@data/values
|
||||
---
|
||||
ports:
|
||||
#! Port on which the Pinniped CLI is listening for a callback (`--listen-port` flag value)
|
||||
#! Used in the Dex configuration to form the valid redirect URIs for our test client.
|
||||
cli: 48095
|
||||
|
||||
#! Kubernetes NodePort that should be forwarded to the Dex service.
|
||||
#! Used to create a Service of type: NodePort
|
||||
node: 31235
|
||||
|
||||
#! External port where Dex ends up exposed on localhost during tests. This value comes from our
|
||||
#! Kind configuration which maps 127.0.0.1:12346 to port 31235 on the Kind worker node.
|
||||
local: 12346
|
||||
+267
-10
@@ -3,20 +3,33 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/sclevine/agouti"
|
||||
"github.com/stretchr/testify/require"
|
||||
"golang.org/x/sync/errgroup"
|
||||
"gopkg.in/square/go-jose.v2"
|
||||
clientauthenticationv1beta1 "k8s.io/client-go/pkg/apis/clientauthentication/v1beta1"
|
||||
|
||||
"go.pinniped.dev/test/library"
|
||||
)
|
||||
|
||||
func TestCLI(t *testing.T) {
|
||||
func TestCLIGetKubeconfig(t *testing.T) {
|
||||
env := library.IntegrationEnv(t).WithCapability(library.ClusterSigningKeyIsAvailable)
|
||||
|
||||
// Create a test webhook configuration to use with the CLI.
|
||||
@@ -26,11 +39,10 @@ func TestCLI(t *testing.T) {
|
||||
idp := library.CreateTestWebhookIDP(ctx, t)
|
||||
|
||||
// Build pinniped CLI.
|
||||
pinnipedExe, cleanupFunc := buildPinnipedCLI(t)
|
||||
defer cleanupFunc()
|
||||
pinnipedExe := buildPinnipedCLI(t)
|
||||
|
||||
// Run pinniped CLI to get kubeconfig.
|
||||
kubeConfigYAML := runPinnipedCLI(t, pinnipedExe, env.TestUser.Token, env.ConciergeNamespace, "webhook", idp.Name)
|
||||
kubeConfigYAML := runPinnipedCLIGetKubeconfig(t, pinnipedExe, env.TestUser.Token, env.ConciergeNamespace, "webhook", idp.Name)
|
||||
|
||||
// In addition to the client-go based testing below, also try the kubeconfig
|
||||
// with kubectl to validate that it works.
|
||||
@@ -58,11 +70,12 @@ func TestCLI(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func buildPinnipedCLI(t *testing.T) (string, func()) {
|
||||
func buildPinnipedCLI(t *testing.T) string {
|
||||
t.Helper()
|
||||
|
||||
pinnipedExeDir, err := ioutil.TempDir("", "pinniped-cli-test-*")
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { require.NoError(t, os.RemoveAll(pinnipedExeDir)) })
|
||||
|
||||
pinnipedExe := filepath.Join(pinnipedExeDir, "pinniped")
|
||||
output, err := exec.Command(
|
||||
@@ -73,13 +86,10 @@ func buildPinnipedCLI(t *testing.T) (string, func()) {
|
||||
"go.pinniped.dev/cmd/pinniped",
|
||||
).CombinedOutput()
|
||||
require.NoError(t, err, string(output))
|
||||
|
||||
return pinnipedExe, func() {
|
||||
require.NoError(t, os.RemoveAll(pinnipedExeDir))
|
||||
}
|
||||
return pinnipedExe
|
||||
}
|
||||
|
||||
func runPinnipedCLI(t *testing.T, pinnipedExe, token, namespaceName, idpType, idpName string) string {
|
||||
func runPinnipedCLIGetKubeconfig(t *testing.T, pinnipedExe, token, namespaceName, idpType, idpName string) string {
|
||||
t.Helper()
|
||||
|
||||
output, err := exec.Command(
|
||||
@@ -94,3 +104,250 @@ func runPinnipedCLI(t *testing.T, pinnipedExe, token, namespaceName, idpType, id
|
||||
|
||||
return string(output)
|
||||
}
|
||||
|
||||
type loginProviderPatterns struct {
|
||||
Name string
|
||||
IssuerPattern *regexp.Regexp
|
||||
LoginPagePattern *regexp.Regexp
|
||||
UsernameSelector string
|
||||
PasswordSelector string
|
||||
LoginButtonSelector string
|
||||
}
|
||||
|
||||
func getLoginProvider(t *testing.T) *loginProviderPatterns {
|
||||
t.Helper()
|
||||
issuer := library.IntegrationEnv(t).OIDCUpstream.Issuer
|
||||
for _, p := range []loginProviderPatterns{
|
||||
{
|
||||
Name: "Okta",
|
||||
IssuerPattern: regexp.MustCompile(`\Ahttps://.+\.okta\.com/.+\z`),
|
||||
LoginPagePattern: regexp.MustCompile(`\Ahttps://.+\.okta\.com/.+\z`),
|
||||
UsernameSelector: "input#okta-signin-username",
|
||||
PasswordSelector: "input#okta-signin-password",
|
||||
LoginButtonSelector: "input#okta-signin-submit",
|
||||
},
|
||||
{
|
||||
Name: "Dex",
|
||||
IssuerPattern: regexp.MustCompile(`\Ahttp://127\.0\.0\.1.+/dex.*\z`),
|
||||
LoginPagePattern: regexp.MustCompile(`\Ahttp://127\.0\.0\.1.+/dex/auth/local.+\z`),
|
||||
UsernameSelector: "input#login",
|
||||
PasswordSelector: "input#password",
|
||||
LoginButtonSelector: "button#submit-login",
|
||||
},
|
||||
} {
|
||||
if p.IssuerPattern.MatchString(issuer) {
|
||||
return &p
|
||||
}
|
||||
}
|
||||
require.Failf(t, "could not find login provider for issuer %q", issuer)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestCLILoginOIDC(t *testing.T) {
|
||||
env := library.IntegrationEnv(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
|
||||
defer cancel()
|
||||
|
||||
// Find the login CSS selectors for the test issuer, or fail fast.
|
||||
loginProvider := getLoginProvider(t)
|
||||
|
||||
// Start the browser driver.
|
||||
t.Logf("opening browser driver")
|
||||
agoutiDriver := agouti.ChromeDriver(
|
||||
agouti.ChromeOptions("args", []string{
|
||||
"--no-sandbox",
|
||||
"--headless", // Comment out this line to see the tests happen in a visible browser window.
|
||||
}),
|
||||
// Uncomment this to see stdout/stderr from chromedriver.
|
||||
// agouti.Debug,
|
||||
)
|
||||
require.NoError(t, agoutiDriver.Start())
|
||||
t.Cleanup(func() { require.NoError(t, agoutiDriver.Stop()) })
|
||||
page, err := agoutiDriver.NewPage(agouti.Browser("chrome"))
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, page.Reset())
|
||||
|
||||
// Build pinniped CLI.
|
||||
t.Logf("building CLI binary")
|
||||
pinnipedExe := buildPinnipedCLI(t)
|
||||
|
||||
// Start the CLI running the "alpha login oidc [...]" command with stdout/stderr connected to pipes.
|
||||
t.Logf("starting CLI subprocess")
|
||||
cmd := exec.CommandContext(ctx, pinnipedExe, "alpha", "login", "oidc",
|
||||
"--issuer", env.OIDCUpstream.Issuer,
|
||||
"--client-id", env.OIDCUpstream.ClientID,
|
||||
"--listen-port", strconv.Itoa(env.OIDCUpstream.LocalhostPort),
|
||||
"--skip-browser",
|
||||
)
|
||||
stderr, err := cmd.StderrPipe()
|
||||
require.NoError(t, err)
|
||||
stdout, err := cmd.StdoutPipe()
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, cmd.Start())
|
||||
t.Cleanup(func() {
|
||||
err := cmd.Wait()
|
||||
t.Logf("CLI subprocess exited with code %d", cmd.ProcessState.ExitCode())
|
||||
require.NoErrorf(t, err, "CLI process did not exit cleanly")
|
||||
})
|
||||
|
||||
// Start a background goroutine to read stderr from the CLI and parse out the login URL.
|
||||
loginURLChan := make(chan string)
|
||||
spawnTestGoroutine(t, func() (err error) {
|
||||
defer func() {
|
||||
closeErr := stderr.Close()
|
||||
if closeErr == nil || errors.Is(closeErr, os.ErrClosed) {
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
err = fmt.Errorf("stderr stream closed with error: %w", closeErr)
|
||||
}
|
||||
}()
|
||||
|
||||
reader := bufio.NewReader(stderr)
|
||||
line, err := reader.ReadString('\n')
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not read login URL line from stderr: %w", err)
|
||||
}
|
||||
const prompt = "Please log in: "
|
||||
if !strings.HasPrefix(line, prompt) {
|
||||
return fmt.Errorf("expected %q to have prefix %q", line, prompt)
|
||||
}
|
||||
loginURLChan <- strings.TrimPrefix(line, prompt)
|
||||
return readAndExpectEmpty(reader)
|
||||
})
|
||||
|
||||
// Start a background goroutine to read stdout from the CLI and parse out an ExecCredential.
|
||||
credOutputChan := make(chan clientauthenticationv1beta1.ExecCredential)
|
||||
spawnTestGoroutine(t, func() (err error) {
|
||||
defer func() {
|
||||
closeErr := stderr.Close()
|
||||
if closeErr == nil || errors.Is(closeErr, os.ErrClosed) {
|
||||
return
|
||||
}
|
||||
if err == nil {
|
||||
err = fmt.Errorf("stdout stream closed with error: %w", closeErr)
|
||||
}
|
||||
}()
|
||||
reader := bufio.NewReader(stdout)
|
||||
var out clientauthenticationv1beta1.ExecCredential
|
||||
if err := json.NewDecoder(reader).Decode(&out); err != nil {
|
||||
return fmt.Errorf("could not read ExecCredential from stdout: %w", err)
|
||||
}
|
||||
credOutputChan <- out
|
||||
return readAndExpectEmpty(reader)
|
||||
})
|
||||
|
||||
// Wait for the CLI to print out the login URL and open the browser to it.
|
||||
t.Logf("waiting for CLI to output login URL")
|
||||
var loginURL string
|
||||
select {
|
||||
case <-time.After(1 * time.Minute):
|
||||
require.Fail(t, "timed out waiting for login URL")
|
||||
case loginURL = <-loginURLChan:
|
||||
}
|
||||
t.Logf("navigating to login page")
|
||||
require.NoError(t, page.Navigate(loginURL))
|
||||
|
||||
// Expect to be redirected to the login page.
|
||||
t.Logf("waiting for redirect to %s login page", loginProvider.Name)
|
||||
waitForURL(t, page, loginProvider.LoginPagePattern)
|
||||
|
||||
// Wait for the login page to be rendered.
|
||||
waitForVisibleElements(t, page, loginProvider.UsernameSelector, loginProvider.PasswordSelector, loginProvider.LoginButtonSelector)
|
||||
|
||||
// Fill in the username and password and click "submit".
|
||||
t.Logf("logging into %s", loginProvider.Name)
|
||||
require.NoError(t, page.First(loginProvider.UsernameSelector).Fill(env.OIDCUpstream.Username))
|
||||
require.NoError(t, page.First(loginProvider.PasswordSelector).Fill(env.OIDCUpstream.Password))
|
||||
require.NoError(t, page.First(loginProvider.LoginButtonSelector).Click())
|
||||
|
||||
// Wait for the login to happen and us be redirected back to a localhost callback.
|
||||
t.Logf("waiting for redirect to localhost callback")
|
||||
callbackURLPattern := regexp.MustCompile(`\Ahttp://127.0.0.1:` + strconv.Itoa(env.OIDCUpstream.LocalhostPort) + `/.+\z`)
|
||||
waitForURL(t, page, callbackURLPattern)
|
||||
|
||||
// Wait for the "pre" element that gets rendered for a `text/plain` page, and
|
||||
// assert that it contains the success message.
|
||||
t.Logf("verifying success page")
|
||||
waitForVisibleElements(t, page, "pre")
|
||||
msg, err := page.First("pre").Text()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "you have been logged in and may now close this tab", msg)
|
||||
|
||||
// Expect the CLI to output an ExecCredential in JSON format.
|
||||
t.Logf("waiting for CLI to output ExecCredential JSON")
|
||||
var credOutput clientauthenticationv1beta1.ExecCredential
|
||||
select {
|
||||
case <-time.After(10 * time.Second):
|
||||
require.Fail(t, "timed out waiting for exec credential output")
|
||||
case credOutput = <-credOutputChan:
|
||||
}
|
||||
|
||||
// Assert some properties of the ExecCredential.
|
||||
t.Logf("validating ExecCredential")
|
||||
require.NotNil(t, credOutput.Status)
|
||||
require.Empty(t, credOutput.Status.ClientKeyData)
|
||||
require.Empty(t, credOutput.Status.ClientCertificateData)
|
||||
|
||||
// There should be at least 1 minute of remaining expiration (probably more).
|
||||
require.NotNil(t, credOutput.Status.ExpirationTimestamp)
|
||||
ttl := time.Until(credOutput.Status.ExpirationTimestamp.Time)
|
||||
require.Greater(t, ttl.Milliseconds(), (1 * time.Minute).Milliseconds())
|
||||
|
||||
// Assert some properties about the token, which should be a valid JWT.
|
||||
require.NotEmpty(t, credOutput.Status.Token)
|
||||
jws, err := jose.ParseSigned(credOutput.Status.Token)
|
||||
require.NoError(t, err)
|
||||
claims := map[string]interface{}{}
|
||||
require.NoError(t, json.Unmarshal(jws.UnsafePayloadWithoutVerification(), &claims))
|
||||
require.Equal(t, env.OIDCUpstream.Issuer, claims["iss"])
|
||||
require.Equal(t, env.OIDCUpstream.ClientID, claims["aud"])
|
||||
require.Equal(t, env.OIDCUpstream.Username, claims["email"])
|
||||
require.NotEmpty(t, claims["nonce"])
|
||||
}
|
||||
|
||||
func waitForVisibleElements(t *testing.T, page *agouti.Page, selectors ...string) {
|
||||
t.Helper()
|
||||
require.Eventually(t,
|
||||
func() bool {
|
||||
for _, sel := range selectors {
|
||||
vis, err := page.First(sel).Visible()
|
||||
if !(err == nil && vis) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
},
|
||||
10*time.Second,
|
||||
100*time.Millisecond,
|
||||
)
|
||||
}
|
||||
|
||||
func waitForURL(t *testing.T, page *agouti.Page, pat *regexp.Regexp) {
|
||||
require.Eventually(t, func() bool {
|
||||
url, err := page.URL()
|
||||
return err == nil && pat.MatchString(url)
|
||||
}, 10*time.Second, 100*time.Millisecond)
|
||||
}
|
||||
|
||||
func readAndExpectEmpty(r io.Reader) (err error) {
|
||||
var remainder bytes.Buffer
|
||||
_, err = io.Copy(&remainder, r)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if r := remainder.String(); r != "" {
|
||||
return fmt.Errorf("expected remainder to be empty, but got %q", r)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func spawnTestGoroutine(t *testing.T, f func() error) {
|
||||
t.Helper()
|
||||
var eg errgroup.Group
|
||||
t.Cleanup(func() {
|
||||
require.NoError(t, eg.Wait(), "background goroutine failed")
|
||||
})
|
||||
eg.Go(f)
|
||||
}
|
||||
|
||||
+25
-10
@@ -6,6 +6,7 @@ package library
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
@@ -15,10 +16,10 @@ import (
|
||||
idpv1alpha1 "go.pinniped.dev/generated/1.19/apis/idp/v1alpha1"
|
||||
)
|
||||
|
||||
type TestClusterCapability string
|
||||
type Capability string
|
||||
|
||||
const (
|
||||
ClusterSigningKeyIsAvailable = TestClusterCapability("clusterSigningKeyIsAvailable")
|
||||
ClusterSigningKeyIsAvailable Capability = "clusterSigningKeyIsAvailable"
|
||||
)
|
||||
|
||||
// TestEnv captures all the external parameters consumed by our integration tests.
|
||||
@@ -29,7 +30,7 @@ type TestEnv struct {
|
||||
SupervisorNamespace string `json:"supervisorNamespace"`
|
||||
ConciergeAppName string `json:"conciergeAppName"`
|
||||
SupervisorAppName string `json:"supervisorAppName"`
|
||||
Capabilities map[TestClusterCapability]bool `json:"capabilities"`
|
||||
Capabilities map[Capability]bool `json:"capabilities"`
|
||||
TestWebhook idpv1alpha1.WebhookIdentityProviderSpec `json:"testWebhook"`
|
||||
SupervisorAddress string `json:"supervisorAddress"`
|
||||
|
||||
@@ -38,9 +39,17 @@ type TestEnv struct {
|
||||
ExpectedUsername string `json:"expectedUsername"`
|
||||
ExpectedGroups []string `json:"expectedGroups"`
|
||||
} `json:"testUser"`
|
||||
|
||||
OIDCUpstream struct {
|
||||
Issuer string `json:"issuer"`
|
||||
ClientID string `json:"clientID"`
|
||||
LocalhostPort int `json:"localhostPort"`
|
||||
Username string `json:"username"`
|
||||
Password string `json:"password"`
|
||||
} `json:"oidcUpstream"`
|
||||
}
|
||||
|
||||
// IntegrationEnv gets the integration test environment from a Kubernetes Secret in the test cluster. This
|
||||
// IntegrationEnv gets the integration test environment from OS environment variables. This
|
||||
// method also implies SkipUnlessIntegration().
|
||||
func IntegrationEnv(t *testing.T) *TestEnv {
|
||||
t.Helper()
|
||||
@@ -79,29 +88,35 @@ func IntegrationEnv(t *testing.T) *TestEnv {
|
||||
result.SupervisorAppName = needEnv("PINNIPED_TEST_SUPERVISOR_APP_NAME")
|
||||
result.SupervisorAddress = needEnv("PINNIPED_TEST_SUPERVISOR_ADDRESS")
|
||||
result.TestWebhook.TLS = &idpv1alpha1.TLSSpec{CertificateAuthorityData: needEnv("PINNIPED_TEST_WEBHOOK_CA_BUNDLE")}
|
||||
|
||||
result.OIDCUpstream.Issuer = needEnv("PINNIPED_TEST_CLI_OIDC_ISSUER")
|
||||
result.OIDCUpstream.ClientID = needEnv("PINNIPED_TEST_CLI_OIDC_CLIENT_ID")
|
||||
result.OIDCUpstream.LocalhostPort, _ = strconv.Atoi(needEnv("PINNIPED_TEST_CLI_OIDC_LOCALHOST_PORT"))
|
||||
result.OIDCUpstream.Username = needEnv("PINNIPED_TEST_CLI_OIDC_USERNAME")
|
||||
result.OIDCUpstream.Password = needEnv("PINNIPED_TEST_CLI_OIDC_PASSWORD")
|
||||
result.t = t
|
||||
return &result
|
||||
}
|
||||
|
||||
func (e *TestEnv) HasCapability(cap TestClusterCapability) bool {
|
||||
func (e *TestEnv) HasCapability(cap Capability) bool {
|
||||
e.t.Helper()
|
||||
isCapable, capabilityWasDescribed := e.Capabilities[cap]
|
||||
require.True(e.t, capabilityWasDescribed, `the cluster's "%s" capability was not described`, cap)
|
||||
require.Truef(e.t, capabilityWasDescribed, "the %q capability of the test environment was not described", cap)
|
||||
return isCapable
|
||||
}
|
||||
|
||||
func (e *TestEnv) WithCapability(cap TestClusterCapability) *TestEnv {
|
||||
func (e *TestEnv) WithCapability(cap Capability) *TestEnv {
|
||||
e.t.Helper()
|
||||
if !e.HasCapability(cap) {
|
||||
e.t.Skipf(`skipping integration test because cluster lacks the "%s" capability`, cap)
|
||||
e.t.Skipf("skipping integration test because test environment lacks the %q capability", cap)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
func (e *TestEnv) WithoutCapability(cap TestClusterCapability) *TestEnv {
|
||||
func (e *TestEnv) WithoutCapability(cap Capability) *TestEnv {
|
||||
e.t.Helper()
|
||||
if e.HasCapability(cap) {
|
||||
e.t.Skipf(`skipping integration test because cluster has the "%s" capability`, cap)
|
||||
e.t.Skipf("skipping integration test because test environment has the %q capability", cap)
|
||||
}
|
||||
return e
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user