mirror of
https://github.com/hatchet-dev/hatchet.git
synced 2026-02-14 04:09:11 -06:00
342 lines
7.5 KiB
Go
342 lines
7.5 KiB
Go
package cli
|
|
|
|
import (
|
|
_ "embed"
|
|
|
|
"fmt"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strings"
|
|
|
|
"github.com/fatih/color"
|
|
"sigs.k8s.io/yaml"
|
|
|
|
"github.com/hatchet-dev/hatchet/internal/config/database"
|
|
"github.com/hatchet-dev/hatchet/internal/config/loader"
|
|
"github.com/hatchet-dev/hatchet/internal/config/server"
|
|
"github.com/hatchet-dev/hatchet/internal/encryption"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
var certDir string
|
|
var generatedConfigDir string
|
|
var skip []string
|
|
var overwrite bool
|
|
|
|
const (
|
|
StageCerts string = "certs"
|
|
StageKeys string = "keys"
|
|
StageSeed string = "seed"
|
|
)
|
|
|
|
var quickstartCmd = &cobra.Command{
|
|
Use: "quickstart",
|
|
Short: "Command used to setup a Hatchet instance",
|
|
Run: func(cmd *cobra.Command, args []string) {
|
|
err := runQuickstart()
|
|
|
|
if err != nil {
|
|
red := color.New(color.FgRed)
|
|
red.Printf("Error running [%s]:%s\n", cmd.Use, err.Error())
|
|
os.Exit(1)
|
|
}
|
|
},
|
|
}
|
|
|
|
func init() {
|
|
rootCmd.AddCommand(quickstartCmd)
|
|
|
|
quickstartCmd.PersistentFlags().StringVar(
|
|
&certDir,
|
|
"cert-dir",
|
|
"./certs",
|
|
"path to the directory where certificates should be stored",
|
|
)
|
|
|
|
quickstartCmd.PersistentFlags().StringVar(
|
|
&generatedConfigDir,
|
|
"generated-config-dir",
|
|
"./generated",
|
|
"path to the directory where the generated config should be written",
|
|
)
|
|
|
|
quickstartCmd.PersistentFlags().StringArrayVar(
|
|
&skip,
|
|
"skip",
|
|
[]string{},
|
|
"a list of steps to skip. possible values are \"certs\"",
|
|
)
|
|
|
|
quickstartCmd.PersistentFlags().BoolVar(
|
|
&overwrite,
|
|
"overwrite",
|
|
true,
|
|
"whether generated files should be overwritten, if they exist",
|
|
)
|
|
}
|
|
|
|
func runQuickstart() error {
|
|
generated, err := loadBaseConfigFiles()
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("could not get base config files: %w", err)
|
|
}
|
|
|
|
if !shouldSkip(StageCerts) {
|
|
err := setupCerts(generated)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("could not setup certs: %w", err)
|
|
}
|
|
}
|
|
|
|
if !shouldSkip(StageKeys) {
|
|
err := generateKeys(generated)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("could not generate keys: %w", err)
|
|
}
|
|
}
|
|
|
|
err = writeGeneratedConfig(generated)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("could not write generated config files: %w", err)
|
|
}
|
|
|
|
if !shouldSkip(StageSeed) {
|
|
// reload config at this point
|
|
configLoader := loader.NewConfigLoader(configDirectory)
|
|
err = runSeed(configLoader)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("could not run seed: %w", err)
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func shouldSkip(stage string) bool {
|
|
for _, skipStage := range skip {
|
|
if stage == skipStage {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
//go:embed certs/cluster-cert.conf
|
|
var ClusterCertConf []byte
|
|
|
|
//go:embed certs/internal-admin-client-cert.conf
|
|
var InternalAdminClientCertConf []byte
|
|
|
|
//go:embed certs/worker-client-cert.conf
|
|
var WorkerClientCertConf []byte
|
|
|
|
//go:embed certs/generate-certs.sh
|
|
var GenerateCertsScript string
|
|
|
|
type generatedConfigFiles struct {
|
|
sc *server.ServerConfigFile
|
|
dc *database.ConfigFile
|
|
}
|
|
|
|
func setupCerts(generated *generatedConfigFiles) error {
|
|
color.New(color.FgGreen).Printf("Generating certificates in cert directory %s\n", certDir)
|
|
|
|
// verify that bash and openssl are installed on the system
|
|
if !commandExists("openssl") {
|
|
return fmt.Errorf("openssl must be installed and available in your $PATH")
|
|
}
|
|
|
|
if !commandExists("bash") {
|
|
return fmt.Errorf("bash must be installed and available in your $PATH")
|
|
}
|
|
|
|
// write certificate config files to system
|
|
fullPathCertDir, err := filepath.Abs(certDir)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = os.MkdirAll(fullPathCertDir, os.ModePerm)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("could not create cert directory: %w", err)
|
|
}
|
|
|
|
err = os.WriteFile(filepath.Join(fullPathCertDir, "./cluster-cert.conf"), ClusterCertConf, 0666)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("could not create cluster-cert.conf file: %w", err)
|
|
}
|
|
|
|
err = os.WriteFile(filepath.Join(fullPathCertDir, "./internal-admin-client-cert.conf"), InternalAdminClientCertConf, 0666)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("could not create internal-admin-client-cert.conf file: %w", err)
|
|
}
|
|
|
|
err = os.WriteFile(filepath.Join(fullPathCertDir, "./worker-client-cert.conf"), WorkerClientCertConf, 0666)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("could not create worker-client-cert.conf file: %w", err)
|
|
}
|
|
|
|
// if CA files don't exists, run the script to regenerate all certs
|
|
if overwrite || (!fileExists(filepath.Join(fullPathCertDir, "./ca.key")) || !fileExists(filepath.Join(fullPathCertDir, "./ca.cert"))) {
|
|
// run openssl commands
|
|
c := exec.Command("bash", "-s", "-", fullPathCertDir)
|
|
|
|
c.Stdin = strings.NewReader(GenerateCertsScript)
|
|
c.Stdout = os.Stdout
|
|
c.Stderr = os.Stderr
|
|
|
|
err = c.Run()
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
generated.sc.TLS.TLSRootCAFile = filepath.Join(fullPathCertDir, "ca.cert")
|
|
generated.sc.TLS.TLSCertFile = filepath.Join(fullPathCertDir, "cluster.pem")
|
|
generated.sc.TLS.TLSKeyFile = filepath.Join(fullPathCertDir, "cluster.key")
|
|
|
|
return nil
|
|
}
|
|
|
|
func generateKeys(generated *generatedConfigFiles) error {
|
|
color.New(color.FgGreen).Printf("Generating encryption keys for Hatchet server\n")
|
|
|
|
cookieHashKey, err := encryption.GenerateRandomBytes(8)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("could not generate hash key for instance: %w", err)
|
|
}
|
|
|
|
cookieBlockKey, err := encryption.GenerateRandomBytes(8)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("could not generate block key for instance: %w", err)
|
|
}
|
|
|
|
if overwrite || (generated.sc.Auth.Cookie.Secrets == "") {
|
|
generated.sc.Auth.Cookie.Secrets = fmt.Sprintf("%s %s", cookieHashKey, cookieBlockKey)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func fileExists(filename string) bool {
|
|
info, err := os.Stat(filename)
|
|
if os.IsNotExist(err) {
|
|
return false
|
|
}
|
|
return !info.IsDir()
|
|
}
|
|
|
|
func commandExists(cmd string) bool {
|
|
_, err := exec.LookPath(cmd)
|
|
return err == nil
|
|
}
|
|
|
|
func loadBaseConfigFiles() (*generatedConfigFiles, error) {
|
|
res := &generatedConfigFiles{}
|
|
var err error
|
|
|
|
res.dc, err = loader.LoadDatabaseConfigFile(getFiles("database.yaml")...)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
res.sc, err = loader.LoadServerConfigFile(getFiles("server.yaml")...)
|
|
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func shouldWriteConfig(conf string) bool {
|
|
return overwrite || conf == ""
|
|
}
|
|
|
|
func getFiles(name string) [][]byte {
|
|
files := [][]byte{}
|
|
|
|
basePath := filepath.Join(configDirectory, name)
|
|
|
|
if fileExists(basePath) {
|
|
configFileBytes, err := os.ReadFile(basePath)
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
files = append(files, configFileBytes)
|
|
}
|
|
|
|
generatedPath := filepath.Join(generatedConfigDir, name)
|
|
|
|
if fileExists(generatedPath) {
|
|
generatedFileBytes, err := os.ReadFile(filepath.Join(generatedConfigDir, name))
|
|
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
|
|
files = append(files, generatedFileBytes)
|
|
}
|
|
|
|
return files
|
|
}
|
|
|
|
func writeGeneratedConfig(generated *generatedConfigFiles) error {
|
|
color.New(color.FgGreen).Printf("Generating config files %s\n", generatedConfigDir)
|
|
|
|
err := os.MkdirAll(generatedConfigDir, os.ModePerm)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("could not create generated config directory: %w", err)
|
|
}
|
|
|
|
databasePath := filepath.Join(generatedConfigDir, "./database.yaml")
|
|
|
|
databaseConfigBytes, err := yaml.Marshal(generated.dc)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = os.WriteFile(databasePath, databaseConfigBytes, 0666)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("could not write database.yaml file: %w", err)
|
|
}
|
|
|
|
serverPath := filepath.Join(generatedConfigDir, "./server.yaml")
|
|
|
|
serverConfigBytes, err := yaml.Marshal(generated.sc)
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = os.WriteFile(serverPath, serverConfigBytes, 0666)
|
|
|
|
if err != nil {
|
|
return fmt.Errorf("could not write server.yaml file: %w", err)
|
|
}
|
|
|
|
return nil
|
|
}
|