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

Run Nx dependency graph backwards (leaves first) #9322

Open
HadiModarres opened this issue Mar 15, 2022 · 19 comments
Open

Run Nx dependency graph backwards (leaves first) #9322

HadiModarres opened this issue Mar 15, 2022 · 19 comments
Assignees
Labels
community This is a good first issue for contributing scope: core core nx functionality type: feature

Comments

@HadiModarres
Copy link

HadiModarres commented Mar 15, 2022

Description

I need to run certain command for my packages in a dependency graph, but backwards. So the tree of dependencies will have commands run around the edges at the leaves first the moving to the root.

Motivation

To successfully remove the deployed stacks on AWS we need to destroy stacks that aren't depended on first. So if I have stacks that depend on each other like: stack1 -> stack2 -> [stack3, stack4] I need stacks 3, 4 to be destroyed first then stack2 then stack1. This is the reverse of deployment in which we want stack1 to be deployed first and so on.

@AgentEnder AgentEnder added the scope: core core nx functionality label Mar 15, 2022
@seriouscoderone
Copy link

seriouscoderone commented Apr 12, 2022

I need this features as well.

my nx.json configures my aws dependencies like this

"targetDependencies": {
    "build": [
      {
        "target": "build",
        "projects": "dependencies"
      }
    ],
    "deploy": [
      {
        "target": "deploy",
        "projects": "dependencies"
      }
    ]
  },

and each project.json for my aws projects specifies adds in the implicit dependencies

i.e.

  "implicitDependencies": ["service-framework-networking", "service-framework-layer-transunion-ssl-layer"]

The above allows me to build and deploy my AWS cdk stacks...

but I want to add a destroy command that will reverse the dependencies, and destroy starting with the leaves

@jonathanmorley
Copy link
Contributor

jonathanmorley commented Aug 26, 2022

I think this could be neatly solved by adding support for dependents as a valid value to projects, i.e.

"targetDependencies": {
    "destroy": [
      {
        "target": "destroy",
        "projects": "dependents"
      }
    ]
  }
}

@ThomasAribart
Copy link

@HadiModarres @AgentEnder I can try implementing this feature if it's okay for you 👍 #Hacktoberfest

@barakcoh
Copy link

we've found that reversing the order of yarn nx affected:apps achieves the desired effect (we're also using it to destroy Pulumi stacks)

@ThomasAribart
Copy link

@barakcoh Sure but how did you achieve that ?

@barakcoh
Copy link

@ThomasAribart like so

function getAffectedDeployStackApps() {
  return new Promise<string[]>((resolve) => {
    exec(
      'yarn nx affected:apps base=HEAD~1 | grep stack | xargs',
      (_, stdout, __) => {
        resolve(
          stdout
            .trim()
            .replace('- ', ' - ')
            .split(' - ')
            .filter(Boolean)
            .reverse()
        );
      }
    );
  });
}

@barakcoh
Copy link

@ThomasAribart apparently, the solution we had in place didn't work reliably. we replaced it with a proper topological sort:

async function destroyStacks() {
  try {
    const adjList = new Map<string, string[]>();
    const affected = JSON.parse(
      (
        await $`yarn -s nx print-affected`
      ).toString()
    );

    const dependencies = Object.values(
      affected['projectGraph']['dependencies']
    ) as Edge[][];
    chain(dependencies)
      .flatten()
      .forEach(({ source, target }) => {
        console.log(`Adding dependency: ${source} -> ${target}`);
        adjList.set(source, (adjList.get(source) ?? []).concat(target));
      })
      .value();

    const sortedStacksWithDependencies = findTopologicalSort(adjList);

    console.log(
      `Destroying stacks with dependencies in order`,
      sortedStacksWithDependencies.join(' > ')
    );

    await $`yarn nx run-many --target=destroy --projects=${sortedStacksWithDependencies.join(
      ','
    )} --args="--infraEnv=${INFRA_ENV} --appsEnv=${APPS_ENV}"`;
}

where findTopologicalSort is an implementation of a topological

@FilipPyrek
Copy link

Hi team @jeffbcross @vsavkin @FrozenPandaz @jaysoo, would it be possible to push this feature? 🙏

It's quite important for use-cases when you are using Nx to drive your deployment process which I feel is quite common. 🙂

@FrozenPandaz
Copy link
Collaborator

FrozenPandaz commented Feb 28, 2023

Hey all

In theory, it is reasonable to reverse the task graph. I would love to hear more use cases for this to make sure the solution solves the problem.

To successfully remove the deployed stacks on AWS we need to destroy stacks that aren't depended on first.

Is this something that happens often? What's a more concrete example of when you would do this?

I think this could be neatly solved by adding support for dependents as a valid value to projects, i.e.

"targetDependencies": {
    "destroy": [
      {
        "target": "destroy",
        "projects": "dependents"
      }
    ]
  }
}

