From 7de359496e72a4f084aa2007dceceb092c64bcf1 Mon Sep 17 00:00:00 2001 From: Yassine-Chamkhi Date: Mon, 6 Nov 2023 15:42:52 +0100 Subject: [PATCH 01/11] update project configuration --- .eslintrc.js | 2 +- jest.config.js | 32 ++++++++++++++++++++++---------- jsdoc.config.json | 2 +- 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index a5496d9..137ffb6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -32,8 +32,8 @@ module.exports = { ignorePatterns: [ 'docs/*', 'dist/*', + 'src/lidy/*', 'src/assets/index.js', - 'src/antlr/*', ], // add your custom rules here diff --git a/jest.config.js b/jest.config.js index a8bc1e2..35b13d7 100755 --- a/jest.config.js +++ b/jest.config.js @@ -1,21 +1,23 @@ -/* - * For a detailed explanation regarding each configuration property, visit: - * https://jestjs.io/docs/configuration - */ - module.exports = { globals: { __DEV__: true, }, // Jest assumes we are testing in node environment, specify jsdom environment instead testEnvironment: 'node', - // Needed in JS codebases too because of feature flags - coveragePathIgnorePatterns: ['/node_modules/', '.d.ts$'], + testEnvironmentOptions: { + customExportConditions: ['node', 'node-addons'], + }, + // noStackTrace: true, + // bail: true, + // cache: false, + // verbose: true, + // watch: true, testMatch: [ '/tests/unit/**/*.spec.js', ], - moduleFileExtensions: ['js'], + moduleFileExtensions: ['js', 'json'], moduleNameMapper: { + '^~/(.*)$': '/$1', '^src/(.*)$': '/src/$1', '^tests/(.*)$': '/tests/$1', 'package.json': 'package.json', @@ -24,9 +26,19 @@ module.exports = { 'node_modules', ], transform: { - '^.+\\.(js|jsx)?$': 'babel-jest', + '^.+\\.js?$': 'babel-jest', + }, + // Needed in JS codebases too because of feature flags + coveragePathIgnorePatterns: ['/node_modules/', '.d.ts$'], + coverageThreshold: { + global: { + // branches: 50, + // functions: 50, + // lines: 50, + // statements: 50 + }, }, - transformIgnorePatterns: ['/node_modules/(?!antlr)'], + transformIgnorePatterns: ['/node_modules/(?!lidy-js)'], testResultsProcessor: 'jest-sonar-reporter', collectCoverage: true, collectCoverageFrom: ['src/**/*.js'], diff --git a/jsdoc.config.json b/jsdoc.config.json index 7b018e7..433a69a 100644 --- a/jsdoc.config.json +++ b/jsdoc.config.json @@ -24,7 +24,7 @@ "navLinks": [ { "label": "Github", - "href": "https://github.com/ditrit/terrator-plugin#README.md" + "href": "https://github.com/ditrit/dockercomposator-plugin#README.md" } ] } From 4e3a45cd503c9814f8f1376a6a3a0b0cb386a46a Mon Sep 17 00:00:00 2001 From: RayenRomdhane Date: Tue, 7 Nov 2023 10:34:00 +0100 Subject: [PATCH 02/11] update icons --- public/icons/DefaultIcon.svg | 11 +++++++++++ public/icons/compose.svg | 24 ++++++++++++++++++++++++ public/icons/config.svg | 2 ++ public/icons/dockercomposator-plugin.svg | 14 +++++++++++++- public/icons/network.svg | 3 +++ public/icons/secret.svg | 1 + public/icons/service.svg | 1 + public/icons/volume.svg | 19 +++++++++++++++++++ public/models/DefaultContainer.svg | 16 +++++++--------- public/models/DefaultModel.svg | 13 +++++++------ 10 files changed, 88 insertions(+), 16 deletions(-) create mode 100644 public/icons/DefaultIcon.svg create mode 100644 public/icons/compose.svg create mode 100644 public/icons/config.svg create mode 100644 public/icons/network.svg create mode 100644 public/icons/secret.svg create mode 100644 public/icons/service.svg create mode 100644 public/icons/volume.svg diff --git a/public/icons/DefaultIcon.svg b/public/icons/DefaultIcon.svg new file mode 100644 index 0000000..25abf5a --- /dev/null +++ b/public/icons/DefaultIcon.svg @@ -0,0 +1,11 @@ + + + + + + + + diff --git a/public/icons/compose.svg b/public/icons/compose.svg new file mode 100644 index 0000000..738439a --- /dev/null +++ b/public/icons/compose.svg @@ -0,0 +1,24 @@ + + + + + + + + + + \ No newline at end of file diff --git a/public/icons/config.svg b/public/icons/config.svg new file mode 100644 index 0000000..833fc08 --- /dev/null +++ b/public/icons/config.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/public/icons/dockercomposator-plugin.svg b/public/icons/dockercomposator-plugin.svg index 8ec4448..31aa4f5 100644 --- a/public/icons/dockercomposator-plugin.svg +++ b/public/icons/dockercomposator-plugin.svg @@ -1 +1,13 @@ - \ No newline at end of file + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/icons/network.svg b/public/icons/network.svg new file mode 100644 index 0000000..3d31c44 --- /dev/null +++ b/public/icons/network.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/public/icons/secret.svg b/public/icons/secret.svg new file mode 100644 index 0000000..2a85bf0 --- /dev/null +++ b/public/icons/secret.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/service.svg b/public/icons/service.svg new file mode 100644 index 0000000..62744ce --- /dev/null +++ b/public/icons/service.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons/volume.svg b/public/icons/volume.svg new file mode 100644 index 0000000..18163a0 --- /dev/null +++ b/public/icons/volume.svg @@ -0,0 +1,19 @@ + + + + + folder_plus [#1780] + Created with Sketch. + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/models/DefaultContainer.svg b/public/models/DefaultContainer.svg index 4fc1f90..ae5d97b 100644 --- a/public/models/DefaultContainer.svg +++ b/public/models/DefaultContainer.svg @@ -18,8 +18,8 @@ @@ -29,15 +29,16 @@ + font-family="Roboto"> {{ id }} {{ definition.type }} + x="50%" y="72">{{ definition.displayName or definition.type }} + fill="white" + style="outline: 1px dashed #5B81A5; outline-offset: -2px" /> + ry="4" rx="4" /> @@ -19,9 +19,9 @@ + x="10" y="5" + fill="#F2C037" + viewBox="0 0 512 512"> @@ -32,16 +32,17 @@ {{ id }} {{ definition.type }} + x="50%" y="72">{{ definition.displayName or definition.type }} From 0aac1225dc26e0f88572177e61eebb5f1b09327c Mon Sep 17 00:00:00 2001 From: RayenRomdhane Date: Tue, 7 Nov 2023 10:46:14 +0100 Subject: [PATCH 03/11] add metadata --- src/assets/metadata/docker-compose.json | 365 ++++++++++++++++++ src/assets/metadata/index.js | 3 + src/metadata/DockerComposeMetadata.js | 161 +++++++- src/metadata/ValidationSchema.js | 49 +++ .../metadata/getComposatorMetadata.js | 17 + tests/resources/metadata/invalid.json | 3 + tests/resources/metadata/valid.json | 353 +++++++++++++++++ .../metadata/DockerComposeMetadata.spec.js | 29 +- 8 files changed, 956 insertions(+), 24 deletions(-) create mode 100644 src/assets/metadata/docker-compose.json create mode 100644 src/assets/metadata/index.js create mode 100644 src/metadata/ValidationSchema.js create mode 100644 tests/resources/metadata/getComposatorMetadata.js create mode 100644 tests/resources/metadata/invalid.json create mode 100644 tests/resources/metadata/valid.json diff --git a/src/assets/metadata/docker-compose.json b/src/assets/metadata/docker-compose.json new file mode 100644 index 0000000..d7f523d --- /dev/null +++ b/src/assets/metadata/docker-compose.json @@ -0,0 +1,365 @@ +[ + { + "type": "Docker-Compose", + "description": "Represents the Compose file, it's the parent of all other components and it contains the top-level version property", + "url": "https://docs.docker.com/compose/compose-file/03-compose-file", + "model": "DefaultContainer", + "displayName": "Docker Compose", + "icon": "compose", + "isContainer": true, + "attributes": [ + { + "name": "version", + "type": "String", + "required": true + } + ] + }, + { + "type": "Service", + "description": "A service is an abstract definition of a computing resource within an application which can be scaled or replaced independently from other components. Services are backed by a set of containers", + "url": "https://docs.docker.com/compose/compose-file/05-services/", + "model": "DefaultModel", + "icon": "service", + "displayName": "Service", + "isContainer": false, + "attributes": [ + { + "name": "image", + "type": "String", + "required": true + }, + { + "name": "build", + "type": "Object", + "expanded": true, + "attributes": [ + { + "name": "context", + "type": "String" + }, + { + "name": "dockerfile", + "type": "String" + }, + { + "name": "args", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "String" + } + ] + } + ] + }, + { + "name": "depends_on", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "Object", + "attributes": [ + { + "name": "service", + "type": "Link", + "linkRef": "Service", + "linkColor": "red" + }, + { + "name": "condition", + "type": "String" + } + ] + } + ] + }, + { + "name": "environment", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "String" + } + ] + }, + { + "name": "ports", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "String" + } + ] + }, + { + "name": "healthcheck", + "type": "Object", + "expanded": true, + "attributes": [ + { + "name": "test", + "type": "String" + }, + { + "name": "interval", + "type": "String" + }, + { + "name": "timeout", + "type": "String" + }, + { + "name": "retries", + "type": "Number" + } + ] + }, + { + "name": "networks", + "type": "Link", + "linkRef": "Network", + "linkColor": "purple" + }, + { + "name": "volumes", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "Object", + "attributes": [ + { + "name": "volume-name", + "type": "Link", + "linkRef": "Volume", + "linkColor": "green" + }, + { + "name": "mount-path", + "type": "String" + } + ] + } + ] + }, + { + "name": "configs", + "type": "Link", + "linkRef": "Config", + "linkColor": "blue" + }, + { + "name": "secrets", + "type": "Link", + "linkRef": "Secret", + "linkColor": "pink" + }, + { + "name": "command", + "type": "String" + }, + { + "name": "stdin_open", + "type": "Boolean" + }, + { + "name": "privileged", + "type": "Boolean" + }, + { + "name": "tty", + "type": "Boolean" + } + ] + }, + { + "type": "Volume", + "description": "Volumes are persistent data stores implemented by the container engine.", + "url": "https://docs.docker.com/compose/compose-file/07-volumes/", + "model": "DefaultModel", + "icon": "volume", + "displayName": "Volume", + "isContainer": false, + "attributes": [ + { + "name": "driver", + "type": "String", + "required": true + }, + { + "name": "driver_opts", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "String" + } + ] + }, + { + "name": "labels", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "String" + } + ] + }, + { + "name": "external", + "type": "Boolean" + } + ] + }, + { + "type": "Network", + "description": "Networks are the layer that allow services to communicate with each other.", + "url": "https://docs.docker.com/compose/compose-file/06-networks/", + "model": "DefaultModel", + "icon": "network", + "displayName": "Network", + "isContainer": false, + "attributes": [ + { + "name": "driver", + "type": "String", + "required": true + }, + { + "name": "driver_opts", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "String" + } + ] + }, + { + "name": "enable_ipv6", + "type": "Boolean" + }, + { + "name": "ipam", + "type": "Object", + "expanded": true, + "attributes": [ + { + "name": "driver", + "type": "String" + }, + { + "name": "config", + "type": "Object", + "attributes": [ + { + "name": "subnet", + "type": "String" + }, + { + "name": "ip_range", + "type": "String" + }, + { + "name": "gateway", + "type": "Array" + }, + { + "name": "aux_adresses", + "type": "Array", + "attributes": [ + { + "name": "host", + "type": "String" + } + ] + } + ] + }, + { + "name": "options", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "String" + } + ] + } + ] + }, + { + "name": "labels", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "String" + } + ] + }, + { + "name": "external", + "type": "Boolean" + } + ] + }, + { + "type": "Config", + "description": "Configs allow services to adapt their behaviour without the need to rebuild a Docker image. Services can only access configs when explicitly granted by a configs attribute within the services top-level element.", + "url": "https://docs.docker.com/compose/compose-file/08-configs/", + "model": "DefaultModel", + "icon": "config", + "displayName": "Config", + "isContainer": false, + "attributes": [ + { + "name": "file", + "type": "String", + "required": true + }, + { + "name": "name", + "type": "String" + }, + { + "name": "external", + "type": "Boolean" + } + ] + }, + { + "type": "Secret", + "description": "Secrets are a flavor of Configs focusing on sensitive data, with specific constraint for this usage.", + "url": "https://docs.docker.com/compose/compose-file/09-secrets/", + "model": "DefaultModel", + "icon": "secret", + "displayName": "Secret", + "isContainer": false, + "attributes": [ + { + "name": "file", + "type": "String", + "required": true + }, + { + "name": "name", + "type": "String" + }, + { + "name": "environment", + "type": "String" + }, + { + "name": "external", + "type": "Boolean" + } + ] + } +] \ No newline at end of file diff --git a/src/assets/metadata/index.js b/src/assets/metadata/index.js new file mode 100644 index 0000000..87d20ba --- /dev/null +++ b/src/assets/metadata/index.js @@ -0,0 +1,3 @@ +import jsonComponents from 'src/assets/metadata/docker-compose.json'; + +export default jsonComponents; diff --git a/src/metadata/DockerComposeMetadata.js b/src/metadata/DockerComposeMetadata.js index d3bcf5a..0c9f578 100644 --- a/src/metadata/DockerComposeMetadata.js +++ b/src/metadata/DockerComposeMetadata.js @@ -1,24 +1,171 @@ -import { DefaultMetadata } from 'leto-modelizer-plugin-core'; +import Ajv from 'ajv'; +import { + DefaultMetadata, + ComponentDefinition, + ComponentAttributeDefinition, +} from 'leto-modelizer-plugin-core'; +import jsonComponents from 'src/assets/metadata'; +import Schema from 'src/metadata/ValidationSchema'; -/** - * Class to validate and retrieve component definitions from DockerCompose metadata. +/* + * Metadata is used to generate definitions of Components and ComponentAttributes. + * + * In our plugin managing Docker Composator, we use [Ajv](https://ajv.js.org/) to validate metadata. + * And we provide a `assets/metadata/docker-compose.json` to define all metadata. + * */ class DockerComposeMetadata extends DefaultMetadata { + /** + * Default constructor. + * @param {object} pluginData - Plugin data. + */ + constructor(pluginData) { + super(pluginData); + /** + * ajv. + * @type {Ajv} + */ + this.ajv = new Ajv(); + /** + * schema. + * @type {Schema} + */ + this.schema = Schema; + + /** + * dockerComosator components + * @type {jsonComponents} + */ + this.jsonComponents = jsonComponents; + + /** + * dockerComosator validation + * @type {validate} + */ + this.validate = this.validate.bind(this); + } + /** * Validate the provided metadata with a schema. * @returns {boolean} True if metadata is valid. */ validate() { + const errors = []; + const validate = this.ajv.compile(this.schema); + if (!validate(this.jsonComponents)) { + errors.push({ + ...this.jsonComponents, + errors: validate.errors, + }); + } + + if (errors.length > 0) { + return false; + } + return true; } /** - * Parse all component/link definitions from metadata. + * Function that adds component definitions from JSON file to pluginData. */ parse() { - this.pluginData.definitions = { - components: [], - }; + const componentDefs = jsonComponents.flatMap( + (component) => this.getComponentDefinition(component), + ); + this.setChildrenTypes(componentDefs); + this.pluginData.definitions.components.push(...componentDefs); + } + + /** + * Convert a JSON component definition object to a ComponentDefinition. + * @param {object} component - JSON component definition object to parse. + * @returns {ComponentDefinition} Parsed component definition. + */ + getComponentDefinition(component) { + const attributes = [...component.attributes]; + if (component.type !== 'Docker-Compose') { + // All components are children of the Docker-Compose, so they must have a reference attibute. + attributes.push({ + name: 'parentCompose', + type: 'Reference', + containerRef: 'Docker-Compose', + required: true, + }); + } + const definedAttributes = attributes.map(this.getAttributeDefinition, this); + const componentDef = new ComponentDefinition({ + ...component, + definedAttributes, + }); + componentDef.parentTypes = this.getParentTypes(componentDef); + return componentDef; + } + + /** + * Convert a JSON attribute object to a ComponentAttributeDefinition. + * @param {object} attribute - JSON attribute definition object to parse. + * @returns {ComponentAttributeDefinition} Parsed attribute definition. + */ + getAttributeDefinition(attribute) { + const subAttributes = attribute.attributes || []; + const attributeDef = new ComponentAttributeDefinition({ + ...attribute, + displayName: attribute.displayName || this.formatDisplayName(attribute.name), + definedAttributes: subAttributes.map(this.getAttributeDefinition, this), + }); + attributeDef.expanded = attribute.expanded || false; + return attributeDef; + } + + /** + * Format a name into a readable displayName. + * @param {string} name - Name to format. + * @returns {string} Formatted displayName. + */ + formatDisplayName(name) { + if (!name) { + return name; + } + const s = name.replace(/([A-Z])/g, ' $1'); + return s.charAt(0).toUpperCase() + s.slice(1); + } + + /** + * Get all possible parent container types. + * @param {DockerComposeComponentDefinition} componentDefinition - Definition to get all parent + * container types. + * @returns {string[]} All possible parent container types. + */ + getParentTypes(componentDefinition) { + const { parentTypes } = componentDefinition; + + componentDefinition.definedAttributes + .filter((attribute) => attribute.type === 'Reference') + .map((attribute) => attribute.containerRef) + .filter((ref) => !parentTypes.includes(ref)) + .forEach((ref) => parentTypes.push(ref)); + return parentTypes; + } + + /** + * Set the childrenTypes of all containers from children's parentTypes. + * @param {DockerComposeComponentDefinition[]} componentDefinitions - Array of + * component definitions. + */ + setChildrenTypes(componentDefinitions) { + const children = componentDefinitions + .filter((def) => def.parentTypes.length > 0) + .reduce((acc, def) => { + def.parentTypes.forEach((parentType) => { + acc[parentType] = [...(acc[parentType] || []), def.type]; + }); + return acc; + }, {}); + componentDefinitions.filter((def) => children[def.type]) + .forEach((def) => { + def.childrenTypes = children[def.type]; + }); } } diff --git a/src/metadata/ValidationSchema.js b/src/metadata/ValidationSchema.js new file mode 100644 index 0000000..52872e2 --- /dev/null +++ b/src/metadata/ValidationSchema.js @@ -0,0 +1,49 @@ +export default { + type: 'array', + items: { + type: 'object', + properties: { + type: { type: 'string' }, + model: { type: 'string' }, + displayName: { type: 'string' }, + icon: { type: 'string' }, + isContainer: { type: 'boolean' }, + childrenTypes: { + type: 'array', + items: { type: 'string' }, + }, + parentTypes: { + type: 'array', + items: { type: 'string' }, + }, + attributes: { + type: 'array', + items: { + $ref: '#/definitions/attribute', + }, + }, + }, + }, + definitions: { + attribute: { + type: 'object', + properties: { + name: { anyOf: [{ type: 'string' }, { type: 'null' }] }, + type: { + type: 'string', + pattern: '(String|Boolean|Number|Array|Object|Link|Reference)', + }, + required: { type: 'boolean' }, + containerRef: { type: 'string' }, + expanded: { type: 'boolean' }, + attributes: { + type: 'array', + items: { $ref: '#/definitions/attribute' }, + }, + linkRef: { type: 'string' }, + linkColor: { type: 'string' }, + }, + required: ['type'], + }, + }, +}; diff --git a/tests/resources/metadata/getComposatorMetadata.js b/tests/resources/metadata/getComposatorMetadata.js new file mode 100644 index 0000000..2f49ffa --- /dev/null +++ b/tests/resources/metadata/getComposatorMetadata.js @@ -0,0 +1,17 @@ +import fs from 'fs'; +import DockerComposeMetadata from 'src/metadata/DockerComposeMetadata'; +import DockerComposeData from 'src/models/DockerComposeData'; + +/** + * Create metadata from a specific metadata JSON file. + * @param {string} metadataName - metadata name. + * @param {string} metadataUrl - path to metadata JSON file. + * @returns {DockerComposeMetadata} DockerComposeMetadata instance containing metadata + * from specified url. + */ +export function getComposatorMetadata(metadataName, metadataUrl) { + const metadata = JSON.parse(fs.readFileSync(metadataUrl, 'utf8')); + const dockerComposatorPluginMetadata = new DockerComposeMetadata(new DockerComposeData()); + dockerComposatorPluginMetadata.jsonComponents = metadata; + return dockerComposatorPluginMetadata; +} diff --git a/tests/resources/metadata/invalid.json b/tests/resources/metadata/invalid.json new file mode 100644 index 0000000..7ecffdf --- /dev/null +++ b/tests/resources/metadata/invalid.json @@ -0,0 +1,3 @@ +{ + "type" : "invalid" +} \ No newline at end of file diff --git a/tests/resources/metadata/valid.json b/tests/resources/metadata/valid.json new file mode 100644 index 0000000..2b65e5e --- /dev/null +++ b/tests/resources/metadata/valid.json @@ -0,0 +1,353 @@ +[ + { + "type": "Docker-Compose", + "model": "DefaultContainer", + "displayName": "Docker Compose", + "icon": "compose", + "isContainer": true, + "attributes": [ + { + "name": "version", + "type": "String", + "required": true + } + ] + }, + { + "type": "Service", + "model": "DefaultModel", + "icon": "service", + "displayName": "Service", + "isContainer": false, + "attributes": [ + { + "name": "image", + "type": "String", + "required": true + }, + { + "name": "build", + "type": "Object", + "expanded": true, + "attributes": [ + { + "name": "context", + "type": "String" + }, + { + "name": "dockerfile", + "type": "String" + }, + { + "name": "args", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "String" + } + ] + } + ] + }, + { + "name": "depends_on", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "Object", + "attributes": [ + { + "name": "service", + "type": "Link", + "linkRef": "Service", + "linkColor": "red" + }, + { + "name": "condition", + "type": "String" + } + ] + } + ] + }, + { + "name": "environment", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "String" + } + ] + }, + { + "name": "ports", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "String" + } + ] + }, + { + "name": "healthcheck", + "type": "Object", + "expanded": true, + "attributes": [ + { + "name": "test", + "type": "String" + }, + { + "name": "interval", + "type": "String" + }, + { + "name": "timeout", + "type": "String" + }, + { + "name": "retries", + "type": "Number" + } + ] + }, + { + "name": "networks", + "type": "Link", + "linkRef": "Network", + "linkColor": "purple" + }, + { + "name": "volumes", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "Object", + "attributes": [ + { + "name": "volume-name", + "type": "Link", + "linkRef": "Volume", + "linkColor": "green" + }, + { + "name": "mount-path", + "type": "String" + } + ] + } + ] + }, + { + "name": "configs", + "type": "Link", + "linkRef": "Config", + "linkColor": "blue" + }, + { + "name": "secrets", + "type": "Link", + "linkRef": "Secret", + "linkColor": "pink" + }, + { + "name": "command", + "type": "String" + }, + { + "name": "stdin_open", + "type": "Boolean" + }, + { + "name": "privileged", + "type": "Boolean" + }, + { + "name": "tty", + "type": "Boolean" + } + ] + }, + { + "type": "Volume", + "model": "DefaultModel", + "icon": "volume", + "displayName": "Volume", + "isContainer": false, + "attributes": [ + { + "name": "driver", + "type": "String", + "required": true + }, + { + "name": "driver_opts", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "String" + } + ] + }, + { + "name": "labels", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "String" + } + ] + }, + { + "name": "external", + "type": "Boolean" + } + ] + }, + { + "type": "Network", + "model": "DefaultModel", + "icon": "network", + "displayName": "Network", + "isContainer": false, + "attributes": [ + { + "name": "driver", + "type": "String", + "required": true + }, + { + "name": "driver_opts", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "String" + } + ] + }, + { + "name": "enable_ipv6", + "type": "Boolean" + }, + { + "name": "ipam", + "type": "Object", + "expanded": true, + "attributes": [ + { + "name": "driver", + "type": "String" + }, + { + "name": "config", + "type": "Object", + "attributes": [ + { + "name": "subnet", + "type": "String" + }, + { + "name": "ip_range", + "type": "String" + }, + { + "name": "gateway", + "type": "Array" + }, + { + "name": "aux_adresses", + "type": "Array", + "attributes": [ + { + "name": "host", + "type": "String" + } + ] + } + ] + }, + { + "name": "options", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "String" + } + ] + } + ] + }, + { + "name": "labels", + "type": "Array", + "attributes": [ + { + "name": null, + "type": "String" + } + ] + }, + { + "name": "external", + "type": "Boolean" + } + ] + }, + { + "type": "Config", + "model": "DefaultModel", + "icon": "config", + "displayName": "Config", + "isContainer": false, + "attributes": [ + { + "name": "file", + "type": "String", + "required": true + }, + { + "name": "name", + "type": "String" + }, + { + "name": "external", + "type": "Boolean" + } + ] + }, + { + "type": "Secret", + "model": "DefaultModel", + "icon": "secret", + "displayName": "Secret", + "isContainer": false, + "attributes": [ + { + "name": "file", + "type": "String", + "required": true + }, + { + "name": "name", + "type": "String" + }, + { + "name": "environment", + "type": "String" + }, + { + "name": "external", + "type": "Boolean" + } + ] + } +] \ No newline at end of file diff --git a/tests/unit/metadata/DockerComposeMetadata.spec.js b/tests/unit/metadata/DockerComposeMetadata.spec.js index e5447be..39bb879 100644 --- a/tests/unit/metadata/DockerComposeMetadata.spec.js +++ b/tests/unit/metadata/DockerComposeMetadata.spec.js @@ -1,21 +1,16 @@ -import DockerComposeMetadata from 'src/metadata/DockerComposeMetadata'; -import { DefaultData } from 'leto-modelizer-plugin-core'; +import { getComposatorMetadata } from 'tests/resources/metadata/getComposatorMetadata'; -describe('Test class: DockerComposeMetadata', () => { - describe('Test method: validate', () => { - it('should return true', () => { - expect(new DockerComposeMetadata().validate()).toEqual(true); - }); - }); - - describe('Test method: parse', () => { - it('should set components definitions to empty array', () => { - const pluginData = new DefaultData(); - pluginData.definitions.components = ['a']; - - new DockerComposeMetadata(pluginData).parse(); - - expect(pluginData.definitions.components).toEqual([]); +describe('Test DockerComposeMetadata', () => { + describe('Test methods', () => { + describe('Test method: validate', () => { + it('Should return true on valid metadata', () => { + const metadata = getComposatorMetadata('validMetadata', 'tests/resources/metadata/valid.json'); + expect(metadata.validate()).toBeTruthy(); + }); + it('Should return false on invalid metadata', () => { + const metadata = getComposatorMetadata('invalidMetadata', 'tests/resources/metadata/invalid.json'); + expect(metadata.validate()).toBeFalsy(); + }); }); }); }); From 663b96b7f88220eca325c6bf176a49915b056f90 Mon Sep 17 00:00:00 2001 From: Yassine-Chamkhi Date: Tue, 7 Nov 2023 10:50:39 +0100 Subject: [PATCH 04/11] update drawer --- src/draw/DockerComposeDrawer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/draw/DockerComposeDrawer.js b/src/draw/DockerComposeDrawer.js index bd48f99..c728458 100644 --- a/src/draw/DockerComposeDrawer.js +++ b/src/draw/DockerComposeDrawer.js @@ -23,7 +23,8 @@ class DockerComposeDrawer extends DefaultDrawer { ...options, minHeight: 80, minWidth: 110, - margin: 5, + margin: 10, + padding: 20, }); } } From 2b80fffd855ab6d99347510a49866d51ecb8e015 Mon Sep 17 00:00:00 2001 From: Yassine-Chamkhi Date: Tue, 7 Nov 2023 10:51:52 +0100 Subject: [PATCH 05/11] update configuration, add syntax highlighting --- src/configuration/syntax.js | 259 +++++++++++++++++++++++ src/models/DockerComposeConfiguration.js | 6 +- 2 files changed, 262 insertions(+), 3 deletions(-) create mode 100644 src/configuration/syntax.js diff --git a/src/configuration/syntax.js b/src/configuration/syntax.js new file mode 100644 index 0000000..db3976e --- /dev/null +++ b/src/configuration/syntax.js @@ -0,0 +1,259 @@ +export default { + name: 'docker-compose', + languageSettings: { + id: 'docker-compose', + extensions: ['.yaml', '.yml'], + aliases: ['docker-compose', 'docker compose'], + mimetypes: ['string'], + }, + languageConfiguration: { + comments: { + lineComment: '#', + blockComment: ['/*', '*/'], + }, + brackets: [ + ['{', '}'], + ['[', ']'], + ], + colorizedBracketPairs: [ + ['{', '}'], + ['[', ']'], + ], + autoClosingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: "'", close: "'", notIn: ['string'] }, + { open: '"', close: '"', notIn: ['string'] }, + ], + surroundingPairs: [ + { open: '{', close: '}' }, + { open: '[', close: ']' }, + { open: "'", close: "'" }, + { open: '"', close: '"' }, + ], + }, + tokenProvider: { + tokenPostfix: '.yaml', + + brackets: [ + { token: 'delimiter.bracket', open: '{', close: '}' }, + { token: 'delimiter.square', open: '[', close: ']' }, + ], + + keywords: [ + 'true', + 'True', + 'TRUE', + 'false', + 'False', + 'FALSE', + 'null', + 'Null', + 'Null', + '~', + ], + + numberInteger: /(?:0|[+-]?[0-9]+)/, + numberFloat: /(?:0|[+-]?[0-9]+)(?:\.[0-9]+)?(?:e[-+][1-9][0-9]*)?/, + numberOctal: /0o[0-7]+/, + numberHex: /0x[0-9a-fA-F]+/, + numberInfinity: /[+-]?\.(?:inf|Inf|INF)/, + numberNaN: /\.(?:nan|Nan|NAN)/, + numberDate: + /\d{4}-\d\d-\d\d([Tt ]\d\d:\d\d:\d\d(\.\d+)?(( ?[+-]\d\d?(:\d\d)?)|Z)?)?/, + + escapes: /\\(?:[btnfr\\'']|[0-7][0-7]?|[0-3][0-7]{2})/, + + tokenizer: { + root: [ + { include: '@whitespace' }, + { include: '@comment' }, + + // Directive + [/%[^ ]+.*$/, 'meta.directive'], + + // Document Markers + [/---/, 'operators.directivesEnd'], + [/\.{3}/, 'operators.documentEnd'], + + // Block Structure Indicators + [/[-?:](?= )/, 'operators'], + + { include: '@anchor' }, + { include: '@tagHandle' }, + { include: '@flowCollections' }, + { include: '@blockStyle' }, + + // Numbers + [/@numberInteger(?![ \t]*\S+)/, 'number'], + [/@numberFloat(?![ \t]*\S+)/, 'number.float'], + [/@numberOctal(?![ \t]*\S+)/, 'number.octal'], + [/@numberHex(?![ \t]*\S+)/, 'number.hex'], + [/@numberInfinity(?![ \t]*\S+)/, 'number.infinity'], + [/@numberNaN(?![ \t]*\S+)/, 'number.nan'], + [/@numberDate(?![ \t]*\S+)/, 'number.date'], + + // Key:Value pair + [ + /('.*?'|'.*?'|.*?)([ \t]*)(:)( |$)/, + ['type', 'white', 'operators', 'white'], + ], + + { include: '@flowScalars' }, + + // String nodes + [ + /.+$/, + { + cases: { + '@keywords': 'keyword', + '@default': 'string', + }, + }, + ], + ], + + // Flow Collection: Flow Mapping + object: [ + { include: '@whitespace' }, + { include: '@comment' }, + + // Flow Mapping termination + [/\}/, '@brackets', '@pop'], + + // Flow Mapping delimiter + [/,/, 'delimiter.comma'], + + // Flow Mapping Key:Value delimiter + [/:(?= )/, 'operators'], + + // Flow Mapping Key:Value key + [/(?:'.*?'|'.*?'|[^,{[]+?)(?=: )/, 'type'], + + // Start Flow Style + { include: '@flowCollections' }, + { include: '@flowScalars' }, + + // Scalar Data types + { include: '@tagHandle' }, + { include: '@anchor' }, + { include: '@flowNumber' }, + + // Other value (keyword or string) + [ + /[^},]+/, + { + cases: { + '@keywords': 'keyword', + '@default': 'string', + }, + }, + ], + ], + + // Flow Collection: Flow Sequence + array: [ + { include: '@whitespace' }, + { include: '@comment' }, + + // Flow Sequence termination + [/\]/, '@brackets', '@pop'], + + // Flow Sequence delimiter + [/,/, 'delimiter.comma'], + + // Start Flow Style + { include: '@flowCollections' }, + { include: '@flowScalars' }, + + // Scalar Data types + { include: '@tagHandle' }, + { include: '@anchor' }, + { include: '@flowNumber' }, + + // Other value (keyword or string) + [ + /[^\],]+/, + { + cases: { + '@keywords': 'keyword', + '@default': 'string', + }, + }, + ], + ], + + // Flow Scalars (quoted strings) + string: [ + [/[^\\'']+/, 'string'], + [/@escapes/, 'string.escape'], + [/\\./, 'string.escape.invalid'], + [ + /['']/, + { + cases: { + '$#==$S2': { token: 'string', next: '@pop' }, + '@default': 'string', + }, + }, + ], + ], + + // First line of a Block Style + multiString: [[/^( +).+$/, 'string', '@multiStringContinued.$1']], + + // Further lines of a Block Style + // Workaround for indentation detection + multiStringContinued: [ + [ + /^( *).+$/, + { + cases: { + '$1==$S2': 'string', + '@default': { token: '@rematch', next: '@popall' }, + }, + }, + ], + ], + + whitespace: [[/[ \t\r\n]+/, 'white']], + + // Only line comments + comment: [[/#.*$/, 'comment']], + + // Start Flow Collections + flowCollections: [ + [/\[/, '@brackets', '@array'], + [/\{/, '@brackets', '@object'], + ], + + // Start Flow Scalars (quoted strings) + flowScalars: [ + [/"/, 'string', '@string."'], + [/'/, 'string', '@string.\''], + ], + + // Start Block Scalar + blockStyle: [[/[>|][0-9]*[+-]?$/, 'operators', '@multiString']], + + // Numbers in Flow Collections (terminate with ,]}) + flowNumber: [ + [/@numberInteger(?=[ \t]*[,\]}])/, 'number'], + [/@numberFloat(?=[ \t]*[,\]}])/, 'number.float'], + [/@numberOctal(?=[ \t]*[,\]}])/, 'number.octal'], + [/@numberHex(?=[ \t]*[,\]}])/, 'number.hex'], + [/@numberInfinity(?=[ \t]*[,\]}])/, 'number.infinity'], + [/@numberNaN(?=[ \t]*[,\]}])/, 'number.nan'], + [/@numberDate(?=[ \t]*[,\]}])/, 'number.date'], + ], + + tagHandle: [ + [/![^ ]*/, 'tag'], + ], + + anchor: [ + [/[&*][^ ]+/, 'namespace'], + ], + }, + }, +}; diff --git a/src/models/DockerComposeConfiguration.js b/src/models/DockerComposeConfiguration.js index 63b7d5e..81a1a67 100644 --- a/src/models/DockerComposeConfiguration.js +++ b/src/models/DockerComposeConfiguration.js @@ -1,4 +1,5 @@ import { DefaultConfiguration, Tag } from 'leto-modelizer-plugin-core'; +import syntax from 'src/configuration/syntax'; /** * DockerCompose configuration. @@ -11,10 +12,9 @@ class DockerComposeConfiguration extends DefaultConfiguration { constructor(props) { super({ ...props, - defaultFileName: 'compose.yaml', - defaultFileExtension: 'yml', editor: { - ...props?.editor, + ...props.editor, + syntax, }, tags: [ new Tag({ type: 'language', value: 'DockerCompose' }), From faf27f6528715734aa8a53864ef19a31a1214f26 Mon Sep 17 00:00:00 2001 From: Yassine-Chamkhi Date: Tue, 7 Nov 2023 10:54:53 +0100 Subject: [PATCH 06/11] add DockerComposeComponent --- src/models/DockerComposeComponent.js | 31 +++++++ .../models/DockerComposeComponent.spec.js | 82 +++++++++++++++++++ 2 files changed, 113 insertions(+) create mode 100644 src/models/DockerComposeComponent.js create mode 100644 tests/unit/models/DockerComposeComponent.spec.js diff --git a/src/models/DockerComposeComponent.js b/src/models/DockerComposeComponent.js new file mode 100644 index 0000000..8db2bbc --- /dev/null +++ b/src/models/DockerComposeComponent.js @@ -0,0 +1,31 @@ +import { Component } from 'leto-modelizer-plugin-core'; + +/** + * Specific Docker compose component. + */ +class DockerComposeComponent extends Component { + /** + * Get attribute by name recursively. + * @param {ComponentAttribute[]} attributes - Array of component attributes. + * @param {string} name - Name of the attribute to search for. + * @returns {ComponentAttribute|null} Found attribute or null if not found. + * @private + */ + __getAttributeByName(attributes, name) { + for (let index = 0; index < attributes.length; index += 1) { + if (attributes[index].name === name) { + return attributes[index]; + } + if (attributes[index].type === 'Object' || attributes[index].type === 'Array') { + const attribute = this.__getAttributeByName(attributes[index].value, name); + if (attribute) { + return attribute; + } + } + } + + return null; + } +} + +export default DockerComposeComponent; diff --git a/tests/unit/models/DockerComposeComponent.spec.js b/tests/unit/models/DockerComposeComponent.spec.js new file mode 100644 index 0000000..d49af8f --- /dev/null +++ b/tests/unit/models/DockerComposeComponent.spec.js @@ -0,0 +1,82 @@ +import { ComponentAttribute } from 'leto-modelizer-plugin-core'; +import DockerComposeComponent from 'src/models/DockerComposeComponent'; + +describe('Test methods', () => { + describe('Test method: __getAttributeByName', () => { + it('Should find correct attribute when it exists', () => { + const component = new DockerComposeComponent(); + + const objectAttribute = new ComponentAttribute({ + name: 'parentObject', + type: 'Object', + value: [ + new ComponentAttribute({ + name: 'sonObject', + }), + new ComponentAttribute({ + name: 'test-attribute', + }), + ], + }); + + const wrongObjectAttribute = new ComponentAttribute({ + name: 'parentObject', + type: 'Object', + value: [ + new ComponentAttribute({ + name: 'sonObject', + }), + ], + }); + + const arrayAttribute = new ComponentAttribute({ + name: 'parentObject', + type: 'Array', + value: [ + new ComponentAttribute({ + name: 'sonObject', + }), + new ComponentAttribute({ + name: 'test-attribute', + }), + ], + }); + + const wrongAttribute = new ComponentAttribute({ + name: 'wrongName', + }); + + let attributes = [objectAttribute]; + expect(component.__getAttributeByName(attributes, 'test-attribute')?.name).toBe('test-attribute'); + attributes = [arrayAttribute]; + expect(component.__getAttributeByName(attributes, 'test-attribute')?.name).toBe('test-attribute'); + attributes = [wrongAttribute]; + expect(component.__getAttributeByName(attributes, 'test-attribute')).toBe(null); + attributes = [wrongObjectAttribute]; + expect(component.__getAttributeByName(attributes, 'test-attribute')).toBe(null); + }); + + it('Should return null when correct attribute does not exist', () => { + const component = new DockerComposeComponent(); + + const wrongObjectAttribute = new ComponentAttribute({ + name: 'parentObject', + type: 'Object', + value: [ + new ComponentAttribute({ + name: 'sonObject', + }), + ], + }); + + const wrongAttribute = new ComponentAttribute({ + name: 'wrongName', + }); + + let attributes = [wrongAttribute]; + expect(component.__getAttributeByName(attributes, 'test-attribute')).toBe(null); + attributes = [wrongObjectAttribute]; + expect(component.__getAttributeByName(attributes, 'test-attribute')).toBe(null); + }); + }); +}); From 2c62de21d8f3e0300d422489b9cc8f497291f666 Mon Sep 17 00:00:00 2001 From: RayenRomdhane Date: Tue, 7 Nov 2023 10:57:07 +0100 Subject: [PATCH 07/11] add data --- src/models/DockerComposeData.js | 112 ++++++++++++ tests/resources/models/DataGetLinkTests.js | 180 ++++++++++++++++++++ tests/unit/models/DockerComposeData.spec.js | 17 ++ 3 files changed, 309 insertions(+) create mode 100644 src/models/DockerComposeData.js create mode 100644 tests/resources/models/DataGetLinkTests.js create mode 100644 tests/unit/models/DockerComposeData.spec.js diff --git a/src/models/DockerComposeData.js b/src/models/DockerComposeData.js new file mode 100644 index 0000000..f66a676 --- /dev/null +++ b/src/models/DockerComposeData.js @@ -0,0 +1,112 @@ +import { + DefaultData, + ComponentLink, + ComponentLinkDefinition, +} from 'leto-modelizer-plugin-core'; +// import Component from 'src/models/DockerComposeComponent'; + +/** + * Specific Docker compose data. + * @augments {DefaultData} + */ +class DockerComposeData extends DefaultData { + /** + * Get all component links. + * @returns {ComponentLink[]} Array of component links. + */ + getLinks() { + const links = []; + + // Create depends_on links for Service Components + this.components.forEach((component) => { + const dependsOnAttribute = component.attributes.find( + ({ name }) => name === 'depends_on', + ); + if (dependsOnAttribute) { + dependsOnAttribute.value.forEach((item) => { + const definition = this.definitions.links.find( + ({ attributeRef }) => attributeRef === 'service', + ); + + links.push(new ComponentLink({ + definition, + source: component.id, + target: item.value.find( + ({ name }) => name.startsWith('service'), + ).value[0], + })); + }); + } + }); + + // Create volumes links for Service components + this.components.forEach((component) => { + const volumesAttribute = component.attributes.find( + ({ name }) => name === 'volumes', + ); + if (volumesAttribute) { + volumesAttribute.value.forEach((item) => { + const definition = this.definitions.links.find( + ({ attributeRef }) => attributeRef === 'volume-name', + ); + + links.push(new ComponentLink({ + definition, + source: component.id, + target: item.value.find( + ({ name }) => name.startsWith('volume'), + ).value[0], + })); + }); + } + }); + + // Create other links based on link definitions + this.definitions.links.forEach((definition) => { + const components = this.getComponentsByType(definition.sourceRef); + components.forEach((component) => { + const attribute = component.getAttributeByName(definition.attributeRef); + if (!attribute) { + return; + } + attribute.value.forEach((value) => { + links.push(new ComponentLink({ + definition, + source: component.id, + target: value, + })); + }); + }); + }); + + return links; + } + + /** + * Set link definition in link definitions. + * @param {string} type - Component type to link. + * @param {ComponentAttributeDefinition[]} definedAttributes - Component attribute definitions. + * @private + */ + __setLinkDefinitions(type, definedAttributes) { + definedAttributes.forEach((attributeDefinition) => { + if (attributeDefinition.type === 'Link') { + const linkDefinition = new ComponentLinkDefinition({ + type: attributeDefinition.linkType, + attributeRef: attributeDefinition.name, + sourceRef: type, + targetRef: attributeDefinition.linkRef, + color: attributeDefinition.linkColor, + width: attributeDefinition.linkWidth, + dashStyle: attributeDefinition.linkDashStyle, + }); + + this.definitions.links.push(linkDefinition); + } else if (attributeDefinition.type === 'Object' || attributeDefinition.type === 'Array') { + this.__setLinkDefinitions(type, attributeDefinition.definedAttributes); + } + }); + } +} + +export default DockerComposeData; diff --git a/tests/resources/models/DataGetLinkTests.js b/tests/resources/models/DataGetLinkTests.js new file mode 100644 index 0000000..4a2b923 --- /dev/null +++ b/tests/resources/models/DataGetLinkTests.js @@ -0,0 +1,180 @@ +import { ComponentAttribute } from 'leto-modelizer-plugin-core'; +import DockerComposeData from 'src/models/DockerComposeData'; +import DockerComposeComponent from 'src/models/DockerComposeComponent'; +import DockerComposeMetadata from 'src/metadata/DockerComposeMetadata'; + +const pluginData = new DockerComposeData(); +const metadata = new DockerComposeMetadata(pluginData); +metadata.parse(); + +// Component definitions +const dockerComposeDef = pluginData.definitions.components + .find(({ type }) => type === 'Docker-Compose'); +const serviceDef = pluginData.definitions.components + .find(({ type }) => type === 'Service'); +const networkDef = pluginData.definitions.components + .find(({ type }) => type === 'Network'); +const volumeDef = pluginData.definitions.components + .find(({ type }) => type === 'Volume'); + +// Common attributes definitions +const serviceImageAttributeDef = serviceDef.definedAttributes.find(({ name }) => name === 'image'); +const dependsOnLinkDef = serviceDef.definedAttributes + .find(({ name }) => name === 'depends_on').definedAttributes[0].definedAttributes + .find(({ type }) => type === 'Link'); + +const parentComposeAttribute = new ComponentAttribute({ + name: 'parentCompose', + type: 'String', + definition: serviceDef.definedAttributes + .find(({ name }) => name === 'parentCompose'), + value: 'veto-full-compose', +}); + +// Instantiating components +const dockerCompose = new DockerComposeComponent({ + id: 'veto-full-compose', + path: './veto-full-compose.yaml', + definition: dockerComposeDef, + attributes: [ + new ComponentAttribute({ + name: 'version', + type: 'String', + definition: dockerComposeDef.definedAttributes + .find(({ name }) => name === 'version'), + value: '3.9', + }), + ], +}); + +const veterinaryConfigServerService = new DockerComposeComponent({ + id: 'veterinary-config-server', + path: './veto-full-compose.yaml', + definition: serviceDef, + attributes: [ + new ComponentAttribute({ + name: 'image', + type: 'String', + definition: serviceImageAttributeDef, + value: 'veterinary-config-server:0.2', + }), + new ComponentAttribute({ + name: 'networks', + type: 'Array', + definition: serviceDef.definedAttributes + .find(({ name }) => name === 'networks'), + value: ['backend'], + }), + new ComponentAttribute({ ...parentComposeAttribute }), + + ], +}); + +const veterinaryMsService = new DockerComposeComponent({ + id: 'veterinary-ms', + path: './veto-full-compose.yaml', + definition: serviceDef, + attributes: [ + new ComponentAttribute({ + name: 'image', + type: 'String', + definition: serviceImageAttributeDef, + value: 'veterinary-ms:0.2', + }), + new ComponentAttribute({ + name: 'depends_on', + type: 'Array', + definition: serviceDef.definedAttributes.find(({ name }) => name === 'depends_on'), + value: [ + new ComponentAttribute({ + name: null, + type: 'Object', + value: [ + new ComponentAttribute({ + name: 'service_veterinary-ms_0', + type: 'Array', + definition: dependsOnLinkDef, + value: ['veterinary-config-server'], + }), + new ComponentAttribute({ + name: 'condition', + type: 'String', + value: 'service healthy', + }), + ], + }), + ], + }), + new ComponentAttribute({ + name: 'volumes', + type: 'Array', + definition: serviceDef.definedAttributes.find(({ name }) => name === 'volumes'), + value: [ + new ComponentAttribute({ + name: null, + type: 'Object', + value: [ + new ComponentAttribute({ + name: 'volume_veterinary-ms_0', + type: 'Array', + value: ['data'], + }), + new ComponentAttribute({ + name: 'mount-path', + type: 'String', + value: 'service healthy', + }), + ], + }), + ], + }), + new ComponentAttribute({ ...parentComposeAttribute }), + + ], +}); + +const backendNetwork = new DockerComposeComponent({ + id: 'backend', + path: './veto-full-compose.yaml', + definition: networkDef, + attributes: [ + new ComponentAttribute({ + name: 'driver', + type: 'String', + definition: networkDef.definedAttributes + .find(({ name }) => name === 'driver'), + value: 'custom-driver-0', + }), + new ComponentAttribute({ ...parentComposeAttribute }), + + ], +}); + +const dataVolume = new DockerComposeComponent({ + id: 'data', + path: './veto-full-compose.yaml', + definition: volumeDef, + attributes: [ + new ComponentAttribute({ + name: 'driver', + type: 'String', + definition: volumeDef.definedAttributes + .find(({ name }) => name === 'driver'), + value: 'custom-driver', + }), + new ComponentAttribute({ ...parentComposeAttribute }), + + ], +}); + +// Adding components to pluginData +pluginData.components.push(dockerCompose); +pluginData.components.push(backendNetwork); +pluginData.components.push(dataVolume); +pluginData.components.push(veterinaryConfigServerService); +pluginData.components.push(veterinaryMsService); + +// Invoke the setLinkDefinitions method to generate the links +pluginData.__setLinkDefinitions('Service', serviceDef.definedAttributes); + +export default pluginData; diff --git a/tests/unit/models/DockerComposeData.spec.js b/tests/unit/models/DockerComposeData.spec.js new file mode 100644 index 0000000..392eaad --- /dev/null +++ b/tests/unit/models/DockerComposeData.spec.js @@ -0,0 +1,17 @@ +import dataGetLinkTestsPluginData from 'tests/resources/models/DataGetLinkTests'; + +describe('DockerComposeData', () => { + describe('Test function: getLinks', () => { + it('should return component links based on service link attributes', () => { + const data = dataGetLinkTestsPluginData; + + const links = data.getLinks(); + expect(links.length).toBe(3); + expect(links).toContainEqual( + expect.objectContaining({ source: 'veterinary-ms', target: 'veterinary-config-server' }), + expect.objectContaining({ source: 'veterinary-ms', target: 'backend' }), + expect.objectContaining({ source: 'veterinary-ms', target: 'data' }), + ); + }); + }); +}); From 361538f16cb140906519f7e315525c74c3249fbd Mon Sep 17 00:00:00 2001 From: Yassine-Chamkhi Date: Tue, 7 Nov 2023 10:59:18 +0100 Subject: [PATCH 08/11] update plugin --- src/models/DockerComposePlugin.js | 17 ++++++++++------- tests/unit/models/DockerComposePlugin.spec.js | 16 +++++----------- 2 files changed, 15 insertions(+), 18 deletions(-) diff --git a/src/models/DockerComposePlugin.js b/src/models/DockerComposePlugin.js index 023da82..41e990b 100644 --- a/src/models/DockerComposePlugin.js +++ b/src/models/DockerComposePlugin.js @@ -1,7 +1,7 @@ import { DefaultPlugin, - DefaultData, } from 'leto-modelizer-plugin-core'; +import DockerComposeData from 'src/models/DockerComposeData'; import DockerComposeDrawer from 'src/draw/DockerComposeDrawer'; import DockerComposeMetadata from 'src/metadata/DockerComposeMetadata'; import DockerComposeParser from 'src/parser/DockerComposeParser'; @@ -10,20 +10,23 @@ import DockerComposeConfiguration from 'src/models/DockerComposeConfiguration'; import packageInfo from 'package.json'; /** - * DockerCompose plugin. + * Docker compose plugin. */ class DockerComposePlugin extends DefaultPlugin { /** * Default constructor. - * @param {object} [props] - Object that contains all properties to set. - * @param {object} [props.event] - Event manager. - * @param {Function} [props.event.next] - Function to emit event. + * @param {object} props - Plugin properties. + * @param {string} props.event - Event data. */ constructor(props = { event: null, }) { - const configuration = new DockerComposeConfiguration(); - const pluginData = new DefaultData(configuration, { + const configuration = new DockerComposeConfiguration({ + defaultFileName: 'docker-compose.yaml', + defaultFileExtension: 'yaml', + }); + + const pluginData = new DockerComposeData(configuration, { name: packageInfo.name, version: packageInfo.version, }, props.event); diff --git a/tests/unit/models/DockerComposePlugin.spec.js b/tests/unit/models/DockerComposePlugin.spec.js index 501984b..949bdcd 100644 --- a/tests/unit/models/DockerComposePlugin.spec.js +++ b/tests/unit/models/DockerComposePlugin.spec.js @@ -1,15 +1,9 @@ import DockerComposePlugin from 'src/models/DockerComposePlugin'; -describe('Test class: DockerComposePlugin', () => { - describe('Test constructor', () => { - it('Check variable initialization', () => { - const plugin = new DockerComposePlugin(); - - expect(plugin.data).not.toBeNull(); - expect(plugin.__drawer).not.toBeNull(); - expect(plugin.__parser).not.toBeNull(); - expect(plugin.__metadata).not.toBeNull(); - expect(plugin.__renderer).not.toBeNull(); - }); +describe('DockerComposePlugin', () => { + it('should create a DockerComposePlugin instance ', () => { + const plugin = new DockerComposePlugin(); + expect(plugin).not.toBeNull(); + expect(plugin).toBeInstanceOf(DockerComposePlugin); }); }); From c41cd12966d48c23319d4dd5a7d7fb86c41ab87d Mon Sep 17 00:00:00 2001 From: Yassine-Chamkhi Date: Tue, 7 Nov 2023 11:04:18 +0100 Subject: [PATCH 09/11] update index tests --- tests/unit/index.spec.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/tests/unit/index.spec.js b/tests/unit/index.spec.js index 17237e7..f93885a 100644 --- a/tests/unit/index.spec.js +++ b/tests/unit/index.spec.js @@ -4,4 +4,12 @@ describe('Test index of project', () => { it('should return DockerComposePlugin', () => { expect(new Plugin().constructor.name).toEqual('DockerComposePlugin'); }); + + it('Index should return all needed objects', () => { + expect(Plugin.PluginDrawer).not.toBeNull(); + expect(Plugin.PluginMetadata).not.toBeNull(); + expect(Plugin.PluginParser).not.toBeNull(); + expect(Plugin.PluginRenderer).not.toBeNull(); + expect(Plugin.resources).not.toBeNull(); + }); }); From 4ac8fb839ac055fec94f802f8551e48365bf5ea4 Mon Sep 17 00:00:00 2001 From: Yassine-Chamkhi Date: Tue, 7 Nov 2023 11:13:33 +0100 Subject: [PATCH 10/11] update dependencies, install libraries js-yaml, lidy-js --- package.json | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 6c837a2..6900353 100644 --- a/package.json +++ b/package.json @@ -30,26 +30,31 @@ }, "homepage": "https://github.com/ditrit/dockercomposator-plugin#readme", "dependencies": { - "leto-modelizer-plugin-core": "github:ditrit/leto-modelizer-plugin-core#0.17.0" + "js-yaml": "^4.1.0", + "leto-modelizer-plugin-core": "github:ditrit/leto-modelizer-plugin-core#0.21.0", + "lidy-js": "github:ditrit/lidy-js.git#main" }, "devDependencies": { - "@babel/core": "=7.22.9", - "@babel/preset-env": "=7.22.9", - "babel-jest": "=29.6.1", - "babel-loader": "=9.1.3", - "better-docs": "=2.7.2", - "eslint": "=8.45.0", - "eslint-config-airbnb-base": "=15.0.0", - "eslint-formatter-json-relative": "=0.1.0", - "eslint-plugin-import": "=2.27.5", - "eslint-plugin-jest": "=27.2.3", - "eslint-plugin-jsdoc": "=46.4.4", - "eslint-webpack-plugin": "=4.0.1", - "jest": "=29.6.1", - "jest-environment-jsdom": "=29.6.1", - "jest-sonar-reporter": "=2.0.0", - "jsdoc": "=4.0.2", - "webpack": "=5.88.2", - "webpack-cli": "=5.1.4" + "@babel/core": "^7.23.2", + "@babel/preset-env": "^7.23.2", + "babel-jest": "^29.7.0", + "babel-loader": "^9.1.3", + "better-docs": "^2.7.2", + "eslint": "^8.51.0", + "eslint-config-airbnb-base": "^15.0.0", + "eslint-formatter-json-relative": "^0.1.0", + "eslint-plugin-import": "^2.28.1", + "eslint-plugin-jest": "^27.4.2", + "eslint-plugin-jsdoc": "^44.2.7", + "eslint-webpack-plugin": "^4.0.1", + "jest": "^29.7.0", + "jest-environment-jsdom": "^29.7.0", + "jest-sonar-reporter": "^2.0.0", + "js-yaml": "^4.1.0", + "jsdoc": "^4.0.2", + "leto-modelizer-plugin-core": "github:ditrit/leto-modelizer-plugin-core#0.21.0", + "lidy-js": "github:ditrit/lidy-js#main", + "webpack": "^5.89.0", + "webpack-cli": "^5.1.4" } } From 363dd4df7a2e2c7058dac4b2177db9cc91a3a660 Mon Sep 17 00:00:00 2001 From: Yassine-Chamkhi Date: Tue, 7 Nov 2023 11:17:33 +0100 Subject: [PATCH 11/11] update changelog --- changelog.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/changelog.md b/changelog.md index eb6508d..1ef594b 100644 --- a/changelog.md +++ b/changelog.md @@ -5,8 +5,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) -## [Unreleased] +## [0.1.0] ### Added -- Setup project. +- Add yaml syntax highlighting +- Add Docker-Compose metadata. +- leto-modelizer-plugin-core version 0.21.0 \ No newline at end of file