From a5a4b31f7c90e78c228ba8147b18cb4d4688d7ae Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Fri, 20 Apr 2018 04:22:09 -0700 Subject: [PATCH 01/31] Convert status endpoint response to JSON --- common/types.go | 12 +++++++++ daemon/inertia/mock_test.go | 5 ++-- daemon/inertia/project/deployment.go | 16 +++--------- daemon/inertia/status.go | 37 +++------------------------- daemon/inertia/status_test.go | 20 ++++++--------- deploy.go | 12 +++++++-- format.go | 37 ++++++++++++++++++++++++++++ 7 files changed, 76 insertions(+), 63 deletions(-) create mode 100644 common/types.go create mode 100644 format.go diff --git a/common/types.go b/common/types.go new file mode 100644 index 00000000..189fd419 --- /dev/null +++ b/common/types.go @@ -0,0 +1,12 @@ +package common + +// DeploymentStatus lists details about the deployed project +type DeploymentStatus struct { + InertiaVersion string `json:"version"` + Branch string `json:"branch"` + CommitHash string `json:"commit_hash"` + CommitMessage string `json:"commit_message"` + BuildType string `json:"build_type"` + Containers []string `json:"containers"` + BuildContainerActive bool `json:"build_active"` +} diff --git a/daemon/inertia/mock_test.go b/daemon/inertia/mock_test.go index 86ab6b8a..79d96cfb 100644 --- a/daemon/inertia/mock_test.go +++ b/daemon/inertia/mock_test.go @@ -4,6 +4,7 @@ import ( "io" docker "github.com/docker/docker/client" + "github.com/ubclaunchpad/inertia/common" "github.com/ubclaunchpad/inertia/daemon/inertia/project" ) @@ -19,7 +20,7 @@ type FakeDeployment struct { DestroyFunc func(in1 *docker.Client, in2 io.Writer) error DownFunc func(in1 *docker.Client, in2 io.Writer) error GetBranchFunc func() string - GetStatusFunc func(in1 *docker.Client) (*project.DeploymentStatus, error) + GetStatusFunc func(in1 *docker.Client) (*common.DeploymentStatus, error) } func (f *FakeDeployment) Deploy(o project.DeployOptions, c *docker.Client, w io.Writer) error { @@ -34,7 +35,7 @@ func (f *FakeDeployment) Destroy(c *docker.Client, w io.Writer) error { return f.DestroyFunc(c, w) } -func (f *FakeDeployment) GetStatus(c *docker.Client) (*project.DeploymentStatus, error) { +func (f *FakeDeployment) GetStatus(c *docker.Client) (*common.DeploymentStatus, error) { return f.GetStatusFunc(c) } diff --git a/daemon/inertia/project/deployment.go b/daemon/inertia/project/deployment.go index e5e8e381..91bbf42e 100644 --- a/daemon/inertia/project/deployment.go +++ b/daemon/inertia/project/deployment.go @@ -39,7 +39,7 @@ type Deployer interface { Down(*docker.Client, io.Writer) error Destroy(*docker.Client, io.Writer) error - GetStatus(*docker.Client) (*DeploymentStatus, error) + GetStatus(*docker.Client) (*common.DeploymentStatus, error) SetConfig(DeploymentConfig) GetBranch() string CompareRemotes(string) error @@ -169,18 +169,8 @@ func (d *Deployment) Destroy(cli *docker.Client, out io.Writer) error { return common.RemoveContents(Directory) } -// DeploymentStatus lists details about the deployed project -type DeploymentStatus struct { - Branch string - CommitHash string - CommitMessage string - BuildType string - Containers []string - BuildContainerActive bool -} - // GetStatus returns the status of the deployment -func (d *Deployment) GetStatus(cli *docker.Client) (*DeploymentStatus, error) { +func (d *Deployment) GetStatus(cli *docker.Client) (*common.DeploymentStatus, error) { // Get repository status head, err := d.repo.Head() if err != nil { @@ -212,7 +202,7 @@ func (d *Deployment) GetStatus(cli *docker.Client) (*DeploymentStatus, error) { } } - return &DeploymentStatus{ + return &common.DeploymentStatus{ Branch: strings.TrimSpace(head.Name().Short()), CommitHash: strings.TrimSpace(head.Hash().String()), CommitMessage: strings.TrimSpace(commit.Message), diff --git a/daemon/inertia/status.go b/daemon/inertia/status.go index f2329160..97167a78 100644 --- a/daemon/inertia/status.go +++ b/daemon/inertia/status.go @@ -1,17 +1,12 @@ package main import ( - "fmt" + "encoding/json" "net/http" docker "github.com/docker/docker/client" ) -const ( - msgBuildInProgress = "It appears that your build is still in progress." - msgNoContainersActive = "No containers are active." -) - // statusHandler returns a formatted string about the status of the // deployment and lists currently active project containers func statusHandler(w http.ResponseWriter, r *http.Request) { @@ -36,33 +31,7 @@ func statusHandler(w http.ResponseWriter, r *http.Request) { return } - branchStatus := " - Branch: " + status.Branch + "\n" - commitStatus := " - Commit: " + status.CommitHash + "\n" - commitMessage := " - Message: " + status.CommitMessage + "\n" - buildTypeStatus := " - Build Type: " + status.BuildType + "\n" - statusString := inertiaStatus + branchStatus + commitStatus + commitMessage + buildTypeStatus - - // If build container is active, that means that a build - // attempt was made but only the daemon and docker-compose - // are active, indicating a build failure or build-in-progress - if len(status.Containers) == 0 { - if status.BuildContainerActive { - errorString := statusString + msgBuildInProgress - http.Error(w, errorString, http.StatusOK) - } else { - errorString := statusString + msgNoContainersActive - http.Error(w, errorString, http.StatusOK) - } - return - } - - activeContainers := "Active containers:\n" - for _, container := range status.Containers { - activeContainers += " - " + container + "\n" - } - statusString += activeContainers - - w.Header().Set("Content-Type", "text/html") + w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) - fmt.Fprint(w, statusString) + json.NewEncoder(w).Encode(status) } diff --git a/daemon/inertia/status_test.go b/daemon/inertia/status_test.go index e2472b38..1de796c0 100644 --- a/daemon/inertia/status_test.go +++ b/daemon/inertia/status_test.go @@ -8,15 +8,15 @@ import ( docker "github.com/docker/docker/client" "github.com/stretchr/testify/assert" - "github.com/ubclaunchpad/inertia/daemon/inertia/project" + "github.com/ubclaunchpad/inertia/common" ) func TestStatusHandlerBuildInProgress(t *testing.T) { defer func() { deployment = nil }() // Set up condition deployment = &FakeDeployment{ - GetStatusFunc: func(*docker.Client) (*project.DeploymentStatus, error) { - return &project.DeploymentStatus{ + GetStatusFunc: func(*docker.Client) (*common.DeploymentStatus, error) { + return &common.DeploymentStatus{ Branch: "wow", CommitHash: "abcde", CommitMessage: "", @@ -36,15 +36,14 @@ func TestStatusHandlerBuildInProgress(t *testing.T) { handler.ServeHTTP(recorder, req) assert.Equal(t, recorder.Code, http.StatusOK) - assert.Contains(t, recorder.Body.String(), msgBuildInProgress) } func TestStatusHandlerNoContainers(t *testing.T) { defer func() { deployment = nil }() // Set up condition deployment = &FakeDeployment{ - GetStatusFunc: func(*docker.Client) (*project.DeploymentStatus, error) { - return &project.DeploymentStatus{ + GetStatusFunc: func(*docker.Client) (*common.DeploymentStatus, error) { + return &common.DeploymentStatus{ Branch: "wow", CommitHash: "abcde", CommitMessage: "", @@ -64,15 +63,14 @@ func TestStatusHandlerNoContainers(t *testing.T) { handler.ServeHTTP(recorder, req) assert.Equal(t, recorder.Code, http.StatusOK) - assert.Contains(t, recorder.Body.String(), msgNoContainersActive) } func TestStatusHandlerActiveContainers(t *testing.T) { defer func() { deployment = nil }() // Set up condition deployment = &FakeDeployment{ - GetStatusFunc: func(*docker.Client) (*project.DeploymentStatus, error) { - return &project.DeploymentStatus{ + GetStatusFunc: func(*docker.Client) (*common.DeploymentStatus, error) { + return &common.DeploymentStatus{ Branch: "wow", CommitHash: "abcde", CommitMessage: "", @@ -92,8 +90,6 @@ func TestStatusHandlerActiveContainers(t *testing.T) { handler.ServeHTTP(recorder, req) assert.Equal(t, recorder.Code, http.StatusOK) - assert.NotContains(t, recorder.Body.String(), msgNoContainersActive) - assert.NotContains(t, recorder.Body.String(), msgBuildInProgress) assert.Contains(t, recorder.Body.String(), "mycontainer_1") assert.Contains(t, recorder.Body.String(), "yourcontainer_2") } @@ -102,7 +98,7 @@ func TestStatusHandlerStatusError(t *testing.T) { defer func() { deployment = nil }() // Set up condition deployment = &FakeDeployment{ - GetStatusFunc: func(*docker.Client) (*project.DeploymentStatus, error) { + GetStatusFunc: func(*docker.Client) (*common.DeploymentStatus, error) { return nil, errors.New("uh oh") }, } diff --git a/deploy.go b/deploy.go index 10cf8df9..54a52fb0 100644 --- a/deploy.go +++ b/deploy.go @@ -2,12 +2,15 @@ package main import ( "bufio" + "encoding/json" "errors" "fmt" "io/ioutil" "net/http" "strings" + "github.com/ubclaunchpad/inertia/common" + log "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/ubclaunchpad/inertia/client" @@ -124,16 +127,21 @@ var deploymentStatusCmd = &cobra.Command{ log.Fatal(err) } - defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { log.Fatal(err) } + defer resp.Body.Close() switch resp.StatusCode { case http.StatusOK: fmt.Printf("(Status code %d) Daemon at remote '%s' online at %s\n", resp.StatusCode, deployment.Name, host) - fmt.Printf("%s", body) + var status common.DeploymentStatus + err := json.NewDecoder(resp.Body).Decode(status) + if err != nil { + log.Fatal(err) + } + println(formatStatus(&status)) case http.StatusForbidden: fmt.Printf("(Status code %d) Bad auth: %s\n", resp.StatusCode, body) case http.StatusNotFound: diff --git a/format.go b/format.go new file mode 100644 index 00000000..653c5653 --- /dev/null +++ b/format.go @@ -0,0 +1,37 @@ +package main + +import ( + "github.com/ubclaunchpad/inertia/common" +) + +const ( + msgBuildInProgress = "It appears that your build is still in progress." + msgNoContainersActive = "No containers are active." +) + +func formatStatus(status *common.DeploymentStatus) string { + inertiaStatus := "inertia daemon " + status.InertiaVersion + "\n" + branchStatus := " - Branch: " + status.Branch + "\n" + commitStatus := " - Commit: " + status.CommitHash + "\n" + commitMessage := " - Message: " + status.CommitMessage + "\n" + buildTypeStatus := " - Build Type: " + status.BuildType + "\n" + statusString := inertiaStatus + branchStatus + commitStatus + commitMessage + buildTypeStatus + + // If build container is active, that means that a build + // attempt was made but only the daemon and docker-compose + // are active, indicating a build failure or build-in-progress + if len(status.Containers) == 0 { + errorString := statusString + msgNoContainersActive + if status.BuildContainerActive { + errorString = statusString + msgBuildInProgress + } + return errorString + } + + activeContainers := "Active containers:\n" + for _, container := range status.Containers { + activeContainers += " - " + container + "\n" + } + statusString += activeContainers + return statusString +} From 93b1d85246ba6241020784901d8e97e0431b6625 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Fri, 20 Apr 2018 05:48:49 -0700 Subject: [PATCH 02/31] Fix improper cookie pathing --- daemon/inertia/auth/sessions.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/daemon/inertia/auth/sessions.go b/daemon/inertia/auth/sessions.go index 9d49fa70..e5f8625d 100644 --- a/daemon/inertia/auth/sessions.go +++ b/daemon/inertia/auth/sessions.go @@ -88,7 +88,7 @@ func (s *sessionManager) BeginSession(username string, w http.ResponseWriter, r Name: s.cookieName, Value: url.QueryEscape(id), Domain: s.cookieDomain, - MaxAge: int(s.cookieTimeout / time.Second), + Path: "/", HttpOnly: true, Expires: expiration, }) @@ -117,9 +117,9 @@ func (s *sessionManager) EndSession(w http.ResponseWriter, r *http.Request) erro // Set cookie to expire immediately http.SetCookie(w, &http.Cookie{ Name: s.cookieName, - Value: url.QueryEscape(id), + Value: "", Domain: s.cookieDomain, - MaxAge: -1, + Path: "/", HttpOnly: true, Expires: time.Unix(0, 0), }) @@ -128,7 +128,6 @@ func (s *sessionManager) EndSession(w http.ResponseWriter, r *http.Request) erro // GetSession verifies if given request is from a valid session and returns it func (s *sessionManager) GetSession(w http.ResponseWriter, r *http.Request) (*session, error) { - cookie, err := r.Cookie(s.cookieName) if err != nil || cookie.Value == "" { return nil, errCookieNotFound @@ -142,6 +141,7 @@ func (s *sessionManager) GetSession(w http.ResponseWriter, r *http.Request) (*se session, found := s.internal[id] if !found { s.RUnlock() + s.EndSession(w, r) return nil, errSessionNotFound } if !s.isValidSession(session) { From d05f63e650cbdef46dd090068a7b2411cc373596 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Fri, 20 Apr 2018 05:49:11 -0700 Subject: [PATCH 03/31] Refactored daemon requests, scaffold status API call --- daemon/web/client.js | 60 +++++++++++++++++++++++++++++++-- daemon/web/components/App.js | 7 +--- daemon/web/components/Home.js | 61 +++++++++++++++++++++++----------- daemon/web/components/Login.js | 19 +++-------- 4 files changed, 105 insertions(+), 42 deletions(-) diff --git a/daemon/web/client.js b/daemon/web/client.js index 3c1bb322..afca7119 100644 --- a/daemon/web/client.js +++ b/daemon/web/client.js @@ -5,6 +5,62 @@ export default class InertiaClient { this.url = 'https://' + url; } + async logout() { + const endpoint = '/user/logout'; + const params = { + headers: { + 'Accept': 'application/json' + } + }; + return this._post(endpoint, params); + } + + async login(username, password) { + const endpoint = '/user/login'; + const params = { + headers: { + 'Accept': 'application/json', + 'Content-Type': 'application/x-www-form-urlencoded' + }, + body: JSON.stringify({ + username: username, + password: password, + }) + }; + return this._post(endpoint, params); + } + + async validate() { + const params = { + headers: { 'Accept': 'application/json' }, + }; + return this._get('/user/validate', params); + } + + async getContainerLogs(container = 'inertia-daemon') { + const endpoint = '/logs'; + const params = { + headers: { + 'Accept': 'text/plain' + }, + body: JSON.stringify({ + container: container, + stream: true, + }) + }; + return this._get(endpoint, params); + } + + async getRemoteStatus() { + const endpoint = '/status'; + const params = { + headers: { + 'Accept': 'application/json' + } + }; + return this._get(endpoint, params); + } + /** * Makes a GET request to the given API endpoint with the given params. * @param {String} endpoint @@ -18,7 +74,7 @@ export default class InertiaClient { try { return await fetch(request); } catch (e) { - return e; + return Promise.reject(e); } } @@ -35,7 +91,7 @@ export default class InertiaClient { try { return await fetch(request); } catch (e) { - return e; + return Promise.reject(e); } } } diff --git a/daemon/web/components/App.js b/daemon/web/components/App.js index 209a53f0..e719b5f4 100644 --- a/daemon/web/components/App.js +++ b/daemon/web/components/App.js @@ -54,12 +54,7 @@ export default class App extends React.Component { } async isAuthenticated() { - const params = { - headers: { 'Accept': 'application/json' }, - }; - const response = await this.props.client._get( - '/user/validate', params - ); + const response = await this.props.client.validate(); return (response.status === 200); } diff --git a/daemon/web/components/Home.js b/daemon/web/components/Home.js index 735a7dfe..a119ea66 100644 --- a/daemon/web/components/Home.js +++ b/daemon/web/components/Home.js @@ -1,4 +1,7 @@ import React from 'react'; +import PropTypes from 'prop-types'; + +import InertiaClient from '../client'; const SidebarHeader = ({ children }) => (
@@ -44,36 +47,52 @@ const sidebarButtonStyles = { } }; - export default class Home extends React.Component { constructor(props) { super(props); - this.handleGetLogs = this.handleGetLogs.bind(this); - this.handleLogout = this.handleLogout.bind(this); - } + this.state = { + loading: true, - async handleGetLogs() { - const endpoint = '/logs'; - const params = { - headers: { - 'Accept': 'application/json' - } + remoteVersion: '', + remoteStatus: '', + + repoBranch: '', + repoCommitHash: '', + repoCommitMessage: '', + repoBuildType: '', + repoBuilding: false, + + containers: [], }; - const response = await this.props.client._get(endpoint, params); + + this.handleLogout = this.handleLogout.bind(this); + this.handleGetStatus = this.handleGetStatus.bind(this); + + this.handleGetStatus() + .then(() => this.setState({ loading: false })) + .catch((err) => console.error(err)); } async handleLogout() { - const endpoint = '/user/logout'; - const params = { - headers: { - 'Accept': 'application/json' - } - }; - - const response = await this.props.client._post(endpoint, params); + const response = await this.props.client.logout(); + if (response.status != 200) console.error(response); this.props.history.push('/login'); } + async handleGetStatus() { + const response = await this.props.client.getRemoteStatus(); + switch (response.status) { + case 200: + console.log(JSON.parse(response.body)); + break; + case 404: + console.log(response); + break; + default: + Promise.reject(Error('bad response:', response.body)); + } + } + render() { return (
@@ -105,6 +124,10 @@ export default class Home extends React.Component { } } +Home.propTypes = { + client: PropTypes.instanceOf(InertiaClient), +}; + // hardcode all styles for now, until we flesh out UI const styles = { container: { diff --git a/daemon/web/components/Login.js b/daemon/web/components/Login.js index cbe212e0..28fe05c0 100644 --- a/daemon/web/components/Login.js +++ b/daemon/web/components/Login.js @@ -14,25 +14,14 @@ export default class Login extends React.Component { } async handleLoginSubmit() { - const endpoint = '/user/login'; - const params = { - headers: { - 'Accept': 'application/json', - 'Content-Type': 'application/x-www-form-urlencoded' - }, - body: JSON.stringify({ - username: this.state.username, - password: this.state.password, - email: '', - admin: true - }) - }; - const response = await this.props.client._post(endpoint, params); + const response = await this.props.client.login( + this.state.username, + this.state.password + ); if (response.status !== 200) { this.setState({ loginAlert: 'Username and/or password is incorrect' }); return; } - this.props.history.push('/home'); } From 476394ee4b1fcea388f76136673bbe5619049cc6 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Fri, 20 Apr 2018 12:36:06 -0700 Subject: [PATCH 04/31] Faster daemon build if dependencies already installed --- .travis.yml | 2 +- Dockerfile | 8 +++++--- Makefile | 6 ++++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.travis.yml b/.travis.yml index ce68d759..93fa15ae 100644 --- a/.travis.yml +++ b/.travis.yml @@ -39,7 +39,7 @@ before_install: script: - make testenv VPS_OS="$VPS_OS" VPS_VERSION="$VERSION" SSH_PORT=69 - make testdaemon SSH_PORT=69 # Send test daemon to testVPS - - go test -v -race -coverprofile=coverage.out ./... # Execute tests + - go test -race -coverprofile=coverage.out ./... # Execute tests - goveralls -coverprofile=coverage.out -service=travis-ci -repotoken "$COVERALLS_TOKEN" - make lint # Static code analysis - docker kill $(docker ps -q) # Remove testvps, etc diff --git a/Dockerfile b/Dockerfile index af99f746..bd3da8f0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -14,19 +14,21 @@ RUN npm run build ### Part 2 - Building the Inertia daemon FROM golang:alpine AS daemon-build-env -ENV BUILD_HOME=/go/src/github.com/ubclaunchpad/inertia +ARG INERTIA_VERSION +ENV BUILD_HOME=/go/src/github.com/ubclaunchpad/inertia \ + INERTIA_VERSION=${INERTIA_VERSION} # Mount source code. ADD . ${BUILD_HOME} WORKDIR ${BUILD_HOME} # Install dependencies if not already available. -RUN apk add --update --no-cache git RUN if [ ! -d "vendor" ]; then \ + apk add --update --no-cache git; \ go get -u github.com/golang/dep/cmd/dep; \ dep ensure; \ fi # Build daemon binary. RUN go build -o /bin/inertia \ - -ldflags "-X main.Version=$(git describe --tags)" \ + -ldflags "-X main.Version=$INERTIA_VERSION" \ ./daemon/inertia ### Part 3 - Copy builds into combined image diff --git a/Makefile b/Makefile index cab398b6..971b19c5 100644 --- a/Makefile +++ b/Makefile @@ -74,7 +74,8 @@ testenv: # Requires Inertia version to be "test" testdaemon: rm -f ./inertia-daemon-image - docker build -t ubclaunchpad/inertia:test . + docker build --build-arg INERTIA_VERSION=$(TAG) \ + -t ubclaunchpad/inertia:test . docker save -o ./inertia-daemon-image ubclaunchpad/inertia:test docker rmi ubclaunchpad/inertia:test chmod 400 ./test/keys/id_rsa @@ -89,7 +90,8 @@ testdaemon: # Creates a daemon release and pushes it to Docker Hub repository. # Requires access to the UBC Launch Pad Docker Hub. daemon: - docker build -t ubclaunchpad/inertia:$(RELEASE) . + docker build --build-arg INERTIA_VERSION=$(RELEASE) \ + -t ubclaunchpad/inertia:$(RELEASE) . docker push ubclaunchpad/inertia:$(RELEASE) # Recompiles assets. Use whenever a script in client/bootstrap is From c5d935c46d0358b292b41c1dc7fbcbfbf18dd94b Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Fri, 20 Apr 2018 12:37:43 -0700 Subject: [PATCH 05/31] Decode JSON into struct pointer --- deploy.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy.go b/deploy.go index 54a52fb0..04f35e98 100644 --- a/deploy.go +++ b/deploy.go @@ -136,12 +136,12 @@ var deploymentStatusCmd = &cobra.Command{ switch resp.StatusCode { case http.StatusOK: fmt.Printf("(Status code %d) Daemon at remote '%s' online at %s\n", resp.StatusCode, deployment.Name, host) - var status common.DeploymentStatus + status := &common.DeploymentStatus{} err := json.NewDecoder(resp.Body).Decode(status) if err != nil { log.Fatal(err) } - println(formatStatus(&status)) + println(formatStatus(status)) case http.StatusForbidden: fmt.Printf("(Status code %d) Bad auth: %s\n", resp.StatusCode, body) case http.StatusNotFound: From 204c604ea4bce37a4620d7bbf3398ccaaf6d46c5 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Fri, 20 Apr 2018 23:10:13 -0700 Subject: [PATCH 06/31] Improved status response --- daemon/inertia/status.go | 14 +++++++++----- deploy.go | 4 ---- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/daemon/inertia/status.go b/daemon/inertia/status.go index 97167a78..8a6029d9 100644 --- a/daemon/inertia/status.go +++ b/daemon/inertia/status.go @@ -5,17 +5,20 @@ import ( "net/http" docker "github.com/docker/docker/client" + "github.com/ubclaunchpad/inertia/common" ) // statusHandler returns a formatted string about the status of the // deployment and lists currently active project containers func statusHandler(w http.ResponseWriter, r *http.Request) { - inertiaStatus := "inertia daemon " + daemonVersion + "\n" if deployment == nil { - http.Error( - w, inertiaStatus+msgNoDeployment, - http.StatusNotFound, - ) + status := &common.DeploymentStatus{ + InertiaVersion: Version, + Containers: make([]string, 0), + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + json.NewEncoder(w).Encode(status) return } @@ -30,6 +33,7 @@ func statusHandler(w http.ResponseWriter, r *http.Request) { http.Error(w, err.Error(), http.StatusInternalServerError) return } + status.InertiaVersion = Version w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusOK) diff --git a/deploy.go b/deploy.go index 04f35e98..24bb029e 100644 --- a/deploy.go +++ b/deploy.go @@ -144,10 +144,6 @@ var deploymentStatusCmd = &cobra.Command{ println(formatStatus(status)) case http.StatusForbidden: fmt.Printf("(Status code %d) Bad auth: %s\n", resp.StatusCode, body) - case http.StatusNotFound: - fmt.Printf("(Status code %d) Problem with deployment: %s\n", resp.StatusCode, body) - case http.StatusPreconditionFailed: - fmt.Printf("(Status code %d) Problem with deployment setup: %s\n", resp.StatusCode, body) default: fmt.Printf("(Status code %d) Unknown response from daemon: %s\n", resp.StatusCode, body) From a9be64d46ce6b0410dca76a9ba88a6948abb8a09 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Fri, 20 Apr 2018 23:12:54 -0700 Subject: [PATCH 07/31] Fetch and report status --- daemon/web/client.js | 11 +++--- daemon/web/components/Home.js | 71 ++++++++++++++++++++++++++--------- 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/daemon/web/client.js b/daemon/web/client.js index afca7119..4d677d00 100644 --- a/daemon/web/client.js +++ b/daemon/web/client.js @@ -31,16 +31,14 @@ export default class InertiaClient { } async validate() { - const params = { - headers: { 'Accept': 'application/json' }, - }; - return this._get('/user/validate', params); + return this._get('/user/validate', {}); } async getContainerLogs(container = 'inertia-daemon') { const endpoint = '/logs'; const params = { headers: { + 'Content-Type': 'application/json', 'Accept': 'text/plain' }, body: JSON.stringify({ @@ -55,6 +53,7 @@ export default class InertiaClient { const endpoint = '/status'; const params = { headers: { + 'Content-Type': 'application/json', 'Accept': 'application/json' } }; @@ -74,7 +73,7 @@ export default class InertiaClient { try { return await fetch(request); } catch (e) { - return Promise.reject(e); + return e; } } @@ -91,7 +90,7 @@ export default class InertiaClient { try { return await fetch(request); } catch (e) { - return Promise.reject(e); + return e; } } } diff --git a/daemon/web/components/Home.js b/daemon/web/components/Home.js index a119ea66..5c657b89 100644 --- a/daemon/web/components/Home.js +++ b/daemon/web/components/Home.js @@ -47,6 +47,28 @@ const sidebarButtonStyles = { } }; +const SidebarText = ({ children }) => ( +
+

+ {children} +

+
+); +const sidebarTextStyles = { + container: { + display: 'flex', + alignItems: 'center', + height: '3rem', + width: '100%', + paddingLeft: '3rem' + }, + + text: { + textDecoration: 'none', + color: '#101010' + } +}; + export default class Home extends React.Component { constructor(props) { super(props); @@ -54,14 +76,12 @@ export default class Home extends React.Component { loading: true, remoteVersion: '', - remoteStatus: '', repoBranch: '', repoCommitHash: '', repoCommitMessage: '', repoBuildType: '', repoBuilding: false, - containers: [], }; @@ -81,34 +101,52 @@ export default class Home extends React.Component { async handleGetStatus() { const response = await this.props.client.getRemoteStatus(); - switch (response.status) { - case 200: - console.log(JSON.parse(response.body)); - break; - case 404: - console.log(response); - break; - default: - Promise.reject(Error('bad response:', response.body)); - } + if (response.status !== 200) return new Error('bad response: ' + response); + const status = await response.json(); + this.setState({ + remoteVersion: status.version, + repoBranch: status.branch, + repoBuilding: status.build_active, + repoCommitHash: status.commit_hash, + repoCommitMessage: status.commit_message, + containers: status.containers, + }); } render() { + const containers = this.state.containers.map((c) => + {c} + ); + + const buildMessage = this.state.repoBuilding + ? {buildMessage} + : No deployment active; + const repoState = this.state.repoCommitHash + ? ( +
+ {this.state.repoBranch} ({this.state.repoBuildType}) + {this.state.repoCommitMessage} ({this.state.repoCommitHash}) +
+ ) + : null; + return (

Inertia Web

+

{this.state.remoteVersion}

logout
- Deployments - project-app - project-db - project-server + Status + {buildMessage} + {repoState} + Containers + {containers}
@@ -118,7 +156,6 @@ export default class Home extends React.Component {
-
); } From 7484ba9fe28785fec35ce55704b90f321db1aa1e Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Sat, 21 Apr 2018 10:37:50 -0700 Subject: [PATCH 08/31] Scaffold dashboard and log view --- daemon/web/client.js | 6 +-- daemon/web/components/Dashboard.js | 64 ++++++++++++++++++++++++ daemon/web/components/Home.js | 38 +++++++------- daemon/web/components/metrics/LogView.js | 35 +++++++++++++ deploy.go | 18 ++++--- 5 files changed, 134 insertions(+), 27 deletions(-) create mode 100644 daemon/web/components/Dashboard.js create mode 100644 daemon/web/components/metrics/LogView.js diff --git a/daemon/web/client.js b/daemon/web/client.js index 4d677d00..5992ddfb 100644 --- a/daemon/web/client.js +++ b/daemon/web/client.js @@ -38,10 +38,10 @@ export default class InertiaClient { const endpoint = '/logs'; const params = { headers: { - 'Content-Type': 'application/json', - 'Accept': 'text/plain' + 'Accept': 'text/plain', + 'Content-Type': 'application/json' }, - body: JSON.stringify({ + query: JSON.stringify({ container: container, stream: true, }) diff --git a/daemon/web/components/Dashboard.js b/daemon/web/components/Dashboard.js new file mode 100644 index 00000000..bbc3702e --- /dev/null +++ b/daemon/web/components/Dashboard.js @@ -0,0 +1,64 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import InertiaClient from '../client'; +import LogView from './metrics/LogView'; + +export default class Dashboard extends React.Component { + constructor(props) { + super(props); + this.state = { + errored: false, + logEntries: [], + }; + this.getLogs = this.getLogs.bind(this); + this.getMessage = this.getMessage.bind(this); + } + + async getLogs() { + try { + let resp; + if (this.props.container) { + resp = await this.props.client.getContainerLogs(); + } else { + resp = await this.props.client.getContainerLogs(this.props.container); + } + if (resp.status != 200) this.setState({ errored: true, logEntries: [] }); + const logs = await resp.json(); + console.log(logs) + } catch (e) { + console.log(e); + } + } + + getMessage() { + if (this.state.errored) { + return

Yikes, something went wrong

; + } else if (this.state.logEntries.length == 0) { + return

No logs to show

; + } + } + + render() { + this.getLogs(); + return ( +
+ {this.getMessage()} + +
+ ); + } +} + +Dashboard.propTypes = { + container: PropTypes.string, + client: PropTypes.instanceOf(InertiaClient), +}; + +const styles = { + underConstruction: { + textAlign: 'center', + fontSize: 24, + color: '#9f9f9f' + } +}; diff --git a/daemon/web/components/Home.js b/daemon/web/components/Home.js index 5c657b89..265952c3 100644 --- a/daemon/web/components/Home.js +++ b/daemon/web/components/Home.js @@ -2,6 +2,7 @@ import React from 'react'; import PropTypes from 'prop-types'; import InertiaClient from '../client'; +import Dashboard from './Dashboard'; const SidebarHeader = ({ children }) => (
@@ -25,9 +26,9 @@ const sidebarHeaderStyles = { } }; -const SidebarButton = ({ children }) => ( +const SidebarButton = ({ children, onClick }) => ( @@ -83,6 +84,8 @@ export default class Home extends React.Component { repoBuildType: '', repoBuilding: false, containers: [], + + viewContainer: '', }; this.handleLogout = this.handleLogout.bind(this); @@ -107,6 +110,7 @@ export default class Home extends React.Component { remoteVersion: status.version, repoBranch: status.branch, repoBuilding: status.build_active, + repoBuildType: status.build_type, repoCommitHash: status.commit_hash, repoCommitMessage: status.commit_message, containers: status.containers, @@ -114,18 +118,25 @@ export default class Home extends React.Component { } render() { + // Render container list const containers = this.state.containers.map((c) => - {c} + { + this.setState({ viewContainer: c }); + }} + key={c} >{c} ); + // Report repository status const buildMessage = this.state.repoBuilding - ? {buildMessage} - : No deployment active; + ? Build in progress + : null; const repoState = this.state.repoCommitHash ? (
- {this.state.repoBranch} ({this.state.repoBuildType}) - {this.state.repoCommitMessage} ({this.state.repoCommitHash}) + Type: {this.state.repoBuildType} + Branch: {this.state.repoBranch} + Commit {this.state.repoCommitHash.substr(1, 5)}: {this.state.repoCommitMessage}
) : null; @@ -150,11 +161,10 @@ export default class Home extends React.Component {
-
-

coming soon!

-
+
-
); @@ -210,10 +220,4 @@ const styles = { button: { flex: 'none' }, - - underConstruction: { - textAlign: 'center', - fontSize: 24, - color: '#9f9f9f' - } }; diff --git a/daemon/web/components/metrics/LogView.js b/daemon/web/components/metrics/LogView.js new file mode 100644 index 00000000..ed6a2190 --- /dev/null +++ b/daemon/web/components/metrics/LogView.js @@ -0,0 +1,35 @@ +import React from 'react'; +import PropTypes from 'prop-types'; + +import InertiaClient from '../../client'; + +export default class LogView extends React.Component { + constructor(props) { + super(props); + + this.getEntries = this.getEntries.bind(this); + } + + getEntries() { + let logText = ''; + for (let i = this.props.logs.length; i > 0; i--) { + logText = logText + this.props.logs[i] + '\n'; + } + return ( +

{logText}

+ ); + } + + render() { + const resultList = this.getEntries(); + return ( +
+ {resultList} +
+ ); + } +} + +LogView.propTypes = { + logs: PropTypes.array, +}; diff --git a/deploy.go b/deploy.go index 24bb029e..d9c1b125 100644 --- a/deploy.go +++ b/deploy.go @@ -127,12 +127,6 @@ var deploymentStatusCmd = &cobra.Command{ log.Fatal(err) } - body, err := ioutil.ReadAll(resp.Body) - if err != nil { - log.Fatal(err) - } - defer resp.Body.Close() - switch resp.StatusCode { case http.StatusOK: fmt.Printf("(Status code %d) Daemon at remote '%s' online at %s\n", resp.StatusCode, deployment.Name, host) @@ -143,9 +137,19 @@ var deploymentStatusCmd = &cobra.Command{ } println(formatStatus(status)) case http.StatusForbidden: + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() fmt.Printf("(Status code %d) Bad auth: %s\n", resp.StatusCode, body) default: - fmt.Printf("(Status code %d) Unknown response from daemon: %s\n", + body, err := ioutil.ReadAll(resp.Body) + if err != nil { + log.Fatal(err) + } + defer resp.Body.Close() + fmt.Printf("(Status code %d) %s\n", resp.StatusCode, body) } }, From a098d48e8dd6aa073dc1e815ce7adcf09c69b143 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Sat, 21 Apr 2018 17:02:43 -0700 Subject: [PATCH 09/31] Add link to DevOpsDays 2018 presentation --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8e1069af..6cae7f84 100644 --- a/README.md +++ b/README.md @@ -160,8 +160,8 @@ The deployment daemon runs persistently in the background on the server, receivi Inertia is set up serverside by executing a script over SSH that installs Docker and starts an Inertia daemon image with [access to the host Docker socket](https://bobheadxi.github.io/dockerception/#docker-in-docker). This Docker-in-Docker configuration gives the daemon the ability to start up other containers *alongside* it, rather than *within* it, as required. Once the daemon is set up, we avoid using further SSH commands and execute Docker commands through Docker's Golang API. Instead of installing the docker-compose toolset, we [use a docker-compose image](https://bobheadxi.github.io/dockerception/#docker-compose-in-docker) to build and deploy user projects. Inertia also supports projects configured for Heroku buildpacks using the [gliderlabs/herokuish](https://github.com/gliderlabs/herokuish) Docker image for builds and deployments. The team has made a few presentations about Inertia that go over its design in a bit more detail: -- [First UBC Launch Pad internal demo](https://drive.google.com/file/d/1foO57l6egbaQ7I5zIDDe019XOgJm-ocn/view?usp=sharing) -- [Vancouver DevOpsDays 2018: Building a Simple, Self-hosted Continuous Deployment Application](https://drive.google.com/open?id=1DV2NR_YXpUZai-S7ttGcwrhWJXL7BcwiIrBJn69-IJg) +- [UBC Launch Pad internal demo](https://drive.google.com/file/d/1foO57l6egbaQ7I5zIDDe019XOgJm-ocn/view?usp=sharing) +- [Vancouver DevOpsDays 2018](https://drive.google.com/open?id=1DV2NR_YXpUZai-S7ttGcwrhWJXL7BcwiIrBJn69-IJg) ([video](https://youtu.be/amBYMEKGzTs?t=4h59m5s)) # :books: Contributing From 66adae9ffb24f3a0bb7860f59160a0feb66891d7 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Sat, 21 Apr 2018 19:43:16 -0700 Subject: [PATCH 10/31] Update /status and PermissionsHandler tests --- daemon/inertia/auth/permissions_test.go | 2 +- daemon/inertia/status_test.go | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/daemon/inertia/auth/permissions_test.go b/daemon/inertia/auth/permissions_test.go index 7736cc4e..c2653c57 100644 --- a/daemon/inertia/auth/permissions_test.go +++ b/daemon/inertia/auth/permissions_test.go @@ -132,7 +132,7 @@ func TestServeHTTPWithUserLoginAndLogout(t *testing.T) { assert.True(t, len(logoutResp.Cookies()) > 0) cookie = logoutResp.Cookies()[0] assert.Equal(t, "ubclaunchpad-inertia", cookie.Name) - assert.Equal(t, -1, cookie.MaxAge) + assert.Equal(t, 0, cookie.MaxAge) } func TestServeHTTPWithUserLoginAndAccept(t *testing.T) { diff --git a/daemon/inertia/status_test.go b/daemon/inertia/status_test.go index 1de796c0..7f949f4a 100644 --- a/daemon/inertia/status_test.go +++ b/daemon/inertia/status_test.go @@ -27,7 +27,7 @@ func TestStatusHandlerBuildInProgress(t *testing.T) { } // Assmble request - req, err := http.NewRequest("POST", "/status", nil) + req, err := http.NewRequest("GET", "/status", nil) assert.Nil(t, err) // Record responses @@ -54,7 +54,7 @@ func TestStatusHandlerNoContainers(t *testing.T) { } // Assmble request - req, err := http.NewRequest("POST", "/status", nil) + req, err := http.NewRequest("GET", "/status", nil) assert.Nil(t, err) // Record responses @@ -81,7 +81,7 @@ func TestStatusHandlerActiveContainers(t *testing.T) { } // Assmble request - req, err := http.NewRequest("POST", "/status", nil) + req, err := http.NewRequest("GET", "/status", nil) assert.Nil(t, err) // Record responses @@ -104,7 +104,7 @@ func TestStatusHandlerStatusError(t *testing.T) { } // Assmble request - req, err := http.NewRequest("POST", "/status", nil) + req, err := http.NewRequest("GET", "/status", nil) assert.Nil(t, err) // Record responses @@ -117,7 +117,7 @@ func TestStatusHandlerStatusError(t *testing.T) { func TestStatusHandlerNoDeployment(t *testing.T) { // Assmble request - req, err := http.NewRequest("POST", "/status", nil) + req, err := http.NewRequest("GET", "/status", nil) assert.Nil(t, err) // Record responses @@ -125,6 +125,6 @@ func TestStatusHandlerNoDeployment(t *testing.T) { handler := http.HandlerFunc(statusHandler) handler.ServeHTTP(recorder, req) - assert.Equal(t, recorder.Code, http.StatusNotFound) - assert.Contains(t, recorder.Body.String(), msgNoDeployment) + assert.Equal(t, recorder.Code, http.StatusOK) + assert.Contains(t, recorder.Body.String(), "\"branch\":\"\"") } From b75ed4ddf3972168461978de5e9311b057edaf10 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Sat, 21 Apr 2018 19:58:01 -0700 Subject: [PATCH 11/31] Refactor walkthrough helper into input.go, add tests for format.go --- format.go | 25 +++++++---- format_test.go | 47 +++++++++++++++++++++ input.go | 73 +++++++++++++++++++++++++++++++++ remote_test.go => input_test.go | 0 remote.go | 65 ----------------------------- 5 files changed, 136 insertions(+), 74 deletions(-) create mode 100644 format_test.go create mode 100644 input.go rename remote_test.go => input_test.go (100%) diff --git a/format.go b/format.go index 653c5653..bcf46a02 100644 --- a/format.go +++ b/format.go @@ -7,29 +7,36 @@ import ( const ( msgBuildInProgress = "It appears that your build is still in progress." msgNoContainersActive = "No containers are active." + msgNoDeployment = "No deployment found - try running 'inertia [VPS] up'" ) -func formatStatus(status *common.DeploymentStatus) string { - inertiaStatus := "inertia daemon " + status.InertiaVersion + "\n" - branchStatus := " - Branch: " + status.Branch + "\n" - commitStatus := " - Commit: " + status.CommitHash + "\n" - commitMessage := " - Message: " + status.CommitMessage + "\n" - buildTypeStatus := " - Build Type: " + status.BuildType + "\n" +func formatStatus(s *common.DeploymentStatus) string { + inertiaStatus := "inertia daemon " + s.InertiaVersion + "\n" + branchStatus := " - Branch: " + s.Branch + "\n" + commitStatus := " - Commit: " + s.CommitHash + "\n" + commitMessage := " - Message: " + s.CommitMessage + "\n" + buildTypeStatus := " - Build Type: " + s.BuildType + "\n" statusString := inertiaStatus + branchStatus + commitStatus + commitMessage + buildTypeStatus + // If no branch/commit, then it's likely the deployment has not + // been instantiated on the remote yet + if s.Branch == "" && s.CommitHash == "" && s.CommitMessage == "" { + return statusString + msgNoDeployment + } + // If build container is active, that means that a build // attempt was made but only the daemon and docker-compose // are active, indicating a build failure or build-in-progress - if len(status.Containers) == 0 { + if len(s.Containers) == 0 { errorString := statusString + msgNoContainersActive - if status.BuildContainerActive { + if s.BuildContainerActive { errorString = statusString + msgBuildInProgress } return errorString } activeContainers := "Active containers:\n" - for _, container := range status.Containers { + for _, container := range s.Containers { activeContainers += " - " + container + "\n" } statusString += activeContainers diff --git a/format_test.go b/format_test.go new file mode 100644 index 00000000..871927da --- /dev/null +++ b/format_test.go @@ -0,0 +1,47 @@ +package main + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/ubclaunchpad/inertia/common" +) + +func TestFormatStatus(t *testing.T) { + output := formatStatus(&common.DeploymentStatus{ + InertiaVersion: "9000", + Branch: "call", + CommitHash: "me", + CommitMessage: "maybe", + BuildContainerActive: true, + Containers: []string{"wow"}, + }) + assert.Contains(t, output, "inertia daemon 9000") + assert.Contains(t, output, "Active containers") +} + +func TestFormatStatusBuildActive(t *testing.T) { + output := formatStatus(&common.DeploymentStatus{ + InertiaVersion: "9000", + Branch: "call", + CommitHash: "me", + CommitMessage: "maybe", + BuildContainerActive: true, + Containers: make([]string, 0), + }) + assert.Contains(t, output, "inertia daemon 9000") + assert.Contains(t, output, msgBuildInProgress) +} + +func TestFormatStatusNoDeployment(t *testing.T) { + output := formatStatus(&common.DeploymentStatus{ + InertiaVersion: "9000", + Branch: "", + CommitHash: "", + CommitMessage: "", + BuildContainerActive: false, + Containers: make([]string, 0), + }) + assert.Contains(t, output, "inertia daemon 9000") + assert.Contains(t, output, msgNoDeployment) +} diff --git a/input.go b/input.go new file mode 100644 index 00000000..5d7297a9 --- /dev/null +++ b/input.go @@ -0,0 +1,73 @@ +package main + +import ( + "fmt" + "io" + "os" + "path/filepath" + + "github.com/ubclaunchpad/inertia/client" +) + +// addRemoteWalkthough is the command line walkthrough that asks +// users for RemoteVPS details +func addRemoteWalkthrough(in io.Reader, name, port, sshPort, currBranch string, config *client.Config) error { + homeEnvVar := os.Getenv("HOME") + sshDir := filepath.Join(homeEnvVar, ".ssh") + defaultSSHLoc := filepath.Join(sshDir, "id_rsa") + + var response string + fmt.Println("Enter location of PEM file (leave blank to use '" + defaultSSHLoc + "'):") + _, err := fmt.Fscanln(in, &response) + if err != nil { + response = defaultSSHLoc + } + pemLoc := response + + fmt.Println("Enter user:") + n, err := fmt.Fscanln(in, &response) + if err != nil || n == 0 { + return errInvalidUser + } + user := response + + fmt.Println("Enter IP address of remote:") + n, err = fmt.Fscanln(in, &response) + if err != nil || n == 0 { + return errInvalidAddress + } + address := response + + fmt.Println("Enter webhook secret:") + n, err = fmt.Fscanln(in, &response) + if err != nil || n == 0 { + return errInvalidSecret + } + secret := response + + branch := currBranch + fmt.Println("Enter project branch to deploy (leave blank for current branch):") + n, err = fmt.Fscanln(in, &response) + if err == nil && n != 0 { + branch = response + } + + fmt.Println("\nPort " + port + " will be used as the daemon port.") + fmt.Println("Port " + sshPort + " will be used as the SSH port.") + fmt.Println("Run 'inertia remote add' with the -p flag to set a custom Daemon port") + fmt.Println("of the -ssh flag to set a custom SSH port.") + + config.AddRemote(&client.RemoteVPS{ + Name: name, + IP: address, + User: user, + PEM: pemLoc, + Branch: branch, + Daemon: &client.DaemonConfig{ + Port: port, + SSHPort: sshPort, + Secret: secret, + }, + }) + return config.Write() +} diff --git a/remote_test.go b/input_test.go similarity index 100% rename from remote_test.go rename to input_test.go diff --git a/remote.go b/remote.go index 0d36b49a..d9e1a615 100644 --- a/remote.go +++ b/remote.go @@ -3,9 +3,7 @@ package main import ( "errors" "fmt" - "io" "os" - "path/filepath" log "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -78,69 +76,6 @@ file. Specify a VPS name.`, }, } -// addRemoteWalkthough is the command line walkthrough that asks -// users for RemoteVPS details -func addRemoteWalkthrough(in io.Reader, name, port, sshPort, currBranch string, config *client.Config) error { - homeEnvVar := os.Getenv("HOME") - sshDir := filepath.Join(homeEnvVar, ".ssh") - defaultSSHLoc := filepath.Join(sshDir, "id_rsa") - - var response string - fmt.Println("Enter location of PEM file (leave blank to use '" + defaultSSHLoc + "'):") - _, err := fmt.Fscanln(in, &response) - if err != nil { - response = defaultSSHLoc - } - pemLoc := response - - fmt.Println("Enter user:") - n, err := fmt.Fscanln(in, &response) - if err != nil || n == 0 { - return errInvalidUser - } - user := response - - fmt.Println("Enter IP address of remote:") - n, err = fmt.Fscanln(in, &response) - if err != nil || n == 0 { - return errInvalidAddress - } - address := response - - fmt.Println("Enter webhook secret:") - n, err = fmt.Fscanln(in, &response) - if err != nil || n == 0 { - return errInvalidSecret - } - secret := response - - branch := currBranch - fmt.Println("Enter project branch to deploy (leave blank for current branch):") - n, err = fmt.Fscanln(in, &response) - if err == nil && n != 0 { - branch = response - } - - fmt.Println("\nPort " + port + " will be used as the daemon port.") - fmt.Println("Port " + sshPort + " will be used as the SSH port.") - fmt.Println("Run 'inertia remote add' with the -p flag to set a custom Daemon port") - fmt.Println("of the -ssh flag to set a custom SSH port.") - - config.AddRemote(&client.RemoteVPS{ - Name: name, - IP: address, - User: user, - PEM: pemLoc, - Branch: branch, - Daemon: &client.DaemonConfig{ - Port: port, - SSHPort: sshPort, - Secret: secret, - }, - }) - return config.Write() -} - var listCmd = &cobra.Command{ Use: "ls", Short: "List currently configured remotes", From 97d37d6de930c68487064f95ef0b215cff09a7ca Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Sun, 22 Apr 2018 20:47:06 -0700 Subject: [PATCH 12/31] Add bootstrap and deps to "make", update contribution guide --- .github/CONTRIBUTING.md | 162 ++++++++++++++++++++++++++-------------- Makefile | 2 +- README.md | 4 +- client/bootstrap.go | 2 +- test/deps.sh | 2 + 5 files changed, 113 insertions(+), 59 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index b39db661..b8c2c6f4 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing +# :books: Contributing This document outlines key considerations and tips for anyone contributing to Inertia. @@ -10,19 +10,29 @@ This document outlines key considerations and tips for anyone contributing to In # Opening an Issue -Please do a quick search of past issues before opening a ticket. If working on a ticket, please assign it to yourself or leave a comment saying you are working on it. If you have decide to stop working on a ticket before it gets resolved, please un-assign yourself. +πŸŽ‰ An issue, whether it be bugs, feature requests, or general feedback is welcome! + +However, please do a quick search of past issues before opening a ticket. If you are working on a ticket, please assign it to yourself or leave a comment noting that you are working on it - similarly, if you decide to stop working on a ticket before it gets resolved, please un-assign yourself or leave a comment. This helps us keep track of which tickets are in progress. # Submitting a Pull Request +πŸ‘ Contributions of any size are very much appreciated. + All pull requests should be connected to one or more issues. Please try to fill out the pull request template to the best of your ability and use a clear, descriptive title. At the very least, all pull requests need to pass our Travis builds and receive an approval from a reviewer. Please include tests whenever possible. +Read on for a comprehensive guide on how to get started with Inertia's codebase! + # Development Tips -This section outlines the various tools available to help you get started developing Inertia. Run `make ls` to list all the Makefile shortcuts available. +πŸ‘· This section will walk you through Inertia's codebase, how to get a development environment set up, and outline the various tools available to help you out. -If you would like to contribute, feel free to comment on an issue or make one and open up a pull request! +Please free free to open up a ticket if any of these instructions are unclear or straight up do not work on your platform! + +- [Setup](#setup) +- [Overview](#overview) +- [Testing Environment](#setting-up-a-testing-environment) ## Setup @@ -32,69 +42,70 @@ First, [install Go](https://golang.org/doc/install#install) and grab Inertia's s $> go get -u github.com/ubclaunchpad/inertia ``` -We use [dep](https://github.com/golang/dep) for managing Golang dependencies, and [npm](https://www.npmjs.com) to manage dependencies for Inertia's React web app. Make sure both are installed before running the following commands. +If you are looking to contribute, you can then set your own fork as a remote: ```bash -$> make deps # Install all dependencies -$> make RELEASE=test # installs Inertia build tagged as "test" -$> inertia --version # check what version you have installed +$> git remote rename origin upstream # Set the official repo as you + # "upstream" so you can pull + # updates +$> git remote add origin https://github.com/$AMAZING_YOU/inertia.git ``` -A build tagged as `test` allows you to use `make testdaemon` for local development. See the next section for more details. - -Alternatively, you can manually edit `.inertia.toml` to use your desired daemon version - see the [Release Streams](#release-streams) documentation for more details. +Inertia uses: +- [dep](https://github.com/golang/dep) for managing Golang dependencies +- [npm](https://www.npmjs.com) to manage dependencies for Inertia's React web app +- [Docker](https://www.docker.com/community-edition) for various application functionalities and integration testing -Note that if you install Inertia using these commands or any variation of `go install`, you may have to remove the binary using `go clean -i github.com/ubclaunchpad/inertia` to go back to using an Inertia CLI installed using Homebrew. To go back to a `go install`ed version of Inertia, you need to run `brew uninstall inertia`. +Make sure all of the above are installed before running: -## Repository Structure - -The codebase for the CLI is in the root directory. This code should only include the user interface - all client-based logic and functionality should go into the client. +```bash +$> make RELEASE=test # installs dependencies and an Inertia + # build tagged as "test" to gopath +$> inertia --version # check what version you have installed +``` -### Client +A build tagged as `test` allows you to use `make testdaemon` for local development. See the next section for more details. Alternatively, you can manually edit `.inertia.toml` to use your desired daemon version - see the [Release Streams](#release-streams) documentation for more details. -The Inertia client manages all clientside functionality. The client codebase is in `client/`. +Note that if you install Inertia using these commands or any variation of `go install`, you may have to remove the binary using `go clean -i github.com/ubclaunchpad/inertia` to use an Inertia CLI installed using Homebrew. To go back to a `go install`ed version of Inertia, you need to run `brew uninstall inertia`. -### Daemon +## Overview -The Inertia daemon manages all serverside functionality. The daemon codebase is in `daemon/inertia`. +### CLI -### Inertia Web +The codebase for the CLI is in the root directory. This code should only include the user interface - all client-based logic and functionality should go into the client. -The Inertia Web application provides a web interface to manage an Inertia deployment. The web application codebase is in `daemon/web`. +### Client -## Testing and Locally Deploying +The Inertia client manages all clientside functionality. The client codebase is in `./client/`. -You will need Docker installed and running to run the Inertia test suite, which includes a number of integration tests. +To bootstrap servers, some bash scripting is often involved, but we'd like to avoid shipping bash scripts with our go binary. So we use [go-bindata](https://github.com/jteeuwen/go-bindata) to compile shell scripts into our go executables. ```bash -$> make test-all # test against ubuntu:latest -$> make test-all VPS_OS=ubuntu VERSION=14.04 # test against ubuntu:14.04 +$> go get -u github.com/jteeuwen/go-bindata/... ``` -You can also manually start a container that sets up a mock VPS for testing: +If you make changes to the bootstrapping shell scripts in `client/bootstrap/`, convert them to `Assets` by running: ```bash -$> make testenv VPS_OS=ubuntu VERSION=16.04 -# This defaults to ubuntu:lastest without args. -# Note the location of the key that is printed and use that when -# adding your local remote. +$> make bootstrap ``` -You can [SSH into this testvps container](https://bobheadxi.github.io/dockerception/#ssh-services-in-docker) and otherwise treat it just as you would treat a real VPS: +Then use your asset! -```bash -$> cd /path/to/my/dockercompose/project -$> inertia init -$> inertia remote add local -# PEM file: inertia/test/keys/id_rsa, User: 'root', Address: 0.0.0.0 -$> inertia local init -$> inertia local status -``` +```go +shellScriptData, err := Asset("cmd/bootstrap/myshellscript.sh") +if err != nil { + log.Fatal("No asset with that name") +} -The above steps will pull and use a daemon image from Docker Hub based on the version in your `.inertia.toml`. +// Optionally run shell script over SSH. +result, _ := remote.RunSSHCommand(string(shellScriptData)) +``` ### Daemon +The Inertia daemon manages all serverside functionality. The daemon codebase is in `./daemon/inertia/`. + To use a daemon compiled from source, set your Inertia version in `.inertia.toml` to `test` and run: ```bash @@ -121,38 +132,77 @@ You probably need to go into your Docker settings and add this line to the Docke This sneaky configuration file can be found under `Docker -> Preferences -> Daemon -> Advanced -> Edit File`. -### Web App +### Web + +The Inertia Web application provides a web interface to manage an Inertia deployment. The web application codebase is in `./daemon/web/`. -Inertia Web is a React application. To run a local instance of Inertia Web: +To run a local instance of Inertia Web: ```bash -$> make web-run +$> make web-deps # install npm dependencies +$> make web-run # run local instance of application ``` Make sure you have a local daemon set up for this web app to work - see the previous section for more details. -## Compiling Bash Scripts +## Setting up a Testing Environment -To bootstrap servers, some bash scripting is often involved, but we'd like to avoid shipping bash scripts with our go binary. So we use [go-bindata](https://github.com/jteeuwen/go-bindata) to compile shell scripts into our go executables. +You will need Docker installed and running to run whole the Inertia test suite, which includes a number of integration tests. ```bash -$> go get -u github.com/jteeuwen/go-bindata/... +$> make test-all # test against ubuntu:latest +$> make test-all VPS_OS=ubuntu VERSION=14.04 # test against ubuntu:14.04 ``` -If you make changes to the bootstrapping shell scripts in `client/bootstrap/`, convert them to `Assets` by running: +Alternatively, `make test` will just run the unit tests. + +Setting up a more comprehensive test environment, where you take a project from setup to deployment using Inertia, is a bit trickier - these are the recommended steps: + +1. **Manually set up a mock VPS** ```bash -$> make bootstrap +$> make testenv VPS_OS=ubuntu VERSION=16.04 +# This defaults to ubuntu:lastest without args. +# Note the location of the key that is printed and use that when +# adding your local remote. ``` -Then use your asset! +You can [SSH into this testvps container](https://bobheadxi.github.io/dockerception/#ssh-services-in-docker) and otherwise treat it just as you would treat a real VPS. -```go -shellScriptData, err := Asset("cmd/bootstrap/myshellscript.sh") -if err != nil { - log.Fatal("No asset with that name") -} +2. **Compile and install Inertia** -// Optionally run shell script over SSH. -result, _ := remote.RunSSHCommand(string(shellScriptData)) +```bash +$> make RELEASE=test +``` + +3. **Build and deliver Inertia daemon to the `testvps`** + +```bash +$> make testdaemon +``` + +4. **Set up a test project** + +You will need a GitHub repository you own, since you need permission to add deploy keys. The Inertia team typically uses the [inertia-deploy-test](https://github.com/ubclaunchpad/inertia-deploy-test) repository - you could just fork this repository. + +```bash +$> git clone https://github.com/$AWESOME_YOU/inertia-deploy-test.git +$> cd inertia-deploy-test +$> inertia init +$> inertia remote add local +# - PEM file: $INERTIA_PATH/test/keys/id_rsa +# - Address: 127.0.0.1 +# - User: root +$> inertia local init +$> inertia local status +``` + +The above steps will pull and use a daemon image from Docker Hub based on the version in your `.inertia.toml`. + +Following these steps, you can run Inertia through deployment: + +```bash +$> inertia local up --stream +$> inertia local status +$> inertia local logs ``` diff --git a/Makefile b/Makefile index bfdaba89..f1243db5 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ VPS_VERSION = latest VPS_OS = ubuntu RELEASE = canary -all: inertia +all: bootstrap deps inertia # List all commands ls: diff --git a/README.md b/README.md index 6cae7f84..dfdc3fce 100644 --- a/README.md +++ b/README.md @@ -165,4 +165,6 @@ The team has made a few presentations about Inertia that go over its design in a # :books: Contributing -Any contribution (pull requests, feedback, bug reports, ideas, etc.) is welcome! Please see our [contribution guide](https://github.com/ubclaunchpad/inertia/blob/master/.github/CONTRIBUTING.md) for more details and development tips. +Any contribution (pull requests, feedback, bug reports, ideas, etc.) is welcome! + +Please see our [contribution guide](https://github.com/ubclaunchpad/inertia/blob/master/.github/CONTRIBUTING.md) for contribution guidelines and a detailed guide to help you get started with Inertia's codebase. diff --git a/client/bootstrap.go b/client/bootstrap.go index e0931dbf..3fbe38a3 100644 --- a/client/bootstrap.go +++ b/client/bootstrap.go @@ -107,7 +107,7 @@ func clientBootstrapDaemonUpSh() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "client/bootstrap/daemon-up.sh", size: 1707, mode: os.FileMode(493), modTime: time.Unix(1524293604, 0)} + info := bindataFileInfo{name: "client/bootstrap/daemon-up.sh", size: 1707, mode: os.FileMode(493), modTime: time.Unix(1524325552, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/test/deps.sh b/test/deps.sh index 2bfa0e91..aeba7e27 100644 --- a/test/deps.sh +++ b/test/deps.sh @@ -2,11 +2,13 @@ METALINTER_DIR=bin +echo "Installing linter" if [ ! -d "$METALINTER_DIR" ]; then curl -sfL https://install.goreleaser.com/github.com/alecthomas/gometalinter.sh | bash else echo "./bin directory detected - skipping gometalinter install" fi +echo "Installing build images" docker pull docker/compose:1.21.0 docker pull gliderlabs/herokuish:v0.4.0 From e6b09d4009a6a56de1e13af34c7b6d94c102cff4 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Sun, 22 Apr 2018 22:16:24 -0700 Subject: [PATCH 13/31] Improve error handling, fix double-writing non-stream log requests Using logger.Success() was writing to both stdout and the user - thus, successive calls to "inertia local logs" will have duplicated timestamps --- daemon/inertia/logs.go | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/daemon/inertia/logs.go b/daemon/inertia/logs.go index db63b61f..3bcd8c98 100644 --- a/daemon/inertia/logs.go +++ b/daemon/inertia/logs.go @@ -3,6 +3,7 @@ package main import ( "bytes" "encoding/json" + "fmt" "io/ioutil" "net/http" "strings" @@ -17,6 +18,7 @@ func logHandler(w http.ResponseWriter, r *http.Request) { // Get container name from request body, err := ioutil.ReadAll(r.Body) if err != nil { + println(err.Error()) http.Error(w, err.Error(), http.StatusLengthRequired) return } @@ -24,6 +26,7 @@ func logHandler(w http.ResponseWriter, r *http.Request) { var upReq common.DaemonRequest err = json.Unmarshal(body, &upReq) if err != nil { + println(err.Error()) http.Error(w, err.Error(), http.StatusBadRequest) return } @@ -60,9 +63,12 @@ func logHandler(w http.ResponseWriter, r *http.Request) { if upReq.Stream { stop := make(chan struct{}) common.FlushRoutine(w, logs, stop) + defer close(stop) } else { buf := new(bytes.Buffer) buf.ReadFrom(logs) - logger.Success(buf.String(), http.StatusOK) + w.Header().Set("Content-Type", "text/html") + w.WriteHeader(http.StatusOK) + fmt.Fprint(w, buf.String()) } } From 3ed1b0e4a3e5b18ddac5411daf7b1c80cb747466 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Sun, 22 Apr 2018 22:20:01 -0700 Subject: [PATCH 14/31] Update webhook URL documentation Use 'host/webhook' instead of just 'host/' (less ambiguity) Change in daemon handler was implemented previously --- README.md | 2 +- client/remote.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index dfdc3fce..1d692d9b 100644 --- a/README.md +++ b/README.md @@ -107,7 +107,7 @@ To enable continuous deployment, you need the webhook URL that is printed during ```bash GitHub WebHook URL (add here https://www.github.com//settings/hooks/new): -http://myhost.com:8081 +http://myhost.com:8081/webhook Github WebHook Secret: inertia ``` diff --git a/client/remote.go b/client/remote.go index c18c8dd2..c41881d3 100644 --- a/client/remote.go +++ b/client/remote.go @@ -92,7 +92,7 @@ func (remote *RemoteVPS) Bootstrap(runner SSHSession, name string, config *Confi // Output Webhook url to user. println(">> GitHub WebHook URL (add to https://www.github.com//settings/hooks/new): ") - println("WebHook Address: https://" + remote.IP + ":" + remote.Daemon.Port) + println("WebHook Address: https://" + remote.IP + ":" + remote.Daemon.Port + "/webhook") println("WebHook Secret: " + remote.Daemon.Secret) println(`Note that you will have to disable SSH verification in your webhook settings - Inertia uses self-signed certificates that GitHub won't From 3c556d59cd32ace2ff695d7036bbf1395c46ee5d Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Mon, 23 Apr 2018 02:18:14 -0700 Subject: [PATCH 15/31] Barely functional log streaming UI changes etc etc as well --- daemon/inertia/project/docker.go | 2 + daemon/web/client.js | 16 +++---- daemon/web/components/App.js | 4 +- daemon/web/components/Dashboard.js | 35 ++++++++++++--- daemon/web/components/Home.js | 55 +++++++++++------------- daemon/web/components/metrics/LogView.js | 32 ++++++++++---- 6 files changed, 90 insertions(+), 54 deletions(-) diff --git a/daemon/inertia/project/docker.go b/daemon/inertia/project/docker.go index f5a874c0..749dcb8f 100644 --- a/daemon/inertia/project/docker.go +++ b/daemon/inertia/project/docker.go @@ -22,6 +22,7 @@ var ( type LogOptions struct { Container string Stream bool + Detailed bool } // ContainerLogs get logs ;) @@ -32,6 +33,7 @@ func ContainerLogs(cli *docker.Client, opts LogOptions) (io.ReadCloser, error) { ShowStderr: true, Follow: opts.Stream, Timestamps: true, + Details: opts.Detailed, }) } diff --git a/daemon/web/client.js b/daemon/web/client.js index 5992ddfb..ff29c7fb 100644 --- a/daemon/web/client.js +++ b/daemon/web/client.js @@ -34,19 +34,18 @@ export default class InertiaClient { return this._get('/user/validate', {}); } - async getContainerLogs(container = 'inertia-daemon') { + async getContainerLogs(container = '/inertia-daemon') { const endpoint = '/logs'; const params = { headers: { - 'Accept': 'text/plain', - 'Content-Type': 'application/json' + 'Content-Type': 'application/json', }, - query: JSON.stringify({ - container: container, + body: JSON.stringify({ stream: true, + container: container, }) }; - return this._get(endpoint, params); + return this._post(endpoint, params); } async getRemoteStatus() { @@ -73,7 +72,7 @@ export default class InertiaClient { try { return await fetch(request); } catch (e) { - return e; + throw e; } } @@ -86,11 +85,10 @@ export default class InertiaClient { params.method = 'POST'; params.credentials = 'include'; const request = new Request(this.url + endpoint, params); - try { return await fetch(request); } catch (e) { - return e; + throw e; } } } diff --git a/daemon/web/components/App.js b/daemon/web/components/App.js index e719b5f4..246eac70 100644 --- a/daemon/web/components/App.js +++ b/daemon/web/components/App.js @@ -43,7 +43,9 @@ export default class App extends React.Component { const history = createHistory(); history.listen(() => { - this.setState({ loading: true, authenticated: false }); + this.setState({ + loading: true, + }); this.isAuthenticated().then((authenticated) => { this.setState({ loading: false, diff --git a/daemon/web/components/Dashboard.js b/daemon/web/components/Dashboard.js index bbc3702e..36d1f0c4 100644 --- a/daemon/web/components/Dashboard.js +++ b/daemon/web/components/Dashboard.js @@ -16,18 +16,44 @@ export default class Dashboard extends React.Component { } async getLogs() { + this.setState({ errored: false, logEntries: [] }); try { let resp; - if (this.props.container) { + if (!this.props.container) { resp = await this.props.client.getContainerLogs(); } else { resp = await this.props.client.getContainerLogs(this.props.container); } if (resp.status != 200) this.setState({ errored: true, logEntries: [] }); - const logs = await resp.json(); - console.log(logs) + + const reader = resp.body.getReader(); + const decoder = new TextDecoder('utf-8'); + const stream = () => { + return reader.read().then((data) => { + const parts = decoder.decode(data.value).split('\n'); + this.setState({ + logEntries: this.state.logEntries.concat(parts), + }); + return stream(); + }); + }; + stream(); } catch (e) { - console.log(e); + this.setState({ + errored: true, + logEntries: [], + }); + console.error(e); + } + } + + componentDidMount() { + this.getLogs(); + } + + componentDidUpdate(prevProps) { + if (prevProps.container != this.props.container) { + this.getLogs(); } } @@ -40,7 +66,6 @@ export default class Dashboard extends React.Component { } render() { - this.getLogs(); return (
{this.getMessage()} diff --git a/daemon/web/components/Home.js b/daemon/web/components/Home.js index 265952c3..aed6f43f 100644 --- a/daemon/web/components/Home.js +++ b/daemon/web/components/Home.js @@ -4,20 +4,22 @@ import PropTypes from 'prop-types'; import InertiaClient from '../client'; import Dashboard from './Dashboard'; -const SidebarHeader = ({ children }) => ( +const SidebarHeader = ({ children, onClick }) => ( ); + const sidebarHeaderStyles = { container: { display: 'flex', alignItems: 'center', height: '3rem', width: '100%', - paddingLeft: '2rem' + paddingLeft: '1.5rem', + paddingTop: '1rem' }, text: { @@ -27,44 +29,33 @@ const sidebarHeaderStyles = { }; const SidebarButton = ({ children, onClick }) => ( -
- + ); -const sidebarButtonStyles = { - container: { - display: 'flex', - alignItems: 'center', - height: '3rem', - width: '100%', - paddingLeft: '3rem' - }, - - text: { - textDecoration: 'none', - color: '#101010' - } -}; const SidebarText = ({ children }) => ( -
-

+

+

{children}

); + const sidebarTextStyles = { container: { display: 'flex', alignItems: 'center', - height: '3rem', + height: 'flex', width: '100%', - paddingLeft: '3rem' + paddingLeft: '2rem', + paddingTop: '0.5rem' }, text: { + fontSize: '80%', textDecoration: 'none', color: '#101010' } @@ -74,8 +65,6 @@ export default class Home extends React.Component { constructor(props) { super(props); this.state = { - loading: true, - remoteVersion: '', repoBranch: '', @@ -92,7 +81,7 @@ export default class Home extends React.Component { this.handleGetStatus = this.handleGetStatus.bind(this); this.handleGetStatus() - .then(() => this.setState({ loading: false })) + .then(() => { }) .catch((err) => console.error(err)); } @@ -136,7 +125,7 @@ export default class Home extends React.Component {
Type: {this.state.repoBuildType} Branch: {this.state.repoBranch} - Commit {this.state.repoCommitHash.substr(1, 5)}: {this.state.repoCommitMessage} + Commit: {this.state.repoCommitHash.substr(1, 8)} "{this.state.repoCommitMessage}"
) : null; @@ -146,17 +135,21 @@ export default class Home extends React.Component {

Inertia Web

-

{this.state.remoteVersion}

logout
- Status + { + this.setState({ viewContainer: '' }); + }}>Daemon + {this.state.remoteVersion} + Repository Status {buildMessage} {repoState} - Containers + Active Containers {containers}
@@ -197,7 +190,7 @@ const styles = { alignItems: 'center', width: '100%', height: '4rem', - padding: '0 2rem', + padding: '0 1.5rem', borderBottom: '1px solid #c1c1c1' }, diff --git a/daemon/web/components/metrics/LogView.js b/daemon/web/components/metrics/LogView.js index ed6a2190..d3f8fe1f 100644 --- a/daemon/web/components/metrics/LogView.js +++ b/daemon/web/components/metrics/LogView.js @@ -8,23 +8,39 @@ export default class LogView extends React.Component { super(props); this.getEntries = this.getEntries.bind(this); + this.scrollToBottom = this.scrollToBottom.bind(this); + } + + scrollToBottom() { + this.messagesEnd.scrollIntoView({ behavior: 'smooth' }); + } + + componentDidMount() { + this.scrollToBottom(); + } + + componentDidUpdate() { + this.scrollToBottom(); } getEntries() { - let logText = ''; - for (let i = this.props.logs.length; i > 0; i--) { - logText = logText + this.props.logs[i] + '\n'; - } - return ( -

{logText}

- ); + let i = 0; + return this.props.logs.map((l) => { + i++; + return (

{l}

); + }); } render() { const resultList = this.getEntries(); return (
- {resultList} +
+ {resultList} +
+
{ this.messagesEnd = el; }}> +
); } From d156989731e6e4debf71a792b90dea7a41fe54e9 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Mon, 23 Apr 2018 23:43:17 -0700 Subject: [PATCH 16/31] Disable timestamps for nested logs --- daemon/inertia/project/build.go | 10 ++++++++-- daemon/inertia/project/docker.go | 9 +++++---- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/daemon/inertia/project/build.go b/daemon/inertia/project/build.go index 5eafc3cb..f8b9cd56 100644 --- a/daemon/inertia/project/build.go +++ b/daemon/inertia/project/build.go @@ -89,7 +89,10 @@ func dockerCompose(d *Deployment, cli *docker.Client, out io.Writer) error { } // Attach logs and report build progress until container exits - reader, err := ContainerLogs(cli, LogOptions{Container: resp.ID, Stream: true}) + reader, err := ContainerLogs(cli, LogOptions{ + Container: resp.ID, Stream: true, + NoTimestamps: true, + }) if err != nil { return err } @@ -239,7 +242,10 @@ func herokuishBuild(d *Deployment, cli *docker.Client, out io.Writer) error { } // Attach logs and report build progress until container exits - reader, err := ContainerLogs(cli, LogOptions{Container: resp.ID, Stream: true}) + reader, err := ContainerLogs(cli, LogOptions{ + Container: resp.ID, Stream: true, + NoTimestamps: true, + }) if err != nil { return err } diff --git a/daemon/inertia/project/docker.go b/daemon/inertia/project/docker.go index 749dcb8f..50ffbe9b 100644 --- a/daemon/inertia/project/docker.go +++ b/daemon/inertia/project/docker.go @@ -20,9 +20,10 @@ var ( // LogOptions is used to configure retrieved container logs type LogOptions struct { - Container string - Stream bool - Detailed bool + Container string + Stream bool + Detailed bool + NoTimestamps bool } // ContainerLogs get logs ;) @@ -32,7 +33,7 @@ func ContainerLogs(cli *docker.Client, opts LogOptions) (io.ReadCloser, error) { ShowStdout: true, ShowStderr: true, Follow: opts.Stream, - Timestamps: true, + Timestamps: opts.NoTimestamps, Details: opts.Detailed, }) } From b1dfa48acc15577b9070a7b0ba788416fca3aae6 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Mon, 23 Apr 2018 23:43:36 -0700 Subject: [PATCH 17/31] Add ubuntu monospace font for codey stuff --- daemon/web/components/metrics/LogView.js | 4 ++-- daemon/web/index.css | 6 +++++- daemon/web/index.html | 1 + 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/daemon/web/components/metrics/LogView.js b/daemon/web/components/metrics/LogView.js index d3f8fe1f..d04e873b 100644 --- a/daemon/web/components/metrics/LogView.js +++ b/daemon/web/components/metrics/LogView.js @@ -27,14 +27,14 @@ export default class LogView extends React.Component { let i = 0; return this.props.logs.map((l) => { i++; - return (

{l}

); + return ({l}
); }); } render() { const resultList = this.getEntries(); return ( -
+
{resultList}
diff --git a/daemon/web/index.css b/daemon/web/index.css index f7b2074e..79652fc4 100644 --- a/daemon/web/index.css +++ b/daemon/web/index.css @@ -30,7 +30,11 @@ body { font-family: 'Roboto', sans-serif; } +code { + font-family: 'Ubuntu Mono', monospace; +} + #app { height: 100%; width: 100%; -} \ No newline at end of file +} diff --git a/daemon/web/index.html b/daemon/web/index.html index 41a96e35..78a8520c 100644 --- a/daemon/web/index.html +++ b/daemon/web/index.html @@ -4,6 +4,7 @@ + Inertia Web From 305dd44f14da23c6008ff57a6225aaf96357af93 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Tue, 24 Apr 2018 21:35:03 -0700 Subject: [PATCH 18/31] Style fixes --- daemon/inertia/project/docker.go | 2 +- daemon/web/components/Dashboard.js | 26 ++++++++++++++++++++---- daemon/web/components/Home.js | 18 ++++++++++------ daemon/web/components/metrics/LogView.js | 9 ++++++-- 4 files changed, 42 insertions(+), 13 deletions(-) diff --git a/daemon/inertia/project/docker.go b/daemon/inertia/project/docker.go index 50ffbe9b..bef7a742 100644 --- a/daemon/inertia/project/docker.go +++ b/daemon/inertia/project/docker.go @@ -33,7 +33,7 @@ func ContainerLogs(cli *docker.Client, opts LogOptions) (io.ReadCloser, error) { ShowStdout: true, ShowStderr: true, Follow: opts.Stream, - Timestamps: opts.NoTimestamps, + Timestamps: !opts.NoTimestamps, Details: opts.Detailed, }) } diff --git a/daemon/web/components/Dashboard.js b/daemon/web/components/Dashboard.js index 36d1f0c4..82aa16d0 100644 --- a/daemon/web/components/Dashboard.js +++ b/daemon/web/components/Dashboard.js @@ -24,16 +24,28 @@ export default class Dashboard extends React.Component { } else { resp = await this.props.client.getContainerLogs(this.props.container); } - if (resp.status != 200) this.setState({ errored: true, logEntries: [] }); + if (resp.status !== 200) this.setState({ + errored: true, logEntries: [], + }); const reader = resp.body.getReader(); const decoder = new TextDecoder('utf-8'); + let buffer = ''; const stream = () => { return reader.read().then((data) => { - const parts = decoder.decode(data.value).split('\n'); + const chunk = decoder.decode(data.value); + const parts = chunk.split('\n'); + + parts[0] = buffer + parts[0]; + buffer = ''; + if (!chunk.endsWith('\n')) { + buffer = parts.pop(); + } + this.setState({ logEntries: this.state.logEntries.concat(parts), }); + return stream(); }); }; @@ -67,7 +79,13 @@ export default class Dashboard extends React.Component { render() { return ( -
+
{this.getMessage()}
@@ -84,6 +102,6 @@ const styles = { underConstruction: { textAlign: 'center', fontSize: 24, - color: '#9f9f9f' + color: '#9f9f9f', } }; diff --git a/daemon/web/components/Home.js b/daemon/web/components/Home.js index aed6f43f..54fc4487 100644 --- a/daemon/web/components/Home.js +++ b/daemon/web/components/Home.js @@ -30,7 +30,7 @@ const sidebarHeaderStyles = { const SidebarButton = ({ children, onClick }) => ( @@ -58,6 +58,12 @@ const sidebarTextStyles = { fontSize: '80%', textDecoration: 'none', color: '#101010' + }, + + button: { + fontSize: '80%', + textDecoration: 'none', + color: '#101010', } }; @@ -113,7 +119,7 @@ export default class Home extends React.Component { onClick={() => { this.setState({ viewContainer: c }); }} - key={c} >{c} + key={c} >{c} ); // Report repository status @@ -123,9 +129,9 @@ export default class Home extends React.Component { const repoState = this.state.repoCommitHash ? (
- Type: {this.state.repoBuildType} - Branch: {this.state.repoBranch} - Commit: {this.state.repoCommitHash.substr(1, 8)} "{this.state.repoCommitMessage}" + Type: {this.state.repoBuildType} + Branch: {this.state.repoBranch} + Commit: {this.state.repoCommitHash.substr(1, 8)}
"{this.state.repoCommitMessage}"
) : null; @@ -145,7 +151,7 @@ export default class Home extends React.Component { onClick={() => { this.setState({ viewContainer: '' }); }}>Daemon - {this.state.remoteVersion} + {this.state.remoteVersion} Repository Status {buildMessage} {repoState} diff --git a/daemon/web/components/metrics/LogView.js b/daemon/web/components/metrics/LogView.js index d04e873b..c0645208 100644 --- a/daemon/web/components/metrics/LogView.js +++ b/daemon/web/components/metrics/LogView.js @@ -34,8 +34,13 @@ export default class LogView extends React.Component { render() { const resultList = this.getEntries(); return ( -
-
+
+
{resultList}
Date: Tue, 24 Apr 2018 21:50:25 -0700 Subject: [PATCH 19/31] Update slideshow link --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d692d9b..bca6d02f 100644 --- a/README.md +++ b/README.md @@ -161,7 +161,7 @@ Inertia is set up serverside by executing a script over SSH that installs Docker The team has made a few presentations about Inertia that go over its design in a bit more detail: - [UBC Launch Pad internal demo](https://drive.google.com/file/d/1foO57l6egbaQ7I5zIDDe019XOgJm-ocn/view?usp=sharing) -- [Vancouver DevOpsDays 2018](https://drive.google.com/open?id=1DV2NR_YXpUZai-S7ttGcwrhWJXL7BcwiIrBJn69-IJg) ([video](https://youtu.be/amBYMEKGzTs?t=4h59m5s)) +- [Vancouver DevOpsDays 2018](https://docs.google.com/presentation/d/e/2PACX-1vRJXUnRmxpegHNVTgn_Kd8VFyeuiIwzDQl9c0oQqi1QSnIjFUIIjawsvLdu2RfHAXv_5T8kvSgSWGuq/pub?start=false&loop=false&delayms=15000) ([video](https://youtu.be/amBYMEKGzTs?t=4h59m5s)) # :books: Contributing From b5197313a3890fe54a6eb031f1dd3c56e62cb482 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Wed, 25 Apr 2018 00:17:27 -0700 Subject: [PATCH 20/31] Refactored log reader --- daemon/web/components/Dashboard.js | 72 ++++++++++-------------- daemon/web/components/Login.js | 2 +- daemon/web/components/metrics/LogView.js | 57 ++++++++++++++++--- 3 files changed, 79 insertions(+), 52 deletions(-) diff --git a/daemon/web/components/Dashboard.js b/daemon/web/components/Dashboard.js index 82aa16d0..7eea3b2c 100644 --- a/daemon/web/components/Dashboard.js +++ b/daemon/web/components/Dashboard.js @@ -10,62 +10,48 @@ export default class Dashboard extends React.Component { this.state = { errored: false, logEntries: [], + switch: true, + reader: null, }; this.getLogs = this.getLogs.bind(this); this.getMessage = this.getMessage.bind(this); } async getLogs() { - this.setState({ errored: false, logEntries: [] }); - try { - let resp; - if (!this.props.container) { - resp = await this.props.client.getContainerLogs(); - } else { - resp = await this.props.client.getContainerLogs(this.props.container); - } - if (resp.status !== 200) this.setState({ - errored: true, logEntries: [], - }); - - const reader = resp.body.getReader(); - const decoder = new TextDecoder('utf-8'); - let buffer = ''; - const stream = () => { - return reader.read().then((data) => { - const chunk = decoder.decode(data.value); - const parts = chunk.split('\n'); + let resp; + if (!this.props.container) { + resp = await this.props.client.getContainerLogs(); + } else { + resp = await this.props.client.getContainerLogs(this.props.container); + } - parts[0] = buffer + parts[0]; - buffer = ''; - if (!chunk.endsWith('\n')) { - buffer = parts.pop(); - } + if (resp.status !== 200) Promise.reject(new Error('non-200 response')); - this.setState({ - logEntries: this.state.logEntries.concat(parts), - }); + const reader = resp.body.getReader(); + this.setState({ reader: reader }); + } - return stream(); - }); - }; - stream(); - } catch (e) { + componentDidMount() { + this.getLogs().catch((err) => { this.setState({ errored: true, - logEntries: [], + reader: null, }); - console.error(e); - } + }); } - componentDidMount() { - this.getLogs(); - } - - componentDidUpdate(prevProps) { - if (prevProps.container != this.props.container) { - this.getLogs(); + componentDidUpdate(prevProps, prevState) { + if (prevProps.container !== this.props.container) { + this.setState({ + errored: false, + reader: null, + }); + this.getLogs().catch((err) => { + this.setState({ + errored: true, + reader: null, + }); + }); } } @@ -87,7 +73,7 @@ export default class Dashboard extends React.Component { position: 'relative' }}> {this.getMessage()} - +
); } diff --git a/daemon/web/components/Login.js b/daemon/web/components/Login.js index 28fe05c0..3d8bd577 100644 --- a/daemon/web/components/Login.js +++ b/daemon/web/components/Login.js @@ -43,7 +43,7 @@ export default class Login extends React.Component {

- +

{this.state.loginAlert}

diff --git a/daemon/web/components/metrics/LogView.js b/daemon/web/components/metrics/LogView.js index c0645208..831b9e5a 100644 --- a/daemon/web/components/metrics/LogView.js +++ b/daemon/web/components/metrics/LogView.js @@ -7,7 +7,12 @@ export default class LogView extends React.Component { constructor(props) { super(props); - this.getEntries = this.getEntries.bind(this); + this.state = { + entries: [], + streamRunning: false, + }; + + this.readLogs = this.readLogs.bind(this); this.scrollToBottom = this.scrollToBottom.bind(this); } @@ -17,22 +22,58 @@ export default class LogView extends React.Component { componentDidMount() { this.scrollToBottom(); + if (!this.state.streamRunning) this.readLogs().catch((err) => { + this.setState({ streamRunning: false }); + }); } componentDidUpdate() { this.scrollToBottom(); + if (!this.state.streamRunning) this.readLogs().catch((err) => { + this.setState({ streamRunning: false }); + }); + } + + async readLogs() { + const decoder = new TextDecoder('utf-8'); + let buffer = ''; + const stream = () => { + if (!this.props.logs) { + this.setState({ + streamRunning: false, + }); + return; + } + + return this.props.logs.read().then((data) => { + const chunk = decoder.decode(data.value); + const parts = chunk.split('\n') + .filter((p) => p.length > 0); + if (parts.length === 0) return; + + parts[0] = buffer + parts[0]; + buffer = ''; + if (!chunk.endsWith('\n')) { + buffer = parts.pop(); + } + + this.setState({ + entries: this.state.entries.concat(parts), + }); + + return stream(); + }); + }; + return stream(); } - getEntries() { + render() { let i = 0; - return this.props.logs.map((l) => { + const entries = this.state.entries.map((l) => { i++; return ({l}
); }); - } - render() { - const resultList = this.getEntries(); return (
- {resultList} + {entries}
{ this.messagesEnd = el; }}> @@ -52,5 +93,5 @@ export default class LogView extends React.Component { } LogView.propTypes = { - logs: PropTypes.array, + reader: PropTypes.instanceOf(ReadableStream), }; From 275a3554eb1e824dbe8ca34c7fa195376ee4bc79 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Wed, 25 Apr 2018 00:17:27 -0700 Subject: [PATCH 21/31] Revert "Refactored log reader" This reverts commit b5197313a3890fe54a6eb031f1dd3c56e62cb482. --- daemon/web/components/Dashboard.js | 72 ++++++++++++++---------- daemon/web/components/Login.js | 2 +- daemon/web/components/metrics/LogView.js | 57 +++---------------- 3 files changed, 52 insertions(+), 79 deletions(-) diff --git a/daemon/web/components/Dashboard.js b/daemon/web/components/Dashboard.js index 7eea3b2c..82aa16d0 100644 --- a/daemon/web/components/Dashboard.js +++ b/daemon/web/components/Dashboard.js @@ -10,48 +10,62 @@ export default class Dashboard extends React.Component { this.state = { errored: false, logEntries: [], - switch: true, - reader: null, }; this.getLogs = this.getLogs.bind(this); this.getMessage = this.getMessage.bind(this); } async getLogs() { - let resp; - if (!this.props.container) { - resp = await this.props.client.getContainerLogs(); - } else { - resp = await this.props.client.getContainerLogs(this.props.container); - } + this.setState({ errored: false, logEntries: [] }); + try { + let resp; + if (!this.props.container) { + resp = await this.props.client.getContainerLogs(); + } else { + resp = await this.props.client.getContainerLogs(this.props.container); + } + if (resp.status !== 200) this.setState({ + errored: true, logEntries: [], + }); - if (resp.status !== 200) Promise.reject(new Error('non-200 response')); + const reader = resp.body.getReader(); + const decoder = new TextDecoder('utf-8'); + let buffer = ''; + const stream = () => { + return reader.read().then((data) => { + const chunk = decoder.decode(data.value); + const parts = chunk.split('\n'); - const reader = resp.body.getReader(); - this.setState({ reader: reader }); - } + parts[0] = buffer + parts[0]; + buffer = ''; + if (!chunk.endsWith('\n')) { + buffer = parts.pop(); + } - componentDidMount() { - this.getLogs().catch((err) => { + this.setState({ + logEntries: this.state.logEntries.concat(parts), + }); + + return stream(); + }); + }; + stream(); + } catch (e) { this.setState({ errored: true, - reader: null, + logEntries: [], }); - }); + console.error(e); + } } - componentDidUpdate(prevProps, prevState) { - if (prevProps.container !== this.props.container) { - this.setState({ - errored: false, - reader: null, - }); - this.getLogs().catch((err) => { - this.setState({ - errored: true, - reader: null, - }); - }); + componentDidMount() { + this.getLogs(); + } + + componentDidUpdate(prevProps) { + if (prevProps.container != this.props.container) { + this.getLogs(); } } @@ -73,7 +87,7 @@ export default class Dashboard extends React.Component { position: 'relative' }}> {this.getMessage()} - +
); } diff --git a/daemon/web/components/Login.js b/daemon/web/components/Login.js index 3d8bd577..28fe05c0 100644 --- a/daemon/web/components/Login.js +++ b/daemon/web/components/Login.js @@ -43,7 +43,7 @@ export default class Login extends React.Component {

- +

{this.state.loginAlert}

diff --git a/daemon/web/components/metrics/LogView.js b/daemon/web/components/metrics/LogView.js index 831b9e5a..c0645208 100644 --- a/daemon/web/components/metrics/LogView.js +++ b/daemon/web/components/metrics/LogView.js @@ -7,12 +7,7 @@ export default class LogView extends React.Component { constructor(props) { super(props); - this.state = { - entries: [], - streamRunning: false, - }; - - this.readLogs = this.readLogs.bind(this); + this.getEntries = this.getEntries.bind(this); this.scrollToBottom = this.scrollToBottom.bind(this); } @@ -22,58 +17,22 @@ export default class LogView extends React.Component { componentDidMount() { this.scrollToBottom(); - if (!this.state.streamRunning) this.readLogs().catch((err) => { - this.setState({ streamRunning: false }); - }); } componentDidUpdate() { this.scrollToBottom(); - if (!this.state.streamRunning) this.readLogs().catch((err) => { - this.setState({ streamRunning: false }); - }); - } - - async readLogs() { - const decoder = new TextDecoder('utf-8'); - let buffer = ''; - const stream = () => { - if (!this.props.logs) { - this.setState({ - streamRunning: false, - }); - return; - } - - return this.props.logs.read().then((data) => { - const chunk = decoder.decode(data.value); - const parts = chunk.split('\n') - .filter((p) => p.length > 0); - if (parts.length === 0) return; - - parts[0] = buffer + parts[0]; - buffer = ''; - if (!chunk.endsWith('\n')) { - buffer = parts.pop(); - } - - this.setState({ - entries: this.state.entries.concat(parts), - }); - - return stream(); - }); - }; - return stream(); } - render() { + getEntries() { let i = 0; - const entries = this.state.entries.map((l) => { + return this.props.logs.map((l) => { i++; return ({l}
); }); + } + render() { + const resultList = this.getEntries(); return (
- {entries} + {resultList}
{ this.messagesEnd = el; }}> @@ -93,5 +52,5 @@ export default class LogView extends React.Component { } LogView.propTypes = { - reader: PropTypes.instanceOf(ReadableStream), + logs: PropTypes.array, }; From e5dcd9073b16bc999a554fa251c83f86d51d4ac5 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Wed, 25 Apr 2018 22:31:17 -0700 Subject: [PATCH 22/31] Blur password field --- daemon/web/components/Login.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/daemon/web/components/Login.js b/daemon/web/components/Login.js index 28fe05c0..031a0202 100644 --- a/daemon/web/components/Login.js +++ b/daemon/web/components/Login.js @@ -43,7 +43,7 @@ export default class Login extends React.Component {

- +

{this.state.loginAlert}

From f94df410508a89b3fd30bc0703d5ecddd14cdbf3 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Wed, 25 Apr 2018 23:28:22 -0700 Subject: [PATCH 23/31] Close log reader properly I am so done with this :( --- daemon/web/components/Dashboard.js | 45 ++++++++++++++++++------------ daemon/web/package-lock.json | 5 ++++ daemon/web/package.json | 1 + 3 files changed, 33 insertions(+), 18 deletions(-) diff --git a/daemon/web/components/Dashboard.js b/daemon/web/components/Dashboard.js index 82aa16d0..7e449a02 100644 --- a/daemon/web/components/Dashboard.js +++ b/daemon/web/components/Dashboard.js @@ -10,13 +10,15 @@ export default class Dashboard extends React.Component { this.state = { errored: false, logEntries: [], + logReader: null, }; this.getLogs = this.getLogs.bind(this); this.getMessage = this.getMessage.bind(this); } async getLogs() { - this.setState({ errored: false, logEntries: [] }); + if (this.state.logReader) await this.state.logReader.cancel(); + this.setState({ errored: false, logEntries: [], logReader: null }); try { let resp; if (!this.props.container) { @@ -29,33 +31,34 @@ export default class Dashboard extends React.Component { }); const reader = resp.body.getReader(); + this.setState({ logReader: reader }); + const decoder = new TextDecoder('utf-8'); let buffer = ''; const stream = () => { - return reader.read().then((data) => { - const chunk = decoder.decode(data.value); - const parts = chunk.split('\n'); + return promiseState(reader.closed).then((s) => { + if (s == 'pending') return reader.read().then((data) => { + const chunk = decoder.decode(data.value); + const parts = chunk.split('\n') + .filter((c) => c); - parts[0] = buffer + parts[0]; - buffer = ''; - if (!chunk.endsWith('\n')) { - buffer = parts.pop(); - } + parts[0] = buffer + parts[0]; + buffer = ''; + if (!chunk.endsWith('\n')) { + buffer = parts.pop(); + } - this.setState({ - logEntries: this.state.logEntries.concat(parts), - }); + this.setState({ + logEntries: this.state.logEntries.concat(parts), + }); - return stream(); + return stream(); + }); }); }; stream(); } catch (e) { - this.setState({ - errored: true, - logEntries: [], - }); - console.error(e); + console.log(e); } } @@ -105,3 +108,9 @@ const styles = { color: '#9f9f9f', } }; + +function promiseState(p) { + const t = {}; + return Promise.race([p, t]) + .then(v => (v === t) ? 'pending' : 'fulfilled', () => 'rejected'); +} diff --git a/daemon/web/package-lock.json b/daemon/web/package-lock.json index df99c6d3..5de7f2f2 100644 --- a/daemon/web/package-lock.json +++ b/daemon/web/package-lock.json @@ -9,6 +9,11 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==" }, + "abortcontroller-polyfill": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.1.9.tgz", + "integrity": "sha512-omvG7zOHIs3BphdH62Kh3xy8nlftAsTyp7PDa9EmC3Jz9pa6sZFYk7UhNgu9Y4sIBhj6jF0RgeFZYvPnsP5sBw==" + }, "accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", diff --git a/daemon/web/package.json b/daemon/web/package.json index d218cff1..f2cc34be 100644 --- a/daemon/web/package.json +++ b/daemon/web/package.json @@ -12,6 +12,7 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { + "abortcontroller-polyfill": "^1.1.9", "babel-core": "^6.26.0", "babel-loader": "^7.1.4", "babel-minify-webpack-plugin": "^0.3.1", From de5da58f6aa0ad56b1b67f5748300b81e41ea046 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Fri, 27 Apr 2018 00:22:07 -0700 Subject: [PATCH 24/31] Remove unused dependency --- daemon/web/package-lock.json | 5 ----- daemon/web/package.json | 1 - 2 files changed, 6 deletions(-) diff --git a/daemon/web/package-lock.json b/daemon/web/package-lock.json index 5de7f2f2..df99c6d3 100644 --- a/daemon/web/package-lock.json +++ b/daemon/web/package-lock.json @@ -9,11 +9,6 @@ "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.7.0.tgz", "integrity": "sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==" }, - "abortcontroller-polyfill": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/abortcontroller-polyfill/-/abortcontroller-polyfill-1.1.9.tgz", - "integrity": "sha512-omvG7zOHIs3BphdH62Kh3xy8nlftAsTyp7PDa9EmC3Jz9pa6sZFYk7UhNgu9Y4sIBhj6jF0RgeFZYvPnsP5sBw==" - }, "accepts": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", diff --git a/daemon/web/package.json b/daemon/web/package.json index f2cc34be..d218cff1 100644 --- a/daemon/web/package.json +++ b/daemon/web/package.json @@ -12,7 +12,6 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { - "abortcontroller-polyfill": "^1.1.9", "babel-core": "^6.26.0", "babel-loader": "^7.1.4", "babel-minify-webpack-plugin": "^0.3.1", From 08c71da82ff0b59d74bfc7ad866b8fff2b0717a8 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Fri, 27 Apr 2018 00:34:56 -0700 Subject: [PATCH 25/31] Point tests to 127.0.0.1, improve build test contaienr cleanup --- client/remote_test.go | 4 +-- daemon/inertia/project/build_test.go | 45 ++++++++++++++++++++++------ 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/client/remote_test.go b/client/remote_test.go index e70b974a..e5243666 100644 --- a/client/remote_test.go +++ b/client/remote_test.go @@ -24,7 +24,7 @@ func getTestConfig(writer io.Writer) *Config { func getTestRemote() *RemoteVPS { remote := &RemoteVPS{ - IP: "0.0.0.0", + IP: "127.0.0.1", PEM: "../test/keys/id_rsa", User: "root", Daemon: &DaemonConfig{ @@ -96,7 +96,7 @@ func TestBootstrap(t *testing.T) { script, err = ioutil.ReadFile("bootstrap/daemon-up.sh") assert.Nil(t, err) - daemonScript := fmt.Sprintf(string(script), "test", "8081", "0.0.0.0") + daemonScript := fmt.Sprintf(string(script), "test", "8081", "127.0.0.1") var writer bytes.Buffer session := mockSSHRunner{r: remote} diff --git a/daemon/inertia/project/build_test.go b/daemon/inertia/project/build_test.go index 6e040856..c778a4fa 100644 --- a/daemon/inertia/project/build_test.go +++ b/daemon/inertia/project/build_test.go @@ -9,10 +9,34 @@ import ( "time" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" docker "github.com/docker/docker/client" "github.com/stretchr/testify/assert" ) +func cleanupContainers(cli *docker.Client) error { + ctx := context.Background() + containers, err := cli.ContainerList(ctx, types.ContainerListOptions{}) + if err != nil { + return err + } + + // Gracefully take down all containers except the testvps + for _, container := range containers { + if container.Names[0] != "/testvps" { + timeout := 10 * time.Second + err := cli.ContainerStop(ctx, container.ID, &timeout) + if err != nil { + return err + } + } + } + + // Prune images + _, err = cli.ContainersPrune(ctx, filters.Args{}) + return err +} + func TestDockerComposeIntegration(t *testing.T) { if testing.Short() { t.Skip("skipping integration test") @@ -31,14 +55,15 @@ func TestDockerComposeIntegration(t *testing.T) { project: testProjectName, buildType: "docker-compose", } - d.Down(cli, os.Stdout) + err = cleanupContainers(cli) + assert.Nil(t, err) // Execute build err = dockerCompose(d, cli, os.Stdout) assert.Nil(t, err) // Arbitrary wait for containers to start - time.Sleep(5 * time.Second) + time.Sleep(10 * time.Second) containers, err := cli.ContainerList( context.Background(), @@ -58,7 +83,7 @@ func TestDockerComposeIntegration(t *testing.T) { // try again if project no up (workaround for Travis) if !foundP { - time.Sleep(5 * time.Second) + time.Sleep(10 * time.Second) containers, err = cli.ContainerList( context.Background(), types.ContainerListOptions{}, @@ -74,7 +99,7 @@ func TestDockerComposeIntegration(t *testing.T) { assert.True(t, foundDC, "docker-compose container should be active") assert.True(t, foundP, "project container should be active") - err = d.Down(cli, os.Stdout) + err = cleanupContainers(cli) assert.Nil(t, err) } @@ -96,7 +121,8 @@ func TestDockerBuildIntegration(t *testing.T) { project: testProjectName, buildType: "dockerfile", } - d.Down(cli, os.Stdout) + err = cleanupContainers(cli) + assert.Nil(t, err) // Execute build err = dockerBuild(d, cli, os.Stdout) @@ -118,7 +144,7 @@ func TestDockerBuildIntegration(t *testing.T) { } assert.True(t, foundP, "project container should be active") - err = d.Down(cli, os.Stdout) + err = cleanupContainers(cli) assert.Nil(t, err) } @@ -140,7 +166,8 @@ func TestHerokuishBuildIntegration(t *testing.T) { project: testProjectName, buildType: "herokuish", } - d.Down(cli, os.Stdout) + err = cleanupContainers(cli) + assert.Nil(t, err) // Execute build err = herokuishBuild(d, cli, os.Stdout) @@ -162,6 +189,6 @@ func TestHerokuishBuildIntegration(t *testing.T) { } assert.True(t, foundP, "project container should be active") - // err = d.Down(cli, os.Stdout) - // assert.Nil(t, err) + err = cleanupContainers(cli) + assert.Nil(t, err) } From 9a59a10503c2b473c55b04f22b991b3c629145f0 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Fri, 27 Apr 2018 01:11:25 -0700 Subject: [PATCH 26/31] Improve build test setup, remove port 80 binding from testvps --- daemon/inertia/project/build.go | 2 +- daemon/inertia/project/build_test.go | 24 ++++++++++++++---------- test/start_vps.sh | 1 - 3 files changed, 15 insertions(+), 12 deletions(-) diff --git a/daemon/inertia/project/build.go b/daemon/inertia/project/build.go index f8b9cd56..5a74e02e 100644 --- a/daemon/inertia/project/build.go +++ b/daemon/inertia/project/build.go @@ -81,7 +81,7 @@ func dockerCompose(d *Deployment, cli *docker.Client, out io.Writer) error { return errors.New(warnings) } - // Start the herokuish container to build project + // Start container to build project fmt.Fprintln(out, "Building project...") err = cli.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}) if err != nil { diff --git a/daemon/inertia/project/build_test.go b/daemon/inertia/project/build_test.go index c778a4fa..6bc3602b 100644 --- a/daemon/inertia/project/build_test.go +++ b/daemon/inertia/project/build_test.go @@ -21,11 +21,10 @@ func cleanupContainers(cli *docker.Client) error { return err } - // Gracefully take down all containers except the testvps + // Take down all containers except the testvps for _, container := range containers { if container.Names[0] != "/testvps" { - timeout := 10 * time.Second - err := cli.ContainerStop(ctx, container.ID, &timeout) + err := cli.ContainerKill(ctx, container.ID, "SIGKILL") if err != nil { return err } @@ -45,6 +44,10 @@ func TestDockerComposeIntegration(t *testing.T) { assert.Nil(t, err) defer cli.Close() + // Set up + err = cleanupContainers(cli) + assert.Nil(t, err) + testProjectDir := path.Join( os.Getenv("GOPATH"), "/src/github.com/ubclaunchpad/inertia/test/build/docker-compose", @@ -55,16 +58,15 @@ func TestDockerComposeIntegration(t *testing.T) { project: testProjectName, buildType: "docker-compose", } - err = cleanupContainers(cli) - assert.Nil(t, err) // Execute build err = dockerCompose(d, cli, os.Stdout) assert.Nil(t, err) // Arbitrary wait for containers to start - time.Sleep(10 * time.Second) + time.Sleep(5 * time.Second) + // Check for containers containers, err := cli.ContainerList( context.Background(), types.ContainerListOptions{}, @@ -111,6 +113,9 @@ func TestDockerBuildIntegration(t *testing.T) { assert.Nil(t, err) defer cli.Close() + err = cleanupContainers(cli) + assert.Nil(t, err) + testProjectDir := path.Join( os.Getenv("GOPATH"), "/src/github.com/ubclaunchpad/inertia/test/build/dockerfile", @@ -121,8 +126,6 @@ func TestDockerBuildIntegration(t *testing.T) { project: testProjectName, buildType: "dockerfile", } - err = cleanupContainers(cli) - assert.Nil(t, err) // Execute build err = dockerBuild(d, cli, os.Stdout) @@ -156,6 +159,9 @@ func TestHerokuishBuildIntegration(t *testing.T) { assert.Nil(t, err) defer cli.Close() + err = cleanupContainers(cli) + assert.Nil(t, err) + testProjectDir := path.Join( os.Getenv("GOPATH"), "/src/github.com/ubclaunchpad/inertia/test/build/herokuish", @@ -166,8 +172,6 @@ func TestHerokuishBuildIntegration(t *testing.T) { project: testProjectName, buildType: "herokuish", } - err = cleanupContainers(cli) - assert.Nil(t, err) // Execute build err = herokuishBuild(d, cli, os.Stdout) diff --git a/test/start_vps.sh b/test/start_vps.sh index 57430fee..4622c36a 100644 --- a/test/start_vps.sh +++ b/test/start_vps.sh @@ -8,7 +8,6 @@ IMAGE=$2 # argument 2: VPS image to build docker run --rm -d \ -p $SSH_PORT:22 \ - -p 80:80 \ -p 8081:8081 \ -p 8000:8000 \ --name testvps \ From 41f743f531fca14b1d92c93be8e8dd37af340f81 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Fri, 27 Apr 2018 21:27:10 -0700 Subject: [PATCH 27/31] Install go-bindata on make --- Makefile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index f1243db5..2bcfdee0 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ VPS_VERSION = latest VPS_OS = ubuntu RELEASE = canary -all: bootstrap deps inertia +all: deps bootstrap inertia # List all commands ls: @@ -14,6 +14,7 @@ ls: # Sets up all dependencies deps: + go get -u github.com/jteeuwen/go-bindata/... dep ensure make web-deps bash test/deps.sh From 61e36db42587c823217729780b8c1569853da764 Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Fri, 27 Apr 2018 21:43:14 -0700 Subject: [PATCH 28/31] Allow numbers as valid characters --- daemon/inertia/auth/password.go | 3 ++- daemon/inertia/auth/password_test.go | 3 +++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/daemon/inertia/auth/password.go b/daemon/inertia/auth/password.go index d0735570..dfca80e6 100644 --- a/daemon/inertia/auth/password.go +++ b/daemon/inertia/auth/password.go @@ -44,7 +44,8 @@ func validateCredentialValues(username, password string) error { // isLegalString returns true if `str` only contains characters [A-Z], [a-z], or '_' or '-' func isLegalString(str string) bool { for _, c := range str { - if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && c != '_' && c != '-' { + if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < 49 || c > 57) && c != '_' && c != '-' { + println(c) return false } } diff --git a/daemon/inertia/auth/password_test.go b/daemon/inertia/auth/password_test.go index 2b9342b3..dfafee88 100644 --- a/daemon/inertia/auth/password_test.go +++ b/daemon/inertia/auth/password_test.go @@ -30,6 +30,9 @@ func TestValidateCredentialValues(t *testing.T) { err := validateCredentialValues("finasdfsdfe", "okaasdfasdy") assert.Nil(t, err) + err = validateCredentialValues("finasdfsdfe", "12345") + assert.Nil(t, err) + err = validateCredentialValues("ohnoitsme", "ohnoitsme") assert.Equal(t, errSameUsernamePassword, err) From 852121c166bfa9170b5e8b523173c47d0edc684b Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Fri, 27 Apr 2018 21:56:21 -0700 Subject: [PATCH 29/31] We love 0 too Add 0 as allowed character --- daemon/inertia/auth/password.go | 3 +-- daemon/inertia/auth/password_test.go | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/daemon/inertia/auth/password.go b/daemon/inertia/auth/password.go index dfca80e6..20675980 100644 --- a/daemon/inertia/auth/password.go +++ b/daemon/inertia/auth/password.go @@ -44,8 +44,7 @@ func validateCredentialValues(username, password string) error { // isLegalString returns true if `str` only contains characters [A-Z], [a-z], or '_' or '-' func isLegalString(str string) bool { for _, c := range str { - if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < 49 || c > 57) && c != '_' && c != '-' { - println(c) + if (c < 'a' || c > 'z') && (c < 'A' || c > 'Z') && (c < 48 || c > 57) && c != '_' && c != '-' { return false } } diff --git a/daemon/inertia/auth/password_test.go b/daemon/inertia/auth/password_test.go index dfafee88..a90cdae1 100644 --- a/daemon/inertia/auth/password_test.go +++ b/daemon/inertia/auth/password_test.go @@ -30,7 +30,7 @@ func TestValidateCredentialValues(t *testing.T) { err := validateCredentialValues("finasdfsdfe", "okaasdfasdy") assert.Nil(t, err) - err = validateCredentialValues("finasdfsdfe", "12345") + err = validateCredentialValues("0123456789a", "0123456789") assert.Nil(t, err) err = validateCredentialValues("ohnoitsme", "ohnoitsme") From 4a3f1956f0955e9ac06316853abd6fe22f27216d Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Sat, 28 Apr 2018 00:41:10 -0700 Subject: [PATCH 30/31] Update credential error messages --- daemon/inertia/auth/password.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/daemon/inertia/auth/password.go b/daemon/inertia/auth/password.go index 20675980..fdc3ab81 100644 --- a/daemon/inertia/auth/password.go +++ b/daemon/inertia/auth/password.go @@ -8,8 +8,8 @@ import ( var ( errSameUsernamePassword = errors.New("Username and password must be different") - errInvalidUsername = errors.New("Only letters, numbers, underscores, and dashes are allowed in usernames, and username must be at least 3 characters") - errInvalidPassword = errors.New("Only letters, numbers, underscores, and dashes are allowed in passwords, and password must be at least 5 characters") + errInvalidUsername = errors.New("Username must be at least 3 characters and only letters, numbers, underscores, and dashes are allowed") + errInvalidPassword = errors.New("Password must be at least 5 characters and only letters, numbers, underscores, and dashes are allowed") ) // hashPassword generates a bcrypt-encrypted hash from given password From f81e5c462ee55e34f7763d4eb3ff1419c689acfb Mon Sep 17 00:00:00 2001 From: Robert Lin Date: Sat, 28 Apr 2018 01:58:39 -0700 Subject: [PATCH 31/31] Update documentation --- .github/CONTRIBUTING.md | 20 ++++++++------------ README.md | 11 +++++++---- client/bootstrap.go | 4 ++-- client/bootstrap/docker.sh | 2 +- 4 files changed, 18 insertions(+), 19 deletions(-) diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index b8c2c6f4..6f4e0b00 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -72,19 +72,13 @@ Note that if you install Inertia using these commands or any variation of `go in ### CLI -The codebase for the CLI is in the root directory. This code should only include the user interface - all client-based logic and functionality should go into the client. +The codebase for the CLI is in the root directory. This code should only include the CLI user interface - all client-based logic and functionality should go into the `client` package. ### Client -The Inertia client manages all clientside functionality. The client codebase is in `./client/`. +The Inertia client package manages all clientside functionality. The client codebase is in `./client/`. -To bootstrap servers, some bash scripting is often involved, but we'd like to avoid shipping bash scripts with our go binary. So we use [go-bindata](https://github.com/jteeuwen/go-bindata) to compile shell scripts into our go executables. - -```bash -$> go get -u github.com/jteeuwen/go-bindata/... -``` - -If you make changes to the bootstrapping shell scripts in `client/bootstrap/`, convert them to `Assets` by running: +To bootstrap servers, some bash scripting is often involved, but we'd like to avoid shipping bash scripts with our go binary - instead, we use [go-bindata](https://github.com/jteeuwen/go-bindata) to compile shell scripts into our Go executables. If you make changes to the bootstrapping shell scripts in `client/bootstrap/`, convert them to `Assets` by running: ```bash $> make bootstrap @@ -93,7 +87,7 @@ $> make bootstrap Then use your asset! ```go -shellScriptData, err := Asset("cmd/bootstrap/myshellscript.sh") +shellScriptData, err := Asset("client/bootstrap/myshellscript.sh") if err != nil { log.Fatal("No asset with that name") } @@ -104,7 +98,7 @@ result, _ := remote.RunSSHCommand(string(shellScriptData)) ### Daemon -The Inertia daemon manages all serverside functionality. The daemon codebase is in `./daemon/inertia/`. +The Inertia daemon package manages all serverside functionality. The daemon codebase is in `./daemon/inertia/`. To use a daemon compiled from source, set your Inertia version in `.inertia.toml` to `test` and run: @@ -134,7 +128,7 @@ This sneaky configuration file can be found under `Docker -> Preferences -> Daem ### Web -The Inertia Web application provides a web interface to manage an Inertia deployment. The web application codebase is in `./daemon/web/`. +Inertia Web provides a web interface to manage an Inertia deployment. The web application codebase is in `./daemon/web/`. To run a local instance of Inertia Web: @@ -206,3 +200,5 @@ $> inertia local up --stream $> inertia local status $> inertia local logs ``` + +Please free free to open up an Issue if any of these steps are no clear or don't work! diff --git a/README.md b/README.md index bca6d02f..55ce1ccf 100644 --- a/README.md +++ b/README.md @@ -37,11 +37,12 @@ Inertia is a simple cross-platform command line application that enables effortl ----|----------------- πŸš€ | Simple setup from your computer without ever having to manually SSH into your remote 🍰 | Use any Linux-based remote virtual private server platform you want -βš’ | Deploy a wide range of supported project types (including docker-compose and Heroku buildpacks) -πŸš„ | Have your project automatically updated as soon as you `git push` -πŸ›‚ | Start up and shut down your deployment with ease -πŸ“š | Monitor your deployed application's logs straight from your command line +βš’ | Deploy a wide range of supported project types (including Dockerfile, docker-compose, and Heroku projects) +πŸš„ | Have your project automatically updated, rebuilt, and deployed as soon as you `git push` +πŸ›‚ | Start up, shut down, and monitor your deployment with ease 🏷 | Configure deployment to your liking with branch settings and more +🌐 | Add users and check on your deployment anywhere through Inertia Web +πŸ”‘ | Secured with tokens and HTTPS across the board ---------------- @@ -101,6 +102,8 @@ $> inertia $VPS_NAME up --stream Run `inertia $VPS_NAME --help` to see the other commands Inertia offers for managing your deployment. +Inertia also offers a web application - this can be accessed at `https://$ADDRESS:8081/web` once users have been added through the `inertia $VPS_NAME user` commands. + ## Continuous Deployment To enable continuous deployment, you need the webhook URL that is printed during `inertia $VPS_NAME init`: diff --git a/client/bootstrap.go b/client/bootstrap.go index 3fbe38a3..1a0260ee 100644 --- a/client/bootstrap.go +++ b/client/bootstrap.go @@ -112,7 +112,7 @@ func clientBootstrapDaemonUpSh() (*asset, error) { return a, nil } -var _clientBootstrapDockerSh = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x52\x5d\x6f\xd4\x30\x10\x7c\xf7\xaf\x18\x7a\xa7\x16\x24\x7c\xa1\xf7\x48\x55\xa4\x72\x77\x4f\x54\x3a\xe9\xae\x7d\x46\xae\xb3\x49\xac\x26\x76\xea\xdd\xb4\x04\xc4\x7f\x47\xf9\x84\x82\x80\xe6\xc9\xf1\xcc\xce\x8e\x67\x77\xf1\x2a\xb9\x73\x3e\xe1\x42\xa9\x05\x3e\x86\x20\x2c\xd1\xd4\x0c\x83\xca\xd8\xc2\x79\x42\x16\x22\xd2\x60\xef\x29\xc2\xf8\x74\x3c\x6a\x1b\xaa\x3a\x30\xad\x94\x62\x12\x68\x52\x6a\xbb\xdf\x7c\xda\x1d\x3e\x1f\xf7\xb7\x87\xcd\xee\x32\x27\x59\x0d\xd4\x95\x0d\xd5\x04\x6e\x77\xc7\x9b\xcb\xb3\x44\xaa\x3a\xc9\x49\xf4\x48\xe0\xe2\xac\xeb\x7e\xbc\x77\x35\x9c\x67\x31\x65\x69\xc4\x05\x0f\x97\x61\x3b\x74\x76\x0c\x53\x46\x32\x69\x3b\x31\x28\x5d\x29\x97\xa1\x30\x5c\x4c\xf6\xd6\x1f\x92\x94\x1e\x13\xdf\x94\xe5\x05\xa4\x20\xaf\x00\x80\xbe\x38\xc1\x3b\x95\xb9\x0b\xa5\x32\x12\x5b\x64\xae\xa4\xd7\x6f\xf0\xad\x47\x17\xb8\x8a\x39\xbf\x1f\xcf\xc0\xf2\x1c\x1c\x9a\x68\x09\xb7\x87\xeb\x9f\xb7\x6b\xa4\xc4\xe2\xfc\xe0\xab\x53\x58\xf5\xe0\xe4\xc0\x36\xb1\xfc\x4b\xff\xee\xe3\x26\x0d\x03\x47\x67\x7c\xbc\xee\xba\xe8\x80\x93\xe5\xfa\x64\xb0\x58\x4e\x3a\x4f\x39\xc9\xff\x74\x7a\x8e\xde\xf7\xe5\x58\x9e\x8f\x0a\x4c\x33\x2b\x92\x34\xd1\x63\x40\xba\x77\x7f\xef\xe2\xbd\xaa\xcc\xd7\xe0\xb1\xdb\x1c\x87\x08\xbd\x25\x46\xa4\x87\xc6\x45\x82\x6d\x58\x42\x35\x65\xdb\x05\x9b\x47\xaa\xa1\x1f\xa6\xb2\x84\xc4\x26\xdc\xb2\x50\xa5\x23\x95\x64\x98\x7e\xf1\xd6\xfb\x6a\x9b\x59\x00\xba\x1d\x87\xa2\x66\x67\x0b\xdc\xc4\x16\x12\x90\x86\x27\x5f\x06\x93\xa2\x61\xe7\xf3\x21\x96\x10\xfb\x67\xbd\x1d\x99\x77\x94\x85\x48\x88\xc4\x21\x4a\x47\x92\x30\x49\x4f\x25\x73\xfe\xf3\x4c\xb1\x7c\xb6\x82\xf3\x6f\xb7\x74\xbf\xe7\x58\x3c\x43\xff\x8c\xd0\xd4\xa2\xbb\x98\x9b\x3a\x35\x42\x38\x3d\x9d\x6f\xf4\xbc\x81\xbd\x8d\xb9\xe2\x45\x36\xfe\x69\x20\x73\x2a\x73\x4a\xf5\x59\x32\xc5\x47\x67\x69\xda\x6c\x16\x13\x45\xfd\x08\x00\x00\xff\xff\x55\xed\x48\x14\xac\x03\x00\x00") +var _clientBootstrapDockerSh = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\x8c\x92\x4f\x6f\xd3\x40\x10\xc5\xef\xfb\x29\x1e\x4d\xd4\x82\x84\x63\x9a\x23\x55\x91\x4a\x92\x13\x95\x22\x25\xed\x19\x6d\xed\xb1\xbd\xaa\xbd\xeb\xce\x8c\x5b\x0c\xe2\xbb\x23\xff\x85\x82\x80\xe6\x14\xef\xbc\x79\xf3\xf6\xb7\xb3\x78\x15\xdf\x39\x1f\x4b\x61\xcc\x02\x1f\x43\x50\x51\xb6\xb5\xc0\xa2\xb2\x49\xe1\x3c\x21\x0b\x8c\x34\x24\xf7\xc4\x2b\x63\x84\x14\x11\x19\xb3\xdd\x6f\x3e\xed\x0e\x9f\x8f\xfb\xdb\xc3\x66\x77\x99\x93\xae\x46\x49\x12\xaa\xa9\xb8\xdd\x1d\x6f\x2e\xcf\x62\xad\xea\x38\x27\x8d\x46\x81\x14\x67\xdd\xa8\xe3\xbd\xab\xe1\xbc\xa8\x2d\x4b\xab\x2e\x78\xb8\x0c\xdb\x5e\x02\x27\xb0\x25\x93\x4d\xdb\x49\x41\xe9\xca\xb8\x0c\x85\x95\x62\xcc\x82\xf5\x87\x38\xa5\xc7\xd8\x37\x65\x79\x01\x2d\xc8\x1b\x00\xa0\x2f\x4e\xf1\xce\x64\xee\xc2\x98\x8c\x34\x29\x32\x57\xd2\xeb\x37\xf8\xd6\x57\x17\xb8\xe2\x5c\xde\x8f\xff\x81\xe5\x39\x24\x34\x9c\x10\x6e\x0f\xd7\x3f\x4f\xd7\x48\x49\xd4\xf9\x21\x57\xe7\xb0\xea\x8b\x53\x82\xa4\xe1\xf2\x2f\xf3\xbb\x9f\x34\x69\x18\x34\x51\x26\xc7\xeb\x6e\x4a\x14\x70\xb2\x5c\x9f\x0c\x11\xcb\xc9\xe7\x29\x27\xfd\x9f\x4f\xaf\x89\xf6\x7d\x3b\x96\xe7\xa3\x83\xd0\xac\x62\xd2\x86\x3d\x86\x4a\x77\xef\xef\x1d\xde\xab\xca\x7e\x0d\x1e\xbb\xcd\x71\x40\xe8\x13\x12\x30\x3d\x34\x8e\x09\x49\x23\x1a\xaa\x89\x6d\x07\x36\x67\xaa\x11\x3d\x4c\x6d\x31\x69\x12\x4b\x2b\x4a\x55\xc4\x54\x92\x15\xfa\x25\x5b\x9f\xab\x6d\x66\x03\x44\xed\xf8\x28\x66\x4e\xb6\xc0\x0d\xb7\xd0\x80\x34\x3c\xf9\x32\xd8\x14\x8d\x38\x9f\x0f\x58\x02\xf7\xd7\x7a\x3b\x2a\xef\x28\x0b\x4c\x60\x92\xc0\xda\x89\x34\x4c\xd6\x53\xcb\xcc\x7f\x7e\x53\x2c\x9f\xad\xe0\xfc\xd9\x2d\xdd\xef\x1c\x8b\x67\xd5\x3f\x11\xda\x5a\xa3\x0e\x73\x53\xa7\x56\x09\xa7\xa7\xf3\x49\x34\x6f\x60\x1f\x63\xee\x78\x51\x8c\x7f\x06\xc8\x9c\xc9\x9c\x31\x3d\x4b\x21\x7e\x74\x09\x4d\x9b\x2d\x6a\x59\xcd\x8f\x00\x00\x00\xff\xff\x6e\x88\x52\x3d\x99\x03\x00\x00") func clientBootstrapDockerShBytes() ([]byte, error) { return bindataRead( @@ -127,7 +127,7 @@ func clientBootstrapDockerSh() (*asset, error) { return nil, err } - info := bindataFileInfo{name: "client/bootstrap/docker.sh", size: 940, mode: os.FileMode(493), modTime: time.Unix(1524096431, 0)} + info := bindataFileInfo{name: "client/bootstrap/docker.sh", size: 921, mode: os.FileMode(493), modTime: time.Unix(1524904851, 0)} a := &asset{bytes: bytes, info: info} return a, nil } diff --git a/client/bootstrap/docker.sh b/client/bootstrap/docker.sh index 43532641..e7409a7d 100755 --- a/client/bootstrap/docker.sh +++ b/client/bootstrap/docker.sh @@ -1,6 +1,6 @@ #!/bin/sh -# Bootstraps a machine for docker and docker-compose. +# Bootstraps a machine for docker. set -e