blob: 6aa487c517dbf09b412fddf8b611f89527e36623 [file] [log] [blame]
package main
import (
"encoding/json"
"flag"
"fmt"
"log"
"net"
"os"
"os/signal"
"path/filepath"
"strings"
"syscall"
"time"
"golang.org/x/crypto/ssh"
"github.com/go-git/go-billy/v5/osfs"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/cache"
gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
"github.com/go-git/go-git/v5/storage/filesystem"
)
var port = flag.Int("port", 3000, "Port to listen on")
var appId = flag.String("app-id", "", "Application ID")
var service = flag.String("service", "", "Service name")
var agentMode = flag.Bool("agent-mode", false, "Sketch agent mode")
var repoAddr = flag.String("repo-addr", "", "Git repository address")
var branch = flag.String("branch", "", "Name of the branch to process")
var rootDir = flag.String("root-dir", "/", "Path to the app code")
var sshKey = flag.String("ssh-key", "", "Private SSH key to access Git repository")
var appDir = flag.String("app-dir", "", "Path to store application repository locally")
var runCfg = flag.String("run-cfg", "", "Run configuration")
var managerAddr = flag.String("manager-addr", "", "Address of the manager")
type Command struct {
Bin string `json:"bin"`
Args []string `json:"args"`
Env []string `json:"env"`
}
type Commit struct {
Hash string `json:"hash"`
Message string `json:"message"`
}
func CloneRepositoryBranch(addr, branch, rootDir string, signer ssh.Signer, path string) (*Commit, error) {
ref := fmt.Sprintf("refs/heads/%s", branch)
opts := &git.CloneOptions{
URL: addr,
RemoteName: "origin",
ReferenceName: plumbing.ReferenceName(ref),
SingleBranch: true,
Depth: 1,
InsecureSkipTLS: true,
Progress: os.Stdout,
}
if signer != nil {
opts.Auth = &gitssh.PublicKeys{
User: "git",
Signer: signer,
HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
// TODO(giolekva): verify server public key
fmt.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
return nil
},
},
}
}
repo, err := git.Clone(
filesystem.NewStorage(
osfs.New(filepath.Join(path, ".git"), osfs.WithBoundOS()),
cache.NewObjectLRUDefault(),
),
osfs.New(path, osfs.WithBoundOS()),
opts,
)
if err != nil {
return nil, err
}
head, err := repo.Head()
if err != nil {
return nil, err
}
commit, err := repo.CommitObject(head.Hash())
if err != nil {
return nil, err
}
return &Commit{
Hash: head.Hash().String(),
Message: commit.Message,
}, nil
}
func readRunConfiguration(p string) []Command {
r, err := os.Open(p)
if err != nil {
panic(err)
}
defer r.Close()
var cmds []Command
if err := json.NewDecoder(r).Decode(&cmds); err != nil {
log.Fatal(err)
}
return cmds
}
func main() {
flag.Parse()
self, ok := os.LookupEnv("SELF_IP")
if !ok {
panic("no SELF_IP")
}
id, ok := os.LookupEnv("SELF_ID")
if !ok {
panic("no SELF_ID")
}
var signer ssh.Signer
// TODO(gio): revisit this logic
if *sshKey != "" && !(strings.HasPrefix(*repoAddr, "http://") || strings.HasPrefix(*repoAddr, "https://")) {
key, err := os.ReadFile(*sshKey)
if err != nil {
panic(err)
}
signer, err = ssh.ParsePrivateKey(key)
if err != nil {
panic(err)
}
}
if !*agentMode {
if err := os.Mkdir(*appDir, os.ModePerm); err != nil {
panic(err)
}
}
cmds := readRunConfiguration(*runCfg)
s := NewServer(*agentMode, *port, *appId, *service, id, *repoAddr, *branch, *rootDir, signer, *appDir, cmds, self, *managerAddr)
go func() {
if err := s.Start(); err != nil {
log.Fatal(err)
} else {
log.Println("Done")
os.Exit(0)
}
}()
go func() {
for {
time.Sleep(30 * time.Second)
newCmds := readRunConfiguration(*runCfg)
if commandsChanged(cmds, newCmds) {
s.UpdateRunCommands(newCmds)
cmds = newCmds
}
}
}()
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
<-sigChan
s.Stop()
}
func commandsChanged(a, b []Command) bool {
if len(a) != len(b) {
return true
}
for i, x := range a {
y := b[i]
if x.Bin != y.Bin {
return true
}
if len(x.Args) != len(y.Args) {
return true
}
for j, k := range x.Args {
l := y.Args[j]
if k != l {
return true
}
}
if !*agentMode {
if len(x.Env) != len(y.Env) {
return true
}
for j, k := range x.Env {
l := y.Env[j]
if k != l {
return true
}
}
}
}
return false
}