1

Question: Has anyone been able to work with the js-yaml library to write a docker compose like this?

Context:

I am working on dotnet templates and have some pre-defined application architecture patterns for our teams. I would like to take it a step further and create the docker compose for the entirety of the ecosystem.

The main issue is on this line that I need to change the tokens and write to the YAML.

I have tried both of the following in the template and cannot have valid YAML output.

command: "./daprd -app-id ecosystemtemplate-${application} -app-port ${EXPOSED_PORT:-4443} --resources-path /app/config"
command: [ "./daprd", "-app-id", "ecosystemtemplate-${application}", "-app-port", "${EXPOSED_PORT:-4443}", "--resources-path", "/app/config"

How do I have the js-yaml lib respect the items that are quoted and it should preserve the quotes?

Taking the above command YAML and running it through json2yaml.com, the YAML->json looks correct, as does the json->YAML. To recreate this, I first copy the template YAML into the UI, then I copy the json and paste it again, thus updating the YAML output, which looks correct.

enter image description here

If I run this using the nodejs, I have incorrect output in the YAML. the image, command, and network_mode all need quotes. I have hacked at this too many ways to list.

enter image description here

The PoC will become much more involved when this moves to product-ready enterprise-scale applications.

enter image description here

Example:

From the template, I create a solution.json file that contains information that the ecosystem template uses.

enter image description here

The following code is used to read the solution.json

async function createDockerComposeYaml () {
    console.log("Creating Docker Compose");
    const fileName = 'docker-compose.yml';
    const applicationDescriptor = '${application}';

    await (async () => {
        try {
          await fs.unlink(fileName);
        } catch (e) {
          // Swallow the error.
          // Normally happens when the file does not exist the first time.
        }
      })();

    try {
        const dockerComposeYaml = await fs.readFile('./docker-templates/compose/docker-compose-template.yml', 'utf-8');
        const serviceYamlTokenized = await fs.readFile('./docker-templates/compose/docker-compose-service-template.yml', 'utf-8');
        const daprSidecarYamlTokenized = await fs.readFile('./docker-templates/compose/docker-compose-service-dapr-sidecar-template.yml', 'utf-8');

        let dockerCompose = YAML.load(dockerComposeYaml);

        solutions.forEach(solution => {
            // Build the service for the application.
            let serviceYaml = serviceYamlTokenized.replaceAll('${application}', solution.name.toLowerCase());
            serviceYaml = serviceYaml.replace('${dockerfile}', solution.dockerfile);
            const service = YAML.load(serviceYaml);

            // Build the service for the application dapr sidecar.
            const serviceSidecarYaml = daprSidecarYamlTokenized.replaceAll('${application}', solution.name.toLowerCase());
            const serviceSidecar = YAML.load(serviceSidecarYaml);

            if (dockerCompose.services === null) {
                dockerCompose.services = [];
            }

            dockerCompose.services.push(service);
            dockerCompose.services.push(serviceSidecar);
        });

        writeFile(fileName, YAML.dump(dockerCompose, { 'forceQuotes': true }));
    } catch(e) {
        console.log(e);
    }
}

Content:

solution.json

{
  "name": "DddTemplate",
  "template": "ddd",
  "dockerfile": "DddTemplate/src/DddTemplate.Api/Dockerfile",
  "launchSettings": "DddTemplate/src/DddTemplate.Api/Properties/launchSettings.json"
}

docker-compose-template.yml

version: '3.7'
name: EcosystemTemplate

volumes:

services:

docker-compose-service-template.yml

  ${application}:
    image: ${DOCKER_REGISTRY-}${application}:${IMAGE_TAG}
    build:
      context: .
      dockerfile: "${dockerfile}"
      args:
        - IMG_REGISTRY=${REGISTRY:-mcr.microsoft.com}
    environment:
      - ASPNETCORE_URLS=${ASPNETCORE_URLS:-https://*:4443}
    ports:
      - "43301:${EXPOSED_PORT:-4443}"
    volumes:
      - ~/.aspnet/https:/https:ro

docker-compose-service-dapr-sidecar-template.yml

  ${application}-dapr:
    image: "daprio/daprd:latest"
    command: "./daprd -app-id ${application} -app-port ${EXPOSED_PORT:-4443} --resources-path /app/config"
    depends_on:
      - ${application}
    network_mode: "service:${application}"
    volumes_from:
      - ${application}
Meester Over
  • 161
  • 2
  • 13

1 Answers1

1

Here's a quick-and-dirty suggestion:

You could disable quotes, and use the replacer function, and then add some placeholder only on fields and values you want to modify (you can modify the code accordingly), and then those values will be quoted, and then you create YAML file, but remove the placeholder from it just before you write the file.

Try this:

const somePlaceholder = '@@';

// add fields that you want to process
const fields = ['image', 'command', 'network_mode'];

function myReplacer(key, value) {

    if (typeof value === 'string') {

        if (fields.includes(key)) {
        // or
        // if (!value.includes('${')) {

            // handle command field not being quoted by adding quotes
            if(key === 'command') {

                return `'${somePlaceholder}${value}${somePlaceholder}'`
            } else {

                return `${somePlaceholder}${value}${somePlaceholder}`;
            }

        } else {
            return value;
        }
    } else {
        return value;
    }
}


// replace placeholder
const replaced = YAML.dump(dockerCompose, { replacer: myReplacer }).replaceAll('@@', '');

writeFile(fileName, replaced);
traynor
  • 5,490
  • 3
  • 13
  • 23
  • Nice traynor, this is working for the most part. I just need to figure out how to properly quote `command`, it still looks like this for me. I cannot figure out from where the '>-' comes or what it means in the YAML. --- command: >- ./daprd -app-id invoices -app-port ${EXPOSED_PORT:-4443} --resources-path /app/config – Meester Over Aug 26 '23 at 20:19
  • FYI, this is a node script that comes with a template, so it does not have to be production hardened IMO. This is to improve our developer experience. I do like the creative thinking. – Meester Over Aug 26 '23 at 20:21
  • ah, right... it's strange. Well, I guess a bit more hardcoding wouldn't hurt, so, you could add extra quotes to that field: `if(key === 'command') { return \`'${somePlaceholder}${value}${somePlaceholder}'\` }` It should probably take some digging around source code to figure out what's happening with the quotes.. The YAML file seems to be valid, it passes online validators – traynor Aug 26 '23 at 20:35
  • That is still not working for me. Still putting the weird ">-" at the front of the command. I really do not want to dig through OSS code to find this bug, but I may have to. I hacked it with this -- .replaceAll('>-', '');. However, I am still not having quotes around the command value. I guess I could see if docker compose will run the command properly without quotes, but doubtful. – Meester Over Aug 26 '23 at 20:46
  • I've edited the code, it adds single quote.. that seems to be valid YAML syntax: [How do I break a string in YAML over multiple lines?](https://stackoverflow.com/questions/3790454/how-do-i-break-a-string-in-yaml-over-multiple-lines) – traynor Aug 26 '23 at 20:48
  • I just saw your update and that looks to work. Thanks for helping me hack around the OSS. I posted a late night rant several hours after my bedtime as an issue on github. I should go clean it up to make it a bit less grumpy. Unsure if they are still maintaining the lib though. This appears to be a good answer. Give me another day to verify please, then I will likely accept it as the hack-around. – Meester Over Aug 26 '23 at 20:52
  • great. well, lots of parsers use a callback function which allows to modify data, so I guess replacer function is the way to go for doing such specific modifications.. Although, it's not clear where these quotes are coming from, and why it didn't add quote to string starting with `./` in the `command` field.. I guess it's something here somewhere..: https://github.com/nodeca/js-yaml/blob/master/lib/dumper.js – traynor Aug 26 '23 at 21:00