diff --git a/cmd/check.go b/cmd/check.go index 8484de5..9ada39d 100644 --- a/cmd/check.go +++ b/cmd/check.go @@ -54,14 +54,23 @@ pinned-package-updater --remote
check [Dockerf cobra.CheckErr(currentDirectoryErr) defaultDockerfilePath := path.Join(currentDirectory, "Dockerfile") args = append(args, defaultDockerfilePath) - log.Debugf("No dockerfile path provided, attempting to use the default %s dockerfile", defaultDockerfilePath) + log.WithFields(log.Fields{ + "defaultFilePath": defaultDockerfilePath, + }).Debug("no dockerfile path provided, attempting to use the default dockerfile path") } // Aggregate the parsed updates - for _, dockerfilePath := range args { - log.Debugf("Parsing %s", dockerfilePath) + numberOfFiles := len(args) + for i, dockerfilePath := range args { + log.WithFields(log.Fields{ + "filePath": dockerfilePath, + }).Debugf("parsing docker file %d/%d", i+1, numberOfFiles) _, err := dockerfile.Parse(dockerfilePath) if err != nil { + log.WithFields(log.Fields{ + "filePath": dockerfilePath, + "sourceError": err, + }).Fatal("unexpected error while parsing dockerfile") return } } @@ -77,7 +86,7 @@ func init() { err := parser.InitCommandParsers() if err != nil { - log.Errorf("Unexpected error while ") + log.Fatal("unexpected error while initializing command parsers") return } diff --git a/cmd/root.go b/cmd/root.go index 0f0a428..af48e52 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -31,14 +31,14 @@ import ( type LogFormat enumflag.Flag const ( - LoggingLogFmt LogFormat = iota - LoggingText + LoggingText LogFormat = iota + LoggingLogFmt LoggingJson ) var LogFormatIds = map[LogFormat][]string{ - LoggingLogFmt: {"logfmt"}, LoggingText: {"text"}, + LoggingLogFmt: {"logfmt"}, LoggingJson: {"json"}, } @@ -137,19 +137,22 @@ func initLogging() { } switch logFormat { + case LoggingText: + log.SetFormatter(&log.TextFormatter{}) case LoggingLogFmt: log.SetFormatter(&log.TextFormatter{ DisableColors: true, FullTimestamp: true, }) - case LoggingText: - log.SetFormatter(&log.TextFormatter{}) case LoggingJson: log.SetFormatter(&log.JSONFormatter{}) default: log.SetFormatter(&log.TextFormatter{}) } - log.Debugf("Set the logging level to %s", log.GetLevel().String()) - log.Debugf("Set the logging formatter to '%s'", LogFormatIds[logFormat][0]) + log.WithFields(log.Fields{ + "loggingFormatter": LogFormatIds[logFormat][0], + "loggingLevel": log.GetLevel().String(), + "loggingVerbosity": logVerbosity, + }).Info("configured logging") } diff --git a/internal/parser/debian.go b/internal/parser/debian.go index a879446..04a6305 100644 --- a/internal/parser/debian.go +++ b/internal/parser/debian.go @@ -33,17 +33,19 @@ type DebianCommandParser struct { } // GetRepositories Returns a list of Repository entries that were identified -func (c DebianCommandParser) GetRepositories(command string) ([]*Repository, error) { +func (c DebianCommandParser) GetRepositories(logger *log.Entry, command string) ([]*Repository, error) { var repositories []*Repository reader := strings.NewReader(command) - parsedScript, scriptParseErr := c.parser.Parse(reader, "") - if scriptParseErr != nil { - log.Fatalf("Failed to parse script \"%s\": %s", command, scriptParseErr) - return nil, scriptParseErr + parsedScript, err := c.parser.Parse(reader, "") + if err != nil { + logger.WithFields(log.Fields{ + "sourceError": err, + }).Fatal("failed to parse script") + return nil, err } - var hasErrorWhileWalkingScript bool = false + var erroredWhileWalkingScript = false var errorWhileWalkingScript error syntax.Walk(parsedScript, func(node syntax.Node) bool { switch parsedStatement := node.(type) { @@ -51,22 +53,27 @@ func (c DebianCommandParser) GetRepositories(command string) ([]*Repository, err switch parsedCommand := parsedStatement.Cmd.(type) { case *syntax.CallExpr: if isAddRepositoryExpression(*parsedCommand, parsedStatement.Redirs) { - repositoryInformation, repositoryParseErr := c.getAddedRepository(*parsedCommand, parsedStatement.Redirs) - if repositoryParseErr != nil { - log.Fatalf("Unexpected error while parsing repository from \"%s\": %s", c.shellNodeToString(node), repositoryParseErr) - hasErrorWhileWalkingScript = true - errorWhileWalkingScript = repositoryParseErr + repositoryInformation, err := c.getAddedRepository(*parsedCommand, parsedStatement.Redirs) + if err != nil { + logger.WithFields(log.Fields{ + "rawScriptNode": c.shellNodeToString(node), + "sourceError": err, + }).Fatal("unexpected error while parsing repository information") + erroredWhileWalkingScript = true + errorWhileWalkingScript = err // Terminate walking early so we can raise an error return false } repositories = append(repositories, &Repository{Information: repositoryInformation}) - log.Debugf("Found repository \"%s\"", repositoryInformation) + logger.WithFields(log.Fields{ + "repositoryInformation": repositoryInformation, + }).Debug("found repository") } } } return true }) - if hasErrorWhileWalkingScript { + if erroredWhileWalkingScript { return repositories, errorWhileWalkingScript } @@ -74,17 +81,19 @@ func (c DebianCommandParser) GetRepositories(command string) ([]*Repository, err } // GetPinnedPackages get a list of all pinned packages -func (c DebianCommandParser) GetPinnedPackages(command string) ([]*Package, error) { +func (c DebianCommandParser) GetPinnedPackages(logger *log.Entry, command string) ([]*Package, error) { var packages []*Package reader := strings.NewReader(command) - parsedScript, scriptParseErr := c.parser.Parse(reader, "") - if scriptParseErr != nil { - log.Fatalf("Failed to parse script \"%s\"", command) - return nil, scriptParseErr + parsedScript, err := c.parser.Parse(reader, "") + if err != nil { + logger.WithFields(log.Fields{ + "sourceError": err, + }).Fatal("failed to parse script") + return nil, err } - var hasErrorWhileWalkingScript bool = false + var erroredWhileWalkingScript = false var errorWhileWalkingScript error syntax.Walk(parsedScript, func(node syntax.Node) bool { switch parsedStatement := node.(type) { @@ -92,24 +101,29 @@ func (c DebianCommandParser) GetPinnedPackages(command string) ([]*Package, erro switch parsedCommand := parsedStatement.Cmd.(type) { case *syntax.CallExpr: if isInstallerExpression(*parsedCommand) { - pinnedPackages, pinnedPackageParseErr := c.getInstalledPinnedPackages(*parsedCommand) - if pinnedPackageParseErr != nil { - log.Fatalf("Unexpected error while parsing pinned packages from \"%s\": %s", c.shellNodeToString(node), pinnedPackageParseErr) - hasErrorWhileWalkingScript = true - errorWhileWalkingScript = pinnedPackageParseErr + pinnedPackages, err := c.getInstalledPinnedPackages(*parsedCommand) + if err != nil { + logger.WithFields(log.Fields{ + "rawScriptNode": c.shellNodeToString(node), + "sourceError": err, + }).Fatal("unexpected error while parsing pinned packages") + erroredWhileWalkingScript = true + errorWhileWalkingScript = err // Terminate walking early so we can raise an error return false } for _, pinnedPackage := range pinnedPackages { packages = append(packages, pinnedPackage) - log.Debugf("Found pinned package \"%s=%s\"", pinnedPackage.Name, pinnedPackage.Version) + logger.WithFields(log.Fields{ + "pinnedPackageInformation": pinnedPackage, + }).Debug("found pinned package") } } } } return true }) - if hasErrorWhileWalkingScript { + if erroredWhileWalkingScript { return packages, errorWhileWalkingScript } @@ -194,6 +208,10 @@ func (c DebianCommandParser) getAddedRepository(command syntax.CallExpr, redirec return "", errors.New("failed to parse repository") } +/* +Pinned Package Helpers +*/ + // isAddRepositoryExpression Checks for expressions that add to the repository func isInstallerExpression(command syntax.CallExpr) bool { // Adding via debian tooling @@ -204,10 +222,6 @@ func isInstallerExpression(command syntax.CallExpr) bool { return false } -/* -Pinned Package Helpers -*/ - func (c DebianCommandParser) getInstalledPinnedPackages(command syntax.CallExpr) ([]*Package, error) { var packages []*Package diff --git a/internal/parser/dockerfile/dockerfile.go b/internal/parser/dockerfile/dockerfile.go index 7b3dd63..e042743 100644 --- a/internal/parser/dockerfile/dockerfile.go +++ b/internal/parser/dockerfile/dockerfile.go @@ -19,7 +19,6 @@ package dockerfile import ( "errors" - "fmt" "os" "strings" @@ -33,63 +32,118 @@ import ( // Parse reads a Dockerfile, interprets the AST and returns information about the image(s) and package(s) defined func Parse(dockerfilePath string) (parser.ParseResult, error) { - dockerfile, dockerfileOpenErr := os.Open(dockerfilePath) - if dockerfileOpenErr != nil { - log.Fatalf("Unexpected error reading file %s: %s", dockerfilePath, dockerfileOpenErr) - return parser.ParseResult{}, dockerfileOpenErr + + parseLogger := log.WithFields(log.Fields{ + "filePath": dockerfilePath, + }) + + dockerfile, err := os.Open(dockerfilePath) + if err != nil { + log.WithFields(log.Fields{ + "sourceError": err, + }).Fatal("unexpected error reading file") + return parser.ParseResult{}, err } defer func(dockerfile *os.File) { - dockerfileCloseErr := dockerfile.Close() - if dockerfileCloseErr != nil { - log.Error("Unexpected error closing file %s: %s", dockerfilePath, dockerfileCloseErr) + err := dockerfile.Close() + if err != nil { + parseLogger.WithFields(log.Fields{ + "sourceError": err, + }).Error("unexpected error closing file") } }(dockerfile) - parsedASTResult, ASTParseErr := buildkitParser.Parse(dockerfile) - if ASTParseErr != nil { - log.Fatalf("Unexpected error parsing Dockerfile %s: %s", dockerfilePath, ASTParseErr) - return parser.ParseResult{}, ASTParseErr + parsedASTResult, err := buildkitParser.Parse(dockerfile) + if err != nil { + parseLogger.WithFields(log.Fields{ + "sourceError": err, + }).Fatal("unexpected error parsing Dockerfile") + return parser.ParseResult{}, err } - parseResult, parseErr := parseDockerfileAST(parsedASTResult.AST) - if parseErr != nil { - log.Fatalf("Unexpected error while parsing AST for %s: %s", dockerfilePath, parseErr) - return parser.ParseResult{}, parseErr + parseResult, err := parseDockerfileAST(parseLogger, dockerfilePath, parsedASTResult.AST) + if err != nil { + parseLogger.WithFields(log.Fields{ + "sourceError": err, + }).Fatal("unexpected error while parsing AST") + return parser.ParseResult{}, err } return parseResult, nil } -// parseDockerfileAST Read the parsed AST and extract the required information from each stage -func parseDockerfileAST(dockerfileAST *buildkitParser.Node) (parser.ParseResult, error) { +/* +General Helpers +*/ + +// getCommandLineLocation parse commandLineLocation information into the structure we will persist +func getContentLocation(location []buildkitParser.Range) (parser.ContentLocation, error) { + + commandLocationStart := location[0].Start + commandLocationEnd := location[len(location)-1].End + + return parser.ContentLocation{ + StartLine: commandLocationStart.Line, + StartCharacter: commandLocationStart.Character, + EndLine: commandLocationEnd.Line, + EndCharacter: commandLocationEnd.Character, + }, nil +} + +/* +Parsing Helper +*/ - dockerfileStages, _, stageParsingErr := buildkitInstructions.Parse(dockerfileAST) - if stageParsingErr != nil { - log.Fatalf("Unexpected error while parsing Dockerfile Instructions: %s", stageParsingErr) - return parser.ParseResult{}, stageParsingErr +// parseDockerfileAST Read the parsed AST and extract the required information from each stage +func parseDockerfileAST(logger *log.Entry, dockerFilePath string, dockerfileAST *buildkitParser.Node) (parser.ParseResult, error) { + + dockerfileStages, _, err := buildkitInstructions.Parse(dockerfileAST) + if err != nil { + logger.WithFields(log.Fields{ + "sourceError": err, + }).Fatal("unexpected error while parsing Dockerfile instructions") + return parser.ParseResult{}, err } var stages []*parser.Stage for stageIndex, stage := range dockerfileStages { - stage, stageErr := parseDockerfileStage(stage) - if stageErr != nil { - log.Fatal("Unexpected error while parsing stage %d", stageIndex) - return parser.ParseResult{}, stageErr + stageLogger := logger.WithFields(log.Fields{ + "stageIndex": stageIndex, + "stageName": stage.Name, + }) + stage, err := parseDockerfileStage(stageLogger, stageIndex, stage) + if err != nil { + stageLogger.WithFields(log.Fields{ + "sourceError": err, + }).Fatal("unexpected error while parsing stage") + return parser.ParseResult{}, err } stages = append(stages, &stage) } - return parser.ParseResult{Stages: stages}, nil + return parser.ParseResult{FilePath: dockerFilePath, Stages: stages}, nil } -func parseDockerfileStage(dockerfileStage buildkitInstructions.Stage) (parser.Stage, error) { +func parseDockerfileStage(logger *log.Entry, dockerfileStageIndex int, dockerfileStage buildkitInstructions.Stage) (parser.Stage, error) { + + stageLocation, err := getContentLocation(dockerfileStage.Location) + if err != nil { + logger.WithFields(log.Fields{ + "rawStageLocation": dockerfileStage.Location, + }).Fatal("unexpected failure parsing stage location information") + return parser.Stage{}, err + } + + stageLogger := logger.WithFields(log.Fields{ + "stageImage": dockerfileStage.BaseName, + "stageLocation": stageLocation, + }) imageParts := strings.Split(dockerfileStage.BaseName, ":") if len(imageParts) < 2 { - message := fmt.Sprintf("Not enough information in image (%s) to determine base", imageParts) - log.Fatal(message) - return parser.Stage{}, errors.New(message) + stageLogger.Fatal("not enough information in image to determine base") + return parser.Stage{}, errors.New("not enough information in image to determine base") } name := dockerfileStage.Name @@ -98,80 +152,125 @@ func parseDockerfileStage(dockerfileStage buildkitInstructions.Stage) (parser.St Tag: imageParts[1], } - commandParser, commandParserRetrievalErr := parser.GetCommandParser(image) - if commandParserRetrievalErr != nil { - log.Fatalf("Unexpected error while determining command parser for image \"%s\": %s", image, commandParserRetrievalErr) - return parser.Stage{}, commandParserRetrievalErr + commandParser, err := parser.GetCommandParser(image) + if err != nil { + stageLogger.WithFields(log.Fields{ + "sourceError": err, + }).Fatal("unexpected error while determining command parser for image") + return parser.Stage{}, err } - repositories, repositoryCommandParseErr := parseRepositoriesFromDockerfileStage(dockerfileStage, commandParser) - if repositoryCommandParseErr != nil { - message := fmt.Sprintf("Unexpected error while parsing repositories from stage: %s", repositoryCommandParseErr) - log.Fatal(message) - return parser.Stage{}, errors.New(message) + repositories, repositoryCommandLocations, err := parseRepositoriesFromDockerfileStage(stageLogger, dockerfileStage, commandParser) + if err != nil { + stageLogger.WithFields(log.Fields{ + "sourceError": err, + }).Fatal("unexpected error while parsing repositories from stage") + return parser.Stage{}, err } - packages, packageCommandParseErr := parsePackagesFromDockerfileStage(dockerfileStage, commandParser) - if packageCommandParseErr != nil { - message := fmt.Sprintf("Unexpected error while parsing packages from stage: %s", packageCommandParseErr) - log.Fatal(message) - return parser.Stage{}, errors.New(message) + packages, packageCommandLocations, err := parsePackagesFromDockerfileStage(stageLogger, dockerfileStage, commandParser) + if err != nil { + stageLogger.WithFields(log.Fields{ + "sourceError": err, + }).Fatal("unexpected error while parsing packages from stage") + return parser.Stage{}, err } + var commandLocations []*parser.ContentLocation + commandLocations = append(commandLocations, repositoryCommandLocations...) + commandLocations = append(commandLocations, packageCommandLocations...) + return parser.Stage{ - Name: name, - Image: &image, - Repositories: repositories, - Packages: packages, + Index: dockerfileStageIndex, + Name: name, + Image: &image, + StageLocation: stageLocation, + Repositories: repositories, + Packages: packages, + CommandLocations: commandLocations, }, nil } -func parseRepositoriesFromDockerfileStage(dockerfileStage buildkitInstructions.Stage, commandParser *parser.CommandParser) ([]*parser.Repository, error) { +func parseRepositoriesFromDockerfileStage(logger *log.Entry, dockerfileStage buildkitInstructions.Stage, commandParser *parser.CommandParser) ([]*parser.Repository, []*parser.ContentLocation, error) { var repositories []*parser.Repository + var commandLocations []*parser.ContentLocation + typedCommandParser := *commandParser for _, command := range dockerfileStage.Commands { - switch command.(type) { + switch typedCommand := command.(type) { case *buildkitInstructions.RunCommand: - runCommand := command.(*buildkitInstructions.RunCommand) - - log.Tracef("Parsing RunCommand \"%s\" for repositories", runCommand.CmdLine) - - parsedRepositories, repositoryParseErr := typedCommandParser.GetRepositories(runCommand.CmdLine[0]) - if repositoryParseErr != nil { - log.Fatalf("Unexpected error while parsing repositories: %s", repositoryParseErr) - return nil, repositoryParseErr + commandLocation, err := getContentLocation(typedCommand.Location()) + if err != nil { + logger.WithFields(log.Fields{ + "rawCommandLineLocation": typedCommand.Location(), + }).Fatal("unexpected failure parsing script location information") + return repositories, commandLocations, err } - for _, parsedRepository := range parsedRepositories { - repositories = append(repositories, parsedRepository) + commandLocations = append(commandLocations, &commandLocation) + + for _, line := range typedCommand.CmdLine { + logger.WithFields(log.Fields{ + "commandLine": line, + "commandLocation": commandLocation, + }).Trace("parsing RunCommand for repositories") + parsedRepositories, err := typedCommandParser.GetRepositories(logger, line) + if err != nil { + logger.WithFields(log.Fields{ + "commandLine": line, + "commandLocation": commandLocation, + "sourceError": err, + }).Fatal("unexpected error while parsing repositories") + return repositories, commandLocations, err + } + for _, parsedRepository := range parsedRepositories { + repositories = append(repositories, parsedRepository) + } } } } - return repositories, nil + return repositories, commandLocations, nil } -func parsePackagesFromDockerfileStage(dockerfileStage buildkitInstructions.Stage, commandParser *parser.CommandParser) ([]*parser.Package, error) { +func parsePackagesFromDockerfileStage(logger *log.Entry, dockerfileStage buildkitInstructions.Stage, commandParser *parser.CommandParser) ([]*parser.Package, []*parser.ContentLocation, error) { var packages []*parser.Package + var commandLocations []*parser.ContentLocation + typedCommandParser := *commandParser for _, command := range dockerfileStage.Commands { - switch command.(type) { + switch typedCommand := command.(type) { case *buildkitInstructions.RunCommand: - runCommand := command.(*buildkitInstructions.RunCommand) - - log.Tracef("Parsing RunCommand \"%s\" for packages", runCommand.CmdLine) - - parsedPinnedPackages, packageParseErr := typedCommandParser.GetPinnedPackages(runCommand.CmdLine[0]) - if packageParseErr != nil { - log.Fatalf("Unexpected error while parsing pinned packages: %s", packageParseErr) - return nil, packageParseErr + commandLocation, err := getContentLocation(typedCommand.Location()) + if err != nil { + logger.WithFields(log.Fields{ + "rawCommandLineLocation": typedCommand.Location(), + }).Fatal("unexpected failure parsing script location information") + return packages, commandLocations, err } - for _, parsedPinnedPackage := range parsedPinnedPackages { - packages = append(packages, parsedPinnedPackage) + commandLocations = append(commandLocations, &commandLocation) + + for _, line := range typedCommand.CmdLine { + logger.WithFields(log.Fields{ + "commandLine": line, + "commandLocation": commandLocation, + }).Trace("parsing RunCommand for packages") + parsedPinnedPackages, err := typedCommandParser.GetPinnedPackages(logger, line) + if err != nil { + logger.WithFields(log.Fields{ + "commandLine": line, + "commandLocation": commandLocation, + "sourceError": err, + }).Fatal("unexpected error while parsing pinned packages") + return packages, commandLocations, err + } + for _, parsedPinnedPackage := range parsedPinnedPackages { + packages = append(packages, parsedPinnedPackage) + } } } } - return packages, nil + return packages, commandLocations, nil } diff --git a/internal/parser/parser.go b/internal/parser/parser.go index 04fbaf6..5eea1b2 100644 --- a/internal/parser/parser.go +++ b/internal/parser/parser.go @@ -20,21 +20,62 @@ package parser import ( "errors" "fmt" - log "github.com/sirupsen/logrus" ) // ParseResult is the standard representation for am image declaration type ParseResult struct { - Stages []*Stage + FilePath string + Stages []*Stage +} + +// String implements fmt.Stringer interface +func (pr ParseResult) String() string { + return fmt.Sprintf( + "%s with %d stage(s)", + pr.FilePath, + len(pr.Stages), + ) } // Stage is the representation of a stage in the image declaration type Stage struct { - Name string - Image *Image - Repositories []*Repository - Packages []*Package + Index int + Name string + Image *Image + Repositories []*Repository + Packages []*Package + StageLocation ContentLocation + CommandLocations []*ContentLocation +} + +// String implements fmt.Stringer interface +func (s Stage) String() string { + return fmt.Sprintf( + "%d %s %s", + s.Index, + s.Name, + s.Image, + ) +} + +// ContentLocation information about where a specific set of information exists within the file/manifest +type ContentLocation struct { + StartLine int + StartCharacter int + EndLine int + EndCharacter int +} + +// String implements fmt.Stringer interface +func (cl ContentLocation) String() string { + return fmt.Sprintf( + "%d.%d:%d.%d", + cl.StartLine, + cl.StartCharacter, + cl.EndLine, + cl.EndCharacter, + ) } // Image is the representation of the reference to a container image layer @@ -43,17 +84,43 @@ type Image struct { Tag string } +// String implements fmt.Stringer interface +func (i Image) String() string { + return fmt.Sprintf( + "%s:%s", + i.Name, + i.Tag, + ) +} + // Package is the representation of an installed pinned package defined in an image stage declaration type Package struct { Name string Version string } +// String implements fmt.Stringer interface +func (p Package) String() string { + return fmt.Sprintf( + "%s=%s", + p.Name, + p.Version, + ) +} + // Repository is the representation of an added repository defined in a stage declaration type Repository struct { Information string } +// String implements fmt.Stringer interface +func (r Repository) String() string { + return fmt.Sprintf( + "%s", + r.Information, + ) +} + // CommandParserType represents the recognized type of parser usable for a specific image type CommandParserType int @@ -61,12 +128,13 @@ const ( CommandParserTypeDebian CommandParserType = iota ) -// CommandParserTypeIds is a lookup map between CommandParserType and a representation string +// commandParserTypeIds is a lookup map between CommandParserType and a representation string var commandParserTypeIds = map[CommandParserType][]string{ CommandParserTypeDebian: {"debian"}, } -func lookupCommandParserTypeName(parserType CommandParserType) string { +// findCommandParserTypeName lookup the command parser name by type enum +func findCommandParserTypeName(parserType CommandParserType) string { if parserTypeName, ok := commandParserTypeIds[parserType]; ok { return parserTypeName[0] } else { @@ -78,8 +146,8 @@ var availableCommandParsers = map[CommandParserType]CommandParser{} // CommandParser handles parsing commands from RUN arguments dependent on the base image type type CommandParser interface { - GetRepositories(runCommand string) ([]*Repository, error) - GetPinnedPackages(runCommand string) ([]*Package, error) + GetRepositories(logger *log.Entry, runCommand string) ([]*Repository, error) + GetPinnedPackages(logger *log.Entry, runCommand string) ([]*Package, error) } // InitCommandParsers exposes functionality to configure the behavior of mapping images to a CommandParser @@ -88,21 +156,29 @@ func InitCommandParsers() error { // Registering known parsers availableCommandParsers[CommandParserTypeDebian] = newDebianCommandParser() + // TODO: Add dynamic loading from configuration + return nil } func GetCommandParser(image Image) (*CommandParser, error) { - parserType, determineParserErr := determineParserForImage(image) - if determineParserErr != nil { - log.Fatalf("Failed to determine a matching CommandParser for %s: %s", image, determineParserErr) - return nil, determineParserErr + parserType, err := determineParserForImage(image) + if err != nil { + log.WithFields(log.Fields{ + "imageName": image.Name, + "imageTag": image.Tag, + "sourceError": err, + }).Fatal("failed to determine a matching CommandParser for image") + return nil, err } if requestedCommandParser, ok := availableCommandParsers[parserType]; ok { return &requestedCommandParser, nil } else { - message := fmt.Sprintf("Unable to identify parser for %s", lookupCommandParserTypeName(parserType)) - log.Fatal(message) + message := "unable to identify command parser type" + log.WithFields(log.Fields{ + "commandParserType": findCommandParserTypeName(parserType), + }).Fatal(message) return nil, errors.New(message) } }