Consistently use job-full-name not name or slug
Signed-off-by: Andrew Shirley <andrew.shirley@sainsburys.co.uk> Signed-off-by: blam <ben@blam.sh>
This commit is contained in:
@@ -71,10 +71,10 @@ kind: Component
|
||||
metadata:
|
||||
name: artist-lookup
|
||||
annotations:
|
||||
'jenkins.io/job-slug': teamA/artistLookup-build
|
||||
'jenkins.io/job-full-name': teamA/artistLookup-build
|
||||
```
|
||||
|
||||
The old annotation name of `jenkins.io/github-folder` is equivalent to `jenkins.io/job-slug`
|
||||
The old annotation name of `jenkins.io/github-folder` is equivalent to `jenkins.io/job-full-name`
|
||||
|
||||
#### Example - Multiple global instances
|
||||
|
||||
@@ -103,7 +103,7 @@ kind: Component
|
||||
metadata:
|
||||
name: artist-lookup
|
||||
annotations:
|
||||
'jenkins.io/job-slug': departmentFoo:teamA/artistLookup-build
|
||||
'jenkins.io/job-full-name': departmentFoo:teamA/artistLookup-build
|
||||
```
|
||||
|
||||
If the `departmentFoo:` part is omitted, the default instance will be assumed.
|
||||
@@ -124,7 +124,7 @@ jenkins:
|
||||
|
||||
### Custom JenkinsInfoProvider
|
||||
|
||||
An example of a bespoke JenkinsInfoProvider which uses an organisation specific annotation to look up the Jenkins info (including jobName):
|
||||
An example of a bespoke JenkinsInfoProvider which uses an organisation specific annotation to look up the Jenkins info (including jobFullName):
|
||||
|
||||
```typescript
|
||||
class AcmeJenkinsInfoProvider implements JenkinsInfoProvider {
|
||||
@@ -132,7 +132,7 @@ class AcmeJenkinsInfoProvider implements JenkinsInfoProvider {
|
||||
|
||||
async getInstance(opt: {
|
||||
entityRef: EntityName;
|
||||
jobName?: string;
|
||||
jobFullName?: string;
|
||||
}): Promise<JenkinsInfo> {
|
||||
const PAAS_ANNOTATION = 'acme.example.com/paas-project-name';
|
||||
|
||||
@@ -157,7 +157,7 @@ class AcmeJenkinsInfoProvider implements JenkinsInfoProvider {
|
||||
const { team, dept } = this.lookupPaasInfo(paasProjectName);
|
||||
|
||||
const baseUrl = `https://jenkins-${dept}.example.com/`;
|
||||
const jobName = `${team}/${paasProjectName}`;
|
||||
const jobFullName = `${team}/${paasProjectName}`;
|
||||
const username = 'backstage-bot';
|
||||
const apiKey = this.getJenkinsApiKey(paasProjectName);
|
||||
const creds = btoa(`${username}:${apiKey}`);
|
||||
@@ -167,7 +167,7 @@ class AcmeJenkinsInfoProvider implements JenkinsInfoProvider {
|
||||
headers: {
|
||||
Authorization: `Basic ${creds}`,
|
||||
},
|
||||
jobName,
|
||||
jobFullName,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -205,3 +205,5 @@ The domain model for Jenkins is not particularly clear but for the purposes of t
|
||||
|
||||
Jenkins contains a tree of *job*s which have children of either; other *job*s (making it a _folder_) or *build*s (making it a _project_).
|
||||
Concepts like _pipeline_ and *view*s are meaningless (pipelines are just jobs for our purposes, views are (as the name suggests) just views of subsets of jobs)
|
||||
|
||||
A _job full name_ is a slash separated list of the names of the job, and the folders which contain it. For example `teamA/artistLookupService/develop`, and the same way that a filesystem path has folders and file names.
|
||||
|
||||
@@ -23,10 +23,10 @@ export class DefaultJenkinsInfoProvider implements JenkinsInfoProvider {
|
||||
// (undocumented)
|
||||
getInstance(opt: {
|
||||
entityRef: EntityName;
|
||||
jobName?: string;
|
||||
jobFullName?: string;
|
||||
}): Promise<JenkinsInfo>;
|
||||
// (undocumented)
|
||||
static readonly NEW_JENKINS_ANNOTATION = "jenkins.io/job-slug";
|
||||
static readonly NEW_JENKINS_ANNOTATION = "jenkins.io/job-full-name";
|
||||
// (undocumented)
|
||||
static readonly OLD_JENKINS_ANNOTATION = "jenkins.io/github-folder";
|
||||
}
|
||||
@@ -38,7 +38,7 @@ export interface JenkinsInfo {
|
||||
// (undocumented)
|
||||
headers?: Record<string, string | string[]>;
|
||||
// (undocumented)
|
||||
jobName: string;
|
||||
jobFullName: string;
|
||||
}
|
||||
|
||||
// @public (undocumented)
|
||||
@@ -46,7 +46,7 @@ export interface JenkinsInfoProvider {
|
||||
// (undocumented)
|
||||
getInstance(options: {
|
||||
entityRef: EntityName;
|
||||
jobName?: string;
|
||||
jobFullName?: string;
|
||||
}): Promise<JenkinsInfo>;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,12 +31,12 @@ const mockedJenkinsClient = {
|
||||
const mockedJenkins = jenkins as jest.Mocked<any>;
|
||||
mockedJenkins.mockReturnValue(mockedJenkinsClient);
|
||||
|
||||
const jobName = 'example-jobName/foo';
|
||||
const jobFullName = 'example-jobName/foo';
|
||||
const buildNumber = 19;
|
||||
const jenkinsInfo: JenkinsInfo = {
|
||||
baseUrl: 'https://jenkins.example.com',
|
||||
headers: { headerName: 'headerValue' },
|
||||
jobName: 'example-jobName',
|
||||
jobFullName: 'example-jobName',
|
||||
};
|
||||
|
||||
describe('JenkinsApi', () => {
|
||||
@@ -74,7 +74,7 @@ describe('JenkinsApi', () => {
|
||||
promisify: true,
|
||||
});
|
||||
expect(mockedJenkinsClient.job.get).toBeCalledWith({
|
||||
name: jenkinsInfo.jobName,
|
||||
name: jenkinsInfo.jobFullName,
|
||||
tree: expect.anything(),
|
||||
});
|
||||
expect(result).toHaveLength(1);
|
||||
@@ -117,7 +117,7 @@ describe('JenkinsApi', () => {
|
||||
promisify: true,
|
||||
});
|
||||
expect(mockedJenkinsClient.job.get).toBeCalledWith({
|
||||
name: `${jenkinsInfo.jobName}/testBranchName`,
|
||||
name: `${jenkinsInfo.jobFullName}/testBranchName`,
|
||||
tree: expect.anything(),
|
||||
});
|
||||
expect(result).toHaveLength(1);
|
||||
@@ -368,7 +368,7 @@ describe('JenkinsApi', () => {
|
||||
mockedJenkinsClient.job.get.mockResolvedValueOnce(project);
|
||||
mockedJenkinsClient.build.get.mockResolvedValueOnce(build);
|
||||
|
||||
await jenkinsApi.getBuild(jenkinsInfo, jobName, buildNumber);
|
||||
await jenkinsApi.getBuild(jenkinsInfo, jobFullName, buildNumber);
|
||||
|
||||
expect(mockedJenkins).toHaveBeenCalledWith({
|
||||
baseUrl: jenkinsInfo.baseUrl,
|
||||
@@ -376,19 +376,22 @@ describe('JenkinsApi', () => {
|
||||
promisify: true,
|
||||
});
|
||||
expect(mockedJenkinsClient.job.get).toBeCalledWith({
|
||||
name: jobName,
|
||||
name: jobFullName,
|
||||
depth: 1,
|
||||
});
|
||||
expect(mockedJenkinsClient.build.get).toBeCalledWith(jobName, buildNumber);
|
||||
expect(mockedJenkinsClient.build.get).toBeCalledWith(
|
||||
jobFullName,
|
||||
buildNumber,
|
||||
);
|
||||
});
|
||||
it('buildProject', async () => {
|
||||
await jenkinsApi.buildProject(jenkinsInfo, jobName);
|
||||
await jenkinsApi.buildProject(jenkinsInfo, jobFullName);
|
||||
|
||||
expect(mockedJenkins).toHaveBeenCalledWith({
|
||||
baseUrl: jenkinsInfo.baseUrl,
|
||||
headers: jenkinsInfo.headers,
|
||||
promisify: true,
|
||||
});
|
||||
expect(mockedJenkinsClient.job.build).toBeCalledWith(jobName);
|
||||
expect(mockedJenkinsClient.job.build).toBeCalledWith(jobFullName);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -67,18 +67,18 @@ export class JenkinsApiImpl {
|
||||
|
||||
if (branch) {
|
||||
// we have been asked to filter to a single branch.
|
||||
// Assume jenkinsInfo.jobName is a folder which contains one job per branch.
|
||||
// Assume jenkinsInfo.jobFullName is a folder which contains one job per branch.
|
||||
// TODO: extract a strategy interface for this
|
||||
const job = await client.job.get({
|
||||
name: `${jenkinsInfo.jobName}/${branch}`,
|
||||
name: `${jenkinsInfo.jobFullName}/${branch}`,
|
||||
tree: JenkinsApiImpl.jobTreeSpec.replace(/\s/g, ''),
|
||||
});
|
||||
projects.push(this.augmentProject(job));
|
||||
} else {
|
||||
// We aren't filtering
|
||||
// Assume jenkinsInfo.jobName is a folder which contains one job per branch.
|
||||
// Assume jenkinsInfo.jobFullName is a folder which contains one job per branch.
|
||||
const folder = await client.job.get({
|
||||
name: jenkinsInfo.jobName,
|
||||
name: jenkinsInfo.jobFullName,
|
||||
// Filter only be the information we need, instead of loading all fields.
|
||||
// Limit to only show the latest build for each job and only load 50 jobs
|
||||
// at all.
|
||||
@@ -107,17 +107,17 @@ export class JenkinsApiImpl {
|
||||
*/
|
||||
async getBuild(
|
||||
jenkinsInfo: JenkinsInfo,
|
||||
jobName: string,
|
||||
jobFullName: string,
|
||||
buildNumber: number,
|
||||
) {
|
||||
const client = await JenkinsApiImpl.getClient(jenkinsInfo);
|
||||
|
||||
const project = await client.job.get({
|
||||
name: jobName,
|
||||
name: jobFullName,
|
||||
depth: 1,
|
||||
});
|
||||
|
||||
const build = await client.build.get(jobName, buildNumber);
|
||||
const build = await client.build.get(jobFullName, buildNumber);
|
||||
const jobScmInfo = JenkinsApiImpl.extractScmDetailsFromJob(project);
|
||||
|
||||
return this.augmentBuild(build, jobScmInfo);
|
||||
@@ -127,7 +127,7 @@ export class JenkinsApiImpl {
|
||||
* Trigger a build of a project
|
||||
* @see ../../../jenkins/src/api/JenkinsApi.ts#retry
|
||||
*/
|
||||
async buildProject(jenkinsInfo: JenkinsInfo, jobName: string) {
|
||||
async buildProject(jenkinsInfo: JenkinsInfo, jobFullName: string) {
|
||||
const client = await JenkinsApiImpl.getClient(jenkinsInfo);
|
||||
|
||||
// looks like the current SDK only supports triggering a new build
|
||||
@@ -135,7 +135,7 @@ export class JenkinsApiImpl {
|
||||
|
||||
// Note Jenkins itself has concepts of rebuild and replay on a job.
|
||||
// The latter should be possible to trigger with a POST to /replay/rebuild
|
||||
await client.job.build(jobName);
|
||||
await client.job.build(jobFullName);
|
||||
}
|
||||
|
||||
// private helper methods
|
||||
|
||||
@@ -61,7 +61,7 @@ describe('DefaultJenkinsInfoProvider', () => {
|
||||
{
|
||||
metadata: {
|
||||
annotations: {
|
||||
'jenkins.io/job-slug': 'teamA/artistLookup-build',
|
||||
'jenkins.io/job-full-name': 'teamA/artistLookup-build',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -75,7 +75,7 @@ describe('DefaultJenkinsInfoProvider', () => {
|
||||
Authorization:
|
||||
'Basic YmFja3N0YWdlIC0gYm90OjEyMzQ1Njc4OWFiY2RlZjAxMjM0NTY3ODlhYmNlZGYwMTI=',
|
||||
},
|
||||
jobName: 'teamA/artistLookup-build',
|
||||
jobFullName: 'teamA/artistLookup-build',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -96,7 +96,7 @@ describe('DefaultJenkinsInfoProvider', () => {
|
||||
{
|
||||
metadata: {
|
||||
annotations: {
|
||||
'jenkins.io/job-slug': 'teamA/artistLookup-build',
|
||||
'jenkins.io/job-full-name': 'teamA/artistLookup-build',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -106,7 +106,7 @@ describe('DefaultJenkinsInfoProvider', () => {
|
||||
expect(mockCatalog.getEntityByName).toBeCalledWith(entityRef);
|
||||
expect(info).toMatchObject({
|
||||
baseUrl: 'https://jenkins.example.com',
|
||||
jobName: 'teamA/artistLookup-build',
|
||||
jobFullName: 'teamA/artistLookup-build',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -133,7 +133,7 @@ describe('DefaultJenkinsInfoProvider', () => {
|
||||
{
|
||||
metadata: {
|
||||
annotations: {
|
||||
'jenkins.io/job-slug': 'teamA/artistLookup-build',
|
||||
'jenkins.io/job-full-name': 'teamA/artistLookup-build',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -143,7 +143,7 @@ describe('DefaultJenkinsInfoProvider', () => {
|
||||
expect(mockCatalog.getEntityByName).toBeCalledWith(entityRef);
|
||||
expect(info).toMatchObject({
|
||||
baseUrl: 'https://jenkins.example.com',
|
||||
jobName: 'teamA/artistLookup-build',
|
||||
jobFullName: 'teamA/artistLookup-build',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -170,7 +170,7 @@ describe('DefaultJenkinsInfoProvider', () => {
|
||||
{
|
||||
metadata: {
|
||||
annotations: {
|
||||
'jenkins.io/job-slug': 'other:teamA/artistLookup-build',
|
||||
'jenkins.io/job-full-name': 'other:teamA/artistLookup-build',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -180,7 +180,7 @@ describe('DefaultJenkinsInfoProvider', () => {
|
||||
expect(mockCatalog.getEntityByName).toBeCalledWith(entityRef);
|
||||
expect(info).toMatchObject({
|
||||
baseUrl: 'https://jenkins-other.example.com',
|
||||
jobName: 'teamA/artistLookup-build',
|
||||
jobFullName: 'teamA/artistLookup-build',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -196,7 +196,7 @@ describe('DefaultJenkinsInfoProvider', () => {
|
||||
{
|
||||
metadata: {
|
||||
annotations: {
|
||||
'jenkins.io/job-slug': 'default:teamA/artistLookup-build',
|
||||
'jenkins.io/job-full-name': 'default:teamA/artistLookup-build',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -206,7 +206,7 @@ describe('DefaultJenkinsInfoProvider', () => {
|
||||
expect(mockCatalog.getEntityByName).toBeCalledWith(entityRef);
|
||||
expect(info).toMatchObject({
|
||||
baseUrl: 'https://jenkins.example.com',
|
||||
jobName: 'teamA/artistLookup-build',
|
||||
jobFullName: 'teamA/artistLookup-build',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -232,7 +232,7 @@ describe('DefaultJenkinsInfoProvider', () => {
|
||||
expect(mockCatalog.getEntityByName).toBeCalledWith(entityRef);
|
||||
expect(info).toMatchObject({
|
||||
baseUrl: 'https://jenkins.example.com',
|
||||
jobName: 'teamA/artistLookup-build',
|
||||
jobFullName: 'teamA/artistLookup-build',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -253,7 +253,7 @@ describe('DefaultJenkinsInfoProvider', () => {
|
||||
{
|
||||
metadata: {
|
||||
annotations: {
|
||||
'jenkins.io/job-slug': 'other:teamA/artistLookup-build',
|
||||
'jenkins.io/job-full-name': 'other:teamA/artistLookup-build',
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -263,7 +263,7 @@ describe('DefaultJenkinsInfoProvider', () => {
|
||||
expect(mockCatalog.getEntityByName).toBeCalledWith(entityRef);
|
||||
expect(info).toMatchObject({
|
||||
baseUrl: 'https://jenkins-other.example.com',
|
||||
jobName: 'teamA/artistLookup-build',
|
||||
jobFullName: 'teamA/artistLookup-build',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -30,14 +30,14 @@ export interface JenkinsInfoProvider {
|
||||
/**
|
||||
* A specific job to get. This is only passed in when we know about a job name we are interested in.
|
||||
*/
|
||||
jobName?: string;
|
||||
jobFullName?: string;
|
||||
}): Promise<JenkinsInfo>;
|
||||
}
|
||||
|
||||
export interface JenkinsInfo {
|
||||
baseUrl: string;
|
||||
headers?: Record<string, string | string[]>;
|
||||
jobName: string; // TODO: make this an array
|
||||
jobFullName: string; // TODO: make this an array
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,7 +47,7 @@ export interface JenkinsInfo {
|
||||
*/
|
||||
export class DefaultJenkinsInfoProvider implements JenkinsInfoProvider {
|
||||
static readonly OLD_JENKINS_ANNOTATION = 'jenkins.io/github-folder';
|
||||
static readonly NEW_JENKINS_ANNOTATION = 'jenkins.io/job-slug';
|
||||
static readonly NEW_JENKINS_ANNOTATION = 'jenkins.io/job-full-name';
|
||||
|
||||
private constructor(
|
||||
private readonly config: {
|
||||
@@ -71,7 +71,7 @@ export class DefaultJenkinsInfoProvider implements JenkinsInfoProvider {
|
||||
|
||||
async getInstance(opt: {
|
||||
entityRef: EntityName;
|
||||
jobName?: string;
|
||||
jobFullName?: string;
|
||||
}): Promise<JenkinsInfo> {
|
||||
// load entity
|
||||
const entity = await this.catalog.getEntityByName(opt.entityRef);
|
||||
@@ -81,7 +81,7 @@ export class DefaultJenkinsInfoProvider implements JenkinsInfoProvider {
|
||||
);
|
||||
}
|
||||
|
||||
// lookup `[jenkinsName#]jobName` from entity annotation
|
||||
// lookup `[jenkinsName#]jobFullName` from entity annotation
|
||||
const jenkinsAndJobName = DefaultJenkinsInfoProvider.getEntityAnnotationValue(
|
||||
entity,
|
||||
);
|
||||
@@ -93,16 +93,16 @@ export class DefaultJenkinsInfoProvider implements JenkinsInfoProvider {
|
||||
);
|
||||
}
|
||||
|
||||
let jobName;
|
||||
let jobFullName;
|
||||
let jenkinsName: string | undefined;
|
||||
const splitIndex = jenkinsAndJobName.indexOf(':');
|
||||
if (splitIndex === -1) {
|
||||
// no jenkinsName specified, use default
|
||||
jobName = jenkinsAndJobName;
|
||||
jobFullName = jenkinsAndJobName;
|
||||
} else {
|
||||
// There is a jenkinsName specified
|
||||
jenkinsName = jenkinsAndJobName.substring(0, splitIndex);
|
||||
jobName = jenkinsAndJobName.substring(
|
||||
jobFullName = jenkinsAndJobName.substring(
|
||||
splitIndex + 1,
|
||||
jenkinsAndJobName.length,
|
||||
);
|
||||
@@ -124,7 +124,7 @@ export class DefaultJenkinsInfoProvider implements JenkinsInfoProvider {
|
||||
headers: {
|
||||
Authorization: `Basic ${creds}`,
|
||||
},
|
||||
jobName,
|
||||
jobFullName,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -73,9 +73,15 @@ export async function createRouter(
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/v1/entity/:namespace/:kind/:name/job/:jobName/:buildNumber',
|
||||
'/v1/entity/:namespace/:kind/:name/job/:jobFullName/:buildNumber',
|
||||
async (request, response) => {
|
||||
const { namespace, kind, name, jobName, buildNumber } = request.params;
|
||||
const {
|
||||
namespace,
|
||||
kind,
|
||||
name,
|
||||
jobFullName,
|
||||
buildNumber,
|
||||
} = request.params;
|
||||
|
||||
const jenkinsInfo = await jenkinsInfoProvider.getInstance({
|
||||
entityRef: {
|
||||
@@ -83,12 +89,12 @@ export async function createRouter(
|
||||
namespace,
|
||||
name,
|
||||
},
|
||||
jobName,
|
||||
jobFullName,
|
||||
});
|
||||
|
||||
const build = await jenkinsApi.getBuild(
|
||||
jenkinsInfo,
|
||||
jobName,
|
||||
jobFullName,
|
||||
parseInt(buildNumber, 10),
|
||||
);
|
||||
|
||||
@@ -99,9 +105,9 @@ export async function createRouter(
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/v1/entity/:namespace/:kind/:name/job/:jobName/:buildNumber::rebuild',
|
||||
'/v1/entity/:namespace/:kind/:name/job/:jobFullName/:buildNumber::rebuild',
|
||||
async (request, response) => {
|
||||
const { namespace, kind, name, jobName } = request.params;
|
||||
const { namespace, kind, name, jobFullName } = request.params;
|
||||
|
||||
const jenkinsInfo = await jenkinsInfoProvider.getInstance({
|
||||
entityRef: {
|
||||
@@ -109,10 +115,10 @@ export async function createRouter(
|
||||
namespace,
|
||||
name,
|
||||
},
|
||||
jobName,
|
||||
jobFullName,
|
||||
});
|
||||
|
||||
await jenkinsApi.buildProject(jenkinsInfo, jobName);
|
||||
await jenkinsApi.buildProject(jenkinsInfo, jobFullName);
|
||||
response.json({});
|
||||
},
|
||||
);
|
||||
|
||||
@@ -36,7 +36,7 @@ export async function startStandaloneServer(
|
||||
logger,
|
||||
jenkinsInfoProvider: {
|
||||
async getInstance(_: { entityRef: EntityRef }): Promise<JenkinsInfo> {
|
||||
return { baseUrl: 'https://example.com/', jobName: 'build-foo' };
|
||||
return { baseUrl: 'https://example.com/', jobFullName: 'build-foo' };
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user