diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ccafb85d8d3ff909330cf64c4bfb1e4b478b797b..e4bb82ddf854aa0982f5993e80597869227f3730 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,6 +5,13 @@ include: variables: INGRESS_HOSTNAME: manifest-$CI_COMMIT_REF_SLUG +build image: + before_script: + - "[ -z \"$APP_VERSION\" ] && export APP_VERSION=$CI_COMMIT_TAG" + - "[ -z \"$APP_VERSION\" ] && export APP_VERSION=$(cat package.json | grep 'version' | cut -f 4 -d'\"')" + - echo "Building version $APP_VERSION" + extends: .build image + deploy helm chart: extends: .auto-deploy-helm-chart environment: diff --git a/Dockerfile b/Dockerfile index 0d5fe54f616742ea9e9293c45b6d266dcc353a64..02fad59921b3c2af2cf5ff4426c24260bf407c01 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,13 @@ FROM node:17-alpine LABEL maintainer="ui-team@open-xchange.com" +ARG APP_VERSION +ARG BUILD_TIMESTAMP +ARG CI_COMMIT_SHA +ENV APP_VERSION=$APP_VERSION +ENV BUILD_TIMESTAMP=$BUILD_TIMESTAMP +ENV CI_COMMIT_SHA=$CI_COMMIT_SHA + WORKDIR /app ADD . /app RUN yarn --production --non-interactive --no-progress -s diff --git a/spec/meta_test.js b/spec/meta_test.js new file mode 100644 index 0000000000000000000000000000000000000000..c866b84561c05a98d599011a86d85595087e8018 --- /dev/null +++ b/spec/meta_test.js @@ -0,0 +1,80 @@ +import { describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from '@jest/globals' +import mockfs from 'mock-fs' +import request from 'supertest' +import { createApp } from '../src/createApp' +import { createMockServer, generateSimpleViteManifest, getRandomPort } from './util.js' + +describe('Responses contain custom headers', () => { + let app + let mockserver + const port = getRandomPort() + + beforeAll(() => { + mockfs({ + './config/manifests': { + 'urls.yaml': `manifests: + - http://localhost:${port}/manifest.json` + } + }) + app = createApp() + }) + + afterAll(() => { + mockfs.restore() + }) + + beforeEach(async () => { + mockserver = await createMockServer({ port }) + mockserver.respondWith({ + '/manifest.json': generateSimpleViteManifest({ + 'example.js': {} + }), + '/example.js': (req, res) => res.setHeader('content-type', 'application/javascript').status(200).send('this is example'), + '/meta.json': { name: 'sample-service', version: '1.0' } + }) + await request(app).get('/ready') + }) + + afterEach(() => { + mockserver.close() + delete process.env.APP_VERSION + delete process.env.BUILD_TIMESTAMP + delete process.env.CI_COMMIT_SHA + }) + + it('has own metadata', async () => { + process.env.APP_VERSION = '4.2' + process.env.BUILD_TIMESTAMP = '0123456789' + process.env.CI_COMMIT_SHA = '0123456789abcdef' + + const response = await request(app).get('/meta') + expect(response.statusCode).toBe(200) + expect(response.body).toContainEqual({ + id: 'manifest-service', + name: 'Manifest Service', + buildDate: '0123456789', + commitSha: '0123456789abcdef', + version: '4.2' + }) + }) + + it('has metadata from another ui service if available', async () => { + const response = await request(app).get('/meta') + expect(response.statusCode).toBe(200) + expect(response.body).toContainEqual({ + name: 'sample-service', + version: '1.0' + }) + }) + + it('does not have metadata from ui service when unavailable', async () => { + await mockserver.close() + + const response = await request(app).get('/meta') + expect(response.statusCode).toBe(200) + expect(response.body).not.toContain({ + name: 'sample-service', + version: '1.0' + }) + }) +}) diff --git a/src/createApp.js b/src/createApp.js index 5026ce348af98693d2c549f2bfe1f54bd854dbf0..7c3727cc9ad6a226f51882321ee2c4867c460a3f 100644 --- a/src/createApp.js +++ b/src/createApp.js @@ -20,6 +20,7 @@ import yaml from 'js-yaml' import fs from 'fs' import { getCSSDependenciesFor, getDependencies, getOxManifests, getVersion, loadViteManifests, viteManifestToDeps } from './manifests.js' import { fileCache } from './files.js' +import { getMergedMetadata } from './meta.js' const ignorePaths = ['/ready', '/healthy'] const swaggerDocument = yaml.load(fs.readFileSync('./src/swagger.yaml', 'utf8')) @@ -93,6 +94,14 @@ export function createApp () { } }) + app.get('/meta', async (req, res, next) => { + try { + res.json(await getMergedMetadata()) + } catch (err) { + next(err) + } + }) + app.get('/', async (req, res, next) => { const { 'content-type': contentType, content } = fileCache.get('/index.html') if (content) return res.setHeader('content-type', contentType).status(200).send(content) diff --git a/src/meta.js b/src/meta.js new file mode 100644 index 0000000000000000000000000000000000000000..1cdd7c082153586201ba5bf3a1ca38d02ef5bb20 --- /dev/null +++ b/src/meta.js @@ -0,0 +1,24 @@ +import { config } from './config.js' +import fetch from 'node-fetch' + +export async function getMergedMetadata () { + const metadata = await Promise.all(config.urls.map(async url => { + const { origin } = new URL(url) + try { + const response = await fetch(new URL('meta.json', origin)) + if (!response.ok) return + return response.json() + } catch (e) { + // unhandled + } + })) + metadata.push({ + id: 'manifest-service', + name: 'Manifest Service', + buildDate: process.env.BUILD_TIMESTAMP, + commitSha: process.env.CI_COMMIT_SHA, + version: process.env.APP_VERSION + }) + // only return when contains data + return metadata.filter(Boolean) +}