Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions .github/workflows/e2e-tests-kind.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
name: E2E Tests (kind)

on:
workflow_call:
inputs:
image:
required: true
type: string

env:
REGISTRY: quay.io
IMAGE_NAME: rhacs-eng/roxie

jobs:
e2e-tests-kind:
runs-on: ubuntu-latest
env:
SKIP_OLM_TESTS: "true"
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha || github.sha }}

- name: Set up Go
uses: actions/setup-go@v6
with:
go-version-file: go.mod
cache: true

- name: Log in to Quay.io
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_TOKEN }}

- name: Extract roxie binary from image
run: |
docker create --name roxie-extract "${{ inputs.image }}"
docker cp roxie-extract:/usr/local/bin/roxie "$GITHUB_WORKSPACE/roxie"
docker rm roxie-extract

- name: Install roxie binary
run: |
cp "${GITHUB_WORKSPACE}/roxie" /usr/local/bin/roxie
chmod +x /usr/local/bin/roxie
roxie version

- name: Install roxctl
env:
ROXCTL_VERSION: "4.10.0"
ROXCTL_SHA256: "5db647b14569465866c0162522e83393ebf02f671f4556b1b3ed551b9f8433bc"
run: |
curl -fsSLo /usr/local/bin/roxctl \
"https://mirror.openshift.com/pub/rhacs/assets/${ROXCTL_VERSION}/bin/Linux/roxctl"
echo "${ROXCTL_SHA256} /usr/local/bin/roxctl" | sha256sum -c -
chmod +x /usr/local/bin/roxctl
roxctl version

- name: Create kind cluster
uses: helm/kind-action@v1
with:
cluster_name: roxie-e2e

- name: Run e2e tests
env:
REGISTRY_USERNAME: ${{ secrets.QUAY_RHACS_ENG_RO_USERNAME }}
REGISTRY_PASSWORD: ${{ secrets.QUAY_RHACS_ENG_RO_PASSWORD }}
run: |
make run-test-e2e
7 changes: 7 additions & 0 deletions .github/workflows/main-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ jobs:
image: ${{ needs.build-roxie-image.outputs.image }}
secrets: inherit

e2e-tests-kind:
needs: [ build-roxie-image ]
uses: ./.github/workflows/e2e-tests-kind.yml
with:
image: ${{ needs.build-roxie-image.outputs.image }}
secrets: inherit

delete-dev-cluster:
if: ${{ always() && needs.create-dev-cluster.result == 'success' }}
needs: [ create-dev-cluster, e2e-tests ]
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,13 @@ jobs:
image: ${{ needs.build-roxie-image.outputs.image }}
secrets: inherit

e2e-tests-kind:
needs: [ build-roxie-image ]
uses: ./.github/workflows/e2e-tests-kind.yml
with:
image: ${{ needs.build-roxie-image.outputs.image }}
secrets: inherit

delete-dev-cluster:
if: ${{ always() && needs.create-dev-cluster.result == 'success' }}
needs: [ create-dev-cluster, e2e-tests ]
Expand Down
8 changes: 0 additions & 8 deletions cmd/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,6 @@ func runDeploy(cmd *cobra.Command, args []string) error {
return errors.New("running without a controlling terminal requires --envrc to be set")
}

if envrc != "" && portForwarding {
return errors.New("cannot use --envrc with --port-forwarding. The --envrc flag is for non-interactive mode with remote cluster access")
}

if envrc != "" && exposure == "none" {
return errors.New("cannot use --envrc with --exposure=none. The --envrc flag requires a remotely accessible endpoint (e.g., --exposure=loadbalancer)")
}

portForwardEnabledFinal := portForwarding || exposure == "none"

if env.RunningInRoxieContainer {
Expand Down
17 changes: 13 additions & 4 deletions internal/deployer/deploy_via_operator.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,11 +607,20 @@ func (d *Deployer) configureCentralEndpoint(ctx context.Context, exposure string
}
}

