Bläddra i källkod

postgres wait for healthy

Alexander Belanger 5 år sedan
förälder
incheckning
ec13f50b3c

+ 24 - 0
cli/cmd/docker/agent.go

@@ -6,6 +6,7 @@ import (
 	"fmt"
 	"io"
 	"strings"
+	"time"
 
 	"github.com/docker/docker/api/types"
 	"github.com/docker/docker/api/types/container"
@@ -184,6 +185,29 @@ func (a *Agent) WaitForContainerStop(id string) error {
 	return nil
 }
 
+// WaitForContainerHealthy waits until a container is returning a healthy status. Streak
+// is the maximum number of failures in a row, while timeout is the length of time between
+// checks.
+func (a *Agent) WaitForContainerHealthy(id string, streak int) error {
+	for {
+		cont, err := a.client.ContainerInspect(a.ctx, id)
+
+		if err != nil {
+			return a.handleDockerClientErr(err, "Error waiting for stopped container")
+		}
+
+		health := cont.State.Health
+
+		if health == nil || health.Status == "healthy" || health.FailingStreak >= streak {
+			break
+		}
+
+		time.Sleep(time.Second)
+	}
+
+	return nil
+}
+
 // ------------------------- AGENT HELPER FUNCTIONS ------------------------- //
 
 func (a *Agent) handleDockerClientErr(err error, errPrefix string) error {

+ 6 - 0
cli/cmd/docker/porter.go

@@ -199,6 +199,12 @@ func (a *Agent) pullAndCreatePostgresContainer(opts PostgresOpts) (id string, er
 		ExposedPorts: nat.PortSet{
 			"5432": struct{}{},
 		},
+		Healthcheck: &container.HealthConfig{
+			Test:     []string{"CMD-SHELL", "pg_isready"},
+			Interval: 10 * time.Second,
+			Timeout:  5 * time.Second,
+			Retries:  3,
+		},
 	}, &container.HostConfig{
 		Mounts: opts.Mounts,
 	}, nil, opts.Name)

+ 28 - 5
cli/cmd/start.go

@@ -3,7 +3,10 @@ package cmd
 import (
 	"fmt"
 	"os"
+	"os/exec"
 	"path/filepath"
+	"runtime"
+	"time"
 
 	"github.com/porter-dev/porter/cli/cmd/docker"
 	"k8s.io/client-go/util/homedir"
@@ -267,6 +270,9 @@ func start(
 
 		pgID, err := agent.StartPostgresContainer(startOpts)
 
+		fmt.Println("Waiting for postgres:latest to be healthy...")
+		agent.WaitForContainerHealthy(pgID, 10)
+
 		if err != nil {
 			return err
 		}
@@ -302,13 +308,30 @@ func start(
 		return err
 	}
 
-	if !insecure {
-		fmt.Printf("Server ready: go to localhost:%d/login and log in with the admin account you just created.\n", port)
-	} else {
-		fmt.Printf("Server ready: go to localhost:%d/login\n", port)
-	}
+	fmt.Println("Spinning up the server...")
+	time.Sleep(7 * time.Second)
+	openBrowser(fmt.Sprintf("http://localhost:%d/login?email=%s", port, username))
+	fmt.Printf("Server ready: listening on localhost:%d\n", port)
 
 	agent.WaitForContainerStop(id)
 
 	return nil
 }
+
+// openBrowser opens the specified URL in the default browser of the user.
+func openBrowser(url string) error {
+	var cmd string
+	var args []string
+
+	switch runtime.GOOS {
+	case "windows":
+		cmd = "cmd"
+		args = []string{"/c", "start"}
+	case "darwin":
+		cmd = "open"
+	default: // "linux", "freebsd", "openbsd", "netbsd"
+		cmd = "xdg-open"
+	}
+	args = append(args, url)
+	return exec.Command(cmd, args...).Start()
+}

+ 3 - 0
dashboard/src/main/Login.tsx

@@ -30,6 +30,9 @@ export default class Login extends Component<PropsType, StateType> {
   }
 
   componentDidMount() {
+    let urlParams = new URLSearchParams(window.location.search);
+    let emailFromCLI = urlParams.get('email');
+    emailFromCLI ? this.setState({email: emailFromCLI}) :
     document.addEventListener("keydown", this.handleKeyDown);
   }
 

+ 0 - 1
dashboard/src/main/home/dashboard/Dashboard.tsx

@@ -37,7 +37,6 @@ export default class Dashboard extends Component<PropsType, StateType> {
   // Allows rollback to update the top-level chart
   refreshChart = () => {
     let { currentCluster } = this.props;
-    console.log(currentCluster)
     api.getChart('<token>', {
       namespace: this.state.namespace,
       context: currentCluster,

+ 18 - 4
dashboard/src/main/home/dashboard/expanded-chart/ExpandedChart.tsx

@@ -38,15 +38,15 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
     components: [] as ResourceType[]
   }
 
-  componentDidMount() {
-    let { currentCluster, setCurrentError } = this.context;
+  updateResources = () => {
+    let { currentCluster } = this.context;
     let { currentChart } = this.props;
 
     api.getChartComponents('<token>', {
       namespace: currentChart.namespace,
       context: currentCluster,
       storage: StorageType.Secret
-    }, { name: currentChart.name, revision: 0 }, (err: any, res: any) => {
+    }, { name: currentChart.name, revision: currentChart.version }, (err: any, res: any) => {
       if (err) {
         console.log(err)
       } else {
@@ -55,6 +55,16 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
     });
   }
 
+  componentDidMount() {
+    this.updateResources();
+  }
+
+  componentDidUpdate(prevProps: PropsType) {
+    if (this.props.currentChart !== prevProps.currentChart) {
+      this.updateResources();
+    }
+  }
+
   renderIcon = () => {
     let { currentChart } = this.props;
 
@@ -74,12 +84,16 @@ export default class ExpandedChart extends Component<PropsType, StateType> {
 
   renderTabContents = () => {
     let { currentChart, refreshChart, setSidebar} = this.props;
+
     if (this.state.currentTab === 'graph') {
       return (
         <GraphSection
           components={this.state.components}
-          currentChartName={currentChart.name}
+          currentChart={currentChart}
           setSidebar={setSidebar}
+          
+          // Handle resize YAML wrapper
+          showRevisions={this.state.showRevisions}
         />
       );
     } else if (this.state.currentTab === 'list') {

+ 6 - 4
dashboard/src/main/home/dashboard/expanded-chart/GraphSection.tsx

@@ -2,15 +2,16 @@ import React, { Component } from 'react';
 import styled from 'styled-components';
 
 import { Context } from '../../../../shared/Context';
-import { ResourceType } from '../../../../shared/types';
+import { ResourceType, ChartType } from '../../../../shared/types';
 
 import GraphDisplay from './graph/GraphDisplay';
 import Loading from '../../../../components/Loading';
 
 type PropsType = {
   components: ResourceType[],
-  currentChartName: string,
-  setSidebar: (x: boolean) => void
+  currentChart: ChartType,
+  setSidebar: (x: boolean) => void,
+  showRevisions: boolean
 };
 
 type StateType = {
@@ -29,7 +30,8 @@ export default class GraphSection extends Component<PropsType, StateType> {
           setSidebar={this.props.setSidebar}
           components={this.props.components}
           isExpanded={this.state.isExpanded}
-          currentChartName={this.props.currentChartName}
+          currentChart={this.props.currentChart}
+          showRevisions={this.props.showRevisions}
         />
       );
     }

+ 2 - 1
dashboard/src/main/home/dashboard/expanded-chart/RevisionSection.tsx

@@ -38,7 +38,8 @@ export default class RevisionSection extends Component<PropsType, StateType> {
       if (err) {
         console.log(err)
       } else {
-        this.setState({ revisions: res.data.reverse() });
+        res.data.sort((a: ChartType, b: ChartType) => { return -(a.version - b.version) });
+        this.setState({ revisions: res.data });
       }
     });
   }

+ 4 - 1
dashboard/src/main/home/dashboard/expanded-chart/ValuesYaml.tsx

@@ -26,7 +26,10 @@ export default class ValuesYaml extends Component<PropsType, StateType> {
   }
 
   updateValues() {
-    let values = yaml.dump(this.props.currentChart.config);
+    let values = '# Nothing here yet';
+    if (this.props.currentChart.config) {
+      values = yaml.dump(this.props.currentChart.config);
+    }
     this.setState({ values });
   }
 

+ 49 - 17
dashboard/src/main/home/dashboard/expanded-chart/graph/GraphDisplay.tsx

@@ -1,7 +1,7 @@
 import React, { Component } from 'react';
 import styled from 'styled-components';
 
-import { ResourceType, NodeType, EdgeType } from '../../../../../shared/types';
+import { ResourceType, NodeType, EdgeType, ChartType } from '../../../../../shared/types';
 
 import Node from './Node';
 import Edge from './Edge';
@@ -15,7 +15,10 @@ type PropsType = {
   components: ResourceType[],
   isExpanded: boolean,
   setSidebar: (x: boolean) => void,
-  currentChartName: string
+  currentChart: ChartType,
+
+  // Handle revisions expansion for YAML wrapper
+  showRevisions: boolean
 };
 
 type StateType = {
@@ -45,6 +48,7 @@ type StateType = {
   openedNode: NodeType | null,
   suppressCloseNode: boolean, // Still click should close opened unless on a node
   suppressDisplay: boolean, // Ignore clicks + pan/zoom on InfoPanel or ButtonSection
+  version?: number // Track in localstorage for handling updates when unmounted
 };
 
 // TODO: region-based unselect, shift-click, multi-region
@@ -87,7 +91,7 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
   }
 
   componentDidMount() {
-    let { components } = this.props;
+    let { components, currentChart } = this.props;
 
     // Initialize origin
     let height = this.spaceRef.offsetHeight;
@@ -101,21 +105,39 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
     this.spaceRef.addEventListener("touchmove", (e: any) => e.preventDefault());
     this.spaceRef.addEventListener("mousewheel", (e: any) => e.preventDefault());
 
-    let graph = localStorage.getItem(`charts.${this.props.currentChartName}`)
-    let nodes = [] as NodeType[]
-    let edges = [] as EdgeType[]
+    document.addEventListener("keydown", this.handleKeyDown);
+    document.addEventListener("keyup", this.handleKeyUp);
 
+    // Handle graph from localstorage
+    let graph = localStorage.getItem(`charts.${currentChart.name}-${currentChart.version}`);
+    let nodes = [] as NodeType[];
+    let edges = [] as EdgeType[];
     if (!graph) {
-      nodes = this.createNodes(components)
-      edges = this.createEdges(components)
+      nodes = this.createNodes(components);
+      edges = this.createEdges(components);
       this.setState({ nodes, edges });
     } else {
-      let storedState = JSON.parse(localStorage.getItem(`charts.${this.props.currentChartName}`))
+      let storedState = JSON.parse(localStorage.getItem(
+        `charts.${currentChart.name}-${currentChart.version}`
+      ));
       this.setState(storedState);
     }
 
-    document.addEventListener("keydown", this.handleKeyDown);
-    document.addEventListener("keyup", this.handleKeyUp);
+    window.onbeforeunload = () => {
+      this.storeChart();
+    }
+  }
+
+  // Live update on rollback/upgrade
+  componentDidUpdate(prevProps: PropsType) {
+    if (prevProps.components !== this.props.components) {
+      let nodes = [] as NodeType[];
+      let edges = [] as EdgeType[];
+  
+      nodes = this.createNodes(this.props.components);
+      edges = this.createEdges(this.props.components);
+      this.setState({ nodes, edges, openedNode: null });
+    }
   }
 
   createNodes = (components: ResourceType[]) => {
@@ -142,7 +164,7 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
   }
 
   createEdges = (components: ResourceType[]) => {
-    let edges = [] as EdgeType[]
+    let edges = [] as EdgeType[];
     components.map((c: ResourceType) => {
       c.Relations?.ControlRels?.map((rel: any) => {
         if (rel.Source == c.ID) {
@@ -160,13 +182,14 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
         }
       })
     });
-    return edges
+    return edges;
   }
 
-  componentWillUnmount() {
-    let graph = this.state;
+  storeChart = () => {
+    let { currentChart } = this.props;
+    let graph = JSON.parse(JSON.stringify(this.state));
 
-    // flush non-persistent data
+    // Flush non-persistent data
     graph.activeIds = [];
     graph.currentNode = null;
     graph.currentEdge = null;
@@ -175,7 +198,15 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
     graph.suppressDisplay = false;
     graph.suppressCloseNode = false;
 
-    localStorage.setItem(`charts.${this.props.currentChartName}`, JSON.stringify(graph))
+    localStorage.setItem(
+      `charts.${currentChart.name}-${currentChart.version}`,
+      JSON.stringify(graph)
+    );
+  }
+
+  componentWillUnmount() {
+    this.storeChart();
+    
     this.spaceRef.removeEventListener("touchmove", (e: any) => e.preventDefault());
     this.spaceRef.removeEventListener("mousewheel", (e: any) => e.preventDefault());
     document.removeEventListener("keydown", this.handleKeyDown);
@@ -475,6 +506,7 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
 
           // For YAML wrapper to trigger resize
           isExpanded={this.state.isExpanded}
+          showRevisions={this.props.showRevisions}
         />
       </StyledGraphDisplay>
     );

+ 5 - 2
dashboard/src/main/home/dashboard/expanded-chart/graph/InfoPanel.tsx

@@ -13,7 +13,8 @@ type PropsType = {
   openedNode: NodeType,
   setSuppressDisplay: (x: boolean) => void,
   closeNode: () => void,
-  isExpanded: boolean
+  isExpanded: boolean,
+  showRevisions: boolean
 };
 
 type StateType = {
@@ -51,7 +52,8 @@ export default class InfoPanel extends Component<PropsType, StateType> {
 
   componentDidUpdate(prevProps: PropsType) {
     if ((prevProps.openedNode !== this.props.openedNode 
-      || prevProps.isExpanded !== this.props.isExpanded) && this.wrapperRef
+      || prevProps.isExpanded !== this.props.isExpanded
+      || prevProps.showRevisions !== this.props.showRevisions) && this.wrapperRef
     ) {
       this.setState({ wrapperHeight: this.wrapperRef.offsetHeight });
     }
@@ -210,6 +212,7 @@ const StyledInfoPanel = styled.div`
   height: ${(props: { expanded: boolean }) => props.expanded ? 'calc(100% - 68px)' : '40px'};
   width: ${(props: { expanded: boolean }) => props.expanded ? 'calc(50% - 68px)' : '400px'};
   max-width: 600px;
+  min-width: 400px;
   background: #34373Cdf;
   border-radius: 3px;
   padding-left: 11px;

+ 6 - 4
internal/helm/grapher/object.go

@@ -17,6 +17,12 @@ func ParseObjs(objs []map[string]interface{}) []Object {
 
 	for i, obj := range objs {
 		kind := getField(obj, "kind")
+
+		// ignore block comments
+		if kind == nil {
+			continue
+		}
+
 		name := getField(obj, "metadata", "name")
 		namespace := getField(obj, "metadata", "namespace")
 
@@ -24,10 +30,6 @@ func ParseObjs(objs []map[string]interface{}) []Object {
 			namespace = "default"
 		}
 
-		if kind == nil {
-			kind = ""
-		}
-
 		if name == nil {
 			name = ""
 		}

+ 1 - 2
internal/helm/grapher/relation.go

@@ -1,7 +1,6 @@
 package grapher
 
 import (
-	"fmt"
 	"strconv"
 )
 
@@ -295,7 +294,7 @@ func (parsed *ParsedObjs) findRBACTargets(parentID int, yaml map[string]interfac
 					Target: o.ID,
 				},
 			}
-			fmt.Println(tr["namespace"], o.Kind, tr["kind"], o.Name, tr["name"])
+
 			// first consider case of targets added via subjects, which are namespace scoped.
 			if tr["namespace"] != nil && o.Kind == tr["kind"] && o.Name == tr["name"] &&
 				(o.Namespace == tr["namespace"] || o.Namespace == "default") {