From 657df8afd0a2e809e465378281a4a192a0b90de1 Mon Sep 17 00:00:00 2001 From: Tedi Mitiku Date: Fri, 18 Oct 2024 22:37:30 +0900 Subject: [PATCH] feat: allow overriding env vars in flows (#270) --- kardinal-cli/cmd/root.go | 106 +++++++++++++++--- .../api/golang/server/server.gen.go | 33 ++++++ .../api/golang/types/types.gen.go | 6 +- .../api/typescript/client/types.d.ts | 6 + libs/cli-kontrol-api/specs/api.yaml | 6 + 5 files changed, 137 insertions(+), 20 deletions(-) diff --git a/kardinal-cli/cmd/root.go b/kardinal-cli/cmd/root.go index ea5ac22..86f79eb 100644 --- a/kardinal-cli/cmd/root.go +++ b/kardinal-cli/cmd/root.go @@ -58,8 +58,8 @@ const ( kloudKontrolAPIHost = kloudKontrolHost + "/api" tcpProtocol = "tcp" - httpSchme = "http" - httpsScheme = httpSchme + "s" + httpScheme = "http" + httpsScheme = httpScheme + "s" deleteAllDevFlowsFlagName = "all" @@ -85,6 +85,7 @@ var ( templateDescription string templateArgsFile string flowID string + flowSpecFilepath string ) var rootCmd = &cobra.Command{ @@ -212,12 +213,49 @@ var listCmd = &cobra.Command{ }, } +// FlowSpec represents a map of service names to their corresponding PodSpec +type FlowSpec map[string]corev1.PodSpec + +// DeserializeConfig deserializes a YAML file into a ServiceConfig +func DeserializeFlowSpec(filePath string) (*FlowSpec, error) { + data, err := os.ReadFile(filePath) + if err != nil { + return nil, fmt.Errorf("error reading YAML file: %w", err) + } + + var config FlowSpec + err = json.Unmarshal(data, &config) + if err != nil { + return nil, fmt.Errorf("error unmarshaling YAML: %w", err) + } + + return &config, nil +} + var createCmd = &cobra.Command{ Use: "create [service name] [image name]", Short: "Create a new service in development mode", - Args: cobra.ExactArgs(2), + Args: func(cmd *cobra.Command, args []string) error { + if len(args) > 0 && flowSpecFilepath != "" { + return errors.New("Please provide either args or flow config file, but not both.") + } + if len(args) > 0 && len(args) != 2 { + return fmt.Errorf("accepts %d arg(s), received %d", 2, len(args)) + } + return nil + }, Run: func(cmd *cobra.Command, args []string) { - serviceName, imageName := args[0], args[1] + var flowSpec *FlowSpec + var serviceName, imageName string + if flowSpecFilepath != "" { + fs, err := DeserializeFlowSpec(flowSpecFilepath) + if err != nil { + log.Fatalf("An error occurred deserializing flow spec: %v", err) + } + flowSpec = fs + } else { + serviceName, imageName = args[0], args[1] + } pairsMap := parsePairs(serviceImagePairs) pairsMap[serviceName] = imageName @@ -241,7 +279,7 @@ var createCmd = &cobra.Command{ log.Fatalf("Error parsing template arguments: %v", err) } - createDevFlow(tenantUuid.String(), pairsMap, templateName, templateArgs) + createDevFlow(tenantUuid.String(), pairsMap, templateName, templateArgs, flowSpec) }, } @@ -425,7 +463,7 @@ func isPortOpenAndHTTP(localPortStr string) error { } // Check if there is an HTTP server running on the port - httpServerAddr := fmt.Sprintf("%s://%s", httpSchme, localServiceAddress) + httpServerAddr := fmt.Sprintf("%s://%s", httpScheme, localServiceAddress) resp, err := client.Get(httpServerAddr) if err != nil { return stacktrace.Propagate(err, "failing to call an HTTP server on '%s'", httpServerAddr) @@ -659,6 +697,7 @@ func init() { createCmd.Flags().StringSliceVarP(&serviceImagePairs, "service-image", "s", []string{}, "Extra service and respective image to include in the same flow (can be used multiple times)") createCmd.Flags().StringVarP(&templateName, "template", "t", "", "Template name to use for the flow creation") createCmd.Flags().StringVarP(&templateArgsFile, "template-args", "a", "", "JSON with the template arguments or path to YAML file containing template arguments") + createCmd.Flags().StringVarP(&flowSpecFilepath, "flow-spec-config", "", "", "Path to the flow spec configuration file") createCmd.Flags().StringVarP(&flowID, "id", "i", "", "Set the flow id") deployCmd.PersistentFlags().StringVarP(&kubernetesManifestFile, "k8s-manifest", "k", "", "Path to the K8S manifest file") @@ -861,18 +900,49 @@ func getTenantUuidFlows(tenantUuid api_types.Uuid) ([]api_types.Flow, error) { return nil, stacktrace.NewError("Failed to get tenant UUID '%s' dev flows, '%d' status code received", tenantUuid, resp.StatusCode()) } -func createDevFlow(tenantUuid api_types.Uuid, pairsMap map[string]string, templateName string, templateArgs map[string]interface{}) { +func createDevFlow(tenantUuid api_types.Uuid, pairsMap map[string]string, templateName string, templateArgs map[string]interface{}, flowSpec *FlowSpec) { ctx := context.Background() devSpec := api_types.FlowSpec{} - for serviceName, imageLocator := range pairsMap { - devSpec = append(devSpec, struct { - ImageLocator string `json:"image-locator"` - ServiceName string `json:"service-name"` - }{ - ImageLocator: imageLocator, - ServiceName: serviceName, - }) + + if flowSpec != nil { + for serviceName, podSpec := range *flowSpec { + envVarOverrides := map[string]string{} + secretEnvVarOverrides := map[string]string{} + for _, envVar := range podSpec.Containers[0].Env { + if envVar.ValueFrom != nil { + secretEnvVarOverrides[envVar.Name] = envVar.ValueFrom.SecretKeyRef.Name + } else { + envVarOverrides[envVar.Name] = envVar.Value + } + } + + devSpec = append(devSpec, struct { + EnvVarOverrides *map[string]string `json:"env-var-overrides,omitempty"` + ImageLocator string `json:"image-locator"` + SecretEnvVarOverrides *map[string]string `json:"secret-env-var-overrides,omitempty"` + ServiceName string `json:"service-name"` + }{ + EnvVarOverrides: &envVarOverrides, + ImageLocator: podSpec.Containers[0].Image, + ServiceName: serviceName, + SecretEnvVarOverrides: &secretEnvVarOverrides, + }) + } + } else { + for serviceName, imageLocator := range pairsMap { + devSpec = append(devSpec, struct { + EnvVarOverrides *map[string]string `json:"env-var-overrides,omitempty"` + ImageLocator string `json:"image-locator"` + SecretEnvVarOverrides *map[string]string `json:"secret-env-var-overrides,omitempty"` + ServiceName string `json:"service-name"` + }{ + EnvVarOverrides: nil, + ImageLocator: imageLocator, + ServiceName: serviceName, + SecretEnvVarOverrides: nil, + }) + } } client := getKontrolServiceClient() @@ -1131,7 +1201,7 @@ func getKontrolBaseURLForUI() (string, error) { ) if devMode { - scheme = httpSchme + scheme = httpScheme host = localFrontendHost } else { scheme = httpsScheme @@ -1150,7 +1220,7 @@ func getKontrolBaseURLForCLI() (string, error) { ) if devMode { - scheme = httpSchme + scheme = httpScheme host = localKontrolAPIHost } else { scheme = httpsScheme @@ -1175,7 +1245,7 @@ func getKontrolBaseURLForManager() (string, error) { switch kontrolLocation { case kontrol.KontrolLocationLocal: - scheme = httpSchme + scheme = httpScheme host = localMinikubeKontrolAPIHost case kontrol.KontrolLocationKloud: scheme = httpsScheme diff --git a/libs/cli-kontrol-api/api/golang/server/server.gen.go b/libs/cli-kontrol-api/api/golang/server/server.gen.go index 99fbcd8..5dbbd68 100644 --- a/libs/cli-kontrol-api/api/golang/server/server.gen.go +++ b/libs/cli-kontrol-api/api/golang/server/server.gen.go @@ -1058,6 +1058,38 @@ func (sh *strictHandler) GetTenantUuidTopology(ctx echo.Context, uuid Uuid) erro // Base64 encoded, gzipped, json marshaled Swagger object var swaggerSpec = []string{ +<<<<<<< HEAD + "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", +======= "H4sIAAAAAAAC/9waW2/bvPWvENweZTvZOmDwW7+kaYOmRdA4A4YiKBjpWGYjkSpJOTEC//cPvOliUbbU", "NG3TN1k89zuP9YhjnhecAVMSzx9xQQTJQYEwv5YZv5/QRD8mIGNBC0U5w3N8lvF7RBNgii4pCBxhql8X", "RK1whBnJAc8r7AgL+FZSAQmeK1FChGW8gpxosmpTaFCpBGUp3m4jrCAvMqJgYqnsctZvEV8itQLkQcPs", @@ -1089,6 +1121,7 @@ var swaggerSpec = []string{ "FgkDSsYFlcpc9rxiVb7oWzVDzoovpoBUeoycSCvH/qqxdIhD+y851fTH4L55af95I10ddF3p/NnL7Ut1", "WD22vtwaNeVVMeYfPto/+J5n4Gt/YTZs7Ksc9WLHPtX4JGdAOffQv6aa78un3U+MQmlVTzJLLnLD5bf1", "1Hb7dwAAAP//1z52lM4pAAA=", +>>>>>>> main } // GetSwagger returns the content of the embedded swagger specification file diff --git a/libs/cli-kontrol-api/api/golang/types/types.gen.go b/libs/cli-kontrol-api/api/golang/types/types.gen.go index 4806aff..1b8ef41 100644 --- a/libs/cli-kontrol-api/api/golang/types/types.gen.go +++ b/libs/cli-kontrol-api/api/golang/types/types.gen.go @@ -49,8 +49,10 @@ type Flow struct { // FlowSpec defines model for FlowSpec. type FlowSpec = []struct { - ImageLocator string `json:"image-locator"` - ServiceName string `json:"service-name"` + EnvVarOverrides *map[string]string `json:"env-var-overrides,omitempty"` + ImageLocator string `json:"image-locator"` + SecretEnvVarOverrides *map[string]string `json:"secret-env-var-overrides,omitempty"` + ServiceName string `json:"service-name"` } // GatewayConfig defines model for GatewayConfig. diff --git a/libs/cli-kontrol-api/api/typescript/client/types.d.ts b/libs/cli-kontrol-api/api/typescript/client/types.d.ts index 9fd0c18..7eb1e66 100644 --- a/libs/cli-kontrol-api/api/typescript/client/types.d.ts +++ b/libs/cli-kontrol-api/api/typescript/client/types.d.ts @@ -256,6 +256,12 @@ export interface components { "image-locator": string; /** @example backend-service-a */ "service-name": string; + "env-var-overrides"?: { + [key: string]: string; + }; + "secret-env-var-overrides"?: { + [key: string]: string; + }; }[]; TemplateSpec: { /** @description name of the template */ diff --git a/libs/cli-kontrol-api/specs/api.yaml b/libs/cli-kontrol-api/specs/api.yaml index 422e127..1d467fb 100644 --- a/libs/cli-kontrol-api/specs/api.yaml +++ b/libs/cli-kontrol-api/specs/api.yaml @@ -370,6 +370,12 @@ components: service-name: type: string example: backend-service-a + env-var-overrides: + type: object + additionalProperties: { type: string } + secret-env-var-overrides: + type: object + additionalProperties: { type: string } required: - image-locator - service-name