feat(gh-85): take git tokens from environment (#88)

This commit is contained in:
Andrea Lamparelli 2023-12-10 22:05:53 +01:00 committed by GitHub
parent aac73bf7c5
commit 70da575afc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 456 additions and 24 deletions

View file

@ -97,7 +97,7 @@ This tool comes with some inputs that allow users to override the default behavi
| Target Branches | -tb, --target-branch | N | Comma separated list of branches where the changes must be backported to | | | Target Branches | -tb, --target-branch | N | Comma separated list of branches where the changes must be backported to | |
| Pull Request | -pr, --pull-request | N | Original pull request url, the one that must be backported, e.g., https://github.com/kiegroup/git-backporting/pull/1 | | | Pull Request | -pr, --pull-request | N | Original pull request url, the one that must be backported, e.g., https://github.com/kiegroup/git-backporting/pull/1 | |
| Configuration File | -cf, --config-file | N | Configuration file, in JSON format, containing all options to be overridded, note that if provided all other CLI options will be ignored | | | Configuration File | -cf, --config-file | N | Configuration file, in JSON format, containing all options to be overridded, note that if provided all other CLI options will be ignored | |
| Auth | -a, --auth | N | `GITHUB_TOKEN`, `GITLAB_TOKEN` or a `repo` scoped [Personal Access Token](https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token) | "" | | Auth | -a, --auth | N | Git access/authorization token, if provided all token env variables will be ignored. See [auth token](#authorization-token) section for more details | "" |
| Folder | -f, --folder | N | Local folder full name of the repository that will be checked out, e.g., /tmp/folder | {cwd}/bp | | Folder | -f, --folder | N | Local folder full name of the repository that will be checked out, e.g., /tmp/folder | {cwd}/bp |
| Git User | -gu, --git-user | N | Local git user name | "GitHub" | | Git User | -gu, --git-user | N | Local git user name | "GitHub" |
| Git Email | -ge, --git-email | N | Local git user email | "noreply@github.com" | | Git Email | -ge, --git-email | N | Local git user email | "noreply@github.com" |
@ -118,6 +118,17 @@ This tool comes with some inputs that allow users to override the default behavi
> **NOTE**: `pull request` and `target branch` are *mandatory*, they must be provided as CLI options or as part of the configuration file (if used). > **NOTE**: `pull request` and `target branch` are *mandatory*, they must be provided as CLI options or as part of the configuration file (if used).
#### Authorization token
Since version `4.5.0` we introduced a new feature that allows user to provide the git access token through environment variables. These env variables are taken into consideration only if the `--auth/-a` is not provided as argument/input.
Here the supported list of env variables:
- `GITHUB_TOKEN`: this is checked only if backporting on Github platform.
- `GITLAB_TOKEN`: this is checked only if backporting on Gitlab platform.
- `CODEBERG_TOKEN`: this is checked only if backporting on Codeberg platform.
- `GIT_TOKEN`: this is considered if none of the previous envs are set.
> **NOTE**: if `--auth` argument is provided, all env variables will be ignored even if not empty.
#### Configuration file example #### Configuration file example
This is an example of a configuration file that can be used. This is an example of a configuration file that can be used.
@ -194,6 +205,9 @@ on:
- closed - closed
- labeled - labeled
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs: jobs:
backporting: backporting:
name: "Backporting" name: "Backporting"
@ -216,7 +230,6 @@ jobs:
with: with:
target-branch: v1 target-branch: v1
pull-request: ${{ github.event.pull_request.url }} pull-request: ${{ github.event.pull_request.url }}
auth: ${{ secrets.GITHUB_TOKEN }}
``` ```
For a complete description of all inputs see [Inputs section](#inputs). For a complete description of all inputs see [Inputs section](#inputs).

View file

@ -15,7 +15,7 @@ inputs:
required: false required: false
default: "false" default: "false"
auth: auth:
description: "GITHUB_TOKEN or a `repo` scoped Personal Access Token (PAT)" description: "GITHUB_TOKEN or a `repo` scoped Personal Access Token (PAT), if not provided will look for existing env variables like GITHUB_TOKEN"
default: ${{ github.token }} default: ${{ github.token }}
required: false required: false
git-user: git-user:

85
dist/cli/index.js vendored
View file

@ -182,7 +182,7 @@ class CLIArgsParser extends args_parser_1.default {
.option("-tb, --target-branch <branches>", "comma separated list of branches where changes must be backported to") .option("-tb, --target-branch <branches>", "comma separated list of branches where changes must be backported to")
.option("-pr, --pull-request <pr-url>", "pull request url, e.g., https://github.com/kiegroup/git-backporting/pull/1") .option("-pr, --pull-request <pr-url>", "pull request url, e.g., https://github.com/kiegroup/git-backporting/pull/1")
.option("-d, --dry-run", "if enabled the tool does not create any pull request nor push anything remotely") .option("-d, --dry-run", "if enabled the tool does not create any pull request nor push anything remotely")
.option("-a, --auth <auth>", "git service authentication string, e.g., github token") .option("-a, --auth <auth>", "git authentication string, if not provided fallback by looking for existing env variables like GITHUB_TOKEN")
.option("-gu, --git-user <git-user>", "local git user name, default is 'GitHub'") .option("-gu, --git-user <git-user>", "local git user name, default is 'GitHub'")
.option("-ge, --git-email <git-email>", "local git user email, default is 'noreply@github.com'") .option("-ge, --git-email <git-email>", "local git user email, default is 'noreply@github.com'")
.option("-f, --folder <folder>", "local folder where the repo will be checked out, e.g., /tmp/folder") .option("-f, --folder <folder>", "local folder where the repo will be checked out, e.g., /tmp/folder")
@ -251,7 +251,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
const configs_types_1 = __nccwpck_require__(4753);
const logger_service_factory_1 = __importDefault(__nccwpck_require__(8936)); const logger_service_factory_1 = __importDefault(__nccwpck_require__(8936));
const git_types_1 = __nccwpck_require__(750);
/** /**
* Abstract configuration parser class in charge to parse * Abstract configuration parser class in charge to parse
* Args and produces a common Configs object * Args and produces a common Configs object
@ -273,10 +275,68 @@ class ConfigsParser {
} }
return Promise.resolve(configs); return Promise.resolve(configs);
} }
/**
* Retrieve the git token from env variable, the default is taken from GIT_TOKEN env.
* All specific git env variable have precedence and override the default one.
* @param gitType
* @returns tuple where
* - the first element is the corresponding env value
* - the second element is true if the value is not undefined nor empty
*/
getGitTokenFromEnv(gitType) {
let [token] = this.getEnv(configs_types_1.AuthTokenId.GIT_TOKEN);
let [specToken, specOk] = [undefined, false];
if (git_types_1.GitClientType.GITHUB == gitType) {
[specToken, specOk] = this.getEnv(configs_types_1.AuthTokenId.GITHUB_TOKEN);
}
else if (git_types_1.GitClientType.GITLAB == gitType) {
[specToken, specOk] = this.getEnv(configs_types_1.AuthTokenId.GITLAB_TOKEN);
}
else if (git_types_1.GitClientType.CODEBERG == gitType) {
[specToken, specOk] = this.getEnv(configs_types_1.AuthTokenId.CODEBERG_TOKEN);
}
if (specOk) {
token = specToken;
}
return token;
}
/**
* Get process env variable given the input key string
* @param key
* @returns tuple where
* - the first element is the corresponding env value
* - the second element is true if the value is not undefined nor empty
*/
getEnv(key) {
const val = process.env[key];
return [val, val !== undefined && val !== ""];
}
} }
exports["default"] = ConfigsParser; exports["default"] = ConfigsParser;
/***/ }),
/***/ 4753:
/***/ ((__unused_webpack_module, exports) => {
"use strict";
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.AuthTokenId = void 0;
var AuthTokenId;
(function (AuthTokenId) {
// github specific token
AuthTokenId["GITHUB_TOKEN"] = "GITHUB_TOKEN";
// gitlab specific token
AuthTokenId["GITLAB_TOKEN"] = "GITLAB_TOKEN";
// codeberg specific token
AuthTokenId["CODEBERG_TOKEN"] = "CODEBERG_TOKEN";
// generic git token
AuthTokenId["GIT_TOKEN"] = "GIT_TOKEN";
})(AuthTokenId = exports.AuthTokenId || (exports.AuthTokenId = {}));
/***/ }), /***/ }),
/***/ 6618: /***/ 6618:
@ -311,9 +371,18 @@ class PullRequestConfigsParser extends configs_parser_1.default {
if (bpBranchNames.length > 1 && bpBranchNames.length != targetBranches.length) { if (bpBranchNames.length > 1 && bpBranchNames.length != targetBranches.length) {
throw new Error(`The number of backport branch names, if provided, must match the number of target branches or just one, provided ${bpBranchNames.length} branch names instead`); throw new Error(`The number of backport branch names, if provided, must match the number of target branches or just one, provided ${bpBranchNames.length} branch names instead`);
} }
// setup the auth token
let token = args.auth;
if (token === undefined) {
this.logger.info("Auth argument not provided, checking available tokens from env..");
token = this.getGitTokenFromEnv(this.gitClient.getClientType());
if (!token) {
this.logger.info("Git token not set in the env");
}
}
return { return {
dryRun: args.dryRun, dryRun: args.dryRun,
auth: args.auth, auth: token,
folder: `${folder.startsWith("/") ? "" : process.cwd() + "/"}${args.folder ?? this.getDefaultFolder()}`, folder: `${folder.startsWith("/") ? "" : process.cwd() + "/"}${args.folder ?? this.getDefaultFolder()}`,
mergeStrategy: args.strategy, mergeStrategy: args.strategy,
mergeStrategyOption: args.strategyOption, mergeStrategyOption: args.strategyOption,
@ -567,7 +636,7 @@ class GitClientFactory {
GitClientFactory.instance = new gitlab_client_1.default(authToken, apiUrl); GitClientFactory.instance = new gitlab_client_1.default(authToken, apiUrl);
break; break;
case git_types_1.GitClientType.CODEBERG: case git_types_1.GitClientType.CODEBERG:
GitClientFactory.instance = new github_client_1.default(authToken, apiUrl); GitClientFactory.instance = new github_client_1.default(authToken, apiUrl, true);
break; break;
default: default:
throw new Error(`Invalid git service type received: ${type}`); throw new Error(`Invalid git service type received: ${type}`);
@ -673,12 +742,16 @@ const github_mapper_1 = __importDefault(__nccwpck_require__(5764));
const octokit_factory_1 = __importDefault(__nccwpck_require__(4257)); const octokit_factory_1 = __importDefault(__nccwpck_require__(4257));
const logger_service_factory_1 = __importDefault(__nccwpck_require__(8936)); const logger_service_factory_1 = __importDefault(__nccwpck_require__(8936));
class GitHubClient { class GitHubClient {
constructor(token, apiUrl) { constructor(token, apiUrl, isForCodeberg = false) {
this.apiUrl = apiUrl; this.apiUrl = apiUrl;
this.isForCodeberg = isForCodeberg;
this.logger = logger_service_factory_1.default.getLogger(); this.logger = logger_service_factory_1.default.getLogger();
this.octokit = octokit_factory_1.default.getOctokit(token, this.apiUrl); this.octokit = octokit_factory_1.default.getOctokit(token, this.apiUrl);
this.mapper = new github_mapper_1.default(); this.mapper = new github_mapper_1.default();
} }
getClientType() {
return this.isForCodeberg ? git_types_1.GitClientType.CODEBERG : git_types_1.GitClientType.GITHUB;
}
// READ // READ
getDefaultGitUser() { getDefaultGitUser() {
return this.apiUrl.includes(git_types_1.GitClientType.CODEBERG.toString()) ? "Codeberg" : "GitHub"; return this.apiUrl.includes(git_types_1.GitClientType.CODEBERG.toString()) ? "Codeberg" : "GitHub";
@ -889,6 +962,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
const git_types_1 = __nccwpck_require__(750);
const logger_service_factory_1 = __importDefault(__nccwpck_require__(8936)); const logger_service_factory_1 = __importDefault(__nccwpck_require__(8936));
const gitlab_mapper_1 = __importDefault(__nccwpck_require__(2675)); const gitlab_mapper_1 = __importDefault(__nccwpck_require__(2675));
const axios_1 = __importDefault(__nccwpck_require__(8757)); const axios_1 = __importDefault(__nccwpck_require__(8757));
@ -909,6 +983,9 @@ class GitLabClient {
}); });
this.mapper = new gitlab_mapper_1.default(this.client); this.mapper = new gitlab_mapper_1.default(this.client);
} }
getClientType() {
return git_types_1.GitClientType.GITLAB;
}
getDefaultGitUser() { getDefaultGitUser() {
return "Gitlab"; return "Gitlab";
} }

83
dist/gha/index.js vendored
View file

@ -221,7 +221,9 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
const configs_types_1 = __nccwpck_require__(4753);
const logger_service_factory_1 = __importDefault(__nccwpck_require__(8936)); const logger_service_factory_1 = __importDefault(__nccwpck_require__(8936));
const git_types_1 = __nccwpck_require__(750);
/** /**
* Abstract configuration parser class in charge to parse * Abstract configuration parser class in charge to parse
* Args and produces a common Configs object * Args and produces a common Configs object
@ -243,10 +245,68 @@ class ConfigsParser {
} }
return Promise.resolve(configs); return Promise.resolve(configs);
} }
/**
* Retrieve the git token from env variable, the default is taken from GIT_TOKEN env.
* All specific git env variable have precedence and override the default one.
* @param gitType
* @returns tuple where
* - the first element is the corresponding env value
* - the second element is true if the value is not undefined nor empty
*/
getGitTokenFromEnv(gitType) {
let [token] = this.getEnv(configs_types_1.AuthTokenId.GIT_TOKEN);
let [specToken, specOk] = [undefined, false];
if (git_types_1.GitClientType.GITHUB == gitType) {
[specToken, specOk] = this.getEnv(configs_types_1.AuthTokenId.GITHUB_TOKEN);
}
else if (git_types_1.GitClientType.GITLAB == gitType) {
[specToken, specOk] = this.getEnv(configs_types_1.AuthTokenId.GITLAB_TOKEN);
}
else if (git_types_1.GitClientType.CODEBERG == gitType) {
[specToken, specOk] = this.getEnv(configs_types_1.AuthTokenId.CODEBERG_TOKEN);
}
if (specOk) {
token = specToken;
}
return token;
}
/**
* Get process env variable given the input key string
* @param key
* @returns tuple where
* - the first element is the corresponding env value
* - the second element is true if the value is not undefined nor empty
*/
getEnv(key) {
const val = process.env[key];
return [val, val !== undefined && val !== ""];
}
} }
exports["default"] = ConfigsParser; exports["default"] = ConfigsParser;
/***/ }),
/***/ 4753:
/***/ ((__unused_webpack_module, exports) => {
"use strict";
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.AuthTokenId = void 0;
var AuthTokenId;
(function (AuthTokenId) {
// github specific token
AuthTokenId["GITHUB_TOKEN"] = "GITHUB_TOKEN";
// gitlab specific token
AuthTokenId["GITLAB_TOKEN"] = "GITLAB_TOKEN";
// codeberg specific token
AuthTokenId["CODEBERG_TOKEN"] = "CODEBERG_TOKEN";
// generic git token
AuthTokenId["GIT_TOKEN"] = "GIT_TOKEN";
})(AuthTokenId = exports.AuthTokenId || (exports.AuthTokenId = {}));
/***/ }), /***/ }),
/***/ 6618: /***/ 6618:
@ -281,9 +341,18 @@ class PullRequestConfigsParser extends configs_parser_1.default {
if (bpBranchNames.length > 1 && bpBranchNames.length != targetBranches.length) { if (bpBranchNames.length > 1 && bpBranchNames.length != targetBranches.length) {
throw new Error(`The number of backport branch names, if provided, must match the number of target branches or just one, provided ${bpBranchNames.length} branch names instead`); throw new Error(`The number of backport branch names, if provided, must match the number of target branches or just one, provided ${bpBranchNames.length} branch names instead`);
} }
// setup the auth token
let token = args.auth;
if (token === undefined) {
this.logger.info("Auth argument not provided, checking available tokens from env..");
token = this.getGitTokenFromEnv(this.gitClient.getClientType());
if (!token) {
this.logger.info("Git token not set in the env");
}
}
return { return {
dryRun: args.dryRun, dryRun: args.dryRun,
auth: args.auth, auth: token,
folder: `${folder.startsWith("/") ? "" : process.cwd() + "/"}${args.folder ?? this.getDefaultFolder()}`, folder: `${folder.startsWith("/") ? "" : process.cwd() + "/"}${args.folder ?? this.getDefaultFolder()}`,
mergeStrategy: args.strategy, mergeStrategy: args.strategy,
mergeStrategyOption: args.strategyOption, mergeStrategyOption: args.strategyOption,
@ -537,7 +606,7 @@ class GitClientFactory {
GitClientFactory.instance = new gitlab_client_1.default(authToken, apiUrl); GitClientFactory.instance = new gitlab_client_1.default(authToken, apiUrl);
break; break;
case git_types_1.GitClientType.CODEBERG: case git_types_1.GitClientType.CODEBERG:
GitClientFactory.instance = new github_client_1.default(authToken, apiUrl); GitClientFactory.instance = new github_client_1.default(authToken, apiUrl, true);
break; break;
default: default:
throw new Error(`Invalid git service type received: ${type}`); throw new Error(`Invalid git service type received: ${type}`);
@ -643,12 +712,16 @@ const github_mapper_1 = __importDefault(__nccwpck_require__(5764));
const octokit_factory_1 = __importDefault(__nccwpck_require__(4257)); const octokit_factory_1 = __importDefault(__nccwpck_require__(4257));
const logger_service_factory_1 = __importDefault(__nccwpck_require__(8936)); const logger_service_factory_1 = __importDefault(__nccwpck_require__(8936));
class GitHubClient { class GitHubClient {
constructor(token, apiUrl) { constructor(token, apiUrl, isForCodeberg = false) {
this.apiUrl = apiUrl; this.apiUrl = apiUrl;
this.isForCodeberg = isForCodeberg;
this.logger = logger_service_factory_1.default.getLogger(); this.logger = logger_service_factory_1.default.getLogger();
this.octokit = octokit_factory_1.default.getOctokit(token, this.apiUrl); this.octokit = octokit_factory_1.default.getOctokit(token, this.apiUrl);
this.mapper = new github_mapper_1.default(); this.mapper = new github_mapper_1.default();
} }
getClientType() {
return this.isForCodeberg ? git_types_1.GitClientType.CODEBERG : git_types_1.GitClientType.GITHUB;
}
// READ // READ
getDefaultGitUser() { getDefaultGitUser() {
return this.apiUrl.includes(git_types_1.GitClientType.CODEBERG.toString()) ? "Codeberg" : "GitHub"; return this.apiUrl.includes(git_types_1.GitClientType.CODEBERG.toString()) ? "Codeberg" : "GitHub";
@ -859,6 +932,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod }; return (mod && mod.__esModule) ? mod : { "default": mod };
}; };
Object.defineProperty(exports, "__esModule", ({ value: true })); Object.defineProperty(exports, "__esModule", ({ value: true }));
const git_types_1 = __nccwpck_require__(750);
const logger_service_factory_1 = __importDefault(__nccwpck_require__(8936)); const logger_service_factory_1 = __importDefault(__nccwpck_require__(8936));
const gitlab_mapper_1 = __importDefault(__nccwpck_require__(2675)); const gitlab_mapper_1 = __importDefault(__nccwpck_require__(2675));
const axios_1 = __importDefault(__nccwpck_require__(8757)); const axios_1 = __importDefault(__nccwpck_require__(8757));
@ -879,6 +953,9 @@ class GitLabClient {
}); });
this.mapper = new gitlab_mapper_1.default(this.client); this.mapper = new gitlab_mapper_1.default(this.client);
} }
getClientType() {
return git_types_1.GitClientType.GITLAB;
}
getDefaultGitUser() { getDefaultGitUser() {
return "Gitlab"; return "Gitlab";
} }

