Skip to content

Commit ea98661

Browse files
authored
Merge pull request #83 from spinup-host/idoqo/gh-80-simplify-startup
include spinup UI in setup script, improve CLI with cobra
2 parents 677401b + cbac5ad commit ea98661

File tree

12 files changed

+480
-121
lines changed

12 files changed

+480
-121
lines changed

.github/workflows/go.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ name: "pre-release"
66
jobs:
77
releases-matrix:
88
runs-on: ubuntu-latest
9+
env:
10+
RELEASE_TAG_NAME: ${{ github.event.release.tag_name }}
911
strategy:
1012
matrix:
1113
# build and publish in parallel: linux/386, linux/amd64, windows/386, windows/amd64, darwin/amd64
@@ -23,5 +25,5 @@ jobs:
2325
goarch: ${{ matrix.goarch }}
2426
goversion: "https://dl.google.com/go/go1.16.5.linux-amd64.tar.gz"
2527
binary_name: "spinup-backend"
26-
ldflags: "-X 'main.apiVersion=v0.2-alpha'"
28+
ldflags: "-X 'main.apiVersion=$RELEASE_TAG_NAME'"
2729
extra_files: README.md CONTRIBUTING.md

README.md

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,19 @@ We are currently using Github Authentication. We should be able to support other
1212
Currently we only support Postgres dbms, but we should be able to support other open source databases like [MySQL](https://www.mysql.com/), [MariaDB](https://mariadb.org/) etc.
1313

1414
![architecture](architecture.jpeg)
15-
### How to run
15+
## Installation
16+
### Linux
17+
- To get started with Spinup on Linux, run the installation script using the command below:
18+
```bash
19+
$ bash < <(curl -s https://raw.githubusercontent.com/spinup-host/spinup/main/scripts/install-spinup.sh)
20+
```
21+
- Add the Spinup installation directory (default is `$HOME/.local/spinup`) to your shell PATH to use Spinup from your terminal.
22+
- Start the Spinup servers (both API and frontend) by running:
23+
```bash
24+
$ spinup start
25+
```
1626

17-
#### Requirement for JWT
18-
We use JWT for verification. You need to have a private and public key that you can create using OpenSSL:
27+
### Others
1928

2029
**To create a private key**
2130
```
@@ -47,6 +56,9 @@ common:
4756

4857
```go run main.go```
4958

59+
#### Authentication
60+
We use JWT for verification. You need to have a private and public key that you can create using OpenSSL:
61+
5062
On another terminal you can start the [dash](https://github.com/spinup-host/spinup-dash) to access the backend.
5163

5264
To check the API endpoint:

api/create.go

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"database/sql"
77
"encoding/json"
88
"fmt"
9+
"github.com/spinup-host/internal/dockerservice"
910
"io/ioutil"
1011
"log"
1112
"net"
@@ -16,11 +17,9 @@ import (
1617
"time"
1718

1819
"github.com/docker/docker/client"
20+
_ "github.com/mattn/go-sqlite3"
1921
"github.com/robfig/cron/v3"
2022
"github.com/spinup-host/backup"
21-
"github.com/spinup-host/internal"
22-
23-
_ "github.com/mattn/go-sqlite3"
2423
"github.com/spinup-host/config"
2524
"github.com/spinup-host/misc"
2625
)
@@ -215,7 +214,7 @@ func startService(s service, path string) (serviceContainerID string, err error)
215214
return serviceContainerID, err
216215
}
217216

218-
pgExporter := internal.NewPgExporterService(cli, s.DockerNetwork, s.Db.Name, s.Db.Username, s.Db.Password)
217+
pgExporter := dockerservice.NewPgExporterService(cli, s.DockerNetwork, s.Db.Name, s.Db.Username, s.Db.Password)
219218
if err := pgExporter.Start(); err != nil {
220219
return serviceContainerID, err
221220
}

backup/backup.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,18 @@ package backup
22

33
import (
44
"fmt"
5+
"github.com/spinup-host/internal/dockerservice"
56
"log"
67

78
"github.com/docker/docker/client"
8-
"github.com/spinup-host/internal"
99
)
1010

1111
func TriggerBackup(networkName, awsAccessKey, awsAccessKeyId, pgHost, pgUsername, pgPassword, walgS3Prefix string) func() {
1212
cli, err := client.NewClientWithOpts(client.FromEnv)
1313
if err != nil {
1414
fmt.Printf("error creating client %v", err)
1515
}
16-
backupSvc := internal.NewPgBackupService(cli, awsAccessKey, awsAccessKeyId, pgHost, walgS3Prefix, networkName, pgUsername, pgPassword)
16+
backupSvc := dockerservice.NewPgBackupService(cli, awsAccessKey, awsAccessKeyId, pgHost, walgS3Prefix, networkName, pgUsername, pgPassword)
1717
return func() {
1818
fmt.Println("backup triggered")
1919
err = backupSvc.Start()

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,5 +17,6 @@ require (
1717
github.com/robfig/cron/v3 v3.0.1
1818
github.com/rs/cors v1.8.0
1919
github.com/rs/zerolog v1.25.0
20+
github.com/spf13/cobra v1.2.1
2021
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
2122
)

go.sum

Lines changed: 214 additions & 5 deletions
Large diffs are not rendered by default.

internal/cmd/root.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"github.com/spf13/cobra"
6+
)
7+
8+
var rootCmd = &cobra.Command{
9+
Use: "spinup",
10+
Short: "Spinup CLI",
11+
TraverseChildren: true,
12+
}
13+
14+
func Execute(ctx context.Context, apiVersion string) error {
15+
rootCmd.AddCommand(versionCmd(apiVersion))
16+
rootCmd.AddCommand(startCmd())
17+
18+
return rootCmd.ExecuteContext(ctx)
19+
}

internal/cmd/start.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package cmd
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"io/ioutil"
7+
"log"
8+
"math/rand"
9+
"net"
10+
"net/http"
11+
"os"
12+
"os/signal"
13+
"syscall"
14+
"time"
15+
16+
"github.com/spinup-host/api"
17+
"github.com/spinup-host/config"
18+
"github.com/spinup-host/metrics"
19+
20+
"github.com/golang-jwt/jwt"
21+
"github.com/rs/cors"
22+
"github.com/spf13/cobra"
23+
"gopkg.in/yaml.v3"
24+
)
25+
26+
var (
27+
cfgFile string
28+
uiPath string
29+
30+
apiPort = ":4434"
31+
uiPort = ":3000"
32+
)
33+
34+
func apiHandler() http.Handler {
35+
rand.Seed(time.Now().UnixNano())
36+
mux := http.NewServeMux()
37+
mux.HandleFunc("/hello", api.Hello)
38+
mux.HandleFunc("/createservice", api.CreateService)
39+
mux.HandleFunc("/githubAuth", api.GithubAuth)
40+
mux.HandleFunc("/logs", api.Logs)
41+
mux.HandleFunc("/jwt", api.JWT)
42+
mux.HandleFunc("/jwtdecode", api.JWTDecode)
43+
mux.HandleFunc("/streamlogs", api.StreamLogs)
44+
mux.HandleFunc("/listcluster", api.ListCluster)
45+
mux.HandleFunc("/cluster", api.GetCluster)
46+
mux.HandleFunc("/metrics", metrics.HandleMetrics)
47+
mux.HandleFunc("/createbackup", api.CreateBackup)
48+
c := cors.New(cors.Options{
49+
AllowedOrigins: []string{"https://app.spinup.host", "http://localhost:3000"},
50+
AllowedHeaders: []string{"authorization", "content-type"},
51+
})
52+
53+
return c.Handler(mux)
54+
}
55+
56+
func uiHandler() http.Handler {
57+
fs := http.FileServer(http.Dir(uiPath))
58+
http.Handle("/", fs)
59+
60+
return http.DefaultServeMux
61+
}
62+
63+
func startCmd() *cobra.Command {
64+
sc := &cobra.Command{
65+
Use: "start",
66+
Short: "start the spinup API and frontend servers",
67+
Run: func(cmd *cobra.Command, args []string) {
68+
home, err := os.UserHomeDir()
69+
if err != nil {
70+
log.Fatalf("FATAL: obtaining home directory: %v", err)
71+
}
72+
cmd.PersistentFlags().StringVar(&cfgFile, "config",
73+
fmt.Sprintf("%s/.local/spinup/config.yaml", home), "Path to spinup configuration")
74+
cmd.PersistentFlags().StringVar(&uiPath, "ui-path",
75+
fmt.Sprintf("%s/.local/spinup/spinup-dash", home), "Path to spinup frontend")
76+
77+
log.Println(fmt.Sprintf("INFO: using config file: %s", cfgFile))
78+
if err = validateConfig(cfgFile); err != nil {
79+
log.Fatalf("FATAL: validating config: %v", err)
80+
}
81+
log.Println("INFO: initial validations successful")
82+
83+
apiListener, err := net.Listen("tcp", apiPort)
84+
if err != nil {
85+
log.Fatalf("FATAL: starting API server %v", err)
86+
}
87+
uiListener, err := net.Listen("tcp", uiPort)
88+
if err != nil {
89+
log.Fatalf("FATAL: starting UI server %v", err)
90+
}
91+
apiServer := &http.Server{
92+
Handler: apiHandler(),
93+
}
94+
uiServer := &http.Server{
95+
Handler: uiHandler(),
96+
}
97+
98+
stopCh := make(chan os.Signal, 1)
99+
go func() {
100+
log.Println(fmt.Sprintf("INFO: starting Spinup API on port %s", apiPort))
101+
apiServer.Serve(apiListener)
102+
}()
103+
104+
go func() {
105+
log.Println(fmt.Sprintf("INFO: starting Spinup UI on port %s", uiPort))
106+
uiServer.Serve(uiListener)
107+
}()
108+
109+
defer stop(apiServer)
110+
defer stop(uiServer)
111+
112+
signal.Notify(stopCh, syscall.SIGINT, syscall.SIGTERM)
113+
log.Println(fmt.Sprint(<-stopCh))
114+
log.Println("stopping spinup apiServer")
115+
},
116+
}
117+
118+
return sc
119+
}
120+
121+
func validateConfig(path string) error {
122+
file, err := os.Open(path)
123+
if err != nil {
124+
return err
125+
}
126+
defer file.Close()
127+
128+
d := yaml.NewDecoder(file)
129+
if err = d.Decode(&config.Cfg); err != nil {
130+
return err
131+
}
132+
133+
signBytes, err := ioutil.ReadFile(config.Cfg.Common.ProjectDir + "/app.rsa")
134+
if err != nil {
135+
return err
136+
}
137+
138+
if config.Cfg.SignKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes); err != nil {
139+
return err
140+
}
141+
142+
verifyBytes, err := ioutil.ReadFile(config.Cfg.Common.ProjectDir + "/app.rsa.pub")
143+
if err != nil {
144+
return err
145+
}
146+
147+
if config.Cfg.VerifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes); err != nil {
148+
return err
149+
}
150+
151+
return nil
152+
}
153+
154+
func stop(server *http.Server) {
155+
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) //nolint
156+
defer cancel()
157+
if err := server.Shutdown(ctx); err != nil {
158+
log.Printf("Can't stop Spinup API correctly: %v", err)
159+
}
160+
}

internal/cmd/version.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package cmd
2+
3+
import (
4+
"fmt"
5+
"github.com/spf13/cobra"
6+
)
7+
8+
func versionCmd(version string) *cobra.Command {
9+
return &cobra.Command{
10+
Use: "version",
11+
Short: "Print the SpinUp version",
12+
Run: func(cmd *cobra.Command, args []string) {
13+
fmt.Println(fmt.Sprintf("spinup version: %s", version))
14+
},
15+
}
16+
}

internal/dockerservice.go renamed to internal/dockerservice/dockerservice.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
package internal
1+
package dockerservice
22

33
import (
44
"context"

0 commit comments

Comments
 (0)