Skip to content

Commit ddb5e1c

Browse files
committed
added /scenarios/globals
fix globals
1 parent 135a3a6 commit ddb5e1c

File tree

5 files changed

+283
-6
lines changed

5 files changed

+283
-6
lines changed

PROGRESS.md

Lines changed: 110 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
## Current Status
44

55
**Last updated:** 2026-01-13
6-
**Current phase:** REST API Enhancement - POST /scenarios/detail COMPLETED
6+
**Current phase:** REST API Enhancement - POST /scenarios/globals COMPLETED
77

88
### Completed
99
- Project scaffolding with Kubebuilder
@@ -70,6 +70,16 @@
7070
- ✅ Returns detailed scenario information (title, description, input fields)
7171
- ✅ Returns 404 if scenario not found
7272
- ✅ Handler registered at POST /scenarios/detail/{scenario_name}
73+
- **POST /scenarios/globals/{scenario_name} endpoint completed:**
74+
- ✅ Extracts scenario_name from URL path
75+
- ✅ Same registry configuration pattern as /scenarios and /scenarios/detail
76+
- ✅ Body is optional (registry config only)
77+
- ✅ Defaults to quay.io when no body provided
78+
- ✅ Calls GetGlobalEnvironment(registry, scenario_name)
79+
- ✅ Returns global environment details for single scenario
80+
- ✅ Returns 404 if global environment not found
81+
- ✅ Type fields converted to strings (not enum integers)
82+
- ✅ Handler registered at POST /scenarios/globals/{scenario_name}
7383

7484
### In Progress
7585
- None
@@ -732,6 +742,101 @@ Same as POST /scenarios - registry configuration for private registry
732742
- Dependencies and mutual exclusions
733743
- Secret field marking
734744

745+
### Refactoring: Type Field as String ✅
746+
**Status:** COMPLETED
747+
748+
**Issue:** The `Type` field in input fields was being serialized as integer (enum value) instead of string.
749+
750+
**Solution:**
751+
1. Created `InputFieldResponse` and `ScenarioDetailResponse` wrapper types
752+
2. Convert `typing.Type` enum to string using `Type.String()` method
753+
3. Map all fields from krknctl models to response models
754+
755+
**Before (incorrect):**
756+
```json
757+
{
758+
"type": 0 // Integer enum value
759+
}
760+
```
761+
762+
**After (correct):**
763+
```json
764+
{
765+
"type": "string" // Human-readable string
766+
}
767+
```
768+
769+
**Possible type values:**
770+
- `"string"`, `"number"`, `"boolean"`, `"enum"`, `"file"`, `"file_base64"`
771+
772+
### Phase 11: POST /scenarios/globals/{scenario_name} Endpoint ✅
773+
**Status:** COMPLETED
774+
775+
1. ✅ Handler Implementation (internal/api/handlers.go)
776+
- Implemented `PostScenarioGlobals(w, r)` handler
777+
- Extracts scenario_name from URL path
778+
- Request body is optional (same as /scenarios/detail)
779+
- Same registry configuration pattern as other scenario endpoints
780+
- Mode selection: provider.Quay (default) vs provider.Private
781+
- Factory pattern: `factory.NewProviderFactory(&cfg).NewInstance(mode)`
782+
- Calls `GetGlobalEnvironment(registry, scenarioName)` for single scenario
783+
- Returns 404 if global environment not found
784+
- Converts Type fields to strings using `field.Type.String()`
785+
- Returns `ScenarioDetailResponse` directly (not a map)
786+
- Comprehensive error handling (400, 404, 500)
787+
788+
2. ✅ Route Registration (internal/api/server.go)
789+
- Registered POST /scenarios/globals/{scenario_name} route
790+
- Handler accessible at http://operator:8080/scenarios/globals/{scenario_name}
791+
792+
3. ✅ Build Verification
793+
- Successful compilation with no errors
794+
- All dependencies resolved
795+
- Binary built and ready for testing
796+
797+
**Implementation Highlights:**
798+
- **Path parameter**: scenario_name in URL path (same as /scenarios/detail)
799+
- **Body optional**: Only for private registry configuration
800+
- **Default mode**: Uses quay.io when no body provided
801+
- **Response format**: Single ScenarioDetailResponse (not a map)
802+
- **Consistency**: Identical pattern to /scenarios/detail/{scenario_name}
803+
- **Error handling**: 404 when global environment not found
804+
805+
**Usage Examples:**
806+
807+
Default (quay.io):
808+
```bash
809+
curl -X POST http://localhost:8080/scenarios/globals/pod-scenarios | jq
810+
```
811+
812+
With private registry:
813+
```bash
814+
curl -X POST http://localhost:8080/scenarios/globals/pod-scenarios \
815+
-H "Content-Type: application/json" \
816+
-d '{
817+
"registryUrl": "registry.example.com",
818+
"scenarioRepository": "org/krkn-scenarios"
819+
}' | jq
820+
```
821+
822+
**Response Example:**
823+
```json
824+
{
825+
"name": "krkn",
826+
"title": "Global Environment",
827+
"description": "Global environment variables for krkn",
828+
"fields": [
829+
{
830+
"name": "kubeconfig",
831+
"variable": "KUBECONFIG",
832+
"type": "file",
833+
"required": true,
834+
"mount_path": "/root/.kube/config"
835+
}
836+
]
837+
}
838+
```
839+
735840
## Technical Notes
736841

