Skip to content

Commit bf803e8

Browse files
authored
feat(supported-version): add services configuration (mage-os#353)
* feat(supported-version): add services configuration * slightly revise integration doc
1 parent 51c6533 commit bf803e8

9 files changed

Lines changed: 486 additions & 37 deletions

File tree

.github/workflows/integration-README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ See the [integration.yaml](./integration.yaml)
88

99
| Input | Description | Required | Default |
1010
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- | -------- | ----------------------------- |
11-
| matrix | JSON string of [version matrix for Magento](./#matrix-format) | true | NULL |
11+
| matrix | JSON string of [version matrix for Magento](./#matrix-format). Must include a `services` key (pass `include_services: true` to the supported-version action). | true | NULL |
1212
| fail-fast | Same as Github's [fail-fast](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstrategyfail-fast) | false | true |
1313
| package_name | The name of the package | true | NULL |
1414
| source_folder | The source folder of the package | false | $GITHUB_WORKSPACE |

supported-version/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ See the [action.yml](./action.yml)
1717
| project | The project to return the supported versions for. Allowed values are `mage-os` and `magento-open-source` | false | 'magento-open-source' |
1818
| custom_versions | The versions you want to support, as a comma-separated string, i.e. 'magento/project-community-edition:2.3.7-p3, magento/project-community-edition:2.4.2-p2' | false | '' |
1919
| recent_time_frame | The time frame (from today) used when `kind` is `recent`. Combination of years (y), months (m), and days (d), e.g. `2y 2m 2d`. | false | '2y' |
20+
| include_services | Whether to include a `services` key in each matrix entry with GitHub Actions service container configurations for MySQL, search engine, RabbitMQ, and cache. | false | 'true' |
2021

2122
## Kinds
2223
- `currently-supported` - The currently supported Magento Open Source versions by Adobe.

supported-version/action.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ inputs:
2121
required: false
2222
default: "2y"
2323
description: "The time frame (from today). Only used in `recent` kind. String that defines a time duration using a combination of years (y), months (m), and days (d). Each unit is optional and can appear in any order, separated by spaces. For example `2y 2m 2d`. "
24+
include_services:
25+
required: false
26+
default: "true"
27+
description: "Whether to include a `services` key in each matrix entry with GitHub Actions service configurations."
2428

2529
outputs:
2630
matrix:

supported-version/dist/index.js

Lines changed: 35 additions & 35 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

supported-version/src/index.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as core from '@actions/core';
22
import { validateKind } from './kind/validate-kinds';
33
import { getMatrixForKind } from './matrix/get-matrix-for-kind';
44
import { validateProject } from "./project/validate-projects";
5+
import { buildServicesForEntry } from "./services/build-services";
56

67

78
export async function run(): Promise<void> {
@@ -10,12 +11,26 @@ export async function run(): Promise<void> {
1011
const customVersions = core.getInput("custom_versions");
1112
const project = core.getInput("project");
1213
const recent_time_frame = core.getInput("recent_time_frame");
14+
const include_services = core.getInput("include_services") === "true";
1315

1416
validateProject(<any>project)
1517

1618
validateKind(<any>kind, customVersions ? customVersions.split(',') : undefined);
1719

1820
core.setOutput('matrix', getMatrixForKind(kind, project, customVersions, recent_time_frame));
21+
let matrix = getMatrixForKind(kind, project, customVersions);
22+
23+
if (include_services) {
24+
matrix = {
25+
magento: matrix.magento,
26+
include: matrix.include.map((entry) => ({
27+
...entry,
28+
services: buildServicesForEntry(entry)
29+
}))
30+
};
31+
}
32+
33+
core.setOutput('matrix', matrix);
1934
}
2035
catch (error) {
2136
core.setFailed(error.message);

supported-version/src/matrix/matrix-type.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,14 @@
1+
export interface ServiceConfig {
2+
image: string;
3+
env?: Record<string, string>;
4+
ports?: string[];
5+
options?: string;
6+
}
7+
8+
export interface Services {
9+
[serviceName: string]: ServiceConfig;
10+
}
11+
112
export interface PackageMatrixVersion {
213
magento: string,
314
php: string | number,
@@ -12,7 +23,8 @@ export interface PackageMatrixVersion {
1223
nginx: string,
1324
os: string,
1425
release: string,
15-
eol: string
26+
eol: string,
27+
services?: Services
1628
}
1729

1830
export interface GithubActionsMatrix {
Lines changed: 248 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,248 @@
1+
import { buildServicesForEntry } from './build-services';
2+
import { PackageMatrixVersion } from '../matrix/matrix-type';
3+
4+
const createTestEntry = (overrides: Partial<PackageMatrixVersion> = {}): PackageMatrixVersion => ({
5+
magento: 'magento/project-community-edition:2.4.7',
6+
php: '8.3',
7+
composer: '2.7.4',
8+
mysql: 'mysql:8.4',
9+
elasticsearch: 'elasticsearch:8.11.4',
10+
opensearch: 'opensearchproject/opensearch:2.19.1',
11+
rabbitmq: 'rabbitmq:4.0-management',
12+
redis: 'redis:7.2',
13+
varnish: 'varnish:7.5',
14+
valkey: 'valkey:8.0',
15+
nginx: 'nginx:1.26',
16+
os: 'ubuntu-latest',
17+
release: '2024-04-09T00:00:00+0000',
18+
eol: '2027-04-09T00:00:00+0000',
19+
...overrides
20+
});
21+
22+
describe('buildServicesForEntry', () => {
23+
describe('search engine selection', () => {
24+
it('should prefer opensearch when both are available', () => {
25+
const entry = createTestEntry();
26+
const services = buildServicesForEntry(entry);
27+
28+
expect(services.opensearch).toBeDefined();
29+
expect(services.opensearch.image).toBe('opensearchproject/opensearch:2.19.1');
30+
expect(services.elasticsearch).toBeUndefined();
31+
});
32+
33+
it('should fall back to elasticsearch when opensearch is empty', () => {
34+
const entry = createTestEntry({ opensearch: '' });
35+
const services = buildServicesForEntry(entry);
36+
37+
expect(services.elasticsearch).toBeDefined();
38+
expect(services.elasticsearch.image).toBe('elasticsearch:8.11.4');
39+
expect(services.opensearch).toBeUndefined();
40+
});
41+
42+
it('should not include search engine when neither is available', () => {
43+
const entry = createTestEntry({ opensearch: '', elasticsearch: '' });
44+
const services = buildServicesForEntry(entry);
45+
46+
expect(services.opensearch).toBeUndefined();
47+
expect(services.elasticsearch).toBeUndefined();
48+
});
49+
});
50+
51+
describe('cache selection', () => {
52+
it('should prefer valkey when both are available', () => {
53+
const entry = createTestEntry();
54+
const services = buildServicesForEntry(entry);
55+
56+
expect(services.valkey).toBeDefined();
57+
expect(services.valkey.image).toBe('valkey:8.0');
58+
expect(services.redis).toBeUndefined();
59+
});
60+
61+
it('should fall back to redis when valkey is empty', () => {
62+
const entry = createTestEntry({ valkey: '' });
63+
const services = buildServicesForEntry(entry);
64+
65+
expect(services.redis).toBeDefined();
66+
expect(services.redis.image).toBe('redis:7.2');
67+
expect(services.valkey).toBeUndefined();
68+
});
69+
70+
it('should not include cache when neither is available', () => {
71+
const entry = createTestEntry({ valkey: '', redis: '' });
72+
const services = buildServicesForEntry(entry);
73+
74+
expect(services.valkey).toBeUndefined();
75+
expect(services.redis).toBeUndefined();
76+
});
77+
});
78+
79+
describe('mysql configuration', () => {
80+
it('should include mysql when available', () => {
81+
const entry = createTestEntry();
82+
const services = buildServicesForEntry(entry);
83+
84+
expect(services.mysql).toBeDefined();
85+
expect(services.mysql.image).toBe('mysql:8.4');
86+
});
87+
88+
it('should not include mysql when empty', () => {
89+
const entry = createTestEntry({ mysql: '' });
90+
const services = buildServicesForEntry(entry);
91+
92+
expect(services.mysql).toBeUndefined();
93+
});
94+
95+
it('should include correct mysql env configuration', () => {
96+
const entry = createTestEntry();
97+
const services = buildServicesForEntry(entry);
98+
99+
expect(services.mysql.env).toEqual({
100+
MYSQL_DATABASE: 'magento_integration_tests',
101+
MYSQL_USER: 'user',
102+
MYSQL_PASSWORD: 'password',
103+
MYSQL_ROOT_PASSWORD: 'rootpassword'
104+
});
105+
});
106+
107+
it('should include correct mysql ports', () => {
108+
const entry = createTestEntry();
109+
const services = buildServicesForEntry(entry);
110+
111+
expect(services.mysql.ports).toEqual(['3306:3306']);
112+
});
113+
114+
it('should include mysql health check options', () => {
115+
const entry = createTestEntry();
116+
const services = buildServicesForEntry(entry);
117+
118+
expect(services.mysql.options).toContain('--health-cmd');
119+
expect(services.mysql.options).toContain('mysqladmin ping');
120+
});
121+
});
122+
123+
describe('rabbitmq configuration', () => {
124+
it('should include rabbitmq when available', () => {
125+
const entry = createTestEntry();
126+
const services = buildServicesForEntry(entry);
127+
128+
expect(services.rabbitmq).toBeDefined();
129+
expect(services.rabbitmq.image).toBe('rabbitmq:4.0-management');
130+
});
131+
132+
it('should not include rabbitmq when empty', () => {
133+
const entry = createTestEntry({ rabbitmq: '' });
134+
const services = buildServicesForEntry(entry);
135+
136+
expect(services.rabbitmq).toBeUndefined();
137+
});
138+
139+
it('should include correct rabbitmq env configuration', () => {
140+
const entry = createTestEntry();
141+
const services = buildServicesForEntry(entry);
142+
143+
expect(services.rabbitmq.env).toEqual({
144+
RABBITMQ_DEFAULT_USER: 'guest',
145+
RABBITMQ_DEFAULT_PASS: 'guest'
146+
});
147+
});
148+
149+
it('should include correct rabbitmq ports', () => {
150+
const entry = createTestEntry();
151+
const services = buildServicesForEntry(entry);
152+
153+
expect(services.rabbitmq.ports).toEqual(['5672:5672']);
154+
});
155+
});
156+
157+
describe('opensearch configuration', () => {
158+
it('should include correct opensearch env configuration', () => {
159+
const entry = createTestEntry();
160+
const services = buildServicesForEntry(entry);
161+
162+
expect(services.opensearch.env).toEqual({
163+
'discovery.type': 'single-node',
164+
'DISABLE_INSTALL_DEMO_CONFIG': 'true',
165+
'DISABLE_SECURITY_PLUGIN': 'true'
166+
});
167+
});
168+
169+
it('should include correct opensearch ports', () => {
170+
const entry = createTestEntry();
171+
const services = buildServicesForEntry(entry);
172+
173+
expect(services.opensearch.ports).toEqual(['9200:9200']);
174+
});
175+
176+
it('should include opensearch health check options', () => {
177+
const entry = createTestEntry();
178+
const services = buildServicesForEntry(entry);
179+
180+
expect(services.opensearch.options).toContain('--health-cmd');
181+
expect(services.opensearch.options).toContain('curl');
182+
});
183+
});
184+
185+
describe('elasticsearch configuration', () => {
186+
it('should include correct elasticsearch env configuration', () => {
187+
const entry = createTestEntry({ opensearch: '' });
188+
const services = buildServicesForEntry(entry);
189+
190+
expect(services.elasticsearch.env).toEqual({
191+
'discovery.type': 'single-node',
192+
'xpack.security.enabled': 'false',
193+
'xpack.security.http.ssl.enabled': 'false',
194+
'xpack.security.transport.ssl.enabled': 'false'
195+
});
196+
});
197+
198+
it('should include correct elasticsearch ports', () => {
199+
const entry = createTestEntry({ opensearch: '' });
200+
const services = buildServicesForEntry(entry);
201+
202+
expect(services.elasticsearch.ports).toEqual(['9200:9200']);
203+
});
204+
});
205+
206+
describe('cache configuration', () => {
207+
it('should include correct valkey ports', () => {
208+
const entry = createTestEntry();
209+
const services = buildServicesForEntry(entry);
210+
211+
expect(services.valkey.ports).toEqual(['6379:6379']);
212+
});
213+
214+
it('should include correct redis ports', () => {
215+
const entry = createTestEntry({ valkey: '' });
216+
const services = buildServicesForEntry(entry);
217+
218+
expect(services.redis.ports).toEqual(['6379:6379']);
219+
});
220+
});
221+
222+
describe('complete service output', () => {
223+
it('should build all services when all are available', () => {
224+
const entry = createTestEntry();
225+
const services = buildServicesForEntry(entry);
226+
227+
expect(Object.keys(services)).toHaveLength(4);
228+
expect(services.mysql).toBeDefined();
229+
expect(services.opensearch).toBeDefined();
230+
expect(services.rabbitmq).toBeDefined();
231+
expect(services.valkey).toBeDefined();
232+
});
233+
234+
it('should handle entry with minimal services', () => {
235+
const entry = createTestEntry({
236+
mysql: '',
237+
elasticsearch: '',
238+
opensearch: '',
239+
rabbitmq: '',
240+
redis: '',
241+
valkey: ''
242+
});
243+
const services = buildServicesForEntry(entry);
244+
245+
expect(Object.keys(services)).toHaveLength(0);
246+
});
247+
});
248+
});

0 commit comments

Comments
 (0)