Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: error transfer between typescript and golang #5676

Merged
merged 1 commit into from
Jan 28, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
133 changes: 125 additions & 8 deletions cliv2/cmd/cliv2/logheader.go → cliv2/cmd/cliv2/logheaderfooter.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,23 @@ import _ "github.com/snyk/go-application-framework/pkg/networking/fips_enable"
import (
"crypto/sha256"
"encoding/hex"
"errors"
"fmt"
"net/http"
"regexp"
"strconv"
"strings"

"github.com/snyk/go-application-framework/pkg/local_workflows/config_utils"

"github.com/snyk/cli/cliv2/internal/cliv2"
"github.com/snyk/cli/cliv2/internal/utils"
"github.com/snyk/error-catalog-golang-public/snyk_errors"
"github.com/snyk/go-application-framework/pkg/auth"
"github.com/snyk/go-application-framework/pkg/configuration"
"github.com/snyk/go-application-framework/pkg/local_workflows/config_utils"

localworkflows "github.com/snyk/go-application-framework/pkg/local_workflows"
"github.com/snyk/go-application-framework/pkg/networking"
"github.com/snyk/go-application-framework/pkg/networking/fips"

"github.com/snyk/cli/cliv2/internal/cliv2"
)

func logHeaderAuthorizationInfo(
Expand Down Expand Up @@ -82,6 +85,14 @@ func getFipsStatus(config configuration.Configuration) string {
return fipsEnabled
}

func tablePrint(name string, value string) {
title := name
if len(name) > 0 {
title = title + ":"
}
globalLogger.Printf("%-22s %s", title, value)
}

func writeLogHeader(config configuration.Configuration, networkAccess networking.NetworkAccess) {
authorization, _, userAgent := logHeaderAuthorizationInfo(config, networkAccess)

Expand All @@ -101,10 +112,6 @@ func writeLogHeader(config configuration.Configuration, networkAccess networking
previewFeaturesEnabled = "enabled"
}

tablePrint := func(name string, value string) {
globalLogger.Printf("%-22s %s", name+":", value)
}

fipsEnabled := getFipsStatus(config)

tablePrint("Version", cliv2.GetFullVersion()+" "+buildType)
Expand Down Expand Up @@ -136,3 +143,113 @@ func writeLogHeader(config configuration.Configuration, networkAccess networking
tablePrint(" Configuration", "all good")
}
}

func writeLogFooter(exitCode int, errs []error) {
// output error details
if exitCode > 1 && len(errs) > 0 {
ecErrs := formatErrorCatalogErrors(errs)

for i, err := range ecErrs {
tablePrint(fmt.Sprintf("Error (%d)", i), fmt.Sprintf("%s (%s)", err.ErrorCode, err.Title))
tablePrint(" Type", err.Type)
tablePrint(" Classification", err.Classification)

// description
if _, ok := err.Meta["description"]; ok && len(err.Description) == 0 {
tablePrint(" Description", err.Meta["description"].(string))
}
tablePrint(" Description", err.Description)

// details
_, hasDetails := err.Meta["details"]
if hasDetails && len(err.Meta["details"].([]string)) > 0 {
tablePrint(" Details", "")
for i, details := range utils.Dedupe(err.Meta["details"].([]string)) {
tablePrint(fmt.Sprintf(" %d", i), details)
}
}

// links
if len(err.Links) > 0 {
tablePrint(" Links", "")
for i, link := range err.Links {
tablePrint(fmt.Sprintf(" %d", i), link)
}
}

// requests
_, hasRequestDetails := err.Meta["requests"]
if hasRequestDetails && len(err.Meta["requests"].([]string)) > 0 {
tablePrint(" Requests", "")
for i, request := range err.Meta["requests"].([]string) {
tablePrint(fmt.Sprintf(" %d", i), request)
}
}
}
}
tablePrint("Exit Code", strconv.Itoa(exitCode))
}

func formatErrorCatalogErrors(errs []error) []snyk_errors.Error {
var formattedErrs []snyk_errors.Error

for _, err := range errs {
var snykError snyk_errors.Error
if errors.As(err, &snykError) {
snykError = updateMeta(snykError)

// Check if an error with the same ErrorCode already exists in formattedErrs
found := false
for i, fErr := range formattedErrs {
if snykError.ErrorCode == fErr.ErrorCode {
// Merge requests
formattedErrs[i].Meta["requests"] = append(
fErr.Meta["requests"].([]string),
snykError.Meta["requests"].([]string)...,
)
// Merge details
formattedErrs[i].Meta["details"] = append(
fErr.Meta["details"].([]string),
snykError.Meta["details"].([]string)...,
)
found = true
break
}
}

// If no matching error was found, append the current error
if !found {
formattedErrs = append(formattedErrs, snykError)
}
}
}

return formattedErrs
}

func updateMeta(err snyk_errors.Error) snyk_errors.Error {
if err.Meta == nil {
err.Meta = make(map[string]interface{})
}

// add requests meta
if _, ok := err.Meta["requests"]; !ok {
err.Meta["requests"] = []string{}
}
if requestID, hasID := err.Meta["request-id"]; hasID {
if requestPath, hasPath := err.Meta["request-path"]; hasPath {
err.Meta["requests"] = append(
err.Meta["requests"].([]string),
fmt.Sprintf("%s - %s", requestID, requestPath),
)
}
}

// add details meta
if _, ok := err.Meta["details"]; !ok {
err.Meta["details"] = []string{}
}
err.Meta["details"] = append(err.Meta["details"].([]string), err.Detail)

return err
}
19 changes: 9 additions & 10 deletions cliv2/cmd/cliv2/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,8 @@ const (
)

func main() {
errorCode := MainWithErrorCode()
globalLogger.Printf("Exiting with %d", errorCode)
errorCode, errs := MainWithErrorCode()
writeLogFooter(errorCode, errs)
os.Exit(errorCode)
}

Expand Down Expand Up @@ -478,9 +478,12 @@ func displayError(err error, userInterface ui.UserInterface, config configuratio
}
}

