From 652ac2aca49c6ed396a744e9f5443f3b888b0238 Mon Sep 17 00:00:00 2001
From: David May <1301201+wass3r@users.noreply.github.com>
Date: Mon, 16 May 2022 17:59:20 +0000
Subject: [PATCH] feat: add velaci manager  (#14803)

---
 lib/modules/manager/api.ts                    |  2 +
 .../velaci/__fixtures__/.vela-secrets.yml     | 24 ++++++
 .../velaci/__fixtures__/.vela-services.yml    | 15 ++++
 .../velaci/__fixtures__/.vela-stages.yaml     | 21 +++++
 .../velaci/__fixtures__/.vela-steps.yml       | 17 +++++
 lib/modules/manager/velaci/extract.spec.ts    | 76 +++++++++++++++++++
 lib/modules/manager/velaci/extract.ts         | 56 ++++++++++++++
 lib/modules/manager/velaci/index.ts           |  9 +++
 lib/modules/manager/velaci/readme.md          |  3 +
 lib/modules/manager/velaci/types.ts           | 25 ++++++
 10 files changed, 248 insertions(+)
 create mode 100644 lib/modules/manager/velaci/__fixtures__/.vela-secrets.yml
 create mode 100644 lib/modules/manager/velaci/__fixtures__/.vela-services.yml
 create mode 100644 lib/modules/manager/velaci/__fixtures__/.vela-stages.yaml
 create mode 100644 lib/modules/manager/velaci/__fixtures__/.vela-steps.yml
 create mode 100644 lib/modules/manager/velaci/extract.spec.ts
 create mode 100644 lib/modules/manager/velaci/extract.ts
 create mode 100644 lib/modules/manager/velaci/index.ts
 create mode 100644 lib/modules/manager/velaci/readme.md
 create mode 100644 lib/modules/manager/velaci/types.ts

diff --git a/lib/modules/manager/api.ts b/lib/modules/manager/api.ts
index 3f45b28b1b..ebe04c9b60 100644
--- a/lib/modules/manager/api.ts
+++ b/lib/modules/manager/api.ts
@@ -67,6 +67,7 @@ import * as terragrunt from './terragrunt';
 import * as terragruntVersion from './terragrunt-version';
 import * as travis from './travis';
 import type { ManagerApi } from './types';
+import * as velaci from './velaci';
 
 const api = new Map<string, ManagerApi>();
 export default api;
@@ -139,3 +140,4 @@ api.set('terraform-version', terraformVersion);
 api.set('terragrunt', terragrunt);
 api.set('terragrunt-version', terragruntVersion);
 api.set('travis', travis);
+api.set('velaci', velaci);
diff --git a/lib/modules/manager/velaci/__fixtures__/.vela-secrets.yml b/lib/modules/manager/velaci/__fixtures__/.vela-secrets.yml
new file mode 100644
index 0000000000..69f7d1a31e
--- /dev/null
+++ b/lib/modules/manager/velaci/__fixtures__/.vela-secrets.yml
@@ -0,0 +1,24 @@
+version: "1"
+
+steps:
+  - name: node
+    image: amd64/node:10.0.0@sha256:5082d4db78ee2d24f72b25eb75537f2e19c49f04a595378d1ae8814d6f2fbf28
+    commands:
+      - npm install
+      - npm test
+
+secrets:
+  # Implicit secret definition.
+  - name: vault_token
+
+  - origin:
+      name: private vault
+      image: target/secret-vault:v0.1.0
+      secrets: [ vault_token ]
+      parameters:
+        addr: vault.example.com
+        auth_method: token
+        username: octocat
+        items:
+          - source: secret/docker
+            path: docker
diff --git a/lib/modules/manager/velaci/__fixtures__/.vela-services.yml b/lib/modules/manager/velaci/__fixtures__/.vela-services.yml
new file mode 100644
index 0000000000..4f04272d5d
--- /dev/null
+++ b/lib/modules/manager/velaci/__fixtures__/.vela-services.yml
@@ -0,0 +1,15 @@
+version: "1"
+
+services:
+  - name: mysql
+    image: mysql:5.7.24
+
+  - name: redis
+    image: redis:alpine
+
+steps:
+  - name: node
+    image: amd64/node:10.0.0@sha256:5082d4db78ee2d24f72b25eb75537f2e19c49f04a595378d1ae8814d6f2fbf28
+    commands:
+      - npm install
+      - npm test
diff --git a/lib/modules/manager/velaci/__fixtures__/.vela-stages.yaml b/lib/modules/manager/velaci/__fixtures__/.vela-stages.yaml
new file mode 100644
index 0000000000..24e084dc8e
--- /dev/null
+++ b/lib/modules/manager/velaci/__fixtures__/.vela-stages.yaml
@@ -0,0 +1,21 @@
+version: "1"
+
+stages:
+  go:
+    steps:
+      - name: go
+        image: golang:1.13
+        environment:
+          CGO_ENABLED: '0'
+          GOOS: linux
+        commands:
+          - go get ./...
+          - go test ./...
+  node:
+    steps:
+      - name: node
+        image: amd64/node:10.0.0@sha256:5082d4db78ee2d24f72b25eb75537f2e19c49f04a595378d1ae8814d6f2fbf28
+        commands:
+          - npm install
+          - npm test
+
diff --git a/lib/modules/manager/velaci/__fixtures__/.vela-steps.yml b/lib/modules/manager/velaci/__fixtures__/.vela-steps.yml
new file mode 100644
index 0000000000..75546f7c6a
--- /dev/null
+++ b/lib/modules/manager/velaci/__fixtures__/.vela-steps.yml
@@ -0,0 +1,17 @@
+version: "1"
+
+steps:
+  - name: go
+    image: golang:1.13
+    environment:
+      CGO_ENABLED: '0'
+      GOOS: linux
+    commands:
+      - go get ./...
+      - go test ./...
+
+  - name: node
+    image: amd64/node:10.0.0@sha256:5082d4db78ee2d24f72b25eb75537f2e19c49f04a595378d1ae8814d6f2fbf28
+    commands:
+      - npm install
+      - npm test
diff --git a/lib/modules/manager/velaci/extract.spec.ts b/lib/modules/manager/velaci/extract.spec.ts
new file mode 100644
index 0000000000..50be1f0ad0
--- /dev/null
+++ b/lib/modules/manager/velaci/extract.spec.ts
@@ -0,0 +1,76 @@
+import { Fixtures } from '../../../../test/fixtures';
+import { extractPackageFile } from '.';
+
+describe('modules/manager/velaci/extract', () => {
+  describe('extractPackageFile()', () => {
+    it('should handle invalid YAML', () => {
+      const res = extractPackageFile('foo: bar: invalid');
+      expect(res).toBeNull();
+    });
+
+    it('should handle YAML without pipeline/images', () => {
+      const res = extractPackageFile('no: pipeline');
+      expect(res).toBeNull();
+    });
+
+    it('extracts multiple step pipeline image lines', () => {
+      const res = extractPackageFile(Fixtures.get('.vela-steps.yml'));
+      expect(res.deps).toMatchObject([
+        {
+          currentValue: '1.13',
+          depName: 'golang',
+        },
+        {
+          currentValue: '10.0.0',
+          depName: 'node',
+        },
+      ]);
+    });
+
+    it('extracts multiple services pipeline image lines', () => {
+      const res = extractPackageFile(Fixtures.get('.vela-services.yml'));
+      expect(res.deps).toMatchObject([
+        {
+          currentValue: '10.0.0',
+          depName: 'node',
+        },
+        {
+          currentValue: '5.7.24',
+          depName: 'mysql',
+        },
+        {
+          currentValue: 'alpine',
+          depName: 'redis',
+        },
+      ]);
+    });
+
+    it('extracts multiple stages pipeline image lines', () => {
+      const res = extractPackageFile(Fixtures.get('.vela-stages.yaml'));
+      expect(res.deps).toMatchObject([
+        {
+          currentValue: '1.13',
+          depName: 'golang',
+        },
+        {
+          currentValue: '10.0.0',
+          depName: 'node',
+        },
+      ]);
+    });
+
+    it('extracts multiple secrets pipeline image lines', () => {
+      const res = extractPackageFile(Fixtures.get('.vela-secrets.yml'));
+      expect(res.deps).toMatchObject([
+        {
+          currentValue: '10.0.0',
+          depName: 'node',
+        },
+        {
+          currentValue: 'v0.1.0',
+          depName: 'target/secret-vault',
+        },
+      ]);
+    });
+  });
+});
diff --git a/lib/modules/manager/velaci/extract.ts b/lib/modules/manager/velaci/extract.ts
new file mode 100644
index 0000000000..05fa1279b1
--- /dev/null
+++ b/lib/modules/manager/velaci/extract.ts
@@ -0,0 +1,56 @@
+import { load } from 'js-yaml';
+import { logger } from '../../../logger';
+import { getDep } from '../dockerfile/extract';
+import type { PackageDependency, PackageFile } from '../types';
+import type { VelaPipelineConfiguration } from './types';
+
+export function extractPackageFile(file: string): PackageFile | null {
+  let doc: VelaPipelineConfiguration | undefined;
+
+  try {
+    doc = load(file, { json: true }) as VelaPipelineConfiguration;
+  } catch (err) {
+    logger.debug({ err, file }, 'Failed to parse Vela file.');
+    return null;
+  }
+
+  const deps: PackageDependency[] = [];
+
+  // iterate over steps
+  for (const step of doc.steps ?? []) {
+    const dep = getDep(step.image);
+
+    deps.push(dep);
+  }
+
+  // iterate over services
+  for (const service of doc.services ?? []) {
+    const dep = getDep(service.image);
+
+    deps.push(dep);
+  }
+
+  // iterate over stages
+  for (const stage of Object.values(doc.stages ?? {})) {
+    for (const step of stage.steps ?? []) {
+      const dep = getDep(step.image);
+
+      deps.push(dep);
+    }
+  }
+
+  // check secrets
+  for (const secret of Object.values(doc.secrets ?? {})) {
+    if (secret.origin) {
+      const dep = getDep(secret.origin.image);
+
+      deps.push(dep);
+    }
+  }
+
+  if (!deps.length) {
+    return null;
+  }
+
+  return { deps };
+}
diff --git a/lib/modules/manager/velaci/index.ts b/lib/modules/manager/velaci/index.ts
new file mode 100644
index 0000000000..4b08231104
--- /dev/null
+++ b/lib/modules/manager/velaci/index.ts
@@ -0,0 +1,9 @@
+import { DockerDatasource } from '../../datasource/docker';
+
+export { extractPackageFile } from './extract';
+
+export const defaultConfig = {
+  fileMatch: ['(^|/).vela.ya?ml$'],
+};
+
+export const supportedDatasources = [DockerDatasource.id];
diff --git a/lib/modules/manager/velaci/readme.md b/lib/modules/manager/velaci/readme.md
new file mode 100644
index 0000000000..3826cd9176
--- /dev/null
+++ b/lib/modules/manager/velaci/readme.md
@@ -0,0 +1,3 @@
+Extracts Docker-type dependencies from VelaCI config files.
+
+If you need to change the versioning format, read the [versioning](https://docs.renovatebot.com/modules/versioning/) documentation to learn more.
diff --git a/lib/modules/manager/velaci/types.ts b/lib/modules/manager/velaci/types.ts
new file mode 100644
index 0000000000..d1cf8e9236
--- /dev/null
+++ b/lib/modules/manager/velaci/types.ts
@@ -0,0 +1,25 @@
+/**
+ * VelaPipelineConfiguration
+ *
+ * Spec: https://github.com/go-vela/types/releases/latest/download/schema.json
+ * Docs: https://go-vela.github.io/docs/reference/yaml/
+ */
+export interface VelaPipelineConfiguration {
+  secrets?: {
+    origin?: ObjectWithImageProp;
+  }[];
+
+  services?: ObjectWithImageProp[];
+
+  stages?: Record<string, Stage>;
+
+  steps?: ObjectWithImageProp[];
+}
+
+export interface ObjectWithImageProp {
+  image: string;
+}
+
+export interface Stage {
+  steps: ObjectWithImageProp[];
+}
-- 
GitLab