From 29b7d1b56f8edf2ec3ddec5df58b8938c30129c9 Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 6 Apr 2026 14:29:15 -0600 Subject: [PATCH 1/2] test --- .github/workflows/ci-test-e2e.yml | 1 + development/mockServer/Dockerfile | 13 -- development/mockServer/go.mod | 3 - development/mockServer/main.go | 361 ------------------------------ docker-compose-ci.yml | 6 +- 5 files changed, 4 insertions(+), 380 deletions(-) delete mode 100644 development/mockServer/Dockerfile delete mode 100644 development/mockServer/go.mod delete mode 100644 development/mockServer/main.go diff --git a/.github/workflows/ci-test-e2e.yml b/.github/workflows/ci-test-e2e.yml index 99395082a9fbc..e87ecbc9c9c5f 100644 --- a/.github/workflows/ci-test-e2e.yml +++ b/.github/workflows/ci-test-e2e.yml @@ -182,6 +182,7 @@ jobs: env: ENTERPRISE_LICENSE: ${{ inputs.enterprise-license }} TRANSPORTER: ${{ inputs.transporter }} + COMPOSE_PROFILES: ${{ inputs.type == 'api' && 'api' || '' }} run: | DEBUG_LOG_LEVEL=${DEBUG_LOG_LEVEL:-0} docker compose -f docker-compose-ci.yml up -d --wait diff --git a/development/mockServer/Dockerfile b/development/mockServer/Dockerfile deleted file mode 100644 index 1482eca071ec1..0000000000000 --- a/development/mockServer/Dockerfile +++ /dev/null @@ -1,13 +0,0 @@ -FROM golang:1.23-alpine AS build -WORKDIR /src -COPY go.mod ./ -COPY main.go ./ -RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /mock-server . -RUN adduser -D -u 1001 mockuser - -FROM scratch -COPY --from=build /etc/passwd /etc/passwd -COPY --from=build /mock-server /mock-server -USER mockuser -EXPOSE 8080 -ENTRYPOINT ["/mock-server"] diff --git a/development/mockServer/go.mod b/development/mockServer/go.mod deleted file mode 100644 index debf7bad4d70d..0000000000000 --- a/development/mockServer/go.mod +++ /dev/null @@ -1,3 +0,0 @@ -module github.com/opentdf/mock-server - -go 1.23 diff --git a/development/mockServer/main.go b/development/mockServer/main.go deleted file mode 100644 index 76ea57c75968a..0000000000000 --- a/development/mockServer/main.go +++ /dev/null @@ -1,361 +0,0 @@ -package main - -import ( - "encoding/json" - "fmt" - "log" - "net/http" - "os" - "strings" - "sync" - "time" -) - -type MockResponse struct { - StatusCode int `json:"status_code"` - Headers map[string]string `json:"headers,omitempty"` - Body json.RawMessage `json:"body"` - Times int `json:"times,omitempty"` -} - -type MockRule struct { - MockResponse - remaining int // -1 = unlimited -} - -type DynamicBulkRule struct { - PermitValues []string `json:"permit_values"` - DefaultDecision string `json:"default_decision"` -} - -type server struct { - mu sync.Mutex - mocks map[string][]*MockRule - dynamicBulk *DynamicBulkRule - log []RequestLog -} - -type RequestLog struct { - Timestamp string `json:"timestamp"` - Method string `json:"method"` - Path string `json:"path"` - Headers map[string]string `json:"headers"` - Body json.RawMessage `json:"body,omitempty"` - Matched bool `json:"matched"` -} - -func (s *server) handleSet(w http.ResponseWriter, r *http.Request) { - var req struct { - Method string `json:"method"` - Path string `json:"path"` - Response MockResponse `json:"response"` - } - if err := json.NewDecoder(r.Body).Decode(&req); err != nil { - http.Error(w, fmt.Sprintf(`{"error":%q}`, err.Error()), http.StatusBadRequest) - return - } - if req.Method == "" { - req.Method = "POST" - } - if req.Response.StatusCode == 0 { - req.Response.StatusCode = 200 - } - - remaining := req.Response.Times - if remaining == 0 { - remaining = -1 - } - - key := req.Method + " " + req.Path - rule := &MockRule{MockResponse: req.Response, remaining: remaining} - - s.mu.Lock() - s.mocks[key] = append(s.mocks[key], rule) - s.mu.Unlock() - - w.Header().Set("Content-Type", "application/json") - fmt.Fprintf(w, `{"ok":true,"key":%q}`, key) -} - -func (s *server) handleSetMany(w http.ResponseWriter, r *http.Request) { - var reqs []struct { - Method string `json:"method"` - Path string `json:"path"` - Response MockResponse `json:"response"` - } - if err := json.NewDecoder(r.Body).Decode(&reqs); err != nil { - http.Error(w, fmt.Sprintf(`{"error":%q}`, err.Error()), http.StatusBadRequest) - return - } - - s.mu.Lock() - for _, req := range reqs { - if req.Method == "" { - req.Method = "POST" - } - if req.Response.StatusCode == 0 { - req.Response.StatusCode = 200 - } - remaining := req.Response.Times - if remaining == 0 { - remaining = -1 - } - key := req.Method + " " + req.Path - s.mocks[key] = append(s.mocks[key], &MockRule{MockResponse: req.Response, remaining: remaining}) - } - s.mu.Unlock() - - w.Header().Set("Content-Type", "application/json") - fmt.Fprintf(w, `{"ok":true,"count":%d}`, len(reqs)) -} - -// POST /__mock/set-bulk-decision — register a dynamic bulk decision handler. -// -// { -// "permit_values": ["admin@test.com"], -// "default_decision": "DECISION_DENY" -// } -// -// When a GetDecisionBulk request comes in, the mock inspects each entity's -// emailAddress or id field. If the value is in permit_values → DECISION_PERMIT, -// otherwise → default_decision. -func (s *server) handleSetBulkDecision(w http.ResponseWriter, r *http.Request) { - var rule DynamicBulkRule - if err := json.NewDecoder(r.Body).Decode(&rule); err != nil { - http.Error(w, fmt.Sprintf(`{"error":%q}`, err.Error()), http.StatusBadRequest) - return - } - if rule.DefaultDecision == "" { - rule.DefaultDecision = "DECISION_DENY" - } - - s.mu.Lock() - s.dynamicBulk = &rule - s.mu.Unlock() - - w.Header().Set("Content-Type", "application/json") - fmt.Fprint(w, `{"ok":true}`) -} - -func (s *server) handleReset(w http.ResponseWriter, r *http.Request) { - s.mu.Lock() - s.mocks = make(map[string][]*MockRule) - s.dynamicBulk = nil - s.log = nil - s.mu.Unlock() - - w.Header().Set("Content-Type", "application/json") - fmt.Fprint(w, `{"ok":true}`) -} - -func (s *server) handleLog(w http.ResponseWriter, r *http.Request) { - s.mu.Lock() - logs := s.log - s.mu.Unlock() - - w.Header().Set("Content-Type", "application/json") - json.NewEncoder(w).Encode(logs) -} - -func (s *server) handleHealth(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Content-Type", "application/json") - fmt.Fprint(w, `{"status":"ok"}`) -} - -// buildDynamicBulkResponse inspects a GetDecisionBulk request body and returns -// per-entity decisions based on the dynamic rule. -func (s *server) buildDynamicBulkResponse(rule *DynamicBulkRule, body json.RawMessage) json.RawMessage { - var req struct { - DecisionRequests []struct { - EntityIdentifier struct { - EntityChain struct { - Entities []json.RawMessage `json:"entities"` - } `json:"entityChain"` - } `json:"entityIdentifier"` - Resources []struct { - EphemeralId string `json:"ephemeralId"` - } `json:"resources"` - } `json:"decisionRequests"` - } - if err := json.Unmarshal(body, &req); err != nil { - log.Printf("dynamic bulk: failed to parse request: %v", err) - return []byte(`{"decisionResponses":[]}`) - } - - permitSet := make(map[string]bool, len(rule.PermitValues)) - for _, v := range rule.PermitValues { - permitSet[strings.ToLower(v)] = true - } - - type resourceDecision struct { - Decision string `json:"decision"` - EphemeralResourceId string `json:"ephemeralResourceId,omitempty"` - } - type decisionResponse struct { - ResourceDecisions []resourceDecision `json:"resourceDecisions"` - } - - var responses []decisionResponse - for _, dr := range req.DecisionRequests { - entityValue := extractEntityValue(dr.EntityIdentifier.EntityChain.Entities) - decision := rule.DefaultDecision - if permitSet[strings.ToLower(entityValue)] { - decision = "DECISION_PERMIT" - } - - var rds []resourceDecision - for _, res := range dr.Resources { - rds = append(rds, resourceDecision{ - Decision: decision, - EphemeralResourceId: res.EphemeralId, - }) - } - if len(rds) == 0 { - rds = append(rds, resourceDecision{Decision: decision}) - } - responses = append(responses, decisionResponse{ResourceDecisions: rds}) - } - - result, _ := json.Marshal(map[string]interface{}{ - "decisionResponses": responses, - }) - return result -} - -// extractEntityValue pulls the emailAddress or id from an entity object. -func extractEntityValue(entities []json.RawMessage) string { - for _, raw := range entities { - var entity map[string]interface{} - if err := json.Unmarshal(raw, &entity); err != nil { - continue - } - if v, ok := entity["emailAddress"].(string); ok { - return v - } - if v, ok := entity["id"].(string); ok { - return v - } - } - return "" -} - -func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { - switch { - case r.URL.Path == "/__mock/set" && r.Method == http.MethodPost: - s.handleSet(w, r) - return - case r.URL.Path == "/__mock/set-many" && r.Method == http.MethodPost: - s.handleSetMany(w, r) - return - case r.URL.Path == "/__mock/set-bulk-decision" && r.Method == http.MethodPost: - s.handleSetBulkDecision(w, r) - return - case r.URL.Path == "/__mock/reset" && (r.Method == http.MethodDelete || r.Method == http.MethodPost): - s.handleReset(w, r) - return - case r.URL.Path == "/__mock/log" && r.Method == http.MethodGet: - s.handleLog(w, r) - return - case r.URL.Path == "/__mock/health" && r.Method == http.MethodGet: - s.handleHealth(w, r) - return - } - - var bodyBytes json.RawMessage - if r.Body != nil { - _ = json.NewDecoder(r.Body).Decode(&bodyBytes) - } - - headers := make(map[string]string) - for k := range r.Header { - headers[k] = r.Header.Get(k) - } - - entry := RequestLog{ - Timestamp: time.Now().UTC().Format(time.RFC3339), - Method: r.Method, - Path: r.URL.Path, - Headers: headers, - Body: bodyBytes, - } - - key := r.Method + " " + r.URL.Path - - s.mu.Lock() - - // Check for dynamic bulk decision handler first (only for GetDecisionBulk). - if strings.HasSuffix(r.URL.Path, "/GetDecisionBulk") && s.dynamicBulk != nil { - rule := s.dynamicBulk - entry.Matched = true - s.log = append(s.log, entry) - s.mu.Unlock() - - responseBody := s.buildDynamicBulkResponse(rule, bodyBytes) - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusOK) - w.Write(responseBody) - log.Printf("HIT %s %s → 200 (dynamic bulk)", r.Method, r.URL.Path) - return - } - - rules := s.mocks[key] - var matched *MockRule - if len(rules) > 0 { - matched = rules[0] - if matched.remaining > 0 { - matched.remaining-- - if matched.remaining == 0 { - s.mocks[key] = rules[1:] - } - } - } - entry.Matched = matched != nil - s.log = append(s.log, entry) - s.mu.Unlock() - - if matched == nil { - w.Header().Set("Content-Type", "application/json") - w.WriteHeader(http.StatusNotFound) - fmt.Fprintf(w, `{"error":"no mock registered","method":%q,"path":%q}`, r.Method, r.URL.Path) - log.Printf("MISS %s %s", r.Method, r.URL.Path) - return - } - - for k, v := range matched.Headers { - w.Header().Set(k, v) - } - if w.Header().Get("Content-Type") == "" { - w.Header().Set("Content-Type", "application/json") - } - w.WriteHeader(matched.StatusCode) - w.Write(matched.Body) - log.Printf("HIT %s %s → %d", r.Method, r.URL.Path, matched.StatusCode) -} - -func main() { - if len(os.Args) > 1 && os.Args[1] == "-healthcheck" { - port := os.Getenv("PORT") - if port == "" { - port = "8080" - } - resp, err := http.Get("http://127.0.0.1:" + port + "/__mock/health") - if err != nil || resp.StatusCode != 200 { - os.Exit(1) - } - os.Exit(0) - } - - port := os.Getenv("PORT") - if port == "" { - port = "8080" - } - - s := &server{ - mocks: make(map[string][]*MockRule), - } - - log.Printf("mock-server listening on :%s", port) - if err := http.ListenAndServe(":"+port, s); err != nil { - log.Fatal(err) - } -} diff --git a/docker-compose-ci.yml b/docker-compose-ci.yml index 80dee56453df3..1c8aed4d09ee2 100644 --- a/docker-compose-ci.yml +++ b/docker-compose-ci.yml @@ -201,9 +201,9 @@ services: image: kong/httpbin mock-server: - build: - context: ./development/mockServer - dockerfile: Dockerfile + image: docker.io/kaleman14/mockserver:latest + profiles: + - api ports: - 4000:8080 healthcheck: From 664d1c4add6308e8327315b287befd780a0f83ef Mon Sep 17 00:00:00 2001 From: Kevin Aleman Date: Mon, 6 Apr 2026 18:01:12 -0600 Subject: [PATCH 2/2] Apply suggestion from @KevLehman --- docker-compose-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker-compose-ci.yml b/docker-compose-ci.yml index 1c8aed4d09ee2..32e31b448f9b4 100644 --- a/docker-compose-ci.yml +++ b/docker-compose-ci.yml @@ -201,7 +201,7 @@ services: image: kong/httpbin mock-server: - image: docker.io/kaleman14/mockserver:latest + image: kaleman14/mockserver:latest profiles: - api ports: