Skip to content

Commit

Permalink
Socket connection to provide ability to start different project build…
Browse files Browse the repository at this point in the history
… from script.
  • Loading branch information
tvrzna committed Dec 21, 2023
1 parent d74719f commit 5db25b2
Show file tree
Hide file tree
Showing 6 changed files with 188 additions and 3 deletions.
9 changes: 8 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@ var buildVersion string
const delimiter = "="

type Config struct {
client bool
port int
appUrl string
path string
name string
action socketAction
data string
}

func LoadConfig(args []string) *Config {
Expand All @@ -33,8 +36,12 @@ func LoadConfig(args []string) *Config {
c.appUrl = value
case "-n", "--name":
c.name = value
case "-sj", "--start-job":
c.client = true
c.action = socketActionStart
c.data = value
case "-h", "--help":
fmt.Printf("Usage: lurch [options]\nOptions:\n\t-h, --help\t\t\tprint this help\n\t-v, --version\t\t\tprint version\n\t-t, --path [PATH]\t\tabsolute path to work dir\n\t-p, --port [PORT]\t\tsets port for listening\n\t-a, --app-url [APP_URL]\t\tapplication url (if behind proxy)\n\t-n, --name [NAME]\t\tname of application to be displayed\n")
fmt.Printf("Usage: lurch [options]\nOptions:\n\t-h, --help\t\t\tprint this help\n\t-v, --version\t\t\tprint version\n\t-t, --path [PATH]\t\tabsolute path to work dir\n\t-p, --port [PORT]\t\tsets port for listening\n\t-a, --app-url [APP_URL]\t\tapplication url (if behind proxy)\n\t-n, --name [NAME]\t\tname of application to be displayed\n\t-sj, --start-job [PROJECT]\tmakes client call to origin server and starts the build of [PROJECT]\n")
os.Exit(0)
case "-v", "--version":
fmt.Printf("lurch %s\nhttps://github.com/tvrzna/lurch\n\nReleased under the MIT License.\n", c.GetVersion())
Expand Down
14 changes: 12 additions & 2 deletions context.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,12 @@ func (c *Context) start(b *Job) {
b.p.SetParams(b.params)
b.p.SaveParams()

c.setEnv(cmd, b)
socket := startServerSocket(c, b)
if socket != nil {
defer socket.stop()
}

c.setEnv(cmd, b, socket)
cmd.Dir = b.WorkspacePath()
output, err := os.OpenFile(b.OutputPath(), os.O_RDWR|os.O_APPEND|os.O_CREATE, 0644)
if err != nil {
Expand Down Expand Up @@ -135,7 +140,7 @@ func (c *Context) start(b *Job) {
log.Printf("<< finished job #%s for %s", b.name, b.p.name)
}

func (c *Context) setEnv(cmd *exec.Cmd, b *Job) {
func (c *Context) setEnv(cmd *exec.Cmd, b *Job, socket *socketServerContext) {
envs := os.Environ()

if b.params != nil {
Expand All @@ -144,6 +149,11 @@ func (c *Context) setEnv(cmd *exec.Cmd, b *Job) {
}
}

if socket != nil {
envs = append(envs, fmt.Sprintf("%s=%d", envSocketPort, socket.port))
envs = append(envs, fmt.Sprintf("%s=%s", envSocketToken, socket.token))
}

cmd.Env = envs
}

Expand Down
4 changes: 4 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import (
func main() {
c := NewContext(LoadConfig(os.Args))

if c.conf.client {
os.Exit(makeSocketClientAction(c.conf.action, c.conf.data))
}

go runWebServer(c)

handleStop(c)
Expand Down
16 changes: 16 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ Options:
-p, --port [PORT] sets port for listening
-a, --app-url [APP_URL] application url (if behind proxy)
-n, --name [NAME] name of application to be displayed
-sj, --start-job [PROJECT] makes client call to origin server and starts the build of [PROJECT]
```

## How to setup project
Expand All @@ -35,6 +36,20 @@ rm -rf .git/
```
4. Open lurch in browser and start the job.

### Start build from script of different project
Inside your project build script call lurch with parameter `-sj` followed by project name. If build is started, the result code of `lurch -sj` is `0`, otherwise it is `1`.

```bash
#!/bin/sh -e

git clone repository ./

make clean build

/usr/bin/lurch -sj repository-deploy

```

## Roadmap
- [x] Core (0.1.0)
- [x] REST API (0.1.0)
Expand All @@ -45,4 +60,5 @@ rm -rf .git/
- [X] Custom name of application (0.3.0)
- [ ] Periodical watcher (running custom script saving state of last check)
- [ ] Pipelining (jobs started according the result status)
- [X] Starting build from build script
- [X] Size and existance of artifact
136 changes: 136 additions & 0 deletions socket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
package main

import (
"bufio"
"fmt"
"log"
"net"
"os"
)

type socketAction byte
type socketResponse byte

const (
socketActionStart socketAction = iota + 1
)

const (
socketResponseOk socketResponse = iota
socketResponseFail
)

const (
envSocketPort = "LURCH_SOCKET_PORT"
envSocketToken = "LURCH_SOCKET_TOKEN"
)

type socketServerContext struct {
port int
c *Context
j *Job
l net.Listener
token string
}

type socketData struct {
token string
action socketAction
length int
data string
}

func (s *socketServerContext) parseSocketData(str string) *socketData {
result := &socketData{}
_, err := fmt.Sscanf(str, "%s %d %d %s", &result.token, &result.action, &result.length, &result.data)

if err != nil || len(result.data) != result.length || s.token != result.token {
return nil
}
return result
}

func (s *socketData) String() string {
s.length = len(s.data)
return fmt.Sprintf("%s %d %d %s", s.token, s.action, s.length, s.data)
}

func startServerSocket(c *Context, j *Job) *socketServerContext {
s := &socketServerContext{c: c, j: j, token: randomToken(32)}

var err error
s.l, err = net.Listen("tcp", "localhost:0")
if err != nil {
log.Print("could not start server socket for ", j.p.name, " #", j.name)
return nil
}

address := s.l.Addr().(*net.TCPAddr)
s.port = address.Port

go s.handle()

return s
}

func (s *socketServerContext) handle() {
for {
con, err := s.l.Accept()
if err != nil {
break
}

msg, err := bufio.NewReader(con).ReadString('\n')
if err != nil {
break
}
d := s.parseSocketData(msg)
if d == nil {
log.Print("incomming data are not valid")
con.Write([]byte{byte(socketResponseFail), '\n'})
con.Close()
continue
}

response := socketResponseFail

switch d.action {
case socketActionStart:
p := s.c.OpenProject(d.data)
if p != nil {
if r := s.c.StartJob(p, nil); r != "" {
response = socketResponseOk
}
}
}

con.Write([]byte{byte(response), '\n'})
con.Close()
}
}

func (s *socketServerContext) stop() {
s.l.Close()
}

func makeSocketClientAction(action socketAction, data string) int {
s := &socketData{action: action, data: data}
s.token = os.Getenv(envSocketToken)

con, err := net.Dial("tcp", fmt.Sprintf("localhost:%s", os.Getenv(envSocketPort)))
if err != nil {
log.Println(err)
return int(socketResponseFail)
}
defer con.Close()

con.Write([]byte(s.String() + "\n"))
res, err := bufio.NewReader(con).ReadString('\n')
if err != nil {
log.Println(err)
}

log.Print("result code: ", int(res[0]))

return int(res[0])
}
12 changes: 12 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"bufio"
"fmt"
"math/rand"
"os"
"regexp"
"strings"
Expand Down Expand Up @@ -61,3 +62,14 @@ func loadParams(path string) map[string]string {
}
return result
}

// Generates random token of defined length
func randomToken(n int) string {
var letterRunes = []rune("0123456789abcdefghijklmnopqrstuvwxyz")

b := make([]rune, n)
for i := range b {
b[i] = letterRunes[rand.Intn(len(letterRunes))]
}
return string(b)
}

0 comments on commit 5db25b2

Please sign in to comment.