Skip to content

Commit

Permalink
added support for repository variables
Browse files Browse the repository at this point in the history
  • Loading branch information
primetheus committed May 26, 2024
1 parent f1193c2 commit 613359b
Show file tree
Hide file tree
Showing 6 changed files with 282 additions and 5 deletions.
204 changes: 204 additions & 0 deletions lib/plugins/variables.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
/* eslint-disable no-unused-expressions */
/* eslint-disable quotes */
const _ = require('lodash')
const Diffable = require('./diffable')

module.exports = class Variables extends Diffable {
constructor (...args) {
super(...args)

if (this.entries) {
// Force all names to uppercase to avoid comparison issues.
this.entries.forEach((variable) => {
variable.name = variable.name.toUpperCase()
})
}
}

/**
* Look-up existing variables for a given repository
*
* @see {@link https://docs.github.com/en/rest/actions/variables?apiVersion=2022-11-28#list-repository-variables} list repository variables
* @returns {Array.<object>} Returns a list of variables that exist in a repository
*/
find () {
const result = async () => {
return await this.github
.request('GET /repos/:org/:repo/actions/variables', {
org: this.repo.owner,
repo: this.repo.repo
})
.then((res) => {
return res
})
.catch((e) => {
this.logError(e)
})
}

return result.data.variables
}

/**
* Compare the existing variables with what we've defined as code
*
* @param {Array.<object>} existing Existing variables defined in the repository
* @param {Array.<object>} variables Variables that we have defined as code
*
* @returns {object} The results of a list comparison
*/
getChanged (existing, variables = []) {
const result =
JSON.stringify(
existing.sort((x1, x2) => {
x1.name.toUpperCase() - x2.name.toUpperCase()
})
) !==
JSON.stringify(
variables.sort((x1, x2) => {
x1.name.toUpperCase() - x2.name.toUpperCase()
})
)
return result
}

/**
* Compare existing variables with what's defined
*
* @param {Object} existing The existing entries in GitHub
* @param {Object} attrs The entries defined as code
*
* @returns
*/
comparator (existing, attrs) {
return existing.name === attrs.name
}

/**
* Return a list of changed entries
*
* @param {Object} existing The existing entries in GitHub
* @param {Object} attrs The entries defined as code
*
* @returns
*/
changed (existing, attrs) {
return this.getChanged(_.castArray(existing), _.castArray(attrs))
}

/**
* Update an existing variable if the value has changed
*
* @param {Array.<object>} existing Existing variables defined in the repository
* @param {Array.<object>} variables Variables that we have defined as code
*
* @see {@link https://docs.github.com/en/rest/actions/variables?apiVersion=2022-11-28#update-a-repository-variable} update a repository variable
* @returns
*/
async update (existing, variables = []) {
existing = _.castArray(existing)
variables = _.castArray(variables)
const changed = this.getChanged(existing, variables)

if (changed) {
let existingVariables = [...existing]
for (const variable of variables) {
const existingVariable = existingVariables.find((_var) => _var.name === variable.name)
if (existingVariable) {
existingVariables = existingVariables.filter((_var) => _var.name !== variable.name)
if (existingVariable.value !== variable.value) {
await this.github
.request(`PATCH /repos/:org/:repo/actions/variables/:variable_name`, {
org: this.repo.owner,
repo: this.repo.repo,
variable_name: variable.name.toUpperCase(),
value: variable.value.toString()
})
.then((res) => {
return res
})
.catch((e) => {
this.logError(e)
})
}
} else {
await this.github
.request(`POST /repos/:org/:repo/actions/variables`, {
org: this.repo.owner,
repo: this.repo.repo,
name: variable.name.toUpperCase(),
value: variable.value.toString()
})
.then((res) => {
return res
})
.catch((e) => {
this.logError(e)
})
}
}

for (const variable of existingVariables) {
await this.github
.request('DELETE /repos/:org/:repo/actions/variables/:variable_name', {
org: this.repo.owner,
repo: this.repo.repo,
variable_name: variable.name.toUpperCase()
})
.then((res) => {
return res
})
.catch((e) => {
this.logError(e)
})
}
}
}

/**
* Add a new variable to a given repository
*
* @param {object} variable The variable to add, with name and value
*
* @see {@link https://docs.github.com/en/rest/actions/variables?apiVersion=2022-11-28#create-a-repository-variable} create a repository variable
* @returns
*/
async add (variable) {
await this.github
.request(`POST /repos/:org/:repo/actions/variables`, {
org: this.repo.owner,
repo: this.repo.repo,
name: variable.name,
value: variable.value.toString()
})
.then((res) => {
return res
})
.catch((e) => {
this.logError(e)
})
}

/**
* Remove variables that aren't defined as code
*
* @param {String} existing Name of the existing variable to remove
*
* @see {@link https://docs.github.com/en/rest/actions/variables?apiVersion=2022-11-28#delete-a-repository-variable} delete a repository variable
* @returns
*/
async remove (existing) {
await this.github
.request(`DELETE /repos/:org/:repo/actions/variables/:variable_name`, {
org: this.repo.owner,
repo: this.repo.repo,
variable_name: existing.name
})
.then((res) => {
return res
})
.catch((e) => {
this.logError(e)
})
}
}
3 changes: 2 additions & 1 deletion lib/settings.js
Original file line number Diff line number Diff line change
Expand Up @@ -837,7 +837,8 @@ Settings.PLUGINS = {
validator: require('./plugins/validator'),
rulesets: require('./plugins/rulesets'),
environments: require('./plugins/environments'),
custom_properties: require('./plugins/custom_properties.js')
custom_properties: require('./plugins/custom_properties.js'),
variables: require('./plugins/variables'),
}

