Procházet zdrojové kódy

pulled random stuff

Sean Rhee před 5 roky
rodič
revize
1aa328e0a7
31 změnil soubory, kde provedl 494 přidání a 59 odebrání
  1. 1 2
      dashboard/src/components/ResourceTab.tsx
  2. 3 2
      dashboard/src/main/home/Home.tsx
  3. 2 1
      dashboard/src/main/home/cluster-dashboard/ClusterDashboard.tsx
  4. 0 1
      dashboard/src/main/home/cluster-dashboard/chart/ChartList.tsx
  5. 0 1
      dashboard/src/main/home/cluster-dashboard/expanded-chart/graph/GraphDisplay.tsx
  6. 48 2
      dashboard/src/main/home/cluster-dashboard/expanded-chart/status/ControllerTab.tsx
  7. 2 2
      dashboard/src/main/home/dashboard/Dashboard.tsx
  8. 1 2
      dashboard/src/main/home/dashboard/StatusPlaceholder.tsx
  9. 1 1
      dashboard/src/main/home/new-project/NewProject.tsx
  10. 2 8
      dashboard/src/main/home/project-settings/ProjectSettings.tsx
  11. 4 3
      dashboard/src/main/home/provisioner/AWSFormSection.tsx
  12. 4 4
      dashboard/src/main/home/provisioner/GCPFormSection.tsx
  13. 13 8
      dashboard/src/main/home/provisioner/ProvisionerStatus.tsx
  14. 2 2
      dashboard/src/main/home/templates/Templates.tsx
  15. 1 1
      dashboard/src/main/home/templates/expanded-template/ExpandedTemplate.tsx
  16. 2 0
      dashboard/src/main/home/templates/expanded-template/LaunchTemplate.tsx
  17. 5 6
      dashboard/src/main/home/templates/expanded-template/TemplateInfo.tsx
  18. 8 4
      dashboard/src/shared/Context.tsx
  19. 2 2
      dashboard/src/shared/api.tsx
  20. 11 7
      dashboard/src/shared/common.tsx
  21. 23 0
      helm/.helmignore
  22. 23 0
      helm/Chart.yaml
  23. 21 0
      helm/templates/NOTES.txt
  24. 63 0
      helm/templates/_helpers.tpl
  25. 60 0
      helm/templates/deployment.yaml
  26. 28 0
      helm/templates/hpa.yaml
  27. 43 0
      helm/templates/ingress.yaml
  28. 15 0
      helm/templates/service.yaml
  29. 12 0
      helm/templates/serviceaccount.yaml
  30. 15 0
      helm/templates/tests/test-connection.yaml
  31. 79 0
      helm/values.yaml

+ 1 - 2
dashboard/src/components/ResourceTab.tsx

@@ -176,7 +176,6 @@ const Tooltip = styled.div`
 `;
 
 const ExpandWrapper = styled.div`
-  overflow: hidden;
 `;
 
 const ResourceHeader = styled.div`
@@ -213,7 +212,7 @@ const Metadata = styled.div`
   display: flex;
   align-items: center;
   position: relative;
-  max-width: ${(props: { hasStatus: boolean }) => props.hasStatus ? 'calc(100% - 50px)' : '100%'};
+  max-width: ${(props: { hasStatus: boolean }) => props.hasStatus ? 'calc(100% - 20px)' : '100%'};
 `;
 
 const Status = styled.div`

+ 3 - 2
dashboard/src/main/home/Home.tsx

@@ -67,9 +67,10 @@ export default class Home extends Component<PropsType, StateType> {
           return;
         }
         
-        if (res.data.length > 0 && !(currentCluster || includesCompletedInfraSet(res.data))) {
+        if (res.data.length > 0 && (!currentCluster || !includesCompletedInfraSet(res.data))) {
           this.setState({ currentView: 'provisioner', sidebarReady: true, });
         } else {
+          // console.log('getting here', currentCluster)
           this.setState({ currentView: 'dashboard', sidebarReady: true });
         }
       });
