github.com/xgoffin/jenkins-library@v1.154.0/cmd/hadolintExecute.go (about)

     1  package cmd
     2  
     3  import (
     4  	"bytes"
     5  	"io"
     6  	"net/http"
     7  	"os"
     8  	"time"
     9  
    10  	"github.com/SAP/jenkins-library/pkg/command"
    11  	piperhttp "github.com/SAP/jenkins-library/pkg/http"
    12  	"github.com/SAP/jenkins-library/pkg/log"
    13  	"github.com/SAP/jenkins-library/pkg/piperutils"
    14  	"github.com/SAP/jenkins-library/pkg/telemetry"
    15  	"github.com/pkg/errors"
    16  )
    17  
    18  const hadolintCommand = "hadolint"
    19  
    20  // HadolintPiperFileUtils abstracts piperutils.Files
    21  // mock generated with: mockery --name HadolintPiperFileUtils --dir cmd --output pkg/hadolint/mocks
    22  type HadolintPiperFileUtils interface {
    23  	FileExists(filename string) (bool, error)
    24  	FileWrite(filename string, data []byte, perm os.FileMode) error
    25  }
    26  
    27  // HadolintClient abstracts http.Client
    28  // mock generated with: mockery --name hadolintClient --dir cmd --output pkg/hadolint/mocks
    29  type HadolintClient interface {
    30  	SetOptions(options piperhttp.ClientOptions)
    31  	DownloadFile(url, filename string, header http.Header, cookies []*http.Cookie) error
    32  }
    33  
    34  // hadolintRunner abstracts command.Command
    35  type hadolintRunner interface {
    36  	RunExecutable(executable string, params ...string) error
    37  	Stdout(err io.Writer)
    38  	Stderr(err io.Writer)
    39  }
    40  
    41  type hadolintUtils struct {
    42  	HadolintPiperFileUtils
    43  	HadolintClient
    44  	hadolintRunner
    45  }
    46  
    47  func hadolintExecute(config hadolintExecuteOptions, _ *telemetry.CustomData) {
    48  	runner := command.Command{
    49  		ErrorCategoryMapping: map[string][]string{},
    50  	}
    51  	// reroute runner output to logging framework
    52  	runner.Stdout(log.Writer())
    53  	runner.Stderr(log.Writer())
    54  
    55  	utils := hadolintUtils{
    56  		HadolintPiperFileUtils: &piperutils.Files{},
    57  		HadolintClient:         &piperhttp.Client{},
    58  		hadolintRunner:         &runner,
    59  	}
    60  
    61  	if err := runHadolint(config, utils); err != nil {
    62  		log.Entry().WithError(err).Fatal("Execution failed")
    63  	}
    64  }
    65  
    66  func runHadolint(config hadolintExecuteOptions, utils hadolintUtils) error {
    67  	var outputBuffer bytes.Buffer
    68  	var errorBuffer bytes.Buffer
    69  	utils.Stdout(&outputBuffer)
    70  	utils.Stderr(&errorBuffer)
    71  
    72  	options := []string{"--format", "checkstyle"}
    73  	// load config file from URL
    74  	if !hasConfigurationFile(config.ConfigurationFile, utils) && len(config.ConfigurationURL) > 0 {
    75  		clientOptions := piperhttp.ClientOptions{
    76  			TransportTimeout:          20 * time.Second,
    77  			TransportSkipVerification: true,
    78  		}
    79  		if len(config.CustomTLSCertificateLinks) > 0 {
    80  			clientOptions.TransportSkipVerification = false
    81  			clientOptions.TrustedCerts = config.CustomTLSCertificateLinks
    82  		}
    83  
    84  		if len(config.ConfigurationUsername) > 0 {
    85  			clientOptions.Username = config.ConfigurationUsername
    86  			clientOptions.Password = config.ConfigurationPassword
    87  		}
    88  		utils.SetOptions(clientOptions)
    89  		if err := loadConfigurationFile(config.ConfigurationURL, config.ConfigurationFile, utils); err != nil {
    90  			return errors.Wrap(err, "failed to load configuration file from URL")
    91  		}
    92  	}
    93  	// use config
    94  	if hasConfigurationFile(config.ConfigurationFile, utils) {
    95  		options = append(options, "--config", config.ConfigurationFile)
    96  		log.Entry().WithField("file", config.ConfigurationFile).Debug("Using configuration file")
    97  	} else {
    98  		log.Entry().Debug("No configuration file found.")
    99  	}
   100  	// execute scan command
   101  	err := utils.RunExecutable(hadolintCommand, append([]string{config.DockerFile}, options...)...)
   102  
   103  	//TODO: related to https://github.com/hadolint/hadolint/issues/391
   104  	// hadolint exists with 1 if there are processing issues but also if there are findings
   105  	// thus check stdout first if a report was created
   106  	if output := outputBuffer.String(); len(output) > 0 {
   107  		log.Entry().WithField("report", output).Debug("Report created")
   108  		utils.FileWrite(config.ReportFile, []byte(output), 0666)
   109  	} else if err != nil {
   110  		// if stdout is empty a processing issue occured
   111  		return errors.Wrap(err, errorBuffer.String())
   112  	}
   113  	//TODO: mock away in tests
   114  	// persist report information
   115  	piperutils.PersistReportsAndLinks("hadolintExecute", "./", []piperutils.Path{{Target: config.ReportFile}}, []piperutils.Path{})
   116  	return nil
   117  }
   118  
   119  // loadConfigurationFile loads a file from the provided url
   120  func loadConfigurationFile(url, file string, utils hadolintUtils) error {
   121  	log.Entry().WithField("url", url).Debug("Loading configuration file from URL")
   122  	return utils.DownloadFile(url, file, nil, nil)
   123  }
   124  
   125  // hasConfigurationFile checks if the given file exists
   126  func hasConfigurationFile(file string, utils hadolintUtils) bool {
   127  	exists, err := utils.FileExists(file)
   128  	if err != nil {
   129  		log.Entry().WithError(err).Error()
   130  	}
   131  	return exists
   132  }