Skip to content

Commit 76fd4f0

Browse files
committed
e2e: add building blocks and test agent mode
add building blocks for tests: - proxy which acts as a reverse proxy between agent and backend - observer to count the requests between agent and backend - actioner to act as a user on backend or to create jwt for agent - dbReadWriter to act directly on the db to check or create resources like private keys for agent authentication Signed-off-by: Cosmin Tupangiu <cosmin@redhat.com>
1 parent 42fd4ae commit 76fd4f0

File tree

13 files changed

+875
-38
lines changed

13 files changed

+875
-38
lines changed

.github/workflows/e2e.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,6 @@ jobs:
5858
- name: Run e2e tests
5959
run: |
6060
./bin/e2e \
61-
--backend-image=${{ env.BACKEND_IMAGE }}:${{ env.BACKEND_TAG }} \
62-
--agent-image=${{ env.AGENT_IMAGE }} \
63-
--podman-socket=${{ env.PODMAN_SOCKET }}
61+
-backend-image=${{ env.BACKEND_IMAGE }}:${{ env.BACKEND_TAG }} \
62+
-agent-image=${{ env.AGENT_IMAGE }} \
63+
-podman-socket=${{ env.PODMAN_SOCKET }}

Makefile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: generate generate.proto build build.e2e run help tidy tidy-check clean lint format check-format check-generate validate-all image
1+
.PHONY: generate generate.proto build build.e2e e2e run help tidy tidy-check clean lint format check-format check-generate validate-all image
22

33
PODMAN ?= podman
44
GIT_COMMIT=$(shell git rev-list -1 HEAD --abbrev-commit)
@@ -19,6 +19,8 @@ GO_BUILD_FLAGS := ${GO_BUILD_FLAGS}
1919
help:
2020
@echo "Targets:"
2121
@echo " build: build the agent binary"
22+
@echo " build.e2e: build the e2e test binary"
23+
@echo " e2e: run e2e tests"
2224
@echo " image: build container image"
2325
@echo " run: run the agent"
2426
@echo " run.ui: start React dev server"
@@ -44,6 +46,13 @@ build.e2e:
4446
go build -tags "exclude_graphdriver_btrfs containers_image_openpgp" -o bin/e2e ./test/e2e
4547
@echo "Build complete: bin/e2e"
4648

