code-intelligence.com/cifuzz@v0.40.0/internal/cmd/container/run/run.go (about)

     1  package run
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"os/signal"
     9  	"syscall"
    10  
    11  	"github.com/docker/docker/pkg/stdcopy"
    12  	"github.com/pkg/errors"
    13  	"github.com/spf13/cobra"
    14  
    15  	"code-intelligence.com/cifuzz/internal/bundler"
    16  	"code-intelligence.com/cifuzz/internal/cmdutils"
    17  	"code-intelligence.com/cifuzz/internal/cmdutils/logging"
    18  	"code-intelligence.com/cifuzz/internal/cmdutils/resolve"
    19  	"code-intelligence.com/cifuzz/internal/completion"
    20  	"code-intelligence.com/cifuzz/internal/config"
    21  	"code-intelligence.com/cifuzz/internal/container"
    22  	"code-intelligence.com/cifuzz/pkg/log"
    23  )
    24  
    25  type containerRunOpts struct {
    26  	bundler.Opts  `mapstructure:",squash"`
    27  	Interactive   bool   `mapstructure:"interactive"`
    28  	ContainerPath string `mapstructure:"container"`
    29  }
    30  
    31  type containerRunCmd struct {
    32  	*cobra.Command
    33  	opts *containerRunOpts
    34  }
    35  
    36  func New() *cobra.Command {
    37  	return newWithOptions(&containerRunOpts{})
    38  }
    39  
    40  func (opts *containerRunOpts) Validate() error {
    41  	return opts.Opts.Validate()
    42  }
    43  
    44  func newWithOptions(opts *containerRunOpts) *cobra.Command {
    45  	var bindFlags func()
    46  
    47  	cmd := &cobra.Command{
    48  		Use:   "run",
    49  		Short: "Build and run a Fuzz Test container image locally",
    50  		Long: `This command builds and runs a Fuzz Test container image locally.
    51  It can be used as a containerized version of the 'cifuzz bundle' command, where the
    52  container is built and run locally instead of being pushed to a CI Sense server.`,
    53  		ValidArgsFunction: completion.ValidFuzzTests,
    54  		Args:              cobra.ExactArgs(1),
    55  		PreRunE: func(cmd *cobra.Command, args []string) error {
    56  			// Bind viper keys to flags. We can't do this in the New
    57  			// function, because that would re-bind viper keys which
    58  			// were bound to the flags of other commands before.
    59  			bindFlags()
    60  
    61  			var argsToPass []string
    62  			if cmd.ArgsLenAtDash() != -1 {
    63  				argsToPass = args[cmd.ArgsLenAtDash():]
    64  				args = args[:cmd.ArgsLenAtDash()]
    65  			}
    66  
    67  			err := config.FindAndParseProjectConfig(opts)
    68  			if err != nil {
    69  				log.Errorf(err, "Failed to parse cifuzz.yaml: %v", err.Error())
    70  				return cmdutils.WrapSilentError(err)
    71  			}
    72  
    73  			fuzzTests, err := resolve.FuzzTestArguments(opts.ResolveSourceFilePath, args, opts.BuildSystem, opts.ProjectDir)
    74  			if err != nil {
    75  				log.Print(err.Error())
    76  				return cmdutils.WrapSilentError(err)
    77  			}
    78  			opts.FuzzTests = fuzzTests
    79  			opts.BuildSystemArgs = argsToPass
    80  
    81  			return opts.Validate()
    82  		},
    83  		RunE: func(c *cobra.Command, args []string) error {
    84  			cmd := &containerRunCmd{Command: c, opts: opts}
    85  			return cmd.run()
    86  		},
    87  	}
    88  	bindFlags = cmdutils.AddFlags(cmd,
    89  		cmdutils.AddAdditionalFilesFlag,
    90  		cmdutils.AddBranchFlag,
    91  		cmdutils.AddBuildCommandFlag,
    92  		cmdutils.AddCleanCommandFlag,
    93  		cmdutils.AddBuildJobsFlag,
    94  		cmdutils.AddCommitFlag,
    95  		cmdutils.AddDictFlag,
    96  		cmdutils.AddDockerImageFlag,
    97  		cmdutils.AddEngineArgFlag,
    98  		cmdutils.AddEnvFlag,
    99  		cmdutils.AddPrintJSONFlag,
   100  		cmdutils.AddProjectDirFlag,
   101  		cmdutils.AddProjectFlag,
   102  		cmdutils.AddSeedCorpusFlag,
   103  		cmdutils.AddServerFlag,
   104  		cmdutils.AddTimeoutFlag,
   105  		cmdutils.AddResolveSourceFileFlag,
   106  	)
   107  	cmd.Flags().StringVar(&opts.ContainerPath, "container", "", "Path of an existing container to start a run with.")
   108  
   109  	return cmd
   110  }
   111  
   112  func (c *containerRunCmd) run() error {
   113  	var err error
   114  
   115  	logging.StartBuildProgressSpinner(log.ContainerBuildInProgressMsg)
   116  	containerID, err := c.buildContainerFromImage()
   117  	if err != nil {
   118  		logging.StopBuildProgressSpinnerOnError(log.ContainerBuildInProgressErrorMsg)
   119  		return err
   120  	}
   121  
   122  	logging.StopBuildProgressSpinnerOnSuccess(log.ContainerBuildInProgressSuccessMsg, false)
   123  
   124  	logging.StartBuildProgressSpinner(log.ContainerRunInProgressMsg)
   125  
   126  	// Handle signal interrupts
   127  	sigChan := make(chan os.Signal, 1)
   128  	signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
   129  	go func() {
   130  		<-sigChan
   131  		logging.StopBuildProgressSpinnerOnError("Received interrupt, stopping container and cifuzz...")
   132  		err := container.Stop(containerID)
   133  		if err != nil {
   134  			log.Error(errors.Wrap(err, "container could not be stopped"))
   135  		}
   136  	}()
   137  
   138  	out, err := container.Start(containerID)
   139  	if err != nil {
   140  		logging.StopBuildProgressSpinnerOnError(log.ContainerRunInProgressErrorMsg)
   141  		return err
   142  	}
   143  	logging.StopBuildProgressSpinnerOnSuccess(log.ContainerRunInProgressSuccessMsg, false)
   144  
   145  	// Copy the logs to two different vars, so that we can pass them around
   146  	// independently.
   147  	containerStdOut := new(bytes.Buffer)
   148  	containerStdErr := new(bytes.Buffer)
   149  	_, err = stdcopy.StdCopy(containerStdOut, containerStdErr, out)
   150  	if err != nil && err != io.EOF {
   151  		return err
   152  	}
   153  
   154  	// TODO: make output pretty
   155  	//  Remove 'cifuzz version' from output
   156  	fmt.Println(containerStdOut.String())
   157  	fmt.Println(containerStdErr.String())
   158  
   159  	return nil
   160  }
   161  
   162  func (c *containerRunCmd) buildContainerFromImage() (string, error) {
   163  	b := bundler.New(&c.opts.Opts)
   164  	bundlePath, err := b.Bundle()
   165  	if err != nil {
   166  		return "", err
   167  	}
   168  
   169  	err = container.BuildImageFromBundle(bundlePath)
   170  	if err != nil {
   171  		return "", err
   172  	}
   173  
   174  	return container.Create(c.opts.FuzzTests[0])
   175  }