endpoint, err := d.portForward.Start(d.centralNamespace, serviceName, 443, 8443)
if err != nil {
return fmt.Errorf("failed to start port-forward: %w", err)
if d.envrcFile != "" {
endpoint, pid, err := d.portForward.StartDetached(d.centralNamespace, serviceName, 443, 8443)
if err != nil {
return fmt.Errorf("failed to start detached port-forward: %w", err)
}
d.centralEndpoint = endpoint
d.portForwardPID = pid
} else {
endpoint, err := d.portForward.Start(d.centralNamespace, serviceName, 443, 8443)
if err != nil {
return fmt.Errorf("failed to start port-forward: %w", err)
}
d.centralEndpoint = endpoint
}
d.centralEndpoint = endpoint
} else if exposure == "loadbalancer" {
endpoint, err := d.waitForLoadBalancer(ctx, d.centralNamespace, "central-loadbalancer", 300)
if err != nil {
Expand Down
30 changes: 29 additions & 1 deletion internal/deployer/deployer.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import (
"fmt"
"os"
"os/exec"
"strconv"
"strings"
"sync"
"syscall"
"time"

"github.com/fatih/color"
Expand Down Expand Up @@ -128,6 +130,7 @@ type Deployer struct {
securedClusterOverrides map[string]interface{}
featureFlagOverrides map[string]interface{}
envrcFile string
portForwardPID int
useOLM bool
useKonflux bool
shouldDeployOperator bool
Expand Down Expand Up @@ -555,6 +558,12 @@ func New(log *logger.Logger) (*Deployer, error) {
d.roxCACertFile = caCert
}

if pidStr := os.Getenv("ROXIE_PORT_FORWARD_PID"); pidStr != "" {
if pid, err := strconv.Atoi(pidStr); err == nil {
d.portForwardPID = pid
}
}

d.kubeContext = env.GetCurrentContext()

clusterResourceKinds, err := d.getClusterResourceKinds()
Expand Down Expand Up @@ -601,6 +610,22 @@ func (d *Deployer) Cleanup() {
}
}

func (d *Deployer) stopDetachedPortForward() {
if d.portForwardPID == 0 {
return
}
proc, err := os.FindProcess(d.portForwardPID)
if err != nil {
return
}
if err := proc.Signal(syscall.SIGKILL); err != nil {
d.logger.Dimf("Detached port-forward (pid %d) already gone", d.portForwardPID)
return
}
d.logger.Dimf("Stopped detached port-forward (pid %d)", d.portForwardPID)
d.portForwardPID = 0
}

// Deploy deploys the specified components to the cluster.
func (d *Deployer) Deploy(ctx context.Context, components component.Component, resources, exposure string) error {
adjustedResources, adjustedExposure, adjustedPortForward := d.clusterDefaults.ApplyConvenienceDefaults(
Expand Down Expand Up @@ -680,7 +705,6 @@ func (d *Deployer) deployCentral(ctx context.Context, resources, exposure string
return err
}

// envrc may be used from different processes, so use actual endpoint not port-forward
if d.envrcFile != "" {
d.logger.Dimf("Writing environment variables to %s", d.envrcFile)
if err := d.writeEnvrcFile(ctx, exposure, portForwardWanted); err != nil {
Expand Down Expand Up @@ -757,6 +781,7 @@ func (d *Deployer) teardownCentral(ctx context.Context) error {
}

d.portForward.Stop()
d.stopDetachedPortForward()

// Add pause-reconcile annotation to not have the operator interfere during resource deletion.
if d.doesResourceExist(ctx, "central", "stackrox-central-services", d.centralNamespace) {
Expand Down Expand Up @@ -1103,6 +1128,9 @@ func (d *Deployer) writeEnvrcFile(ctx context.Context, exposure string, portForw
fmt.Fprintf(&content, "export ROX_USERNAME=%q\n", AdminUsername)
fmt.Fprintf(&content, "export ROX_ADMIN_PASSWORD=%q\n", d.centralPassword)
fmt.Fprintf(&content, "export ROX_CA_CERT_FILE=%q\n", d.roxCACertFile)
if d.portForwardPID != 0 {
fmt.Fprintf(&content, "export ROXIE_PORT_FORWARD_PID=%d\n", d.portForwardPID)
}

if err := os.WriteFile(d.envrcFile, []byte(content.String()), 0600); err != nil {
return fmt.Errorf("failed to write envrc file: %w", err)
Expand Down
45 changes: 45 additions & 0 deletions internal/portforward/portforward.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,51 @@ func (m *Manager) Start(namespace, serviceName string, remotePort, preferredLoca
return endpoint, nil
}

// StartDetached starts port-forward as a detached process that survives the
// parent process exiting. Returns the endpoint and the PID of the subprocess.
// The caller is responsible for killing the process when done.
func (m *Manager) StartDetached(namespace, serviceName string, remotePort, preferredLocalPort int) (string, int, error) {
localPort, err := m.findFreeLocalPort(preferredLocalPort)
if err != nil {
return "", 0, fmt.Errorf("failed to find free port: %w", err)
}

cmd := exec.Command(
m.kubectl,
"-n", namespace,
"port-forward",
fmt.Sprintf("svc/%s", serviceName),
fmt.Sprintf("%d:%d", localPort, remotePort),
"--address", "127.0.0.1",
)

cmd.SysProcAttr = &syscall.SysProcAttr{
Setsid: true,
}

cmd.Stdout = nil
cmd.Stderr = nil

if err := cmd.Start(); err != nil {
return "", 0, fmt.Errorf("failed to start port-forward: %w", err)
}

pid := cmd.Process.Pid

// Release the process so it won't be waited on by this process.
cmd.Process.Release()

if !m.waitTCPReady("127.0.0.1", localPort, 20.0) {
syscall.Kill(pid, syscall.SIGTERM)
return "", 0, fmt.Errorf("port-forward did not become ready")
}

endpoint := fmt.Sprintf("127.0.0.1:%d", localPort)
m.logger.Successf("✓ Detached port-forward active at https://%s (pid %d)", endpoint, pid)

return endpoint, pid, nil
}

// Stop stops the active port-forward if running
func (m *Manager) Stop() {
if m.proc == nil || m.proc.Process == nil {
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/basic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ func TestDeployBothSimple(t *testing.T) {
envrcFile.Close()

t.Log("=== Deploying both components together ===")
args := append([]string{roxieBinary, "deploy", "--early-readiness", "both", "--envrc", envrcPath}, commonDeployArgsNoPortForward...)
args := append([]string{roxieBinary, "deploy", "--early-readiness", "both", "--envrc", envrcPath}, commonDeployArgs...)
runCommand(t, deployTimeout*2, nil, args...)

// Verify namespaces exist and have managed-by labels
Expand Down
2 changes: 1 addition & 1 deletion tests/e2e/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ func TestDeployBothComponentsTogetherInSingleNamespace(t *testing.T) {
envrcFile.Close()

t.Log("=== Deploying both components in single namespace ===")
args := append([]string{roxieBinary, "deploy", "both", "--single-namespace", "--early-readiness", "--envrc", envrcPath}, commonDeployArgsNoPortForward...)
args := append([]string{roxieBinary, "deploy", "both", "--single-namespace", "--early-readiness", "--envrc", envrcPath}, commonDeployArgs...)
runCommand(t, deployTimeout*2, nil, args...)

verifyCentralInstalled(t, "stackrox")
Expand Down
3 changes: 1 addition & 2 deletions tests/e2e/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ const (
)

var (
commonDeployArgs = []string{"--port-forwarding", "--exposure=none", "--resources=small"}
commonDeployArgsNoPortForward = []string{"--exposure=loadbalancer", "--resources=small"}
commonDeployArgs = []string{"--resources=small"}

roxieBinary = "roxie"
)
Expand Down
18 changes: 9 additions & 9 deletions tests/e2e/olm_switch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ func TestOLMToNonOLMSwitch(t *testing.T) {

// Step 1: Deploy central with OLM operator
t.Log("=== Step 1: Deploy central with OLM operator ===")
args := append([]string{roxieBinary, "deploy", "central", "--olm", "--envrc", envrcPath}, commonDeployArgsNoPortForward...)
args := append([]string{roxieBinary, "deploy", "central", "--olm", "--envrc", envrcPath}, commonDeployArgs...)
runCommand(t, deployTimeout, nil, args...)

// Verify operator is in OLM mode
Expand All @@ -92,7 +92,7 @@ func TestOLMToNonOLMSwitch(t *testing.T) {

// Step 2: Deploy central again without OLM (should switch modes)
t.Log("=== Step 2: Redeploy central without OLM (triggering mode switch) ===")
args = append([]string{roxieBinary, "deploy", "central", "--envrc", envrcPath}, commonDeployArgsNoPortForward...)
args = append([]string{roxieBinary, "deploy", "central", "--envrc", envrcPath}, commonDeployArgs...)
runCommand(t, deployTimeout, nil, args...)

// Verify operator switched to non-OLM mode
Expand Down Expand Up @@ -127,7 +127,7 @@ func TestNonOLMToOLMSwitch(t *testing.T) {

// Step 1: Deploy central without OLM (non-OLM operator)
t.Log("=== Step 1: Deploy central with non-OLM operator ===")
args := append([]string{roxieBinary, "deploy", "central", "--envrc", envrcPath}, commonDeployArgsNoPortForward...)
args := append([]string{roxieBinary, "deploy", "central", "--envrc", envrcPath}, commonDeployArgs...)
runCommand(t, deployTimeout, nil, args...)

// Verify operator is in non-OLM mode
Expand All @@ -140,7 +140,7 @@ func TestNonOLMToOLMSwitch(t *testing.T) {

// Step 2: Deploy central again with OLM (should switch modes)
t.Log("=== Step 2: Redeploy central with OLM (triggering mode switch) ===")
args = append([]string{roxieBinary, "deploy", "central", "--olm", "--envrc", envrcPath}, commonDeployArgsNoPortForward...)
args = append([]string{roxieBinary, "deploy", "central", "--olm", "--envrc", envrcPath}, commonDeployArgs...)
runCommand(t, deployTimeout, nil, args...)

// Verify operator switched to OLM mode
Expand Down Expand Up @@ -179,7 +179,7 @@ func TestOLMOperatorVersionUpgrade(t *testing.T) {

// Step 1: Deploy central with OLM operator
t.Log("=== Step 1: Deploy central with OLM operator ===")
args := append([]string{roxieBinary, "deploy", "central", "--olm", "--envrc", envrcPath}, commonDeployArgsNoPortForward...)
args := append([]string{roxieBinary, "deploy", "central", "--olm", "--envrc", envrcPath}, commonDeployArgs...)
runCommand(t, deployTimeout, nil, args...)

// Verify operator is in OLM mode
Expand All @@ -201,7 +201,7 @@ func TestOLMOperatorVersionUpgrade(t *testing.T) {

// Step 2: Redeploy with same version (should skip if version matches)
t.Log("=== Step 2: Redeploy with same version (should detect correct version) ===")
args = append([]string{roxieBinary, "deploy", "central", "--olm", "--envrc", envrcPath}, commonDeployArgsNoPortForward...)
args = append([]string{roxieBinary, "deploy", "central", "--olm", "--envrc", envrcPath}, commonDeployArgs...)
runCommand(t, deployTimeout, nil, args...)

// Verify operator is still in OLM mode and deployment exists
Expand Down Expand Up @@ -237,7 +237,7 @@ func TestSecuredClusterWithOLMSwitch(t *testing.T) {

// Step 1: Deploy central with OLM
t.Log("=== Step 1: Deploy central with OLM ===")
args := append([]string{roxieBinary, "deploy", "--early-readiness", "central", "--olm", "--envrc", envrcPath}, commonDeployArgsNoPortForward...)
args := append([]string{roxieBinary, "deploy", "--early-readiness", "central", "--olm", "--envrc", envrcPath}, commonDeployArgs...)
runCommand(t, deployTimeout, nil, args...)

verifyOperatorMode(t, true)
Expand All @@ -251,7 +251,7 @@ func TestSecuredClusterWithOLMSwitch(t *testing.T) {

// Step 2: Deploy secured-cluster (should reuse OLM operator)
t.Log("=== Step 2: Deploy secured-cluster (should reuse OLM operator) ===")
args = append([]string{roxieBinary, "deploy", "--early-readiness", "secured-cluster", "--olm"}, commonDeployArgsNoPortForward...)
args = append([]string{roxieBinary, "deploy", "--early-readiness", "secured-cluster", "--olm"}, commonDeployArgs...)
runCommand(t, deployTimeout, envrcEnv, args...)

// Verify operator is still in OLM mode
Expand All @@ -260,7 +260,7 @@ func TestSecuredClusterWithOLMSwitch(t *testing.T) {

// Step 3: Switch to non-OLM by redeploying secured-cluster without --olm
t.Log("=== Step 3: Redeploy secured-cluster without OLM (triggering mode switch) ===")
args = append([]string{roxieBinary, "deploy", "--early-readiness", "secured-cluster"}, commonDeployArgsNoPortForward...)
args = append([]string{roxieBinary, "deploy", "--early-readiness", "secured-cluster"}, commonDeployArgs...)
runCommand(t, deployTimeout, envrcEnv, args...)

// Verify operator switched to non-OLM mode
Expand Down
Loading