I could see this design working but would have to think about it some more. 🤔

I can try implementing this feature if it's okay for you #Hacktoberfest

I love the enthusiasm but let's gather more info before implementing this.

@FilipPyrek
Copy link

Hi @FrozenPandaz 🙂

sounds reasonable. 👍

Let me describe our use-case at @purple-technology:

What we have now

  • we started in 2019 to build our apps with Lerna monorepo setup - https://github.com/purple-technology/purple-stack
  • our biggest monorepo now contains 30+ Serverless Framework services and 40+ shared packages and we have couple of a bit smaller monorepos like this
  • our developers are doing ephemeral deployments of the whole monorepo for every feature they develop and test, so that means that we have to deploy whole big app and remove it every couple of days for each developer.
  • we created our own extension for Lerna called lerna smart run which helps us to package and redeploy only services which have actually changed while keeping correct order of deployment

What are we migrating to at the moment and why is this issue for us

  • we are now migrating this old Lerna flow to Nx and doing many other improvements in our pipelines
  • it's nice that we can set implicit dependencies between every service and take advantage of the caching etc. etc.
  • in our case the dependency means Fn::ImportValue between stacks, which is a "hard" dependency in CloudFormation, so that's why need to deploy and remove everything in correct order.
  • Only solution at the moment for us is to create our own script which would build the dependency graph on it's own and trigger the removal Nx scripts in individual services, which is very very crappy solution.

Nx has this beautiful feature of dependency graph and putting there a flag to make the execution in a reverse order sounds like a reasonable feature.

Let me know your any other thoughts on how this could be done in order to meet our needs. 🤔

@FilipPyrek
Copy link

Or maybe just some Nx plugin could be sufficient for this 🤔 I saw in the docs it's possible to manipulate the dependency graph from inside a plugin, which could do the job. Let me know what do you think.

@ThomasAribart
Copy link

@FrozenPandaz My need is the same as @FilipPyrek

I develop a Serverless application on AWS, with microservices as Cloudformation stacks. We need to remove them from dev accounts every week (this is a policy of the company I work for, to avoid drift). We cannot create a nx script because of the Fn::ImportValue dependency between our core microservice and the others.

@github-actions
Copy link

This issue has been automatically marked as stale because it hasn't had any recent activity. It will be closed in 14 days if no further activity occurs.
If we missed this issue please reply to keep it active.
Thanks for being a part of the Nx community! 🙏

@github-actions github-actions bot added the stale label Mar 17, 2023
@HadiModarres
Copy link
Author

I did the same thing as @FilipPyrek and @ThomasAribart, but I also think this whole approach of using implicit dependencies for correct deployment order is suboptimal as it affects the affected packages reported by Nx, so it'll run scripts like test for all affected packages including ones that I specified in implicit dependencies which is wrong because their source code don't rely on each other to affect tests.
I think a possible solution would be to split the concept of dependencies into source dependencies and runtime dependencies, and for runtime dependencies we would have the optiion of setting up which runs the tree root first and tear down which runs the tree leaves first.

@github-actions github-actions bot removed the stale label Mar 18, 2023
@github-actions
Copy link

github-actions bot commented Apr 1, 2023

This issue has been automatically marked as stale because it hasn't had any recent activity. It will be closed in 14 days if no further activity occurs.
If we missed this issue please reply to keep it active.
Thanks for being a part of the Nx community! 🙏

@github-actions github-actions bot added the stale label Apr 1, 2023
@mattzcarey
Copy link

Hey, has any progress been made on this or any further solutions. My usecase is exactly the same as @ThomasAribart and @FilipPyrek, literally to the letter.

Thanks

@NathanJAdams
Copy link

Sounds reasonable and would solve a use-case for me too. We have to publish packages, some of which depend on others, if there was say a --reverse-order flag that would be perfect.

@leongold2
Copy link

I second this for the exact same use-case (AWS stacks)

@FrozenPandaz FrozenPandaz added the community This is a good first issue for contributing label Apr 2, 2024
@FrozenPandaz
Copy link
Collaborator

FrozenPandaz commented Apr 2, 2024

I see, that all seems reasonable.

This is kind of complicated but the core team does not have bandwidth right now to work on this.

I believe reversing the task graph would yield the desired outcome?

It would be a quite a bit of work but if someone from the community would like to try implementing this, I believe somewhere around here would be where we would reverse the graph. I'm not sure if it's that simple though. I'm not sure if we could handle "dependent" task inputs for these kinds of tasks 🤔 It seems like for these scenarios, caching is not required though.

@FrozenPandaz FrozenPandaz self-assigned this Apr 2, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
community This is a good first issue for contributing scope: core core nx functionality type: feature
Projects
None yet
Development

No branches or pull requests