func MainWithErrorCode() int {
func MainWithErrorCode() (int, []error) {
initDebugBuild()

errorList := []error{}
errorListMutex := sync.Mutex{}

startTime := time.Now()
var err error
rInfo := runtimeinfo.New(runtimeinfo.WithName("snyk-cli"), runtimeinfo.WithVersion(cliv2.GetFullVersion()))
Expand Down Expand Up @@ -528,7 +531,7 @@ func MainWithErrorCode() int {
err = globalEngine.Init()
if err != nil {
globalLogger.Print("Failed to init Workflow Engine!", err)
return constants.SNYK_EXIT_CODE_ERROR
return constants.SNYK_EXIT_CODE_ERROR, errorList
}

// add output flags as persistent flags
Expand All @@ -539,9 +542,6 @@ func MainWithErrorCode() int {
// add workflows as commands
createCommandsForWorkflows(rootCommand, globalEngine)

errorList := []error{}
errorListMutex := sync.Mutex{}

// init NetworkAccess
ua := networking.UserAgent(networking.UaWithConfig(globalConfiguration), networking.UaWithRuntimeInfo(rInfo), networking.UaWithOS(internalOS))
networkAccess := globalEngine.GetNetworkAccess()
Expand Down Expand Up @@ -592,12 +592,11 @@ func MainWithErrorCode() int {
}

if err != nil {
errorList = append(errorList, err)
for _, tempError := range errorList {
cliAnalytics.AddError(tempError)
}

cliAnalytics.AddError(err)

err = legacyCLITerminated(err, errorList)
}

Expand Down Expand Up @@ -633,7 +632,7 @@ func MainWithErrorCode() int {
globalLogger.Printf("Failed to cleanup %v", err)
}

return exitCode
return exitCode, errorList
}

func legacyCLITerminated(err error, errorList []error) error {
Expand Down
29 changes: 26 additions & 3 deletions cliv2/cmd/cliv2/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,9 +44,32 @@ func Test_MainWithErrorCode(t *testing.T) {
os.Args = []string{"snyk", "--version"}
defer func() { os.Args = oldArgs }()

err := MainWithErrorCode()

assert.Equal(t, 0, err)
errCode, _ := MainWithErrorCode()

assert.Equal(t, 0, errCode)

t.Run("outputs an error list", func(t *testing.T) {
t.Setenv("SNYK_TOKEN", "invalidToken")
defer cleanup()
oldArgs := append([]string{}, os.Args...)
os.Args = []string{"snyk", "whoami", "--experimental"}
defer func() {
os.Args = oldArgs
}()

errCode, errs := MainWithErrorCode()
assert.Equal(t, 2, errCode)

unauthorizedErrorCode := "SNYK-0005"
var actualErrorCodes []string
for _, err := range errs {
var snykError snyk_errors.Error
if errors.As(err, &snykError) {
actualErrorCodes = append(actualErrorCodes, snykError.ErrorCode)
}
}
assert.Contains(t, actualErrorCodes, unauthorizedErrorCode)
})
}

func Test_initApplicationConfiguration_DisablesAnalytics(t *testing.T) {
Expand Down
47 changes: 47 additions & 0 deletions cliv2/internal/cliv2/cliv2.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ import (
"os"
"os/exec"
"path"
"path/filepath"
"regexp"
"slices"
"strings"
"time"

"github.com/gofrs/flock"
"github.com/google/uuid"
"github.com/rs/zerolog"
"github.com/snyk/error-catalog-golang-public/snyk_errors"
"github.com/snyk/go-application-framework/pkg/configuration"
"github.com/snyk/go-application-framework/pkg/instrumentation"
"github.com/snyk/go-application-framework/pkg/runtimeinfo"
Expand Down Expand Up @@ -58,6 +61,8 @@ const (
V2_ABOUT Handler = iota
)

const configKeyErrFile = "INTERNAL_ERR_FILE_PATH"

func NewCLIv2(config configuration.Configuration, debugLogger *log.Logger, ri runtimeinfo.RuntimeInfo) (*CLI, error) {
cacheDirectory := config.GetString(configuration.CACHE_PATH)

Expand Down Expand Up @@ -351,6 +356,7 @@ func PrepareV1EnvironmentVariables(
// Fill environment variables for the legacy CLI from the given configuration.
func fillEnvironmentFromConfig(inputAsMap map[string]string, config configuration.Configuration, args []string) {
inputAsMap[constants.SNYK_INTERNAL_ORGID_ENV] = config.GetString(configuration.ORGANIZATION)
inputAsMap[constants.SNYK_INTERNAL_ERR_FILE] = config.GetString(configKeyErrFile)

if config.GetBool(configuration.PREVIEW_FEATURES_ENABLED) {
inputAsMap[constants.SNYK_INTERNAL_PREVIEW_FEATURES_ENABLED] = "1"
Expand Down Expand Up @@ -402,6 +408,9 @@ func (c *CLI) executeV1Default(proxyInfo *proxy.ProxyInfo, passThroughArgs []str
defer cancel()
}

filePath := filepath.Join(c.globalConfig.GetString(configuration.TEMP_DIR_PATH), fmt.Sprintf("err-file-%s", uuid.NewString()))
c.globalConfig.Set(configKeyErrFile, filePath)

snykCmd, err := c.PrepareV1Command(ctx, c.v1BinaryLocation, passThroughArgs, proxyInfo, c.GetIntegrationName(), GetFullVersion())

if c.DebugLogger.Writer() != io.Discard {
Expand Down Expand Up @@ -447,9 +456,47 @@ func (c *CLI) executeV1Default(proxyInfo *proxy.ProxyInfo, passThroughArgs []str
if errors.Is(ctx.Err(), context.DeadlineExceeded) {
return ctx.Err()
}

if err == nil {
return nil
}

if sentErrs, fileErr := c.getErrorFromFile(filePath); fileErr == nil {
err = errors.Join(err, sentErrs)
}

return err
}

func (c *CLI) getErrorFromFile(errFilePath string) (data error, err error) {
bytes, fileErr := os.ReadFile(errFilePath)
if fileErr != nil {
c.DebugLogger.Println("Failed to read error file: ", fileErr)
return nil, fileErr
}

jsonErrors, serErr := snyk_errors.FromJSONAPIErrorBytes(bytes)
if serErr != nil {
c.DebugLogger.Println("Failed to deserialize file: ", serErr)
return nil, fileErr
}

if len(jsonErrors) != 0 {
errs := make([]error, len(jsonErrors)+1)
for _, jerr := range jsonErrors {
jerr.Meta["orign"] = "Typescript-CLI"
errs = append(errs, jerr)
}

err = errors.Join(errs...)
c.DebugLogger.Println("Error file contained ", len(jsonErrors), " errors: ", err)
return err, nil
}

c.DebugLogger.Println("The file didn't contain any errors")
return nil, errors.New("no errorrs were sent thought the error file")
}

func (c *CLI) Execute(proxyInfo *proxy.ProxyInfo, passThroughArgs []string) error {
var err error
handler := determineHandler(passThroughArgs)
Expand Down
3 changes: 3 additions & 0 deletions cliv2/internal/cliv2/cliv2_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ func Test_PrepareV1EnvironmentVariables_Fill_and_Filter(t *testing.T) {
"HTTPS_PROXY=proxy",
"NODE_EXTRA_CA_CERTS=cacertlocation",
"SNYK_SYSTEM_NO_PROXY=noProxy",
"SNYK_ERR_FILE=",
"SNYK_SYSTEM_HTTP_PROXY=httpProxy",
"SNYK_SYSTEM_HTTPS_PROXY=httpsProxy",
"SNYK_INTERNAL_ORGID=" + orgid,
Expand Down Expand Up @@ -112,6 +113,7 @@ func Test_PrepareV1EnvironmentVariables_DontOverrideExistingIntegration(t *testi
"SNYK_SYSTEM_NO_PROXY=",
"SNYK_SYSTEM_HTTP_PROXY=",
"SNYK_SYSTEM_HTTPS_PROXY=",
"SNYK_ERR_FILE=",
"SNYK_INTERNAL_ORGID=" + orgid,
"SNYK_CFG_ORG=" + orgid,
"SNYK_API=" + testapi,
Expand Down Expand Up @@ -146,6 +148,7 @@ func Test_PrepareV1EnvironmentVariables_OverrideProxyAndCerts(t *testing.T) {
"NODE_EXTRA_CA_CERTS=cacertlocation",
"SNYK_SYSTEM_NO_PROXY=312123",
"SNYK_SYSTEM_HTTP_PROXY=exists",
"SNYK_ERR_FILE=",
"SNYK_SYSTEM_HTTPS_PROXY=already",
"SNYK_INTERNAL_ORGID=" + orgid,
"SNYK_CFG_ORG=" + orgid,
Expand Down
1 change: 1 addition & 0 deletions cliv2/internal/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const SNYK_OAUTH_ACCESS_TOKEN_ENV = "SNYK_OAUTH_TOKEN"
const SNYK_API_TOKEN_ENV = "SNYK_TOKEN"
const SNYK_ANALYTICS_DISABLED_ENV = "SNYK_DISABLE_ANALYTICS"
const SNYK_INTERNAL_ORGID_ENV = "SNYK_INTERNAL_ORGID"
const SNYK_INTERNAL_ERR_FILE = "SNYK_ERR_FILE"
const SNYK_INTERNAL_PREVIEW_FEATURES_ENABLED = "SNYK_INTERNAL_PREVIEW_FEATURES"
const SNYK_ENDPOINT_ENV = "SNYK_API"
const SNYK_ORG_ENV = "SNYK_CFG_ORG"
Expand Down
Loading
Loading