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 }