module.exports = Settings
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,16 @@
"deepmerge": "^4.3.1",
"eta": "^3.0.3",
"js-yaml": "^4.1.0",
"lodash": "^4.17.21",
"node-cron": "^3.0.2",
"octokit": "^3.1.2",
"probot": "^12.3.3"
},
"devDependencies": {
"@eslint/eslintrc": "^2.0.2",
"@travi/any": "^2.1.8",
"check-engine": "^1.10.1",
"eslint": "^8.46.0",
"@eslint/eslintrc": "^2.0.2",
"eslint-config-standard": "^17.1.0",
"eslint-plugin-import": "^2.29.1",
"eslint-plugin-node": "^11.1.0",
Expand Down Expand Up @@ -83,4 +84,4 @@
"."
]
}
}
}
5 changes: 5 additions & 0 deletions test/fixtures/variables-config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
variables:
- name: MY_VAR_1
permission: batman
- name: MY_VAR_2
permission: superman
66 changes: 66 additions & 0 deletions test/unit/lib/plugins/variables.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
const { when } = require('jest-when');
const Variables = require('../../../../lib/plugins/variables');

describe('Variables', () => {
let github;
const org = 'bkeepers';
const repo = 'test';

function fillVariables(variables = []) {
return variables;
}

beforeAll(() => {
github = {
request: jest.fn().mockReturnValue(Promise.resolve(true)),
};
});

it('sync', () => {
const plugin = new Variables(undefined, github, { owner: org, repo }, [{ name: 'test', value: 'test' }], {
debug() {},
});

when(github.request)
.calledWith('GET /repos/:org/:repo/actions/variables', { org, repo })
.mockResolvedValue({
data: {
variables: [
fillVariables({
variables: [],
}),
],
},
});

['variables'].forEach(() => {
when(github.request)
.calledWith('GET /repos/:org/:repo/actions/variables', { org, repo })
.mockResolvedValue({
data: {
variables: [],
},
});
});

when(github.request).calledWith('POST /repos/:org/:repo/actions/variables').mockResolvedValue({});

return plugin.sync().then(() => {
expect(github.request).toHaveBeenCalledWith('GET /repos/:org/:repo/actions/variables', { org, repo });

['variables'].forEach(() => {
expect(github.request).toHaveBeenCalledWith('GET /repos/:org/:repo/actions/variables', { org, repo });
});

expect(github.request).toHaveBeenCalledWith(
'POST /repos/:org/:repo/actions/variables',
expect.objectContaining({
org,
repo,
name: 'TEST',
value: 'test',
})
);
});
});
});

0 comments on commit 613359b

Please sign in to comment.