diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..64b2342 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +/.env +/.idea \ No newline at end of file diff --git a/cmd/clone.go b/cmd/clone.go new file mode 100644 index 0000000..71e42a5 --- /dev/null +++ b/cmd/clone.go @@ -0,0 +1,105 @@ +/* +Copyright © 2024 NAME HERE +*/ +package cmd + +import ( + "bufio" + "encoding/json" + "fmt" + "github.com/joho/godotenv" + "github.com/spf13/cobra" + "io/ioutil" + "log" + "net/http" + "os" + "strconv" +) + +// Mmm structs +// I actually lvoe this part of go +type Repository struct { + FullName string `json:"full_name"` + CloneURL string `json:"clone_url"` +} + +var cloneCmd = &cobra.Command{ + Use: "clone", + Short: "Gets all git repos, tries to figure out which one you wanna download, and then downloads it", + Long: `A longer description that spans multiple lines and likely contains examples +and usage of using your command. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + Run: clone, +} + +func init() { + godotenv.Load() + rootCmd.AddCommand(cloneCmd) +} + +func clone(cmd *cobra.Command, args []string) { + fmt.Println("clone called") + //we'll do this one in indivudual funcs + //connect to GH + //get all repos + repos := connect() + //ask user which one they want + + fmt.Println("Select a project to start:") + for i, repo := range repos { + fmt.Printf("[%d] %s\n", i+1, repo.FullName) + } + + scanner := bufio.NewScanner(os.Stdin) + fmt.Print("Enter your choice: ") + scanner.Scan() + choice := scanner.Text() + + choiceIndex, err := strconv.Atoi(choice) + if err != nil || choiceIndex < 1 || choiceIndex > len(repos) { + fmt.Println("Invalid choice") + return + } + + //download it + +} + +func connect() []Repository { + + // i'll need to restructure this if I make a github app, can't seem to get pricate repos working + + client := &http.Client{} + req, err := http.NewRequest("GET", "https://api.github.com/user/repos", nil) + if err != nil { + log.Fatal(err) + } + + req.Header.Add("Authorization", "token "+os.Getenv("GITHUB_ACCESS_TOKEN")) + + response, err := client.Do(req) + if err != nil { + log.Fatal(err) + } + defer response.Body.Close() + + responseData, err := ioutil.ReadAll(response.Body) + if err != nil { + log.Fatal(err) + } + + var repositories []Repository + err = json.Unmarshal(responseData, &repositories) + if err != nil { + log.Fatal(err) + } + + for _, repo := range repositories { + fmt.Println(repo.FullName) + fmt.Println(repo.CloneURL) + } + return repositories +} diff --git a/cmd/list.go b/cmd/list.go new file mode 100644 index 0000000..533ab4f --- /dev/null +++ b/cmd/list.go @@ -0,0 +1,67 @@ +package cmd + +import ( + "fmt" + "io/ioutil" + "path/filepath" + "strings" + + "github.com/fatih/color" + "github.com/spf13/cobra" +) + +// listCmd represents the list command +var listCmd = &cobra.Command{ + Use: "list", + Short: "Lists available projects", + Long: `Lists available projects. For example:`, + Run: availableProjects, +} + +func init() { + rootCmd.AddCommand(listCmd) +} + +/* +This was my old method to get all the projects, my new one is now in start.go fun! :) +*/ +func availableProjects(cmd *cobra.Command, args []string) { + path := "../" + file1 := "docker-compose.yml" + file2 := "makefile" + green := color.New(color.FgGreen).SprintFunc() + red := color.New(color.FgRed).SprintFunc() + prefix := "project-" + directories, err := ioutil.ReadDir(path) + if err != nil { + fmt.Printf("Error reading directory: %s\n", err) + return + } + + for _, dir := range directories { + if dir.IsDir() && strings.HasPrefix(dir.Name(), prefix) { + dirPath := filepath.Join(path, dir.Name()) + files, err := ioutil.ReadDir(dirPath) + if err != nil { + fmt.Printf("Error reading directory '%s': %s\n", dirPath, err) + continue + } + + found1, found2 := false, false + for _, file := range files { + if file.Name() == file1 { + found1 = true + } else if file.Name() == file2 { + found2 = true + } + } + + if found1 || found2 { + fmt.Println(green("✔"), dir.Name()) + } else { + fmt.Println(red("✖"), dir.Name()) + } + + } + } +} diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..f1eba45 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,45 @@ +/* +Copyright © 2024 NAME HERE +*/ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "llama-orc", + Short: "A brief description of your application", + Long: `A longer description that spans multiple lines and likely contains +examples and usage of using your application. For example: + +Cobra is a CLI library for Go that empowers applications. +This application is a tool to generate the needed files +to quickly create a Cobra application.`, + + //Run: func(cmd *cobra.Command, args []string) {}, +} + +// Execute adds all child commands to the root command and sets flags appropriately. +// This is called by main.main(). It only needs to happen once to the rootCmd. +func Execute() { + err := rootCmd.Execute() + if err != nil { + os.Exit(1) + } +} + +func init() { + // Here you will define your flags and configuration settings. + // Cobra supports persistent flags, which, if defined here, + // will be global for your application. + + // rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.llama-orc.yaml)") + + // Cobra also supports local flags, which will only run + // when this action is called directly. + rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") +} diff --git a/cmd/start.go b/cmd/start.go new file mode 100644 index 0000000..246e84c --- /dev/null +++ b/cmd/start.go @@ -0,0 +1,208 @@ +package cmd + +/* +Still lots to do & learn +But tl;dr this file can +Check if you need/your Proxy is working +Adds a spinner while it starts, if it doesn't think it's started & it will ask you if you want to start it + +It will check the ../dir you have it installed and lists all the projects you have, THAT contain a makefile or a docker compose +it will then ask you to select one, and it will prioitise the makefile if it exists, if not it will use docker compose up -d + + + +*/ + +import ( + "bufio" + "bytes" + "fmt" + "github.com/fatih/color" + "github.com/joho/godotenv" + "github.com/spf13/cobra" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "time" +) + +// startCmd represents the start command +var startCmd = &cobra.Command{ + Use: "start", + Short: "Start a selected project using Docker Compose", + Long: `Start a selected project using Docker Compose. For example:`, + Run: startProject, +} + +var cursors = []string{"|", "/", "-", "\\"} +var green = color.New(color.FgGreen).SprintFunc() +var red = color.New(color.FgRed).SprintFunc() + +func init() { + err := godotenv.Load() + if err != nil { + return + } + rootCmd.AddCommand(startCmd) +} + +func startProject(cmd *cobra.Command, args []string) { + + //before we do anything lets do some preflight checks + preChecks() + + path := "../" + projects := listProjects(path) + + fmt.Println("Select a project to start:") + for i, project := range projects { + fmt.Printf("[%d] %s\n", i+1, green("✔ ")+project) + } + + scanner := bufio.NewScanner(os.Stdin) + fmt.Print("Enter your choice: ") + scanner.Scan() + choice := scanner.Text() + + choiceIndex, err := strconv.Atoi(choice) + if err != nil || choiceIndex < 1 || choiceIndex > len(projects) { + fmt.Println("Invalid choice") + return + } + files, err := os.ReadDir(path) + if err != nil { + fmt.Println("Error reading directory:", err) + return + } + + fmt.Println("Starting project:", projects[choiceIndex-1]) + var hasMakefile, hasDockerCompose bool + for _, file := range files { + if file.IsDir() && file.Name() == projects[choiceIndex-1] { + fmt.Printf("Subdirectory: %s\n", file.Name()) + + subDirPath := filepath.Join(path, file.Name()) + + subFiles, err := os.ReadDir(subDirPath) + if err != nil { + fmt.Println("Error reading subdirectory:", err) + continue + } + + for _, subFile := range subFiles { + switch subFile.Name() { + case "makefile": + hasMakefile = true + case "docker-compose.yml", "docker-compose.yaml": + hasDockerCompose = true + } + } + + } + } + + if hasMakefile { + fmt.Printf("Starting project %s using makefile\n", projects[choiceIndex-1]) + exec.Command("make", "up").Run() + } else if hasDockerCompose { + fmt.Printf("Starting project %s using docker-compose\n", projects[choiceIndex-1]) + exec.Command("docker-compose", "up", "-d").Run() + } +} + +func preChecks() { + //@todo: + // ✔ check if on VPN + //check if proxy is running + print("sh", "-c", "docker ps | grep proxy") + //maybe find a way to better check if the proxy is running, bind it? + proxyCheck := exec.Command("sh", "-c", "docker ps | grep proxy") + var out bytes.Buffer + proxyCheck.Stdout = &out + result := proxyCheck.Run() + + if result != nil { + + fmt.Println(red("✖ "), os.Getenv("PROXY_NAME")+" is not running, would you like to start it? [Y, N]") + scanner := bufio.NewScanner(os.Stdin) + scanner.Scan() + choice := scanner.Text() + + if choice == "Y" || choice == "y" || choice == "Yes" || choice == "yes" { + fmt.Println("Starting", os.Getenv("PROXY_NAME")) + stopSpinner := make(chan bool) + go startSpinner(stopSpinner) + path := "../" + os.Getenv("PROXY_NAME") + + cmd := exec.Command("make", "up") + cmd.Dir = path + + if err := cmd.Run(); err != nil { + fmt.Println("Error running Docker command:", err) + stopSpinner <- true + return + } + + stopSpinner <- true + proxyCheck.Run() + // Additional logic after the Docker command completes + fmt.Println(green("✔ "), os.Getenv("PROXY_NAME")+" Started") + fmt.Println("------------") + + } + } else { + if out.String() != "" { + fmt.Println(green("✔ ") + os.Getenv("PROXY_NAME") + " is already running") + fmt.Println("------------") + } + } +} + +func listProjects(path string) []string { + var projects []string + + directories, err := ioutil.ReadDir(path) + if err != nil { + fmt.Printf("Error reading directory: %s\n", err) + return nil + } + + for _, dir := range directories { + if dir.IsDir() && strings.HasPrefix(dir.Name(), os.Getenv("FILE_PREFIX")) { + files, err := ioutil.ReadDir(filepath.Join(path, dir.Name())) + if err != nil { + fmt.Printf("Error reading directory '%s': %s\n", filepath.Join(path, dir.Name()), err) + continue + } + + for _, file := range files { + if file.Name() == "makefile" || file.Name() == "docker-compose.yml" || file.Name() == "docker-compose.yaml" { + projects = append(projects, dir.Name()) + break + } + } + } + } + + return projects +} + +func startSpinner(stop chan bool) { + ticker := time.NewTicker(100 * time.Millisecond) + defer ticker.Stop() + + i := 0 + for { + select { + case <-stop: + fmt.Print("\r \r") // Clear the spinner + return + case <-ticker.C: + fmt.Printf("\r%s", cursors[i%len(cursors)]) + i++ + } + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..8e361e6 --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module llama-orc + +go 1.21.4 + +require ( + github.com/fatih/color v1.16.0 + github.com/spf13/cobra v1.8.0 +) + +require ( + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/joho/godotenv v1.5.1 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/spf13/pflag v1.0.5 // indirect + golang.org/x/sys v0.14.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..258be2e --- /dev/null +++ b/go.sum @@ -0,0 +1,31 @@ +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3 h1:ns/ykhmWi7G9O+8a448SecJU3nSMBXJfqQkl0upE1jI= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..0578253 --- /dev/null +++ b/main.go @@ -0,0 +1,17 @@ +/* +Copyright © 2024 NAME HERE +*/ +package main + +import ( + "fmt" + "llama-orc/cmd" +) + +var version string + +func main() { + //version number + fmt.Println("Llama Orc v" + version) + cmd.Execute() +} diff --git a/readme.md b/readme.md new file mode 100644 index 0000000..bea2faf --- /dev/null +++ b/readme.md @@ -0,0 +1,37 @@ +# {Insert Name Here} - Your Local Development Environment Orchestrator + +## Overview + +{Insert Name Here} is a powerful CLI tool designed to enhance your local development workflow. As a senior software engineer, this tool automates and simplifies the process of managing and orchestrating multiple projects, making your development process more efficient. + +## Features (To-Do List) +- [ ] **Auto-Detect Projects**: Automatically detect projects in your local environment. +- [ ] **Project Cloning**: Integrate with GitHub APIs to clone projects automatically. +- [ ] **Proxy Management**: Check if the local proxy is running and manage its status. +- [ ] **CLI Dashboard**: A user-friendly command-line interface for managing local projects. +- [ ] **Easy Project Management**: Simplify the process of taking down and running projects. +- [ ] **Deployment Simplification**: Deploy projects easily with integrated commands. +- [ ] **GitHub App Integration**: Use a GitHub app for better management of your GitHub account. +- [ ] **Environment Orchestration**: Orchestrate your development environment with deployment capabilities. + +## Getting Started +1. **Installation**: Ensure Go is installed on your system. Clone this repository and run `go build`. +2. **Basic Commands**(Coming Soon): + +## Project Detection and serving +- The `start.go` file checks your proxy status and manages it accordingly. +- It scans the specified directory for projects containing a Makefile or Docker Compose file. +- You can select a project from the list, with priority given to `Makefile`, otherwise using `docker compose up -d`. + +## Contribution +Your contributions are valuable! Feel free to fork, submit pull requests, or open issues for enhancements or bug fixes. + +--- + +_Note: This README is subject to updates as the project progresses._ + + +Dev notes/commands +build +```VERSION=$(git describe --tags)``` +```go build -ldflags="-X 'main.version=$VERSION'" -o your_binary_name``` \ No newline at end of file