GithubHelp home page GithubHelp logo

labs42io / circleci-monorepo Goto Github PK

View Code? Open in Web Editor NEW
280.0 13.0 74.0 48 KB

An example of monorepo with CircleCI using conditional workflows and pipeline parameters.

License: MIT License

Shell 100.00%
circleci-workflow circleci monorepo

circleci-monorepo's Introduction

CircleCI monorepo

CircleCI

UPDATE
There is now support for detecting changed files and running multistage workflows natively in CircleCI. See these links: https://medium.com/nerd-for-tech/mastering-monorepos-with-circlecis-new-dynamic-config-2e187fe7934c https://circleci.com/blog/introducing-dynamic-config-via-setup-workflows/


UPDATE
There is a new experimental script in branch v2 that has the following improvements:

  • Improved calculation of base commit for new branches that takes into account also the history of past CI builds. Current script doesn't identify correctly the parent when creating new branches from merge commits.
  • Improved diff calculation using the two-dot git diff command.
  • Improved calculation of CI workflow status that considers success when all jobs succeeds. Current script considers a workflow successful when at least one job succeeds.
  • Possibility to customize the list of packages with advanced path specifications, that allows to list multiple paths for each package as well as exclude certain paths.
  • Possibility to provide additional custom pipeline parameters.
  • Possibility to customize the number of API pages with jobs to get from CircleCI. Existing script takes only the first page with 100 jobs.

The new script is planned to be published as CircleCI orb to allow for easier integration.

Monorepo brings simplicity to the development process by having all code in one place, but raises the complexity of automated builds and deploy.

For a relatively small monorepo it can be acceptable to have builds run for each service on every change. However, if you have a monorepo with a dozen of services/components, even the smallest change can introduce big delays in the CI process making it less efficient.

This repository is an example of configuring CircleCI for a monorepo that has four services. The CircleCI configuration file has workflows defined per each service, that are triggered on every push only when the corresponding service has code changes.

The sample services/components are api, app, auth and gateway all located in the subdirectory /packages. For each service a CircleCI workflow with the same name is defined in .circleci/config.yml file.

Important Disclaimer

This repository relies on CircleCI API v2 changes which are currently in Preview release.

Use of the v2 API as long as this notice is in the master branch (current as of June 2019) is done at your own risk and is governed by CircleCI’s Terms of Service.

Additionally, it requires use of v2.1 configuration files as well as having Pipelines enabled.

How it works

Whenever a change is pushed to GIT, by default the ci workflow is triggered in CircleCI. The ci workflow consist of a single job trigger-workflows, which performs a checkout and executes the circle_trigger.sh bash script from the .circleci folder. The circle_trigger.sh bash script is then responsible for detecting which services contain code changes and trigger their corresponding workflow via CircleCI API 2.0.

By convention, each service is located in a separate directory in packages. For each service there should be a separate workflow defined in the workflows section in .circleci/config.yml configuration file. The workflow is matched by the service's name, which is the same as the directory name from packages.

Each workflow is conditioned using a when clause, that depends on a pipeline parameter. The name of the parameter is the same as the name of the service.

Change detection

Each workflow that corresponds to a service is triggered only when there are code changes in the corresponding service directory. Below is a more detailed explanation of how it detects changes:

  • The first step consists of finding the commit SHA of the changes for which the most recently CircleCI pipeline was triggered
    • Firstly it attempts to get the latest completed CircleCI workflow for current branch (together with the commit SHA)
    • If there are no builds for current branch (which is usually the case with feature branches), it looks for nearest parent branch and gets its commit SHA (using this solution)
    • If there are no builds for parent branch then it uses master
  • Once it has the commit SHA of latest CircleCI workflow, it uses git log to list all changes between the two commits and flag those services for which changes are found in their directories.

Before you start

To be able to trigger workflows via API, you need a CircleCI personal API token. The circle_trigger.sh script expects to find the token in CIRCLE_TOKEN environment variable. To prevent having the tokens published to git, you can use project environment variables.

How to configure new services/components

Once you add a new service/component to your monorepo you have to do the following steps:

  • Add a directory in packages/ that will be the root of your service/component. The name of the directory will be used as the name of the service/component.

  • In .circleci/config.yml configuration file in parameters section add a corresponding parameter:

parameters:
  my_awesome_service: # this should be the name of your service
    type: boolean
    default: false
  • In .circleci/config.yml configuration file in jobs section define all the jobs that are need by current service/component. To set the job's working directory to the directory of the service, you can use job parameter:
build:
  parameters:
    package_name:
      type: string

  working_directory: ~/project/packages/<< parameters.package_name >>
  ...
  steps:
    - checkout:
        path: ~/project
  • In .circleci/config.yml configuration file in workflows section add a corresponding workflow:
