blob: 0449ca5e6024743aaba86facdf9bcb8fbef58743 [file] [log] [blame]
gio0eaf2712024-04-14 13:08:46 +04001package main
2
3import (
4 "encoding/json"
5 "flag"
6 "fmt"
gio5e4d1a72024-10-09 15:25:29 +04007 "log"
gio0eaf2712024-04-14 13:08:46 +04008 "net"
9 "os"
gio24d6e9a2025-06-24 15:02:29 +040010 "path/filepath"
gioa421b062025-04-21 09:45:04 +040011 "strings"
gio0eaf2712024-04-14 13:08:46 +040012
13 "golang.org/x/crypto/ssh"
14
15 "github.com/go-git/go-billy/v5/osfs"
16 "github.com/go-git/go-git/v5"
gio2b1157a2024-10-24 08:45:07 +040017 "github.com/go-git/go-git/v5/plumbing"
gio24d6e9a2025-06-24 15:02:29 +040018 "github.com/go-git/go-git/v5/plumbing/cache"
gio0eaf2712024-04-14 13:08:46 +040019 gitssh "github.com/go-git/go-git/v5/plumbing/transport/ssh"
gio24d6e9a2025-06-24 15:02:29 +040020 "github.com/go-git/go-git/v5/storage/filesystem"
gio0eaf2712024-04-14 13:08:46 +040021)
22
23var port = flag.Int("port", 3000, "Port to listen on")
gio266c04f2024-07-03 14:18:45 +040024var appId = flag.String("app-id", "", "Application ID")
giob87415c2025-05-08 22:32:11 +040025var service = flag.String("service", "", "Service name")
gioe65d9a92025-06-19 09:02:32 +040026var agentMode = flag.Bool("agent-mode", false, "Sketch agent mode")
gio0eaf2712024-04-14 13:08:46 +040027var repoAddr = flag.String("repo-addr", "", "Git repository address")
gio2b1157a2024-10-24 08:45:07 +040028var branch = flag.String("branch", "", "Name of the branch to process")
giofc441e32024-11-11 16:26:14 +040029var rootDir = flag.String("root-dir", "/", "Path to the app code")
gio0eaf2712024-04-14 13:08:46 +040030var sshKey = flag.String("ssh-key", "", "Private SSH key to access Git repository")
31var appDir = flag.String("app-dir", "", "Path to store application repository locally")
32var runCfg = flag.String("run-cfg", "", "Run configuration")
gioa60f0de2024-07-08 10:49:48 +040033var managerAddr = flag.String("manager-addr", "", "Address of the manager")
gio0eaf2712024-04-14 13:08:46 +040034
35type Command struct {
36 Bin string `json:"bin"`
37 Args []string `json:"args"`
gio1364e432024-06-29 11:39:18 +040038 Env []string `json:"env"`
gio0eaf2712024-04-14 13:08:46 +040039}
40
gio9635ccb2025-05-22 08:33:38 +040041type Commit struct {
42 Hash string `json:"hash"`
43 Message string `json:"message"`
44}
45
46func CloneRepositoryBranch(addr, branch, rootDir string, signer ssh.Signer, path string) (*Commit, error) {
gio2b1157a2024-10-24 08:45:07 +040047 ref := fmt.Sprintf("refs/heads/%s", branch)
giofc441e32024-11-11 16:26:14 +040048 opts := &git.CloneOptions{
49 URL: addr,
50 RemoteName: "origin",
51 ReferenceName: plumbing.ReferenceName(ref),
52 SingleBranch: true,
53 Depth: 1,
54 InsecureSkipTLS: true,
55 Progress: os.Stdout,
56 }
57 if signer != nil {
58 opts.Auth = &gitssh.PublicKeys{
gio0eaf2712024-04-14 13:08:46 +040059 User: "git",
60 Signer: signer,
61 HostKeyCallbackHelper: gitssh.HostKeyCallbackHelper{
62 HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
63 // TODO(giolekva): verify server public key
64 fmt.Printf("--- %+v\n", ssh.MarshalAuthorizedKey(key))
65 return nil
66 },
67 },
giofc441e32024-11-11 16:26:14 +040068 }
69 }
gio24d6e9a2025-06-24 15:02:29 +040070 repo, err := git.Clone(
71 filesystem.NewStorage(
72 osfs.New(filepath.Join(path, ".git"), osfs.WithBoundOS()),
73 cache.NewObjectLRUDefault(),
74 ),
75 osfs.New(path, osfs.WithBoundOS()),
76 opts,
77 )
giof5ffedb2024-06-19 14:14:43 +040078 if err != nil {
gio9635ccb2025-05-22 08:33:38 +040079 return nil, err
giof5ffedb2024-06-19 14:14:43 +040080 }
gio9635ccb2025-05-22 08:33:38 +040081 head, err := repo.Head()
giof5ffedb2024-06-19 14:14:43 +040082 if err != nil {
gio9635ccb2025-05-22 08:33:38 +040083 return nil, err
giof5ffedb2024-06-19 14:14:43 +040084 }
gio9635ccb2025-05-22 08:33:38 +040085 commit, err := repo.CommitObject(head.Hash())
86 if err != nil {
87 return nil, err
88 }
89 return &Commit{
90 Hash: head.Hash().String(),
91 Message: commit.Message,
92 }, nil
gio0eaf2712024-04-14 13:08:46 +040093}
94
95func main() {
96 flag.Parse()
97 self, ok := os.LookupEnv("SELF_IP")
98 if !ok {
99 panic("no SELF_IP")
100 }
gio5155c1a2025-05-21 15:36:21 +0400101 id, ok := os.LookupEnv("SELF_ID")
102 if !ok {
103 panic("no SELF_ID")
104 }
giofc441e32024-11-11 16:26:14 +0400105 var signer ssh.Signer
gioa421b062025-04-21 09:45:04 +0400106 // TODO(gio): revisit this logic
107 if *sshKey != "" && !(strings.HasPrefix(*repoAddr, "http://") || strings.HasPrefix(*repoAddr, "https://")) {
giofc441e32024-11-11 16:26:14 +0400108 key, err := os.ReadFile(*sshKey)
109 if err != nil {
110 panic(err)
111 }
112 signer, err = ssh.ParsePrivateKey(key)
113 if err != nil {
114 panic(err)
115 }
gio0eaf2712024-04-14 13:08:46 +0400116 }
gioe65d9a92025-06-19 09:02:32 +0400117 if !*agentMode {
118 if err := os.Mkdir(*appDir, os.ModePerm); err != nil {
119 panic(err)
120 }
gio0eaf2712024-04-14 13:08:46 +0400121 }
122 r, err := os.Open(*runCfg)
123 if err != nil {
124 panic(err)
125 }
126 defer r.Close()
127 var cmds []Command
128 if err := json.NewDecoder(r).Decode(&cmds); err != nil {
129 panic(err)
130 }
gioe65d9a92025-06-19 09:02:32 +0400131 s := NewServer(*agentMode, *port, *appId, *service, id, *repoAddr, *branch, *rootDir, signer, *appDir, cmds, self, *managerAddr)
gio5e4d1a72024-10-09 15:25:29 +0400132 if err := s.Start(); err != nil {
133 log.Fatal(err)
134 }
gio0eaf2712024-04-14 13:08:46 +0400135}