From 4a45145bd6086463ec71a981a18282a2a4eac080 Mon Sep 17 00:00:00 2001
From: "richard.petersen" <richard.petersen@open-xchange.com>
Date: Wed, 26 Jan 2022 10:10:20 +0000
Subject: [PATCH] Introduce metadata from ui-containers and manifest-service
 container

---
 .gitlab-ci.yml    |  7 +++++
 Dockerfile        |  7 +++++
 spec/meta_test.js | 80 +++++++++++++++++++++++++++++++++++++++++++++++
 src/createApp.js  |  9 ++++++
 src/meta.js       | 24 ++++++++++++++
 5 files changed, 127 insertions(+)
 create mode 100644 spec/meta_test.js
 create mode 100644 src/meta.js

diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index ccafb85..e4bb82d 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 0d5fe54..02fad59 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 0000000..c866b84
--- /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 5026ce3..7c3727c 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 0000000..1cdd7c0
--- /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)
+}
-- 
GitLab