737842
### KrknTargetRequest Structure
@@ -825,9 +930,10 @@ type KrknTargetRequestStatus struct {
825930
8. ✅ Kubernetes Service for REST API access
826931
9. ✅ Implement POST /scenarios endpoint with krknctl integration
827932
10. ✅ Implement POST /scenarios/detail/{scenario_name} endpoint
828-
11. Create API documentation (OpenAPI/Swagger spec)
829-
12. Add additional endpoints as requirements evolve
830-
13. Implement controller logic for KrknTargetRequest
933+
11. ✅ Implement POST /scenarios/globals endpoint
934+
12. Create API documentation (OpenAPI/Swagger spec)
935+
13. Add additional endpoints as requirements evolve
936+
14. Implement controller logic for KrknTargetRequest
831937

832938
## Future Work (Not in Current Scope)
833939

REQUIREMENTS.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,20 @@ container in the same pod.
6565
- - if no payload is passed it must default on quay.io
6666
- the purpose is to return the detail of the scenario in json format using the `GetScenarioDetail` method or 404 if not found
6767

68-
#### refactoring ✅ COMPLETED
69-
- type should be returned with the string value of the enum and not with the numeric value
68+
### /scenarios/globals/{scenario_name} ✅ COMPLETED
69+
- this method must be built using the already available golang package made for krknctl to retrieve
70+
the available krkn scenarios either from quay.io or from a private registry
71+
- the package is on the following repo https://github.com/krkn-chaos/krknctl/tree/main/pkg/provider
72+
- the method must instantiate the factory of the scenario providers based on the user parameters
73+
- - if the payload contains private registry infos `RegistryV2` provider must be instantiated
74+
- - if no payload is passed it must default on quay.io
75+
- the purpose is to return the global fields of the scenario using the `GetGlobalEnvironment` method of the provider
76+
- the method takes scenario_name as path parameter (same pattern as /scenarios/detail/{scenario_name})
77+
- the method returns 404 if global environment not found
78+
79+
80+
#### refactoring
81+
- type should be returned with the string value of the enum and not with the numeric value ✅ COMPLETED
7082

7183
# Grpc python service requirement
7284

internal/api/handlers.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,3 +620,148 @@ func (h *Handler) PostScenarioDetail(w http.ResponseWriter, r *http.Request) {
620620

621621
writeJSON(w, http.StatusOK, response)
622622
}
623+
624+
// PostScenarioGlobals handles POST /scenarios/globals/{scenario_name} endpoint
625+
// It returns global environment fields for a specific scenario
626+
func (h *Handler) PostScenarioGlobals(w http.ResponseWriter, r *http.Request) {
627+
// Extract scenario_name from path: /scenarios/globals/{scenario_name}
628+
path := r.URL.Path
629+
prefix := "/scenarios/globals/"
630+
631+
if len(path) <= len(prefix) {
632+
writeJSONError(w, http.StatusBadRequest, ErrorResponse{
633+
Error: "bad_request",
634+
Message: "scenario_name parameter is required in path",
635+
})
636+
return
637+
}
638+
639+
scenarioName := path[len(prefix):]
640+
if scenarioName == "" {
641+
writeJSONError(w, http.StatusBadRequest, ErrorResponse{
642+
Error: "bad_request",
643+
Message: "scenario_name parameter cannot be empty",
644+
})
645+
return
646+
}
647+
648+
// Parse optional request body (same as /scenarios for registry config)
649+
var req ScenariosRequest
650+
var registry *models.RegistryV2
651+
var mode provider.Mode
652+
653+
// Check if body is provided
654+
if r.ContentLength > 0 {
655+
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
656+
writeJSONError(w, http.StatusBadRequest, ErrorResponse{
657+
Error: "bad_request",
658+
Message: "Invalid request body: " + err.Error(),
659+
})
660+
return
661+
}
662+
663+
// If registry info is provided, validate and use private registry mode
664+
if req.RegistryURL != "" && req.ScenarioRepository != "" {
665+
registry = &models.RegistryV2{
666+
Username: req.Username,
667+
Password: req.Password,
668+
Token: req.Token,
669+
RegistryURL: req.RegistryURL,
670+
ScenarioRepository: req.ScenarioRepository,
671+
SkipTLS: req.SkipTLS,
672+
Insecure: req.Insecure,
673+
}
674+
mode = provider.Private
675+
} else if req.RegistryURL != "" || req.ScenarioRepository != "" {
676+
// Partial registry info provided - error
677+
writeJSONError(w, http.StatusBadRequest, ErrorResponse{
678+
Error: "bad_request",
679+
Message: "Both registryUrl and scenarioRepository are required for private registry",
680+
})
681+
return
682+
} else {
683+
// Body provided but no registry info - use quay.io
684+
mode = provider.Quay
685+
}
686+
} else {
687+
// No body provided - default to quay.io
688+
mode = provider.Quay
689+
}
690+
691+
// Load krknctl config
692+
cfg, err := config.LoadConfig()
693+
if err != nil {
694+
writeJSONError(w, http.StatusInternalServerError, ErrorResponse{
695+
Error: "internal_error",
696+
Message: "Failed to load krknctl config: " + err.Error(),
697+
})
698+
return
699+
}
700+
701+
// Create provider factory
702+
providerFactory := factory.NewProviderFactory(&cfg)
703+
704+
// Get provider instance based on mode
705+
scenarioProvider := providerFactory.NewInstance(mode)
706+
if scenarioProvider == nil {
707+
writeJSONError(w, http.StatusInternalServerError, ErrorResponse{
708+
Error: "internal_error",
709+
Message: "Failed to create scenario provider",
710+
})
711+
return
712+
}
713+
714+
// Get global environment
715+
globalDetail, err := scenarioProvider.GetGlobalEnvironment(registry, scenarioName)
716+
if err != nil {
717+
writeJSONError(w, http.StatusInternalServerError, ErrorResponse{
718+
Error: "internal_error",
719+
Message: "Failed to get global environment: " + err.Error(),
720+
})
721+
return
722+
}
723+
724+
// Check if global environment was found
725+
if globalDetail == nil {
726+
writeJSONError(w, http.StatusNotFound, ErrorResponse{
727+
Error: "not_found",
728+
Message: "Global environment for scenario '" + scenarioName + "' not found",
729+
})
730+
return
731+
}
732+
733+
// Convert krknctl models.ScenarioDetail to ScenarioDetailResponse
734+
// This ensures Type fields are serialized as strings instead of int64
735+
var fields []InputFieldResponse
736+
for _, field := range globalDetail.Fields {
737+
fields = append(fields, InputFieldResponse{
738+
Name: field.Name,
739+
ShortDescription: field.ShortDescription,
740+
Description: field.Description,
741+
Variable: field.Variable,
742+
Type: field.Type.String(), // Convert enum to string
743+
Default: field.Default,
744+
Validator: field.Validator,
745+
ValidationMessage: field.ValidationMessage,
746+
Separator: field.Separator,
747+
AllowedValues: field.AllowedValues,
748+
Required: field.Required,
749+
MountPath: field.MountPath,
750+
Requires: field.Requires,
751+
MutuallyExcludes: field.MutuallyExcludes,
752+
Secret: field.Secret,
753+
})
754+
}
755+
756+
response := ScenarioDetailResponse{
757+
Name: globalDetail.Name,
758+
Digest: globalDetail.Digest,
759+
Size: globalDetail.Size,
760+
LastModified: globalDetail.LastModified,
761+
Title: globalDetail.Title,
762+
Description: globalDetail.Description,
763+
Fields: fields,
764+
}
765+
766+
writeJSON(w, http.StatusOK, response)
767+
}

