Skip to content

Commit

Permalink
feat(gitlab): support GitLab CI/CD component references
Browse files Browse the repository at this point in the history
  • Loading branch information
fgreinacher committed Jan 16, 2024
1 parent 8ea6078 commit 7458246
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 0 deletions.
@@ -1,5 +1,42 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`modules/manager/gitlabci-include/extract extractPackageFile() extracts component references 1`] = `
[
{
"datasource": "gitlab-tags",
"depName": "an-org/a-project",
"depType": "repository",
"registryUrls": [
"gitlab.example.com",
],
},
{
"datasource": "gitlab-tags",
"depName": "an-org/a-subgroup/a-project",
"depType": "repository",
"registryUrls": [
"gitlab.example.com",
],
},
{
"datasource": "gitlab-tags",
"depName": "an-org/a-subgroup/another-project",
"depType": "repository",
"registryUrls": [
"gitlab.example.com",
],
},
{
"datasource": "gitlab-tags",
"depName": "another-org/a-project",
"depType": "repository",
"registryUrls": [
"gitlab.example.com",
],
},
]
`;

exports[`modules/manager/gitlabci-include/extract extractPackageFile() extracts multiple include blocks 1`] = `
[
{
Expand Down
7 changes: 7 additions & 0 deletions lib/modules/manager/gitlabci-include/common.ts
@@ -1,6 +1,7 @@
import is from '@sindresorhus/is';
import type {
GitlabInclude,
GitlabIncludeComponent,
GitlabIncludeLocal,
GitlabIncludeProject,
GitlabPipeline,
Expand Down Expand Up @@ -32,3 +33,9 @@ export function isGitlabIncludeLocal(
): include is GitlabIncludeLocal {
return !is.undefined((include as GitlabIncludeLocal).local);
}

export function isGitlabIncludeComponent(
include: GitlabInclude,
): include is GitlabIncludeComponent {
return !is.undefined((include as GitlabIncludeComponent).component);
}
18 changes: 18 additions & 0 deletions lib/modules/manager/gitlabci-include/extract.spec.ts
Expand Up @@ -56,6 +56,24 @@ describe('modules/manager/gitlabci-include/extract', () => {
expect(res).toBeNull();
});

it('extracts component references', () => {
const includeWithoutProjectRef = `include:
- component: gitlab.example.com/an-org/a-project/a-component@1.0
inputs:
stage: build
- component: gitlab.example.com/an-org/a-subgroup/a-project/a-component@e3262fdd0914fa823210cdb79a8c421e2cef79d8
- component: gitlab.example.com/an-org/a-subgroup/another-project/a-component@main
- component: gitlab.example.com/another-org/a-project/a-component@~latest
inputs:
stage: test
- component: gitlab.example.com/malformed-component-reference
- component:
malformed: true`;
const res = extractPackageFile(includeWithoutProjectRef);
expect(res?.deps).toMatchSnapshot();
expect(res?.deps).toHaveLength(4);
});

it('normalizes configured endpoints', () => {
const endpoints = [
'http://gitlab.test/api/v4',
Expand Down
76 changes: 76 additions & 0 deletions lib/modules/manager/gitlabci-include/extract.ts
Expand Up @@ -6,17 +6,25 @@ import { parseSingleYaml } from '../../../util/yaml';
import { GitlabTagsDatasource } from '../../datasource/gitlab-tags';
import type {
GitlabInclude,
GitlabIncludeComponent,
GitlabIncludeProject,
GitlabPipeline,
} from '../gitlabci/types';
import { replaceReferenceTags } from '../gitlabci/utils';
import type { PackageDependency, PackageFileContent } from '../types';
import {
filterIncludeFromGitlabPipeline,
isGitlabIncludeComponent,
isGitlabIncludeProject,
isNonEmptyObject,
} from './common';

// See https://docs.gitlab.com/ee/ci/components/index.html#use-a-component
const componentReferenceRegex = regEx(
/(?<fqdn>[^/]+)\/(?<projectPath>.+)\/(?<componentName>.+)@(?<specificVersion>.+)/,
);
const componentReferenceLatestVersion = '~latest';

function extractDepFromIncludeFile(
includeObj: GitlabIncludeProject,
): PackageDependency {
Expand All @@ -33,6 +41,36 @@ function extractDepFromIncludeFile(
return dep;
}

function extractDepFromIncludeComponent(
includeComponent: GitlabIncludeComponent,
): PackageDependency | null {
const componentReferenceMatch = componentReferenceRegex?.exec(
includeComponent.component,
);
if (!componentReferenceMatch?.groups) {
logger.debug(
{ componentReference: includeComponent.component },
'Ignoring malformed component reference',
);
return null;
}
const dep: PackageDependency = {
datasource: GitlabTagsDatasource.id,
depName: componentReferenceMatch.groups.projectPath,
depType: 'repository',

registryUrls: [componentReferenceMatch.groups.fqdn],
};
if (dep.currentValue === componentReferenceLatestVersion) {
logger.debug(
{ componentVersion: dep.currentValue },
'Ignoring component version',
);
dep.skipReason = 'unsupported-version';
}
return dep;
}

function getIncludeProjectsFromInclude(
includeValue: GitlabInclude[] | GitlabInclude,
): GitlabIncludeProject[] {
Expand Down Expand Up @@ -63,6 +101,37 @@ function getAllIncludeProjects(data: GitlabPipeline): GitlabIncludeProject[] {
return childrenData;
}

function getIncludeComponentsFromInclude(
includeValue: GitlabInclude[] | GitlabInclude,
): GitlabIncludeComponent[] {
const includes = is.array(includeValue) ? includeValue : [includeValue];

// Filter out includes that dont have a file & project.
return includes.filter(isGitlabIncludeComponent);
}
function getAllIncludeComponents(
data: GitlabPipeline,
): GitlabIncludeComponent[] {
// If Array, search each element.
if (is.array(data)) {
return (data as GitlabPipeline[])
.filter(isNonEmptyObject)
.map(getAllIncludeComponents)
.flat();
}

const childrenData = Object.values(filterIncludeFromGitlabPipeline(data))
.filter(isNonEmptyObject)
.map(getAllIncludeComponents)
.flat();

// Process include key.
if (data.include) {
childrenData.push(...getIncludeComponentsFromInclude(data.include));
}
return childrenData;
}

export function extractPackageFile(
content: string,
packageFile?: string,
Expand All @@ -82,6 +151,13 @@ export function extractPackageFile(
}
deps.push(dep);
}
const includedComponents = getAllIncludeComponents(doc);
for (const includedComponent of includedComponents) {
const dep = extractDepFromIncludeComponent(includedComponent);
if (dep) {
deps.push(dep);
}
}
} catch (err) /* istanbul ignore next */ {
if (err.stack?.startsWith('YAMLException:')) {
logger.debug(
Expand Down
5 changes: 5 additions & 0 deletions lib/modules/manager/gitlabci/types.ts
Expand Up @@ -16,6 +16,10 @@ export interface GitlabIncludeTemplate {
template: string;
}

export interface GitlabIncludeComponent {
component: string;
}

export interface GitlabPipeline {
include?: GitlabInclude[] | GitlabInclude;
}
Expand All @@ -39,4 +43,5 @@ export type GitlabInclude =
| GitlabIncludeProject
| GitlabIncludeRemote
| GitlabIncludeTemplate
| GitlabIncludeComponent
| string;

0 comments on commit 7458246

Please sign in to comment.