Selaa lähdekoodia

Merge branch 'master' of https://github.com/porter-dev/porter into simplified-view

Justin Rhee 3 vuotta sitten
vanhempi
sitoutus
ced0d2857f
52 muutettua tiedostoa jossa 296 lisäystä ja 89 poistoa
  1. 87 0
      .github/workflows/porter_production.yml
  2. 8 7
      api/server/handlers/registry/list_images.go
  3. 3 2
      api/server/handlers/v1/registry/list_images.go
  4. 4 1
      api/server/shared/config/env/envconfs.go
  5. 36 0
      api/server/shared/config/loader/loader.go
  6. 13 9
      dashboard/src/App.tsx
  7. 2 2
      dashboard/src/components/CloudFormationForm.tsx
  8. 1 1
      dashboard/src/components/ClusterProvisioningPlaceholder.tsx
  9. 1 1
      dashboard/src/components/MultiSelectFilter.tsx
  10. 1 1
      dashboard/src/components/OldPlaceholder.tsx
  11. 1 1
      dashboard/src/components/Placeholder.tsx
  12. 3 3
      dashboard/src/components/ProvisionerSettings.tsx
  13. 7 7
      dashboard/src/components/ProvisionerSettingsOld.tsx
  14. 1 1
      dashboard/src/components/RadioFilter.tsx
  15. 1 1
      dashboard/src/components/ResourceTab.tsx
  16. 1 1
      dashboard/src/components/Selector.tsx
  17. 1 0
      dashboard/src/components/TitleSection.tsx
  18. 3 2
      dashboard/src/components/form-components/InputRow.tsx
  19. 1 1
      dashboard/src/components/porter-form/PorterForm.tsx
  20. 1 1
      dashboard/src/components/porter/Text.tsx
  21. 1 1
      dashboard/src/main/home/Home.tsx
  22. 2 2
      dashboard/src/main/home/cluster-dashboard/chart/Chart.tsx
  23. 1 1
      dashboard/src/main/home/cluster-dashboard/dashboard/ClusterSettings.tsx
  24. 1 1
      dashboard/src/main/home/cluster-dashboard/dashboard/NamespaceList.tsx
  25. 1 1
      dashboard/src/main/home/cluster-dashboard/dashboard/NodeList.tsx
  26. 1 1
      dashboard/src/main/home/cluster-dashboard/env-groups/CreateEnvGroup.tsx
  27. 1 1
      dashboard/src/main/home/cluster-dashboard/env-groups/EnvGroup.tsx
  28. 1 1
      dashboard/src/main/home/cluster-dashboard/env-groups/ExpandedEnvGroup.tsx
  29. 1 1
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedChart.tsx
  30. 1 1
      dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedJobChart.tsx
  31. 1 1
      dashboard/src/main/home/cluster-dashboard/expanded-chart/RevisionSection.tsx
  32. 1 1
      dashboard/src/main/home/cluster-dashboard/expanded-chart/SettingsSection.tsx
  33. 1 1
      dashboard/src/main/home/cluster-dashboard/expanded-chart/jobs/JobResource.tsx
  34. 1 1
      dashboard/src/main/home/cluster-dashboard/expanded-chart/status/Logs.tsx
  35. 1 1
      dashboard/src/main/home/dashboard/ClusterList.tsx
  36. 1 1
      dashboard/src/main/home/dashboard/OldClusterList.tsx
  37. 1 3
      dashboard/src/main/home/integrations/IntegrationList.tsx
  38. 1 1
      dashboard/src/main/home/integrations/IntegrationRow.tsx
  39. 1 1
      dashboard/src/main/home/launch/TemplateList.tsx
  40. 1 1
      dashboard/src/main/home/launch/launch-flow/SettingsPage.tsx
  41. 2 2
      dashboard/src/main/home/launch/launch-flow/SourcePage.tsx
  42. 2 2
      dashboard/src/main/home/onboarding/steps/ProvisionResources/forms/StatusPage.tsx
  43. 1 1
      dashboard/src/main/home/provisioner/ProvisionerSettings.tsx
  44. 2 1
      dashboard/src/main/home/sidebar/ClusterSection.tsx
  45. 1 1
      dashboard/src/main/home/sidebar/Clusters.tsx
  46. 2 1
      dashboard/src/main/home/sidebar/ProjectSection.tsx
  47. 4 3
      dashboard/src/main/home/sidebar/Sidebar.tsx
  48. 12 0
      dashboard/src/shared/themes/midnight.ts
  49. 12 0
      dashboard/src/shared/themes/standard.ts
  50. 39 12
      internal/registry/registry.go
  51. 14 1
      internal/repository/gorm/aws_assume_role_chain.go
  52. 8 0
      zarf/docker/Dockerfile.production

+ 87 - 0
.github/workflows/porter_production.yml

@@ -0,0 +1,87 @@
+"on":
+  push:
+    branches:
+      - master
+name: Deploy to Porter
+jobs:
+  build-go:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v3
+      - name: Setup Go Cache
+        uses: actions/cache@v3
+        with:
+          path: |
+            ~/.cache/go-build
+            ~/go/pkg/mod
+          key: porter-go-${{ hashFiles('**/go.sum') }}
+          restore-keys: porter-go-`
+      - name: Setup Go
+        uses: actions/setup-go@v4
+        with:
+          go-version-file: go.mod
+          cache: false
+      - name: Download Go Modules
+        run: go mod download
+      - name: Build Server Binary
+        run: go build -ldflags="-w -s -X 'main.Version=production'" -o ./bin/app ./cmd/app
+      - name: Build Migration Binary
+        run: go build -ldflags '-w -s' -o ./bin/migrate ./cmd/migrate
+      - name: Store Binaries
+        uses: actions/upload-artifact@v3
+        with:
+          name: go-binaries
+          path: bin/
+  build-npm:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v3
+      - name: Setup Node
+        uses: actions/setup-node@v3
+        with:
+          node-version: 16
+      - name: Install NPM Dependencies
+        run: |
+          cd dashboard
+          npm i --legacy-peer-deps
+      - name: Run NPM Build
+        run: |
+          cd dashboard
+          npm run build
+      - name: Store NPM Static Files
+        uses: actions/upload-artifact@v3
+        with:
+          name: npm-static-files
+          path: dashboard/build/
+  porter-deploy:
+    runs-on: ubuntu-latest
+    needs: [build-go, build-npm]
+    steps:
+      - name: Checkout code
+        uses: actions/checkout@v3
+      - name: Get Go Binaries
+        uses: actions/download-artifact@v3
+        with:
+          name: go-binaries
+          path: bin/
+      - name: Get NPM static files
+        uses: actions/download-artifact@v3
+        with:
+          name: npm-static-files
+          path: build/
+      - name: Set Github tag
+        id: vars
+        run: echo "sha_short=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
+      - name: Update Porter App
+        timeout-minutes: 20
+        uses: porter-dev/porter-update-action@v0.1.0
+        with:
+          app: production
+          cluster: "8"
+          host: https://dashboard.internal-tools.getporter.dev
+          namespace: default
+          project: "1"
+          tag: ${{ steps.vars.outputs.sha_short }}
+          token: ${{ secrets.PORTER_TOKEN_1 }}

+ 8 - 7
api/server/handlers/registry/list_images.go

@@ -29,7 +29,8 @@ func NewRegistryListImagesHandler(
 }
 
 func (c *RegistryListImagesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	reg, _ := r.Context().Value(types.RegistryScope).(*models.Registry)
+	ctx := r.Context()
+	reg, _ := ctx.Value(types.RegistryScope).(*models.Registry)
 
 	repoName, _ := requestutils.GetURLParamString(r, types.URLParamWildcard)
 
@@ -37,12 +38,12 @@ func (c *RegistryListImagesHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 	_reg := registry.Registry(*reg)
 	regAPI := &_reg
 
-	imgs, err := regAPI.ListImages(repoName, c.Repo(), c.Config().DOConf)
-
-	if err != nil && strings.Contains(err.Error(), "RepositoryNotFoundException") {
-		c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("no such repository: %s", repoName)))
-		return
-	} else if err != nil {
+	imgs, err := regAPI.ListImages(ctx, repoName, c.Repo(), c.Config())
+	if err != nil {
+		if strings.Contains(err.Error(), "RepositoryNotFoundException") {
+			c.HandleAPIError(w, r, apierrors.NewErrNotFound(fmt.Errorf("no such repository: %s", repoName)))
+			return
+		}
 		c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 		return
 	}

+ 3 - 2
api/server/handlers/v1/registry/list_images.go

@@ -29,7 +29,8 @@ func NewRegistryListImagesHandler(
 }
 
 func (c *RegistryListImagesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
-	reg, _ := r.Context().Value(types.RegistryScope).(*models.Registry)
+	ctx := r.Context()
+	reg, _ := ctx.Value(types.RegistryScope).(*models.Registry)
 
 	repoName, reqErr := requestutils.GetURLParamString(r, types.URLParamWildcard)
 
@@ -79,7 +80,7 @@ func (c *RegistryListImagesHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
 
 		res.Images = append(res.Images, imgs...)
 	} else {
-		imgs, err := regAPI.ListImages(repoName, c.Repo(), c.Config().DOConf)
+		imgs, err := regAPI.ListImages(ctx, repoName, c.Repo(), c.Config())
 		if err != nil {
 			c.HandleAPIError(w, r, apierrors.NewErrInternal(err))
 			return

+ 4 - 1
api/server/shared/config/env/envconfs.go

@@ -46,7 +46,10 @@ type ServerConf struct {
 	GithubAppWebhookSecret string `env:"GITHUB_APP_WEBHOOK_SECRET"`
 	GithubAppID            string `env:"GITHUB_APP_ID"`
 	GithubAppSecretPath    string `env:"GITHUB_APP_SECRET_PATH"`
-	GithubAppSecret        []byte
+	// GithubAppSecretBase64 is a base64 encoded version of the GithubAppSecret. This can be used instead of GithubAppSecretPath to pass in a key, allowing for support in systems where mounting the secret is not possible.
+	// If GithubAppSecretBase64 is set, it will check for a file at GithubAppSecretPath. If a file is found, the file will NOT be overwritten. If no file it found, then GithubAppSecretBase64 will be decoded and written to GithubAppSecretPath.
+	GithubAppSecretBase64 string `env:"GITHUB_APP_SECRET_BASE64"`
+	GithubAppSecret       []byte
 
 	GoogleClientID         string `env:"GOOGLE_CLIENT_ID"`
 	GoogleClientSecret     string `env:"GOOGLE_CLIENT_SECRET"`

+ 36 - 0
api/server/shared/config/loader/loader.go

@@ -1,10 +1,13 @@
 package loader
 
 import (
+	"encoding/base64"
 	"errors"
 	"fmt"
 	"io/ioutil"
 	"net/http"
+	"os"
+	"path/filepath"
 	"strconv"
 
 	gorillaws "github.com/gorilla/websocket"
@@ -153,6 +156,7 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 		})
 	}
 
+	// TODO: remove this as part of POR-1055
 	if sc.GithubClientID != "" && sc.GithubClientSecret != "" {
 		res.GithubConf = oauth.NewGithubClient(&oauth.Config{
 			ClientID:     sc.GithubClientID,
@@ -162,6 +166,30 @@ func (e *EnvConfigLoader) LoadConfig() (res *config.Config, err error) {
 		})
 	}
 
+	if sc.GithubAppSecretBase64 != "" {
+		if sc.GithubAppSecretPath == "" {
+			sc.GithubAppSecretPath = "github-app-secret-key"
+		}
+		_, err := os.Stat(sc.GithubAppSecretPath)
+		if err != nil {
+			if !errors.Is(err, os.ErrNotExist) {
+				return nil, fmt.Errorf("GITHUB_APP_SECRET_BASE64 provided, but error checking if GITHUB_APP_SECRET_PATH exists: %w", err)
+			}
+			secret, err := base64.StdEncoding.DecodeString(sc.GithubAppSecretBase64)
+			if err != nil {
+				return nil, fmt.Errorf("GITHUB_APP_SECRET_BASE64 provided, but error decoding: %w", err)
+			}
+			_, err = createDirectoryRecursively(sc.GithubAppSecretPath)
+			if err != nil {
+				return nil, fmt.Errorf("GITHUB_APP_SECRET_BASE64 provided, but error creating directory for GITHUB_APP_SECRET_PATH: %w", err)
+			}
+			err = os.WriteFile(sc.GithubAppSecretPath, secret, os.ModePerm)
+			if err != nil {
+				return nil, fmt.Errorf("GITHUB_APP_SECRET_BASE64 provided, but error writing to GITHUB_APP_SECRET_PATH: %w", err)
+			}
+		}
+	}
+
 	if sc.GithubAppClientID != "" &&
 		sc.GithubAppClientSecret != "" &&
 		sc.GithubAppName != "" &&
@@ -286,3 +314,11 @@ func getProvisionerServiceClient(sc *env.ServerConf) (*client.Client, error) {
 
 	return nil, fmt.Errorf("required env vars not set for provisioner")
 }
+
+// createDirectoryRecursively creates a directory and all its parents if they don't exist
+func createDirectoryRecursively(p string) (*os.File, error) {
+	if err := os.MkdirAll(filepath.Dir(p), 0o770); err != nil {
+		return nil, err
+	}
+	return os.Create(p)
+}

+ 13 - 9
dashboard/src/App.tsx

@@ -1,10 +1,12 @@
 import React, { Component } from "react";
 import { BrowserRouter } from "react-router-dom";
 import PorterErrorBoundary from "shared/error_handling/PorterErrorBoundary";
-import styled, { createGlobalStyle } from "styled-components";
+import styled, { ThemeProvider, createGlobalStyle } from "styled-components";
 import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
 
 import MainWrapper from "./main/MainWrapper";
+import midnight from "shared/themes/midnight";
+import standard from "shared/themes/standard";
 
 const queryClient = new QueryClient();
 
@@ -12,14 +14,16 @@ export default class App extends Component {
   render() {
     return (
       <QueryClientProvider client={queryClient}>
-        <StyledMain>
-          <GlobalStyle />
-          <PorterErrorBoundary errorBoundaryLocation="globalErrorBoundary">
-            <BrowserRouter>
-              <MainWrapper />
-            </BrowserRouter>
-          </PorterErrorBoundary>
-        </StyledMain>
+        <ThemeProvider theme={standard}>
+          <StyledMain>
+            <GlobalStyle />
+            <PorterErrorBoundary errorBoundaryLocation="globalErrorBoundary">
+              <BrowserRouter>
+                <MainWrapper />
+              </BrowserRouter>
+            </PorterErrorBoundary>
+          </StyledMain>
+        </ThemeProvider>
       </QueryClientProvider>
     );
   }

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

@@ -47,7 +47,7 @@ const CloudFormationForm: React.FC<Props> = ({
 
   const checkIfRoleExists = async () => {
     let externalId = getExternalId();
-    let targetARN = `arn:aws:iam::${AWSAccountID}:role/porter-role`
+    let targetARN = `arn:aws:iam::${AWSAccountID}:role/porter-manager`
 
     setRoleStatus("loading");
     setErrorMessage(undefined)
@@ -141,7 +141,7 @@ const CloudFormationForm: React.FC<Props> = ({
           }}
           status={
             errorMessage ? (
-              <Error 
+              <Error
                 message={errorMessage}
                 ctaText="Troubleshooting steps"
                 errorModalContents={

+ 1 - 1
dashboard/src/components/ClusterProvisioningPlaceholder.tsx

@@ -61,7 +61,7 @@ const Img = styled.img`
 const ClusterPlaceholder = styled.div`
   padding: 25px;
   border-radius: 5px;
-  background: #26292e;
+  background: ${props => props.theme.fg};
   border: 1px solid #494b4f;
   padding-bottom: 35px;
 `;

+ 1 - 1
dashboard/src/components/MultiSelectFilter.tsx

@@ -189,7 +189,7 @@ const StyledMultiSelectFilter = styled.div`
   font-size: 13px;
   position: relative;
   padding: 10px;
-  background: #26292e;
+  background: ${props => props.theme.fg};
   border-radius: 5px;
   border: 1px solid #aaaabb33;
   display: flex;

+ 1 - 1
dashboard/src/components/OldPlaceholder.tsx

@@ -30,5 +30,5 @@ const StyledPlaceholder = styled.div<{
   font-size: 13px;
   color: #ffffff44;
   border-radius: 5px;
-  background: #ffffff11;
+  background: ${props => props.theme.fg};
 `;

+ 1 - 1
dashboard/src/components/Placeholder.tsx

@@ -60,7 +60,7 @@ const StyledPlaceholder = styled.div<{
   justify-content: center;
   font-size: 13px;
   border-radius: 5px;
-  background: #26292e;
+  background: ${props => props.theme.fg};
   border: 1px solid #494b4f;
   padding-bottom: 60px;
 

+ 3 - 3
dashboard/src/components/ProvisionerSettings.tsx

@@ -75,7 +75,7 @@ const ProvisionerSettings: React.FC<Props> = props => {
   const [isExpanded, setIsExpanded] = useState(false);
   const [minInstances, setMinInstances] = useState(1);
   const [maxInstances, setMaxInstances] = useState(10);
-  const [cidrRange, setCidrRange] = useState("172.0.0.0/16");
+  const [cidrRange, setCidrRange] = useState("10.78.0.0/16");
   const [clusterVersion, setClusterVersion] = useState("v1.24.0");
   const [isReadOnly, setIsReadOnly] = useState(false);
   const [errorMessage, setErrorMessage] = useState<string>(undefined);
@@ -106,7 +106,7 @@ const ProvisionerSettings: React.FC<Props> = props => {
           value: new EKS({
             clusterName,
             clusterVersion: clusterVersion || "v1.24.0",
-            cidrRange: cidrRange || "172.0.0.0/16",
+            cidrRange: cidrRange || "10.78.0.0/16",
             region: awsRegion,
             nodeGroups: [
               new EKSNodeGroup({
@@ -313,7 +313,7 @@ const ProvisionerSettings: React.FC<Props> = props => {
                 value={cidrRange}
                 setValue={(x: string) => setCidrRange(x)}
                 label="VPC CIDR range"
-                placeholder="ex: 172.0.0.0/16"
+                placeholder="ex: 10.78.0.0/16"
               />
             </>
           )

+ 7 - 7
dashboard/src/components/ProvisionerSettingsOld.tsx

@@ -74,15 +74,15 @@ const ProvisionerSettingsOld: React.FC<Props> = props => {
   const [isExpanded, setIsExpanded] = useState(false);
   const [minInstances, setMinInstances] = useState(1);
   const [maxInstances, setMaxInstances] = useState(10);
-  const [cidrRange, setCidrRange] = useState("172.0.0.0/16");
+  const [cidrRange, setCidrRange] = useState("10.78.0.0/16");
   const [clusterVersion, setClusterVersion] = useState("v1.24.0");
   const [isReadOnly, setIsReadOnly] = useState(false);
 
   const markProvisioningStarted = async () => {
     try {
       const res = await api.updateOnboardingStep(
-        "<token>", 
-        { step: "provisioning-started" }, 
+        "<token>",
+        { step: "provisioning-started" },
         {}
       );
     } catch (err) {
@@ -92,7 +92,7 @@ const ProvisionerSettingsOld: React.FC<Props> = props => {
 
   const createCluster = async () => {
     markProvisioningStarted();
-    
+
     var data = new Contract({
       cluster: new Cluster({
         projectId: currentProject.id,
@@ -104,7 +104,7 @@ const ProvisionerSettingsOld: React.FC<Props> = props => {
           value: new EKS({
             clusterName,
             clusterVersion: clusterVersion || "v1.24.0",
-            cidrRange: cidrRange || "172.0.0.0/16",
+            cidrRange: cidrRange || "10.78.0.0/16",
             region: awsRegion,
             nodeGroups: [
               new EKSNodeGroup({
@@ -203,7 +203,7 @@ const ProvisionerSettingsOld: React.FC<Props> = props => {
   }, [props.selectedClusterVersion]);
 
   const renderForm = () => {
-    
+
     // Render simplified form if initial create
     if (!props.clusterId) {
       return (
@@ -294,7 +294,7 @@ const ProvisionerSettingsOld: React.FC<Props> = props => {
                 value={cidrRange}
                 setValue={(x: string) => setCidrRange(x)}
                 label="VPC CIDR range"
-                placeholder="ex: 172.0.0.0/16"
+                placeholder="ex: 10.78.0.0/16"
               />
             </>
           )

+ 1 - 1
dashboard/src/components/RadioFilter.tsx

@@ -207,7 +207,7 @@ const StyledRadioFilter = styled.div<{ noMargin?: boolean }>`
   font-size: 13px;
   position: relative;
   padding: 10px;
-  background: #26292e;
+  background: ${props => props.theme.fg};
   border-radius: 5px;
   display: flex;
   align-items: center;

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

@@ -143,7 +143,7 @@ const StyledResourceTab = styled.div`
   width: 100%;
   margin-bottom: 2px;
   font-size: 13px;
-  background: #ffffff11;
+  background: ${props => props.theme.fg};
   border-bottom-left-radius: ${(props: {
     isLast: boolean;
     roundAllCorners: boolean;

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

@@ -346,7 +346,7 @@ const MainSelector = styled.div<{
   justify-content: space-between;
   align-items: center;
   cursor: ${props => props.disabled ? "not-allowed" : "pointer"};
-  background: ${props => props.expanded ? "#ffffff33" : "#ffffff11"};
+  background: ${props => props.expanded ? "#ffffff33" : props.theme.fg};
   :hover {
     background: ${props => props.expanded ? "#ffffff33" : (
       props.disabled ? "#ffffff11" : "#ffffff22"

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

@@ -91,6 +91,7 @@ const StyledTitle = styled.div<{
 }>`
   font-size: 21px;
   user-select: text;
+  color: ${props => props.theme.text.primary};
   text-transform: ${(props) => (props.capitalize ? "capitalize" : "")};
   display: flex;
   align-items: center;

+ 3 - 2
dashboard/src/components/form-components/InputRow.tsx

@@ -104,10 +104,11 @@ const InputWrapper = styled.div`
   display: flex;
   margin-bottom: -1px;
   align-items: center;
+  overflow: hidden;
   border: 1px solid
     ${(props: { width: string; hasError: boolean }) =>
       props.hasError ? "red" : "#ffffff55"};
-  border-radius: 3px;
+  border-radius: 5px;
   ${(props: { width: string; hasError: boolean }) => {
     if (props.width) {
       return `width:${props.width};`;
@@ -119,7 +120,7 @@ const Input = styled.input<{ disabled: boolean; width: string }>`
   outline: none;
   border: none;
   font-size: 13px;
-  background: #ffffff11;
+  background: ${props => props.theme.fg};
   cursor: ${(props) => (props.disabled ? "not-allowed" : "")};
   width: ${(props) => (props.width ? props.width : "100%")};
   color: ${(props) => (props.disabled ? "#ffffff44" : "white")};

+ 1 - 1
dashboard/src/components/porter-form/PorterForm.tsx

@@ -268,6 +268,6 @@ const StyledPorterForm = styled.div<{ showSave?: boolean }>`
   margin-bottom: 5px;
   font-size: 13px;
   border-radius: 5px;
-  background: #26292e;
+  background: ${props => props.theme.fg};
   border: 1px solid #494b4f;
 `;

+ 1 - 1
dashboard/src/components/porter/Text.tsx

@@ -43,7 +43,7 @@ const StyledText = styled.div<{
 }>`
   line-height: 1.5;
   font-weight: ${props => props.weight || 400};
-  color: ${props => props.color || "#ffffff"};
+  color: ${props => props.color || props.theme.text.primary};
   font-size: ${props => props.size || 13}px;
   display: flex;
   align-items: center;

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

@@ -492,7 +492,7 @@ const ViewWrapper = styled.div`
   flex: 1;
   overflow-y: auto;
   justify-content: center;
-  background: #202227;
+  background: ${props => props.theme.bg};
   position: relative;
 `;
 

+ 2 - 2
dashboard/src/main/home/cluster-dashboard/chart/Chart.tsx

@@ -352,7 +352,7 @@ const Title = styled.div`
   padding: 12px 35px 12px 45px;
   font-size: 14px;
   font-family: "Work Sans", sans-serif;
-  color: #ffffff;
+  color: ${props => props.theme.text.primary};
   width: 80%;
   overflow: hidden;
   white-space: nowrap;
@@ -393,7 +393,7 @@ const StyledChart = styled.div`
   width: calc(100% + 2px);
   height: calc(100% + 2px);
   border-radius: 5px;
-  background: linear-gradient(180deg, #26292e, #24272c);
+  background: ${props => props.theme.clickable.bg};
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/dashboard/ClusterSettings.tsx

@@ -339,7 +339,7 @@ const StyledSettingsSection = styled.div`
   overflow: auto;
   height: 100%;
   border-radius: 5px;
-  background: #26292e;
+  background: ${props => props.theme.fg};
   border: 1px solid #494b4f;
 `;
 

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/dashboard/NamespaceList.tsx

@@ -300,7 +300,7 @@ const StyledCard = styled.div`
     }
   }
   border-radius: 5px;
-  background: #26292e;
+  background: ${props => props.theme.fg};
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/dashboard/NodeList.tsx

@@ -156,7 +156,7 @@ const StyledChart = styled.div`
     margin-bottom: 25px;
   }
   border-radius: 5px;
-  background: #26292e;
+  background: ${props => props.theme.fg};
   border: 1px solid #494b4f;
 `;
 

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/env-groups/CreateEnvGroup.tsx

@@ -234,7 +234,7 @@ const Wrapper = styled.div`
   padding-bottom: 25px;
   border-radius: 5px;
   margin-top: -15px;
-  background: #26292e;
+  background: ${props => props.theme.fg};
   border: 1px solid #494b4f;
   margin-bottom: 30px;
 `;

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/env-groups/EnvGroup.tsx

@@ -201,7 +201,7 @@ const StyledEnvGroup = styled.div`
   width: calc(100% + 2px);
   height: calc(100% + 2px);
   border-radius: 5px;
-  background: #26292e;
+  background: ${props => props.theme.clickable.bg};
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/env-groups/ExpandedEnvGroup.tsx

@@ -913,7 +913,7 @@ const InnerWrapper = styled.div<{ full?: boolean }>`
   overflow: auto;
   margin-bottom: 30px;
   border-radius: 5px;
-  background: #26292e;
+  background: ${props => props.theme.fg};
   border: 1px solid #494b4f;
 `;
 

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

@@ -1157,7 +1157,7 @@ const TabButton = styled.div`
   position: absolute;
   right: 0px;
   height: 30px;
-  background: linear-gradient(to right, #20222700, #202227 20%);
+  background: linear-gradient(to right, #00000000, ${props => props.theme.bg} 20%);
   padding-left: 30px;
   display: flex;
   align-items: center;

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/expanded-chart/ExpandedJobChart.tsx

@@ -752,7 +752,7 @@ const TabButton = styled.div`
   position: absolute;
   right: 0px;
   height: 30px;
-  background: linear-gradient(to right, #20222700, #202227 20%);
+  background: linear-gradient(to right, #00000000, ${props => props.theme.bg} 20%);
   padding-left: 30px;
   display: flex;
   align-items: center;

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

@@ -497,7 +497,7 @@ const StyledRevisionSection = styled.div`
   margin: 20px 0px 18px;
   overflow: hidden;
   border-radius: 5px;
-  background: #26292e;
+  background: ${props => props.theme.fg};
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/expanded-chart/SettingsSection.tsx

@@ -482,7 +482,7 @@ const StyledSettingsSection = styled.div`
   overflow: auto;
   height: calc(100% - 55px);
   border-radius: 5px;
-  background: #26292e;
+  background: ${props => props.theme.fg};
   border: 1px solid #494b4f;
 `;
 

+ 1 - 1
dashboard/src/main/home/cluster-dashboard/expanded-chart/jobs/JobResource.tsx

@@ -279,7 +279,7 @@ const StyledJob = styled.div`
   margin-bottom: 20px;
   overflow: hidden;
   border-radius: 5px;
-  background: #26292e;
+  background: ${props => props.theme.clickable.bg};
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

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

@@ -318,7 +318,7 @@ const LogStream = styled.div`
   float: right;
   height: 100%;
   font-size: 13px;
-  background: #121318;
+  background: #000000;
   user-select: text;
   max-width: 65%;
   overflow-y: auto;

+ 1 - 1
dashboard/src/main/home/dashboard/ClusterList.tsx

@@ -219,7 +219,7 @@ const ClusterRow = styled.div`
   color: #ffffff;
   position: relative;
   border-radius: 5px;
-  background: #26292e;
+  background: ${props => props.theme.fg};
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

+ 1 - 1
dashboard/src/main/home/dashboard/OldClusterList.tsx

@@ -241,7 +241,7 @@ const TemplateBlock = styled.div`
   color: #ffffff;
   position: relative;
   border-radius: 5px;
-  background: #26292e;
+  background: ${props => props.theme.clickable.bg};
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

+ 1 - 3
dashboard/src/main/home/integrations/IntegrationList.tsx

@@ -204,8 +204,6 @@ const MainRow = styled.div`
   padding: 15px;
   border-radius: 5px;
   :hover {
-    background: ${(props: { disabled: boolean }) =>
-      props.disabled ? "" : "#ffffff11"};
     > i {
       background: ${(props: { disabled: boolean }) =>
         props.disabled ? "" : "#ffffff11"};
@@ -233,7 +231,7 @@ const Integration = styled.div`
     props.disabled ? "not-allowed" : "pointer"};
   margin-bottom: 20px;
   border-radius: 5px;
-  background: linear-gradient(180deg, #26292e, #24272c);
+  background: ${props => props.theme.clickable.bg};
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

+ 1 - 1
dashboard/src/main/home/integrations/IntegrationRow.tsx

@@ -133,7 +133,7 @@ const Integration = styled.div`
     props.disabled ? "not-allowed" : "pointer"};
   margin-bottom: 15px;
   border-radius: 5px;
-  background: linear-gradient(180deg, #26292e, #24272c);
+  background: ${props => props.theme.clickable.bg};
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

+ 1 - 1
dashboard/src/main/home/launch/TemplateList.tsx

@@ -224,7 +224,7 @@ const TemplateBlock = styled.div`
   color: #ffffff;
   position: relative;
   border-radius: 5px;
-  background: linear-gradient(160deg, #26292e, #1e2023);
+  background: ${props => props.theme.clickable.bg};
   border: 1px solid #494b4f;
   :hover {
     border: 1px solid #7a7b80;

+ 1 - 1
dashboard/src/main/home/launch/launch-flow/SettingsPage.tsx

@@ -346,7 +346,7 @@ const BackButton = styled.div`
   border-radius: 100px;
   width: ${(props: { width: string }) => props.width};
   color: white;
-  background: #ffffff11;
+  background: ${props => props.theme.fg};
 
   :hover {
     background: #ffffff22;

+ 2 - 2
dashboard/src/main/home/launch/launch-flow/SourcePage.tsx

@@ -425,7 +425,7 @@ const Block = styled.div<{ disabled?: boolean }>`
   position: relative;
 
   border-radius: 5px;
-  background: #26292e;
+  background: ${props => props.theme.clickable.bg};
   border: 1px solid #494b4f;
   :hover {
   }
@@ -491,6 +491,6 @@ const StyledSourceBox = styled.div`
   margin-top: 6px;
   margin-bottom: 25px;
   border-radius: 5px;
-  background: #26292e;
+  background: ${props => props.theme.fg};
   border: 1px solid #494b4f;
 `;

+ 2 - 2
dashboard/src/main/home/onboarding/steps/ProvisionResources/forms/StatusPage.tsx

@@ -189,18 +189,18 @@ export const StatusPage = ({
 
 const Placeholder = styled.div`
   padding: 30px;
-  margin-top: 35px;
   padding-bottom: 40px;
   font-size: 13px;
   color: #ffffff44;
   min-height: 400px;
   height: 50vh;
-  background: #ffffff11;
+  background: ${props => props.theme.fg};
   border-radius: 8px;
   width: 100%;
   display: flex;
   align-items: center;
   justify-content: center;
+  border: 1px solid #494b4f;
 
   > i {
     font-size: 18px;

+ 1 - 1
dashboard/src/main/home/provisioner/ProvisionerSettings.tsx

@@ -401,7 +401,7 @@ const Block = styled.div<{ disabled?: boolean }>`
   color: #ffffff;
   position: relative;
   border-radius: 5px;
-  background: linear-gradient(180deg, #26292e, #24272c);
+  background: ${props => props.theme.clickable.bg};
   border: 1px solid #494b4f;
   :hover {
     border: ${(props) => (props.disabled ? "" : "1px solid #7a7b80")};

+ 2 - 1
dashboard/src/main/home/sidebar/ClusterSection.tsx

@@ -308,7 +308,7 @@ const NavButton = styled(SidebarLink)`
   padding: 0 30px 2px 8px;
   font-size: 13px;
   font-family: "Work Sans", sans-serif;
-  color: #ffffff;
+  color: ${props => props.theme.text.primary};
   cursor: ${(props: { disabled?: boolean }) =>
     props.disabled ? "not-allowed" : "pointer"};
 
@@ -333,6 +333,7 @@ const Img = styled.img<{ enlarge?: boolean }>`
   padding-top: 4px;
   border-radius: 3px;
   margin-right: 8px;
+  opacity: 85%;
 `;
 
 const ClusterName = styled.div`

+ 1 - 1
dashboard/src/main/home/sidebar/Clusters.tsx

@@ -317,7 +317,7 @@ const NavButton = styled(SidebarLink)`
   padding: 0 30px 2px 8px;
   font-size: 13px;
   font-family: "Work Sans", sans-serif;
-  color: #ffffff;
+  color: ${props => props.theme.text.primary};
   cursor: ${(props: { disabled?: boolean }) =>
     props.disabled ? "not-allowed" : "pointer"};
 

+ 2 - 1
dashboard/src/main/home/sidebar/ProjectSection.tsx

@@ -157,7 +157,7 @@ const InitializeButton = styled.div`
   font-size: 13px;
   font-weight: 500;
   border-radius: 3px;
-  color: #ffffff;
+  color: ${props => props.theme.text.primary};
   padding-bottom: 1px;
   cursor: pointer;
   background: #ffffff11;
@@ -255,6 +255,7 @@ const ProjectIconAlt = styled(ProjectIcon)`
 const StyledProjectSection = styled.div`
   position: relative;
   margin-left: 3px;
+  color: ${props => props.theme.text.primary};
 `;
 
 const MainSelector = styled.div`

+ 4 - 3
dashboard/src/main/home/sidebar/Sidebar.tsx

@@ -241,7 +241,7 @@ const NavButton = styled(SidebarLink)`
   padding: 0 30px 2px 6px;
   font-size: 13px;
   font-family: "Work Sans", sans-serif;
-  color: #ffffff;
+  color: ${props => props.theme.text.primary};
   cursor: ${(props: { disabled?: boolean }) =>
     props.disabled ? "not-allowed" : "pointer"};
 
@@ -272,6 +272,7 @@ const Img = styled.img<{ enlarge?: boolean }>`
   padding-top: 4px;
   border-radius: 3px;
   margin-right: 8px;
+  opacity: 0.8;
 `;
 
 const SidebarBg = styled.div`
@@ -279,14 +280,14 @@ const SidebarBg = styled.div`
   top: 0;
   left: 0;
   width: 100%;
-  background-color: #202227;
+  background-color: ${props => props.theme.bg};
   height: 100%;
   z-index: -1;
   border-right: 1px solid #383a3f;
 `;
 
 const SidebarLabel = styled.div`
-  color: #ffffff99;
+  color: ${props => props.theme.text.primary};
   padding: 5px 23px;
   margin-bottom: 5px;
   font-size: 13px;

+ 12 - 0
dashboard/src/shared/themes/midnight.ts

@@ -0,0 +1,12 @@
+const theme = {
+  bg: "#121212",
+  fg: "#171B21",
+  clickable: {
+    bg: "linear-gradient(180deg, #171B21, #121212)",
+  },
+  text: {
+    primary: "#DFDFE1",
+  },
+}
+
+export default theme;

+ 12 - 0
dashboard/src/shared/themes/standard.ts

@@ -0,0 +1,12 @@
+const theme = {
+  bg: "#202227",
+  fg: "#27292e",
+  clickable: {
+    bg: "#27292e",
+  },
+  text: {
+    primary: "#ffffff",
+  },
+}
+
+export default theme;

+ 39 - 12
internal/registry/registry.go

@@ -883,13 +883,21 @@ func (r *Registry) createGARRepository(
 
 // ListImages lists the images for an image repository
 func (r *Registry) ListImages(
+	ctx context.Context,
 	repoName string,
 	repo repository.Repository,
-	doAuth *oauth2.Config, // only required if using DOCR
+	conf *config.Config,
 ) ([]*ptypes.Image, error) {
 	// switch on the auth mechanism to get a token
 	if r.AWSIntegrationID != 0 {
-		return r.listECRImages(repoName, repo)
+		aws, err := repo.AWSIntegration().ReadAWSIntegration(
+			r.ProjectID,
+			r.AWSIntegrationID,
+		)
+		if err != nil {
+			return nil, err
+		}
+		return r.listECRImages(aws, repoName, repo)
 	}
 
 	if r.AzureIntegrationID != 0 {
@@ -905,13 +913,40 @@ func (r *Registry) ListImages(
 	}
 
 	if r.DOIntegrationID != 0 {
-		return r.listDOCRImages(repoName, repo, doAuth)
+		return r.listDOCRImages(repoName, repo, conf.DOConf)
 	}
 
 	if r.BasicIntegrationID != 0 {
 		return r.listPrivateRegistryImages(repoName, repo)
 	}
 
+	project, err := conf.Repo.Project().ReadProject(r.ProjectID)
+	if err != nil {
+		return nil, fmt.Errorf("error getting project for repository: %w", err)
+	}
+
+	if project.CapiProvisionerEnabled {
+		uri := strings.TrimPrefix(r.URL, "https://")
+		splits := strings.Split(uri, ".")
+		accountID := splits[0]
+		region := splits[3]
+		req := connect.NewRequest(&porterv1.AssumeRoleCredentialsRequest{
+			ProjectId:    int64(r.ProjectID),
+			AwsAccountId: accountID,
+		})
+		creds, err := conf.ClusterControlPlaneClient.AssumeRoleCredentials(ctx, req)
+		if err != nil {
+			return nil, fmt.Errorf("error getting capi credentials for repository: %w", err)
+		}
+		aws := &ints.AWSIntegration{
+			AWSAccessKeyID:     []byte(creds.Msg.AwsAccessId),
+			AWSSecretAccessKey: []byte(creds.Msg.AwsSecretKey),
+			AWSSessionToken:    []byte(creds.Msg.AwsSessionToken),
+			AWSRegion:          region,
+		}
+		return r.listECRImages(aws, repoName, repo)
+	}
+
 	return nil, fmt.Errorf("error listing images")
 }
 
@@ -1025,15 +1060,7 @@ func (r *Registry) GetECRPaginatedImages(
 	return res, resp.NextToken, nil
 }
 
-func (r *Registry) listECRImages(repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
-	aws, err := repo.AWSIntegration().ReadAWSIntegration(
-		r.ProjectID,
-		r.AWSIntegrationID,
-	)
-	if err != nil {
-		return nil, err
-	}
-
+func (r *Registry) listECRImages(aws *ints.AWSIntegration, repoName string, repo repository.Repository) ([]*ptypes.Image, error) {
 	sess, err := aws.GetSession()
 	if err != nil {
 		return nil, err

+ 14 - 1
internal/repository/gorm/aws_assume_role_chain.go

@@ -3,6 +3,7 @@ package gorm
 import (
 	"context"
 	"errors"
+	"fmt"
 
 	"github.com/porter-dev/porter/internal/models"
 	"github.com/porter-dev/porter/internal/repository"
@@ -27,7 +28,19 @@ func (cr AWSAssumeRoleChain) List(ctx context.Context, projectID uint) ([]*model
 	if projectID == 0 {
 		return nil, errors.New("must provide a project ID")
 	}
-	tx := cr.db.Where("project_id = ? and target_arn not like '%arn:aws:iam::108458755588%'", projectID).Find(&confs)
+	// porterInternalAccounts are accounts which should be hidden from users, such as bastion or production accounts
+	porterInternalAccounts := []string{
+		"108458755588", // CAPI Bastion
+		"813111008191", // Internal Tooling Cluster
+		"975032674314", // Old production account
+	}
+
+	query := "project_id = ?"
+	for _, account := range porterInternalAccounts {
+		query += fmt.Sprintf(" and target_arn not like '%%arn:aws:iam::%s%%'", account)
+	}
+
+	tx := cr.db.Where(query, projectID).Find(&confs)
 	if tx.Error != nil {
 		return nil, tx.Error
 	}

+ 8 - 0
zarf/docker/Dockerfile.production

@@ -0,0 +1,8 @@
+# All build artifacts are built in github actions, and copied in
+# Ubuntu is used to match Github action, and use github action caching instead of docker caching
+FROM ubuntu as runner
+WORKDIR /porter
+COPY bin/app /porter/
+COPY bin/migrate /porter/
+COPY build/ /porter/static
+RUN chmod +x /porter/app /porter/migrate