internal/api/server.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ func NewServer(port int, client client.Client, namespace string, grpcServerAddr
4646
mux.HandleFunc("/targets/", handler.GetTargetByUUID) // GET /targets/{uuid}
4747
mux.HandleFunc("/scenarios", handler.PostScenarios) // POST /scenarios
4848
mux.HandleFunc("/scenarios/detail/", handler.PostScenarioDetail) // POST /scenarios/detail/{scenario_name}
49+
mux.HandleFunc("/scenarios/globals/", handler.PostScenarioGlobals) // POST /scenarios/globals/{scenario_name}
4950

5051
// Wrap mux with logging middleware
5152
server := &http.Server{

internal/api/types.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,3 +109,16 @@ type ScenarioDetailResponse struct {
109109
Description string `json:"description"`
110110
Fields []InputFieldResponse `json:"fields"`
111111
}
112+
113+
// GlobalsRequest represents the request body for POST /scenarios/globals
114+
type GlobalsRequest struct {
115+
ScenariosRequest
116+
// ScenarioNames is the list of scenario names to get global environments for
117+
ScenarioNames []string `json:"scenarioNames"`
118+
}
119+
120+
// GlobalsResponse represents the response for POST /scenarios/globals endpoint
121+
type GlobalsResponse struct {
122+
// Globals is a map of scenario name to global environment details
123+
Globals map[string]ScenarioDetailResponse `json:"globals"`
124+
}

0 commit comments

Comments
 (0)