Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking β€œSign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create Plugin: Add docker-compose migration for extending from .config #1621

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -46,4 +46,5 @@ playwright/.auth

# Used in CI to pass built packages to the next job
packed-artifacts/
.mise.toml
.mise.toml
.cursor/
94 changes: 30 additions & 64 deletions package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion packages/create-plugin/package.json
Original file line number Diff line number Diff line change
@@ -41,7 +41,8 @@
"minimist": "^1.2.8",
"semver": "^7.3.5",
"title-case": "^4.3.0",
"which": "^5.0.0"
"which": "^5.0.0",
"yaml": "^2.7.0"
},
"devDependencies": {
"@types/glob": "^8.1.0",
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { describe, it, expect } from 'vitest';
import { createDefaultContext } from '../test-utils.js';
import migrate from './001-update-grafana-compose-extend.js';
import { parse, stringify } from 'yaml';

describe('001-update-grafana-compose-extend', () => {
it('should not modify anything if docker-compose.yaml does not exist', async () => {
const context = createDefaultContext();
const initialChanges = context.listChanges();
await migrate(context);
expect(context.listChanges()).toEqual(initialChanges);
});

it('should not modify if grafana service with correct build context is missing', async () => {
const context = createDefaultContext();
context.addFile(
'./docker-compose.yaml',
stringify({
services: {
grafana: {
build: {
context: './wrong-path',
},
},
},
})
);
const initialChanges = context.listChanges();
await migrate(context);
expect(context.listChanges()).toEqual(initialChanges);
});

it('should not modify if base compose file is missing', async () => {
const context = createDefaultContext();
context.addFile(
'./docker-compose.yaml',
stringify({
services: {
grafana: {
build: {
context: './.config',
},
},
},
})
);
const initialChanges = context.listChanges();
await migrate(context);
expect(context.listChanges()).toEqual(initialChanges);
});

it('should update compose file and preserve build args', async () => {
const context = createDefaultContext();
context.addFile(
'./docker-compose.yaml',
stringify({
services: {
grafana: {
build: {
context: './.config',
args: {
grafana_image: 'custom-image',
grafana_version: '10.0.0',
other_arg: 'value',
},
},
},
},
})
);
context.addFile(
'./.config/docker-compose-base.yaml',
stringify({
services: {
grafana: {
build: {
context: '.',
},
},
},
})
);

await migrate(context);

const result = parse(context.getFile('./docker-compose.yaml') || '');
expect(result.services.grafana).toEqual({
extends: {
file: '.config/docker-compose-base.yaml',
service: 'grafana',
},
build: {
args: {
grafana_image: 'custom-image',
grafana_version: '10.0.0',
},
},
});
});

it('should be idempotent', async () => {
const context = createDefaultContext();
context.addFile(
'./docker-compose.yaml',
stringify({
services: {
grafana: {
build: {
context: './.config',
args: {
grafana_image: 'custom-image',
grafana_version: '10.0.0',
},
},
},
},
})
);
context.addFile('./.config/docker-compose-base.yaml', 'exists');
await expect(migrate).toBeIdempotent(context);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { type Context } from '../context.js';
import { parse, stringify } from 'yaml';

export default async function migrate(context: Context) {
// Check if docker-compose.yaml exists
if (!context.doesFileExist('./docker-compose.yaml')) {
return context;
}

// Read and parse the docker-compose file
const composeContent = context.getFile('./docker-compose.yaml');
if (!composeContent) {
return context;
}

const composeData = parse(composeContent);

// Check if grafana service exists with the specified build context
if (composeData?.services?.grafana?.build?.context !== './.config') {
return context;
}

// Check if base compose file exists
if (!context.doesFileExist('./.config/docker-compose-base.yaml')) {
return context;
}

// Preserve build args if they exist
const existingBuildArgs = composeData.services.grafana.build.args;
const preservedArgs: Record<string, string> = {};
if (existingBuildArgs?.grafana_image || existingBuildArgs?.grafana_version) {
if (existingBuildArgs.grafana_image) {
preservedArgs['grafana_image'] = existingBuildArgs.grafana_image;
}
if (existingBuildArgs.grafana_version) {
preservedArgs['grafana_version'] = existingBuildArgs.grafana_version;
}
}

// Update the grafana service configuration
composeData.services.grafana = {
extends: {
file: '.config/docker-compose-base.yaml',
service: 'grafana',
},
...(Object.keys(preservedArgs).length > 0 && {
build: {
args: preservedArgs,
},
}),
};

// Write the updated compose file
context.updateFile('./docker-compose.yaml', stringify(composeData, { lineWidth: 0 }));

return context;
}