-
Notifications
You must be signed in to change notification settings - Fork 94
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update readme and activate preview command
- Loading branch information
1 parent
5982d50
commit b99b61b
Showing
6 changed files
with
290 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,251 @@ | ||
/* | ||
Copyright © 2024 NAME HERE <EMAIL ADDRESS> | ||
*/ | ||
package cmd | ||
|
||
import ( | ||
"fmt" | ||
"log" | ||
"os" | ||
"os/exec" | ||
"strconv" | ||
"strings" | ||
"time" | ||
|
||
"github.com/mightymoud/sidekick/utils" | ||
"github.com/pterm/pterm" | ||
"github.com/spf13/cobra" | ||
"github.com/spf13/viper" | ||
"gopkg.in/yaml.v3" | ||
) | ||
|
||
// composeCmd represents the compose command | ||
var composeCmd = &cobra.Command{ | ||
Use: "compose", | ||
Short: "A brief description of your command", | ||
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: func(cmd *cobra.Command, args []string) { | ||
if configErr := utils.ViperInit(); configErr != nil { | ||
pterm.Error.Println("Sidekick config not found - Run sidekick init") | ||
os.Exit(1) | ||
} | ||
|
||
// keyAddSshCommand := exec.Command("sh", "-s", "-", viper.Get("serverAddress").(string)) | ||
// keyAddSshCommand.Stdin = strings.NewReader(utils.SshKeysScript) | ||
// if sshAddErr := keyAddSshCommand.Run(); sshAddErr != nil { | ||
// panic(sshAddErr) | ||
// } | ||
|
||
if utils.FileExists("./docker-compose.yaml") { | ||
pterm.Info.Println("Docker compose file detected - scanning file for details") | ||
} else { | ||
pterm.Error.Println("No Docker compose files found in current directory.") | ||
os.Exit(1) | ||
} | ||
pterm.Info.Println("Analyzing Compose file...") | ||
res, err := os.ReadFile("./docker-compose.yaml") | ||
if err != nil { | ||
pterm.Error.Println("Unable to process your compose file") | ||
} | ||
var compose utils.DockerComposeFile | ||
err = yaml.Unmarshal(res, &compose) | ||
if err != nil { | ||
log.Fatalf("error unmarshalling yaml: %v", err) | ||
} | ||
|
||
composeServices := []string{} | ||
for name := range compose.Services { | ||
composeServices = append(composeServices, name) | ||
} | ||
httpServiceName, _ := pterm.DefaultInteractiveSelect.WithOptions(composeServices).WithDefaultText("Please select which service in your docker compose file will recieve http requests").Show() | ||
|
||
otherServices := []string{} | ||
for _, v := range composeServices { | ||
if v != httpServiceName { | ||
otherServices = append(otherServices, v) | ||
} | ||
} | ||
|
||
// attempt to get a port from dockerfile | ||
appPort := "" | ||
appPortTextInput := pterm.DefaultInteractiveTextInput.WithDefaultValue(appPort) | ||
appPortTextInput.DefaultText = "Please enter the port at which the app receives requests" | ||
appPort, _ = appPortTextInput.Show() | ||
if appPort == "" { | ||
pterm.Error.Println("You you have to enter a port to accept requests") | ||
os.Exit(0) | ||
} | ||
|
||
appName := "" | ||
appNameTextInput := pterm.DefaultInteractiveTextInput | ||
appNameTextInput.DefaultText = "Please enter your app url friendly app name" | ||
appName, _ = appNameTextInput.Show() | ||
if appName == "" || strings.Contains(appName, " ") { | ||
pterm.Error.Println("You have to enter url friendly app name") | ||
os.Exit(0) | ||
} | ||
|
||
appDomain := "" | ||
appDomainTextInput := pterm.DefaultInteractiveTextInput.WithDefaultValue(fmt.Sprintf("%s.%s.sslip.io", appName, viper.Get("serverAddress").(string))) | ||
appDomainTextInput.DefaultText = "Please enter the domain to point the app to" | ||
appDomain, _ = appDomainTextInput.Show() | ||
|
||
envFileName := "" | ||
envFileNameTextInput := pterm.DefaultInteractiveTextInput.WithDefaultValue(".env") | ||
envFileNameTextInput.DefaultText = "Please enter which env file you would like to load" | ||
envFileName, _ = envFileNameTextInput.Show() | ||
|
||
hasEnvFile := false | ||
envVariables := []string{} | ||
dockerEnvProperty := []string{} | ||
envFileChecksum := "" | ||
if utils.FileExists(fmt.Sprintf("./%s", envFileName)) { | ||
hasEnvFile = true | ||
pterm.Info.Printfln("Env file detected - Loading env vars from %s", envFileName) | ||
res := utils.HandleEnvFile(envFileName, envVariables, dockerEnvProperty, &envFileChecksum) | ||
fmt.Println(res) | ||
fmt.Println(dockerEnvProperty) | ||
defer os.Remove("encrypted.env") | ||
} else { | ||
pterm.Info.Println("No env file detected - Skipping env parsing") | ||
} | ||
// make a docker service | ||
imageName := fmt.Sprintf("%s/%s", viper.Get("dockerUsername").(string), appName) | ||
currentService := compose.Services[httpServiceName] | ||
currentService.Image = imageName | ||
currentService.Labels = []string{ | ||
"traefik.enable=true", | ||
fmt.Sprintf("traefik.http.routers.%s.rule=Host(`%s`)", appName, appDomain), | ||
fmt.Sprintf("traefik.http.services.%s.loadbalancer.server.port=%s", appName, appPort), | ||
fmt.Sprintf("traefik.http.routers.%s.tls=true", appName), | ||
fmt.Sprintf("traefik.http.routers.%s.tls.certresolver=default", appName), | ||
"traefik.docker.network=sidekick", | ||
} | ||
currentService.Environment = dockerEnvProperty | ||
currentService.Networks = []string{ | ||
"sidekick", | ||
} | ||
currentService.DependsOn = otherServices | ||
compose.Services[httpServiceName] = currentService | ||
|
||
compose.Networks = | ||
map[string]utils.DockerNetwork{ | ||
"sidekick": { | ||
External: true, | ||
}, | ||
} | ||
|
||
dockerComposeFile, err := yaml.Marshal(&compose) | ||
if err != nil { | ||
fmt.Printf("Error marshalling YAML: %v\n", err) | ||
return | ||
} | ||
err = os.WriteFile("docker-compose.yaml", dockerComposeFile, 0644) | ||
if err != nil { | ||
fmt.Printf("Error writing file: %v\n", err) | ||
return | ||
} | ||
os.Exit(2) | ||
|
||
multi := pterm.DefaultMultiPrinter | ||
launchPb, _ := pterm.DefaultProgressbar.WithTotal(3).WithWriter(multi.NewWriter()).Start("Booting up app on VPS") | ||
loginSpinner, _ := utils.GetSpinner().WithWriter(multi.NewWriter()).Start("Logging into VPS") | ||
dockerBuildSpinner, _ := utils.GetSpinner().WithWriter(multi.NewWriter()).Start("Preparing docker image") | ||
setupSpinner, _ := utils.GetSpinner().WithWriter(multi.NewWriter()).Start("Setting up application") | ||
|
||
multi.Start() | ||
|
||
loginSpinner.Sequence = []string{"▀ ", " ▀", " ▄", "▄ "} | ||
sshClient, err := utils.Login(viper.Get("serverAddress").(string), "sidekick") | ||
if err != nil { | ||
loginSpinner.Fail("Something went wrong logging in to your VPS") | ||
panic(err) | ||
} | ||
loginSpinner.Success("Logged in successfully!") | ||
launchPb.Increment() | ||
|
||
dockerBuildSpinner.Sequence = []string{"▀ ", " ▀", " ▄", "▄ "} | ||
cwd, _ := os.Getwd() | ||
dockerBuildCommd := exec.Command("sh", "-s", "-", appName, viper.Get("dockerUsername").(string), cwd) | ||
dockerBuildCommd.Stdin = strings.NewReader(utils.DockerHandleScript) | ||
// better handle of errors -> Push it to another writer aside from os.stderr and then flush it when it panics | ||
if dockerBuildErr := dockerBuildCommd.Run(); dockerBuildErr != nil { | ||
log.Fatalln("Failed to run docker") | ||
os.Exit(1) | ||
} | ||
dockerBuildSpinner.Success("Successfully built and pushed docker image") | ||
launchPb.Increment() | ||
|
||
setupSpinner.Sequence = []string{"▀ ", " ▀", " ▄", "▄ "} | ||
_, sessionErr := utils.RunCommand(sshClient, fmt.Sprintf("mkdir %s", appName)) | ||
if sessionErr != nil { | ||
panic(sessionErr) | ||
} | ||
rsync := exec.Command("rsync", "docker-compose.yaml", fmt.Sprintf("%s@%s:%s", "sidekick", viper.Get("serverAddress").(string), fmt.Sprintf("./%s", appName))) | ||
rsync.Run() | ||
if hasEnvFile { | ||
encryptSync := exec.Command("rsync", "encrypted.env", fmt.Sprintf("%s@%s:%s", "sidekick", viper.Get("serverAddress").(string), fmt.Sprintf("./%s", appName))) | ||
encryptSync.Run() | ||
|
||
_, sessionErr1 := utils.RunCommand(sshClient, fmt.Sprintf(`cd %s && sops exec-env encrypted.env 'docker compose -p sidekick up -d'`, appName)) | ||
if sessionErr1 != nil { | ||
fmt.Println("something went wrong") | ||
} | ||
} else { | ||
_, sessionErr1 := utils.RunCommand(sshClient, fmt.Sprintf(`cd %s && docker compose -p sidekick up -d`, appName)) | ||
if sessionErr1 != nil { | ||
panic(sessionErr1) | ||
} | ||
} | ||
|
||
portNumber, err := strconv.ParseUint(appPort, 0, 64) | ||
if err != nil { | ||
panic(err) | ||
} | ||
envConfig := utils.SidekickAppEnvConfig{} | ||
if hasEnvFile { | ||
envConfig.File = envFileName | ||
envConfig.Hash = envFileChecksum | ||
} | ||
// save app config in same folder | ||
sidekickAppConfig := utils.SidekickAppConfig{ | ||
Name: appName, | ||
Version: "V1", | ||
Image: fmt.Sprintf("%s/%s", viper.Get("dockerUsername"), appName), | ||
Port: portNumber, | ||
Url: appDomain, | ||
CreatedAt: time.Now().Format(time.UnixDate), | ||
Env: envConfig, | ||
} | ||
ymlData, err := yaml.Marshal(&sidekickAppConfig) | ||
os.WriteFile("./sidekick.yml", ymlData, 0644) | ||
launchPb.Increment() | ||
|
||
setupSpinner.Success("🙌 App setup successfully 🙌") | ||
multi.Stop() | ||
|
||
pterm.Println() | ||
pterm.Info.Printfln("😎 Access your app at: https://%s", appDomain) | ||
pterm.Println() | ||
|
||
}, | ||
} | ||
|
||
func init() { | ||
rootCmd.AddCommand(composeCmd) | ||
|
||
// Here you will define your flags and configuration settings. | ||
|
||
// Cobra supports Persistent Flags which will work for this command | ||
// and all subcommands, e.g.: | ||
// composeCmd.PersistentFlags().String("foo", "", "A help for foo") | ||
|
||
// Cobra supports local flags which will only run when this command | ||
// is called directly, e.g.: | ||
// composeCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters