UNPKG

podletjs

Version:

JavaScript port of Podlet - Generate Podman Quadlet files from Docker run commands and compose files

395 lines (327 loc) 13.1 kB
import { describe, it, expect, beforeEach, afterEach } from '@jest/globals'; import { QuadletGenerator } from '../../src/quadlet-generator.js'; import { Container } from '../../src/container.js'; import { ComposeParser } from '../../src/compose-parser.js'; import fs from 'fs-extra'; import path from 'path'; import tmp from 'tmp'; describe('PodletJS E2E Tests', () => { let tempDir; beforeEach(() => { tempDir = tmp.dirSync({ unsafeCleanup: true }); }); afterEach(() => { if (tempDir) { tempDir.removeCallback(); } }); describe('Container to Quadlet Generation', () => { it('should generate quadlet file from container configuration', () => { const container = new Container(); container.setImage('nginx:alpine'); container.setContainerName('web-server'); container.addPublishPort('8080:80'); const result = QuadletGenerator.generateFile(container); expect(result).toContain('[Container]'); expect(result).toContain('Image=nginx:alpine'); expect(result).toContain('ContainerName=web-server'); expect(result).toContain('PublishPort=8080:80'); }); it('should generate complex container configuration', () => { const container = new Container(); container.setImage('postgres:13'); container.setContainerName('database'); container.addPublishPort('5432:5432'); container.addEnvironment('POSTGRES_DB=myapp'); container.addEnvironment('POSTGRES_USER=user'); container.addEnvironment('POSTGRES_PASSWORD=secret'); container.addVolume('/data/postgres:/var/lib/postgresql/data:Z'); container.user = '999:999'; container.dropCapability.push('ALL'); container.addCapability.push('SETGID'); container.addCapability.push('SETUID'); const result = QuadletGenerator.generateFile(container); expect(result).toContain('Image=postgres:13'); expect(result).toContain('ContainerName=database'); expect(result).toContain('PublishPort=5432:5432'); expect(result).toContain('Environment=POSTGRES_DB=myapp'); expect(result).toContain('Environment=POSTGRES_USER=user'); expect(result).toContain('Environment=POSTGRES_PASSWORD=secret'); expect(result).toContain('Volume=/data/postgres:/var/lib/postgresql/data:Z'); expect(result).toContain('User=999:999'); expect(result).toContain('DropCapability=ALL'); expect(result).toContain('AddCapability=SETGID SETUID'); }); it('should handle container with network options', () => { const container = new Container(); container.setImage('redis:alpine'); container.setContainerName('app'); container.network.push('frontend'); container.networkAlias.push('web'); container.dns.push('8.8.8.8'); container.hostName = 'myapp'; const result = QuadletGenerator.generateFile(container); expect(result).toContain('Image=redis:alpine'); expect(result).toContain('ContainerName=app'); expect(result).toContain('Network=frontend'); expect(result).toContain('NetworkAlias=web'); expect(result).toContain('DNS=8.8.8.8'); expect(result).toContain('HostName=myapp'); }); }); describe('Docker Compose File Parsing', () => { it('should parse simple docker-compose.yml and generate quadlet files', async () => { const composeContent = ` version: '3.8' services: web: image: nginx:alpine ports: - "8080:80" environment: - NGINX_HOST=example.com volumes: - ./html:/usr/share/nginx/html:ro `; const composeFile = path.join(tempDir.name, 'docker-compose.yml'); await fs.writeFile(composeFile, composeContent); const composeParser = new ComposeParser(); const services = await composeParser.parseFile(composeFile); expect(services).toHaveProperty('web'); const webContainer = services.web; const result = QuadletGenerator.generateFile(webContainer); expect(result).toContain('[Container]'); expect(result).toContain('Image=nginx:alpine'); expect(result).toContain('PublishPort=8080:80'); expect(result).toContain('Environment=NGINX_HOST=example.com'); }); it('should parse multi-service docker-compose.yml', async () => { const composeContent = ` version: '3.8' services: web: image: nginx:alpine ports: - "8080:80" depends_on: - database networks: - frontend - backend database: image: postgres:13 environment: POSTGRES_DB: myapp POSTGRES_USER: user POSTGRES_PASSWORD: secret volumes: - db_data:/var/lib/postgresql/data networks: - backend networks: frontend: backend: volumes: db_data: `; const composeFile = path.join(tempDir.name, 'docker-compose.yml'); await fs.writeFile(composeFile, composeContent); const composeParser = new ComposeParser(); const services = await composeParser.parseFile(composeFile); expect(services).toHaveProperty('web'); expect(services).toHaveProperty('database'); // Web service tests const webResult = QuadletGenerator.generateFile(services.web); expect(webResult).toContain('Image=nginx:alpine'); expect(webResult).toContain('PublishPort=8080:80'); expect(webResult).toContain('Network=frontend'); expect(webResult).toContain('Network=backend'); // Database service tests const dbResult = QuadletGenerator.generateFile(services.database); expect(dbResult).toContain('Image=postgres:13'); expect(dbResult).toContain('Environment=POSTGRES_DB=myapp'); expect(dbResult).toContain('Volume=db_data:/var/lib/postgresql/data'); expect(dbResult).toContain('Network=backend'); }); }); describe('File Generation and Writing', () => { it('should generate and write quadlet files to filesystem', async () => { const container = new Container(); container.setImage('node:16-alpine'); container.setContainerName('test-app'); container.addPublishPort('3000:3000'); const quadletContent = QuadletGenerator.generateFile(container); const outputFile = path.join(tempDir.name, 'test-app.container'); await fs.writeFile(outputFile, quadletContent); expect(await fs.pathExists(outputFile)).toBe(true); const fileContent = await fs.readFile(outputFile, 'utf8'); expect(fileContent).toContain('[Container]'); expect(fileContent).toContain('Image=node:16-alpine'); expect(fileContent).toContain('ContainerName=test-app'); expect(fileContent).toContain('PublishPort=3000:3000'); }); it('should generate multiple quadlet files from compose', async () => { const composeContent = ` version: '3.8' services: web: image: nginx:alpine ports: - "8080:80" api: image: node:16-alpine ports: - "3000:3000" `; const composeFile = path.join(tempDir.name, 'docker-compose.yml'); await fs.writeFile(composeFile, composeContent); const composeParser = new ComposeParser(); const services = await composeParser.parseFile(composeFile); // Write each service to its own .container file for (const [serviceName, container] of Object.entries(services)) { const quadletContent = QuadletGenerator.generateFile(container); const outputFile = path.join(tempDir.name, `${serviceName}.container`); await fs.writeFile(outputFile, quadletContent); expect(await fs.pathExists(outputFile)).toBe(true); const fileContent = await fs.readFile(outputFile, 'utf8'); expect(fileContent).toContain('[Container]'); } // Verify specific files exist expect(await fs.pathExists(path.join(tempDir.name, 'web.container'))).toBe(true); expect(await fs.pathExists(path.join(tempDir.name, 'api.container'))).toBe(true); }); }); describe('Advanced Quadlet Options', () => { it('should generate quadlet with Unit, Service, and Install sections', () => { const container = new Container(); container.setImage('nginx:alpine'); container.setContainerName('production-app'); container.addPublishPort('8080:80'); const options = { unit: { description: 'Production Web Server', after: ['network.target'], wants: ['network-online.target'] }, service: { restart: 'always', restartSec: '10' }, install: { wantedBy: ['multi-user.target'] } }; const result = QuadletGenerator.generateFile(container, options); expect(result).toContain('[Unit]'); expect(result).toContain('Description=Production Web Server'); expect(result).toContain('After=network.target'); expect(result).toContain('Wants=network-online.target'); expect(result).toContain('[Container]'); expect(result).toContain('Image=nginx:alpine'); expect(result).toContain('[Service]'); expect(result).toContain('Restart=always'); expect(result).toContain('RestartSec=10'); expect(result).toContain('[Install]'); expect(result).toContain('WantedBy=multi-user.target'); }); it('should generate quadlet with GlobalArgs section', () => { const container = new Container(); container.setImage('nginx:alpine'); container.setContainerName('debug-app'); const options = { globals: { podmanArgs: '--log-level=debug --events-backend=file' } }; const result = QuadletGenerator.generateFile(container, options); expect(result).toContain('[GlobalArgs]'); expect(result).toContain('PodmanArgs=--log-level=debug --events-backend=file'); }); }); describe('Error Handling', () => { it('should handle invalid container configuration', () => { const container = new Container(); // Container without image should fail validation expect(() => { container.validate(); }).toThrow('Image is required'); }); it('should handle non-existent compose file', async () => { const nonExistentFile = path.join(tempDir.name, 'does-not-exist.yml'); const composeParser = new ComposeParser(); await expect(composeParser.parseFile(nonExistentFile)).rejects.toThrow(); }); it('should handle invalid compose YAML', async () => { const invalidYaml = ` version: 3.8 services: web: image: nginx ports - "80:80" `; const composeFile = path.join(tempDir.name, 'invalid-compose.yml'); await fs.writeFile(composeFile, invalidYaml); const composeParser = new ComposeParser(); await expect(composeParser.parseFile(composeFile)).rejects.toThrow(); }); }); describe('Real-world Scenarios', () => { it('should handle WordPress with MySQL stack', async () => { const composeContent = ` version: '3.8' services: wordpress: image: wordpress:latest ports: - "8080:80" environment: WORDPRESS_DB_HOST: mysql WORDPRESS_DB_USER: wordpress WORDPRESS_DB_PASSWORD: secret WORDPRESS_DB_NAME: wordpress volumes: - wordpress_data:/var/www/html depends_on: - mysql networks: - wordpress-network mysql: image: mysql:8.0 environment: MYSQL_DATABASE: wordpress MYSQL_USER: wordpress MYSQL_PASSWORD: secret MYSQL_ROOT_PASSWORD: rootsecret volumes: - mysql_data:/var/lib/mysql networks: - wordpress-network volumes: wordpress_data: mysql_data: networks: wordpress-network: `; const composeFile = path.join(tempDir.name, 'wordpress-compose.yml'); await fs.writeFile(composeFile, composeContent); const composeParser = new ComposeParser(); const services = await composeParser.parseFile(composeFile); expect(services).toHaveProperty('wordpress'); expect(services).toHaveProperty('mysql'); // WordPress tests const wpResult = QuadletGenerator.generateFile(services.wordpress); expect(wpResult).toContain('Image=wordpress:latest'); expect(wpResult).toContain('PublishPort=8080:80'); expect(wpResult).toContain('Environment=WORDPRESS_DB_HOST=mysql'); expect(wpResult).toContain('Volume=wordpress_data:/var/www/html'); expect(wpResult).toContain('Network=wordpress-network'); // MySQL tests const mysqlResult = QuadletGenerator.generateFile(services.mysql); expect(mysqlResult).toContain('Image=mysql:8.0'); expect(mysqlResult).toContain('Environment=MYSQL_DATABASE=wordpress'); expect(mysqlResult).toContain('Volume=mysql_data:/var/lib/mysql'); expect(mysqlResult).toContain('Network=wordpress-network'); }); }); });