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

Request: accept async function as a job step or a workflow job #160

Open
1 task done
chabou opened this issue Nov 12, 2022 · 1 comment
Open
1 task done

Request: accept async function as a job step or a workflow job #160

chabou opened this issue Nov 12, 2022 · 1 comment

Comments

@chabou
Copy link

chabou commented Nov 12, 2022

Is there an existing issue that is already proposing this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe it

Given a complex configuration with some custom generated jobs with specific step like this one:

import * as CircleCI from "@circleci/circleci-config-sdk";

// Components
const config = new CircleCI.Config();
const dockerExec = new CircleCI.executors.DockerExecutor("cimg/base:stable");
const workflow = new CircleCI.Workflow("main");

// Define "reusable" job, with native JS
type customJobParameters = {
  name: string;
  cluster: string;
  container_name: string;
  task_role: string;
};
const createCustomJob = (parameters: customJobParameters) => {
  const stepsToRun = [
    new CircleCI.commands.Checkout(),
    new CircleCI.commands.Run({ command: "echo 'Hello World'" }),
  ];

  // Add deploy step if tag
  // https://circleci.com/docs/variables/
  if (process.env.CIRCLE_TAG) {
    stepsToRun.push(new CircleCI.commands.Run({ command: "echo 'Deploying'" }));
  }
  return new CircleCI.Job(parameters.name, dockerExec, stepsToRun);
};

const jobDefinitions: customJobParameters[] = [
  {
    name: "job1",
    cluster: "cluster",
    container_name: "container_name",
    task_role: "task_role",
  },
  {
    name: "job2",
    cluster: "cluster",
    container_name: "container_name",
    task_role: "task_role",
  },
];

// Add jobs to workflow

jobDefinitions.forEach((jobDefinition) => {
  workflow.addJob(createCustomJob(jobDefinition));
});

config.addWorkflow(workflow);

// Print config
console.log(config.stringify());

I want to add a Step that can only be generated asynchronously. For example I want to send a slack notification that contains the git author. Something like:

const slackCommand = async () => {
  const author = await getAuthor() // async function with some git calls
  return new CircleCI.reusable.ReusedCommand(orbSlack.commands['notify'], {
    name: 'notify-author',
    event: 'fail',
    custom: JSON.stringify({
      text: `Commit by ${author}`,
    }),
  })
} 

For now, I have to make all the function call chain, asynchronous:

import * as CircleCI from '@circleci/circleci-config-sdk'

// Components
const config = new CircleCI.Config()
const dockerExec = new CircleCI.executors.DockerExecutor('cimg/base:stable')
const workflow = new CircleCI.Workflow('main')

// Asynchronous step
const createSlackCommand = async () => {
  const author = await getAuthor() // async function with some git calls
  return new CircleCI.reusable.ReusedCommand(orbSlack.commands['notify'], {
    name: 'notify-author',
    event: 'fail',
    custom: JSON.stringify({
      text: `Commit by ${author}`,
    }),
  })
}

// Define "reusable" job, with native JS
type customJobParameters = {
  name: string
  cluster: string
  container_name: string
  task_role: string
}
const createCustomJob = async (parameters: customJobParameters) => {
  const slackCmd = await createSlackCommand()
  const stepsToRun = [
    new CircleCI.commands.Checkout(),
    new CircleCI.commands.Run({command: "echo 'Hello World'"}),
    slackCmd,
  ]

  // Add deploy step if tag
  // https://circleci.com/docs/variables/
  if (process.env.CIRCLE_TAG) {
    stepsToRun.push(new CircleCI.commands.Run({command: "echo 'Deploying'"}))
  }
  return new CircleCI.Job(parameters.name, dockerExec, stepsToRun)
}

const jobDefinitions: customJobParameters[] = [
  {
    name: 'job1',
    cluster: 'cluster',
    container_name: 'container_name',
    task_role: 'task_role',
  },
  {
    name: 'job2',
    cluster: 'cluster',
    container_name: 'container_name',
    task_role: 'task_role',
  },
]

// Add jobs to workflow
async function main() {
  jobDefinitions.forEach(async jobDefinition => {
    workflow.addJob(await createCustomJob(jobDefinition))
  })

  config.addWorkflow(workflow)

  // Print config
  console.log(config.stringify())
}

main().catch(console.error)

Describe the solution you'd like

It would be awesome if steps array in Job constructor, accepts an array of Command OR async function that returns a Command.
It could lead to make this possible:

import * as CircleCI from '@circleci/circleci-config-sdk'

// Components
const config = new CircleCI.Config()
const dockerExec = new CircleCI.executors.DockerExecutor('cimg/base:stable')
const workflow = new CircleCI.Workflow('main')

// Asynchronous step
const createSlackCommand = async () => {
  const author = await getAuthor() // async function with some git calls
  return new CircleCI.reusable.ReusedCommand(orbSlack.commands['notify'], {
    name: 'notify-author',
    event: 'fail',
    custom: JSON.stringify({
      text: `Commit by ${author}`,
    }),
  })
}

// Define "reusable" job, with native JS
type customJobParameters = {
  name: string
  cluster: string
  container_name: string
  task_role: string
}
const createCustomJob = (parameters: customJobParameters) => {
  const slackCmd = createSlackCommand()
  const stepsToRun = [
    new CircleCI.commands.Checkout(),
    new CircleCI.commands.Run({command: "echo 'Hello World'"}),
    createSlackCommand,
  ]

  // Add deploy step if tag
  // https://circleci.com/docs/variables/
  if (process.env.CIRCLE_TAG) {
    stepsToRun.push(new CircleCI.commands.Run({command: "echo 'Deploying'"}))
  }
  return new CircleCI.Job(parameters.name, dockerExec, stepsToRun)
}

const jobDefinitions: customJobParameters[] = [
  {
    name: 'job1',
    cluster: 'cluster',
    container_name: 'container_name',
    task_role: 'task_role',
  },
  {
    name: 'job2',
    cluster: 'cluster',
    container_name: 'container_name',
    task_role: 'task_role',
  },
]

// Add jobs to workflow
jobDefinitions.forEach(jobDefinition => {
  workflow.addJob(createCustomJob(jobDefinition))
})

config.addWorkflow(workflow)

// Print config
console.log(config.stringify())

It would be very convenient to do the same with jobs array parameter for Workflow constructor.

Teachability, documentation, adoption, migration strategy

Typescript (and maybe example) would be enough to document it

What is the motivation / use case for changing the behavior?

With more powerful primitive for async job/command generation, it will help users to use more js mechanism instead of 2.1 yaml.

@KyleTryon
Copy link
Contributor

Makes sense! And should be doable! I think it will make the final compilation done in stringify likely an async function (major/breaking change) but it makes sense.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants