func startAnalyzers(sourceDir string, images []string, dind bool) (containers []string, errs []error) { var wg sync.WaitGroup for id, fullImage := range images { wg.Add(1) go func(id int, image string) { analyzerContainer, port := getContainerAndAddress(image, id) if docker.ImageMatches(image, analyzerContainer) { glog.Infof("Reusing analyzer %v started at localhost:%d", image, port) } else { glog.Infof("Found no analyzer container (%v) to reuse for %v", analyzerContainer, image) // Analyzer is either running with the wrong image version, or not running // Stopping in case it's the first case result := docker.Stop(analyzerContainer, 0, true) if result.Err != nil { glog.Infof("Failed to stop %v (may not be running)", analyzerContainer) } result = docker.RunAnalyzer(image, analyzerContainer, sourceDir, localLogs, port, dind) if result.Err != nil { glog.Infof("Could not start %v at localhost:%d: %v, stderr: %v", image, port, result.Err.Error(), result.Stderr) errs = append(errs, result.Err) } else { glog.Infof("Analyzer %v started at localhost:%d", image, port) containers = append(containers, analyzerContainer) } } wg.Done() }(id, fullImage) } if len(images) > 0 { glog.Info("Waiting for dockerized analyzers to start up...") wg.Wait() glog.Info("Analyzers up") } return containers, errs }
// startShipshapeService ensures that there is a service started with the given image and // attached analyzers that can analyze the directory at absRoot (an absolute path). If a // service is not started up that can do this, it will shut down the existing one and start // a new one. // The methods returns the (ready) client, the relative path from the docker container's mapped // volume to the absRoot that we are analyzing, and any errors from attempting to run the service. // TODO(ciera): This *should* check the analyzers that are connected, but does not yet // do so. func startShipshapeService(image, absRoot string, analyzers []string, dind bool) (*client.Client, string, error) { glog.Infof("Starting shipshape...") container := "shipping_container" // subPath is the relatve path from the mapped volume on shipping container // to the directory we are analyzing (absRoot) isMapped, subPath := docker.MappedVolume(absRoot, container) // Stop and restart the container if: // 1: The container is not using the latest image OR // 2: The container is not mapped to the right directory OR // 3: The container is not linked to the right analyzer containers // Otherwise, use the existing container if !docker.ImageMatches(image, container) || !isMapped || !docker.ContainsLinks(container, analyzers) { glog.Infof("Restarting container with %s", image) stop(container, 0) result := docker.RunService(image, container, absRoot, localLogs, analyzers, dind) subPath = "" printStreams(result) if result.Err != nil { return nil, "", result.Err } } glog.Infof("Image %s running in service mode", image) c := client.NewHTTPClient("localhost:10007") return c, subPath, c.WaitUntilReady(10 * time.Second) }
func stop(container string, timeWait time.Duration) { glog.Infof("Stopping and removing %s", container) result := docker.Stop(container, timeWait, true) printStreams(result) if result.Err != nil { glog.Infof("Could not stop %s: %v", container, result.Err) } else { glog.Infoln("Removed.") } }
// RunAnalyzer runs the analyzer image with container analyzerContainer. It runs it at port (mapped // to internal port 10005), binds the volumes for the workspacePath and logsPath, and gives the privileged // if dind (docker-in-docker) is true. func RunAnalyzer(image, analyzerContainer, workspacePath, logsPath string, port int, dind bool) CommandResult { stdout := bytes.NewBuffer(nil) stderr := bytes.NewBuffer(nil) if len(analyzerContainer) == 0 { return CommandResult{"", "", errors.New("need to provide a name for the container")} } volumeMap := map[string]string{ workspacePath: shipshapeWork, logsPath: shipshapeLogs, } args := []string{"run"} if dind { args = append(args, "--privileged") } args = append(args, setupArgs(analyzerContainer, map[int]int{port: 10005}, volumeMap, nil, nil)...) args = append(args, "-d", image) glog.Infof("Running 'docker %v'\n", args) cmd := exec.Command("docker", args...) cmd.Stdout = stdout cmd.Stderr = stderr err := cmd.Run() return CommandResult{stdout.String(), stderr.String(), err} }
// RunService runs the shipshape service at image, as the container named container. It binds the // shipshape workspace and logs appropriately. It starts with the third-party analyzers already // running at analyzerContainers. The service is started with the privileged flag if dind (docker-in-docker) // is true. func RunService(image, container, workspacePath, logsPath string, analyzerContainers []string, dind bool) CommandResult { stdout := bytes.NewBuffer(nil) stderr := bytes.NewBuffer(nil) if len(container) == 0 { return CommandResult{"", "", errors.New("need to provide a name for the container")} } volumeMap := map[string]string{workspacePath: shipshapeWork, logsPath: shipshapeLogs} var locations []string for _, container := range analyzerContainers { locations = append(locations, fmt.Sprintf(`$%s_PORT_10005_TCP_ADDR:$%s_PORT_10005_TCP_PORT`, strings.ToUpper(container), strings.ToUpper(container))) } locations = append(locations, "localhost:10005", "localhost:10006", "localhost:10008") args := []string{"run"} if dind { args = append(args, "--privileged") } args = append(args, setupArgs(container, map[int]int{10007: 10007}, volumeMap, analyzerContainers, map[string]string{"START_SERVICE": "true", "ANALYZERS": strings.Join(locations, ",")})...) args = append(args, "-d", image) glog.Infof("Running 'docker %v'\n", args) cmd := exec.Command("docker", args...) cmd.Stdout = stdout cmd.Stderr = stderr err := cmd.Run() return CommandResult{stdout.String(), stderr.String(), err} }
func printStreams(result docker.CommandResult) { out := strings.TrimSpace(result.Stdout) err := strings.TrimSpace(result.Stderr) if len(out) > 0 { glog.Infof("stdout:\n%s\n", strings.TrimSpace(result.Stdout)) } if len(err) > 0 { glog.Errorf("stderr:\n%s\n", strings.TrimSpace(result.Stderr)) } }
func (i *Invocation) StartService() error { _, paths, cleanup, err := i.startServices() // TODO(ciera): This is a problem right now. Since we call cleanup here, // it's going to immediately take down any third party analyzer, thus making the entire // start service step rather pointless when we use third party analyzers. defer cleanup() if err != nil { return fmt.Errorf("HTTP client did not become healthy: %v", err) } glog.Infof("Service started for %s", paths.absRoot) return nil }
func pull(image string) { if !docker.OutOfDate(image) { return } glog.Infof("Pulling image %s", image) result := docker.Pull(image) printStreams(result) if result.Err != nil { glog.Errorf("Error from pull: %v", result.Err) return } glog.Infoln("Pulling complete") }
func pull(image string) { // If we are "local", use a local version and don't actually do a pull. // Also don't pull if we aren't out of date yet. if strings.HasSuffix(image, ":local") || !docker.OutOfDate(image) { return } glog.Infof("Pulling image %s", image) result := docker.Pull(image) printStreams(result) if result.Err != nil { glog.Errorf("Error from pull: %v", result.Err) return } glog.Infoln("Pulling complete") }
func (i *Invocation) ShowCategories() error { var c *client.Client var res *rpcpb.GetCategoryResponse // Run it on files c, _, cleanup, err := i.startServices() defer cleanup() if err != nil { return fmt.Errorf("HTTP client did not become healthy: %v", err) } glog.Infof("Calling to get the categories") err = c.Call("/ShipshapeService/GetCategory", &rpcpb.GetCategoryRequest{}, &res) if err != nil { fmt.Errorf("Could not get categories: %v", err) return err } fmt.Println(strings.Join(res.Category, "\n")) return nil }
func analyze(c *client.Client, req *rpcpb.ShipshapeRequest, originalDir string, handleResponse func(msg *rpcpb.ShipshapeResponse, directory string) error) (int, error) { var totalNotes = 0 glog.Infof("Calling to the shipshape service with %v", req) rd := c.Stream("/ShipshapeService/Run", req) defer rd.Close() for { var msg rpcpb.ShipshapeResponse if err := rd.NextResult(&msg); err == io.EOF { break } else if err != nil { return 0, fmt.Errorf("received an error from calling run: %v", err.Error()) } err := handleResponse(&msg, originalDir) if err != nil { return 0, fmt.Errorf("could not parse results: %v", err.Error()) } totalNotes += numNotes(&msg) } return totalNotes, nil }
func (i *Invocation) Run() (int, error) { glog.Infof("Starting shipshape...") fs, err := os.Stat(i.options.File) if err != nil { return 0, fmt.Errorf("%s is not a valid file or directory\n", i.options.File) } origDir := i.options.File if !fs.IsDir() { origDir = filepath.Dir(i.options.File) } absRoot, err := filepath.Abs(origDir) if err != nil { return 0, fmt.Errorf("could not get absolute path for %s: %v\n", origDir, err) } if !docker.HasDocker() { return 0, fmt.Errorf("docker could not be found. Make sure you have docker installed.") } image := docker.FullImageName(i.options.Repo, image, i.options.Tag) glog.Infof("Starting shipshape using %s on %s", image, absRoot) // Create the request if len(i.options.TriggerCats) == 0 { glog.Infof("No categories provided. Will be using categories specified by the config file for the event %s", i.options.Event) } if len(i.options.ThirdPartyAnalyzers) == 0 { i.options.ThirdPartyAnalyzers, err = service.GlobalConfig(absRoot) if err != nil { glog.Infof("Could not get global config; using only the default analyzers: %v", err) } } // If we are not running in local mode, pull the latest copy // Notice this will use the local tag as a signal to not pull the // third-party analyzers either. if i.options.Tag != "local" { pull(image) pullAnalyzers(i.options.ThirdPartyAnalyzers) } // Put in this defer before calling run. Even if run fails, it can // still create the container. if !i.options.StayUp { // TODO(ciera): Rather than immediately sending a SIGKILL, // we should use the default 10 seconds and properly handle // SIGTERMs in the endpoint script. defer stop("shipping_container", 0) // Stop all the analyzers, even the ones that had trouble starting, // in case they did actually start for id, analyzerRepo := range i.options.ThirdPartyAnalyzers { container, _ := getContainerAndAddress(analyzerRepo, id) defer stop(container, 0) } } containers, errs := startAnalyzers(absRoot, i.options.ThirdPartyAnalyzers, i.options.Dind) for _, err := range errs { glog.Errorf("Could not start up third party analyzer: %v", err) } var c *client.Client var req *rpcpb.ShipshapeRequest var numNotes int // Run it on files relativeRoot := "" c, relativeRoot, err = startShipshapeService(image, absRoot, containers, i.options.Dind) if err != nil { return 0, fmt.Errorf("HTTP client did not become healthy: %v", err) } var files []string if !fs.IsDir() { files = []string{filepath.Base(i.options.File)} } req = createRequest(i.options.TriggerCats, files, i.options.Event, filepath.Join(workspace, relativeRoot), ctxpb.Stage_PRE_BUILD.Enum()) glog.Infof("Calling with request %v", req) numNotes, err = analyze(c, req, origDir, i.options.HandleResponse) if err != nil { return numNotes, fmt.Errorf("error making service call: %v", err) } // If desired, generate compilation units with a kythe image if i.options.Build != "" { // TODO(ciera): Handle other build systems fullKytheImage := docker.FullImageName(i.options.Repo, kytheImage, i.options.Tag) if !i.options.LocalKythe { pull(fullKytheImage) } // TODO(emso): Add a check for an already running kythe container. // The below defer should stop the one started below but in case this // failed for some reason (or a kythe container was started in some other // way) the below run command will fail. defer stop("kythe", 10*time.Second) glog.Infof("Retrieving compilation units with %s", i.options.Build) result := docker.RunKythe(fullKytheImage, "kythe", absRoot, i.options.Build, i.options.Dind) if result.Err != nil { // kythe spews output, so only capture it if something went wrong. printStreams(result) return numNotes, fmt.Errorf("error from run: %v", result.Err) } glog.Infoln("CompilationUnits prepared") req.Stage = ctxpb.Stage_POST_BUILD.Enum() glog.Infof("Calling with request %v", req) numBuildNotes, err := analyze(c, req, origDir, i.options.HandleResponse) numNotes += numBuildNotes if err != nil { return numNotes, fmt.Errorf("error making service call: %v", err) } } if i.options.ResponsesDone != nil { if err := i.options.ResponsesDone(); err != nil { return numNotes, err } } glog.Infoln("End of Results.") return numNotes, nil }
func (i *Invocation) Run() (int, error) { var c *client.Client var req *rpcpb.ShipshapeRequest var numNotes int // Run it on files c, paths, cleanup, err := i.startServices() defer cleanup() if err != nil { return 0, err } var files []string if !paths.fs.IsDir() { files = []string{filepath.Base(i.options.File)} } if len(i.options.TriggerCats) == 0 { glog.Infof("No categories provided. Will be using categories specified by the config file for the event %s", i.options.Event) } req = createRequest(i.options.TriggerCats, files, i.options.Event, filepath.Join(workspace, paths.relativeRoot), ctxpb.Stage_PRE_BUILD.Enum()) glog.Infof("Calling with request %v", req) numNotes, err = analyze(c, req, paths.origDir, i.options.HandleResponse) if err != nil { return numNotes, fmt.Errorf("error making service call: %v", err) } // If desired, generate compilation units with a kythe image if i.options.Build != "" { // TODO(ciera): Handle other build systems fullKytheImage := docker.FullImageName(i.options.Repo, kytheImage, i.options.Tag) if !i.options.LocalKythe { pull(fullKytheImage) } // Stop kythe if it is running otherwise we will fail when we start kythe below exists, err := docker.ContainerExists(fullKytheImage) if err != nil { return numNotes, fmt.Errorf("error making service call: %v", err) } if exists { stop(fullKytheImage, 0) } // Make sure we stop kythe after we are done defer stop("kythe", 10*time.Second) glog.Infof("Retrieving compilation units with %s", i.options.Build) result := docker.RunKythe(fullKytheImage, "kythe", paths.absRoot, i.options.Build, i.options.Dind) if result.Err != nil { // kythe spews output, so only capture it if something went wrong. printStreams(result) return numNotes, fmt.Errorf("error from run: %v", result.Err) } glog.Infoln("CompilationUnits prepared") req.Stage = ctxpb.Stage_POST_BUILD.Enum() glog.Infof("Calling with request %v", req) numBuildNotes, err := analyze(c, req, paths.origDir, i.options.HandleResponse) numNotes += numBuildNotes if err != nil { return numNotes, fmt.Errorf("error making service call: %v", err) } } if i.options.ResponsesDone != nil { if err := i.options.ResponsesDone(); err != nil { return numNotes, err } } glog.Infoln("End of Results.") return numNotes, nil }
func (i *Invocation) startServices() (*client.Client, Paths, func(), error) { var paths Paths var err error glog.Infof("Starting shipshape...") paths.fs, err = os.Stat(i.options.File) if err != nil { return nil, paths, func() {}, fmt.Errorf("%s is not a valid file or directory\n", i.options.File) } paths.origDir = i.options.File if !paths.fs.IsDir() { paths.origDir = filepath.Dir(i.options.File) } paths.absRoot, err = filepath.Abs(paths.origDir) if err != nil { return nil, paths, func() {}, fmt.Errorf("could not get absolute path for %s: %v\n", paths.origDir, err) } if !docker.HasDocker() { return nil, paths, func() {}, fmt.Errorf("docker could not be found. Make sure you have docker installed.") } image := docker.FullImageName(i.options.Repo, image, i.options.Tag) glog.Infof("Starting shipshape using %s on %s", image, paths.absRoot) if len(i.options.ThirdPartyAnalyzers) == 0 { i.options.ThirdPartyAnalyzers, err = service.GlobalConfig(paths.absRoot) if err != nil { glog.Infof("Could not get global config; using only the default analyzers: %v", err) } } pull(image) pullAnalyzers(i.options.ThirdPartyAnalyzers) // Create a cleanup function that will stop all the containers we started, // if that is desired. cleanup := func() { if !i.options.StayUp { // TODO(ciera): Rather than immediately sending a SIGKILL, // we should use the default 10 seconds and properly handle // SIGTERMs in the endpoint script. stop("shipping_container", 0) } // Stop all the analyzers, even the ones that had trouble starting, // in case they did actually start for id, analyzerRepo := range i.options.ThirdPartyAnalyzers { container, _ := getContainerAndAddress(analyzerRepo, id) stop(container, 0) } } containers, errs := startAnalyzers(paths.absRoot, i.options.ThirdPartyAnalyzers, i.options.Dind) for _, err := range errs { glog.Errorf("Could not start up third party analyzer: %v", err) } var c *client.Client c, paths.relativeRoot, err = startShipshapeService(image, paths.absRoot, containers, i.options.Dind) if err != nil { return nil, paths, cleanup, fmt.Errorf("HTTP client did not become healthy: %v", err) } return c, paths, cleanup, nil }