func installPlugins(l *log.Logger, errChan chan errMsg) { if err := core.LoadConf(); err != nil { errChan <- errMsg{msg: "", err: err} return } plugins := buildPluginFile(l) // Fetch all plugins if len(plugins.Dependencies) == 1 { l.Infof("Fetching 1 plugin...\n") } else { l.Infof("Fetching %d plugins...\n", len(plugins.Dependencies)) } outC, err := exec. Command("/bin/sh", "-c", "go get ./..."). CombinedOutput() if err == nil { syncDependencies(plugins, l, errChan) return } // Show errors only when it's not a private repo issue if !strings.Contains(string(outC), "fatal: could not read Username for") { errChan <- errMsg{msg: string(outC), err: err} return } // TODO enable versioning for private repos l.Infof("Fetching private repos...\n\n") l.Info("*** This will delete your local plugins to fetch remote copies.") _, err = fmt.Print("Continue? [n]: ") if err != nil { errChan <- errMsg{msg: "", err: err} return } reader := bufio.NewReader(os.Stdin) text, err := reader.ReadString('\n') if err != nil { errChan <- errMsg{msg: "", err: errors.New("failed to read from stdin")} return } if text[0] != 'y' && text[0] != 'Y' { errChan <- errMsg{msg: "Canceled", err: nil} return } wg := &sync.WaitGroup{} for name, _ := range plugins.Dependencies { go clonePrivateRepo(name, errChan, wg) } wg.Wait() syncDependencies(plugins, l, errChan) }
func buildPluginFile(l *log.Logger) *core.PluginJSON { // Create plugins.go file, truncate if exists fi, err := os.Create("plugins.go") if err != nil { l.Fatal(err) } defer func() { if err = fi.Close(); err != nil { l.Fatal(err) } }() s := "// This file is generated by `abot plugin install`. Do not edit.\n" s += "package main\n\nimport (\n" for url := range core.Conf().Dependencies { // Insert _ imports s += fmt.Sprintf("\t_ \"%s\"\n", url) } s += ")" _, err = fi.WriteString(s) if err != nil { l.Fatal(err) } return core.Conf() }
func embedPluginConfs(plugins *core.PluginJSON, l *log.Logger) { log.Debug("embedding plugin confs") // Open plugins.go file for writing fi, err := os.OpenFile("plugins.go", os.O_WRONLY|os.O_APPEND, 0666) if err != nil { l.Fatal(err) } defer func() { if err = fi.Close(); err != nil { l.Fatal(err) } }() p := os.Getenv("GOPATH") tokenizedPath := strings.Split(p, string(os.PathListSeparator)) // Insert plugin.json text as comments s := "\n\n/*\n" for u := range plugins.Dependencies { s += u + "\n" log.Debug("reading file", p) p = filepath.Join(tokenizedPath[0], "src", u, "plugin.json") fi2, err2 := os.Open(p) if err2 != nil { l.Fatal(err2) } scn := bufio.NewScanner(fi2) var tmp string for scn.Scan() { line := scn.Text() + "\n" s += line tmp += line } if err2 = scn.Err(); err2 != nil { l.Fatal(err2) } if err2 = fi2.Close(); err2 != nil { l.Fatal(err2) } var plg struct{ Name string } if err2 = json.Unmarshal([]byte(tmp), &plg); err2 != nil { l.Fatal(err2) } // Fetch remote plugin IDs to be included in the plugin confs plg.Name = url.QueryEscape(plg.Name) ul := os.Getenv("ITSABOT_URL") + "/api/plugins/by_name/" + plg.Name req, err2 := http.NewRequest("GET", ul, nil) if err2 != nil { l.Fatal(err2) } client := &http.Client{Timeout: 10 * time.Second} resp, err2 := client.Do(req) if err2 != nil { l.Fatal(err2) } var data struct{ ID uint64 } if err2 := json.NewDecoder(resp.Body).Decode(&data); err2 != nil { l.Fatal(err2) } id := strconv.FormatUint(data.ID, 10) // Remove closing characters to insert additional ID data s = s[:len(s)-3] s += ",\n\t\"ID\": " + id + "\n}\n" } s += "*/" _, err = fi.WriteString(s) if err != nil { l.Fatal(err) } }
func updateGlockfileAndInstall(l *log.Logger) { outC, err := exec. Command("/bin/sh", "-c", `pwd | sed "s|$GOPATH/src/||"`). CombinedOutput() if err != nil { l.Info(string(outC)) l.Fatal(err) } // Update plugin dependency versions in GLOCKFILE p := string(outC) outC, err = exec. Command("/bin/sh", "-c", "glock save "+p). CombinedOutput() if err != nil { l.Info(string(outC)) l.Fatal(err) } outC, err = exec. Command("/bin/sh", "-c", "go install"). CombinedOutput() if err != nil { l.Info(string(outC)) l.Fatal(err) } }
func syncDependencies(plugins *core.PluginJSON, l *log.Logger, errChan chan errMsg) { // Sync each of them to get dependencies wg := &sync.WaitGroup{} rand.Seed(time.Now().UTC().UnixNano()) for url, version := range plugins.Dependencies { wg.Add(1) go func(url, version string) { defer wg.Done() // Check out specific commit var outB []byte var errB error if version != "*" { l.Debug("checking out", url, "at", version) p := filepath.Join(os.Getenv("GOPATH"), "src", url) c := fmt.Sprintf("git -C %s checkout %s", p, version) outB, errB = exec. Command("/bin/sh", "-c", c). CombinedOutput() if errB != nil { l.Debug(string(outB)) errChan <- errMsg{msg: "", err: errB} return } } // Anonymously increment the plugin's download count // at itsabot.org l.Debug("incrementing download count", url) p := struct{ Path string }{Path: url} outB, errB = json.Marshal(p) if errB != nil { errChan <- errMsg{msg: "failed to build itsabot.org JSON.", err: errB} return } var u string u = os.Getenv("ITSABOT_URL") + "/api/plugins.json" req, errB := http.NewRequest("PUT", u, bytes.NewBuffer(outB)) if errB != nil { errChan <- errMsg{msg: "failed to build request to itsabot.org.", err: errB} return } client := &http.Client{Timeout: 10 * time.Second} resp, errB := client.Do(req) if errB != nil { errChan <- errMsg{msg: "failed to update itsabot.org.", err: errB} return } defer func() { if errB = resp.Body.Close(); errB != nil { errChan <- errMsg{msg: "", err: errB} } }() if resp.StatusCode != 200 { l.Infof("WARN: %d - %s\n", resp.StatusCode, resp.Status) } }(url, version) } wg.Wait() // Ensure dependencies are still there with the latest checked out // versions, and install the plugins l.Info("Installing plugins...") outC, err := exec. Command("/bin/sh", "-c", "go get ./..."). CombinedOutput() if err != nil { errChan <- errMsg{msg: string(outC), err: err} return } embedPluginConfs(plugins, l) updateGlockfileAndInstall(l) errChan <- errMsg{msg: "Success!"} }
// newAbot creates a new directory for a new Abot project. It's similar to // `rails new`. func newAbot(l *log.Logger, name, dbconnstr string) error { // Create a new directory for the project if err := os.Mkdir(name, 0777); err != nil { return err } if err := os.Chdir(name); err != nil { return err } // Generate abot.env fi, err := os.Create("abot.env") if err != nil { return err } defer func() { if err = fi.Close(); err != nil { l.Info("failed to close abot.env.", err) } }() dir, err := os.Getwd() if err != nil { return err } _, err = fi.WriteString(serverAbotEnv(name, dir)) if err != nil { return err } // Copy and modify base files p := filepath.Join(os.Getenv("ABOT_PATH"), "base", "plugins.json") if err = core.CopyFileContents(p, "plugins.json"); err != nil { return err } p = filepath.Join(os.Getenv("ABOT_PATH"), "base", "server.go.x") if err = core.CopyFileContents(p, "server.go"); err != nil { return err } p = filepath.Join(os.Getenv("ABOT_PATH"), "base", ".gitignore") if err = core.CopyFileContents(p, ".gitignore"); err != nil { return err } fi2, err := os.OpenFile(".gitignore", os.O_APPEND|os.O_WRONLY, 0666) if err != nil { return err } defer func() { if err = fi2.Close(); err != nil { l.Info("failed to close .gitignore.", err) } }() _, err = fi2.WriteString(name) if err != nil { l.Fatal("failed to write to .gitignore.", err) } // Walk the base/assets dir, copying all files p = filepath.Join(os.Getenv("ABOT_PATH"), "base", "assets") if err = filepath.Walk(p, recursiveCopy); err != nil { return err } p = filepath.Join(os.Getenv("ABOT_PATH"), "base", "cmd") if err = filepath.Walk(p, recursiveCopy); err != nil { return err } p = filepath.Join(os.Getenv("ABOT_PATH"), "base", "data") if err = filepath.Walk(p, recursiveCopy); err != nil { return err } p = filepath.Join(os.Getenv("ABOT_PATH"), "base", "db") if err = filepath.Walk(p, recursiveCopy); err != nil { return err } // Run cmd/dbsetup.sh cmd := exec.Command("/bin/sh", "-c", "cmd/dbsetup.sh "+dbconnstr) cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr if err = cmd.Run(); err != nil { l.Info("Fix the errors above, then re-run cmd/dbsetup.sh") return err } // TODO analytics on a new Abot project return nil }