diff --git a/docker-compose.yml b/docker-compose.yml index cd7483f8..55a789fe 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -84,3 +84,13 @@ services: TF_LOG: INFO command: > sh -c "tail -F /dev/null" + + mock_frontegg: + build: mocks/frontegg + ports: + - "3000:3000" + + mock_cloud_api: + build: mocks/cloud + ports: + - "3001:3001" diff --git a/mocks/cloud/Dockerfile b/mocks/cloud/Dockerfile new file mode 100644 index 00000000..1bda83d2 --- /dev/null +++ b/mocks/cloud/Dockerfile @@ -0,0 +1,30 @@ +# Start from the official Golang base image +FROM golang:1.20 as builder + +# Set the Current Working Directory inside the container +WORKDIR /app + +# Copy go mod and sum files +COPY go.mod go.sum ./ + +# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed +RUN go mod download + +# Copy the source from the current directory to the Working Directory inside the container +COPY . . + +# Build the Go app +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o mockserver . + +# Start a new stage from scratch +FROM alpine:latest + +RUN apk --no-cache add ca-certificates + +WORKDIR /root/ + +# Copy the Pre-built binary file from the previous stage +COPY --from=builder /app/mockserver . + +# Command to run the executable +CMD ["./mockserver"] diff --git a/mocks/cloud/go.mod b/mocks/cloud/go.mod new file mode 100644 index 00000000..44e380d8 --- /dev/null +++ b/mocks/cloud/go.mod @@ -0,0 +1,5 @@ +module cloud-mockserver + +go 1.20 + +require github.com/golang-jwt/jwt/v5 v5.1.0 // indirect diff --git a/mocks/cloud/go.sum b/mocks/cloud/go.sum new file mode 100644 index 00000000..45e080f9 --- /dev/null +++ b/mocks/cloud/go.sum @@ -0,0 +1,2 @@ +github.com/golang-jwt/jwt/v5 v5.1.0 h1:UGKbA/IPjtS6zLcdB7i5TyACMgSbOTiR8qzXgw8HWQU= +github.com/golang-jwt/jwt/v5 v5.1.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= diff --git a/mocks/cloud/mock_server.go b/mocks/cloud/mock_server.go new file mode 100644 index 00000000..5b0f4229 --- /dev/null +++ b/mocks/cloud/mock_server.go @@ -0,0 +1,90 @@ +package main + +import ( + "encoding/json" + "fmt" + "log" + "net/http" +) + +type Region struct { + ID string `json:"id"` + Name string `json:"name"` + CloudProvider string `json:"cloudProvider"` + URL string `json:"url"` + RegionInfo *RegionInfo `json:"regionInfo,omitempty"` +} + +type RegionInfo struct { + SqlAddress string `json:"sqlAddress"` + HttpAddress string `json:"httpAddress"` + Resolvable bool `json:"resolvable"` + EnabledAt string `json:"enabledAt"` +} + +type CloudRegion struct { + RegionInfo *RegionInfo `json:"regionInfo"` +} + +type CloudProviderResponse struct { + Data []Region `json:"data"` + NextCursor string `json:"nextCursor,omitempty"` +} + +// Mock data +var regions = []Region{ + { + ID: "aws/us-east-1", + Name: "us-east-1", + CloudProvider: "aws", + URL: "http://localhost:3001", + RegionInfo: &RegionInfo{ + SqlAddress: "materialized:6877", + HttpAddress: "materialized:6875", + Resolvable: true, + EnabledAt: "2023-01-01T00:00:00Z", + }, + }, + // Add more mock regions if needed later +} + +func main() { + http.HandleFunc("/api/region", regionHandler) + http.HandleFunc("/api/cloud-regions", cloudRegionsHandler) + + fmt.Println("Mock Cloud API server is running at http://localhost:3001") + log.Fatal(http.ListenAndServe(":3001", nil)) +} + +func regionHandler(w http.ResponseWriter, r *http.Request) { + switch r.Method { + case http.MethodGet: + mockRegion := CloudRegion{ + RegionInfo: &RegionInfo{ + SqlAddress: "materialized:6877", + HttpAddress: "materialized:6875", + Resolvable: true, + EnabledAt: "2023-01-01T00:00:00Z", + }, + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(mockRegion) + case http.MethodPatch: + w.WriteHeader(http.StatusOK) + case http.MethodDelete: + w.WriteHeader(http.StatusAccepted) + default: + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + } +} + +func cloudRegionsHandler(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodGet { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + response := CloudProviderResponse{ + Data: regions, + } + json.NewEncoder(w).Encode(response) +} diff --git a/mocks/frontegg/Dockerfile b/mocks/frontegg/Dockerfile new file mode 100644 index 00000000..1bda83d2 --- /dev/null +++ b/mocks/frontegg/Dockerfile @@ -0,0 +1,30 @@ +# Start from the official Golang base image +FROM golang:1.20 as builder + +# Set the Current Working Directory inside the container +WORKDIR /app + +# Copy go mod and sum files +COPY go.mod go.sum ./ + +# Download all dependencies. Dependencies will be cached if the go.mod and go.sum files are not changed +RUN go mod download + +# Copy the source from the current directory to the Working Directory inside the container +COPY . . + +# Build the Go app +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o mockserver . + +# Start a new stage from scratch +FROM alpine:latest + +RUN apk --no-cache add ca-certificates + +WORKDIR /root/ + +# Copy the Pre-built binary file from the previous stage +COPY --from=builder /app/mockserver . + +# Command to run the executable +CMD ["./mockserver"] diff --git a/mocks/frontegg/go.mod b/mocks/frontegg/go.mod new file mode 100644 index 00000000..e5de1648 --- /dev/null +++ b/mocks/frontegg/go.mod @@ -0,0 +1,5 @@ +module frontegg-mockserver + +go 1.20 + +require github.com/golang-jwt/jwt/v5 v5.1.0 // indirect diff --git a/mocks/frontegg/go.sum b/mocks/frontegg/go.sum new file mode 100644 index 00000000..45e080f9 --- /dev/null +++ b/mocks/frontegg/go.sum @@ -0,0 +1,2 @@ +github.com/golang-jwt/jwt/v5 v5.1.0 h1:UGKbA/IPjtS6zLcdB7i5TyACMgSbOTiR8qzXgw8HWQU= +github.com/golang-jwt/jwt/v5 v5.1.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= diff --git a/mocks/frontegg/mock_server.go b/mocks/frontegg/mock_server.go new file mode 100644 index 00000000..4b38aff3 --- /dev/null +++ b/mocks/frontegg/mock_server.go @@ -0,0 +1,60 @@ +package main + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "log" + "net/http" + "strings" +) + +// base64UrlEncode encodes the input in base64 URL-safe format. +func base64UrlEncode(input []byte) string { + encoded := base64.StdEncoding.EncodeToString(input) + encoded = strings.ReplaceAll(encoded, "+", "-") + encoded = strings.ReplaceAll(encoded, "/", "_") + encoded = strings.TrimRight(encoded, "=") + return encoded +} + +func main() { + http.HandleFunc("/identity/resources/auth/v1/api-token", func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) + return + } + + var payload struct { + ClientId string `json:"clientId"` + Secret string `json:"secret"` + } + if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + defer r.Body.Close() + + // Mock authentication logic + if payload.ClientId == "1b2a3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d" && payload.Secret == "7e8f9a0b-1c2d-3e4f-5a6b-7c8d9e0f1a2b" { + // Create a mock JWT token with base64 URL-safe encoding + header := base64UrlEncode([]byte(`{"alg":"HS256","typ":"JWT"}`)) + payload := base64UrlEncode([]byte(`{"email":"mz_system","exp":1700000000}`)) + signature := base64UrlEncode([]byte(`signature`)) + mockToken := fmt.Sprintf("%s.%s.%s", header, payload, signature) + + // For testing locally set the response email to "mz_system" + response := map[string]string{ + "accessToken": mockToken, + "email": "mz_system", + } + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(response) + } else { + http.Error(w, "Invalid credentials", http.StatusUnauthorized) + } + }) + + fmt.Println("Mock Frontegg server is running at http://localhost:3000") + log.Fatal(http.ListenAndServe(":3000", nil)) +} diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 760debcb..47203933 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -166,6 +166,11 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData, version stri continue } + // Check if regionDetails or RegionInfo is nil before proceeding + if regionDetails == nil || regionDetails.RegionInfo == nil { + continue + } + regionsEnabled[clients.Region(provider.ID)] = regionDetails.RegionInfo != nil && regionDetails.RegionInfo.Resolvable // Get the database connection details for the region @@ -191,7 +196,7 @@ func providerConfigure(ctx context.Context, d *schema.ResourceData, version stri // Check if at least one region has been initialized successfully if len(dbClients) == 0 { - return nil, diag.Errorf("No regions were initialized. Please check your configuration.") + return nil, diag.Errorf("No database regions were initialized. Please check your configuration.") } // Debug log the dbClients map