Skip to content

Commit c97dece

Browse files
committed
feat: adding support for github api
1 parent 26b63e8 commit c97dece

4 files changed

Lines changed: 207 additions & 95 deletions

File tree

bin.js

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const ENVIRONMENT_VARIABLES = [
2626
{name: 'GITOPS_BASE_URL', default: 'https://github.com', varialbe: 'url', required: true},
2727
{name: 'GITOPS_AT', default: null, varialbe: 'access_token', required: true},
2828
{name: 'GITOPS_REPO', default: null, varialbe: 'repo', required: true},
29-
{name: 'GITOPS_BRANCH', varialbe: 'branch'},
29+
{name: 'GITOPS_BRANCH', varialbe: 'branch', default: 'master'},
3030
{name: 'GITOPS_APPLICATIONS_DIR', default: 'applications', varialbe: 'applications_dir', required: true},
3131
{name: 'GITOPS_VALUES_FILE', default: 'values.yaml', varialbe: 'values_file', required: true},
3232
]
@@ -39,16 +39,22 @@ parser.add_argument('action', {metavar: 'action', type: String, nargs: '?', help
3939
parser.add_argument('parameters', {metavar: 'parameters', type: String, nargs: '*', help: 'action parameters to be used'});
4040
parser.add_argument('-v', '--version', { action: 'version', version });
4141
parser.add_argument('-t', '--access-token', {type: String, help: 'access token with API permissions'});
42-
parser.add_argument('-u', '--url', {type: String, default: 'https://github.com', help: 'git system base url (e.g. https://github.com)'})
42+
parser.add_argument('-u', '--url', {type: String, help: 'git system base url (e.g. https://github.com)'})
4343
parser.add_argument('--verbose', {action: 'store_true', help: 'increased console output'})
44-
parser.add_argument('--branch', {metavar: 'branch', type: String, default: 'master', help: 'gitops branch to use'})
44+
parser.add_argument('--branch', {metavar: 'branch', type: String, help: 'gitops branch to use'})
4545
parser.add_argument('--repo', {metavar: 'repo', type: String, help: 'gitops repo to use'})
46-
parser.add_argument('--applications-dir', {type: String, default: 'applications', help: 'applications directory in gitops repo'})
47-
parser.add_argument('--values-file', {type: String, default: 'values.yaml', help: 'values file to patch'})
46+
parser.add_argument('--applications-dir', {type: String, help: 'applications directory in gitops repo'})
47+
parser.add_argument('--values-file', {type: String, help: 'values file to patch'})
4848

4949
const args = parser.parse_args()
5050

5151
for(const variable of ENVIRONMENT_VARIABLES) {
52+
if(!process.env[variable.name]) {
53+
if(variable.default) {
54+
process.env[variable.name] = variable.default;
55+
}
56+
}
57+
5258
if(process.env[variable.name] && !args[variable.varialbe]) {
5359
args[variable.varialbe] = process.env[variable.name]
5460
}

index.js

Lines changed: 143 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,12 @@
1-
import {apiTest, getApiDriver} from 'gitlab-x';
2-
import { load as loadYml, dump as dumpYml } from 'js-yaml';
1+
import {
2+
apiTest as gitlabApiTest,
3+
getApiDriver as getGitlabApiDriver,
4+
} from "gitlab-x";
5+
import {
6+
apiTest as githubApiTest,
7+
getApiDriver as getGithubApiDriver,
8+
} from "github-x";
9+
import { load as loadYml, dump as dumpYml } from "js-yaml";
310

411
/*
512
gitops patch function
@@ -19,105 +26,140 @@ required options:
1926
export async function patch(options) {
2027
const verbose = options.verbose;
2128

22-
if(verbose) console.log(`patch options: ${JSON.stringify(options, null, 2)}`);
29+
if (verbose)
30+
console.log(`patch options: ${JSON.stringify(options, null, 2)}`);
2331

24-
if(!(options.values_file.endsWith('.yml') || options.values_file.endsWith('.yaml'))) {
32+
if (
33+
!(
34+
options.values_file.endsWith(".yml") ||
35+
options.values_file.endsWith(".yaml")
36+
)
37+
) {
2538
throw new Error(`values_file must be a *.yml or *.yaml file`);
2639
}
2740

2841
let gitProvider = await getGitProvider(options);
29-
if(verbose) console.log(`Git Provider: ${gitProvider}`);
42+
if (verbose) console.log(`Git Provider: ${gitProvider}`);
3043
let apiDriver;
31-
if(gitProvider === 'gitlab') {
32-
apiDriver = getApiDriver(options);
44+
if (gitProvider === "gitlab") {
45+
apiDriver = getGitlabApiDriver(options);
46+
} else if (gitProvider === "github") {
47+
apiDriver = getGithubApiDriver(options);
48+
} else {
49+
throw new Error(`Unsupported git provider: ${gitProvider}`);
3350
}
3451

35-
if(!await apiDriver.getVersion()) {
52+
if (!(await apiDriver.getVersion())) {
3653
throw new Error(`API check failed`);
3754
}
3855

3956
const projectIdentifier = `/${stripSlashes(options.repo)}`;
40-
41-
let filePath = `/${stripSlashes(options.applications_dir)}/${stripSlashes(options.application)}/${stripSlashes(options.values_file)}`;
42-
if(verbose) console.log(`file path: ${filePath}`);
43-
const fileExists = await apiDriver.fileExists(projectIdentifier, filePath, options.branch);
44-
45-
if(!fileExists) {
46-
if(verbose) console.log(`file '${filePath}' does not exist in branch '${options.branch}'`);
47-
filePath = `/${stripSlashes(options.applications_dir)}/${stripSlashes(options.application)}/${stripSlashes(toggleYamlFileExtension(options.values_file))}`;
48-
if(verbose) console.log(`trying opposite yaml file extension '${filePath}'`);
49-
const toggledYamlFileExists = await apiDriver.fileExists(projectIdentifier, filePath, options.branch);
50-
if(!toggledYamlFileExists) {
57+
58+
let filePath = `/${stripSlashes(options.applications_dir)}/${stripSlashes(
59+
options.application
60+
)}/${stripSlashes(options.values_file)}`;
61+
if (verbose) console.log(`file path: ${filePath}`);
62+
const fileExists = await apiDriver.fileExists(
63+
projectIdentifier,
64+
filePath,
65+
options.branch
66+
);
67+
68+
if (!fileExists) {
69+
if (verbose)
70+
console.log(
71+
`file '${filePath}' does not exist in branch '${options.branch}'`
72+
);
73+
filePath = `/${stripSlashes(options.applications_dir)}/${stripSlashes(
74+
options.application
75+
)}/${stripSlashes(toggleYamlFileExtension(options.values_file))}`;
76+
if (verbose)
77+
console.log(`trying opposite yaml file extension '${filePath}'`);
78+
const toggledYamlFileExists = await apiDriver.fileExists(
79+
projectIdentifier,
80+
filePath,
81+
options.branch
82+
);
83+
if (!toggledYamlFileExists) {
5184
throw new Error(`file '${filePath}' does not exist`);
5285
}
5386
}
54-
55-
if(verbose) console.log(`file '${filePath}' exists`);
56-
57-
const fileContent = await apiDriver.getRawFile(projectIdentifier, filePath, options.branch);
58-
if(verbose) console.log(`file contents: \n\n----\n${fileContent}\n----\n\n`);
59-
87+
88+
if (verbose) console.log(`file '${filePath}' exists`);
89+
90+
const fileContent = await apiDriver.getRawFile(
91+
projectIdentifier,
92+
filePath,
93+
options.branch
94+
);
95+
if (verbose) console.log(`file contents: \n\n----\n${fileContent}\n----\n\n`);
96+
6097
const yml = loadYml(fileContent);
6198
let patchFields = [];
62-
if(options.patch_field.startsWith('.')) {
63-
if(verbose) console.log(`patch field is assumed to be a yaml path`);
99+
if (options.patch_field.startsWith(".")) {
100+
if (verbose) console.log(`patch field is assumed to be a yaml path`);
64101
patchFields = [options.patch_field];
65-
}
66-
else {
67-
if(verbose) console.log(`patch field is assumed to be a field name`);
102+
} else {
103+
if (verbose) console.log(`patch field is assumed to be a field name`);
68104
patchFields = findYmlField(yml, options.patch_field);
69-
if(verbose) console.log(`found patch fields: ${patchFields}`);
105+
if (verbose) console.log(`found patch fields: ${patchFields}`);
70106
}
71107

72108
let changes = false;
73-
for(const patchField of patchFields) {
74-
if(!existsYmlField(yml, patchField)) {
75-
throw new Error(`field '${patchField}' does not exist in file '${filePath}'`);
109+
for (const patchField of patchFields) {
110+
if (!existsYmlField(yml, patchField)) {
111+
throw new Error(
112+
`field '${patchField}' does not exist in file '${filePath}'`
113+
);
76114
}
77-
78-
if(verbose) console.log(`patching field '${patchField}'`);
115+
116+
if (verbose) console.log(`patching field '${patchField}'`);
79117
const patchValue = getYmlFieldValue(yml, patchField);
80-
if(verbose) console.log(`old value: ${patchValue}`);
118+
if (verbose) console.log(`old value: ${patchValue}`);
81119
const newValue = options.patch_value;
82-
if(verbose) console.log(`new value: ${newValue}`);
83-
if(patchValue !== newValue) {
120+
if (verbose) console.log(`new value: ${newValue}`);
121+
if (patchValue !== newValue) {
84122
changes = true;
85123
setYmlFieldValue(yml, patchField, newValue);
86124
}
87125
}
88126

89-
if(!changes) {
127+
if (!changes) {
90128
console.log(`no changes to commit`);
91129
return;
92130
}
93131

94132
const ymlDump = dumpYml(yml);
95133

96-
if(verbose) console.log(`new file contents: \n\n----\n${ymlDump}\n----\n\n`);
134+
if (verbose) console.log(`new file contents: \n\n----\n${ymlDump}\n----\n\n`);
97135

98-
if(verbose) console.log('creating commit')
136+
if (verbose) console.log("creating commit");
99137
let targetBranch = options.branch;
100-
if (typeof ref === 'undefined') {
138+
if (typeof ref === "undefined") {
101139
// TODO add to defaultBranch function to gitlab-x
102-
const defaultBranch = (await apiDriver.getProject(projectIdentifier)).default_branch;
103-
if (verbose) console.log(`'ref' is not specified. Using default branch '${defaultBranch}'`);
140+
const defaultBranch = (await apiDriver.getProject(projectIdentifier))
141+
.default_branch;
142+
if (verbose)
143+
console.log(
144+
`'ref' is not specified. Using default branch '${defaultBranch}'`
145+
);
104146
targetBranch = defaultBranch;
105147
}
106148
let commitObject = {
107-
"branch": targetBranch,
149+
branch: targetBranch,
108150
// TODO add original author to commit message
109-
"commit_message": options.message || `Patched '${filePath}'`,
110-
"actions": [
151+
commit_message: options.message || `Patched '${filePath}'`,
152+
actions: [
111153
{
112-
"action": "update",
113-
"file_path": filePath,
114-
"content": ymlDump,
115-
"encoding": "text"
116-
}
117-
]
154+
action: "update",
155+
file_path: filePath,
156+
content: ymlDump,
157+
encoding: "text",
158+
},
159+
],
118160
};
119161
await apiDriver.postCommit(projectIdentifier, commitObject);
120-
if(verbose) console.log('commit done');
162+
if (verbose) console.log("commit done");
121163
return;
122164
}
123165

@@ -131,52 +173,66 @@ returns
131173
- 'gitlab' if the base url points to a gitlab url with activated api
132174
*/
133175
async function getGitProvider(options) {
134-
if(options.verbose) console.log("checking for gitlab api")
135-
const gitlabApiResult = await apiTest(options);
136-
if(gitlabApiResult) {
137-
if(options.verbose) console.log("found gitlab api");
138-
return 'gitlab';
176+
if (options.verbose) console.log("checking for gitlab api");
177+
try {
178+
const gitlabApiResult = await gitlabApiTest(options);
179+
if (gitlabApiResult) {
180+
if (options.verbose) console.log("found gitlab api");
181+
return "gitlab";
182+
}
183+
} catch (e) {
184+
// error is expected if the gitlab api is not present
139185
}
140-
141-
if(options.verbose) console.log("checking for github api");
142-
throw new Error("GitHub API not implemented yet");
143-
// TODO
186+
187+
try {
188+
if (options.verbose) console.log("checking for github api");
189+
const githubApiResult = await githubApiTest(options);
190+
if (githubApiResult) {
191+
if (options.verbose) console.log("found github api");
192+
return "github";
193+
}
194+
} catch (e) {
195+
// error is expected if the github api is not present
196+
}
197+
return "";
144198
}
145199

146200
// strips tailing and leading slashes from a string
147201
function stripSlashes(str) {
148-
return str.replace(/^\/|\/$/g, '');
202+
return str.replace(/^\/|\/$/g, "");
149203
}
150204

151205
function toggleYamlFileExtension(fileName) {
152-
if(fileName.endsWith('.yml')) {
153-
return fileName.replace('.yml', '.yaml');
154-
} else if(fileName.endsWith('.yaml')) {
155-
return fileName.replace('.yaml', '.yml');
206+
if (fileName.endsWith(".yml")) {
207+
return fileName.replace(".yml", ".yaml");
208+
} else if (fileName.endsWith(".yaml")) {
209+
return fileName.replace(".yaml", ".yml");
156210
} else {
157211
return fileName;
158212
}
159213
}
160214

161-
function findYmlField(yml, fieldName, subPath = '') {
215+
function findYmlField(yml, fieldName, subPath = "") {
162216
let occurences = [];
163-
for(let key in yml) {
164-
if(key === fieldName) {
217+
for (let key in yml) {
218+
if (key === fieldName) {
165219
occurences.push(`${subPath}.${key}`);
166-
} else if(typeof yml[key] === 'object') {
167-
occurences = occurences.concat(findYmlField(yml[key], fieldName, `${subPath}.${key}`));
220+
} else if (typeof yml[key] === "object") {
221+
occurences = occurences.concat(
222+
findYmlField(yml[key], fieldName, `${subPath}.${key}`)
223+
);
168224
}
169225
}
170226
return occurences;
171227
}
172228

173229
function existsYmlField(yml, fieldPath) {
174-
const fieldPathParts = fieldPath.split('.');
230+
const fieldPathParts = fieldPath.split(".");
175231
let currentYml = yml;
176-
for(let i = 0; i < fieldPathParts.length; i++) {
232+
for (let i = 0; i < fieldPathParts.length; i++) {
177233
const fieldPathPart = fieldPathParts[i];
178-
if(fieldPathPart === '') continue;
179-
if(!currentYml[fieldPathPart]) {
234+
if (fieldPathPart === "") continue;
235+
if (!currentYml[fieldPathPart]) {
180236
return false;
181237
}
182238
currentYml = currentYml[fieldPathPart];
@@ -185,30 +241,30 @@ function existsYmlField(yml, fieldPath) {
185241
}
186242

187243
function getYmlFieldValue(yml, fieldPath) {
188-
const fieldPathParts = fieldPath.split('.');
244+
const fieldPathParts = fieldPath.split(".");
189245
let currentYml = yml;
190-
for(let i = 0; i < fieldPathParts.length; i++) {
246+
for (let i = 0; i < fieldPathParts.length; i++) {
191247
const fieldPathPart = fieldPathParts[i];
192-
if(fieldPathPart === '') continue;
248+
if (fieldPathPart === "") continue;
193249
currentYml = currentYml[fieldPathPart];
194250
}
195251
return currentYml;
196252
}
197253

198254
function setYmlFieldValue(yml, fieldPath, value) {
199-
const fieldPathParts = fieldPath.split('.');
255+
const fieldPathParts = fieldPath.split(".");
200256
let currentYml = yml;
201-
for(let i = 0; i < fieldPathParts.length; i++) {
257+
for (let i = 0; i < fieldPathParts.length; i++) {
202258
const fieldPathPart = fieldPathParts[i];
203-
if(fieldPathPart === '') continue;
204-
if(i === fieldPathParts.length - 1) {
259+
if (fieldPathPart === "") continue;
260+
if (i === fieldPathParts.length - 1) {
205261
currentYml[fieldPathPart] = value;
206262
} else {
207-
if(!currentYml[fieldPathPart]) {
263+
if (!currentYml[fieldPathPart]) {
208264
currentYml[fieldPathPart] = {};
209265
}
210266
currentYml = currentYml[fieldPathPart];
211267
}
212268
}
213269
return yml;
214-
}
270+
}

0 commit comments

Comments
 (0)