View file

@ -13,7 +13,7 @@ export default class CLIArgsParser extends ArgsParser {
.option("-tb, --target-branch <branches>", "comma separated list of branches where changes must be backported to") .option("-tb, --target-branch <branches>", "comma separated list of branches where changes must be backported to")
.option("-pr, --pull-request <pr-url>", "pull request url, e.g., https://github.com/kiegroup/git-backporting/pull/1") .option("-pr, --pull-request <pr-url>", "pull request url, e.g., https://github.com/kiegroup/git-backporting/pull/1")
.option("-d, --dry-run", "if enabled the tool does not create any pull request nor push anything remotely") .option("-d, --dry-run", "if enabled the tool does not create any pull request nor push anything remotely")
.option("-a, --auth <auth>", "git service authentication string, e.g., github token") .option("-a, --auth <auth>", "git authentication string, if not provided fallback by looking for existing env variables like GITHUB_TOKEN")
.option("-gu, --git-user <git-user>", "local git user name, default is 'GitHub'") .option("-gu, --git-user <git-user>", "local git user name, default is 'GitHub'")
.option("-ge, --git-email <git-email>", "local git user email, default is 'noreply@github.com'") .option("-ge, --git-email <git-email>", "local git user email, default is 'noreply@github.com'")
.option("-f, --folder <folder>", "local folder where the repo will be checked out, e.g., /tmp/folder") .option("-f, --folder <folder>", "local folder where the repo will be checked out, e.g., /tmp/folder")

View file

@ -1,7 +1,8 @@
import { Args } from "@bp/service/args/args.types"; import { Args } from "@bp/service/args/args.types";
import { Configs } from "@bp/service/configs/configs.types"; import { AuthTokenId, Configs } from "@bp/service/configs/configs.types";
import LoggerService from "../logger/logger-service"; import LoggerService from "../logger/logger-service";
import LoggerServiceFactory from "../logger/logger-service-factory"; import LoggerServiceFactory from "../logger/logger-service-factory";
import { GitClientType } from "../git/git.types";
/** /**
* Abstract configuration parser class in charge to parse * Abstract configuration parser class in charge to parse
@ -34,4 +35,42 @@ import LoggerServiceFactory from "../logger/logger-service-factory";
return Promise.resolve(configs); return Promise.resolve(configs);
} }
/**
* Retrieve the git token from env variable, the default is taken from GIT_TOKEN env.
* All specific git env variable have precedence and override the default one.
* @param gitType
* @returns tuple where
* - the first element is the corresponding env value
* - the second element is true if the value is not undefined nor empty
*/
public getGitTokenFromEnv(gitType: GitClientType): string | undefined {
let [token] = this.getEnv(AuthTokenId.GIT_TOKEN);
let [specToken, specOk]: [string | undefined, boolean] = [undefined, false];
if (GitClientType.GITHUB == gitType) {
[specToken, specOk] = this.getEnv(AuthTokenId.GITHUB_TOKEN);
} else if (GitClientType.GITLAB == gitType) {
[specToken, specOk] = this.getEnv(AuthTokenId.GITLAB_TOKEN);
} else if (GitClientType.CODEBERG == gitType) {
[specToken, specOk] = this.getEnv(AuthTokenId.CODEBERG_TOKEN);
}
if (specOk) {
token = specToken;
}
return token;
}
/**
* Get process env variable given the input key string
* @param key
* @returns tuple where
* - the first element is the corresponding env value
* - the second element is true if the value is not undefined nor empty
*/
public getEnv(key: string): [string | undefined, boolean] {
const val = process.env[key];
return [val, val !== undefined && val !== ""];
}
} }

