Преглед изворни кода

Merge pull request #603 from porter-dev/master

Merge ECR repositories fix to staging
abelanger5 пре 5 година
родитељ
комит
6055242dd6

+ 1 - 1
.github/workflows/release.yaml

@@ -84,7 +84,7 @@ jobs:
         run: |
         run: |
           go build -ldflags="-w -s -X 'github.com/porter-dev/porter/cli/cmd.Version=${{steps.tag_name.outputs.tag}}'" -a -tags cli -o ./porter ./cli &
           go build -ldflags="-w -s -X 'github.com/porter-dev/porter/cli/cmd.Version=${{steps.tag_name.outputs.tag}}'" -a -tags cli -o ./porter ./cli &
           go build -ldflags="-w -s -X 'main.Version=${{steps.tag_name.outputs.tag}}'" -a -o ./docker-credential-porter ./cmd/docker-credential-porter/ &
           go build -ldflags="-w -s -X 'main.Version=${{steps.tag_name.outputs.tag}}'" -a -o ./docker-credential-porter ./cmd/docker-credential-porter/ &
-          go build -ldflags="-w -s" -a -o ./portersvr ./cmd/app/ &
+          go build -ldflags="-w -s -X 'main.Version=${{steps.tag_name.outputs.tag}}'" -a -o ./portersvr ./cmd/app/ &
           wait
           wait
         env:
         env:
           GOOS: linux
           GOOS: linux

+ 109 - 0
CONTRIBUTING.md

@@ -0,0 +1,109 @@
+# Contributing to Porter
+
+First off, thanks for considering contributing to Porter. There are many types of contributions you can make, including bug reports and fixes, improving documentation, writing tutorials, and larger feature requests or changes.
+
+Before you contribute, make sure to read these guidelines thoroughly, so that you can get your pull request reviewed and finalized as quickly as possible. 
+
+- [Reporting Issues](#reporting-issues)
+- [Development Process Overview](#development-process-overview)
+  * [Good first issues and bug fixes](#good-first-issues-and-bug-fixes)
+  * [Improving Documentation and Writing Tutorials](#improving-documentation-and-writing-tutorials)
+  * [Features](#features)
+- [Writing Code](#writing-code)
+  * [Navigating the Codebase](#navigating-the-codebase)
+  * [Getting started](#getting-started)
+  * [Testing](#testing)
+- [Making the PR](#making-the-pr)
+
+> **Note:** we're still working on our contributing process, as we're a young project. If you'd like to suggest or discuss changes to this document or the process in general, we're very open to suggestions and would appreciate if you reach out on Discord or at [contact@getporter.dev](mailto:contact@getporter.dev)! To suggest additions to this document, feel free to raise an issue or make a PR with the changes. 
+
+## Reporting Issues
+
+> **IMPORTANT:** If you've found a security issue, please email us directly at [contact@getporter.dev](mailto:contact@getporter.dev) instead of raising a public issue.
+
+Bug reports help make Porter better for everyone. To create a bug report, select the "Bug Report" template when you create a new issue. This template will provide you with a structure so we can best recreate the issue. Please search within our issues before raising a new one to make sure you're not raising a duplicate.
+
+## Development Process Overview 
+
+We officially build new releases every other Friday, but we merge new features and fixes to our hosted version as soon as those features are ready. If the PR can get reviewed and merged before the next release, we will add it to our public roadmap for that upcoming release (https://github.com/porter-dev/porter/projects), which gets announced to the community every other Friday.
+
+### Good first issues and bug fixes
+
+> **Note:** if you're a first-time contributor, we recommend that you [follow this tutorial](http://makeapullrequest.com/) to learn how to start contributing. 
+
+If you want to start getting familiar with Porter's codebase, we do our best to tag issues with [`good-first-issue`](https://github.com/porter-dev/porter/labels/good%20first%20issue) if the issue is very limited in scope or only requires changes to a few localized files. If you'd like to be assigned an issue, feel free to reach out on Discord or over email, or you can simply comment on an issue you'd like to work on. 
+
+### Improving Documentation and Writing Tutorials
+
+Documentation is hosted at [docs.getporter.dev](https://docs.getporter.dev). To update existing documentation, you can suggest changes directly from the docs website. To create new documentation or write a tutorial, you can add a document in the `/docs` folder and make a PR directly. 
+
+### Features
+
+If you'd like to suggest a feature, we have a **#suggestions** channel on Discord that we frequently check to see which new features to work on. If you'd like to suggest and also work on the feature, we ask that you first message one of us on Discord or send an email describing the feature -- some features may not be entirely feasible. We require that all features have a detailed spec written in a PR **before** work on that feature begins. We will then review that spec in the PR discussion until the spec is clear and accomplishes the end goal of the feature request. 
+
+## Writing Code 
+
+Our backend is written in Golang, and our frontend is written in Typescript (using React). The root of the project is a Go repository containing `go.mod` and `go.sum`, while the `/dashboard` folder contains the React app with `package.json`. Our templates/add-ons are hosted in other repositories and are written using Helm (more info on contributing to these repositories will be added soon). 
+
+### Navigating the Codebase
+
+Here's an annotated directory structure to assist you in navigating the codebase. This only lists the most important folders and packages: 
+
+```bash
+.
+├── cli              # CLI commands and runtime
+├── cmd              # Entrypoint packages (main.go files)
+│   └── app            # The primary entrypoint to running the server
+├── dashboard        # contains the frontend React app
+├── internal         # Internal Go packages
+│   ├── forms          # contains the web form specifications for POST requests
+│   ├── helm           # contains the logic for performing helm actions
+│   ├── kubernetes     # contains the logic for interacting with the kubernetes api
+│   ├── models         # contains the DB (and some other shared) models
+│   └── repository     # implements a repository pattern for DB CRUD operations using gorm
+├── scripts          # contains build scripts for releases (rarely modified)
+├── server           # contains routes, API handlers, and server middleware
+│   ├── api            # api handlers
+│   └── router         # routes and routing middleware
+└── services         # contains auxiliary stand-alone services that are run on Porter
+```
+
+### Getting started
+
+If you've made it this far, you have all the information required to get your dev environment up and running! After forking and cloning the repo, you should save two `.env` files in the repo. 
+
+First, in `/dashboard/.env`:
+
+```
+NODE_ENV=development
+API_SERVER=localhost:8080
+```
+
+Next, in `/docker/.env`:
+
+```
+SERVER_URL=http://localhost:8080
+SERVER_PORT=8080
+DB_HOST=postgres
+DB_PORT=5432
+DB_USER=porter
+DB_PASS=porter
+DB_NAME=porter
+SQL_LITE=false
+```
+
+Once you've done this, go to the root repository, and run `docker-compose -f docker-compose-dev.yaml up`. You should see postgres, webpack, and porter containers spin up. When the webpack and porter containers have finished compiling and have spun up successfully (this will take 5-10 minutes after the containers start), you can navigate to `localhost:8080` and you should be greeted with the "Log In" screen. 
+
+At this point, you can make a change to any `.go` file to trigger a backend rebuild, and any file in `/dashboard/src` to trigger a hot reload. Happy developing!
+
+### Testing 
+
+All backend changes made after [release 0.2.0](https://github.com/porter-dev/porter/projects/2) will require tests. Backend testing is done using Golang's [built in testing package](https://golang.org/pkg/testing/). Before pushing changes, run `go test ./...` in the root directory and make sure that your changes did not break any tests. While we don't require 100% code coverage for tests, we expect tests to cover all functionality and common edge cases/exceptions. If you're fixing a backend bug, add a test to ensure that bug doesn't come up again. 
+
+We do not currently have a process for frontend testing -- if building out a frontend testing process is something you'd like to work on, don't hesitate to reach out as this is something we'll introduce soon. 
+
+## Making the PR
+
+To ensure that your PR is merged before an upcoming release, it is easiest if you prefix the branch with the release version you'd like to be finished by (the upcoming two releases will always exist at https://github.com/porter-dev/porter/projects). If your pull request is related to an issue, please mention that issue in the branch name. So for example, if I'd like to close issue `200` and I'd like to merge the PR by release `0.3.0`, I would run `git checkout -b 0.3.0-200-pod-deletion`. 
+
+For now, request [**@abelanger5**](https://github.com/abelanger5) to review your PR. 

+ 9 - 4
README.md

@@ -1,6 +1,6 @@
 # Porter
 # Porter
 
 
-[![MIT License](https://img.shields.io/apm/l/atomic-design-ui.svg?)](https://github.com/tterb/atomic-design-ui/blob/master/LICENSEs) [![Go Report Card](https://goreportcard.com/badge/gojp/goreportcard)](https://goreportcard.com/report/github.com/porter-dev/porter) [![Discord](https://img.shields.io/discord/542888846271184896?color=7389D8&label=community&logo=discord&logoColor=ffffff)](https://discord.gg/34n7NN7FJ7)
+[![MIT License](https://img.shields.io/apm/l/atomic-design-ui.svg?)](https://github.com/tterb/atomic-design-ui/blob/master/LICENSEs) [![Go Report Card](https://goreportcard.com/badge/gojp/goreportcard)](https://goreportcard.com/report/github.com/porter-dev/porter) [![Discord](https://img.shields.io/discord/542888846271184896?color=7389D8&label=community&logo=discord&logoColor=ffffff)](https://discord.gg/mmGAw5nNjr)
 [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/cloudposse.svg?style=social&label=Follow)](https://twitter.com/getporterdev)
 [![Twitter](https://img.shields.io/twitter/url/https/twitter.com/cloudposse.svg?style=social&label=Follow)](https://twitter.com/getporterdev)
 
 
 **Porter is a Kubernetes-powered PaaS that runs in your own cloud provider.** Porter brings the Heroku experience to your own AWS/GCP account, while upgrading your infrastructure to Kubernetes. Get started on Porter without the overhead of DevOps and customize your infrastructure later when you need to.
 **Porter is a Kubernetes-powered PaaS that runs in your own cloud provider.** Porter brings the Heroku experience to your own AWS/GCP account, while upgrading your infrastructure to Kubernetes. Get started on Porter without the overhead of DevOps and customize your infrastructure later when you need to.
@@ -9,7 +9,7 @@
 
 
 ## Community and Updates
 ## Community and Updates
 
 
-For help, questions, or if you just want a place to hang out, [join our Discord community.](https://discord.gg/34n7NN7FJ7)
+For help, questions, or if you just want a place to hang out, [join our Discord community.](https://discord.gg/mmGAw5nNjr)
 
 
 To keep updated on our progress, please watch the repo for new releases (**Watch > Custom > Releases**) and [follow us on Twitter](https://twitter.com/getporterdev)!
 To keep updated on our progress, please watch the repo for new releases (**Watch > Custom > Releases**) and [follow us on Twitter](https://twitter.com/getporterdev)!
 
 
@@ -36,6 +36,7 @@ Porter brings the simplicity of a traditional PaaS to your own cloud provider wh
 - Heroku-like GUI to monitor application status, logs, and history
 - Heroku-like GUI to monitor application status, logs, and history
 - Application rollback to previously deployed versions
 - Application rollback to previously deployed versions
 - Zero-downtime deploy and health checks
 - Zero-downtime deploy and health checks
+- Monitor CPU, RAM, and Network usage per deployment
 - Marketplace for one click add-ons (e.g. MongoDB, Redis, PostgreSQL)
 - Marketplace for one click add-ons (e.g. MongoDB, Redis, PostgreSQL)
 
 
 ### DevOps Mode
 ### DevOps Mode
@@ -60,10 +61,14 @@ Below are instructions for a quickstart. For full documentation, please visit ou
 
 
 2. Create a Project and [put in your cloud provider credentials](https://docs.getporter.dev/docs/getting-started-with-porter-on-aws). Porter will automatically provision a Kubernetes cluster in your own cloud. It is also possible to [link up an existing Kubernetes cluster.](https://docs.getporter.dev/docs/cli-documentation#connecting-to-an-existing-cluster)
 2. Create a Project and [put in your cloud provider credentials](https://docs.getporter.dev/docs/getting-started-with-porter-on-aws). Porter will automatically provision a Kubernetes cluster in your own cloud. It is also possible to [link up an existing Kubernetes cluster.](https://docs.getporter.dev/docs/cli-documentation#connecting-to-an-existing-cluster)
 
 
-3. Deploy your applications from a [git repository](https://docs.getporter.dev/docs/applications) or [Docker image registry](https://docs.getporter.dev/docs/cli-documentation#porter-docker-configure).
+3. 🚀 Deploy your applications from a [git repository](https://docs.getporter.dev/docs/applications) or [Docker image registry](https://docs.getporter.dev/docs/cli-documentation#porter-docker-configure).
+
+## Running Porter Locally
+
+While it requires a few additional steps, it is possible to run Porter locally. Follow [this guide](https://docs.getporter.dev/docs/running-porter-locally) to run the local version of Porter.
 
 
 ## Want to Help?
 ## Want to Help?
 
 
-We welcome all contributions. Submit an issue or a pull request to help us improve Porter! If you're interested in contributing, please [join our Discord community](https://discord.gg/34n7NN7FJ7) for more info.
+We welcome all contributions. If you're interested in contributing, please read our [contributing guide](https://github.com/porter-dev/porter/blob/master/CONTRIBUTING.md) and [join our Discord community](https://discord.gg/GJynMR3KXK).
 
 
 ![porter](https://user-images.githubusercontent.com/65516095/103712859-def9ee00-4f88-11eb-804c-4b775d697ec4.jpeg)
 ![porter](https://user-images.githubusercontent.com/65516095/103712859-def9ee00-4f88-11eb-804c-4b775d697ec4.jpeg)

+ 8 - 0
cli/cmd/server.go

@@ -210,6 +210,14 @@ func startLocal(
 		"REDIS_ENABLED=false",
 		"REDIS_ENABLED=false",
 	}...)
 	}...)
 
 
+	if _, found := os.LookupEnv("GITHUB_ENABLED"); !found {
+		cmdPorter.Env = append(cmdPorter.Env, "GITHUB_ENABLED=false")
+	}
+
+	if _, found := os.LookupEnv("PROVISIONER_ENABLED"); !found {
+		cmdPorter.Env = append(cmdPorter.Env, "PROVISIONER_ENABLED=false")
+	}
+
 	cmdPorter.Stdout = os.Stdout
 	cmdPorter.Stdout = os.Stdout
 	cmdPorter.Stderr = os.Stderr
 	cmdPorter.Stderr = os.Stderr
 
 

+ 1 - 1
cli/cmd/version.go

@@ -7,7 +7,7 @@ import (
 )
 )
 
 
 // Version will be linked by an ldflag during build
 // Version will be linked by an ldflag during build
-var Version string = "v0.1.0-beta.3.4"
+var Version string = "0.2.0"
 
 
 var versionCmd = &cobra.Command{
 var versionCmd = &cobra.Command{
 	Use:     "version",
 	Use:     "version",

+ 1 - 0
cmd/app/main.go

@@ -107,6 +107,7 @@ func main() {
 		Repository: repo,
 		Repository: repo,
 		ServerConf: appConf.Server,
 		ServerConf: appConf.Server,
 		RedisConf:  &appConf.Redis,
 		RedisConf:  &appConf.Redis,
+		CapConf: 	appConf.Capabilities,
 		DBConf:     appConf.Db,
 		DBConf:     appConf.Db,
 	})
 	})
 
 

+ 10 - 0
dashboard/src/components/image-selector/TagList.tsx

@@ -8,6 +8,8 @@ import { Context } from "shared/Context";
 
 
 import Loading from "../Loading";
 import Loading from "../Loading";
 
 
+var ecrRepoRegex = /(^[a-zA-Z0-9][a-zA-Z0-9-_]*)\.dkr\.ecr(\-fips)?\.([a-zA-Z0-9][a-zA-Z0-9-_]*)\.amazonaws\.com(\.cn)?/igm
+
 type PropsType = {
 type PropsType = {
   setSelectedTag: (x: string) => void;
   setSelectedTag: (x: string) => void;
   selectedTag: string;
   selectedTag: string;
@@ -32,8 +34,16 @@ export default class TagList extends Component<PropsType, StateType> {
 
 
   componentDidMount() {
   componentDidMount() {
     const { currentProject } = this.context;
     const { currentProject } = this.context;
+
     let splits = this.props.selectedImageUrl.split("/");
     let splits = this.props.selectedImageUrl.split("/");
     let repoName = splits[splits.length - 1];
     let repoName = splits[splits.length - 1];
+
+    let matches = this.props.selectedImageUrl.match(ecrRepoRegex)
+
+    if (matches) {
+      repoName = this.props.selectedImageUrl.split(/\/(.+)/)[1]
+    }
+  
     api
     api
       .getImageTags(
       .getImageTags(
         "<token>",
         "<token>",

+ 10 - 1
dashboard/src/main/Main.tsx

@@ -22,6 +22,7 @@ type StateType = {
   isLoggedIn: boolean;
   isLoggedIn: boolean;
   isEmailVerified: boolean;
   isEmailVerified: boolean;
   initialized: boolean;
   initialized: boolean;
+  local: boolean;
 };
 };
 
 
 export default class Main extends Component<PropsType, StateType> {
 export default class Main extends Component<PropsType, StateType> {
@@ -30,6 +31,7 @@ export default class Main extends Component<PropsType, StateType> {
     isLoggedIn: false,
     isLoggedIn: false,
     isEmailVerified: false,
     isEmailVerified: false,
     initialized: localStorage.getItem("init") === "true",
     initialized: localStorage.getItem("init") === "true",
+    local: false,
   };
   };
 
 
   componentDidMount() {
   componentDidMount() {
@@ -53,6 +55,13 @@ export default class Main extends Component<PropsType, StateType> {
         }
         }
       })
       })
       .catch((err) => this.setState({ isLoggedIn: false, loading: false }));
       .catch((err) => this.setState({ isLoggedIn: false, loading: false }));
+    
+    api.getCapabilities("", {}, {})
+    .then((res) => {
+      console.log(res.data)
+      this.setState({local: !res.data?.provisioner})
+    })
+    .catch((err) => console.log(err));
   }
   }
 
 
   initialize = () => {
   initialize = () => {
@@ -100,7 +109,7 @@ export default class Main extends Component<PropsType, StateType> {
     }
     }
 
 
     // if logged in but not verified, block until email verification
     // if logged in but not verified, block until email verification
-    if (this.state.isLoggedIn && !this.state.isEmailVerified) {
+    if (!this.state.local && this.state.isLoggedIn && !this.state.isEmailVerified) {
       return (
       return (
         <Switch>
         <Switch>
           <Route
           <Route

+ 29 - 10
dashboard/src/main/auth/Login.tsx

@@ -16,6 +16,7 @@ type StateType = {
   password: string;
   password: string;
   emailError: boolean;
   emailError: boolean;
   credentialError: boolean;
   credentialError: boolean;
+  hasGithub: boolean;
 };
 };
 
 
 export default class Login extends Component<PropsType, StateType> {
 export default class Login extends Component<PropsType, StateType> {
@@ -24,6 +25,7 @@ export default class Login extends Component<PropsType, StateType> {
     password: "",
     password: "",
     emailError: false,
     emailError: false,
     credentialError: false,
     credentialError: false,
+    hasGithub: true,
   };
   };
 
 
   handleKeyDown = (e: any) => {
   handleKeyDown = (e: any) => {
@@ -36,6 +38,13 @@ export default class Login extends Component<PropsType, StateType> {
     emailFromCLI
     emailFromCLI
       ? this.setState({ email: emailFromCLI })
       ? this.setState({ email: emailFromCLI })
       : document.addEventListener("keydown", this.handleKeyDown);
       : document.addEventListener("keydown", this.handleKeyDown);
+
+    // get capabilities to case on github
+    api.getCapabilities("", {}, {})
+    .then((res) => {
+      this.setState({hasGithub: res.data?.github})
+    })
+    .catch((err) => console.log(err));
   }
   }
 
 
   componentWillUnmount() {
   componentWillUnmount() {
@@ -105,6 +114,25 @@ export default class Login extends Component<PropsType, StateType> {
     window.location.href = redirectUrl;
     window.location.href = redirectUrl;
   };
   };
 
 
+  renderGithubSection = () => {
+    if (this.state.hasGithub) {
+      return (
+        <>
+          <OAuthButton onClick={this.githubRedirect}>
+          <IconWrapper>
+            <Icon src={github} />
+            Log in with GitHub
+          </IconWrapper>
+          </OAuthButton>
+          <OrWrapper>
+            <Line />
+            <Or>or</Or>
+          </OrWrapper>
+        </>
+      )
+    }
+  }
+
   render() {
   render() {
     let { email, password, credentialError, emailError } = this.state;
     let { email, password, credentialError, emailError } = this.state;
 
 
@@ -117,16 +145,7 @@ export default class Login extends Component<PropsType, StateType> {
           <FormWrapper>
           <FormWrapper>
             <Logo src={logo} />
             <Logo src={logo} />
             <Prompt>Log in to Porter</Prompt>
             <Prompt>Log in to Porter</Prompt>
-            <OAuthButton onClick={this.githubRedirect}>
-              <IconWrapper>
-                <Icon src={github} />
-                Log in with GitHub
-              </IconWrapper>
-            </OAuthButton>
-            <OrWrapper>
-              <Line />
-              <Or>or</Or>
-            </OrWrapper>
+            {this.renderGithubSection()}
             <DarkMatter />
             <DarkMatter />
             <InputWrapper>
             <InputWrapper>
               <Input
               <Input

+ 30 - 10
dashboard/src/main/auth/Register.tsx

@@ -17,6 +17,7 @@ type StateType = {
   confirmPassword: string;
   confirmPassword: string;
   emailError: boolean;
   emailError: boolean;
   confirmPasswordError: boolean;
   confirmPasswordError: boolean;
+  hasGithub: boolean;
 };
 };
 
 
 export default class Register extends Component<PropsType, StateType> {
 export default class Register extends Component<PropsType, StateType> {
@@ -26,6 +27,7 @@ export default class Register extends Component<PropsType, StateType> {
     confirmPassword: "",
     confirmPassword: "",
     emailError: false,
     emailError: false,
     confirmPasswordError: false,
     confirmPasswordError: false,
+    hasGithub: true,
   };
   };
 
 
   handleKeyDown = (e: any) => {
   handleKeyDown = (e: any) => {
@@ -34,6 +36,13 @@ export default class Register extends Component<PropsType, StateType> {
 
 
   componentDidMount() {
   componentDidMount() {
     document.addEventListener("keydown", this.handleKeyDown);
     document.addEventListener("keydown", this.handleKeyDown);
+
+    // get capabilities to case on github
+    api.getCapabilities("", {}, {})
+    .then((res) => {      
+      this.setState({hasGithub: res.data?.github})
+    })
+    .catch((err) => console.log(err));
   }
   }
 
 
   componentWillUnmount() {
   componentWillUnmount() {
@@ -106,6 +115,26 @@ export default class Register extends Component<PropsType, StateType> {
     }
     }
   };
   };
 
 
+  renderGithubSection = () => {
+    if (this.state.hasGithub) {
+      return (
+        <>
+          <OAuthButton onClick={this.githubRedirect}>
+              <IconWrapper>
+                <Icon src={github} />
+                Sign up with GitHub
+              </IconWrapper>
+            </OAuthButton>
+            <OrWrapper>
+              <Line />
+              <Or>or</Or>
+            </OrWrapper>
+        </>
+      )
+    }
+  }
+
+
   render() {
   render() {
     let {
     let {
       email,
       email,
@@ -124,16 +153,7 @@ export default class Register extends Component<PropsType, StateType> {
           <FormWrapper>
           <FormWrapper>
             <Logo src={logo} />
             <Logo src={logo} />
             <Prompt>Sign up for Porter</Prompt>
             <Prompt>Sign up for Porter</Prompt>
-            <OAuthButton onClick={this.githubRedirect}>
-              <IconWrapper>
-                <Icon src={github} />
-                Sign up with GitHub
-              </IconWrapper>
-            </OAuthButton>
-            <OrWrapper>
-              <Line />
-              <Or>or</Or>
-            </OrWrapper>
+            {this.renderGithubSection()}
             <DarkMatter />
             <DarkMatter />
             <InputWrapper>
             <InputWrapper>
               <Input
               <Input

+ 21 - 0
dashboard/src/main/home/Home.tsx

@@ -82,6 +82,25 @@ class Home extends Component<PropsType, StateType> {
       });
       });
   };
   };
 
 
+  getCapabilities = () => {
+    let { currentProject } = this.props;
+    if (!currentProject) return;
+
+    api
+      .getCapabilities(
+        "<token>",
+        {},
+        {}
+      )
+      .then((res) => {
+        console.log(res.data)
+        this.context.setCapabilities(res.data)
+      })
+      .catch((err) => {
+        console.log(err)
+      });
+  }
+
   getProjects = (id?: number) => {
   getProjects = (id?: number) => {
     let { user, setProjects } = this.context;
     let { user, setProjects } = this.context;
     let { currentProject } = this.props;
     let { currentProject } = this.props;
@@ -222,6 +241,7 @@ class Home extends Component<PropsType, StateType> {
     this.setState({ ghRedirect: urlParams.get("gh_oauth") !== null });
     this.setState({ ghRedirect: urlParams.get("gh_oauth") !== null });
     urlParams.delete("gh_oauth");
     urlParams.delete("gh_oauth");
     this.getProjects(defaultProjectId);
     this.getProjects(defaultProjectId);
+    this.getCapabilities();
   }
   }
 
 
   // TODO: Need to handle the following cases. Do a deep rearchitecture (Prov -> Dashboard?) if need be:
   // TODO: Need to handle the following cases. Do a deep rearchitecture (Prov -> Dashboard?) if need be:
@@ -237,6 +257,7 @@ class Home extends Component<PropsType, StateType> {
         this.checkDO();
         this.checkDO();
       } else {
       } else {
         this.initializeView();
         this.initializeView();
+        this.getCapabilities();
       }
       }
     }
     }
   }
   }

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

@@ -82,7 +82,7 @@ class Dashboard extends Component<PropsType, StateType> {
             <i className="material-icons">info</i>
             <i className="material-icons">info</i>
             Create a cluster to link to this project.
             Create a cluster to link to this project.
           </Banner>
           </Banner>
-          <ProvisionerSettings infras={this.state.infras} />
+          <ProvisionerSettings infras={this.state.infras} provisioner={true} />
         </>
         </>
       );
       );
     } else {
     } else {
@@ -95,8 +95,21 @@ class Dashboard extends Component<PropsType, StateType> {
   };
   };
 
 
   render() {
   render() {
-    let { currentProject } = this.context;
+    let { currentProject, capabilities } = this.context;
     let { onShowProjectSettings } = this;
     let { onShowProjectSettings } = this;
+
+    let tabOptions = [
+      { label: "Project Overview", value: "overview" },
+      { label: "Create a Cluster", value: "create-cluster" },
+      { label: "Provisioner Status", value: "provisioner" },
+    ]
+    
+    if (!capabilities?.provisioner) {
+      tabOptions = [
+        { label: "Project Overview", value: "overview" },
+      ]
+    }
+
     return (
     return (
       <>
       <>
         {currentProject && (
         {currentProject && (

+ 4 - 2
dashboard/src/main/home/launch/expanded-template/LaunchTemplate.tsx

@@ -581,10 +581,12 @@ class LaunchTemplate extends Component<PropsType, StateType> {
 
 
   // Display if current template uses source (image or repo)
   // Display if current template uses source (image or repo)
   renderSourceSelectorContent = () => {
   renderSourceSelectorContent = () => {
+    let { capabilities } = this.context;
+
     if (this.state.sourceType === "") {
     if (this.state.sourceType === "") {
       return (
       return (
         <BlockList>
         <BlockList>
-          <Block
+          {capabilities.github && (<Block
             onClick={() => {
             onClick={() => {
               this.setState({ sourceType: "repo" });
               this.setState({ sourceType: "repo" });
             }}
             }}
@@ -594,7 +596,7 @@ class LaunchTemplate extends Component<PropsType, StateType> {
             <BlockDescription>
             <BlockDescription>
               Deploy using source from a Git repo.
               Deploy using source from a Git repo.
             </BlockDescription>
             </BlockDescription>
-          </Block>
+          </Block>)}
           <Block
           <Block
             onClick={() => {
             onClick={() => {
               this.setState({ sourceType: "registry" });
               this.setState({ sourceType: "registry" });

+ 9 - 1
dashboard/src/main/home/navbar/Navbar.tsx

@@ -40,10 +40,18 @@ export default class Navbar extends Component<PropsType, StateType> {
     }
     }
   };
   };
 
 
+  renderFeedbackButton = () => {
+    if (this.context?.capabilities?.provisioner) {
+      return (
+        <Feedback currentView={this.props.currentView} />
+      )
+    }
+  }
+
   render() {
   render() {
     return (
     return (
       <StyledNavbar>
       <StyledNavbar>
-        <Feedback currentView={this.props.currentView} />
+        {this.renderFeedbackButton()}
         <NavButton
         <NavButton
           selected={this.state.showDropdown}
           selected={this.state.showDropdown}
           onClick={() =>
           onClick={() =>

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

@@ -23,6 +23,7 @@ export default class NewProject extends Component<PropsType, StateType> {
   };
   };
 
 
   render() {
   render() {
+    let { capabilities } = this.context;
     let { projectName } = this.state;
     let { projectName } = this.state;
     return (
     return (
       <StyledNewProject>
       <StyledNewProject>
@@ -58,7 +59,7 @@ export default class NewProject extends Component<PropsType, StateType> {
             width="470px"
             width="470px"
           />
           />
         </InputWrapper>
         </InputWrapper>
-        <ProvisionerSettings isInNewProject={true} projectName={projectName} />
+        <ProvisionerSettings isInNewProject={true} projectName={projectName} provisioner={capabilities?.provisioner} />
         <Br />
         <Br />
       </StyledNewProject>
       </StyledNewProject>
     );
     );

+ 81 - 29
dashboard/src/main/home/provisioner/ProvisionerSettings.tsx

@@ -17,6 +17,7 @@ type PropsType = RouteComponentProps & {
   isInNewProject?: boolean;
   isInNewProject?: boolean;
   projectName?: string;
   projectName?: string;
   infras?: InfraType[];
   infras?: InfraType[];
+  provisioner?: boolean;
 };
 };
 
 
 type StateType = {
 type StateType = {
@@ -42,11 +43,20 @@ class NewProject extends Component<PropsType, StateType> {
     this.props.history.push("dashboard?tab=overview");
     this.props.history.push("dashboard?tab=overview");
   };
   };
 
 
-  renderSelectedProvider = () => {
+  renderSelectedProvider = (override?: string) => {
     let { selectedProvider } = this.state;
     let { selectedProvider } = this.state;
     let { projectName, infras } = this.props;
     let { projectName, infras } = this.props;
 
 
+    if (override) {
+      selectedProvider = override;
+    }
+
     let renderSkipHelper = () => {
     let renderSkipHelper = () => {
+
+      if (!this.props.provisioner) {
+        return;
+      }
+
       return (
       return (
         <>
         <>
           {selectedProvider === "skipped" ? (
           {selectedProvider === "skipped" ? (
@@ -125,19 +135,80 @@ class NewProject extends Component<PropsType, StateType> {
     }
     }
   };
   };
 
 
-  render() {
+  renderFooter = () => {
     let { selectedProvider } = this.state;
     let { selectedProvider } = this.state;
     let { isInNewProject } = this.props;
     let { isInNewProject } = this.props;
+    let { provisioner } = this.props;
+    let helper = provisioner ? "Note: Provisioning can take up to 15 minutes" : ""
+
+    if (isInNewProject && !selectedProvider) {
+      return (
+        <>
+          <Helper>
+            Already have a Kubernetes cluster?
+            <Highlight
+              onClick={() => this.setState({ selectedProvider: "skipped" })}
+            >
+              Skip
+            </Highlight>
+          </Helper>
+          <Br />
+          <SaveButton
+            text="Submit"
+            disabled={true}
+            onClick={() => {}}
+            makeFlush={true}
+            helper={helper}
+          />
+        </>
+      )
+    }
+  }
+
+  componentDidMount() {
+    let { provisioner } = this.props;
+
+    if (!provisioner) {
+      this.setState({selectedProvider: "skipped"})
+    }
+  }
+
+  componentDidUpdate(prevProps: PropsType) {
+    if (
+      prevProps.provisioner !== this.props.provisioner
+    ) {
+      if (!this.props.provisioner) {
+        this.setState({selectedProvider: "skipped"})
+      }
+    }
+  }
+
+  renderHelperText = () => {
+    let { isInNewProject, provisioner } = this.props;
+    if (!provisioner) {
+      return;
+    }
+
+    if (isInNewProject) {
+      return (
+        <>
+          Select your hosting backend:<Required>*</Required>
+        </>
+      )
+    } else {
+      return (
+        "Need a cluster? Provision through Porter:"
+      )
+    }
+  }
+
+  render() {
+    let { selectedProvider } = this.state;
+
     return (
     return (
       <StyledProvisionerSettings>
       <StyledProvisionerSettings>
         <Helper>
         <Helper>
-          {isInNewProject ? (
-            <>
-              Select your hosting backend:<Required>*</Required>
-            </>
-          ) : (
-            "Need a cluster? Provision through Porter:"
-          )}
+          {this.renderHelperText()}
         </Helper>
         </Helper>
         {!selectedProvider ? (
         {!selectedProvider ? (
           <BlockList>
           <BlockList>
@@ -160,26 +231,7 @@ class NewProject extends Component<PropsType, StateType> {
         ) : (
         ) : (
           <>{this.renderSelectedProvider()}</>
           <>{this.renderSelectedProvider()}</>
         )}
         )}
-        {isInNewProject && !selectedProvider && (
-          <>
-            <Helper>
-              Already have a Kubernetes cluster?
-              <Highlight
-                onClick={() => this.setState({ selectedProvider: "skipped" })}
-              >
-                Skip
-              </Highlight>
-            </Helper>
-            <Br />
-            <SaveButton
-              text="Submit"
-              disabled={true}
-              onClick={() => {}}
-              makeFlush={true}
-              helper="Note: Provisioning can take up to 15 minutes"
-            />
-          </>
-        )}
+        {this.renderFooter()}
       </StyledProvisionerSettings>
       </StyledProvisionerSettings>
     );
     );
   }
   }

+ 5 - 1
dashboard/src/shared/Context.tsx

@@ -1,6 +1,6 @@
 import React, { Component } from "react";
 import React, { Component } from "react";
 
 
-import { ProjectType, ClusterType } from "shared/types";
+import { ProjectType, ClusterType, CapabilityType } from "shared/types";
 
 
 const Context = React.createContext({});
 const Context = React.createContext({});
 
 
@@ -63,6 +63,10 @@ class ContextProvider extends Component {
     setDevOpsMode: (devOpsMode: boolean) => {
     setDevOpsMode: (devOpsMode: boolean) => {
       this.setState({ devOpsMode });
       this.setState({ devOpsMode });
     },
     },
+    capabilities: null as CapabilityType,
+    setCapabilities: (capabilities: CapabilityType) => {
+      this.setState({ capabilities })
+    },
     clearContext: () => {
     clearContext: () => {
       this.setState({
       this.setState({
         currentModal: null,
         currentModal: null,

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

@@ -629,6 +629,10 @@ const getUser = baseApi<{}, { id: number }>("GET", (pathParams) => {
   return `/api/users/${pathParams.id}`;
   return `/api/users/${pathParams.id}`;
 });
 });
 
 
+const getCapabilities = baseApi<{}, {}>("GET", () => {
+  return `/api/capabilities`;
+});
+
 const linkGithubProject = baseApi<
 const linkGithubProject = baseApi<
   {},
   {},
   {
   {
@@ -818,6 +822,7 @@ export default {
   destroyDOKS,
   destroyDOKS,
   getBranchContents,
   getBranchContents,
   getBranches,
   getBranches,
+  getCapabilities,
   getChart,
   getChart,
   getCharts,
   getCharts,
   getChartComponents,
   getChartComponents,

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

@@ -167,3 +167,8 @@ export interface ActionConfigType {
   image_repo_uri: string;
   image_repo_uri: string;
   git_repo_id: number;
   git_repo_id: number;
 }
 }
+
+export interface CapabilityType {
+  github: boolean;
+  provisioner: boolean;
+}

+ 0 - 32
docs/DEVELOPING.md

@@ -1,32 +0,0 @@
-### Development
-
-```sh
-docker-compose -f docker-compose.dev.yaml up --build
-```
-
-And then visit `localhost:8080` in the browser.
-
-### Testing
-
-From the root directory, run `go test ./...` to run all tests and ensure the builds/tests pass.
-
-### Building
-
-From the root directory, run `DOCKER_BUILDKIT=1 docker build . --file ./docker/Dockerfile -t porter`. Then you can run `docker run -p 8080:8080 porter`.
-
-To build the test container, run `DOCKER_BUILDKIT=1 docker build . --file ./docker/Dockerfile -t porter-test --target porter-test`.
-
-### CLI Release
-
-```sh
-docker run --rm --privileged \
--v $PWD:/go/src/github.com/porter-dev/porter \
--v /var/run/docker.sock:/var/run/docker.sock \
--w /go/src/github.com/porter-dev/porter \
--e GORELEASER_GITHUB_TOKEN='THEGITHUBTOKEN' \
-mailchain/goreleaser-xcgo ""
-```
-
-### Dashboard
-
-We use Prettier for all ts/tsx formatting. This will eventually be enforced rigorously.

+ 0 - 26
docs/GCR.md

@@ -1,26 +0,0 @@
-# Google Container Registry (GCR) Connection
-
-To authenticate a private GCR registry, you will first need a Google Cloud service account with registry viewing permissions. To create a new service account, go to your Google Cloud console and navigate to the **IAM & Admin** tab in the navigation menu and select **Service Accounts**:
-
-<img src="https://files.readme.io/a0c0c75-Screen_Shot_2020-06-24_at_2.51.46_PM.png" width="80%">
-
-Select **Create Service Account** and provide a name and brief description for the new service account. Next, choose the role **Viewer** when you are prompted to grant permissions to your service account:
-
-<img src="https://files.readme.io/aa8cda5-Screen_Shot_2020-06-24_at_4.03.33_PM.png" width="80%">
-
-After the service account has been created, you need to create a JSON key for your service account by going to **Actions** -> **Create key** and then selecting JSON as your key type. Once your JSON key file has downloaded, use the `porter connect gcr` command to add the registry to your project.
-
-For example, for a key named `gcp-key-file.json` on Mac:
-
-```diff
-$ cd ~/Downloads
-$ porter connect gcr
-Please provide the full path to a service account key file.
-Key file location: ./gcp-key-file.json
-+ created gcp integration with id 3
-Give this registry a name: gcr-registry
-+ created registry with id 16 and name gcr-test
-+ Set the current registry id as 16
-```
-
-Having issues authenticating your private registry? You can reach us at [contact@getporter.dev](mailto:contact@getporter.dev).

+ 6 - 0
internal/config/config.go

@@ -14,6 +14,7 @@ type Conf struct {
 	Db     DBConf
 	Db     DBConf
 	K8s    K8sConf
 	K8s    K8sConf
 	Redis  RedisConf
 	Redis  RedisConf
+	Capabilities CapConf
 }
 }
 
 
 // ServerConf is the server configuration
 // ServerConf is the server configuration
@@ -70,6 +71,11 @@ type K8sConf struct {
 	IsTesting bool `env:"K8S_IS_TESTING,default=false"`
 	IsTesting bool `env:"K8S_IS_TESTING,default=false"`
 }
 }
 
 
+type CapConf struct {
+	Provisioner bool `env:"PROVISIONER_ENABLED,default=true"`
+	Github bool `env:"GITHUB_ENABLED,default=true"`
+}
+
 // FromEnv generates a configuration from environment variables
 // FromEnv generates a configuration from environment variables
 func FromEnv() *Conf {
 func FromEnv() *Conf {
 	var c Conf
 	var c Conf

+ 0 - 15
scripts/release.sh

@@ -1,15 +0,0 @@
-#!/bin/bash
-
-# Step 0 -- ensure that:
-# (1) GITHUB_TOKEN exists as an env variable
-# (2) Apple ID password exists in keychain
-
-# Step 1 -- build for linux/windows inside a docker container
-docker run --rm --privileged \
--v $PWD:/go/src/github.com/porter-dev/porter \
--v /var/run/docker.sock:/var/run/docker.sock \
--w /go/src/github.com/porter-dev/porter \
-mailchain/goreleaser-xcgo "--rm-dist --skip-validate"
-
-# Step 2 -- build for MacOS using notarization tool
-goreleaser --rm-dist --config .darwin.goreleaser.yml --skip-validate

+ 5 - 0
server/api/api.go

@@ -42,6 +42,7 @@ type AppConfig struct {
 	ServerConf config.ServerConf
 	ServerConf config.ServerConf
 	RedisConf  *config.RedisConf
 	RedisConf  *config.RedisConf
 	DBConf     config.DBConf
 	DBConf     config.DBConf
+	CapConf config.CapConf
 
 
 	// TestAgents if API is in testing mode
 	// TestAgents if API is in testing mode
 	TestAgents *TestAgents
 	TestAgents *TestAgents
@@ -71,6 +72,9 @@ type App struct {
 	// config for db
 	// config for db
 	DBConf config.DBConf
 	DBConf config.DBConf
 
 
+	// config for capabilities
+	CapConf config.CapConf
+
 	// oauth-specific clients
 	// oauth-specific clients
 	GithubUserConf    *oauth2.Config
 	GithubUserConf    *oauth2.Config
 	GithubProjectConf *oauth2.Config
 	GithubProjectConf *oauth2.Config
@@ -102,6 +106,7 @@ func New(conf *AppConfig) (*App, error) {
 		ServerConf: conf.ServerConf,
 		ServerConf: conf.ServerConf,
 		RedisConf:  conf.RedisConf,
 		RedisConf:  conf.RedisConf,
 		DBConf:     conf.DBConf,
 		DBConf:     conf.DBConf,
+		CapConf: 	conf.CapConf,
 		TestAgents: conf.TestAgents,
 		TestAgents: conf.TestAgents,
 		db:         conf.DB,
 		db:         conf.DB,
 		validator:  validator,
 		validator:  validator,

+ 28 - 0
server/api/capability_handler.go

@@ -0,0 +1,28 @@
+package api
+
+import (
+	"encoding/json"
+	"net/http"
+)
+
+// CapabilitiesExternal represents the Capabilities struct that will be sent over REST
+type CapabilitiesExternal struct {
+	Provisioner bool `json:"provisioner"`
+	GitHub bool	`json:"github"`
+}
+
+// HandleGetCapabilities gets the capabilities of the server
+func (app *App) HandleGetCapabilities(w http.ResponseWriter, r *http.Request) {
+
+	cap := app.CapConf
+
+	capExternal := &CapabilitiesExternal{
+		Provisioner: cap.Provisioner,
+		GitHub: cap.Github,
+	}
+
+	if err := json.NewEncoder(w).Encode(capExternal); err != nil {
+		app.handleErrorFormDecoding(err, ErrK8sDecode, w)
+		return
+	}
+}

+ 7 - 0
server/router/router.go

@@ -1352,6 +1352,13 @@ func New(a *api.App) *chi.Mux {
 				mw.ReadAccess,
 				mw.ReadAccess,
 			),
 			),
 		)
 		)
+
+		// capabilities
+		r.Method(
+			"GET",
+			"/capabilities",
+			http.HandlerFunc(a.HandleGetCapabilities),
+		)
 	})
 	})
 
 
 	staticFilePath := a.ServerConf.StaticFilePath
 	staticFilePath := a.ServerConf.StaticFilePath

+ 0 - 11
staging.sh

@@ -1,11 +0,0 @@
-{
-export API_SERVER_CONTAINER=porter-server-657f5c594c-nvdd2; 
-export WEBPACK_SERVER_CONTAINER=porter-webpack-64d48578b5-vnk7x; 
-kubectl port-forward $API_SERVER_CONTAINER 8081:8080 & 
-kubectl port-forward $WEBPACK_SERVER_CONTAINER 8082:8080 & 
-devspace sync --upload-only --container-path /webpack --local-path ./dashboard/ --pod $WEBPACK_SERVER_CONTAINER & 
-devspace sync --upload-only --exclude dashboard --pod $API_SERVER_CONTAINER & 
-kubectl logs $API_SERVER_CONTAINER -f & 
-kubectl logs $WEBPACK_SERVER_CONTAINER -f & 
-nginx -c $(pwd)/docker/nginx_remote.conf;
-}