Sfoglia il codice sorgente

Merge branch 'sean-testing' of https://github.com/porter-dev/porter into beta.3.integration-frontend

jusrhee 5 anni fa
parent
commit
e927f1b7fa

+ 36 - 1
cli/cmd/connect/ecr.go

@@ -4,11 +4,16 @@ import (
 	"context"
 	"fmt"
 	"strings"
+	"time"
 
+	"github.com/aws/aws-sdk-go/service/ecr"
 	"github.com/fatih/color"
 	"github.com/porter-dev/porter/cli/cmd/api"
-	awsLocal "github.com/porter-dev/porter/cli/cmd/providers/aws/local"
 	"github.com/porter-dev/porter/cli/cmd/utils"
+	"github.com/porter-dev/porter/internal/models/integrations"
+
+	"github.com/porter-dev/porter/cli/cmd/providers/aws"
+	awsLocal "github.com/porter-dev/porter/cli/cmd/providers/aws/local"
 )
 
 // ECR creates an ECR integration
@@ -50,6 +55,8 @@ Would you like to proceed? %s `,
 			return ecrManual(client, projectID, region)
 		}
 
+		waitForAuthorizationToken(region, creds)
+
 		integration, err := client.CreateAWSIntegration(
 			context.Background(),
 			projectID,
@@ -142,3 +149,31 @@ func linkRegistry(client *api.Client, projectID uint, intID uint) (uint, error)
 
 	return reg.ID, nil
 }
+
+func waitForAuthorizationToken(region string, creds *aws.PorterAWSCredentials) error {
+	awsInt := &integrations.AWSIntegration{
+		AWSRegion:          region,
+		AWSAccessKeyID:     []byte(creds.AWSAccessKeyID),
+		AWSSecretAccessKey: []byte(creds.AWSSecretAccessKey),
+	}
+
+	sess, err := awsInt.GetSession()
+
+	if err != nil {
+		return err
+	}
+
+	ecrSvc := ecr.New(sess)
+
+	for i := 0; i < 30; i++ {
+		_, err := ecrSvc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{})
+
+		if err == nil {
+			return nil
+		}
+
+		time.Sleep(2 * time.Second)
+	}
+
+	return fmt.Errorf("could not get ECR authorization token, please check credentials")
+}

+ 1 - 1
cli/cmd/providers/aws/agent.go

@@ -134,7 +134,7 @@ func (a *Agent) CreateIAMECRUser(region string) (*PorterAWSCredentials, error) {
 		name = *user.UserName
 	}
 
-	policyArn := "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
+	policyArn := "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess"
 
 	_, err = a.IAMService.AttachUserPolicy(&iam.AttachUserPolicyInput{
 		PolicyArn: &policyArn,

+ 3 - 2
cli/cmd/server.go

@@ -5,6 +5,7 @@ import (
 	"os"
 	"os/exec"
 	"path/filepath"
+	"strings"
 
 	"github.com/fatih/color"
 	"github.com/porter-dev/porter/cli/cmd/docker"
@@ -184,7 +185,7 @@ func startLocal(
 	}
 
 	// otherwise, check the version flag of the binary
-	cmdVersionPorter := exec.Command(cmdPath)
+	cmdVersionPorter := exec.Command(cmdPath, "--version")
 	writer := &versionWriter{}
 	cmdVersionPorter.Stdout = writer
 
@@ -277,7 +278,7 @@ type versionWriter struct {
 }
 
 func (v *versionWriter) Write(p []byte) (n int, err error) {
-	v.Version = string(p)
+	v.Version = strings.TrimSpace(string(p))
 
 	return len(p), nil
 }

+ 1 - 0
dashboard/src/components/Selector.tsx

@@ -58,6 +58,7 @@ export default class Selector extends Component<PropsType, StateType> {
           <Dropdown
             dropdownWidth={this.props.dropdownWidth ? this.props.dropdownWidth : this.props.width}
             dropdownMaxHeight={this.props.dropdownMaxHeight}
+            onClick={() => this.setState({ expanded: false })}
           >
             {this.renderDropdownLabel()}
             {this.renderOptionList()}

+ 28 - 6
dashboard/src/main/home/cluster-dashboard/ClusterDashboard.tsx

@@ -8,6 +8,7 @@ import api from '../../../shared/api';
 
 import ChartList from './chart/ChartList';
 import NamespaceSelector from './NamespaceSelector';
+import SortSelector from './SortSelector';
 import ExpandedChart from './expanded-chart/ExpandedChart';
 
 type PropsType = {
@@ -18,20 +19,28 @@ type PropsType = {
 
 type StateType = {
   namespace: string,
+  sortType: string,
   currentChart: ChartType | null
 };
 
 export default class ClusterDashboard extends Component<PropsType, StateType> {
   state = {
     namespace: 'default',
+    sortType: 'Newest',
     currentChart: null as (ChartType | null)
   }
 
-  componentDidUpdate(prevProps: PropsType) {
+  componentDidMount() {
+    if (localStorage.getItem("SortType")) {
+      this.setState({ sortType: localStorage.getItem("SortType") });
+    }
+  }
 
+  componentDidUpdate(prevProps: PropsType) {
+    localStorage.setItem("SortType", this.state.sortType);
     // Reset namespace filter and close expanded chart on cluster change
     if (prevProps.currentCluster !== this.props.currentCluster) {
-      this.setState({ namespace: 'default', currentChart: null });
+      this.setState({ namespace: 'default', sortType: 'Newest', currentChart: null });
     }
   }
 
@@ -101,15 +110,22 @@ export default class ClusterDashboard extends Component<PropsType, StateType> {
           >
             <i className="material-icons">add</i> Deploy Template
           </Button>
-          <NamespaceSelector
-            setNamespace={(namespace) => this.setState({ namespace })}
-            namespace={this.state.namespace}
-          />
+          <SortFilterWrapper>
+            <SortSelector
+              setSortType={(sortType) => this.setState({ sortType })}
+              sortType={this.state.sortType}
+            />
+            <NamespaceSelector
+              setNamespace={(namespace) => this.setState({ namespace })}
+              namespace={this.state.namespace}
+            />
+          </SortFilterWrapper>
         </ControlRow>
 
         <ChartList
           currentCluster={currentCluster}
           namespace={this.state.namespace}
+          sortType={this.state.sortType}
           setCurrentChart={(x: ChartType | null) => this.setState({ currentChart: x })}
         />
       </div>
@@ -297,4 +313,10 @@ const TitleSection = styled.div`
     }
     margin-bottom: -3px;
   }