workflows:
  when: << pipeline.parameters.my_awesome_service >> # the name of the parameter is the same as service name
    jobs:
      # add here the jobs used by this workflow

You have no restrictions on which jobs can be used by each workflow. It can be a service specific job, or a job reused by several workflows (services). In case you have dependencies between jobs within a workflow, you can have a custom name for the job and then use that name as a requirement:

service:
  ...
  jobs:
    - build:
        name: service-build # give a name for the `build` job used in current workflow
        package_name: service # name of the service passed as parameter; used to set the working directory
        ...
    - deploy:
        ...
        requires:
          - service-build # list as a dependency the `build` job from this workflow

Further notes

Customizing the token

In CircleCI dashboard when viewing a job details you can see who triggered it. In this case it will be the team member whose API token is configured in CIRCLE_TOKEN environment variable (described above).

It would be nicer to show there the name of the team member who made the code changes and triggered the workflow. This allows to see then who triggered a specific job, and have a better integration with CircleCI notifications.

Unfortunately CircleCI currently doesn't support user specific environment variables. One way to workaround it is to define in the CircleCI project a variable for each team member by following a convention, and then use the built-in $CIRCLE_USERNAME to get the name of the variable:

TOKEN_NAME=CIRCLE_TOKEN_${CIRCLE_USERNAME} # this however will fail if username contain chars like `-`, '.' etc.
CIRCLE_TOKEN=${!TOKEN_NAME}

Dependencies

Dependencies can be handled using circleci's advanced logic with the or operator.

Example: if package api depends on common, you should add common as a pipeline parameter and use it to define the when parameter for api's workflow

workflows:
  version: 2

  # The main workflow responsible for triggering all other workflows
  # in which changes are detected.
  ci:
    when: << pipeline.parameters.trigger >>
    jobs:
      - trigger-workflows


  # Workflows defined for each package.

  api:
    when: 
      or: 
        - << pipeline.parameters.api >>
        - << pipeline.parameters.common >>
    jobs:
      - build:
          name: api-build
          package_name: api
      - deploy:
          name: api-deploy
          package_name: api
          requires:
            - api-build

Now, changes in package common will also trigger build for api

circleci-monorepo's People

Contributors

bigsonlvrocha avatar cohei avatar dimadeveatii avatar issmirnov avatar mikerkeating avatar oleduc avatar ozimos avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

circleci-monorepo's Issues

Curl to trigger execution of package workflow fails

Right now I am running into the problem that the curl call to trigger the package workflow fails with a 404 response code:

Received status code: 404
Response:
{
  "message" : "Project not found"
}

The CIRCLE_TOKEN is set, so I am a bit clueless...