49+
E2E_AGENT_IMAGE ?= $(IMAGE_NAME):$(IMAGE_TAG)
50+
E2E_BACKEND_IMAGE ?= quay.io/kubev2v/migration-planner-api:latest
51+
52+
e2e: build.e2e
53+
@echo "🧪 Running e2e tests..."
54+
./bin/e2e -agent-image=$(E2E_AGENT_IMAGE) -backend-image=$(E2E_BACKEND_IMAGE)
55+
4756
# Build container image
4857
image:
4958
@echo "📦 Building container image $(IMAGE_NAME):$(IMAGE_TAG)..."

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,12 @@ require (
1313
github.com/gin-contrib/zap v1.1.5
1414
github.com/gin-gonic/gin v1.11.0
1515
github.com/go-extras/cobraflags v0.0.0-20260116100222-f76efc9500d4
16+
github.com/golang-jwt/jwt/v5 v5.3.0
1617
github.com/google/uuid v1.6.0
1718
github.com/jzelinskie/cobrautil/v2 v2.0.0-20240819150235-f7fe73942d0f
1819
github.com/kubev2v/forklift v0.0.0-20260109132042-6ac29b3130c3
1920
github.com/kubev2v/migration-planner v0.3.1-0.20260120154119-449e7b20a97c
21+
github.com/lib/pq v1.10.0
2022
github.com/oapi-codegen/runtime v1.1.2
2123
github.com/onsi/ginkgo/v2 v2.27.2
2224
github.com/onsi/gomega v1.38.2

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,8 @@ github.com/gofrs/flock v0.8.1 h1:+gYjHKf32LDeiEEFhQaotPbLuUXjY5ZqxKgXy7n59aw=
289289
github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU=
290290
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
291291
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
292+
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
293+
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
292294
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA=
293295
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0=
294296
github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A=

test/e2e/actioner.go

Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"crypto/rsa"
6+
"encoding/json"
7+
"fmt"
8+
"net/http"
9+
"time"
10+
11+
"github.com/golang-jwt/jwt/v5"
12+
)
13+
14+
type Actioner struct {
15+
client *http.Client
16+
backendURL string
17+
}
18+
19+
func NewActioner(backendURL string) *Actioner {
20+
return &Actioner{
21+
client: &http.Client{},
22+
backendURL: backendURL,
23+
}
24+
}
25+
26+
type SourceCreate struct {
27+
Name string `json:"name"`
28+
}
29+
30+
type SourceResponse struct {
31+
ID string `json:"id"`
32+
Name string `json:"name"`
33+
}
34+
35+
type AssessmentForm struct {
36+
Name string `json:"name"`
37+
SourceType string `json:"sourceType"`
38+
SourceID *string `json:"sourceId,omitempty"`
39+
}
40+
41+
type AssessmentResponse struct {
42+
ID string `json:"id"`
43+
Name string `json:"name"`
44+
}
45+
46+
func (a *Actioner) CreateSource(name string) (string, error) {
47+
body := SourceCreate{Name: name}
48+
data, err := json.Marshal(body)
49+
if err != nil {
50+
return "", fmt.Errorf("marshaling request: %w", err)
51+
}
52+
53+
req, err := http.NewRequest(http.MethodPost, a.backendURL+"/api/v1/sources", bytes.NewReader(data))
54+
if err != nil {
55+
return "", fmt.Errorf("creating request: %w", err)
56+
}
57+
req.Header.Set("Content-Type", "application/json")
58+
59+
resp, err := a.client.Do(req)
60+
if err != nil {
61+
return "", fmt.Errorf("sending request: %w", err)
62+
}
63+
defer resp.Body.Close()
64+
65+
if resp.StatusCode != http.StatusCreated {
66+
return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
67+
}
68+
69+
var source SourceResponse
70+
if err := json.NewDecoder(resp.Body).Decode(&source); err != nil {
71+
return "", fmt.Errorf("decoding response: %w", err)
72+
}
73+
74+
return source.ID, nil
75+
}
76+
77+
func (a *Actioner) DeleteSource(sourceID string) error {
78+
req, err := http.NewRequest(http.MethodDelete, a.backendURL+"/api/v1/sources/"+sourceID, nil)
79+
if err != nil {
80+
return fmt.Errorf("creating request: %w", err)
81+
}
82+
83+
resp, err := a.client.Do(req)
84+
if err != nil {
85+
return fmt.Errorf("sending request: %w", err)
86+
}
87+
defer resp.Body.Close()
88+
89+
if resp.StatusCode != http.StatusOK {
90+
return fmt.Errorf("unexpected status code: %d", resp.StatusCode)
91+
}
92+
93+
return nil
94+
}
95+
96+
func (a *Actioner) CreateAssessment(name string, sourceID *string) (string, error) {
97+
body := AssessmentForm{
98+
Name: name,
99+
SourceType: "agent",
100+
SourceID: sourceID,
101+
}
102+
data, err := json.Marshal(body)
103+
if err != nil {
104+
return "", fmt.Errorf("marshaling request: %w", err)
105+
}
106+
107+
req, err := http.NewRequest(http.MethodPost, a.backendURL+"/api/v1/assessments", bytes.NewReader(data))
108+
if err != nil {
109+
return "", fmt.Errorf("creating request: %w", err)
110+
}
111+
req.Header.Set("Content-Type", "application/json")
112+
113+
resp, err := a.client.Do(req)
114+
if err != nil {
115+
return "", fmt.Errorf("sending request: %w", err)
116+
}
117+
defer resp.Body.Close()
118+
119+
if resp.StatusCode != http.StatusCreated {
120+
return "", fmt.Errorf("unexpected status code: %d", resp.StatusCode)
121+
}
122+
123+
var assessment AssessmentResponse
124+
if err := json.NewDecoder(resp.Body).Decode(&assessment); err != nil {
125+
return "", fmt.Errorf("decoding response: %w", err)
126+
}
127+
128+
return assessment.ID, nil
129+
}
130+
131+
func (a *Actioner) GenerateAgentToken(sourceID, kid string, privateKey *rsa.PrivateKey) (string, error) {
132+
type AgentTokenClaims struct {
133+
SourceID string `json:"source_id"`
134+
jwt.RegisteredClaims
135+
}
136+
137+
claims := AgentTokenClaims{
138+
sourceID,
139+
jwt.RegisteredClaims{
140+
ExpiresAt: jwt.NewNumericDate(time.Now().Add(30 * time.Hour)),
141+
IssuedAt: jwt.NewNumericDate(time.Now()),
142+
NotBefore: jwt.NewNumericDate(time.Now()),
143+
Issuer: "assisted-migrations",
144+
Subject: sourceID,
145+
ID: "1",
146+
Audience: []string{"assisted-migrations"},
147+
},
148+
}
149+
150+
token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
151+
token.Header["kid"] = kid
152+
signedToken, err := token.SignedString(privateKey)
153+
if err != nil {
154+
return "", fmt.Errorf("failed to sign agent token: %w", err)
155+
}
156+
157+
return signedToken, nil
158+
}

0 commit comments

Comments
 (0)