Skip to content

Commit

Permalink
Merge pull request #9 from datasparq-ai/release/v0.5.1
Browse files Browse the repository at this point in the history
Release v0.5.1
  • Loading branch information
matt-sparq authored Oct 17, 2023
2 parents 230ed1b + bb3907e commit 847bb9d
Show file tree
Hide file tree
Showing 13 changed files with 406 additions and 133 deletions.
31 changes: 31 additions & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
name: goreleaser

on:
push:
tags:
- '*'

permissions:
contents: write

jobs:
goreleaser:
runs-on: ubuntu-latest
steps:
-
name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
-
name: Set up Go
uses: actions/setup-go@v4
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v5
with:
distribution: goreleaser
version: latest
args: release --clean
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
90 changes: 24 additions & 66 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,97 +1,55 @@

# Houston

Houston is an open source, API based workflow orchestration tool.
Open source, API based workflow orchestration tool.

Documentation: [./docs](./docs/README.md)
Homepage: [callhouston.io](https://callhouston.io)
![Houston Flowchart](https://storage.googleapis.com/houston-static/images/houston-flowchart.gif)

- Homepage: [callhouston.io](https://callhouston.io)
- Quickstart guide: [houston-quickstart-python](https://github.com/datasparq-intelligent-products/houston-quickstart-python)
- Docs: [./docs](./docs/README.md)

This repo contains the API server, go client, and CLI.

### Install

If you have [go](https://golang.org/doc/install) installed you can install with:
### Example Usage

```bash
go install github.com/datasparq-ai/houston
```
![Houston CLI](https://storage.googleapis.com/houston-static/images/houston-cli.gif)

### Example Usage / Quickstart (1 minute)
Start a local server with the default config: `houston api`

Use `houston demo` to quickly run an end-to-end example workflow:
Quickly run an end-to-end example workflow: `houston demo`

```bash
houston demo
```
Or use the Docker container: `docker run -p 8000:8000 datasparq/houston-redis demo`

Alternatively, start a local Houston server with the default config:
See the quickstart for a guide on how to create microservices and complete Houston missions using them:
[quickstart](https://github.com/datasparq-intelligent-products/houston-quickstart-python)

```bash
houston api
```

The server is now running at `localhost:8000`. The Houston client will automatically look for Houston API servers
running at this location.
### Install

(in a separate shell) Create a new Houston key with ID = 'quickstart':
If you have [go](https://golang.org/doc/install) installed you can install with:

```bash
houston create-key --id quickstart --name "Houston Quickstart"
go install github.com/datasparq-ai/houston
```

Save this example plan to local file, e.g. 'example_plan.yaml':

```yaml
name: apollo
stages:
- name: engine-ignition
- name: engine-thrust-ok
upstream:
- engine-ignition
- name: release-holddown-arms
- name: umbilical-disconnected
- name: liftoff
upstream:
- engine-thrust-ok
- release-holddown-arms
- umbilical-disconnected
```

Start a mission using this plan:
### Why Houston?

```bash
export HOUSTON_KEY=quickstart
houston start --plan example_plan.yaml
```
Houston is a simpler, faster, and cheaper alternative to tools like Airflow.

Then go to http://localhost:8000. Enter your Houston key 'quickstart'.
API based orchestration comes with 5 key advantages:
1. Code can run on serverless tools: lower cost, less maintenance, infinite scale
2. The server isn't under heavy load, so can handle hundreds of concurrent missions
3. Pub/Sub message delivery is guaranteed, improving reliability
4. Multiple workflows can share the same task runners, aiding collaboration
5. Task runners can run anywhere in any language, allowing for rapid development with no vendor lock-in

You've created a plan and started a mission. You now need a microservice to complete each of the stages in this mission.
See the quickstart for a guide on how to create microservices and complete Houston missions using them:
[quickstart](https://github.com/datasparq-intelligent-products/houston-quickstart-python)

### Contributing

Please see the [contributing](./docs/contributing.md) guide.

Development of Houston is supported by [Datasparq](https://datasparq.ai).

### Run Unit Tests

Test with development database:
```bash
go test ./...
```

Test with Redis database:
```bash
# remove any existing redis database
rm dump.rdb
# prevent go from using cached test results
go clean -testcache
# create redis db
redis-server &
go test ./...
# stop redis db
kill $!
```
2 changes: 1 addition & 1 deletion api/router-key.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func (a *API) GetKey(w http.ResponseWriter, r *http.Request) {
// @ID post-key
// @Tags Key
// @Param Body body model.Key true "The id, name and usage of key"
// @Success 200 key string
// @Success 200 string key
// @Failure 404,500 {object} model.Error
// @Router /api/v1/key [post]
func (a *API) PostKey(w http.ResponseWriter, r *http.Request) {
Expand Down
65 changes: 65 additions & 0 deletions client/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,68 @@

# Houston Go Client

Example usage:

```go
package main

import "github.com/datasparq-ai/houston/client"

func main() {

// provide a key and base URL to initialise the client
houston := client.New("my-houston-key", "https://houston.example.com")
err := houston.SavePlan("./my_plan.json")

}
```

The client will use environment variables by default:

```go
// this uses the 'HOUSTON_KEY' and 'HOUSTON_BASE_URL' environment variables for key and baseUrl respectively
houston := client.New("", "")
```

```go
houston := client.New("", "")

// we have previously saved a plan with this name
// a mission ID is not provided, so one will be created for us
res, err := houston.CreateMission("my-plan", "")

// trigger first stage
...
```

The same can be achieved with the 'start' command. See [commands](../docs/commands.md):

```go
houston := client.New("", "")
res, err := houston.Start("my-plan", "", nil, nil, nil)
```

It may be easier to start missions with the command line tool. The equivalent would be:

```bash
houston start --plan my-plan
```

Within services, the client is used to start and finish the stage:

```go
// get mission ID and stage name from trigger message
...

houston := client.New("", "")
res, err = houston.StartStage(missionId, stageName, false)

// complete task
...

res, err = houston.FinishStage(missionId, stageName, false)
for _, nextStage := range res.Next {
// trigger stage
...
}
```
71 changes: 42 additions & 29 deletions demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"github.com/datasparq-ai/houston/api"
"github.com/datasparq-ai/houston/model"
"github.com/spf13/cobra"
"math/rand"
"os"
"os/signal"
"syscall"
Expand Down Expand Up @@ -44,7 +45,12 @@ func demo(createCmd *cobra.Command) {
{"name": "release-holddown-arms", "upstream": ["engine-thrust-ok"]},
{"name": "umbilical-disconnected"},
{"name": "liftoff", "upstream": ["release-holddown-arms", "umbilical-disconnected"]},
{"name": "tower-clearance-yaw-maneuver", "upstream": ["liftoff"]}
{"name": "tower-clearance-yaw-maneuver", "upstream": ["liftoff"]},
{"name": "pitch-and-roll-maneuver", "upstream": ["tower-clearance-yaw-maneuver"]},
{"name": "apex", "upstream": ["pitch-and-roll-maneuver"]},
{"name": "mach-one", "upstream": ["liftoff"]},
{"name": "outboard-engine-cutoff", "upstream": ["mach-one"]},
{"name": "iterative-guidance-mode", "upstream": ["pitch-and-roll-maneuver"]}
]
}`)
var plan model.Plan
Expand Down Expand Up @@ -80,40 +86,47 @@ func demo(createCmd *cobra.Command) {

time.Sleep(2 * time.Second)
a.UpdateStageState("demo", "apollo-11", "engine-ignition", "started", false)
time.Sleep(1342 * time.Millisecond)
a.UpdateStageState("demo", "apollo-11", "engine-ignition", "finished", false)
a.UpdateStageState("demo", "apollo-11", "engine-thrust-ok", "started", false)
time.Sleep(1042 * time.Millisecond)
a.UpdateStageState("demo", "apollo-11", "engine-thrust-ok", "finished", false)
a.UpdateStageState("demo", "apollo-11", "umbilical-disconnected", "started", false)
a.UpdateStageState("demo", "apollo-11", "release-holddown-arms", "started", false)
time.Sleep(1820 * time.Millisecond)
a.UpdateStageState("demo", "apollo-11", "umbilical-disconnected", "finished", false)
time.Sleep(1820 * time.Millisecond)
a.UpdateStageState("demo", "apollo-11", "release-holddown-arms", "finished", false)
time.Sleep(1120 * time.Millisecond)
a.UpdateStageState("demo", "apollo-11", "liftoff", "started", false)
time.Sleep(1560 * time.Millisecond)
a.UpdateStageState("demo", "apollo-11", "liftoff", "finished", false)
a.UpdateStageState("demo", "apollo-11", "tower-clearance-yaw-maneuver", "started", false)
time.Sleep(3440 * time.Millisecond)
a.UpdateStageState("demo", "apollo-11", "tower-clearance-yaw-maneuver", "finished", false)

fmt.Printf(">>> %[1]vhouston start%[2]v \u001B[1m-p apollo -i apollo-12%[2]v\n", s, e)
_, err = a.CreateMissionFromPlan("demo", "apollo", "apollo-12")
if err != nil {
panic(err)
}
time.Sleep(1234 * time.Millisecond)
res, _ := a.UpdateStageState("demo", "apollo-11", "engine-ignition", "finished", false)
go continueMission(a, "demo", "apollo-11", res.Next)

time.Sleep(3 * time.Second)

go func() {

fmt.Println("Created mission with ID 'apollo-12'")
missionCount := 1
for missionCount < 30 {
time.Sleep(time.Duration(rand.Float32()*5000.0) * time.Millisecond)
//fmt.Printf(">>> %[1]vhouston start%[2]v \u001B[1m-p apollo -i apollo-12%[2]v\n", s, e)
missionId := fmt.Sprintf("apollo-%v", 11+missionCount)
_, err = a.CreateMissionFromPlan("demo", "apollo", missionId)
if err != nil {
panic(err)
}

time.Sleep(1 * time.Second)
a.UpdateStageState("demo", "apollo-12", "engine-ignition", "started", false)
time.Sleep(1342 * time.Millisecond)
time.Sleep(time.Duration(rand.Float32()*1000.0) * time.Millisecond)
a.UpdateStageState("demo", missionId, "engine-ignition", "started", false)
res, _ = a.UpdateStageState("demo", missionId, "engine-ignition", "finished", false)
go continueMission(a, "demo", missionId, res.Next)

missionCount++
}
}()

// keep the API running until the user exits
exitSignal := make(chan os.Signal)
signal.Notify(exitSignal, syscall.SIGINT, syscall.SIGTERM)
<-exitSignal

}

// continueMission completes every stage of a mission recursively, for demo purposes
func continueMission(a *api.API, key, missionId string, stages []string) {
for _, stageName := range stages {
time.Sleep(time.Millisecond * time.Duration(rand.Float32()*500.0))
a.UpdateStageState("demo", missionId, stageName, "started", false)
time.Sleep(time.Millisecond * time.Duration(rand.Float32()*10000.0))
res, _ := a.UpdateStageState("demo", missionId, stageName, "finished", false)
go continueMission(a, key, missionId, res.Next)
}
}
Loading

0 comments on commit 847bb9d

Please sign in to comment.