curl -s -v -u '****************************************:' -o response.txt -w '%{http_code}' -X POST --header 'Content-Type: application/json' -d '{ "branch": "build-docker-images", "parameters": { "trigger":false, "backoffice-helm":true } }' 'https://circleci.com/api/v2/project/github/********/pipeline'
*   Trying 18.208.34.245...
* TCP_NODELAY set
* Connected to circleci.com (18.208.34.245) port 443 (#0)
* ALPN, offering h2
* ALPN, offering http/1.1
* successfully set certificate verify locations:
*   CAfile: /etc/ssl/certs/ca-certificates.crt
  CApath: /etc/ssl/certs
} [5 bytes data]
* TLSv1.3 (OUT), TLS handshake, Client hello (1):
} [512 bytes data]
* TLSv1.3 (IN), TLS handshake, Server hello (2):
{ [89 bytes data]
* TLSv1.2 (IN), TLS handshake, Certificate (11):
{ [4838 bytes data]
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
{ [333 bytes data]
* TLSv1.2 (IN), TLS handshake, Server finished (14):
{ [4 bytes data]
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
} [70 bytes data]
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
} [1 bytes data]
* TLSv1.2 (OUT), TLS handshake, Finished (20):
} [16 bytes data]
* TLSv1.2 (IN), TLS handshake, Finished (20):
{ [16 bytes data]
* SSL connection using TLSv1.2 / ECDHE-RSA-AES128-GCM-SHA256
* ALPN, server did not agree to a protocol
* Server certificate:
*  subject: CN=*.circleci.com
*  start date: Nov 20 00:00:00 2019 GMT
*  expire date: Dec 20 12:00:00 2020 GMT
*  subjectAltName: host "circleci.com" matched cert's "circleci.com"
*  issuer: C=US; O=Amazon; OU=Server CA 1B; CN=Amazon
*  SSL certificate verify ok.
* Server auth using Basic with user '****************************************'
} [5 bytes data]
> POST /api/v2/project/github/************/pipeline HTTP/1.1
> Host: circleci.com
> Authorization: Basic *************
> User-Agent: curl/7.58.0
> Accept: */*
> Content-Type: application/json
> Content-Length: 94
> 
} [94 bytes data]
* upload completely sent off: 94 out of 94 bytes
{ [5 bytes data]
< HTTP/1.1 404 Not Found
< Content-Type: text/plain;charset=utf-8
< Date: Thu, 30 Jul 2020 16:34:21 GMT
< Server: nginx
< Set-Cookie: ring-session=**********;Path=/;HttpOnly;Expires=Fri, 30 Jul 2021 13:53:29 +0000;Max-Age=1209600;Secure
< Strict-Transport-Security: max-age=15724800
< X-CircleCI-Identity: circle-www-api-v1-75dfdb44f4-5m4dj
< X-Frame-Options: DENY
< X-RateLimit-Limit: 151
< X-RateLimit-Remaining: 150
< X-RateLimit-Reset: 0
< x-request-id: 9bad84c0-4a59-455c-a12c-35f9e19b94dd
< X-route: /api/v2/project/:project-slug/pipeline
< Content-Length: 37
< Connection: keep-alive
< 
{ [37 bytes data]
* Connection #0 to host circleci.com left intact
Received status code: 404
Response:
{
  "message" : "Project not found"
}

Add fallback workfrow

Hi,

Many thanks for great project. I’m using the v2.

Is it possible to add a default workflow if a modified file does not match any of the packages defined in packages.json?

tracking initial author

Thank you for your script. I adapted it to work with the rush monorepo manager.

I know this is not not an issue with the script per se, but right now, any secondary workflows that are triggered are using the CIRCLE_USER_TOKEN token which may not belong to the author of the original commit.

Any ideas on how to keep track of the original author?

Improve documentation on dependencies with Advanced Logic in Config

Advanced logic in Config is a thing now in circleci, so, it should be documented, it wwould be a solution without change the script

the announcement is here

So, given 2 packages, api and service that depends on common, the script should be ok with

workflows:
  version: 2

  # The main workflow responsible for triggering all other workflows
  # in which changes are detected.
  ci:
    when: << pipeline.parameters.trigger >>
    jobs:
      - trigger-workflows


  # Workflows defined for each package.

  api:
    when: 
      or: 
        - << pipeline.parameters.api >>
        - << pipeline.parameters.common >>
    jobs:
      - build:
          name: api-build
          package_name: api
      - deploy:
          name: api-deploy
          package_name: api
          requires:
            - api-build

service:
    when: 
      or: 
        - << pipeline.parameters.service >>
        - << pipeline.parameters.common >>
    jobs:
      - build:
          name: service-build
          package_name: service
      - deploy:
          name: service-deploy
          package_name: service
          requires:
            - service-build

Force pushing and last completed build SHA

I just wanted to mention an issue that I ran into and a potential solution for others who might run into it as well.
I like to rebase my feature branches on to the target branch. This means I need to force push my changes. I ran into an issue with the last completed build SHA was no longer in the branch and caused git log to fail as the $LAST_COMPLETED_BUILD_SHA could not be found: fatal: bad object 2366eaabe7a5fe2f3ddf19fb5821e8a3073537d6
The solution I had was to just just the git log in and if to see if it would error. If it did, I would just move on as if there was no LAST_COMPLETED_BUILD_SHA

LAST_COMPLETED_BUILD_SHA=`curl -Ss -u "${CIRCLE_TOKEN}:" "${LAST_COMPLETED_BUILD_URL}" | jq -r 'map(select(.status == "success") | select(.workflows.workflow_name != "ci")) | .[0]["vcs_revision"]'`

-if  [[ ${LAST_COMPLETED_BUILD_SHA} == "null" ]]; then
+if  [[ ${LAST_COMPLETED_BUILD_SHA} == "null" ]] || ! $(git log -1 $CIRCLE_SHA1 ^$LAST_COMPLETED_BUILD_SHA --format=format:%H --full-diff ${PACKAGE_PATH#/}); then
  echo -e "\e[93mThere are no completed CI builds in branch ${CIRCLE_BRANCH}.\e[0m"

It works. Not sure if there is a better way to maybe try older completed build SHAs before jumping to the branches.

Changes in master never get detected

Hi

I got a weird issue where the script works perfectly fine when I am on a branch, all changes were detected correctly.

But then when I merge the branch back to master, the script doesn't seem to detect any changes hence master is not being built.

All I got is this, could you please help?

`There are no completed CI builds in branch master.

Searching for CI builds in branch 'master' ...

No CI builds for branch master. Using master.

Searching for changes since commit [cd3bf62] ...

Workflows currently in failed status: ().

[-] cart-web

[-] myaccount-web

[-] shared

[-] style

No changes detected in packages. Skip triggering workflows.`

Package.json change not trigger change in packages

Hi,
we are using this repo for some time, and it's working great
a few weeks we move to use the v2 bash code (much better than the first one)
but we are starting to see that package.json change inside are packages not trigger a change in the workflow job
this is the summary we get
Screen Shot 2021-04-27 at 10 50 57 AM

this is the full log
FullLog.txt

monorepo.sh does not detect any change when merging branch A to branch B with MergeCommit

let say we have 2 branches development and master. we have checkout to branch development and make changes and push code. All is good here.
Now we want to merge our development branch to master branch, suppose that we added multiple features in development branch so we want to keep commit history.

When we merge development to master using

  • squash, this work fine
  • merge commit, here our monorepo.sh does not find any changes

The logs are

No config file found at /home/circleci/project/.circleci/monorepo.json. Using defaults.
Getting workflow status:
	GET: [200] https://circleci.com/api/v1.1/project/github/<user>/<repo>/tree/master?shallow=true&limit=100&offset=0
Created build-commit map /home/circleci/project/.circleci/temp/builds.json
First built commit in branch: 5fbce9392a203768006daa39617cf1a75bb37682
	Could not find parent commit relative to first build commit.
	Either branch was force pushed or build commit too old.
Parent commit: 8162e15

Store main/master branch in variable and update documentation

Some pre-amble:

I borrowed your script (Thank you so much) for a personal project of mine, and I wanted to give back with some feedback! I would also love to be involved with extending your work: I genuinely think the mono-repo is the way to go but it can be very difficult to get tools to play nicely with them! So yeah - thanks again!

Issue:

Github now encourages a default branch of 'main' instead of master, and I guess some people might prefer to use a different name (Who knows why??). So running your script for my new repo fails: it can't find master (my branch is called main)

Solutions:

Initially I thought maybe extracting the name of the main branch into a variable and allowing the user to set this in the script (or even as an Env var in circleci) would be useful, however on second thoughts I wonder if a slightly more robust way might be to query their remote for the "default branch from with .circleci/circle_trigger.sh.

Final comments:

I don't know the exact details of how to get this properly working (some googling led me to a solution that might work, but I'd anticipate hitting some caveats). I'd be happy to put a PR in if you're interested!

Edit: I don't understand bash well yet - so if this is already intended behaviour I may have missed it.

Cannot index string with string "status"

Attempting to use the example repo and get the following error;

+ ROOT=./packages
+ REPOSITORY_TYPE=github
+ CIRCLE_API=https://circleci.com/api
+ LAST_COMPLETED_BUILD_URL='https://circleci.com/api/v1.1/project/github/hashicorp/instruqt-packer/tree/fp/move_file?filter=completed&limit=100&shallow=true'
++ curl -Ss -u : 'https://circleci.com/api/v1.1/project/github/hashicorp/instruqt-packer/tree/fp/move_file?filter=completed&limit=100&shallow=true'
++ jq -r 'map(select(.status == "success") | select(.workflows.workflow_name != "ci")) | .[0]["vcs_revision"]'
jq: error (at <stdin>:2): Cannot index string with string "status"
+ LAST_COMPLETED_BUILD_SHA=```

Question: change detection

Hi, first of: thanks for the medium article + the repo!
Looks promising.

I was playing around with your setup and was wondering how you handle feature-branches. I hab a couple of commits on the branch which changed files in two packages. The script picked up the changes ok and ran the two jobs -> errors. Ok, lets fix them in the first one. Great fixed, but it didn't check the second package (build still failed) and on the PR page it said "all ok, no errors, you can merge into master ...".

My question: shouldn't it - when you are working on a branch - always check against parent branch to run tests for all packages that have changes compared to parent branch?

Question: About environment variables.

Thank you very much for providing such a great feature.

I am an apprentice front-end engineer living in Japan.
I am interested in mono repositories and I am glad to find this repository.

Here is the main topic.
I set up a Personal API Token in CircleCI, but when I run it in Bitbucket, it points out that the environment variable is not set.
When I run the same source code on Github, it works perfectly.

CIRCLE_TOKEN="Personal_API_TOKEN" // direct insertion

If you write the generated key in circle_trigger.sh and assign it directly as above, it works perfectly on Bitbucket.
Is there a difference in the way Github and Bitbucket are configured?

Error: no completed CI builds in branch master

Hi,
Thanks a lot for this. I am trying to run this but facing below issue:

There are no completed CI builds in branch master.
Searching for CI builds in branch 'master' ...
No CI builds for branch master. Using master.
Searching for changes since commit [master] ...

It seems the below line is not able to fetch the result from API
curl -Ss -u ${CIRCLE_TOKEN}: ${LAST_COMPLETED_BUILD_URL} > circle.json

I can verify that if I try to fetch using LAST_COMPLETED_BUILD_URL from browser, it fetches the expected result, but somehow not working during build.

I have double verified CIRCLE_TOKEN and it seems fine. Any idea as to where could be the issue?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.