diff --git a/AGENTS.md b/AGENTS.md index 4b06b2d..06841f4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -362,7 +362,7 @@ type FDL struct { - `ListServicesWithContext(ctx, c) ([]*types.Service, error)` - `RemoveService(c, name) error` — DELETE `/system/services/{name}` - `ApplyService(svc, c, method) error` — POST or PUT `/system/services` -- `RunService(c, name, token, endpoint, input) (io.ReadCloser, error)` — synchronous POST `/run/{name}` +- `RunService(c, name, token, endpoint, input,header) (io.ReadCloser, error)` — synchronous POST `/run/{name}` - `JobService(c, name, token, endpoint, input) (io.ReadCloser, error)` — async POST `/job/{name}` - `ListLogs(c, name, page) (types.JobsResponse, error)` — GET `/system/logs/{name}` - `GetLogs(c, svcName, jobName, timestamps) (string, error)` diff --git a/README.md b/README.md index f999c15..89eb83b 100644 --- a/README.md +++ b/README.md @@ -360,6 +360,7 @@ Flags: --decode-output decode the last base64-encoded line in the response and ignore logs -e, --endpoint string endpoint of a non registered cluster -f, --file-input string input file for the request + -H, --header string header on service invocation (e.g. "Content-Type:application/json") -h, --help help for run -o, --output string file path to store the output -i, --text-input string text input string for the request @@ -370,6 +371,11 @@ Global Flags: --config string set the location of the config file (YAML or JSON) ``` +Examples: +``` + oscar-cli service run my-service -i '{"key":"value"}' -H "Content-Type:application/json" +``` + ##### logs list List the logs from a service. diff --git a/cmd/hub_validate.go b/cmd/hub_validate.go index 1fb80c8..5748820 100644 --- a/cmd/hub_validate.go +++ b/cmd/hub_validate.go @@ -35,6 +35,7 @@ type hubValidateOptions struct { name string localPath string printAcceptanceCommands bool + header string } func (o *hubValidateOptions) applyToClient() []hub.Option { @@ -98,7 +99,7 @@ func hubValidateFunc(cmd *cobra.Command, args []string, opts *hubValidateOptions fmt.Fprintf(out, "Acceptance tests for %s\n", args[0]) - results, err := client.ValidateService(cmd.Context(), args[0], conf.Oscar[clusterID], opts.name, opts.localPath) + results, err := client.ValidateService(cmd.Context(), args[0], conf.Oscar[clusterID], opts.name, opts.localPath, opts.header) if err != nil { return err } @@ -154,6 +155,7 @@ func makeHubValidateCmd() *cobra.Command { cmd.Flags().StringVar(&opts.apiBase, "api-base", "", "override the GitHub API base URL") cmd.Flags().StringVarP(&opts.name, "name", "n", "", "override the OSCAR service name during validation") cmd.Flags().StringVar(&opts.localPath, "local-path", "", "use a local directory containing the RO-Crate metadata instead of fetching it from GitHub") + cmd.Flags().StringVarP(&opts.header, "header", "H", "", "header on service invocation (e.g. \"Content-Type:application/json\")") cmd.Flags().BoolVar(&opts.printAcceptanceCommands, "print-acceptance-commands", false, "print the acceptance test shell commands instead of executing them") if flag := cmd.Flags().Lookup("api-base"); flag != nil { flag.Hidden = true diff --git a/cmd/service_run.go b/cmd/service_run.go index cf409ca..0ad2505 100644 --- a/cmd/service_run.go +++ b/cmd/service_run.go @@ -60,6 +60,7 @@ func serviceRunFunc(cmd *cobra.Command, args []string) error { textInput, _ := cmd.Flags().GetString("text-input") outputFile, _ := cmd.Flags().GetString("output") decodeOutput, _ := cmd.Flags().GetBool("decode-output") + header, _ := cmd.Flags().GetString("header") if inputFile == "" && textInput == "" { return errors.New("you must specify \"--file-input\" or \"--text-input\" flag") } @@ -94,7 +95,7 @@ func serviceRunFunc(cmd *cobra.Command, args []string) error { writer.Close() }() // Make the request - resBody, err := service.RunService(conf.Oscar[cluster], args[0], token, endpoint, reader) + resBody, err := service.RunService(conf.Oscar[cluster], args[0], token, endpoint, reader, header) if err != nil { return err } @@ -237,6 +238,7 @@ func makeServiceRunCmd() *cobra.Command { serviceRunCmd.Flags().StringP("file-input", "f", "", "input file for the request") serviceRunCmd.Flags().StringP("text-input", "i", "", "text input string for the request") serviceRunCmd.Flags().StringP("output", "o", "", "file path to store the output") + serviceRunCmd.Flags().StringP("header", "H", "", "header on service invocation") serviceRunCmd.Flags().Bool("decode-output", false, "decode the last base64-encoded line in the response and ignore logs") return serviceRunCmd diff --git a/pkg/hub/validate.go b/pkg/hub/validate.go index e9aca1e..093efbe 100644 --- a/pkg/hub/validate.go +++ b/pkg/hub/validate.go @@ -89,7 +89,7 @@ type AcceptanceCommandSet struct { } // ValidateService downloads the RO-Crate metadata for the provided slug, runs its acceptance tests against the cluster and returns the aggregated results. -func (c *Client) ValidateService(ctx context.Context, slug string, clusterCfg *cluster.Cluster, serviceNameOverride string, localRoot string) ([]AcceptanceResult, error) { +func (c *Client) ValidateService(ctx context.Context, slug string, clusterCfg *cluster.Cluster, serviceNameOverride string, localRoot string, header string) ([]AcceptanceResult, error) { if strings.TrimSpace(slug) == "" { return nil, errors.New("service slug cannot be empty") } @@ -110,7 +110,7 @@ func (c *Client) ValidateService(ctx context.Context, slug string, clusterCfg *c testName = test.ID } c.logf("Running acceptance test: %s\n", testName) - res := c.runAcceptanceTest(ctx, repoPath, slug, test, clusterCfg, serviceNameOverride, localCratePath, serviceCache) + res := c.runAcceptanceTest(ctx, repoPath, slug, test, clusterCfg, serviceNameOverride, localCratePath, serviceCache, header) c.logAcceptanceResult(res) results = append(results, res) } @@ -178,7 +178,7 @@ func (c *Client) loadAcceptanceTests(ctx context.Context, slug string, localRoot return repoPath, localCratePath, tests, nil } -func (c *Client) runAcceptanceTest(ctx context.Context, repoPath, slug string, test AcceptanceTest, clusterCfg *cluster.Cluster, serviceNameOverride string, localCratePath string, svcCache map[string]*types.Service) AcceptanceResult { +func (c *Client) runAcceptanceTest(ctx context.Context, repoPath, slug string, test AcceptanceTest, clusterCfg *cluster.Cluster, serviceNameOverride string, localCratePath string, svcCache map[string]*types.Service, header string) AcceptanceResult { result := AcceptanceResult{Test: test} steps := test.Steps @@ -201,7 +201,7 @@ func (c *Client) runAcceptanceTest(ctx context.Context, repoPath, slug string, t var lastOutput string for _, step := range steps { - stepRes := c.executeAcceptanceStep(ctx, repoPath, slug, test, step, supplyCache, clusterCfg, serviceNameOverride, localCratePath, svcCache, tempDir) + stepRes := c.executeAcceptanceStep(ctx, repoPath, slug, test, step, supplyCache, clusterCfg, serviceNameOverride, localCratePath, svcCache, tempDir, header) result.StepResults = append(result.StepResults, stepRes) if stepRes.Output != "" { @@ -444,7 +444,7 @@ func shellQuote(value string) string { return value } -func (c *Client) executeAcceptanceStep(ctx context.Context, repoPath, slug string, test AcceptanceTest, step AcceptanceStep, baseSupply map[string]TestInput, clusterCfg *cluster.Cluster, serviceNameOverride string, localCratePath string, svcCache map[string]*types.Service, tempDir string) AcceptanceStepResult { +func (c *Client) executeAcceptanceStep(ctx context.Context, repoPath, slug string, test AcceptanceTest, step AcceptanceStep, baseSupply map[string]TestInput, clusterCfg *cluster.Cluster, serviceNameOverride string, localCratePath string, svcCache map[string]*types.Service, tempDir string, header string) AcceptanceStepResult { result := AcceptanceStepResult{Step: step} if strings.TrimSpace(step.Command) == "" { @@ -480,7 +480,7 @@ func (c *Client) executeAcceptanceStep(ctx context.Context, repoPath, slug strin return result } - responseBytes, err := invokeServiceWithContent(clusterCfg, serviceName, payload) + responseBytes, err := invokeServiceWithContent(clusterCfg, serviceName, payload, header) if err != nil { result.Err = err return result @@ -1434,7 +1434,7 @@ func isWhitespace(r rune) bool { return r == ' ' || r == '\t' || r == '\n' || r == '\r' } -func invokeServiceWithContent(clusterCfg *cluster.Cluster, serviceName string, payload []byte) ([]byte, error) { +func invokeServiceWithContent(clusterCfg *cluster.Cluster, serviceName string, payload []byte, header string) ([]byte, error) { reader, writer := io.Pipe() go func() { encoder := base64.NewEncoder(base64.StdEncoding, writer) @@ -1447,7 +1447,7 @@ func invokeServiceWithContent(clusterCfg *cluster.Cluster, serviceName string, p } }() - response, err := service.RunService(clusterCfg, serviceName, "", "", reader) + response, err := service.RunService(clusterCfg, serviceName, "", "", reader, header) if err != nil { return nil, err } diff --git a/pkg/service/service.go b/pkg/service/service.go index 7895ae9..3170344 100644 --- a/pkg/service/service.go +++ b/pkg/service/service.go @@ -258,7 +258,7 @@ func ApplyService(svc *types.Service, c *cluster.Cluster, method string) error { } // RunService invokes a service synchronously (a Serverless backend in the cluster is required) -func RunService(c *cluster.Cluster, name string, token string, endpoint string, input io.Reader) (responseBody io.ReadCloser, err error) { +func RunService(c *cluster.Cluster, name string, token string, endpoint string, input io.Reader, header string) (responseBody io.ReadCloser, err error) { client := &http.Client{Timeout: time.Second * runServiceTimeoutSeconds} if token == "" { client, _ = c.GetClientSafe(runServiceTimeoutSeconds) @@ -288,6 +288,13 @@ func RunService(c *cluster.Cluster, name string, token string, endpoint string, if token != "" { req.Header.Set("Authorization", "Bearer "+token) } + if header != "" { + for _, lines := range strings.Split(header, ",") { + chunck := strings.Split(lines, ":") + req.Header.Add(chunck[0], chunck[1]) + } + + } if err != nil { return nil, cluster.ErrMakingRequest } diff --git a/pkg/service/service_test.go b/pkg/service/service_test.go index 48aaa0c..558a456 100644 --- a/pkg/service/service_test.go +++ b/pkg/service/service_test.go @@ -189,7 +189,7 @@ func TestRunServiceUsesServiceToken(t *testing.T) { SSLVerify: true, } - resp, err := RunService(c, serviceName, "", "", bytes.NewBufferString(payload)) + resp, err := RunService(c, serviceName, "", "", bytes.NewBufferString(payload), "") if err != nil { t.Fatalf("RunService returned error: %v", err) } @@ -224,7 +224,7 @@ func TestRunServiceWithProvidedToken(t *testing.T) { })) defer server.Close() - resp, err := RunService(&cluster.Cluster{Endpoint: server.URL, SSLVerify: true}, serviceName, token, server.URL, bytes.NewBufferString(payload)) + resp, err := RunService(&cluster.Cluster{Endpoint: server.URL, SSLVerify: true}, serviceName, token, server.URL, bytes.NewBufferString(payload), "") if err != nil { t.Fatalf("RunService returned error: %v", err) }