Files
hatchet/pkg/client/loader/loader.go
abelanger5 584ba47044 feat: hatchet cli (#2701)
Creates a new CLI for Hatchet! 

- `hatchet profile [add|remove|list|show|update]` - allows managing multiple profiles, like `local` and `cloud`
- `hatchet server [start|stop]` - manages a Hatchet instance locally via docker
- `hatchet quickstart` - generates quickstart directory, similar to `create-react-app`  
- `hatchet worker dev` - creates a filewatcher for a Hatchet instance and automatically injects profile token from a selected profile. Uses a `hatchet.yaml` file in the current directory 
- `hatchet worker run` - runs a script for triggering a workflow using `hatchet.yaml`
- `hatchet tui` - creates the TUI interface
2026-01-13 13:35:43 -05:00

160 lines
4.1 KiB
Go

package loader
import (
"fmt"
"net/url"
"path/filepath"
"strings"
"github.com/hatchet-dev/hatchet/pkg/config/client"
"github.com/hatchet-dev/hatchet/pkg/config/loader/loaderutils"
)
type ConfigLoader struct {
directory string
}
// LoadClientConfig loads the client configuration
func (c *ConfigLoader) LoadClientConfig(token *string) (res *client.ClientConfig, err error) {
sharedFilePath := filepath.Join(c.directory, "client.yaml")
configFileBytes, err := loaderutils.GetConfigBytes(sharedFilePath)
if err != nil {
return nil, err
}
cf, err := LoadClientConfigFile(configFileBytes...)
if err != nil {
return nil, err
}
if token != nil {
cf.Token = *token
}
return GetClientConfigFromConfigFile(token, cf)
}
// LoadClientConfigFile loads the worker config file via viper
func LoadClientConfigFile(files ...[]byte) (*client.ClientConfigFile, error) {
configFile := &client.ClientConfigFile{}
f := client.BindAllEnv
_, err := loaderutils.LoadConfigFromViper(f, configFile, files...)
return configFile, err
}
func GetClientConfigFromConfigFile(tokenOverride *string, cf *client.ClientConfigFile) (res *client.ClientConfig, err error) {
f := client.BindAllEnv
_, err = loaderutils.LoadConfigFromViper(f, cf)
if err != nil {
return nil, fmt.Errorf("could not load config from viper: %w", err)
}
if tokenOverride != nil {
cf.Token = *tokenOverride
}
// if token is empty, throw an error
if cf.Token == "" {
return nil, fmt.Errorf("API token is required. Set it via the HATCHET_CLIENT_TOKEN environment variable")
}
grpcBroadcastAddress := cf.HostPort
serverURL := cf.ServerURL
tokenConf, err := loaderutils.GetConfFromJWT(cf.Token)
if err != nil {
return nil, err
}
if grpcBroadcastAddress == "" && tokenConf.GrpcBroadcastAddress != "" {
grpcBroadcastAddress = tokenConf.GrpcBroadcastAddress
}
if serverURL == "" && tokenConf.ServerURL != "" {
serverURL = tokenConf.ServerURL
}
// if there's no broadcast address at this point, throw an error
if grpcBroadcastAddress == "" {
return nil, fmt.Errorf("gRPC broadcast address is required. Set it via the HATCHET_CLIENT_HOST_PORT environment variable")
}
// if there's no server URL at this point, throw an error
if serverURL == "" {
return nil, fmt.Errorf("server URL is required. Set it via the HATCHET_CLIENT_SERVER_URL environment variable")
}
if cf.TenantId == "" {
cf.TenantId = tokenConf.TenantId
}
tlsServerName := cf.TLS.TLSServerName
// if the tls server name is empty, parse the domain from the host:port
if tlsServerName == "" {
// parse the domain from the host:port
domain, err := parseDomain(grpcBroadcastAddress)
if err != nil {
return nil, fmt.Errorf("could not parse domain: %w", err)
}
tlsServerName = domain.Hostname()
}
tlsConf, err := loaderutils.LoadClientTLSConfig(&cf.TLS, tlsServerName)
if err != nil {
return nil, fmt.Errorf("could not load TLS config: %w", err)
}
namespace := cf.Namespace
if namespace != "" {
namespace = strings.ToLower(namespace + "_")
}
var rawRunnableActions []string
if cf.RawRunnableActions != nil {
rawRunnableActions = []string{}
for _, action := range cf.RawRunnableActions {
rawRunnableActions = append(rawRunnableActions, namespace+strings.TrimSpace(action))
}
}
presetLabels := make(map[string]string)
if cf.AutoscalingTarget != "" {
presetLabels["hatchet-autoscaling-target"] = cf.AutoscalingTarget
}
return &client.ClientConfig{
TenantId: cf.TenantId,
TLSConfig: tlsConf,
Token: cf.Token,
ServerURL: serverURL,
GRPCBroadcastAddress: grpcBroadcastAddress,
Namespace: namespace,
CloudRegisterID: cf.CloudRegisterID,
RunnableActions: rawRunnableActions,
NoGrpcRetry: cf.NoGrpcRetry,
PresetWorkerLabels: presetLabels,
DisableGzipCompression: cf.DisableGzipCompression,
}, nil
}
func parseDomain(domain string) (*url.URL, error) {
if !strings.HasPrefix(domain, "http://") && !strings.HasPrefix(domain, "https://") {
domain = "https://" + domain
}
return url.Parse(domain)
}