From 2df1c38dcf2eefbcf12b45116b92bfb119bd5bad Mon Sep 17 00:00:00 2001 From: Justin Brooks Date: Tue, 19 Nov 2024 21:05:06 -0500 Subject: [PATCH] output to github actions --- actions/get-resource/action.yml | 4 -- cmd/ctrlc/root/api/api.go | 17 ++++- internal/cliutil/output.go | 117 ++++++++++++++++++++++++++++++++ 3 files changed, 131 insertions(+), 7 deletions(-) diff --git a/actions/get-resource/action.yml b/actions/get-resource/action.yml index d5d5d98..0bc53f1 100644 --- a/actions/get-resource/action.yml +++ b/actions/get-resource/action.yml @@ -49,10 +49,6 @@ runs: echo "$line" done - # Print GITHUB_OUTPUT contents for debugging - echo "Contents of GITHUB_OUTPUT:" - cat "$GITHUB_OUTPUT" - # Exit if no required outputs specified if [ -z "${{ inputs.required_outputs }}" ]; then echo "No required outputs specified." diff --git a/cmd/ctrlc/root/api/api.go b/cmd/ctrlc/root/api/api.go index 188830d..186a54d 100644 --- a/cmd/ctrlc/root/api/api.go +++ b/cmd/ctrlc/root/api/api.go @@ -2,6 +2,7 @@ package api import ( "fmt" + "os" "github.com/ctrlplanedev/cli/cmd/ctrlc/root/api/create" "github.com/ctrlplanedev/cli/cmd/ctrlc/root/api/delete" @@ -19,11 +20,21 @@ func NewAPICmd() *cobra.Command { PersistentPreRunE: func(cmd *cobra.Command, args []string) error { apiURL := viper.GetString("url") if apiURL == "" { - return fmt.Errorf("API URL is required. Set via --url flag or in config") + fmt.Fprintln(cmd.ErrOrStderr(), "API URL is required. Set via --url flag or in config") + os.Exit(1) } apiKey := viper.GetString("api-key") if apiKey == "" { - return fmt.Errorf("API key is required. Set via --api-key flag or in config") + fmt.Fprintln(cmd.ErrOrStderr(), "API key is required. Set via --api-key flag or in config") + os.Exit(1) + } + + templateFlag, _ := cmd.Flags().GetString("template") + formatFlag, _ := cmd.Flags().GetString("format") + + if templateFlag != "" && formatFlag != "json" { + fmt.Fprintln(cmd.ErrOrStderr(), "--template and --format flags cannot be used together") + os.Exit(1) } return nil }, @@ -32,7 +43,7 @@ func NewAPICmd() *cobra.Command { }, } cmd.PersistentFlags().String("template", "", "Template for output format. Accepts Go template format (e.g. --template='{{.status.phase}}')") - cmd.PersistentFlags().String("format", "json", "Output format. Accepts 'json' or 'yaml'") + cmd.PersistentFlags().String("format", "json", "Output format. Accepts 'json', 'yaml', or 'github-action'") cmd.AddCommand(get.NewGetCmd()) cmd.AddCommand(create.NewCreateCmd()) diff --git a/internal/cliutil/output.go b/internal/cliutil/output.go index 20f7692..074c38b 100644 --- a/internal/cliutil/output.go +++ b/internal/cliutil/output.go @@ -4,6 +4,8 @@ import ( "encoding/json" "fmt" "net/http" + "os" + "strings" "text/template" "github.com/spf13/cobra" @@ -45,6 +47,8 @@ func HandleOutput(cmd *cobra.Command, resp *http.Response) error { if err != nil { return fmt.Errorf("failed to marshal to YAML: %w", err) } + case "github-action": + return handleGitHubActionOutput(result) default: output, err = json.MarshalIndent(result, "", " ") if err != nil { @@ -55,3 +59,116 @@ func HandleOutput(cmd *cobra.Command, resp *http.Response) error { fmt.Fprintln(cmd.OutOrStdout(), string(output)) return nil } + +func handleGitHubActionOutput(result map[string]interface{}) error { + writer, err := NewGitHubOutputWriter() + if err != nil { + return fmt.Errorf("failed to create GitHubOutputWriter: %w", err) + } + defer writer.Close() + + output, err := json.Marshal(result) + if err != nil { + return fmt.Errorf("failed to marshal to JSON: %w", err) + } + + writer.Write("json", string(output)) + var data map[string]interface{} + if err := json.Unmarshal(output, &data); err != nil { + return fmt.Errorf("failed to unmarshal JSON: %w", err) + } + + var flatten func(prefix string, v interface{}) error + flatten = func(prefix string, v interface{}) error { + switch val := v.(type) { + case map[string]interface{}: + for k, v := range val { + newPrefix := strings.ReplaceAll(k, "/", "_") + if prefix != "" { + newPrefix = prefix + "_" + newPrefix + } + if err := flatten(newPrefix, v); err != nil { + return err + } + } + case []interface{}: + for i, v := range val { + newPrefix := fmt.Sprintf("%s_%d", prefix, i) + if err := flatten(newPrefix, v); err != nil { + return err + } + } + default: + if val == nil { + return nil + } + + writer.Write(prefix, fmt.Sprintf("%v", val)) + } + return nil + } + + if err := flatten("", data); err != nil { + return fmt.Errorf("failed to flatten output: %w", err) + } + + return nil +} + +// GitHubOutputWriter is a helper for writing to the GITHUB_OUTPUT file. +type GitHubOutputWriter struct { + file *os.File +} + +// NewGitHubOutputWriter creates and initializes a new GitHubOutputWriter. It +// opens the GITHUB_OUTPUT file for appending. +func NewGitHubOutputWriter() (*GitHubOutputWriter, error) { + // Get the GITHUB_OUTPUT environment variable + githubOutput := os.Getenv("GITHUB_OUTPUT") + if githubOutput == "" { + return nil, fmt.Errorf("GITHUB_OUTPUT environment variable is not set") + } + + // Open the file in append mode + file, err := os.OpenFile(githubOutput, os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return nil, fmt.Errorf("error opening GITHUB_OUTPUT file: %w", err) + } + + return &GitHubOutputWriter{file: file}, nil +} + +// Write writes a key-value pair to the GITHUB_OUTPUT file. +func (w *GitHubOutputWriter) Write(key, value string) error { + if w.file == nil { + return fmt.Errorf("GitHubOutputWriter is not initialized") + } + + // Format and write the output + output := fmt.Sprintf("%s=%s\n", key, value) + if _, err := w.file.WriteString(output); err != nil { + return fmt.Errorf("error writing to GITHUB_OUTPUT file: %w", err) + } + + return nil +} + +// Close closes the GITHUB_OUTPUT file. +func (w *GitHubOutputWriter) Close() error { + if w.file == nil { + return nil + } + err := w.file.Close() + w.file = nil + return err +} + +// GetEnv fetches the value of an environment variable or returns a default +// value. +func GetEnv(key string, defaultValue string) string { + value := os.Getenv(key) + if value == "" { + return defaultValue + } + return value +}