func (m *ScanManager) UpdateSession(sc *scan.Scan, obj *scan.Session) error { var index *int isRootSession := !obj.HasParent() if isRootSession { // find session indx for updating, but only for root session for i, s := range sc.Sessions { if s.Id == obj.Id { index = &i break } } } if isRootSession && index == nil { return mgo.ErrNotFound } now := time.Now().UTC() switch st := obj.Status; true { case st == scan.StatusQueued: if obj.Queued == nil { obj.Queued = &now } case st == scan.StatusFinished || st == scan.StatusFailed: if obj.Finished == nil { obj.Finished = &now } case st == scan.StatusWorking: if obj.Started == nil { obj.Started = &now } } scanModified := false // only root sessions have influence on scan if isRootSession { // if session is queued then scan should also be queued if obj.Status == scan.StatusQueued && (sc.Status == scan.StatusCreated || sc.Status == scan.StatusWorking) { sc.Status = scan.StatusQueued scanModified = true } // if session is working then scan should also be working if obj.Status == scan.StatusWorking && (sc.Status != scan.StatusWorking) { sc.Status = scan.StatusWorking scanModified = true } // if session failed then scan should be failed too if obj.Status == scan.StatusFailed { sc.Status = scan.StatusFailed scanModified = true } // if session was the last one if obj.Status == scan.StatusFinished && (*index+1) == len(sc.Sessions) { sc.Status = scan.StatusFinished scanModified = true } } // if scan modified then update the whole scan object if scanModified || !isRootSession { return m.Update(sc) } // TODO (m0sth8): update non root session with set key := fmt.Sprintf("sessions.%d", *index) update := bson.M{"$set": bson.M{key: obj, "updated": now}} m.col.UpdateId(sc.Id, update) return m.Update(sc) }
func (a *Agent) HandleScan(ctx context.Context, sess *scan.Session) error { // take a plugin pl, err := a.api.Plugins.Get(ctx, client.FromId(sess.Plugin)) if err != nil { return stackerr.Wrap(err) } logrus.Infof("plugin: %s", pl) logrus.Info("set session to working state") sess.Status = scan.StatusWorking if sess, err = a.api.Scans.SessionUpdate(ctx, sess); err != nil { return err } setFailed := func(err error) error { logrus.Info("set session to failed state, due to %s", err) sess.Status = scan.StatusFailed if sess, err = a.api.Scans.SessionUpdate(ctx, sess); err != nil { return err } return err } // we have a couple of hack for boot2docker network isBoot2Docker := utils.IsBoot2Docker() hostCfg := &dockerclient.HostConfig{} args := sess.Step.Conf.CommandArgs cfg := &dockerclient.Config{ Image: pl.Container.Image, Tty: true, Cmd: strings.Split(args, " "), } switch pl.Type { case plugin.Util: case plugin.Script: if hostCfg.PortBindings == nil { hostCfg.PortBindings = map[dockerclient.Port][]dockerclient.PortBinding{} } hostIp := "127.0.0.1" if isBoot2Docker { // we should listen on external boot2docker virtual machine interface hostIp = "" } hostCfg.PortBindings["9238/tcp"] = []dockerclient.PortBinding{dockerclient.PortBinding{HostIP: hostIp}} default: return setFailed(fmt.Errorf("Unexpected plugin type %v", pl.Type)) } if sharedFiles := sess.Step.Conf.SharedFiles; sharedFiles != nil && len(sharedFiles) > 0 { tmpRoot := os.TempDir() if isBoot2Docker { // in mac os boot2docker, our binded directories must be inside the /Users home directory // TODO (m0sth8): exclude tmp root to config files home, err := homedir.Dir() if err != nil { logrus.Error(stackerr.Wrap(err)) return setFailed(fmt.Errorf("Can't get a home directory")) } tmpRoot = filepath.Join(home, "Library/Caches/bearded-web") err = os.MkdirAll(tmpRoot, 0755) if err != nil { logrus.Error(stackerr.Wrap(err)) return setFailed(fmt.Errorf("Can't create a tmp directory %s", tmpRoot)) } } tmpDir, err := ioutil.TempDir(tmpRoot, "bearded-volume-") if err != nil { logrus.Error(stackerr.Wrap(err)) return setFailed(fmt.Errorf("Can't create a temp directory")) } defer func() { os.RemoveAll(tmpDir) }() shareDir := filepath.Join(tmpDir, "share") err = os.MkdirAll(shareDir, 0755) if err != nil { logrus.Error(stackerr.Wrap(err)) return setFailed(fmt.Errorf("Can't create a share directory")) } for _, sharedFile := range sharedFiles { base := filepath.Base(sharedFile.Path) dir := filepath.Dir(sharedFile.Path) dir = filepath.Join(shareDir, filepath.Join("/", dir)) err := os.MkdirAll(dir, 0755) if err != nil { logrus.Error(stackerr.Wrap(err)) return setFailed(fmt.Errorf("Can't create a directory")) } err = ioutil.WriteFile(filepath.Join(dir, base), []byte(sharedFile.Text), 0644) if err != nil { logrus.Error(stackerr.Wrap(err)) return setFailed(fmt.Errorf("Can't create a temporary file")) } println("put file", filepath.Join(dir, base)) } hostCfg.Binds = append(hostCfg.Binds, fmt.Sprintf("%s:/share:r", shareDir)) println("bind", fmt.Sprintf("%s:/share:ro", shareDir)) } takeFiles := []string{} if sess.Step.Conf.TakeFiles != nil { for _, f := range sess.Step.Conf.TakeFiles { takeFiles = append(takeFiles, f.Path) } } ch := a.dclient.RunImage(ctx, cfg, hostCfg, takeFiles) // creating container var container *dockerclient.Container select { case <-ctx.Done(): return setFailed(ctx.Err()) case res := <-ch: // container info if res.Err != nil { return setFailed(res.Err) } container = res.Container } // get ports var serv *RemoteServer if pl.Type == plugin.Script { // setup transport between agent and script // script should expose 9238 port := container.NetworkSettings.Ports["9238/tcp"][0].HostPort if port == "" { return setFailed(stackerr.New("Unexpected empty port")) } host := "127.0.0.1" // TODO (m0sth8): extract this logic if isBoot2Docker { bootIp, err := utils.Boot2DocketIp() if err != nil { return setFailed(stackerr.Wrap(err)) } host = string(bootIp) } logrus.Infof("script addr is %s:%s", host, port) // transp := websocket.NewClient(fmt.Sprintf("ws://%s:%s", host, port)) transp, err := mango.NewClient(fmt.Sprintf("tcp://%s:%s", host, port)) if err != nil { return setFailed(stackerr.Wrap(err)) } // setup remote server serv, _ = NewRemoteServer(transp, a.api, sess) go transp.Serve(ctx, serv) err = serv.Connect(ctx) if err != nil { return setFailed(stackerr.Wrap(err)) } } var ( res docker.ContainerResponse // closed bool ) // running select { case <-ctx.Done(): return setFailed(ctx.Err()) case res = <-ch: // if closed { // // TODO (m0sth8): handle closed channel from docker container // return setFailed(stackerr.Newf("Docker channel is closed, %v", res)) // } } if res.Err != nil { logrus.Error(res.Err) return setFailed(stackerr.Wrap(res.Err)) } var rep *report.Report raw := &report.Report{ Type: report.TypeRaw, Raw: report.Raw{Raw: string(res.Log)}, } // handle files from container if sess.Step.Conf.TakeFiles != nil && res.Files != nil { for _, f := range sess.Step.Conf.TakeFiles { if data, found := res.Files[f.Path]; found { name := f.Path if f.Name != "" { name = f.Name } meta, err := a.api.Files.Create(ctx, name, data) if err != nil { logrus.Error(stackerr.Wrap(err)) continue } raw.Files = append(raw.Files, meta) } } } rep = raw switch pl.Type { case plugin.Script: if serv != nil && serv.Rep != nil { if serv.Rep.Type != report.TypeMulti { rep = &report.Report{ Type: report.TypeMulti, } rep.Multi = append(rep.Multi, serv.Rep) } else { rep = serv.Rep } rep.Multi = append(rep.Multi, raw) } } _, err = a.api.Scans.SessionReportCreate(ctx, sess, rep) if err != nil { return setFailed(stackerr.Wrap(err)) } logrus.Info("finished") sess.Status = scan.StatusFinished if sess, err = a.api.Scans.SessionUpdate(ctx, sess); err != nil { return err } return nil }