@@ -256,7 +257,7 @@ export default class Home extends Component<PropsType, StateType> {
 
   renderContents = () => {
     let { currentView, handleDO } = this.state;
-    if (this.context.currentProject) {
+    if (this.context.currentProject && currentView !== 'new-project') {
       if (currentView === 'cluster-dashboard') {
         return this.renderDashboard();
       } else if (currentView === 'dashboard') {

+ 2 - 1
dashboard/src/main/home/cluster-dashboard/ClusterDashboard.tsx

@@ -190,7 +190,7 @@ const Button = styled.div`
   font-family: 'Work Sans', sans-serif;
   border-radius: 20px;
   color: white;
-  height: 30px;
+  height: 35px;
   padding: 0px 8px;
   padding-bottom: 1px;
   margin-right: 10px;
@@ -211,6 +211,7 @@ const Button = styled.div`
     color: white;
     width: 18px;
     height: 18px;
+    font-weight: 600;
     font-size: 12px;
     border-radius: 20px;
     display: flex;

+ 0 - 1
dashboard/src/main/home/cluster-dashboard/chart/ChartList.tsx

@@ -71,7 +71,6 @@ export default class ChartList extends Component<PropsType, StateType> {
 
   setupWebsocket = (kind: string) => {
       let { currentCluster, currentProject } = this.context;
-      console.log(currentCluster)
       let protocol = process.env.NODE_ENV == 'production' ? 'wss' : 'ws';
       let ws = new WebSocket(`${protocol}://${process.env.API_SERVER}/api/projects/${currentProject.id}/k8s/${kind}/status?cluster_id=${currentCluster.id}`);
       ws.onopen = () => {

+ 0 - 1
dashboard/src/main/home/cluster-dashboard/expanded-chart/graph/GraphDisplay.tsx

@@ -430,7 +430,6 @@ export default class GraphDisplay extends Component<PropsType, StateType> {
           node.x = cursorX + scale * (node.x - cursorX);
           node.y = cursorY + scale * (node.y - cursorY);
         } else {
-          console.log('hi')
           node.x = midX + scale * (node.x - midX);
           node.y = midY + scale * (node.y - midY);
         }

+ 48 - 2
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/ControllerTab.tsx

@@ -16,6 +16,7 @@ type PropsType = {
 type StateType = {
   pods: any[],
   raw: any[],
+  showTooltip: boolean,
 };
 
 // Controller tab in log section that displays list of pods on click.
@@ -23,6 +24,7 @@ export default class ControllerTab extends Component<PropsType, StateType> {
   state = {
     pods: [] as any[],
     raw: [] as any[],
+    showTooltip: false,
   }
 
   componentDidMount() {
@@ -108,6 +110,12 @@ export default class ControllerTab extends Component<PropsType, StateType> {
     }
   }
 
+  renderTooltip = (x: string): JSX.Element | undefined => {
+    if (this.state.showTooltip) {
+      return <Tooltip>{x}</Tooltip>;
+    }
+  }
+
   render() {
     let { controller, selectedPod, isLast, selectPod, isFirst } = this.props;
     let [available, total] = this.getAvailability(controller.kind, controller);
@@ -135,9 +143,15 @@ export default class ControllerTab extends Component<PropsType, StateType> {
                   <Rail lastTab={i === this.state.raw.length - 1} />
                 </Gutter>
                 <Name
+<<<<<<< HEAD
+=======
+                  onMouseOver={() => { this.setState({ showTooltip: true }) }}
+                  onMouseOut={() => { this.setState({ showTooltip: false }) }}
+>>>>>>> origin/beta.3.integration-frontend
                 >
                   {pod.metadata?.name}
                 </Name>
+                {this.renderTooltip(pod.metadata?.name)}
                 <Status>
                   <StatusColor status={status} />
                   {status}
@@ -205,8 +219,9 @@ const StatusColor = styled.div`
 `;
 
 const Name = styled.div`
-  width: 50%;
+  max-width: calc(100% - 75px);
   overflow: hidden;
+<<<<<<< HEAD
   text-overflow: ellipsis;
   line-height: 16px;
   word-wrap: break-word;
@@ -215,10 +230,40 @@ const Name = styled.div`
   -webkit-box-orient: vertical;
   -webkit-line-clamp: 2;
 `
+=======
+  white-space: nowrap;
+  text-overflow: ellipsis;
+`;
+
+const Tooltip = styled.div`
+  position: absolute;
+  left: 35px;
+  top: 38px;
+  white-space: nowrap;
+  height: 18px;
+  padding: 2px 5px;
+  background: #383842dd;
+  display: flex;
+  align-items: center;
+  justify-content: center;
+  flex: 1;
+  color: white;
+  text-transform: none;
+  font-size: 12px;
+  font-family: "Work Sans", sans-serif;
+  outline: 1px solid #ffffff55;
+  opacity: 0;
+  animation: faded-in 0.2s 0.15s;
+  animation-fill-mode: forwards;
+  @keyframes faded-in {
+    from { opacity: 0 }
+    to { opacity: 1 }
+  }
+`;
+>>>>>>> origin/beta.3.integration-frontend
 
 const Tab = styled.div`
   width: 100%;
-  overflow: hidden;
   height: 50px;
   position: relative;
   display: flex;
@@ -229,6 +274,7 @@ const Tab = styled.div`
   font-size: 13px;
   padding: 20px 19px 20px 42px;
   text-shadow: 0px 0px 8px none;
+  overflow: visible;
   cursor: pointer;
   :hover {
     color: white;

+ 2 - 2
dashboard/src/main/home/dashboard/Dashboard.tsx

@@ -97,7 +97,7 @@ export default class Dashboard extends Component<PropsType, StateType> {
             {!currentCluster && (
               <Banner>
                 <i className="material-icons">error_outline</i>
-                This project currently has no clusters connected.
+                This project currently has no clusters conncted.
               </Banner>
             )}
             <ProvisionerSettings 
@@ -126,7 +126,7 @@ const Banner = styled.div`
   border-radius: 5px;
   padding-left: 15px;
   align-items: center;
-  background: #616FEEcc;
+  background: #ffffff11;
   > i {
     margin-right: 10px;
     font-size: 18px;

+ 1 - 2
dashboard/src/main/home/dashboard/StatusPlaceholder.tsx

@@ -77,7 +77,6 @@ const LoadingWrapper = styled.div`
 const Highlight = styled.div`
   color: #8590ff;
   cursor: pointer;
-  text-decoration: underline;
   margin-left: 10px;
   margin-right: 10px;
 `;
@@ -91,7 +90,7 @@ const Banner = styled.div`
   border-radius: 5px;
   padding-left: 15px;
   align-items: center;
-  background: #616FEEcc;
+  background: #ffffff11;
   > i {
     margin-right: 10px;
     font-size: 18px;

+ 1 - 1
dashboard/src/main/home/new-project/NewProject.tsx

@@ -343,7 +343,7 @@ const TitleSection = styled.div`
 `;
 
 const StyledNewProject = styled.div`
-  width: calc(90% - 150px);
+  width: calc(90% - 130px);
   min-width: 300px;
   position: relative;
   padding-top: 50px;

+ 2 - 8
dashboard/src/main/home/project-settings/ProjectSettings.tsx

@@ -100,9 +100,9 @@ const TitleSection = styled.div`
 `;
 
 const StyledProjectSettings = styled.div`
-  width: calc(90% - 150px);
+  width: calc(90% - 130px);
   min-width: 300px;
-  padding-top: 45px;
+  padding-top: 70px;
 `;
 
 const LineBreak = styled.div`
@@ -179,10 +179,4 @@ const DeleteButton = styled.div`
   :hover {
     filter: brightness(120%);
   }
-`;
-
-const ContentHolder = styled.div`
-  min-width: 420px;
-  width: 100%;
-  margin-bottom: 55px;
 `;

+ 4 - 3
dashboard/src/main/home/provisioner/AWSFormSection.tsx

@@ -143,15 +143,16 @@ export default class AWSFormSection extends Component<PropsType, StateType> {
             return;
           }
           setProjects(res.data);
-          setCurrentProject(proj);
-          callback && callback();
+          setCurrentProject(proj, () => {
+            callback && callback()
+          });
         });
       }
     });
   }
 
   provisionECR = (callback?: any) => {
-    console.log('Provisioning ECR')
+    console.log('Provisioning ECR');
     let { awsAccessId, awsSecretKey, awsRegion } = this.state;
     let { currentProject } = this.context;
     let { handleError } = this.props;

+ 4 - 4
dashboard/src/main/home/provisioner/GCPFormSection.tsx

@@ -64,7 +64,7 @@ const regionOptions = [
 
 export default class GCPFormSection extends Component<PropsType, StateType> {
   state = {
-    gcpRegion: 'us-east-1',
+    gcpRegion: 'us-east1',
     gcpProjectId: '',
     gcpKeyData: '',
     selectedInfras: [...provisionOptions],
@@ -246,7 +246,7 @@ export default class GCPFormSection extends Component<PropsType, StateType> {
           <Heading isAtTop={true}>
             GCP Credentials
             <GuideButton 
-              href='https://docs.getporter.dev/docs/getting-started-with-porter-on-aws' 
+              href='https://docs.getporter.dev/docs/getting-started-on-gcp'
               target='_blank'
             >
               <i className="material-icons-outlined">help</i> 
@@ -266,7 +266,7 @@ export default class GCPFormSection extends Component<PropsType, StateType> {
             value={gcpProjectId}
             setValue={(x: string) => this.setState({ gcpProjectId: x })}
             label='🏷️ GCP Project ID'
-            placeholder='ex: AKIAIOSFODNN7EXAMPLE'
+            placeholder='ex: blindfold-ceiling-24601'
             width='100%'
             isRequired={true}
           />
@@ -274,7 +274,7 @@ export default class GCPFormSection extends Component<PropsType, StateType> {
             type='password'
             value={gcpKeyData}
             setValue={(x: string) => this.setState({ gcpKeyData: x })}
-            label='🔒 GCP Key Data'
+            label='🔒 GCP Key Data (JSON)'
             placeholder='○ ○ ○ ○ ○ ○ ○ ○ ○'
             width='100%'
             isRequired={true}

+ 13 - 8
dashboard/src/main/home/provisioner/ProvisionerStatus.tsx

@@ -57,6 +57,7 @@ export default class ProvisionerStatus extends Component<PropsType, StateType> {
 
   componentDidMount() {
     let { currentProject } = this.context;
+    // console.log(currentProject)
     let protocol = process.env.NODE_ENV == 'production' ? 'wss' : 'ws'
 
     // Check if current project is provisioning
@@ -66,8 +67,9 @@ export default class ProvisionerStatus extends Component<PropsType, StateType> {
       if (err) {
         console.log(err);
       } 
+      
       let infras = filterOldInfras(res.data);
-      console.log('filtered infras: ', infras);
+      // console.log('filtered infras: ', infras);
       let error = false;
 
       let maxStep = {} as Record<string, number>
@@ -79,8 +81,6 @@ export default class ProvisionerStatus extends Component<PropsType, StateType> {
         }
       });
 
-      console.log(infras)
-
       // Filter historical infras list for most current instances of each
       let websockets = infras.map((infra: any) => {
         let ws = new WebSocket(`${protocol}://${process.env.API_SERVER}/api/projects/${currentProject.id}/provision/${infra.kind}/${infra.id}/logs`)
@@ -125,7 +125,6 @@ export default class ProvisionerStatus extends Component<PropsType, StateType> {
           let d = JSON.parse(msg["Values"]["data"]);
 
           if (d["kind"] == "error") {
-            console.log(d)
             err = d["log"];
             break;
           }
@@ -138,18 +137,15 @@ export default class ProvisionerStatus extends Component<PropsType, StateType> {
       }
 
       if (err) {
-        console.log(err)
         let e = ansiparse(err).map((el: any) => {
           return el.text;
         })
 
-        console.log(e)
-
         let index = this.state.infras.findIndex(el => el.kind === infra.kind)
         infra.status = "error"
         let infras = this.state.infras
         infras[index] = infra
-        this.setState({ logs: e, error: true, infras });
+        this.setState({ logs: [...this.state.logs, ...e], error: true, infras });
         return;
       }
 
@@ -213,8 +209,17 @@ export default class ProvisionerStatus extends Component<PropsType, StateType> {
         } else if (res.data) {
           let clusters = res.data;
           if (clusters.length > 0) {
+            // console.log('response :', res.data);
             this.props.setCurrentView('dashboard');
+            alert('setting to dashboard');
+            // console.log('provision end project: ', this.context.currentProject);
+            // console.log('provision end cluster: ', this.context.currentCluster);
             clearInterval(myInterval);
+          } else {
+            // console.log('looped!')
+            // console.log('response :', res.data);
+            // console.log('provision end project: ', this.context.currentProject);
+            // console.log('provision end cluster: ', this.context.currentCluster);
           }
         }
       });

+ 2 - 2
dashboard/src/main/home/templates/Templates.tsx

@@ -266,7 +266,7 @@ const TitleSection = styled.div`
 `;
 
 const TemplatesWrapper = styled.div`
-  width: calc(90% - 150px);
+  width: calc(90% - 130px);
   min-width: 300px;
-  padding-top: 50px;
+  padding-top: 75px;
 `;

+ 1 - 1
dashboard/src/main/home/templates/expanded-template/ExpandedTemplate.tsx

@@ -105,5 +105,5 @@ const LoadingWrapper = styled.div`
 const StyledExpandedTemplate = styled.div`
   width: calc(90% - 150px);
   min-width: 300px;
-  padding-top: 50px;
+  padding-top: 75px;
 `;

+ 2 - 0
dashboard/src/main/home/templates/expanded-template/LaunchTemplate.tsx

@@ -9,6 +9,7 @@ import { PorterTemplate, ChoiceType, ClusterType, StorageType } from '../../../.
 import Selector from '../../../../components/Selector';
 import ImageSelector from '../../../../components/image-selector/ImageSelector';
 import TabRegion from '../../../../components/TabRegion';
+import Heading from '../../../../components/values-form/Heading';
 import SaveButton from '../../../../components/SaveButton';
 import ValuesWrapper from '../../../../components/values-form/ValuesWrapper';
 import ValuesForm from '../../../../components/values-form/ValuesForm';
@@ -319,6 +320,7 @@ export default class LaunchTemplate extends Component<PropsType, StateType> {
             closeOverlay={true}
           />
         </ClusterSection>
+        <Subtitle>Name</Subtitle>
         {this.renderSourceSelector()}
         {this.renderTabRegion()}
       </StyledLaunchTemplate>

+ 5 - 6
dashboard/src/main/home/templates/expanded-template/TemplateInfo.tsx

@@ -144,8 +144,7 @@ export default class TemplateInfo extends Component<PropsType, StateType> {
 TemplateInfo.contextType = Context;
 
 const Link = styled.a`
-  text-decoration: underline;
-  color: white;
+  color: #8590ff;
   cursor: pointer;
   margin-left: 5px;
 `;
@@ -164,7 +163,7 @@ const Banner = styled.div`
   border-radius: 5px;
   padding-left: 15px;
   align-items: center;
-  background: #616FEEcc;
+  background: #ffffff11;
   > i {
     margin-right: 10px;
     font-size: 18px;
@@ -226,7 +225,7 @@ const Flex = styled.div`
 `;
 
 const Button = styled.div`
-  height: 100%;
+  height: 35px;
   background: ${(props: { isDisabled: boolean }) => (!props.isDisabled ? '#616feecc' : '#aaaabb')};
   :hover {
     background: ${(props: { isDisabled: boolean }) => (!props.isDisabled ? '#505edddd' : '#aaaabb')};
@@ -243,8 +242,8 @@ const Button = styled.div`
   align-items: center;
 
   > img {
-    width: 20px;
-    height: 20px;
+    width: 16px;
+    height: 16px;
     display: flex;
     align-items: center;
     margin-right: 10px;

+ 8 - 4
dashboard/src/shared/Context.tsx

@@ -36,12 +36,16 @@ class ContextProvider extends Component {
       this.setState({ currentError });
     },
     currentCluster: null as ClusterType | null,
-    setCurrentCluster: (currentCluster: ClusterType) => {
-      this.setState({ currentCluster });
+    setCurrentCluster: (currentCluster: ClusterType, callback?: any) => {
+      this.setState({ currentCluster }, () => {
+        callback && callback();
+      });
     },
     currentProject: null as ProjectType | null,
-    setCurrentProject: (currentProject: ProjectType) => {
-      this.setState({ currentProject });
+    setCurrentProject: (currentProject: ProjectType, callback?: any) => {
+      this.setState({ currentProject }, () => {
+        callback && callback();
+      });
     },
     projects: [] as ProjectType[],
     setProjects: (projects: ProjectType[]) => {

+ 2 - 2
dashboard/src/shared/api.tsx

@@ -328,7 +328,7 @@ const createGCR = baseApi<{
 }, {
   project_id: number,
 }>('POST', pathParams => {
-  return `/api/projects/${pathParams.project_id}/provision/test`;
+  return `/api/projects/${pathParams.project_id}/provision/gcr`;
 });
 
 const createGKE = baseApi<{
@@ -337,7 +337,7 @@ const createGKE = baseApi<{
 }, {
   project_id: number,
 }>('POST', pathParams => {
-  return `/api/projects/${pathParams.project_id}/provision/test`;
+  return `/api/projects/${pathParams.project_id}/provision/gke`;
 });
 
 const createInvite = baseApi<{

+ 11 - 7
dashboard/src/shared/common.tsx

@@ -80,13 +80,13 @@ export const getIgnoreCase = (object: any, key: string) => {
   ];
 }
 
-const infraSets = [
-  ['ecr', 'eks'],
-  ['gcr', 'gke'],
-  ['docr', 'doks']
-];
-
 export const includesCompletedInfraSet = (infras: InfraType[]): boolean => {
+  // TODO: declare globally while avoidiing changes to the array on helper call
+  let infraSets = [
+    ['ecr', 'eks'],
+    ['gcr', 'gke'],
+    ['docr', 'doks']
+  ];
   if (infras.length === 0) {
     return false;
   }
@@ -114,6 +114,11 @@ export const includesCompletedInfraSet = (infras: InfraType[]): boolean => {
 }
 
 export const filterOldInfras = (infras: InfraType[]): InfraType[] => {
+  let infraSets = [
+    ['ecr', 'eks'],
+    ['gcr', 'gke'],
+    ['docr', 'doks']
+  ];
   let newestInstances = {} as any;
   let newestId = -1;
   let whitelistedInfras = [] as string[];
@@ -141,6 +146,5 @@ export const filterOldInfras = (infras: InfraType[]): InfraType[] => {
   let result = newestInfras.filter((x: InfraType) => {
     return whitelistedInfras.includes(x.kind)
   });
-  console.log('filtered infras (helper internal): ', result);
   return result;
 }

+ 23 - 0
helm/.helmignore

@@ -0,0 +1,23 @@
+# Patterns to ignore when building packages.
+# This supports shell glob matching, relative path matching, and
+# negation (prefixed with !). Only one pattern per line.
+.DS_Store
+# Common VCS dirs
+.git/
+.gitignore
+.bzr/
+.bzrignore
+.hg/
+.hgignore
+.svn/
+# Common backup files
+*.swp
+*.bak
+*.tmp
+*.orig
+*~
+# Various IDEs
+.project
+.idea/
+*.tmproj
+.vscode/

+ 23 - 0
helm/Chart.yaml

@@ -0,0 +1,23 @@
+apiVersion: v2
+name: porter-prod
+description: A Helm chart for Kubernetes
+
+# A chart can be either an 'application' or a 'library' chart.
+#
+# Application charts are a collection of templates that can be packaged into versioned archives
+# to be deployed.
+#
+# Library charts provide useful utilities or functions for the chart developer. They're included as
+# a dependency of application charts to inject those utilities and functions into the rendering
+# pipeline. Library charts do not define any templates and therefore cannot be deployed.
+type: application
+
+# This is the chart version. This version number should be incremented each time you make changes
+# to the chart and its templates, including the app version.
+# Versions are expected to follow Semantic Versioning (https://semver.org/)
+version: 0.1.0
+
+# This is the version number of the application being deployed. This version number should be
+# incremented each time you make changes to the application. Versions are not expected to
+# follow Semantic Versioning. They should reflect the version the application is using.
+appVersion: 1.16.0

+ 21 - 0
helm/templates/NOTES.txt

@@ -0,0 +1,21 @@
+1. Get the application URL by running these commands:
+{{- if .Values.ingress.enabled }}
+{{- range $host := .Values.ingress.hosts }}
+  {{- range .paths }}
+  http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ . }}
+  {{- end }}
+{{- end }}
+{{- else if contains "NodePort" .Values.service.type }}
+  export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "porter-prod.fullname" . }})
+  export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}")
+  echo http://$NODE_IP:$NODE_PORT
+{{- else if contains "LoadBalancer" .Values.service.type }}
+     NOTE: It may take a few minutes for the LoadBalancer IP to be available.
+           You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "porter-prod.fullname" . }}'
+  export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "porter-prod.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}")
+  echo http://$SERVICE_IP:{{ .Values.service.port }}
+{{- else if contains "ClusterIP" .Values.service.type }}
+  export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "porter-prod.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}")
+  echo "Visit http://127.0.0.1:8080 to use your application"
+  kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:80
+{{- end }}

+ 63 - 0
helm/templates/_helpers.tpl

@@ -0,0 +1,63 @@
+{{/* vim: set filetype=mustache: */}}
+{{/*
+Expand the name of the chart.
+*/}}
+{{- define "porter-prod.name" -}}
+{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Create a default fully qualified app name.
+We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
+If release name contains chart name it will be used as a full name.
+*/}}
+{{- define "porter-prod.fullname" -}}
+{{- if .Values.fullnameOverride }}
+{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- $name := default .Chart.Name .Values.nameOverride }}
+{{- if contains $name .Release.Name }}
+{{- .Release.Name | trunc 63 | trimSuffix "-" }}
+{{- else }}
+{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
+{{- end }}
+{{- end }}
+{{- end }}
+
+{{/*
+Create chart name and version as used by the chart label.
+*/}}
+{{- define "porter-prod.chart" -}}
+{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
+{{- end }}
+
+{{/*
+Common labels
+*/}}
+{{- define "porter-prod.labels" -}}
+helm.sh/chart: {{ include "porter-prod.chart" . }}
+{{ include "porter-prod.selectorLabels" . }}
+{{- if .Chart.AppVersion }}
+app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
+{{- end }}
+app.kubernetes.io/managed-by: {{ .Release.Service }}
+{{- end }}
+
+{{/*
+Selector labels
+*/}}
+{{- define "porter-prod.selectorLabels" -}}
+app.kubernetes.io/name: {{ include "porter-prod.name" . }}
+app.kubernetes.io/instance: {{ .Release.Name }}
+{{- end }}
+
+{{/*
+Create the name of the service account to use
+*/}}
+{{- define "porter-prod.serviceAccountName" -}}
+{{- if .Values.serviceAccount.create }}
+{{- default (include "porter-prod.fullname" .) .Values.serviceAccount.name }}
+{{- else }}
+{{- default "default" .Values.serviceAccount.name }}
+{{- end }}
+{{- end }}

+ 60 - 0
helm/templates/deployment.yaml

@@ -0,0 +1,60 @@
+apiVersion: apps/v1
+kind: Deployment
+metadata:
+  name: {{ include "porter-prod.fullname" . }}
+  labels:
+    {{- include "porter-prod.labels" . | nindent 4 }}
+spec:
+{{- if not .Values.autoscaling.enabled }}
+  replicas: {{ .Values.replicaCount }}
+{{- end }}
+  selector:
+    matchLabels:
+      {{- include "porter-prod.selectorLabels" . | nindent 6 }}
+  template:
+    metadata:
+    {{- with .Values.podAnnotations }}
+      annotations:
+        {{- toYaml . | nindent 8 }}
+    {{- end }}
+      labels:
+        {{- include "porter-prod.selectorLabels" . | nindent 8 }}
+    spec:
+      {{- with .Values.imagePullSecrets }}
+      imagePullSecrets:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      volumes:
+        - name: wss-ssl-certificate
+          secret:
+            secretName: ingress-dashboard
+      serviceAccountName: {{ include "porter-prod.serviceAccountName" . }}
+      securityContext:
+        {{- toYaml .Values.podSecurityContext | nindent 8 }}
+      containers:
+        - name: {{ .Chart.Name }}
+          securityContext:
+            {{- toYaml .Values.securityContext | nindent 12 }}
+          image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
+          imagePullPolicy: {{ .Values.image.pullPolicy }}
+          ports:
+            - name: http
+              containerPort: 8080
+              protocol: TCP
+          resources:
+            {{- toYaml .Values.resources | nindent 12 }}
+          volumeMounts:
+            - name: wss-ssl-certificate
+              mountPath: /etc/wss
+      {{- with .Values.nodeSelector }}
+      nodeSelector:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.affinity }}
+      affinity:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}
+      {{- with .Values.tolerations }}
+      tolerations:
+        {{- toYaml . | nindent 8 }}
+      {{- end }}

+ 28 - 0
helm/templates/hpa.yaml

@@ -0,0 +1,28 @@
+{{- if .Values.autoscaling.enabled }}
+apiVersion: autoscaling/v2beta1
+kind: HorizontalPodAutoscaler
+metadata:
+  name: {{ include "porter-prod.fullname" . }}
+  labels:
+    {{- include "porter-prod.labels" . | nindent 4 }}
+spec:
+  scaleTargetRef:
+    apiVersion: apps/v1
+    kind: Deployment
+    name: {{ include "porter-prod.fullname" . }}
+  minReplicas: {{ .Values.autoscaling.minReplicas }}
+  maxReplicas: {{ .Values.autoscaling.maxReplicas }}
+  metrics:
+  {{- if .Values.autoscaling.targetCPUUtilizationPercentage }}
+    - type: Resource
+      resource:
+        name: cpu
+        targetAverageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }}
+  {{- end }}
+  {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }}
+    - type: Resource
+      resource:
+        name: memory
+        targetAverageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }}
+  {{- end }}
+{{- end }}

+ 43 - 0
helm/templates/ingress.yaml

@@ -0,0 +1,43 @@
+{{- if .Values.ingress.enabled -}}
+{{- $fullName := include "porter-prod.fullname" . -}}
+{{- $svcPort := .Values.service.port -}}
+{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: networking.k8s.io/v1beta1
+{{- else -}}
+apiVersion: extensions/v1beta1
+{{- end }}
+kind: Ingress
+metadata:
+  name: {{ $fullName }}
+  labels:
+    {{- include "porter-prod.labels" . | nindent 4 }}
+  annotations:
+    kubernetes.io/ingress.global-static-ip-name: porter-hosted
+    cert-manager.io/cluster-issuer: letsencrypt-prod
+spec:
+  {{- if .Values.ingress.tls }}
+  tls:
+    {{- range .Values.ingress.tls }}
+    - hosts:
+        {{- range .hosts }}
+        - {{ . | quote }}
+        {{- end }}
+      secretName: {{ .secretName }}
+    {{- end }}
+  {{- end }}
+  backend:
+    serviceName: {{ $fullName }}
+    servicePort: {{ $svcPort }}
+  rules:
+    {{- range .Values.ingress.hosts }}
+    - host: {{ .host | quote }}
+      http:
+        paths:
+          {{- range .paths }}
+          - path: {{ . }}
+            backend:
+              serviceName: {{ $fullName }}
+              servicePort: {{ $svcPort }}
+          {{- end }}
+    {{- end }}
+  {{- end }}

+ 15 - 0
helm/templates/service.yaml

@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Service
+metadata:
+  name: {{ include "porter-prod.fullname" . }}
+  labels:
+    {{- include "porter-prod.labels" . | nindent 4 }}
+spec:
+  type: {{ .Values.service.type }}
+  ports:
+    - port: {{ .Values.service.port }}
+      targetPort: http
+      protocol: TCP
+      name: http
+  selector:
+    {{- include "porter-prod.selectorLabels" . | nindent 4 }}

+ 12 - 0
helm/templates/serviceaccount.yaml

@@ -0,0 +1,12 @@
+{{- if .Values.serviceAccount.create -}}
+apiVersion: v1
+kind: ServiceAccount
+metadata:
+  name: {{ include "porter-prod.serviceAccountName" . }}
+  labels:
+    {{- include "porter-prod.labels" . | nindent 4 }}
+  {{- with .Values.serviceAccount.annotations }}
+  annotations:
+    {{- toYaml . | nindent 4 }}
+  {{- end }}
+{{- end }}

+ 15 - 0
helm/templates/tests/test-connection.yaml

@@ -0,0 +1,15 @@
+apiVersion: v1
+kind: Pod
+metadata:
+  name: "{{ include "porter-prod.fullname" . }}-test-connection"
+  labels:
+    {{- include "porter-prod.labels" . | nindent 4 }}
+  annotations:
+    "helm.sh/hook": test-success
+spec:
+  containers:
+    - name: wget
+      image: busybox
+      command: ['wget']
+      args: ['{{ include "porter-prod.fullname" . }}:{{ .Values.service.port }}']
+  restartPolicy: Never

+ 79 - 0
helm/values.yaml

@@ -0,0 +1,79 @@
+# Default values for porter-prod.
+# This is a YAML-formatted file.
+# Declare variables to be passed into your templates.
+
+replicaCount: 1
+
+image:
+  repository: porteralpha/porter-prod
+  pullPolicy: Always
+  # Overrides the image tag whose default is the chart appVersion.
+  tag: latest
+
+imagePullSecrets: []
+nameOverride: ""
+fullnameOverride: ""
+
+serviceAccount:
+  # Specifies whether a service account should be created
+  create: false
+  # Annotations to add to the service account
+  annotations: {}
+  # The name of the service account to use.
+  # If not set and create is true, a name is generated using the fullname template
+  name: ""
+
+podAnnotations: {}
+
+podSecurityContext: {}
+  # fsGroup: 2000
+
+securityContext: {}
+  # capabilities:
+  #   drop:
+  #   - ALL
+  # readOnlyRootFilesystem: true
+  # runAsNonRoot: true
+  # runAsUser: 1000
+
+service:
+  type: NodePort
+  port: 443
+
+ingress:
+  enabled: true
+  annotations: {}
+    # kubernetes.io/ingress.class: nginx
+    # kubernetes.io/tls-acme: "true"
+  hosts:
+    - host: dashboard.getporter.dev
+      paths: ['/*']
+  tls:
+    - secretName: ingress-dashboard
+      hosts:
+        - dashboard.getporter.dev
+
+resources: {}
+  # We usually recommend not to specify default resources and to leave this as a conscious
+  # choice for the user. This also increases chances charts run on environments with little
+  # resources, such as Minikube. If you do want to specify resources, uncomment the following
+  # lines, adjust them as necessary, and remove the curly braces after 'resources:'.
+  # limits:
+  #   cpu: 100m
+  #   memory: 128Mi
+  # requests:
+  #   cpu: 100m
+  #   memory: 128Mi
+
+autoscaling:
+  enabled: false
+  minReplicas: 1
+  maxReplicas: 100
+  targetCPUUtilizationPercentage: 80
+  # targetMemoryUtilizationPercentage: 80
+
+nodeSelector: {}
+
+tolerations: []
+
+affinity: {}