package main
import (
"bufio"
"bytes"
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"path"
"time"
errors "git.sequentialread.com/forest/pkg-errors"
"golang.org/x/sys/unix"
)
type IngressService struct {
BaseHTTPService
Backend * BackendApp
Model * DBModel
DaemonCommand * exec . Cmd
DaemonIsRunning bool
AdminTenantId int
FrontendPort int
}
type GUITunnel struct {
Protocol string ` json:"protocol" `
HasSubdomain bool ` json:"has_subdomain" `
Subdomain string ` json:"subdomain" `
Domain string ` json:"domain" `
DestinationType string ` json:"destination_type" `
DestinationPort int ` json:"destination_port" `
}
type GreenhouseDaemonStatus struct {
NeedsAPIToken bool ` json:"needs_api_token" `
HashedToken string ` json:"hashed_api_token" `
ApplyConfigStatuses [ ] string ` json:"apply_config_statuses" `
ApplyConfigStatusIndex int ` json:"apply_config_status_index" `
ApplyConfigStatusError string ` json:"apply_config_status_error" `
}
const adminThresholdNodeId = "greenhouse-internal-node"
const greenhouseExternalDomain = "greenhouse-alpha.server.garden"
func NewIngressService ( config * Config , backend * BackendApp , model * DBModel ) * IngressService {
toReturn := & IngressService {
Model : model ,
Backend : backend ,
AdminTenantId : config . AdminTenantId ,
FrontendPort : config . FrontendPort ,
}
toReturn . ClientFactory = func ( ) ( * http . Client , * time . Time , error ) {
// 99 years aka forever, never expire
expiryTime := time . Now ( ) . Add ( time . Hour * 24 * 30 * 12 * 99 )
return & http . Client {
Transport : CreateUnixTransport ( "/var/run/greenhouse-daemon.sock" ) ,
Timeout : 10 * time . Second ,
} , & expiryTime , nil
}
return toReturn
}
func ( service * IngressService ) StartGreenhouseDaemon ( ) error {
if service . DaemonIsRunning {
return errors . New ( "Daemon is already Running " )
}
cwd , err := os . Getwd ( )
if err != nil {
return err
}
greenhouseDaemonPath := path . Join ( cwd , "greenhouse-daemon" )
command := exec . Command ( path . Join ( greenhouseDaemonPath , "greenhouse-daemon" ) )
env := os . Environ ( )
env = append ( env , fmt . Sprintf ( "GREENHOUSE_DAEMON_PATH=%s" , greenhouseDaemonPath ) )
env = append ( env , "GREENHOUSE_DAEMON_USE_UNIX_SOCKETS=true" )
env = append ( env , fmt . Sprintf ( "GREENHOUSE_DAEMON_CLOUD_URL=http://localhost:%d" , service . FrontendPort ) )
command . Env = env
command . Dir = greenhouseDaemonPath
stdoutReader , err := command . StdoutPipe ( )
if err != nil {
log . Printf ( "can't Start greenhouse-daemon, command.StdoutPipe() returned %s" , err )
return err
}
stdoutScanner := bufio . NewScanner ( stdoutReader )
stdoutScanner . Split ( bufio . ScanLines )
go ( func ( ) {
for stdoutScanner . Scan ( ) {
log . Printf ( "[greenhouse-daemon stdout] %s \n" , stdoutScanner . Text ( ) )
}
} ) ( )
stderrReader , err := command . StderrPipe ( )
if err != nil {
log . Printf ( "can't Start(greenhouse-daemon), command.StdoutPipe() returned %s" , err )
return err
}
stderrScanner := bufio . NewScanner ( stderrReader )
stderrScanner . Split ( bufio . ScanLines )
go ( func ( ) {
for stderrScanner . Scan ( ) {
log . Printf ( "[greenhouse-daemon stderr] %s \n" , stderrScanner . Text ( ) )
}
} ) ( )
err = command . Start ( )
if err != nil {
log . Printf ( "can't Start greenhouse-daemon, command.Start() returned %s" , err )
return err
}
log . Printf ( "started greenhouse-daemon with PID %d\n" , command . Process . Pid )
service . DaemonCommand = command
service . DaemonIsRunning = true
go ( func ( ) {
err := command . Wait ( )
service . DaemonIsRunning = false
if err != nil {
log . Printf ( "command.Wait() returned '%s' for greenhouse-daemon child process" , err )
}
log . Printf ( "greenhouse-daemon child process ended with exit code %d" , command . ProcessState . ExitCode ( ) )
} ) ( )
return nil
}
func ( service * IngressService ) StopGreenhouseDaemon ( ) error {
if service . DaemonCommand . Process != nil {
service . DaemonCommand . Process . Signal ( unix . SIGTERM )
service . DaemonIsRunning = false
return nil
} else {
return errors . New ( "daemon Process is nil" )
}
}
func ( service * IngressService ) GetGreenhouseDaemonProcessRunning ( ) bool {
hasProcessState := service . DaemonCommand != nil && service . DaemonCommand . ProcessState != nil
hasProcess := service . DaemonCommand != nil && service . DaemonCommand . Process != nil
appearsToHaveExited := hasProcessState && ( service . DaemonCommand . ProcessState . Exited ( ) || service . DaemonCommand . ProcessState . ExitCode ( ) == - 1 )
appearsToHavePid := hasProcess && service . DaemonCommand . Process . Pid != 0
return appearsToHavePid && ! appearsToHaveExited
}
func ( service * IngressService ) GetGreenhouseDaemonStatus ( ) ( * GreenhouseDaemonStatus , error ) {
responseBytes , err := service . MyHTTP200 ( "GET" , "http://unix/status" , nil , nil )
if err != nil {
return nil , err
}
var responseStatus GreenhouseDaemonStatus
err = json . Unmarshal ( responseBytes , & responseStatus )
if err != nil {
return nil , err
}
return & responseStatus , nil
}
func ( service * IngressService ) ConfigureGreenhouseDaemon ( ) error {
responseStatus , err := service . GetGreenhouseDaemonStatus ( )
if err != nil {
return err
}
tenant , err := service . Model . GetTenant ( service . AdminTenantId )
if err != nil {
return err
}
hasMatchingAPIToken := false
for _ , token := range tenant . APITokens {
if responseStatus . HashedToken == token . HashedToken {
hasMatchingAPIToken = true
}
}
if responseStatus . NeedsAPIToken || ! hasMatchingAPIToken {
log . Printf (
"responseStatus.NeedsAPIToken (%t) || !hasMatchingAPIToken (%t): now reconfiguring the greenhouse daemon's API token...\n" ,
responseStatus . NeedsAPIToken , ! hasMatchingAPIToken ,
)
newTokenName := "greenhouse_builtin_ingress"
apiToken , _ , err := service . Backend . CreateAPIToken ( service . AdminTenantId , newTokenName )
if err != nil {
return err
}
_ , err = service . MyHTTP200 (
"POST" ,
fmt . Sprintf ( "http://unix/register?serverName=%s" , adminThresholdNodeId ) ,
nil ,
func ( request * http . Request ) {
request . Header . Set ( "Authorization" , fmt . Sprintf ( "Bearer %s" , apiToken ) )
} ,
)
if err != nil {
return err
}
}
greenhouseGUITunnels := [ ] GUITunnel {
{
Protocol : "https" ,
HasSubdomain : false ,
Domain : "greenhouse-alpha.server.garden" ,
DestinationType : "local_port" ,
DestinationPort : service . FrontendPort ,
} ,
}
tunnelsBytes , err := json . Marshal ( greenhouseGUITunnels )
if err != nil {
return err
}
_ , err = service . MyHTTP200 (
"POST" ,
"http://unix/apply_config" ,
bytes . NewBuffer ( tunnelsBytes ) ,
nil ,
)
if err != nil {
return err
}
return nil
}