Pulumi + Heroku = Halloumi
You write your application, we run it in the cloud.
halloumi
is a prototype and not under active development.
The halloumi SDK defines an application model where you simply write your http handler, and we take care of the rest:
app := app.NewApp("petStore")
// a cloud web service that returns a number [0-100)
randomNumberService := web.NewWebService("randomNumber", func() http.Handler {
// a normal HTTP handler, halloumi takes care of running this in the cloud
r := mux.NewRouter()
handler := func(w http.ResponseWriter, r *http.Request) {
rand.Seed(time.Now().UnixNano())
fmt.Fprintf(w, "%d", rand.Intn(100))
}
r.HandleFunc("/", handler)
return r
})
app.Register(randomNumberService)
To deploy this app, simply run your program with the halloumi
tool:
$ halloumi main.go
Deployming app petStore
Deploying service randomNumber
Successfully deployed service randomNumber
Service randomNumber running at: web-lb-f1f8017-934112424.us-west-2.elb.amazonaws.com
Successfully deployed app petStore
Halloumi allows consuming services from other services:
// a cloud web service that returns N of a random animal.
// this service takes a dependency on the URLs of the previously defined services
nAnimalsService := web.NewWebService("nAnimals", func() http.Handler {
r := mux.NewRouter()
var num string
var animal string
handler := func(w http.ResponseWriter, r *http.Request) {
// Notice how we have the URL of randomNumberService
// available to consume here!
numResp, err := http.Get(randomNumberService.URL())
if err != nil {
fmt.Fprintf(w, err.Error())
}
defer numResp.Body.Close()
if numResp.StatusCode == http.StatusOK {
bodyBytes, err := ioutil.ReadAll(numResp.Body)
if err != nil {
log.Fatal(err)
}
num = string(bodyBytes)
}
// Notice how we have the URL of randomAnimalService
// available to consume here!
animalResp, err := http.Get(randomAnimalService.URL())
if err != nil {
fmt.Fprintf(w, err.Error())
}
defer numResp.Body.Close()
if animalResp.StatusCode == http.StatusOK {
bodyBytes, err := ioutil.ReadAll(animalResp.Body)
if err != nil {
log.Fatal(err)
}
animal = string(bodyBytes)
}
fmt.Fprintf(w, "Wow, you got %s %ss!", num, animal)
}
r.HandleFunc("/", handler)
return r
})
- Clone repo to $GOPATH
- make (from this directory to install the halloumi tool)
- run the included example
halloumi main.go
- authenticated pulumi CLI (>= v2.10.1)
- AWS CLI + creds
- Docker
The halloumi orchestrator works off of the principal of invoking the program different sets of environment variables
- First pass
DRY_RUN
: this procudes a list of apps and services to deploy allowing the program to exit without starting any http servers. - The orchestrator deploys each app and service, building a docker image from the provided
main.go
file. - When a service is deployed, an environment variable
HALLOUMI_APPNAME_SVCNAME
is set in the ECS task. When the program is run with that environment variable, the program will start a web server for the specified service.
- Can we get rid of the CLI? This demo would be more powerful without it.
- Auto-generate the docker file side by side. Right now it is just checked in along side the example.
- Add some other components besides web services (blob, queue, db).
- Write an SDK for a language like javascript. The SDK layer is super thin and wouldn't be much work.
- Perf/parallelism. Right now to be lazy, I don't detect dependencies and take advantage of the fact that declaration order is also dependency order. This just means that deployments are serial and slow.