ソースを参照

Merge branch 'belanger/add-assume-role-support' into dev

Alexander Belanger 3 年 前
コミット
136cdb5559

+ 1 - 0
api/server/handlers/project_integration/create_aws.go

@@ -57,6 +57,7 @@ func CreateAWSIntegration(request *types.CreateAWSRequest, projectID, userID uin
 		UserID:             userID,
 		ProjectID:          projectID,
 		AWSRegion:          request.AWSRegion,
+		AWSAssumeRoleArn:   request.AWSAssumeRoleArn,
 		AWSClusterID:       []byte(request.AWSClusterID),
 		AWSAccessKeyID:     []byte(request.AWSAccessKeyID),
 		AWSSecretAccessKey: []byte(request.AWSSecretAccessKey),

+ 15 - 10
api/server/shared/config/metadata.go

@@ -15,20 +15,25 @@ type Metadata struct {
 	Analytics          bool   `json:"analytics"`
 	Version            string `json:"version"`
 	Gitlab             bool   `json:"gitlab"`
+
+	DefaultAppHelmRepoURL   string `json:"default_app_helm_repo_url"`
+	DefaultAddonHelmRepoURL string `json:"default_addon_helm_repo_url"`
 }
 
 func MetadataFromConf(sc *env.ServerConf, version string) *Metadata {
 	return &Metadata{
-		Provisioning:       sc.ProvisionerServerURL != "" && sc.ProvisionerToken != "",
-		Github:             hasGithubAppVars(sc),
-		GithubLogin:        sc.GithubClientID != "" && sc.GithubClientSecret != "" && sc.GithubLoginEnabled,
-		BasicLogin:         sc.BasicLoginEnabled,
-		GoogleLogin:        sc.GoogleClientID != "" && sc.GoogleClientSecret != "",
-		SlackNotifications: sc.SlackClientID != "" && sc.SlackClientSecret != "",
-		Email:              sc.SendgridAPIKey != "",
-		Analytics:          sc.SegmentClientKey != "",
-		Version:            version,
-		Gitlab:             sc.EnableGitlab,
+		Provisioning:            sc.ProvisionerServerURL != "" && sc.ProvisionerToken != "",
+		Github:                  hasGithubAppVars(sc),
+		GithubLogin:             sc.GithubClientID != "" && sc.GithubClientSecret != "" && sc.GithubLoginEnabled,
+		BasicLogin:              sc.BasicLoginEnabled,
+		GoogleLogin:             sc.GoogleClientID != "" && sc.GoogleClientSecret != "",
+		SlackNotifications:      sc.SlackClientID != "" && sc.SlackClientSecret != "",
+		Email:                   sc.SendgridAPIKey != "",
+		Analytics:               sc.SegmentClientKey != "",
+		Version:                 version,
+		Gitlab:                  sc.EnableGitlab,
+		DefaultAppHelmRepoURL:   sc.DefaultApplicationHelmRepoURL,
+		DefaultAddonHelmRepoURL: sc.DefaultAddonHelmRepoURL,
 	}
 }
 

+ 1 - 0
api/types/project_integration.go

@@ -80,6 +80,7 @@ type CreateAWSRequest struct {
 	AWSClusterID       string `json:"aws_cluster_id"`
 	AWSAccessKeyID     string `json:"aws_access_key_id"`
 	AWSSecretAccessKey string `json:"aws_secret_access_key"`
+	AWSAssumeRoleArn   string `json:"aws_assume_role_arn"`
 }
 
 type CreateAWSResponse struct {

+ 2 - 2
dashboard/src/components/ProvisionerStatus.tsx

@@ -464,11 +464,11 @@ export const OperationDetails: React.FunctionComponent<OperationDetailsProps> =
 
     const wsConfig = {
       onopen: () => {
-        console.log(`connected to websocket:`, websocketID);
+        // console.log(`connected to websocket:`, websocketID);
       },
       onmessage: parseOperationWebsocketEvent,
       onclose: () => {
-        console.log(`closing websocket:`, websocketID);
+        // console.log(`closing websocket:`, websocketID);
       },
       onerror: (err: ErrorEvent) => {
         console.log(err);

+ 1 - 1
dashboard/src/components/events/useLastSeenPodStatus.ts

@@ -59,7 +59,7 @@ const useLastSeenPodStatus = ({
           name: podName,
         }
       );
-      //console.log(getPodStatus(res.data.status));
+      // console.log(getPodStatus(res.data.status));
 
       setCurrentStatus(getPodStatus(res.data.status));
     } catch (error) {

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

@@ -14,7 +14,7 @@ import ClusterDashboard from "./cluster-dashboard/ClusterDashboard";
 import Dashboard from "./dashboard/Dashboard";
 import WelcomeForm from "./WelcomeForm";
 import Integrations from "./integrations/Integrations";
-import Templates from "./launch/Launch";
+import LaunchWrapper from "./launch/LaunchWrapper";
 
 import Navbar from "./navbar/Navbar";
 import ProjectSettings from "./project-settings/ProjectSettings";
@@ -503,7 +503,7 @@ class Home extends Component<PropsType, StateType> {
               path={"/project-settings"}
               render={() => <GuardedProjectSettings />}
             />
-            <Route path={"*"} render={() => <Templates />} />
+            <Route path={"*"} render={() => <LaunchWrapper />} />
           </Switch>
         </ViewWrapper>
 

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

@@ -111,9 +111,19 @@ class ClusterDashboard extends Component<PropsType, StateType> {
   componentDidUpdate(prevProps: PropsType) {
     // Reset namespace filter and close expanded chart on cluster change
     if (prevProps.currentCluster !== this.props.currentCluster) {
+      let namespace = "default";
+      if (
+        localStorage.getItem(
+          `${this.context.currentProject.id}-${this.context.currentCluster.id}-namespace`
+        )
+      ) {
+        namespace = localStorage.getItem(
+          `${this.context.currentProject.id}-${this.context.currentCluster.id}-namespace`
+        );
+      }
       this.setState(
         {
-          namespace: "default",
+          namespace,
           sortType: localStorage.getItem("SortType")
             ? localStorage.getItem("SortType")
             : "Newest",
@@ -151,7 +161,6 @@ class ClusterDashboard extends Component<PropsType, StateType> {
         <NamespaceSelector
           setNamespace={(namespace) =>
             this.setState({ namespace }, () => {
-              console.log(window.location, namespace);
               pushQueryParams(this.props, {
                 namespace: this.state.namespace || "ALL",
               });

+ 30 - 2
dashboard/src/main/home/cluster-dashboard/NamespaceSelector.tsx

@@ -30,7 +30,11 @@ export const NamespaceSelector: React.FunctionComponent<Props> = ({
       value: string;
     }[]
   >([]);
-  const [defaultNamespace, setDefaultNamespace] = useState<string>("default");
+  const [defaultNamespace, setDefaultNamespace] = useState<string>(
+    localStorage.getItem(
+      `${context.currentProject.id}-${context.currentCluster.id}-namespace`
+    )
+  );
 
   const updateOptions = () => {
     let { currentCluster, currentProject } = context;
@@ -61,7 +65,19 @@ export const NamespaceSelector: React.FunctionComponent<Props> = ({
           const availableNamespaces = res.data.filter((namespace: any) => {
             return namespace.status !== "Terminating";
           });
-          setDefaultNamespace("default");
+          if (
+            localStorage.getItem(
+              `${context.currentProject.id}-${context.currentCluster.id}-namespace`
+            )
+          ) {
+            setDefaultNamespace(
+              localStorage.getItem(
+                `${context.currentProject.id}-${context.currentCluster.id}-namespace`
+              )
+            );
+          } else {
+            setDefaultNamespace("default");
+          }
           availableNamespaces.forEach((x: { name: string }, i: number) => {
             namespaceOptions.push({
               label: x.name,
@@ -99,7 +115,19 @@ export const NamespaceSelector: React.FunctionComponent<Props> = ({
     updateOptions();
   }, [namespace, context.currentCluster]);
 
+  useEffect(() => {
+    setNamespace(
+      localStorage.getItem(
+        `${context.currentProject.id}-${context.currentCluster.id}-namespace`
+      )
+    );
+  }, [context.currentCluster]);
+
   const handleSetActive = (namespace: any) => {
+    localStorage.setItem(
+      `${context.currentProject.id}-${context.currentCluster.id}-namespace`,
+      namespace
+    );
     setNamespace(namespace);
   };
 

+ 0 - 3
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx

@@ -227,7 +227,6 @@ const ExpandedChart: React.FC<Props> = (props) => {
   const onSubmit = async (props: any) => {
     const rawValues = props.values;
 
-    // console.log("raw", rawValues);
     // Convert dotted keys to nested objects
     let values: any = {};
 
@@ -308,7 +307,6 @@ const ExpandedChart: React.FC<Props> = (props) => {
 
     setSaveValueStatus("loading");
 
-    // console.log("valuesYaml", valuesYaml);
     try {
       await api.upgradeChartValues(
         "<token>",
@@ -415,7 +413,6 @@ const ExpandedChart: React.FC<Props> = (props) => {
   const renderTabContents = (currentTab: string) => {
     let { setSidebar } = props;
     let chart = currentChart;
-    // console.log("CONTROLLERS", controllers);
     switch (currentTab) {
       case "logs":
         return (

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

@@ -100,7 +100,7 @@ export default class ValuesYaml extends Component<PropsType, StateType> {
             value={this.state.values}
             onChange={(e: any) => this.setState({ values: e })}
             readOnly={this.props.disabled}
-            height="calc(100vh - 462px)"
+            height="calc(100vh - 412px)"
           />
         </Wrapper>
         {!this.props.disabled && (
@@ -120,7 +120,6 @@ ValuesYaml.contextType = Context;
 
 const Wrapper = styled.div`
   overflow: auto;
-  height: calc(100% - 60px);
   border-radius: 8px;
   border: 1px solid #ffffff33;
 `;
@@ -129,8 +128,7 @@ const StyledValuesYaml = styled.div`
   display: flex;
   flex-direction: column;
   width: 100%;
-  min-height: 400px;
-  height: calc(100vh - 400px);
+  height: calc(100vh - 350px);
   font-size: 13px;
   overflow: hidden;
   border-radius: 8px;

+ 1 - 7
dashboard/src/main/home/cluster-dashboard/expanded-chart/status/Logs.tsx

@@ -195,13 +195,7 @@ const LogsFC: React.FC<{
             Show previous logs
           </Scroll>
         )}
-        <Refresh
-          onClick={() => {
-            // this.refreshLogs();
-            // console.log("Refresh logs");
-            refresh();
-          }}
-        >
+        <Refresh onClick={() => refresh()}>
           <i className="material-icons">autorenew</i>
           Refresh
         </Refresh>

+ 14 - 0
dashboard/src/main/home/infrastructure/components/credentials/AWSCredentialForm.tsx

@@ -45,6 +45,7 @@ const AWSCredentialForm: React.FunctionComponent<Props> = ({
   const { currentProject, setCurrentError } = useContext(Context);
   const [accessId, setAccessId] = useState("");
   const [secretKey, setSecretKey] = useState("");
+  const [assumeRoleArn, setAssumeRoleArn] = useState("");
   const [buttonStatus, setButtonStatus] = useState("");
   const [awsRegion, setAWSRegion] = useState("us-east-1");
   const [isLoading, setIsLoading] = useState(false);
@@ -60,6 +61,7 @@ const AWSCredentialForm: React.FunctionComponent<Props> = ({
           aws_region: awsRegion,
           aws_access_key_id: accessId,
           aws_secret_access_key: secretKey,
+          aws_assume_role_arn: assumeRoleArn,
         },
         {
           id: currentProject.id,
@@ -124,6 +126,17 @@ const AWSCredentialForm: React.FunctionComponent<Props> = ({
         }}
         label="📍 AWS Region"
       />
+      <InputRow
+        type="text"
+        value={assumeRoleArn}
+        setValue={(x: string) => {
+          setAssumeRoleArn(x);
+        }}
+        label="👤 (Optional) AWS Assume Role ARN"
+        placeholder="ex: arn:aws:iam::01234567890:role/my_assumed_role"
+        width="100%"
+        isRequired={false}
+      />
       <Flex>
         <SaveButton
           text="Continue"
@@ -145,6 +158,7 @@ const Flex = styled.div`
   display: flex;
   color: #ffffff;
   align-items: center;
+  margin-top: 30px;
   > i {
     color: #aaaabb;
     font-size: 20px;

+ 13 - 0
dashboard/src/main/home/launch/Boilerplate.tsx

@@ -0,0 +1,13 @@
+import React, { useState } from "react";
+
+import styled from "styled-components";
+
+type Props = {};
+
+export const Boilerplate: React.FC<Props> = (props) => {
+  const [someState, setSomeState] = useState("");
+
+  return <StyledBoilerplate></StyledBoilerplate>;
+};
+
+const StyledBoilerplate = styled.div``;

+ 6 - 4
dashboard/src/main/home/launch/Launch.tsx

@@ -59,11 +59,15 @@ class Templates extends Component<PropsType, StateType> {
   };
 
   async componentDidMount() {
+    let default_addon_helm_repo_url = this.context?.capabilities
+      ?.default_addon_helm_repo_url;
+    let default_app_helm_repo_url = this.context?.capabilities
+      ?.default_app_helm_repo_url;
     try {
       const res = await api.getTemplates(
         "<token>",
         {
-          repo_url: process.env.ADDON_CHART_REPO_URL,
+          repo_url: default_addon_helm_repo_url,
         },
         {}
       );
@@ -90,7 +94,7 @@ class Templates extends Component<PropsType, StateType> {
       const res = await api.getTemplates(
         "<token>",
         {
-          repo_url: process.env.APPLICATION_CHART_REPO_URL,
+          repo_url: default_app_helm_repo_url,
         },
         {}
       );
@@ -117,8 +121,6 @@ class Templates extends Component<PropsType, StateType> {
         currentTemplate = sortedVersionData.find(
           (v: any) => v.name === template_name
         );
-
-        // console.log(currentTemplate);
         if (currentTemplate.versions.find((v: any) => v === version)) {
           currentTemplate.currentVersion = version;
         }

+ 16 - 0
dashboard/src/main/home/launch/LaunchWrapper.tsx

@@ -0,0 +1,16 @@
+import React, { useState, useContext } from "react";
+import { Context } from "shared/Context";
+
+import styled from "styled-components";
+import Launch from "./Launch";
+
+type Props = {};
+
+const LaunchWrapper: React.FC<Props> = (props) => {
+  const { capabilities } = useContext(Context);
+  return <>{capabilities && <Launch />}</>;
+};
+
+export default LaunchWrapper;
+
+const StyledLaunchWrapper = styled.div``;

+ 1 - 0
dashboard/src/shared/api.tsx

@@ -82,6 +82,7 @@ const createAWSIntegration = baseApi<
     aws_cluster_id?: string;
     aws_access_key_id: string;
     aws_secret_access_key: string;
+    aws_assume_role_arn?: string;
   },
   { id: number }
 >("POST", (pathParams) => {

+ 2 - 0
dashboard/src/shared/types.tsx

@@ -328,6 +328,8 @@ export interface CapabilityType {
   github: boolean;
   provisioner: boolean;
   version?: string;
+  default_app_helm_repo_url?: string;
+  default_addon_helm_repo_url?: string;
 }
 
 export type OverlayData = {

+ 2 - 0
ee/api/server/handlers/credentials/get_credentials.go

@@ -1,3 +1,4 @@
+//go:build ee
 // +build ee
 
 package credentials
@@ -120,6 +121,7 @@ func (c *CredentialsGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 			AWSClusterID:       awsInt.AWSClusterID,
 			AWSSecretAccessKey: awsInt.AWSSecretAccessKey,
 			AWSSessionToken:    awsInt.AWSSessionToken,
+			AWSAssumeRoleArn:   []byte(awsInt.AWSAssumeRoleArn),
 		}
 	}
 

+ 6 - 2
internal/models/integrations/aws.go

@@ -29,6 +29,9 @@ type AWSIntegration struct {
 	// The optional AWS region (required by some session configurations)
 	AWSRegion string `json:"aws_region"`
 
+	// The assumed role ARN to use for sessions
+	AWSAssumeRoleArn string
+
 	// ------------------------------------------------------------------
 	// All fields encrypted before storage.
 	// ------------------------------------------------------------------
@@ -141,8 +144,9 @@ func (a *AWSIntegration) GetBearerToken(
 	}
 
 	tok, err := generator.GetWithOptions(&token.GetTokenOptions{
-		Session:   sess,
-		ClusterID: validClusterId,
+		AssumeRoleARN: a.AWSAssumeRoleArn,
+		Session:       sess,
+		ClusterID:     validClusterId,
 	})
 
 	if err != nil {

+ 3 - 0
internal/repository/credentials/credentials.go

@@ -37,6 +37,9 @@ type AWSCredential struct {
 
 	// An optional region associated with this AWS credential
 	AWSRegion []byte `json:"aws_region"`
+
+	// An optional assume role ARN
+	AWSAssumeRoleArn []byte `json:"aws_assume_role_arn"`
 }
 
 type AzureCredential struct {

+ 2 - 0
provisioner/server/handlers/credentials/get_credentials_ee.go

@@ -1,3 +1,4 @@
+//go:build ee
 // +build ee
 
 package credentials
@@ -99,6 +100,7 @@ func (c *CredentialsGetHandler) ServeHTTP(w http.ResponseWriter, r *http.Request
 			AWSSecretAccessKey: awsInt.AWSSecretAccessKey,
 			AWSSessionToken:    awsInt.AWSSessionToken,
 			AWSRegion:          []byte(awsInt.AWSRegion),
+			AWSAssumeRoleArn:   []byte(awsInt.AWSAssumeRoleArn),
 		}
 	} else if ceToken.AzureCredentialID != 0 {
 		azInt, err := repo.AzureIntegration().ReadAzureIntegration(ceToken.ProjectID, ceToken.AzureCredentialID)