View file

@ -21,3 +21,13 @@ export interface Configs {
backportPullRequests: BackportPullRequest[], backportPullRequests: BackportPullRequest[],
} }
export enum AuthTokenId {
// github specific token
GITHUB_TOKEN = "GITHUB_TOKEN",
// gitlab specific token
GITLAB_TOKEN = "GITLAB_TOKEN",
// codeberg specific token
CODEBERG_TOKEN = "CODEBERG_TOKEN",
// generic git token
GIT_TOKEN = "GIT_TOKEN",
}

View file

@ -33,9 +33,19 @@ export default class PullRequestConfigsParser extends ConfigsParser {
throw new Error(`The number of backport branch names, if provided, must match the number of target branches or just one, provided ${bpBranchNames.length} branch names instead`); throw new Error(`The number of backport branch names, if provided, must match the number of target branches or just one, provided ${bpBranchNames.length} branch names instead`);
} }
// setup the auth token
let token = args.auth;
if (token === undefined) {
this.logger.info("Auth argument not provided, checking available tokens from env..");
token = this.getGitTokenFromEnv(this.gitClient.getClientType());
if (!token) {
this.logger.info("Git token not set in the env");
}
}
return { return {
dryRun: args.dryRun!, dryRun: args.dryRun!,
auth: args.auth, auth: token,
folder: `${folder.startsWith("/") ? "" : process.cwd() + "/"}${args.folder ?? this.getDefaultFolder()}`, folder: `${folder.startsWith("/") ? "" : process.cwd() + "/"}${args.folder ?? this.getDefaultFolder()}`,
mergeStrategy: args.strategy, mergeStrategy: args.strategy,
mergeStrategyOption: args.strategyOption, mergeStrategyOption: args.strategyOption,

View file

@ -44,7 +44,7 @@ export default class GitClientFactory {
GitClientFactory.instance = new GitLabClient(authToken, apiUrl); GitClientFactory.instance = new GitLabClient(authToken, apiUrl);
break; break;
case GitClientType.CODEBERG: case GitClientType.CODEBERG:
GitClientFactory.instance = new GitHubService(authToken, apiUrl); GitClientFactory.instance = new GitHubService(authToken, apiUrl, true);
break; break;
default: default:
throw new Error(`Invalid git service type received: ${type}`); throw new Error(`Invalid git service type received: ${type}`);

View file

@ -1,4 +1,4 @@
import { BackportPullRequest, GitPullRequest } from "@bp/service/git/git.types"; import { BackportPullRequest, GitClientType, GitPullRequest } from "@bp/service/git/git.types";
/** /**
* Git management service interface, which provides a common API for interacting * Git management service interface, which provides a common API for interacting
@ -6,6 +6,11 @@ import { BackportPullRequest, GitPullRequest } from "@bp/service/git/git.types";
*/ */
export default interface GitClient { export default interface GitClient {
/**
* @returns {GitClientType} specific git client enum type
*/
getClientType(): GitClientType
// READ // READ
getDefaultGitUser(): string; getDefaultGitUser(): string;

View file

@ -11,16 +11,22 @@ export default class GitHubClient implements GitClient {
private logger: LoggerService; private logger: LoggerService;
private apiUrl: string; private apiUrl: string;
private isForCodeberg: boolean;
private octokit: Octokit; private octokit: Octokit;
private mapper: GitHubMapper; private mapper: GitHubMapper;
constructor(token: string | undefined, apiUrl: string) { constructor(token: string | undefined, apiUrl: string, isForCodeberg = false) {
this.apiUrl = apiUrl; this.apiUrl = apiUrl;
this.isForCodeberg = isForCodeberg;
this.logger = LoggerServiceFactory.getLogger(); this.logger = LoggerServiceFactory.getLogger();
this.octokit = OctokitFactory.getOctokit(token, this.apiUrl); this.octokit = OctokitFactory.getOctokit(token, this.apiUrl);
this.mapper = new GitHubMapper(); this.mapper = new GitHubMapper();
} }
getClientType(): GitClientType {
return this.isForCodeberg ? GitClientType.CODEBERG : GitClientType.GITHUB;
}
// READ // READ
getDefaultGitUser(): string { getDefaultGitUser(): string {

View file

@ -1,6 +1,6 @@
import LoggerService from "@bp/service/logger/logger-service"; import LoggerService from "@bp/service/logger/logger-service";
import GitClient from "@bp/service/git/git-client"; import GitClient from "@bp/service/git/git-client";
import { GitPullRequest, BackportPullRequest } from "@bp/service/git/git.types"; import { GitPullRequest, BackportPullRequest, GitClientType } from "@bp/service/git/git.types";
import LoggerServiceFactory from "@bp/service/logger/logger-service-factory"; import LoggerServiceFactory from "@bp/service/logger/logger-service-factory";
import { CommitSchema, MergeRequestSchema, UserSchema } from "@gitbeaker/rest"; import { CommitSchema, MergeRequestSchema, UserSchema } from "@gitbeaker/rest";
import GitLabMapper from "@bp/service/git/gitlab/gitlab-mapper"; import GitLabMapper from "@bp/service/git/gitlab/gitlab-mapper";
@ -30,6 +30,10 @@ export default class GitLabClient implements GitClient {
this.mapper = new GitLabMapper(this.client); this.mapper = new GitLabMapper(this.client);
} }
getClientType(): GitClientType {
return GitClientType.GITLAB;
}
getDefaultGitUser(): string { getDefaultGitUser(): string {
return "Gitlab"; return "Gitlab";
} }

View file

@ -4,7 +4,7 @@ import PullRequestConfigsParser from "@bp/service/configs/pullrequest/pr-configs
import GitClientFactory from "@bp/service/git/git-client-factory"; import GitClientFactory from "@bp/service/git/git-client-factory";
import { GitClientType } from "@bp/service/git/git.types"; import { GitClientType } from "@bp/service/git/git.types";
import { mockGitHubClient } from "../../../support/mock/git-client-mock-support"; import { mockGitHubClient } from "../../../support/mock/git-client-mock-support";
import { resetProcessArgs } from "../../../support/utils"; import { resetEnvTokens, resetProcessArgs } from "../../../support/utils";
import { MERGED_PR_FIXTURE, REPO, TARGET_OWNER, MULT_COMMITS_PR_FIXTURE } from "../../../support/mock/github-data"; import { MERGED_PR_FIXTURE, REPO, TARGET_OWNER, MULT_COMMITS_PR_FIXTURE } from "../../../support/mock/github-data";
import GitHubMapper from "@bp/service/git/github/github-mapper"; import GitHubMapper from "@bp/service/git/github/github-mapper";
import GitHubClient from "@bp/service/git/github/github-client"; import GitHubClient from "@bp/service/git/github/github-client";
@ -28,6 +28,9 @@ describe("github pull request config parser", () => {
// reset process.env variables // reset process.env variables
resetProcessArgs(); resetProcessArgs();
// reset env tokens
resetEnvTokens();
// mock octokit // mock octokit
mockGitHubClient("http://localhost/api/v3"); mockGitHubClient("http://localhost/api/v3");

View file

@ -1,10 +1,10 @@
import { Args } from "@bp/service/args/args.types"; import { Args } from "@bp/service/args/args.types";
import { Configs } from "@bp/service/configs/configs.types"; import { AuthTokenId, Configs } from "@bp/service/configs/configs.types";
import PullRequestConfigsParser from "@bp/service/configs/pullrequest/pr-configs-parser"; import PullRequestConfigsParser from "@bp/service/configs/pullrequest/pr-configs-parser";
import GitClientFactory from "@bp/service/git/git-client-factory"; import GitClientFactory from "@bp/service/git/git-client-factory";
import { GitClientType } from "@bp/service/git/git.types"; import { GitClientType } from "@bp/service/git/git.types";
import { mockGitHubClient } from "../../../support/mock/git-client-mock-support"; import { mockGitHubClient } from "../../../support/mock/git-client-mock-support";
import { addProcessArgs, createTestFile, removeTestFile, resetProcessArgs } from "../../../support/utils"; import { addProcessArgs, createTestFile, removeTestFile, resetEnvTokens, resetProcessArgs } from "../../../support/utils";
import { MERGED_PR_FIXTURE, OPEN_PR_FIXTURE, NOT_MERGED_PR_FIXTURE, REPO, TARGET_OWNER, MULT_COMMITS_PR_FIXTURE } from "../../../support/mock/github-data"; import { MERGED_PR_FIXTURE, OPEN_PR_FIXTURE, NOT_MERGED_PR_FIXTURE, REPO, TARGET_OWNER, MULT_COMMITS_PR_FIXTURE } from "../../../support/mock/github-data";
import CLIArgsParser from "@bp/service/args/cli/cli-args-parser"; import CLIArgsParser from "@bp/service/args/cli/cli-args-parser";
import GitHubMapper from "@bp/service/git/github/github-mapper"; import GitHubMapper from "@bp/service/git/github/github-mapper";
@ -66,6 +66,9 @@ describe("github pull request config parser", () => {
// reset process.env variables // reset process.env variables
resetProcessArgs(); resetProcessArgs();
// reset env tokens
resetEnvTokens();
// mock octokit // mock octokit
mockGitHubClient("http://localhost/api/v3"); mockGitHubClient("http://localhost/api/v3");
@ -77,7 +80,6 @@ describe("github pull request config parser", () => {
test("parse configs from pull request", async () => { test("parse configs from pull request", async () => {
const args: Args = { const args: Args = {
dryRun: false, dryRun: false,
auth: "",
pullRequest: mergedPRUrl, pullRequest: mergedPRUrl,
targetBranch: "prod", targetBranch: "prod",
gitUser: "GitHub", gitUser: "GitHub",
@ -99,7 +101,7 @@ describe("github pull request config parser", () => {
user: "GitHub", user: "GitHub",
email: "noreply@github.com" email: "noreply@github.com"
}); });
expect(configs.auth).toEqual(""); expect(configs.auth).toEqual(undefined);
expect(configs.folder).toEqual(process.cwd() + "/bp"); expect(configs.folder).toEqual(process.cwd() + "/bp");
expect(configs.originalPullRequest).toEqual({ expect(configs.originalPullRequest).toEqual({
number: 2368, number: 2368,
@ -840,4 +842,82 @@ describe("github pull request config parser", () => {
comments: ["First comment", "Second comment"], comments: ["First comment", "Second comment"],
}); });
}); });
test("override token using auth arg", async () => {
process.env[AuthTokenId.GITHUB_TOKEN] = "mygithubtoken";
const args: Args = {
dryRun: true,
auth: "whatever",
pullRequest: mergedPRUrl,
targetBranch: "prod",
folder: "/tmp/test",
gitUser: "GitHub",
gitEmail: "noreply@github.com",
reviewers: [],
assignees: [],
inheritReviewers: true,
};
const configs: Configs = await configParser.parseAndValidate(args);
expect(configs.dryRun).toEqual(true);
expect(configs.auth).toEqual("whatever");
expect(configs.folder).toEqual("/tmp/test");
expect(configs.git).toEqual({
user: "GitHub",
email: "noreply@github.com"
});
});
test("auth using GITHUB_TOKEN has precedence over GIT_TOKEN env variable", async () => {
process.env[AuthTokenId.GIT_TOKEN] = "mygittoken";
process.env[AuthTokenId.GITHUB_TOKEN] = "mygithubtoken";
const args: Args = {
dryRun: true,
pullRequest: mergedPRUrl,
targetBranch: "prod",
folder: "/tmp/test",
gitUser: "GitHub",
gitEmail: "noreply@github.com",
reviewers: [],
assignees: [],
inheritReviewers: true,
};
const configs: Configs = await configParser.parseAndValidate(args);
expect(configs.dryRun).toEqual(true);
expect(configs.auth).toEqual("mygithubtoken");
expect(configs.folder).toEqual("/tmp/test");
expect(configs.git).toEqual({
user: "GitHub",
email: "noreply@github.com"
});
});
test("ignore env variables related to other git platforms", async () => {
process.env[AuthTokenId.GITLAB_TOKEN] = "mygitlabtoken";
process.env[AuthTokenId.CODEBERG_TOKEN] = "mycodebergtoken";
const args: Args = {
dryRun: true,
pullRequest: mergedPRUrl,
targetBranch: "prod",
folder: "/tmp/test",
gitUser: "GitHub",
gitEmail: "noreply@github.com",
reviewers: [],
assignees: [],
inheritReviewers: true,
};
const configs: Configs = await configParser.parseAndValidate(args);
expect(configs.dryRun).toEqual(true);
expect(configs.auth).toEqual(undefined);
expect(configs.folder).toEqual("/tmp/test");
expect(configs.git).toEqual({
user: "GitHub",
email: "noreply@github.com"
});
});
}); });

View file

@ -7,6 +7,7 @@ import { getAxiosMocked } from "../../../support/mock/git-client-mock-support";
import { MERGED_SQUASHED_MR } from "../../../support/mock/gitlab-data"; import { MERGED_SQUASHED_MR } from "../../../support/mock/gitlab-data";
import GitLabClient from "@bp/service/git/gitlab/gitlab-client"; import GitLabClient from "@bp/service/git/gitlab/gitlab-client";
import GitLabMapper from "@bp/service/git/gitlab/gitlab-mapper"; import GitLabMapper from "@bp/service/git/gitlab/gitlab-mapper";
import { resetEnvTokens } from "../../../support/utils";
jest.spyOn(GitLabMapper.prototype, "mapPullRequest"); jest.spyOn(GitLabMapper.prototype, "mapPullRequest");
jest.spyOn(GitLabClient.prototype, "getPullRequest"); jest.spyOn(GitLabClient.prototype, "getPullRequest");
@ -31,6 +32,9 @@ describe("gitlab merge request config parser", () => {
}); });
beforeEach(() => { beforeEach(() => {
// reset env tokens
resetEnvTokens();
configParser = new PullRequestConfigsParser(); configParser = new PullRequestConfigsParser();
}); });

View file

@ -1,12 +1,12 @@
import { Args } from "@bp/service/args/args.types"; import { Args } from "@bp/service/args/args.types";
import { Configs } from "@bp/service/configs/configs.types"; import { AuthTokenId, Configs } from "@bp/service/configs/configs.types";
import PullRequestConfigsParser from "@bp/service/configs/pullrequest/pr-configs-parser"; import PullRequestConfigsParser from "@bp/service/configs/pullrequest/pr-configs-parser";
import GitClientFactory from "@bp/service/git/git-client-factory"; import GitClientFactory from "@bp/service/git/git-client-factory";
import { GitClientType } from "@bp/service/git/git.types"; import { GitClientType } from "@bp/service/git/git.types";
import { getAxiosMocked } from "../../../support/mock/git-client-mock-support"; import { getAxiosMocked } from "../../../support/mock/git-client-mock-support";
import { CLOSED_NOT_MERGED_MR, MERGED_SQUASHED_MR, OPEN_MR } from "../../../support/mock/gitlab-data"; import { CLOSED_NOT_MERGED_MR, MERGED_SQUASHED_MR, OPEN_MR } from "../../../support/mock/gitlab-data";
import GHAArgsParser from "@bp/service/args/gha/gha-args-parser"; import GHAArgsParser from "@bp/service/args/gha/gha-args-parser";
import { createTestFile, removeTestFile, spyGetInput } from "../../../support/utils"; import { createTestFile, removeTestFile, resetEnvTokens, spyGetInput } from "../../../support/utils";
import GitLabClient from "@bp/service/git/gitlab/gitlab-client"; import GitLabClient from "@bp/service/git/gitlab/gitlab-client";
import GitLabMapper from "@bp/service/git/gitlab/gitlab-mapper"; import GitLabMapper from "@bp/service/git/gitlab/gitlab-mapper";
@ -70,6 +70,9 @@ describe("gitlab merge request config parser", () => {
}); });
beforeEach(() => { beforeEach(() => {
// reset env tokens
resetEnvTokens();
argsParser = new GHAArgsParser(); argsParser = new GHAArgsParser();
configParser = new PullRequestConfigsParser(); configParser = new PullRequestConfigsParser();
}); });
@ -795,4 +798,97 @@ describe("gitlab merge request config parser", () => {
comments: ["First comment", "Second comment"], comments: ["First comment", "Second comment"],
}); });
}); });
test("override token using auth arg", async () => {
process.env[AuthTokenId.GITLAB_TOKEN] = "mygitlabtoken";
const args: Args = {
dryRun: true,
auth: "whatever",
pullRequest: mergedPRUrl,
targetBranch: "prod",
folder: "/tmp/test",
gitUser: "Gitlab",
gitEmail: "noreply@gitlab.com",
reviewers: [],
assignees: [],
inheritReviewers: true,
};
const configs: Configs = await configParser.parseAndValidate(args);
expect(GitLabClient.prototype.getPullRequest).toBeCalledTimes(1);
expect(GitLabClient.prototype.getPullRequest).toBeCalledWith("superuser", "backporting-example", 1, true);
expect(GitLabMapper.prototype.mapPullRequest).toBeCalledTimes(1);
expect(GitLabMapper.prototype.mapPullRequest).toBeCalledWith(expect.anything(), []);
expect(configs.dryRun).toEqual(true);
expect(configs.auth).toEqual("whatever");
expect(configs.folder).toEqual("/tmp/test");
expect(configs.git).toEqual({
user: "Gitlab",
email: "noreply@gitlab.com"
});
});
test("auth using GITLAB_TOKEN has precedence over GIT_TOKEN env variable", async () => {
process.env[AuthTokenId.GIT_TOKEN] = "mygittoken";
process.env[AuthTokenId.GITLAB_TOKEN] = "mygitlabtoken";
const args: Args = {
dryRun: true,
pullRequest: mergedPRUrl,
targetBranch: "prod",
folder: "/tmp/test",
gitUser: "Gitlab",
gitEmail: "noreply@gitlab.com",
reviewers: [],
assignees: [],
inheritReviewers: true,
};
const configs: Configs = await configParser.parseAndValidate(args);
expect(GitLabClient.prototype.getPullRequest).toBeCalledTimes(1);
expect(GitLabClient.prototype.getPullRequest).toBeCalledWith("superuser", "backporting-example", 1, true);
expect(GitLabMapper.prototype.mapPullRequest).toBeCalledTimes(1);
expect(GitLabMapper.prototype.mapPullRequest).toBeCalledWith(expect.anything(), []);
expect(configs.dryRun).toEqual(true);
expect(configs.auth).toEqual("mygitlabtoken");
expect(configs.folder).toEqual("/tmp/test");
expect(configs.git).toEqual({
user: "Gitlab",
email: "noreply@gitlab.com"
});
});
test("ignore env variables related to other git platforms", async () => {
process.env[AuthTokenId.CODEBERG_TOKEN] = "mycodebergtoken";
process.env[AuthTokenId.GITHUB_TOKEN] = "mygithubtoken";
const args: Args = {
dryRun: true,
pullRequest: mergedPRUrl,
targetBranch: "prod",
folder: "/tmp/test",
gitUser: "Gitlab",
gitEmail: "noreply@gitlab.com",
reviewers: [],
assignees: [],
inheritReviewers: true,
};
const configs: Configs = await configParser.parseAndValidate(args);
expect(GitLabClient.prototype.getPullRequest).toBeCalledTimes(1);
expect(GitLabClient.prototype.getPullRequest).toBeCalledWith("superuser", "backporting-example", 1, true);
expect(GitLabMapper.prototype.mapPullRequest).toBeCalledTimes(1);
expect(GitLabMapper.prototype.mapPullRequest).toBeCalledWith(expect.anything(), []);
expect(configs.dryRun).toEqual(true);
expect(configs.auth).toEqual(undefined);
expect(configs.folder).toEqual("/tmp/test");
expect(configs.git).toEqual({
user: "Gitlab",
email: "noreply@gitlab.com"
});
});
}); });

View file

@ -1,4 +1,5 @@
import * as core from "@actions/core"; import * as core from "@actions/core";
import { AuthTokenId } from "@bp/service/configs/configs.types";
import * as fs from "fs"; import * as fs from "fs";
export const addProcessArgs = (args: string[]) => { export const addProcessArgs = (args: string[]) => {
@ -9,6 +10,13 @@ export const resetProcessArgs = () => {
process.argv = ["node", "backporting"]; process.argv = ["node", "backporting"];
}; };
export const resetEnvTokens = () => {
delete process.env[AuthTokenId.GITHUB_TOKEN];
delete process.env[AuthTokenId.GITLAB_TOKEN];
delete process.env[AuthTokenId.CODEBERG_TOKEN];
delete process.env[AuthTokenId.GIT_TOKEN];
};
// eslint-disable-next-line @typescript-eslint/no-explicit-any // eslint-disable-next-line @typescript-eslint/no-explicit-any
export const spyGetInput = (obj: any) => { export const spyGetInput = (obj: any) => {
const mock = jest.spyOn(core, "getInput"); const mock = jest.spyOn(core, "getInput");