Skip to content
This repository has been archived by the owner on Oct 31, 2023. It is now read-only.

Commit

Permalink
Merge pull request #66 from openhie/strawperson
Browse files Browse the repository at this point in the history
goinstant desktop app version 1.0.0-beta
  • Loading branch information
citizenrich authored Dec 28, 2020
2 parents 713c23c + 1bf3cd6 commit 3502c17
Show file tree
Hide file tree
Showing 28 changed files with 1,811 additions and 347 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
# go-instant
*-packr.go
packrd
goinstant/bin/goinstant*
goinstant/data
goinstant/hapi.properties
goinstant/pkged.go

# Logs
logs
Expand Down
40 changes: 35 additions & 5 deletions goinstant/README.md
Original file line number Diff line number Diff line change
@@ -1,23 +1,53 @@
# goinstant

This is a Go app and can be built as a native binary for any operating system. Static assets and templates are built using packr2.
This is a Go desktop app and is provided as a native binary for the AMD64 architecture on Windows, macOS, and Linux.

## Dev prerequisites
> Warning: This app is not meant to be used for container and cluster management in production or with sensitive data. It is meant for demos, training sessions, and by developers. In production and with sensitive data, administrators should use the purpose-built tools like the Docker and Kubernetes CLIs to manage resources directly and according to best practices which are outside the scope of this app.
## How it Works

> The prototype of goinstant was CLI only and that is now disabled. If a CLI is desired, it it suggested to use the yarn commands provided for running from the command line. The yarn CLI will be maintained.
The desktop is rendered using web pages.

* Static assets are bundled into the executable.
* Web pages are served by Go HTTP server.
* Calls from JS go to the Go API, which in turn calls Go functions. While Go apps can easily render and serve pages directly, this separation of concerns exists so that a contributor may add a different Web framework like React or Vue, and build a new frontend while the API remains in Go.
* Go functions generally invoke the shell on the user's OS. There are a few exceptions.
* On init and after accepting the disclaimer, this repo is cloned to the system. (This is done with a git-compatible library, git doesn't need to be installed.)
* The console uses server-side events. These are one-way events sent to the client, not bidirectional like websockets. There is a function which can be called `consoleSender` to send events to the console.

## Security

This desktop app is meant as a prototype and may change. This app resides in userspace but it invokes the command line for containers and clusters. The apps it invokes, Docker and Kubernetes CLI, launch and manage containers and may have admin/root privileges.

Therefore, this app is not meant to be used for container and cluster management in production or with sensitive data. It is meant for demos, training sessions, and by developers. In production and with sensitive data, administrators should use the purpose-built tools like the Docker and Kubernetes CLIs to manage resources directly and according to best practices which are outside the scope of this app.

## Developers

### Dev prerequisites

* Install go, [see here](https://golang.org/doc/install). For Ubuntu you might want to use the go snap package, [see here](https://snapcraft.io/install/go/ubuntu).
* Install packr2: **Outside** of the goinstant folder (so that it doesn't get installed as a module) run: `go get -u github.com/gobuffalo/packr/v2/packr2`
* Add go binaries to you system $PATH, on ubuntu: Add `export PATH=$PATH:$HOME/go/bin` to the end of your ~/.bashrc file. To use this change immediately source it: `source ~/.bashrc`
* Install dependencies, run this from the goinstant folder: `go get`

## Running and building
### Running

Static assets and templates are built using pkger. For development, run the app using `pkger && go run *.go`. `pkger` must be run if you change the static assets.

Run the app using `go run goinstant.go` or build the binary using `go build`. To build releases, create a tag and upload the binaries built. A convenience bash script is included to build binaries.
### Building

To build releases, create a tag and upload the binaries. A convenience bash script is included to build binaries.

> Note that ARM builds are not yet supported in Go 1.15, but such builds can be supported in future for Linux and macOS.
Note: this script won't work if you have a `goinstant/data/` folder that gets created when when starting up the docker-compose files through the go app. Delete this first: `sudo rm -r goinstant/data`

```sh
bash ./buildreleases.sh
git tag 0.0.1
git push origin 0.0.1
# then upload the binaries.
# then upload the binaries to GitHub
```

9 changes: 5 additions & 4 deletions goinstant/buildreleases.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
GOOS=darwin GOARCH=amd64 packr2 build && mv ./goinstant ./bin/goinstant-macos \
&& GOOS=linux GOARCH=amd64 packr2 build && mv ./goinstant ./bin/goinstant-linux \
&& GOOS=windows GOARCH=amd64 packr2 build && mv ./goinstant.exe ./bin/goinstant.exe \
&& packr2 clean
pkger \
&& GOOS=darwin GOARCH=amd64 go build && mv ./goinstant ./bin/goinstant-macos \
&& GOOS=linux GOARCH=amd64 go build && mv ./goinstant ./bin/goinstant-linux \
&& GOOS=windows GOARCH=amd64 go build && mv ./goinstant.exe ./bin/goinstant.exe \
&& go clean
21 changes: 21 additions & 0 deletions goinstant/console.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package main

import (
"net/http"

"github.com/r3labs/sse"
)

func sseHandler(w http.ResponseWriter, r *http.Request) {
// log.Println("new client", r.Header.Get("X-Forwarded-For"))
server.HTTPHandler(w, r)
}

func consoleSender(server *sse.Server, text string) {

// fmt.Println(text)
server.Publish("messages", &sse.Event{
Data: []byte(text),
})

}
35 changes: 35 additions & 0 deletions goinstant/demo/docker-compose.facility.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
version: "3.3"

services:
mongo:
image: mongo:3.6
ports:
- "27017:27017"
restart: unless-stopped

redis:
image: redis
ports:
- "6379:6379"
restart: unless-stopped

hearth:
image: intrahealth/hearth:latest
environment:
- mongodb__url=mongodb://mongo/hearth-dev
- logger__level=warning
- authentication__type=disabled
# - idGenerator=uuidv4 <- setting from jembi/health, is untested
ports:
- "3447:3447"
restart: unless-stopped

facility-registry:
image: intrahealth/facility-registry:latest
environment:
- HEARTH_URL=http://hearth:3447
- REDIS_HOST=redis
- DB_HOST=mongo
ports:
- "3000:3000"
restart: unless-stopped
86 changes: 86 additions & 0 deletions goinstant/disclaimer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package main

import (
"fmt"
"log"
"os"
"path/filepath"
"time"

"github.com/manifoldco/promptui"
)

// existDisclaimer detects if the accept_disclaimer file exists in the $HOME/.instant folder.
// If it exists, then the user has accepted and it returns 'success', otherwise 'fail'.
func existDisclaimer() string {
home, _ := os.UserHomeDir()
// must use filepath.join not path.join for windows compat
dotfiles := filepath.Join(home, ".instant")
fileName := filepath.Join(dotfiles, "accept_disclaimer")
status := "success"
if _, err := os.Stat(fileName); os.IsNotExist(err) {
status = "fail"
}
return status
}

func cliDisclaimer() {
status := existDisclaimer()
if status == "fail" {
pkgerPrint("/templates/disclaimer.txt", "yellow")
prompt := promptui.Select{
Label: "Do you agree to use this application?",
Items: []string{"Yes", "No", "Quit"},
}

_, result, err := prompt.Run()
if err != nil {
fmt.Printf("Prompt failed %v\n", err)
return
}
fmt.Printf("You chose %q\n", result)

switch result {
case "Yes":
makeDisclaimer()
setup()
selectSetup()
case "No":
fmt.Println("Understood. Exiting.")
os.Exit(1)
case "Quit":
os.Exit(1)
}
} else {
selectSetup()
}
}

func makeDisclaimer() string {
home, _ := os.UserHomeDir()
dotfiles := filepath.Join(home, ".instant")
fileName := filepath.Join(dotfiles, "accept_disclaimer")
_, err := os.Stat(fileName)
status := "success"
if os.IsNotExist(err) {
err := os.MkdirAll(dotfiles, os.ModePerm)
if err != nil {
status = "fail"
log.Fatal(err)
}
file, err := os.Create(fileName)
if err != nil {
status = "fail"
log.Fatal(err)
}
defer file.Close()
} else {
currentTime := time.Now().Local()
err = os.Chtimes(fileName, currentTime, currentTime)
if err != nil {
status = "fail"
fmt.Println(err)
}
}
return status
}
157 changes: 157 additions & 0 deletions goinstant/docker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
package main

import (
"bufio"
"bytes"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"runtime"
"strconv"

"github.com/docker/docker/api/types"
"github.com/docker/docker/client"
"github.com/gookit/color"
"golang.org/x/net/context"
)

func debugDocker() {

consoleSender(server, "...checking your Docker setup")

cwd, err := os.Getwd()
if err != nil {
consoleSender(server, "Can't get current working directory... this is not a great error.")
// panic(err)
} else {
consoleSender(server, cwd)
}

// ctx := context.Background()
cli, err := client.NewEnvClient()
if err != nil {
// panic(err)
}

info, err := cli.Info(context.Background())
if err != nil {
consoleSender(server, "Unable to get Docker context. Please ensure that Docker is downloaded and running")
// panic(err)
} else {
// Docker default is 2GB, which may need to be revisited if Instant grows.
str1 := " bytes memory is allocated.\n"
str2 := strconv.FormatInt(info.MemTotal, 10)
result := str2 + str1
consoleSender(server, result)
consoleSender(server, "Docker setup looks good")
}

}

// TODO: change printf to consoleSender
// listDocker may be used in future
func listDocker() {

consoleSender(server, "Listing containers")

// ctx := context.Background()
cli, err := client.NewEnvClient()
if err != nil {
// panic(err)
}

containers, err := cli.ContainerList(context.Background(), types.ContainerListOptions{})
if err != nil {
consoleSender(server, "Unable to list Docker containers. Please ensure that Docker is downloaded and running")
// return
}

for _, container := range containers {
items := fmt.Sprintf("ContainerID: %s Status: %s Image: %s\n", container.ID[:10], container.State, container.Image)
consoleSender(server, items)
}

}

// SomeStuff is the one func to rule them all
func SomeStuff(r *http.Request) {
consoleSender(server, "Note: Initial setup takes 1-5 minutes. wait for the DONE message")
runner := r.URL.Query().Get("runner")
pk := r.URL.Query().Get("package")
state := r.URL.Query().Get("state")
consoleSender(server, "Runner requested: "+runner)
consoleSender(server, "Package requested: "+pk)
consoleSender(server, "State requested: "+state)
home, _ := os.UserHomeDir()

// args := []string{runner, "ever", "you", "like"}
// cmd := exec.Command(app, args...)
// consoleSender(server, args[0])

cmd := exec.Command("docker", "run", "--rm", "-v", "/var/run/docker.sock:/var/run/docker.sock", "-v", home+"/.kube/config:/root/.kube/config:ro", "-v", home+"/.minikube:/home/$USER/.minikube:ro", "--mount=type=volume,src=instant,dst=/instant", "--network", "host", "openhie/instant:latest", state, "-t", runner, pk)
// create a pipe for the output of the script
cmdReader, err := cmd.StdoutPipe()
if err != nil {
fmt.Fprintln(os.Stderr, "Error creating StdoutPipe for Cmd", err)
return
}

scanner := bufio.NewScanner(cmdReader)
go func() {
for scanner.Scan() {
// fmt.Printf("\t > %s\n", scanner.Text())
consoleSender(server, scanner.Text())
}
}()

err = cmd.Start()
if err != nil {
fmt.Fprintln(os.Stderr, "Error starting Cmd", err)
return
}

err = cmd.Wait()
if err != nil {
fmt.Fprintln(os.Stderr, "Error waiting for Cmd", err)
return
}

}

func composeUpCoreDOD() {

home, _ := os.UserHomeDir()
color.Yellow.Println("Running on", runtime.GOOS)
switch runtime.GOOS {
case "linux", "darwin":
// cmd := exec.Command("docker-compose", "-f", composefile, "up", "-d")
cmd := exec.Command("docker", "run", "--rm", "-v", "/var/run/docker.sock:/var/run/docker.sock", "-v", home+"/.kube/config:/root/.kube/config:ro", "-v", home+"/.minikube:/home/$USER/.minikube:ro", "--mount=type=volume,src=instant,dst=/instant", "--network", "host", "openhie/instant:latest", "init", "-t", "docker")

var outb, errb bytes.Buffer
cmd.Stdout = &outb
cmd.Stderr = &errb
// cmd.Stdout = os.Stdout
// cmd.Stderr = os.Stderr
err := cmd.Run()
if err != nil {
log.Fatalf("cmd.Run() failed with %s\n", err)

}
consoleSender(server, outb.String())
fmt.Println("out:", outb.String(), "err:", errb.String())

case "windows":
// cmd := exec.Command("cmd", "/C", "docker-compose", "-f", composefile, "up", "-d")
cmd := exec.Command("cmd", "/C", "docker", "run", "--rm", "-v", "/var/run/docker.sock:/var/run/docker.sock", "-v", home+"\\.kube:/root/.kube/config:ro", "--mount=type=volume,src=instant,dst=/instant", "openhie/instant:latest", "init", "-t", "docker")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Println("Error: ", err)
}
default:
consoleSender(server, "What operating system is this?")
}

}
Loading

0 comments on commit 3502c17

Please sign in to comment.