framework

package
v0.1.16 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 23, 2026 License: Apache-2.0 Imports: 37 Imported by: 0

Documentation

Overview

Package framework provides shared test infrastructure for faros e2e tests.

Package framework provides shared test infrastructure for faros e2e tests.

Index

Constants

View Source
const (
	DefaultHubClusterName   = "faros-e2e-hub"
	DefaultAgentClusterName = "faros-e2e-agent"
	DefaultKindNetwork      = "faros-e2e"
	DefaultChartPath        = "deploy/charts/faros-hub"
	DefaultHubURL           = "https://faros.localhost:9443"

	// DefaultAgentCount is the number of agent clusters created by the e2e
	// test suites. All suites create 2 agent clusters so multi-site tests run
	// in every flavour.
	DefaultAgentCount = 2
)
View Source
const (
	// DexServicePort is the host port where Dex is reachable from the test
	// runner via the kind port mapping. The dexidp Helm chart binds HTTPS
	// to containerPort 5554 (hard-coded --web-https-addr), so we expose
	// the same number on the host for consistency.
	DexServicePort = 5554

	// DexIssuerURL is the OIDC issuer URL used by both the hub pod (cluster
	// DNS) and the test runner (/etc/hosts alias to localhost). HTTPS so
	// embedded kcp's authentication validator (which mandates scheme=https)
	// accepts it; the cert is signed by the faros-selfsigned ClusterIssuer
	// and clients use InsecureSkipVerify.
	DexIssuerURL = "https://dex.faros-system.svc.cluster.local:5554/dex"

	// DexExternalHost is added to the test runner's /etc/hosts as 127.0.0.1
	// so it can reach the in-cluster Dex via the kind port mapping.
	DexExternalHost = "dex.faros-system.svc.cluster.local"

	// DexClientID / DexClientSecret are the OAuth2 credentials for the hub.
	DexClientID     = "faros"
	DexClientSecret = "faros-test-secret"

	// DexTestUserEmail / DexTestUserPassword are the static-password credentials
	// seeded in Dex for e2e OIDC tests (primary user / User A).
	DexTestUserEmail    = "admin@test.faros.local"
	DexTestUserPassword = "Password1!"

	// DexTestUser2Email / DexTestUser2Password are the credentials for the second
	// Dex static-password user, used in cross-user isolation tests (issue #79).
	DexTestUser2Email    = "user2@test.faros.local"
	DexTestUser2Password = "Password1!"
)
View Source
const (
	// DefaultInstallClusterName is the kind cluster the install suites create.
	// Distinct from the docs default ("faros") so a developer's manual
	// walkthrough and an e2e run never fight over the same cluster.
	DefaultInstallClusterName = "faros-e2e-install"

	// InstallStateDirName is the state directory (extracted kubeconfigs,
	// port-forward pidfiles) the install suites pass to the scripts.
	InstallStateDirName = ".faros-install-e2e"

	// InstallGatewayAddr is the local address of the Envoy gateway
	// port-forward started by hack/install/port-forward.sh.
	InstallGatewayAddr = "127.0.0.1:8443"

	// InstallHubURL matches the hack/install default HUB_EXTERNAL_URL. Plain
	// localhost (not faros.localhost): *.localhost subdomains don't resolve on
	// stock macOS, and the install flow should run anywhere the docs do.
	InstallHubURL = "https://localhost:9443"
)
View Source
const (
	// ContainerSSHImage is the Docker image used for the container-based SSH
	// server test.  We use a plain Ubuntu image and install openssh-server at
	// container start time so that we have full control over the sshd
	// configuration (in particular: PermitRootLogin + PermitEmptyPasswords).
	ContainerSSHImage = "ubuntu:22.04"

	// ContainerSSHPort is the port sshd listens on inside the container.
	// We use 2222 to avoid conflicts with any host sshd on port 22.
	ContainerSSHPort = 2222
)
View Source
const DefaultKCPExternalKubeconfigFile = "kcp-admin.kubeconfig"

DefaultKCPExternalKubeconfigFile is the filename written by faros dev init --with-external-kcp for the test runner to reach kcp directly.

View Source
const (
	// DefaultTestSSHPort is the port used by the embedded test SSH server.
	// High enough to be unprivileged and unlikely to conflict on CI runners.
	DefaultTestSSHPort = 2222
)

Variables

View Source
var (
	// SSHKeepaliveDuration is how long the long-lived SSH connection test holds the
	// session open before asserting liveness.
	//
	// Minimum useful value: 60s. The long_lived_connection_stays_alive test uses a
	// 30-second keepalive ticker; setting the duration below 60s means the ticker
	// never fires during the hold period and the keepalive logic is never exercised.
	// Default 60s; bump to 10m+ locally to stress-test keepalive behaviour.
	SSHKeepaliveDuration time.Duration

	// KeepClusters controls whether kind clusters are deleted after the test run.
	// Default is false (clusters are deleted). Set --keep-clusters to retain them
	// for debugging failures.
	KeepClusters bool

	// FarosBin is the path to the faros binary under test.
	FarosBin string

	// DevToken is the static auth token used in standalone (non-OIDC) test suites.
	DevToken string
)
View Source
var RESTClient = &http.Client{
	Timeout: 15 * time.Second,
	Transport: &http.Transport{
		TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
	},
}

RESTClient is a TLS-skip HTTP client targeting the hub's self-signed dev cert. Shared across tenancy cases.

Functions

func AgentBinPath

func AgentBinPath() string

AgentBinPath returns the path to the faros binary under bin/.

func AgentSavedKubeconfigPath

func AgentSavedKubeconfigPath(edgeName string) (string, error)

AgentSavedKubeconfigPath returns the filesystem path where the agent binary persists the kubeconfig received via token-exchange. It mirrors the logic in pkg/agent.AgentKubeconfigPath so tests can verify the file was written.

func CallerIdentityFromContext

func CallerIdentityFromContext(ctx context.Context) string

CallerIdentityFromContext retrieves the expected caller identity from the context.

func ClusterNameFromKubeconfig

func ClusterNameFromKubeconfig(kubeconfigPath string) string

ClusterNameFromKubeconfig reads the kubeconfig at path and extracts the kcp cluster name from the server URL (e.g. "https://hub:9443/clusters/abc123" → "abc123"). Returns "" when the kubeconfig cannot be read or has no cluster path, so callers should skip passing --cluster in that case.

func DeleteOrgViaREST

func DeleteOrgViaREST(ctx context.Context, hubURL, bearer, orgUUID string) (int, error)

DeleteOrgViaREST soft-deletes an Org (best-effort; non-fatal).

func DoRESTRequest

func DoRESTRequest(
	ctx context.Context,
	method, url, bearer string,
	tenantHeaders map[string]string,
	body any,
) (int, []byte, error)

DoRESTRequest performs a JSON request against the hub REST surface and returns the status code + response body. body may be nil for GET/DELETE. tenantHeaders carries X-Faros-Org / X-Faros-Workspace as needed.

func FindPersonalOrgUUID

func FindPersonalOrgUUID(ctx context.Context, hubURL, bearer string) (string, error)

FindPersonalOrgUUID returns the personal-org UUID for the caller. Used by personal-org guardrail tests.

func GatewayGet added in v0.1.11

func GatewayGet(ctx context.Context, host, path string) (int, error)

GatewayGet performs an HTTPS GET through the local Envoy gateway port-forward, dialing InstallGatewayAddr while presenting the given SNI hostname — exactly what a client resolving <host> to the gateway would do. Returns the HTTP status code.

func GenerateTestSSHKeypair

func GenerateTestSSHKeypair() (*rsa.PrivateKey, gossh.PublicKey, []byte, error)

GenerateTestSSHKeypair generates a 2048-bit RSA keypair for use in e2e tests. It returns the RSA private key, the SSH public key, and the PEM-encoded private key bytes (PKCS#1 RSA PRIVATE KEY format accepted by gossh.ParsePrivateKey).

func HTTPGet

func HTTPGet(ctx context.Context, url string) (int, error)

HTTPGet performs a GET to url using an insecure client (self-signed certs in dev) and returns the HTTP status code.

func HTTPGetBody

func HTTPGetBody(ctx context.Context, url string) (int, string, error)

HTTPGetBody performs a GET to url and returns the status code and response body. TLS verification is skipped — used for in-cluster services exposed via kind port mapping with self-signed certs (Dex, hub).

func HubNodePortURL

func HubNodePortURL() string

HubNodePortURL returns the hub URL reachable from inside a pod in another kind cluster — i.e. via the hub node's Docker IP on the shared kind network and NodePort 31443. This is needed because faros.localhost resolves only on the CI runner host, not inside pods. Returns "" if the Docker IP cannot be determined (caller should skip or fall back).

func IndentLines

func IndentLines(text, prefix string) string

IndentLines prepends prefix to every non-empty line in text. Used for embedding PEM blocks in YAML manifests.

func InstallStateDir added in v0.1.11

func InstallStateDir(repoRoot string) string

InstallStateDir returns the state directory used by the install suites.

func IsAuthRejectStatus

func IsAuthRejectStatus(code int) bool

IsAuthRejectStatus is true for any status code the REST surface is allowed to return when an identity is denied. The hub may pick 401 (no/invalid auth), 403 (auth ok, not a member), or 404 (refuse to confirm existence) depending on which check fires first. All three are acceptable for negative checks; 200/2xx is the bug.

func KubectlWithConfig

func KubectlWithConfig(ctx context.Context, kubeconfig string, args ...string) (string, error)

KubectlWithConfig runs kubectl with an explicit kubeconfig path (package-level helper).

func LoadAgentImageIntoCluster

func LoadAgentImageIntoCluster(clusterName string) error

LoadAgentImageIntoCluster loads the agent container image into a kind cluster so that Deployments with imagePullPolicy=Never can use it without a registry pull. This is a no-op unless FAROS_AGENT_IMAGE_PULL_POLICY=Never (i.e. CI with a locally built image).

func PodHubURLFromKubeconfig

func PodHubURLFromKubeconfig(kubeconfigPath string) string

PodHubURLFromKubeconfig returns the hub URL reachable from inside a pod in the agent kind cluster. It reads the server URL from the hub kubeconfig (which contains the full /clusters/<name> path), then replaces the host with the hub node's Docker network NodePort address so that in-cluster agents can connect.

clusterEnv.HubURL is always "https://faros.localhost:9443" (no cluster path), so this function reads the cluster path directly from the hub kubeconfig instead of relying on the HubURL field.

Returns "" if the hub kubeconfig cannot be read or the NodePort address is unavailable.

func Poll

func Poll(ctx context.Context, interval, timeout time.Duration, condition ConditionFunc) error

Poll calls condition repeatedly with the given interval until it returns (true, nil) or the context is done. Returns an error if the context expires or condition returns a non-nil error.

func RepoRoot

func RepoRoot() string

RepoRoot returns the absolute path to the faros repository root, derived from the location of this source file at compile time.

func ResolveCallerIdentity

func ResolveCallerIdentity(ctx context.Context, kubeconfigPath string) (string, error)

ResolveCallerIdentity performs a TokenReview against the hub to discover the username associated with the hub kubeconfig's bearer token. Returns an empty string (without error) if the token is unauthenticated or the server does not support TokenReview.

func RunCmd

func RunCmd(ctx context.Context, name string, args ...string) (string, error)

RunCmd runs an arbitrary command and returns its combined output (package-level helper).

func RunInstallScript added in v0.1.11

func RunInstallScript(ctx context.Context, repoRoot, name string) error

RunInstallScript executes one hack/install/<name> script, streaming output.

func SSHPrivateKeyPEMFromContext

func SSHPrivateKeyPEMFromContext(ctx context.Context) []byte

SSHPrivateKeyPEMFromContext retrieves a PEM-encoded SSH private key from the context.

func SetupClusters

func SetupClusters(workDir string) env.Func

SetupClusters returns an env.Func that creates the hub and agent kind clusters using `faros dev init` with the local Helm chart. It stores a ClusterEnv in the context for use by tests.

If the FAROS_HUB_IMAGE_PULL_POLICY env var is set (e.g. to "Never" in CI when the image is pre-loaded into kind), it is forwarded to `faros dev init`.

func SetupClustersWithAgentCount

func SetupClustersWithAgentCount(workDir string, agentCount int) env.Func

SetupClustersWithAgentCount is like SetupClusters but creates agentCount agent clusters instead of DefaultAgentCount. Use agentCount=1 for suites that do not need multi-agent tests (e.g. SSH) to save cluster creation time.

func SetupClustersWithExternalKCP

func SetupClustersWithExternalKCP(workDir string) env.Func

SetupClustersWithExternalKCP returns an env.Func that creates hub and agent kind clusters using `faros dev init --with-external-kcp`. kcp is deployed via Helm into the hub cluster; the hub is configured to use it.

The external kcp kubeconfig (for test assertions against kcp directly) is stored in ClusterEnv.KCPKubeconfig.

func SetupClustersWithOIDC

func SetupClustersWithOIDC(workDir string) env.Func

SetupClustersWithOIDC is like SetupClusters but also deploys Dex as an OIDC provider inside the hub kind cluster (via --with-dex).

Networking: Dex is exposed as NodePort 31554 on the hub kind node; the kind cluster maps that to localhost:5554. The test runner adds a /etc/hosts entry (127.0.0.1 dex.faros-system.svc.cluster.local) so it can reach the in-cluster Dex on the same hostname that the hub pod uses via cluster DNS.

func SetupInstallFlow added in v0.1.11

func SetupInstallFlow(repoRoot string, scripts []string) env.Func

SetupInstallFlow returns an env.Func that runs the given hack/install scripts in order, starts the port-forwards, waits for the hub and tenant API, and stores a ClusterEnv. Pass the exact script list a doc prescribes, e.g. external: 01,02,03,04,05,06,07 — embedded: 01,03,08.

func StartInstallPortForwards added in v0.1.11

func StartInstallPortForwards(ctx context.Context, repoRoot string) error

StartInstallPortForwards (re)starts the gateway + hub port-forwards.

func TeardownClusters

func TeardownClusters(workDir string) env.Func

TeardownClusters returns an env.Func that deletes the hub and agent kind clusters unless KeepClusters is set.

func TeardownClustersWithAgentCount

func TeardownClustersWithAgentCount(workDir string, agentCount int) env.Func

TeardownClustersWithAgentCount is TeardownClusters for a custom agent count.

func TeardownInstallFlow added in v0.1.11

func TeardownInstallFlow(repoRoot string) env.Func

TeardownInstallFlow deletes the install kind cluster and state via hack/install/teardown.sh, honouring --keep-clusters.

func UseExistingClusters

func UseExistingClusters(workDir string) env.Func

UseExistingClusters wires up ClusterEnv from already-running clusters without creating or destroying anything. It verifies that the hub is healthy before returning. Cluster names can be overridden via FAROS_HUB_CLUSTER_NAME and FAROS_AGENT_CLUSTER_NAME.

func UseExistingClustersWithExternalKCP

func UseExistingClustersWithExternalKCP(workDir string) env.Func

UseExistingClustersWithExternalKCP is the FAROS_USE_EXISTING_CLUSTERS=true variant of SetupClustersWithExternalKCP. It assumes clusters and kcp are already running and just wires up the ClusterEnv.

func UseExistingClustersWithOIDC

func UseExistingClustersWithOIDC(workDir string) env.Func

UseExistingClustersWithOIDC wires up ClusterEnv and DexEnv from already-running clusters (FAROS_USE_EXISTING_CLUSTERS=true path). It verifies that the hub and Dex are reachable but does NOT create or destroy any clusters.

Cluster names can be overridden via FAROS_HUB_CLUSTER_NAME and FAROS_AGENT_CLUSTER_NAME environment variables. This is useful when testing against the dev cluster (faros-hub / faros-agent) instead of the e2e cluster.

func WaitForAgentSavedKubeconfig

func WaitForAgentSavedKubeconfig(ctx context.Context, edgeName string, timeout time.Duration) (string, error)

WaitForAgentSavedKubeconfig polls until the saved kubeconfig file appears at the expected path, or until timeout expires.

func WaitForDeploymentAvailable

func WaitForDeploymentAvailable(ctx context.Context, kubeconfig, namespace, name string, timeout time.Duration) error

WaitForDeploymentAvailable polls kubectl until the named Deployment in the given namespace has availableReplicas >= 1, or the timeout expires. kubeconfig is the path to the kubeconfig for the cluster hosting the Deployment.

func WaitForDexReady

func WaitForDexReady(ctx context.Context) error

WaitForDexReady polls Dex's OIDC discovery endpoint on localhost until it returns 200 or the context deadline is exceeded. The test runner reaches Dex via the kind port mapping on localhost:DexServicePort. Dex serves TLS with a cert valid for the in-cluster name; the client skips verification.

func WaitForHubReady

func WaitForHubReady(ctx context.Context, hubURL string) error

WaitForHubReady polls the hub's /healthz endpoint until it returns 200 or the context deadline is exceeded.

func WaitForTenantAPI

func WaitForTenantAPI(ctx context.Context, client *FarosClient, hubURL, token string) error

WaitForTenantAPI logs in with a static token and polls the hub's token-login endpoint until the tenant/users APIBinding has finished bootstrapping. Until then the hub can 500 ("failed to create user") on the first tenant operations, so suites gate startup on this.

This replaces the pre-decouple WaitForEdgeAPI gate: edges are now an optional out-of-process provider (group edges.faros.sh) that these suites do not bootstrap, so "edge list works" is no longer a valid readiness signal. Edge connectivity has its own dedicated suite.

func WaitForTenantAPIWithOIDC

func WaitForTenantAPIWithOIDC(ctx context.Context, workDir, hubURL string) error

WaitForTenantAPIWithOIDC is like WaitForTenantAPI but proves readiness via a headless OIDC login (users APIBinding must be bound for the login handler to mint a user). Used when the hub runs in OIDC-only mode (--with-dex).

func WithCallerIdentity

func WithCallerIdentity(ctx context.Context, identity string) context.Context

WithCallerIdentity stores the expected caller identity in the context.

func WithClusterEnv

func WithClusterEnv(ctx context.Context, c *ClusterEnv) context.Context

WithClusterEnv stores a ClusterEnv in the context.

func WithDexEnv

func WithDexEnv(ctx context.Context, d *DexEnv) context.Context

WithDexEnv stores DexEnv in a context.

func WithSSHPrivateKeyPEM

func WithSSHPrivateKeyPEM(ctx context.Context, pem []byte) context.Context

WithSSHPrivateKeyPEM stores a PEM-encoded SSH private key in the context.

func WithServerContainer

func WithServerContainer(ctx context.Context, c *ServerContainer) context.Context

WithServerContainer stores a ServerContainer in the context.

func WithServerProcess

func WithServerProcess(ctx context.Context, p *ServerProcess) context.Context

WithServerProcess stores a ServerProcess in the context.

Types

type Agent

type Agent struct {
	// contains filtered or unexported fields
}

Agent manages a faros-agent process for e2e tests.

func NewAgent

func NewAgent(workDir, hubKubeconfig, agentKubeconfig, edgeName string) *Agent

NewAgent creates a new Agent.

func (*Agent) Start

func (a *Agent) Start(ctx context.Context) error

Start launches the faros agent run process. It runs until Stop is called or the parent context is cancelled.

func (*Agent) Stop

func (a *Agent) Stop()

Stop terminates the agent process.

func (*Agent) WithLabels

func (a *Agent) WithLabels(labels map[string]string) *Agent

WithLabels sets site labels the agent will report when registering.

type AgentClusterInfo

type AgentClusterInfo struct {
	Name       string
	Kubeconfig string
}

AgentClusterInfo holds the name and kubeconfig path for a single agent cluster.

type ClusterEnv

type ClusterEnv struct {
	HubClusterName string
	HubKubeconfig  string
	HubURL         string
	Token          string
	WorkDir        string

	// AgentClusters holds all agent clusters in creation order.
	// Use AgentClusters[0] for single-agent tests (backwards-compat).
	AgentClusters []AgentClusterInfo

	// AgentClusterName and AgentKubeconfig are shims for AgentClusters[0]
	// kept for backwards compatibility with existing single-agent test cases.
	AgentClusterName string
	AgentKubeconfig  string

	// KCPKubeconfig is the path to the external kcp admin kubeconfig written
	// to WorkDir. Only populated by SetupClustersWithExternalKCP.
	KCPKubeconfig string

	// HubAdminKubeconfig is the raw kind-cluster admin kubeconfig for the hub
	// cluster, saved before faros login overwrites the main HubKubeconfig with
	// a kcp workspace context. Use this for kubectl commands against the hub
	// kind cluster itself (e.g. deleting pods in the kcp namespace).
	HubAdminKubeconfig string
}

ClusterEnv holds runtime paths and names for a test cluster environment.

func ClusterEnvFrom

func ClusterEnvFrom(ctx context.Context) *ClusterEnv

ClusterEnvFrom retrieves a ClusterEnv from the context.

type ConditionFunc

type ConditionFunc func(ctx context.Context) (bool, error)

ConditionFunc is a function that returns (done bool, err error). A nil error with done=false means retry; a non-nil error stops polling.

type CreateOrgResponse

type CreateOrgResponse struct {
	UUID        string `json:"uuid"`
	DisplayName string `json:"displayName"`
	Personal    bool   `json:"personal"`
}

func CreateOrgViaREST

func CreateOrgViaREST(ctx context.Context, hubURL, bearer, displayName string) (CreateOrgResponse, error)

CreateOrgViaREST creates a non-personal Organization under the given bearer identity and returns the created UUID. Fatal on non-201.

type CreateWorkspaceResponse

type CreateWorkspaceResponse struct {
	UUID        string `json:"uuid"`
	OrgUUID     string `json:"orgUUID"`
	DisplayName string `json:"displayName,omitempty"`
}

func CreateWorkspaceViaREST

func CreateWorkspaceViaREST(ctx context.Context, hubURL, bearer, orgUUID, displayName string) (CreateWorkspaceResponse, error)

CreateWorkspaceViaREST creates a Workspace under an Organization.

type DexEnv

type DexEnv struct {
	IssuerURL    string
	ClientID     string
	ClientSecret string
	// UserEmail / UserPassword are credentials for the primary test user (User A).
	UserEmail    string
	UserPassword string
	// User2Email / User2Password are credentials for the secondary test user (User B).
	// Used in cross-user isolation tests to verify that User B cannot access
	// resources owned by User A.
	User2Email    string
	User2Password string
}

DexEnv holds runtime OIDC provider info stored in the test context.

func DefaultDexEnv

func DefaultDexEnv() *DexEnv

DefaultDexEnv returns the DexEnv used in the e2e OIDC suite.

func DexEnvFrom

func DexEnvFrom(ctx context.Context) *DexEnv

DexEnvFrom retrieves DexEnv from a context.

type EdgeSSHCredentials

type EdgeSSHCredentials struct {
	Username            string
	PasswordSecretRef   string // "<namespace>/<name>" or "" if not set
	PrivateKeySecretRef string // "<namespace>/<name>" or "" if not set
}

EdgeSSHCredentials holds the SSH credentials observed on an edge status.

type FarosClient

type FarosClient struct {
	// contains filtered or unexported fields
}

FarosClient wraps the faros CLI binary for use in e2e tests.

func NewFarosClient

func NewFarosClient(workDir, kubeconfig, hubURL string) *FarosClient

NewFarosClient creates a new FarosClient.

func (*FarosClient) ApplyFile

func (k *FarosClient) ApplyFile(ctx context.Context, path string) error

ApplyFile applies a YAML file via kubectl against the hub kubeconfig.

func (*FarosClient) ApplyManifest

func (k *FarosClient) ApplyManifest(ctx context.Context, yaml string) error

ApplyManifest writes yaml to a temp file and applies it via kubectl.

func (*FarosClient) DeleteVirtualWorkload

func (k *FarosClient) DeleteVirtualWorkload(ctx context.Context, name, namespace string) error

DeleteVirtualWorkload deletes a VirtualWorkload by name and namespace.

func (*FarosClient) EdgeCreate

func (k *FarosClient) EdgeCreate(ctx context.Context, name, edgeType string, labels ...string) error

EdgeCreate creates an Edge resource via kubectl with the given name, type, and optional comma-separated labels. type must be "kubernetes" or "server".

func (*FarosClient) EdgeDelete

func (k *FarosClient) EdgeDelete(ctx context.Context, name string) error

EdgeDelete deletes an Edge resource by name.

func (*FarosClient) EdgeJoinCommand

func (k *FarosClient) EdgeJoinCommand(ctx context.Context, edgeName string) (string, error)

EdgeJoinCommand runs `faros edge join-command <name>` and returns the printed output.

func (*FarosClient) EdgeKubeconfig

func (k *FarosClient) EdgeKubeconfig(ctx context.Context, edgeName, outputPath string) error

EdgeKubeconfig runs `faros kubeconfig edge <name> --output <path>`.

func (*FarosClient) EdgeList

func (k *FarosClient) EdgeList(ctx context.Context) (string, error)

EdgeList returns raw kubectl output for listing all edges.

func (*FarosClient) ExtractEdgeKubeconfig

func (k *FarosClient) ExtractEdgeKubeconfig(ctx context.Context, edgeName, destPath string) error

ExtractEdgeKubeconfig waits for the edge kubeconfig secret to appear in the hub cluster and writes the base64-decoded content to destPath. Secret name format: edge-<edgeName>-kubeconfig in namespace faros-system.

func (*FarosClient) GetEdgeCondition

func (k *FarosClient) GetEdgeCondition(ctx context.Context, edgeName, conditionType string) (string, error)

GetEdgeCondition returns the status (True/False/Unknown) of the named condition on the edge, or "" if the condition is not present.

func (*FarosClient) GetEdgeJoinToken

func (k *FarosClient) GetEdgeJoinToken(ctx context.Context, edgeName string) (string, error)

GetEdgeJoinToken returns the current value of edge.status.joinToken. Returns an empty string (no error) when the field is not yet populated.

func (*FarosClient) GetEdgeSSHCredentials

func (k *FarosClient) GetEdgeSSHCredentials(ctx context.Context, edgeName string) (*EdgeSSHCredentials, error)

GetEdgeSSHCredentials returns the current status.sshCredentials for an edge. Returns nil (no error) when the field is not yet set.

func (*FarosClient) GetEdgeURL

func (k *FarosClient) GetEdgeURL(ctx context.Context, name string) (string, error)

GetEdgeURL polls until edge.status.URL is populated and returns it. It returns an error if the URL is not set within 2 minutes.

func (*FarosClient) Kubectl

func (k *FarosClient) Kubectl(ctx context.Context, args ...string) (string, error)

func (*FarosClient) KubectlWithURL

func (k *FarosClient) KubectlWithURL(ctx context.Context, serverURL string, args ...string) (string, error)

KubectlWithURL runs kubectl against a specific server URL using credentials from the hub kubeconfig. The hub bearer token is passed transparently to the edge proxy endpoint on the hub.

func (*FarosClient) Login

func (k *FarosClient) Login(ctx context.Context, token string) error

Login authenticates to the hub using a static token.

func (*FarosClient) Run

func (k *FarosClient) Run(ctx context.Context, args ...string) (string, error)

Run executes an arbitrary faros command and returns stdout+stderr combined. This is the public variant of the internal run() helper.

func (*FarosClient) WaitForEdgeCondition

func (k *FarosClient) WaitForEdgeCondition(ctx context.Context, edgeName, conditionType, expectedStatus string, timeout time.Duration) error

WaitForEdgeCondition polls until edge condition conditionType reaches the expected status (e.g. "True"), or returns an error after timeout.

func (*FarosClient) WaitForEdgeJoinToken

func (k *FarosClient) WaitForEdgeJoinToken(ctx context.Context, edgeName string, timeout time.Duration) (string, error)

WaitForEdgeJoinToken polls until edge.status.joinToken is set and returns it. Returns an error if the token is not populated within timeout.

func (*FarosClient) WaitForEdgeJoinTokenCleared

func (k *FarosClient) WaitForEdgeJoinTokenCleared(ctx context.Context, edgeName string, timeout time.Duration) error

WaitForEdgeJoinTokenCleared polls until edge.status.joinToken is empty (cleared after successful registration). Returns an error if the field is still non-empty after timeout.

func (*FarosClient) WaitForEdgeKubeconfig

func (k *FarosClient) WaitForEdgeKubeconfig(ctx context.Context, edgeName, outputPath string, timeout time.Duration) error

WaitForEdgeKubeconfig polls until EdgeKubeconfig successfully writes to outputPath.

func (*FarosClient) WaitForEdgePhase

func (k *FarosClient) WaitForEdgePhase(ctx context.Context, edgeName, phase string, timeout time.Duration) error

WaitForEdgePhase polls until the given Edge resource has the expected phase.

func (*FarosClient) WaitForEdgeReady

func (k *FarosClient) WaitForEdgeReady(ctx context.Context, edgeName string, timeout time.Duration) error

WaitForEdgeReady polls until the given Edge resource has phase "Ready".

func (*FarosClient) WaitForEdgeSSHCredentials

func (k *FarosClient) WaitForEdgeSSHCredentials(ctx context.Context, edgeName string, timeout time.Duration) (*EdgeSSHCredentials, error)

WaitForEdgeSSHCredentials polls until edge.status.sshCredentials.username is non-empty and returns the credentials. Returns an error after timeout.

func (*FarosClient) WaitForNoPlacement

func (k *FarosClient) WaitForNoPlacement(ctx context.Context, vwName, namespace, edgeName string, timeout time.Duration) error

WaitForNoPlacement polls until no Placement targeting edgeName exists for the given VirtualWorkload — i.e. the scheduler has not routed to that edge. Returns nil when the condition is confirmed within timeout; returns an error if a matching placement still exists at deadline.

func (*FarosClient) WaitForPlacement

func (k *FarosClient) WaitForPlacement(ctx context.Context, vwName, namespace, edgeName string, timeout time.Duration) error

WaitForPlacement polls until a Placement targeting edgeName exists for the given VirtualWorkload or the timeout expires.

type OIDCLoginResult

type OIDCLoginResult struct {
	// Kubeconfig is the raw kubeconfig YAML returned by the hub.
	Kubeconfig []byte
	// Email is the authenticated user's email address.
	Email string
	// UserID is the authenticated user's ID in the hub.
	UserID string
	// IDToken is the raw OIDC ID token for direct API calls or caching.
	IDToken string
	// RefreshToken can be used to refresh the ID token.
	RefreshToken string
	// ExpiresAt is the Unix timestamp when the ID token expires.
	ExpiresAt int64
	// IssuerURL is the OIDC issuer URL embedded in the kubeconfig.
	IssuerURL string
	// ClientID is the OAuth2 client ID.
	ClientID string
}

OIDCLoginResult holds the result of a headless OIDC login.

func HeadlessOIDCLogin

func HeadlessOIDCLogin(ctx context.Context, hubURL, email, password string) (*OIDCLoginResult, error)

HeadlessOIDCLogin drives the full OIDC authorization-code flow headlessly.

The faros hub auth flow (see pkg/server/auth/handler.go):

  1. GET /auth/authorize?p=<port>&s=<session> → 302 to Dex auth URL
  2. GET Dex auth URL → Dex login page
  3. POST Dex login form with credentials → 302 to hub /auth/callback
  4. GET hub /auth/callback?code=…&state=… → 302 to localhost:<port>/callback?response=<b64>
  5. GET localhost:<port>/callback?response=… → parse LoginResponse JSON

The function starts a local HTTP server on a random port to receive step 5, then drives steps 1–4 using a cookie-aware HTTP client.

type SAResponse

type SAResponse struct {
	UUID        string `json:"uuid"`
	DisplayName string `json:"displayName"`
	Role        string `json:"role"`
}

type SSHWebSocketClient

type SSHWebSocketClient struct {
	// contains filtered or unexported fields
}

SSHWebSocketClient is a programmatic WebSocket SSH client for use in e2e tests. It connects directly to the hub SSH subresource endpoint, bypassing the CLI, so that the interactive PTY path can be exercised without a real terminal.

A single background reader goroutine pumps all inbound WebSocket frames into a buffered channel. CollectOutput drains that channel with a time.Timer instead of using SetReadDeadline. This avoids the gorilla/websocket behaviour of permanently storing read errors: once a deadline fires, c.readErr is set and every subsequent ReadMessage returns that error immediately, making the connection unusable for further reads.

func DialSSH

func DialSSH(ctx context.Context, kubeconfig, name string) (*SSHWebSocketClient, error)

DialSSH connects to the hub SSH WebSocket endpoint for the given edge name. kubeconfig is used to extract the hub credentials and to look up the edge's status.URL (which contains the correct cluster name — not the static "default" path that was previously hardcoded here).

func (*SSHWebSocketClient) Close

func (c *SSHWebSocketClient) Close() error

Close closes the WebSocket connection.

func (*SSHWebSocketClient) CollectOutput

func (c *SSHWebSocketClient) CollectOutput(ctx context.Context, timeout time.Duration) string

CollectOutput drains WebSocket messages for up to timeout and returns all output concatenated as a string. It uses a time.Timer (not SetReadDeadline) so that the underlying connection remains usable after the call returns.

func (*SSHWebSocketClient) SendInput

func (c *SSHWebSocketClient) SendInput(data []byte) error

SendInput sends raw bytes as a cmd message (base64-encoded).

func (*SSHWebSocketClient) SendResize

func (c *SSHWebSocketClient) SendResize(cols, rows int) error

SendResize sends a terminal resize message.

type ServerContainer

type ServerContainer struct {
	// Name is the Docker container name.
	Name string
	// ServerName is the faros Server resource name to register on the hub.
	ServerName string
	// HubURL is the URL of the faros hub, reachable from the runner's network.
	HubURL string
	// HubCluster is the kcp logical cluster name (e.g. "1tww43gelbj45g0k").
	// When set it is passed to the agent via --cluster so that the tunnel is
	// registered under the correct key.  Obtain it with ClusterNameFromKubeconfig.
	HubCluster string
	// Token is the bearer token for the agent.
	Token string
	// AgentBin is the host path to the faros binary.
	AgentBin string
}

ServerContainer manages a Docker container running lscr.io/linuxserver/openssh-server alongside a faros server-mode agent. The container runs with --network host so the agent can reach the hub at faros.localhost:9443.

func ServerContainerFromContext

func ServerContainerFromContext(ctx context.Context) (*ServerContainer, bool)

ServerContainerFromContext retrieves a ServerContainer from the context.

func (*ServerContainer) AgentLogs

func (s *ServerContainer) AgentLogs(ctx context.Context) (string, error)

AgentLogs returns the agent log from inside the container.

func (*ServerContainer) Start

func (s *ServerContainer) Start(ctx context.Context) error

Start launches the container, waits for sshd, copies the agent, and starts it.

func (*ServerContainer) Stop

func (s *ServerContainer) Stop(ctx context.Context) error

Stop removes the container.

func (*ServerContainer) WaitForAgentReady

func (s *ServerContainer) WaitForAgentReady(ctx context.Context, timeout time.Duration) error

WaitForAgentReady polls until the agent log shows the tunnel is connected.

type ServerProcess

type ServerProcess struct {
	// ServerName is the faros Server resource name to register on the hub.
	ServerName string
	// HubURL is the URL of the faros hub (base URL, no /clusters/ path).
	// Used only when HubKubeconfig is empty.
	HubURL string
	// HubKubeconfig is the path to a kubeconfig whose server URL contains the
	// kcp workspace cluster path (e.g. https://hub:9443/clusters/abc123).
	// When set the agent uses --hub-kubeconfig instead of --hub-url so that the
	// cluster name is correctly derived from the URL.  Always set this in e2e
	// tests to avoid the cluster-name mismatch bug with static tokens.
	HubKubeconfig string
	// Token is the bearer token for the agent.
	Token string
	// AgentBin is the path to the faros binary.
	AgentBin string
	// SSHPort is the port for the embedded test SSH server. Defaults to
	// DefaultTestSSHPort if zero.
	SSHPort int
	// SSHUser is the SSH username reported to the hub in Edge.Status.SSHCredentials.
	// When set alongside SSHPassword, the agent will register credentials so that
	// SSHUserMappingInherited tests can verify the username.
	// +optional
	SSHUser string
	// SSHPassword is the SSH password reported to the hub.
	// Required alongside SSHUser to trigger credential registration.
	// +optional
	SSHPassword string

	// SSHServer may be pre-populated with an already-configured TestSSHServer
	// (e.g. one set up with AddUser / AddAnyUserKey).  If non-nil it will be
	// used directly and its Start() method will be called; the SSHPort field
	// must match the server's Port.
	// If nil, a new TestSSHServer bound to SSHPort is created automatically.
	// After Start() returns, SSHServer is always set (whether pre-configured or
	// freshly created) and can be used to inspect ConnectedUsers.
	SSHServer *TestSSHServer
	// contains filtered or unexported fields
}

ServerProcess runs a faros server-mode agent as a local subprocess together with an embedded test SSH server. This replaces the Docker-based ServerContainer and avoids all external SSH daemon configuration issues.

func ServerProcessFromContext

func ServerProcessFromContext(ctx context.Context) (*ServerProcess, bool)

ServerProcessFromContext retrieves a ServerProcess from the context.

func (*ServerProcess) Logs

func (s *ServerProcess) Logs() string

Logs returns the combined stdout+stderr of the agent subprocess.

func (*ServerProcess) Start

func (s *ServerProcess) Start(ctx context.Context) error

Start launches the embedded SSH server and the agent subprocess.

func (*ServerProcess) Stop

func (s *ServerProcess) Stop()

Stop kills the agent subprocess and shuts down the SSH server.

func (*ServerProcess) WaitForAgentReady

func (s *ServerProcess) WaitForAgentReady(ctx context.Context, timeout time.Duration) error

WaitForAgentReady polls until the agent has started AND the revdial tunnel is connected (i.e. the hub can reach back to this agent for SSH sessions).

type TestSSHServer

type TestSSHServer struct {
	Port int
	// contains filtered or unexported fields
}

TestSSHServer is a minimal embedded SSH server for e2e tests. It accepts any authentication by default (security is provided by the revdial tunnel that already authenticated the caller) and executes commands via exec.Command. It is not safe for production use.

To restrict authentication to specific users/keys, call AddUser or AddAnyUserKey before Start.

func NewTestSSHServer

func NewTestSSHServer(port int) *TestSSHServer

NewTestSSHServer creates a TestSSHServer bound to the given port.

func (*TestSSHServer) AddAnyUserKey

func (s *TestSSHServer) AddAnyUserKey(pubKey gossh.PublicKey)

AddAnyUserKey configures the server to accept the given public key for any username. Useful for SSHUserMappingIdentity tests where the username is determined at runtime.

func (*TestSSHServer) AddUser

func (s *TestSSHServer) AddUser(username string, pubKey gossh.PublicKey)

AddUser configures the server to accept the given public key for the given username. May be called multiple times to add multiple users or multiple keys per user. When at least one call to AddUser or AddAnyUserKey has been made, NoClientAuth is disabled and only the configured credentials are accepted.

func (*TestSSHServer) ConnectedUsers

func (s *TestSSHServer) ConnectedUsers() []string

ConnectedUsers returns a snapshot of usernames that have authenticated since the server started. Safe to call concurrently with Start.

func (*TestSSHServer) Start

func (s *TestSSHServer) Start(ctx context.Context) error

Start starts the SSH server and returns once it is listening.

func (*TestSSHServer) Stop

func (s *TestSSHServer) Stop()

Stop closes the listener.

type TokenAgent

type TokenAgent struct {
	// contains filtered or unexported fields
}

TokenAgent manages a faros agent run process authenticated via a bootstrap join token instead of a SA-backed hub kubeconfig.

func NewAgentWithToken

func NewAgentWithToken(workDir, hubURL, edgeName, token string) *TokenAgent

NewAgentWithToken creates a TokenAgent that connects to the hub using a bootstrap join token (faros agent run --token). Use agentKubeconfig="" for server-type edges that have no downstream Kubernetes cluster.

func NewReconnectAgent

func NewReconnectAgent(workDir, hubURL, edgeName string) *TokenAgent

NewReconnectAgent creates a TokenAgent that reconnects to the hub using the previously saved kubeconfig (written during the first successful join-token exchange). No --token is passed — the binary's built-in auto-detection reads the saved kubeconfig from ~/.faros/agent-<edgeName>.kubeconfig.

Use this to verify the reconnect-after-restart flow end-to-end.

func (*TokenAgent) Start

func (a *TokenAgent) Start(ctx context.Context) error

Start launches the faros agent run process with the configured join token. It runs until Stop is called or the parent context is cancelled. When token is empty (e.g. NewReconnectAgent), no --token flag is passed and the binary auto-discovers the saved kubeconfig from ~/.faros/.

func (*TokenAgent) Stop

func (a *TokenAgent) Stop()

Stop terminates the token agent process.

func (*TokenAgent) WithAgentKubeconfig

func (a *TokenAgent) WithAgentKubeconfig(kc string) *TokenAgent

WithAgentKubeconfig sets the kubeconfig for the downstream Kubernetes cluster. For server-type edges this is not required.

func (*TokenAgent) WithCluster

func (a *TokenAgent) WithCluster(clusterName string) *TokenAgent

WithCluster sets the kcp logical cluster name so the agent connects to the correct workspace on the hub. Required when using a join token because the token alone does not carry cluster information.

func (*TokenAgent) WithSSHPassword

func (a *TokenAgent) WithSSHPassword(pass string) *TokenAgent

WithSSHPassword sets the SSH password the agent reports to the hub via X-Faros-SSH-Password WebSocket header (join-token mode) or the --ssh-password flag.

func (*TokenAgent) WithSSHUser

func (a *TokenAgent) WithSSHUser(user string) *TokenAgent

WithSSHUser sets the SSH username the agent reports to the hub via X-Faros-SSH-User WebSocket header (join-token mode) or the --ssh-user flag.

func (*TokenAgent) WithType

func (a *TokenAgent) WithType(t string) *TokenAgent

WithType overrides the edge type (default "server").

type TokenResponse

type TokenResponse struct {
	Token     string `json:"token"`
	ExpiresAt string `json:"expiresAt"`
}

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL