diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-deployer.ts b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-deployer.ts
index df2a5062c3..6de6b8a927 100644
--- a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-deployer.ts
+++ b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-deployer.ts
@@ -45,6 +45,7 @@ interface GitSCMSourceInfo extends DeploySource {
commit: string;
scm: string;
endpointGuid: string;
+ accessToken?: string;
}
// Structure used to provide metadata about the Git Url source
@@ -253,7 +254,8 @@ export class DeployApplicationDeployer {
commit: appSource.gitDetails.commit,
url: appSource.gitDetails.url,
scm: appSource.type.id,
- endpointGuid: appSource.gitDetails.endpointGuid
+ endpointGuid: appSource.gitDetails.endpointGuid,
+ accessToken: appSource.gitDetails.accessToken
};
const msg = {
diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.html b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.html
index 9879d583f3..de3eb1fbbd 100644
--- a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.html
+++ b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.html
@@ -11,10 +11,22 @@
+
+
+
+
+ GitHub Enterprise deployment url is not valid
+
+
+
+
+ [appGithubProjectExists]="sourceType.id + ',' + sourceType.endpointGuid + ',' + (accessToken || '')" required>
diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.ts b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.ts
index 6a3f9d958c..7c98222741 100644
--- a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.ts
+++ b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-step2/deploy-application-step2.component.ts
@@ -50,6 +50,8 @@ import { getCommitGuid } from '../../../../../../git/src/store/git-entity-factor
import { DeployApplicationState, SourceType } from '../../../../store/types/deploy-application.types';
import { ApplicationDeploySourceTypes, DEPLOY_TYPES_IDS } from '../deploy-application-steps.types';
import { GitSuggestedRepo } from './../../../../../../git/src/store/git.public-types';
+import { GitHubSCM } from 'frontend/packages/git/src/shared/scm/github-scm';
+import { BaseSCM } from 'frontend/packages/git/src/shared/scm/scm-base';
@@ -98,6 +100,11 @@ export class DeployApplicationStep2Component
// We don't have any repositories to suggest initially - need user to start typing
suggestedRepos$: Observable;
+ // GitHub Enterprise/private repos
+ isInvalidGithubEnterpriseUrl: boolean;
+ accessToken: string;
+ // --------------
+
// Git URL
gitUrl: string;
gitUrlBranchName: string;
@@ -141,6 +148,7 @@ export class DeployApplicationStep2Component
projectName: this.repository,
branch: this.repositoryBranch,
url: repo.entity.clone_url,
+ accessToken: this.accessToken,
commit: this.isRedeploy ? this.commitInfo.sha : undefined,
endpointGuid: this.sourceType.endpointGuid,
}, null));
@@ -345,6 +353,21 @@ export class DeployApplicationStep2Component
this.subscriptions.push(setProjectName.subscribe());
this.suggestedRepos$ = this.sourceSelectionForm.valueChanges.pipe(
+ tap(form => {
+ const isValidUrl = (input: string) => { try { var url = new URL(input); return Boolean(url) } catch (e) { return false } }
+
+ this.isInvalidGithubEnterpriseUrl = form.githubEnterpriseUrl && !isValidUrl(form.githubEnterpriseUrl)
+
+ if (form.githubEnterpriseUrl && !this.isInvalidGithubEnterpriseUrl) {
+ (this.scm as unknown as BaseSCM).setPublicApi(form.githubEnterpriseUrl)
+ }
+
+ if (form.githubAccessToken) {
+ (this.scm as GitHubSCM).setAccessToken(form.githubAccessToken)
+ } else {
+ (this.scm as GitHubSCM).clearAccessToken()
+ }
+ }),
map(form => form.projectName),
startWith(''),
pairwise(),
diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-steps.types.ts b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-steps.types.ts
index b92c91eb93..d00d4474ff 100644
--- a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-steps.types.ts
+++ b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/deploy-application-steps.types.ts
@@ -34,7 +34,7 @@ export class ApplicationDeploySourceTypes {
name: 'GitHub',
id: DEPLOY_TYPES_IDS.GITHUB,
group: 'gitscm',
- helpText: 'Please select the GitHub project and branch you would like to deploy from.',
+ helpText: 'Please select the GitHub project and branch you would like to deploy from. If the GitHub repository is private or located on a GitHub Enterprise deployment, include an access token (and the GitHub Enterprise deployment url).',
graphic: {
// TODO: Move cf assets to CF package (#3769)
location: '/core/assets/endpoint-icons/github-logo.png',
diff --git a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/github-project-exists.directive.ts b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/github-project-exists.directive.ts
index 805731dc45..9d1333674a 100644
--- a/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/github-project-exists.directive.ts
+++ b/src/frontend/packages/cloud-foundry/src/features/applications/deploy-application/github-project-exists.directive.ts
@@ -41,12 +41,12 @@ export class GithubProjectExistsDirective implements Validator {
return this.lastValue.length && this.lastValue.indexOf(name) === 0;
}
- private getTypeAndEndpoint(): [GitSCMType, string] {
+ private getTypeAndEndpointWithAuth(): [GitSCMType, string, string] {
const res = this.appGithubProjectExists.split(',');
- if (res.length === 2) {
- return [res[0] as GitSCMType, res[1]];
+ if (res.length === 3) {
+ return [res[0] as GitSCMType, res[1], res[2]];
}
- console.warn('appGithubProjectExists value should be `,');
+ console.warn('appGithubProjectExists value should be `,,`');
return null;
}
@@ -64,7 +64,7 @@ export class GithubProjectExistsDirective implements Validator {
debounceTime(250),
tap(createAppState => {
if (createAppState.projectExists && createAppState.projectExists.name !== c.value) {
- this.store.dispatch(new CheckProjectExists(this.scmService.getSCM(...this.getTypeAndEndpoint()), c.value));
+ this.store.dispatch(new CheckProjectExists(this.scmService.getSCM(...this.getTypeAndEndpointWithAuth()), c.value));
}
}),
filter(createAppState =>
diff --git a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/github-commits/github-commits-list-config-deploy.service.ts b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/github-commits/github-commits-list-config-deploy.service.ts
index 11b0179daf..e9ff310ecd 100644
--- a/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/github-commits/github-commits-list-config-deploy.service.ts
+++ b/src/frontend/packages/cloud-foundry/src/shared/components/list/list-types/github-commits/github-commits-list-config-deploy.service.ts
@@ -33,6 +33,7 @@ export class GithubCommitsListConfigServiceDeploy extends GithubCommitsListConfi
map((appSource: DeployApplicationSource) => {
return (appSource.type.id === 'github' || appSource.type.id === 'gitlab') ? {
scm: appSource.type.id as GitSCMType,
+ accessToken: appSource.gitDetails.accessToken,
projectName: appSource.gitDetails.projectName,
sha: appSource.gitDetails.branch.name,
endpointGuid: appSource.gitDetails.endpointGuid
@@ -41,7 +42,7 @@ export class GithubCommitsListConfigServiceDeploy extends GithubCommitsListConfi
filter(fetchDetails => !!fetchDetails && !!fetchDetails.projectName && !!fetchDetails.sha),
first()
).subscribe(fetchDetails => {
- const scm = scmService.getSCM(fetchDetails.scm, fetchDetails.endpointGuid);
+ const scm = scmService.getSCM(fetchDetails.scm, fetchDetails.endpointGuid, fetchDetails.accessToken);
this.dataSource = new GithubCommitsDataSource(this.store, this, scm, fetchDetails.projectName, fetchDetails.sha);
this.initialised.next(true);
diff --git a/src/frontend/packages/cloud-foundry/src/store/types/deploy-application.types.ts b/src/frontend/packages/cloud-foundry/src/store/types/deploy-application.types.ts
index 6b673f0bf0..d3cefce3ee 100644
--- a/src/frontend/packages/cloud-foundry/src/store/types/deploy-application.types.ts
+++ b/src/frontend/packages/cloud-foundry/src/store/types/deploy-application.types.ts
@@ -71,6 +71,7 @@ export interface GitAppDetails {
projectName: string;
branch: GitBranch;
endpointGuid: string;
+ accessToken?: string;
commit?: string;
branchName?: string;
url?: string;
diff --git a/src/frontend/packages/git/src/shared/scm/github-scm.ts b/src/frontend/packages/git/src/shared/scm/github-scm.ts
index babe37ae63..d3f28588e0 100644
--- a/src/frontend/packages/git/src/shared/scm/github-scm.ts
+++ b/src/frontend/packages/git/src/shared/scm/github-scm.ts
@@ -1,7 +1,7 @@
import { HttpClient } from '@angular/common/http';
import { flattenPagination } from '@stratosui/store';
import { Observable } from 'rxjs';
-import { map, switchMap } from 'rxjs/operators';
+import { map, tap, switchMap } from 'rxjs/operators';
import { GitBranch, GitCommit, GitRepo } from '../../store/git.public-types';
import { GitSuggestedRepo } from './../../store/git.public-types';
@@ -14,12 +14,29 @@ import {
import { GitSCM, SCMIcon } from './scm';
import { BaseSCM, GitApiRequest } from './scm-base';
import { GitSCMType } from './scm.service';
+import { HttpOptions } from 'frontend/packages/core/src/core/core.types';
export class GitHubSCM extends BaseSCM implements GitSCM {
- constructor(gitHubURL: string, endpointGuid: string) {
+ private options: HttpOptions;
+
+ constructor(gitHubURL: string, endpointGuid: string, accessToken?: string) {
super(gitHubURL);
this.endpointGuid = endpointGuid;
+ if (accessToken && accessToken.trim() != "") {
+ this.setAccessToken(accessToken);
+ }
+ }
+
+ setAccessToken(input: string) {
+ this.options = new HttpOptions();
+ this.options.headers = {"Authorization": `Bearer ${input}`};
+ }
+
+ clearAccessToken() {
+ if (this.options) {
+ this.options.headers = {};
+ }
}
getType(): GitSCMType {
@@ -38,19 +55,20 @@ export class GitHubSCM extends BaseSCM implements GitSCM {
}
getRepository(httpClient: HttpClient, projectName: string): Observable {
- return this.getAPI().pipe(
- switchMap(api => httpClient.get(`${api.url}/repos/${projectName}`, api.requestArgs))
+ return this.getAPI(this.options).pipe(
+ switchMap(api => {
+ return httpClient.get(`${api.url}/repos/${projectName}`, api.requestArgs)
+ })
);
}
getBranch(httpClient: HttpClient, projectName: string, branchName: string): Observable {
- return this.getAPI().pipe(
+ return this.getAPI(this.options).pipe(
switchMap(api => httpClient.get(`${api.url}/repos/${projectName}/branches/${branchName}`, api.requestArgs))
);
}
-
getBranches(httpClient: HttpClient, projectName: string): Observable {
- return this.getAPI().pipe(
+ return this.getAPI(this.options).pipe(
switchMap(api => {
const url = `${api.url}/repos/${projectName}/branches`;
const config = new GithubFlattenerForArrayPaginationConfig(httpClient, url, api.requestArgs);
@@ -71,7 +89,7 @@ export class GitHubSCM extends BaseSCM implements GitSCM {
}
getCommitApi(projectName: string, commitSha: string): Observable {
- return this.getAPI().pipe(
+ return this.getAPI(this.options).pipe(
map(api => ({
...api,
url: `${api.url}/repos/${projectName}/commits/${commitSha}`,
@@ -80,7 +98,7 @@ export class GitHubSCM extends BaseSCM implements GitSCM {
}
getCommits(httpClient: HttpClient, projectName: string, ref: string): Observable {
- return this.getAPI().pipe(
+ return this.getAPI(this.options).pipe(
switchMap(api => httpClient.get(
`${api.url}/repos/${projectName}/commits?sha=${ref}`, {
...api.requestArgs,
@@ -98,7 +116,7 @@ export class GitHubSCM extends BaseSCM implements GitSCM {
}
getMatchingRepositories(httpClient: HttpClient, projectName: string): Observable {
- return this.getAPI().pipe(
+ return this.getAPI(this.options).pipe(
switchMap(api => {
const prjParts = projectName.split('/');
let url = `${api.url}/search/repositories?q=${projectName}+in:name+fork:true`;
diff --git a/src/frontend/packages/git/src/shared/scm/scm-base.ts b/src/frontend/packages/git/src/shared/scm/scm-base.ts
index f15b77d3d1..21d93481f7 100644
--- a/src/frontend/packages/git/src/shared/scm/scm-base.ts
+++ b/src/frontend/packages/git/src/shared/scm/scm-base.ts
@@ -21,25 +21,29 @@ export abstract class BaseSCM {
constructor(public publicApiUrl: string) { }
+ public setPublicApi(url: string) {
+ this.publicApiUrl = url
+ }
+
public getPublicApi(): string {
return this.publicApiUrl;
}
- public getAPI(): Observable {
+ public getAPI(options: HttpOptions = new HttpOptions()): Observable {
return this.getEndpoint(this.endpointGuid).pipe(
map(endpoint => {
if (!endpoint) {
// No endpoint, use the default or overwritten public api associated with this type
return {
url: this.getPublicApi(),
- requestArgs: {}
+ requestArgs: options
};
}
// We have an endpoint so always proxy via backend
return {
url: `${commonPrefix}/${endpoint.guid}`,
requestArgs: {
- ... new HttpOptions(),
+ ... options,
headers: {
'x-cap-no-token': `${!endpoint.user}`
}
diff --git a/src/frontend/packages/git/src/shared/scm/scm.service.ts b/src/frontend/packages/git/src/shared/scm/scm.service.ts
index 9018084ff4..c8a916b89d 100644
--- a/src/frontend/packages/git/src/shared/scm/scm.service.ts
+++ b/src/frontend/packages/git/src/shared/scm/scm.service.ts
@@ -17,10 +17,10 @@ export class GitSCMService {
) {
}
- public getSCM(type: GitSCMType, endpointGuid: string): GitSCM {
+ public getSCM(type: GitSCMType, endpointGuid: string, accessToken?: string): GitSCM {
switch (type) {
case 'github':
- return new GitHubSCM(this.gitHubURL, endpointGuid);
+ return new GitHubSCM(this.gitHubURL, endpointGuid, accessToken);
case 'gitlab':
return new GitLabSCM(endpointGuid);
}
diff --git a/src/jetstream/plugins/cfapppush/deploy.go b/src/jetstream/plugins/cfapppush/deploy.go
index d5081279b1..4382311771 100644
--- a/src/jetstream/plugins/cfapppush/deploy.go
+++ b/src/jetstream/plugins/cfapppush/deploy.go
@@ -454,11 +454,12 @@ func (cfAppPush *CFAppPush) getGitSCMSource(clientWebSocket *websocket.Conn, tem
log.Debugf("GitSCM SCM: %s, Source: %s, branch %s, url: %s", info.SCM, info.Project, info.Branch, loggerURL)
cloneDetails := CloneDetails{
- Url: cloneURL,
- LoggerUrl: loggerURL,
- Branch: info.Branch,
- Commit: info.CommitHash,
- SkipSSL: skipSSL,
+ Url: cloneURL,
+ LoggerUrl: loggerURL,
+ Branch: info.Branch,
+ Commit: info.CommitHash,
+ SkipSSL: skipSSL,
+ AccessToken: info.AcccessToken,
}
info.CommitHash, err = cloneRepository(cloneDetails, clientWebSocket, tempDir)
if err != nil {
@@ -625,6 +626,10 @@ func cloneRepository(cloneDetails CloneDetails, clientWebSocket *websocket.Conn,
vcsGit := GetVCS()
+ if len(cloneDetails.AccessToken) > 0 {
+ vcsGit = GetVCS(withAccessToken(cloneDetails.AccessToken))
+ }
+
err := vcsGit.Create(cloneDetails.SkipSSL, tempDir, cloneDetails.Url, cloneDetails.Branch)
if err != nil {
log.Infof("Failed to clone repo %s due to %+v", cloneDetails.LoggerUrl, err)
diff --git a/src/jetstream/plugins/cfapppush/types.go b/src/jetstream/plugins/cfapppush/types.go
index 95969d4765..d555c93e9d 100644
--- a/src/jetstream/plugins/cfapppush/types.go
+++ b/src/jetstream/plugins/cfapppush/types.go
@@ -47,6 +47,7 @@ type GitSCMSourceInfo struct {
SCM string `json:"scm"`
EndpointGUID string `json:"endpointGuid"` // credentials of which to use, e.g. of a private GitHub instance
Username string `json:"username"` // GitLab username has to be supplied by the frontend
+ AcccessToken string `json:"accessToken"` // GitHub private repos/enterprise repos can supply a token through the frontend
}
// Structure used to provide metadata about the Git Url source
@@ -117,9 +118,10 @@ type Applications struct {
}
type CloneDetails struct {
- Url string
- LoggerUrl string
- Branch string
- Commit string
- SkipSSL bool
+ Url string
+ LoggerUrl string
+ Branch string
+ Commit string
+ SkipSSL bool
+ AccessToken string
}
diff --git a/src/jetstream/plugins/cfapppush/vcs.go b/src/jetstream/plugins/cfapppush/vcs.go
index 5e85384688..2c8258b070 100644
--- a/src/jetstream/plugins/cfapppush/vcs.go
+++ b/src/jetstream/plugins/cfapppush/vcs.go
@@ -4,6 +4,8 @@ package cfapppush
import (
"bytes"
+ "fmt"
+ "net/url"
"os"
"os/exec"
"strconv"
@@ -15,20 +17,36 @@ import (
var vcsGit = &vcsCmd{
name: "Git",
cmd: "git",
+ accessToken: "",
createCmd: []string{"clone -c http.sslVerify={sslVerify} -b {branch} {repo} {dir} "},
resetToCommitCmd: []string{"reset --hard {commit}"},
checkoutCmd: []string{"checkout refs/remotes/origin/{branch}"},
headCmd: []string{"rev-parse HEAD"},
}
+type vcsOptions func(*vcsCmd)
+
// Currently only git is supported
-func GetVCS() *vcsCmd {
+func GetVCS(opts ...vcsOptions) *vcsCmd {
+ vcsGit := &(*vcsGit)
+
+ for _, opt := range opts {
+ opt(vcsGit)
+ }
+
return vcsGit
}
+func withAccessToken(accessToken string) vcsOptions {
+ return func(vc *vcsCmd) {
+ vc.accessToken = accessToken
+ }
+}
+
type vcsCmd struct {
- name string
- cmd string // name of binary to invoke command
+ name string
+ cmd string // name of binary to invoke command
+ accessToken string // optional, if empty do not use it
createCmd []string // commands to download a fresh copy of a repository
checkoutCmd []string // commands to checkout a branch
@@ -37,8 +55,18 @@ type vcsCmd struct {
}
func (vcs *vcsCmd) Create(skipSSL bool, dir string, repo string, branch string) error {
+ repoUrl, err := url.Parse(repo)
+
+ if err != nil {
+ return fmt.Errorf("could not execute vcs create: %w", err)
+ }
+
+ if len(vcs.accessToken) > 0 {
+ repoUrl.User = url.UserPassword("x-access-token", vcs.accessToken)
+ }
+
for _, cmd := range vcs.createCmd {
- if err := vcs.run(".", cmd, "sslVerify", strconv.FormatBool(!skipSSL), "dir", dir, "repo", repo, "branch", branch); err != nil {
+ if err := vcs.run(".", cmd, "sslVerify", strconv.FormatBool(!skipSSL), "dir", dir, "repo", repoUrl.String(), "branch", branch); err != nil {
return err
}
}