+`;
+
+const SortFilterWrapper = styled.div`
+  width: 468px;
+  display: flex;
+  justify-content: space-between;
 `;

+ 64 - 0
dashboard/src/main/home/cluster-dashboard/SortSelector.tsx

@@ -0,0 +1,64 @@
+import React, { Component } from 'react';
+import styled from 'styled-components';
+
+import { Context } from '../../../shared/Context';
+
+import Selector from '../../../components/Selector';
+
+type PropsType = {
+  setSortType: (x: string) => void,
+  sortType: string
+};
+
+type StateType = {
+  sortOptions: { label: string, value: string }[]
+};
+
+// TODO: fix update to unmounted component 
+export default class SortSelector extends Component<PropsType, StateType> {
+  state = {
+    sortOptions: [
+      { label: 'Newest', value: 'Newest' },
+      { label: 'Oldest', value: 'Oldest' },
+      { label: 'Alphabetical', value: 'Alphabetical' }
+    ] as {label: string, value: string}[]
+  }
+
+  render() {
+    return ( 
+      <StyledSortSelector>
+        <Label>
+          <i className="material-icons">sort</i> Sort
+        </Label>
+        <Selector
+          activeValue={this.props.sortType}
+          setActiveValue={(sortType) => this.props.setSortType(sortType)}
+          options={this.state.sortOptions}
+          dropdownLabel='Sort By'
+          width='150px'
+          dropdownWidth='230px'
+          closeOverlay={true}
+        />
+      </StyledSortSelector>
+    );
+  }
+}
+
+SortSelector.contextType = Context;
+
+const Label = styled.div`
+  display: flex;
+  align-items: center;
+  margin-right: 12px;
+
+  > i {
+    margin-right: 8px;
+    font-size: 18px;
+  }
+`;
+
+const StyledSortSelector = styled.div`
+  display: flex;
+  align-items: center;
+  font-size: 13px;
+`;

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

@@ -11,6 +11,7 @@ import Loading from '../../../../components/Loading';
 type PropsType = {
   currentCluster: ClusterType,
   namespace: string,
+  sortType: string,
   setCurrentChart: (c: ChartType) => void
 };
 
@@ -53,6 +54,13 @@ export default class ChartList extends Component<PropsType, StateType> {
         this.setState({ loading: false, error: true });
       } else {
         let charts = res.data || [];
+        if (this.props.sortType == "Newest") {
+          charts.sort((a: any, b: any) => (Date.parse(a.info.last_deployed) > Date.parse(b.info.last_deployed)) ? -1 : 1);
+        } else if (this.props.sortType == "Oldest") {
+          charts.sort((a: any, b: any) => (Date.parse(a.info.last_deployed) > Date.parse(b.info.last_deployed)) ? 1 : -1);
+        } else if (this.props.sortType == "Alphabetical") {
+          charts.sort((a: any, b: any) => (a.name > b.name) ? 1: -1);
+        }
         this.setState({ charts }, () => {
           this.setState({ loading: false, error: false });
         });
@@ -176,7 +184,8 @@ export default class ChartList extends Component<PropsType, StateType> {
   componentDidUpdate(prevProps: PropsType) {
     // Ret2: Prevents reload when opening ClusterConfigModal
     if (prevProps.currentCluster !== this.props.currentCluster || 
-      prevProps.namespace !== this.props.namespace) {
+      prevProps.namespace !== this.props.namespace ||
+      prevProps.sortType !== this.props.sortType) {
       this.updateCharts(this.getControllers);
     }
   }

+ 1 - 1
internal/config/config.go

@@ -29,7 +29,7 @@ type ServerConf struct {
 	IsLocal        bool          `env:"IS_LOCAL,default=false"`
 	IsTesting      bool          `env:"IS_TESTING,default=false"`
 
-	DefaultHelmRepoURL string `env:"HELM_REPO_URL,default=https://porter-dev.github.io/chart-repo/"`
+	DefaultHelmRepoURL string `env:"HELM_REPO_URL,default=https://s2011r2593.github.io/test-porter-chart-repo/"`
 
 	GithubClientID     string `env:"GITHUB_CLIENT_ID"`
 	GithubClientSecret string `env:"GITHUB_CLIENT_SECRET"`

+ 2 - 0
internal/forms/integration.go

@@ -11,6 +11,7 @@ type CreateGCPIntegrationForm struct {
 	ProjectID    uint   `json:"project_id" form:"required"`
 	GCPKeyData   string `json:"gcp_key_data" form:"required"`
 	GCPProjectID string `json:"gcp_project_id"`
+	GCPRegion    string `json:"gcp_region"`
 }
 
 // ToGCPIntegration converts the project to a gorm project model
@@ -20,6 +21,7 @@ func (cgf *CreateGCPIntegrationForm) ToGCPIntegration() (*ints.GCPIntegration, e
 		ProjectID:    cgf.ProjectID,
 		GCPKeyData:   []byte(cgf.GCPKeyData),
 		GCPProjectID: cgf.GCPProjectID,
+		GCPRegion:    cgf.GCPRegion,
 	}, nil
 }
 

+ 30 - 0
internal/kubernetes/provisioner/gcp/gcp.go

@@ -0,0 +1,30 @@
+package gcp
+
+import (
+	v1 "k8s.io/api/core/v1"
+)
+
+// Conf wraps the GCP integration model
+type Conf struct {
+	GCPRegion, GCPProjectID, GCPKeyData string
+}
+
+// AttachGCPEnv adds the relevant AWS env for the provisioner
+func (conf *Conf) AttachGCPEnv(env []v1.EnvVar) []v1.EnvVar {
+	env = append(env, v1.EnvVar{
+		Name:  "GCP_REGION",
+		Value: conf.GCPRegion,
+	})
+
+	env = append(env, v1.EnvVar{
+		Name:  "GCP_CREDENTIALS",
+		Value: conf.GCPKeyData,
+	})
+
+	env = append(env, v1.EnvVar{
+		Name:  "GCP_PROJECT_ID",
+		Value: conf.GCPProjectID,
+	})
+
+	return env
+}

+ 8 - 0
internal/kubernetes/provisioner/provisioner.go

@@ -11,6 +11,8 @@ import (
 	"github.com/porter-dev/porter/internal/kubernetes/provisioner/aws/ecr"
 	"github.com/porter-dev/porter/internal/kubernetes/provisioner/aws/eks"
 
+	"github.com/porter-dev/porter/internal/kubernetes/provisioner/gcp"
+
 	"github.com/porter-dev/porter/internal/config"
 )
 
@@ -22,6 +24,7 @@ const (
 	Test InfraOption = "test"
 	ECR  InfraOption = "ecr"
 	EKS  InfraOption = "eks"
+	GCR  InfraOption = "gcr"
 )
 
 // Conf is the config required to start a provisioner container
@@ -35,9 +38,14 @@ type Conf struct {
 	Operation ProvisionerOperation
 
 	// provider-specific configurations
+
+	// AWS
 	AWS *aws.Conf
 	ECR *ecr.Conf
 	EKS *eks.Conf
+
+	// GKE
+	GCP *gcp.Conf
 }
 
 type ProvisionerOperation string

+ 4 - 1
internal/models/integrations/gcp.go

@@ -19,11 +19,14 @@ type GCPIntegration struct {
 	ProjectID uint `json:"project_id"`
 
 	// The GCP project id where the service account for this auth mechanism persists
-	GCPProjectID string `json:"gcp-project-id"`
+	GCPProjectID string `json:"gcp_project_id"`
 
 	// The GCP user email that linked this service account
 	GCPUserEmail string `json:"gcp-user-email"`
 
+	// The GCP region, which may or may not be used by the integration
+	GCPRegion string `json:"gcp_region"`
+
 	// ------------------------------------------------------------------
 	// All fields encrypted before storage.
 	// ------------------------------------------------------------------