ghorg/cmd/reclone.go
2025-06-29 11:18:06 -07:00

221 lines
6.5 KiB
Go

package cmd
import (
"fmt"
"os"
"os/exec"
"strings"
"github.com/gabrie30/ghorg/colorlog"
"github.com/gabrie30/ghorg/configs"
"github.com/spf13/cobra"
"gopkg.in/yaml.v2"
)
var reCloneCmd = &cobra.Command{
Use: "reclone",
Short: "Reruns one, multiple, or all preconfigured clones from configuration set in $HOME/.config/ghorg/reclone.yaml",
Long: `Allows you to set preconfigured clone commands for handling multiple users/orgs at once. See https://github.com/gabrie30/ghorg#reclone-command for setup and additional information.`,
Run: reCloneFunc,
}
type ReClone struct {
Cmd string `yaml:"cmd"`
Description string `yaml:"description"`
PostExecScript string `yaml:"post_exec_script"` // optional
}
func isQuietReClone() bool {
return os.Getenv("GHORG_RECLONE_QUIET") == "true"
}
func reCloneFunc(cmd *cobra.Command, argz []string) {
if cmd.Flags().Changed("reclone-path") {
path := cmd.Flag("reclone-path").Value.String()
os.Setenv("GHORG_RECLONE_PATH", path)
}
if cmd.Flags().Changed("quiet") {
os.Setenv("GHORG_RECLONE_QUIET", "true")
}
if cmd.Flags().Changed("env-config-only") {
os.Setenv("GHORG_RECLONE_ENV_CONFIG_ONLY", "true")
}
path := configs.GhorgReCloneLocation()
yamlBytes, err := os.ReadFile(path)
if err != nil {
colorlog.PrintErrorAndExit(fmt.Sprintf("ERROR: parsing reclone.yaml, error: %v", err))
}
mapOfReClones := make(map[string]ReClone)
err = yaml.Unmarshal(yamlBytes, &mapOfReClones)
if err != nil {
colorlog.PrintErrorAndExit(fmt.Sprintf("ERROR: unmarshaling reclone.yaml, error:%v", err))
}
if cmd.Flags().Changed("list") {
colorlog.PrintInfo("**************************************************************")
colorlog.PrintInfo("**** Available reclone commands and optional descriptions ****")
colorlog.PrintInfo("**************************************************************")
fmt.Println("")
for key, value := range mapOfReClones {
colorlog.PrintInfo(fmt.Sprintf("- %s", key))
if value.Description != "" {
colorlog.PrintSubtleInfo(fmt.Sprintf(" description: %s", value.Description))
}
colorlog.PrintSubtleInfo(fmt.Sprintf(" cmd: %s", value.Cmd))
fmt.Println("")
}
os.Exit(0)
}
if len(argz) == 0 {
for rcIdentifier, reclone := range mapOfReClones {
runReClone(reclone, rcIdentifier)
}
} else {
for _, rcIdentifier := range argz {
if _, ok := mapOfReClones[rcIdentifier]; !ok {
colorlog.PrintErrorAndExit(fmt.Sprintf("ERROR: The key %v was not found in reclone.yaml", rcIdentifier))
} else {
runReClone(mapOfReClones[rcIdentifier], rcIdentifier)
}
}
}
printFinalOutput(argz, mapOfReClones)
}
func printFinalOutput(argz []string, reCloneMap map[string]ReClone) {
fmt.Println("")
colorlog.PrintSuccess("Completed! The following reclones were ran successfully...")
if len(argz) == 0 {
for key := range reCloneMap {
colorlog.PrintSuccess(fmt.Sprintf(" * %v", key))
}
} else {
for _, arg := range argz {
colorlog.PrintSuccess(fmt.Sprintf(" * %v", arg))
}
}
}
func sanitizeCmd(cmd string) string {
if strings.Contains(cmd, "-t=") {
splitCmd := strings.Split(cmd, "-t=")
firstHalf := splitCmd[0]
secondHalf := strings.Split(splitCmd[1], " ")[1:]
return firstHalf + "-t=XXXXXXX " + strings.Join(secondHalf, " ")
}
if strings.Contains(cmd, "-t ") {
splitCmd := strings.Split(cmd, "-t ")
firstHalf := splitCmd[0]
secondHalf := strings.Split(splitCmd[1], " ")[1:]
return firstHalf + "-t XXXXXXX " + strings.Join(secondHalf, " ")
}
if strings.Contains(cmd, "--token=") {
splitCmd := strings.Split(cmd, "--token=")
firstHalf := splitCmd[0]
secondHalf := strings.Split(splitCmd[1], " ")[1:]
return firstHalf + "--token=XXXXXXX " + strings.Join(secondHalf, " ")
}
if strings.Contains(cmd, "--token ") {
splitCmd := strings.Split(cmd, "--token ")
firstHalf := splitCmd[0]
secondHalf := strings.Split(splitCmd[1], " ")[1:]
return firstHalf + "--token XXXXXXX " + strings.Join(secondHalf, " ")
}
return cmd
}
func runReClone(rc ReClone, rcIdentifier string) {
// make sure command starts with ghorg clone
splitCommand := strings.Split(rc.Cmd, " ")
ghorg, clone, remainingCommand := splitCommand[0], splitCommand[1], splitCommand[1:]
if ghorg != "ghorg" || clone != "clone" {
colorlog.PrintErrorAndExit("ERROR: Only ghorg clone commands are permitted in your reclone.yaml")
}
safeToLogCmd := sanitizeCmd(strings.Clone(rc.Cmd))
if !isQuietReClone() {
fmt.Println("")
colorlog.PrintInfo(fmt.Sprintf("Running reclone: %v", rcIdentifier))
if rc.Description != "" {
colorlog.PrintInfo(fmt.Sprintf("Description: %v", rc.Description))
fmt.Println("")
}
colorlog.PrintInfo(fmt.Sprintf("> %v", safeToLogCmd))
}
ghorgClone := exec.Command("ghorg", remainingCommand...)
if os.Getenv("GHORG_CONFIG") == "none" {
os.Setenv("GHORG_CONFIG", "")
}
os.Setenv("GHORG_RECLONE_RUNNING", "true")
defer os.Setenv("GHORG_RECLONE_RUNNING", "false")
if os.Getenv("GHORG_RECLONE_ENV_CONFIG_ONLY") == "false" {
// have to unset all ghorg envs because root command will set them on initialization of ghorg cmd
for _, e := range os.Environ() {
keyValue := strings.SplitN(e, "=", 2)
env := keyValue[0]
ghorgEnv := strings.HasPrefix(env, "GHORG_")
// skip global flags and reclone flags which are set in the conf.yaml
if env == "GHORG_COLOR" || env == "GHORG_CONFIG" || env == "GHORG_RECLONE_QUIET" || env == "GHORG_RECLONE_PATH" || env == "GHORG_RECLONE_RUNNING" {
continue
}
if ghorgEnv {
os.Unsetenv(env)
}
}
}
// Connect ghorgClone's stdout and stderr to the current process's stdout and stderr
if !isQuietReClone() {
ghorgClone.Stdout = os.Stdout
ghorgClone.Stderr = os.Stderr
} else {
spinningSpinner.Start()
defer spinningSpinner.Stop()
ghorgClone.Stdout = nil
ghorgClone.Stderr = nil
}
err := ghorgClone.Start()
if err != nil {
spinningSpinner.Stop()
colorlog.PrintErrorAndExit(fmt.Sprintf("ERROR: Starting ghorg clone cmd: %v, err: %v", safeToLogCmd, err))
}
err = ghorgClone.Wait()
status := "success"
if err != nil {
status = "fail"
}
if rc.PostExecScript != "" {
postCmd := exec.Command(rc.PostExecScript, status, rcIdentifier)
postCmd.Stdout = os.Stdout
postCmd.Stderr = os.Stderr
errPost := postCmd.Run()
if errPost != nil {
colorlog.PrintError(fmt.Sprintf("ERROR: Running post_exec_script %s: %v", rc.PostExecScript, errPost))
}
}
if err != nil {
spinningSpinner.Stop()
colorlog.PrintErrorAndExit(fmt.Sprintf("ERROR: Running ghorg clone cmd: %v, err: %v", safeToLogCmd, err))
}
}