{"id":28496903,"url":"https://github.com/salsita/bunny-migrate","last_synced_at":"2025-07-02T21:32:37.893Z","repository":{"id":26365960,"uuid":"108380812","full_name":"salsita/bunny-migrate","owner":"salsita","description":"CLI tool for managing RabbitMQ schema instances","archived":false,"fork":false,"pushed_at":"2025-06-21T00:28:28.000Z","size":706,"stargazers_count":5,"open_issues_count":10,"forks_count":0,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-06-21T01:31:33.314Z","etag":null,"topics":["amqp","instance","instances","migrate","migration","migration-tool","rabbitmq","schema","zero-downtime"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/salsita.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2017-10-26T08:07:08.000Z","updated_at":"2024-02-07T11:53:42.000Z","dependencies_parsed_at":"2023-10-23T08:24:40.008Z","dependency_job_id":"4adf23c8-5e33-41d5-a128-04b07b8b8273","html_url":"https://github.com/salsita/bunny-migrate","commit_stats":{"total_commits":168,"total_committers":4,"mean_commits":42.0,"dds":"0.26190476190476186","last_synced_commit":"940b4fc994c8362282cde1d5dd85fdffbbe9fd7a"},"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/salsita/bunny-migrate","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salsita%2Fbunny-migrate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salsita%2Fbunny-migrate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salsita%2Fbunny-migrate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salsita%2Fbunny-migrate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/salsita","download_url":"https://codeload.github.com/salsita/bunny-migrate/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/salsita%2Fbunny-migrate/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":261049268,"owners_count":23102532,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["amqp","instance","instances","migrate","migration","migration-tool","rabbitmq","schema","zero-downtime"],"created_at":"2025-06-08T12:31:41.130Z","updated_at":"2025-07-02T21:32:37.865Z","avatar_url":"https://github.com/salsita.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Dependency Status](https://img.shields.io/david/salsita/bunny-migrate.svg)](https://david-dm.org/salsita/bunny-migrate)\n[![devDependency Status](https://img.shields.io/david/dev/salsita/bunny-migrate.svg)](https://david-dm.org/salsita/bunny-migrate?type=dev)\n![Downloads](https://img.shields.io/npm/dm/bunny-migrate.svg?style=flat)\n![Licence](https://img.shields.io/npm/l/bunny-migrate.svg?style=flat)\n[![Known Vulnerabilities](https://snyk.io/test/github/salsita/bunny-migrate/badge.svg)](https://snyk.io/test/github/salsita/bunny-migrate)\n\n# bunny-migrate\n\nThis is a command line tool that manages RabbitMQ schema instances.\n\n## Goals\n\nThere are 3 main goals of the tool:\n1. creating prefixed instances of given RabbitMQ schema (yes, prefixed, so that\nyou can create multiple instances of the same schema),\n2. removing schema instances that are no longer used,\n3. managing routing rules for existing schema instances.\n\nTo start with, you need a RabbitMQ schema definition file. It is a JSON file\nthat follows the format described below. You can use this tool to create an\ninstance of this schema, all names there will be prefixed with specified prefix.\n\nOnce new schema instance is added, you can specify routing rules for your\nmain entry exchange. We assume that at the beginning (or near the beginning) of\nyour processing pipeline, there is an exchange that routes the messages to\nexisting schema instances (e.g. according to `stable`, `next`, `latest` message\nrouting keys). That is useful for (beta-) testing and for draining messages\nfrom existing schema instances when switching to newer processing pipelines with\nzero down-time.\n\nWhen you are done with existing processing pipeline (i.e. RabbitMQ schema\ninstance), and there are no managed routing rules defined for it, you can safely\nremove it from Rabbit.\n\n## Installation\n\n```\n$ npm i bunny-migrate\n```\n\nInstalling this module adds a runnable file into your `node_modules/.bin` directory. If installed globally (with the\n`-g` option), you can run `bunny-migrate`, otherwise you can run `./node_modules/.bin/bunny-migrate`.\n\n## Commands\n\nThe tool supports the following commands; for detailed explanation see the sections below:\n* `init`: inits the structures to keep run-info in RabbitMQ,\n* `list`: lists managed schema instances and rules,\n* `add`: adds new schema instance,\n* `remove`: removes existing schema instance,\n* `add-rule`: adds new managed rule,\n* `remove-rule`: removes existing managed rule,\n* `update-rule`: removes existing managed rule and adds a new one in turn,\n* `version`: prints version and terminates,\n* `help`: prints short help and terminates.\n\n## Parameters\n\nParameter values are taken either from configuration file (either default one, which is `bunny-migrate.cfg` file\nlooked up in current working directory, or file explicitly provided with `--config` option from command line),\nor they need to be provided on command line. If a parameter\nis provided in both the config file and on the command line, the one from\ncommand line is used. If any mandatory value (needed for given command) is\nmissing, the tool terminates. Mandatory and [optional] parameters for\neach command are listed in respective sections below.\n\nInformation about added schema instances and associated routing rules are\nstored in RabbitMQ instance itself. There is a special exchange / queue that\nholds run-time information about the system.\n\n## Format of configuration file\n\n```\n{\n  // RabbitMQ instance to connect to\n  \"uri\": \"amqp://user:password@localhost:5672/vhost\",\n\n  // name of exchange / queue holding the run-time information\n  \"bunny-x\": \"bunny-migrate\",\n\n  // prefix of schema to be added / removed, or for which a managed rule is added / removed\n  \"prefix\": \"12345\",\n\n  // path to schema file\n  \"schema\": \"./schema.json\",\n\n  // whether or not to update managed rule when adding a new schema instance\n  \"update-rule\": true,\n\n  // name of the schema \"entry-point\" exchange when adding a managed rule\n  \"destination\": \"channel-router\",\n\n  // name of the exchange that serves as the source exchange of a managed rule\n  \"source\": \"prefix-router\",\n\n  // routing key of a managed rule\n  \"key\": \"latest\",\n\n  // optional arguments object when creating a managed rule\n  \"args\": { }\n}\n```\n\nNote: the comments in the above example must be stripped, they make the JSON invalid.\n\nEven though you can specify all parameters in the configuration file like shown above,\nit makes better sense to store there only the ones commonly used (`uri`, `bunny-x`,\nand perhaps `source`), and provide the remaining parameters on the command line\nwhen invoking the tool.\n\n## Command line parameters\n\nAll of the above configuration file parameters can be provided on command line\nas well. The names are the same, just prefixed with double dashes. The name of\nthe parameter on command line is then followed either with an equal sign or a space and then\nwith the value of the parameter (in case a string value is expected). For\nboolean parameters: if you specify the parameter name, it is considered to have\n`true` value, if it is missing, it is considered to have `false` value.\n\nExample (string value): you can pass `uri` string on command line either as\n`--uri=\"...\"` or as `--uri \"...\"`.\n\nExample (boolean value): the equivalent of configuration setting `\"debug\": true`\non command line is `--debug`.\n\nThe only exception to the above rules is `args` parameter, since it is of\nobject type in config file. To pass this value from command line, you need to\nprovide stringified equivalent of that object value. I.e. to pass equivalent of\n```\n  \"args\": {\n    \"test\": true\n  }\n```\nfrom configuration file, you need to provide on the command line\neither `--args='{\"test\":true}'` or `--args '{\"test\":true}'`.\n\n## Output and exit codes\n\nThere are 4 levels of output, all printed to standard output by default:\n* debug\n* info\n* warning\n* error\n\nInfo level is the default one, so without changing the output level, you will\nsee info, warning and error output messages.\n\nTo see the debug messages as well, you need to pass `--debug` or `-d` command\nline parameter (or config file equivalent).\n\nOn the other hand, when passing `--quiet` or `-q` parameter, all output messages but\nerrors are suppressed.\n\nIn case both `--quiet` and `--debug` parameters are passed, `--debug` takes precedence.\n\nThe tool returns zero exit code upon success, non-zero exit code on errors. The\ntools terminates its execution when running into the first unexpected problem.\nTo keep the RabbitMQ state as healthy and consistent, we check as much as possible\nin advance to minimize the risk of something\ngoing wrong (e.g. the tool verifies that none of the exchanges and queues exists\nbefore it tries creating them, or that all of the exchanges and queues do exist\nbefore removing them).\n\nBut sometimes, you know, life is tough and you have some leftovers in RabbitMQ.\nFor this case we have introduced the `--force` or `-f` command line option (or its config\nfile equivalent), that skips all the tests and the tool does not terminate when\nrunning into unexpected issues. Warning: use with caution! There are still some\ncases (e.g. RabbitMQ connection error) in which even the `--force` parameter will not\nhelp you.\n\n## Run-time initialization\n\nWhen you have your RabbitMQ installed and want to start using this tool, you\nneed to create the exchange / queue that manages run-time information for this\ntool inside the RabbitMQ instance.\n\n```\n$ bunny-migrate init\n```\n\nParameters:\n* `uri`\n* `bunny-x`\n\nThe above command will connect to your RabbitMQ instance as specified using the\n`uri` parameter, will create new `bunny-x` exchange and queue, and will store\nrun-time information for future usage there.\n\n`bunny-x` exchange and queue must not exist prior to running this command (in\ncase either of them does, the tool terminates). Also, you should never manipulate\nmessage in `bunny-x` queue by hand or other tools than `bunny-migrate`.\n\n## Information about running system\n\n```\n$ bunny-migrate list\n```\n\nParameters:\n* `uri`\n* `bunny-x`\n\nThis will give you information about all schema instances added, and all\nrouting rules managed by this tool. Note: this command will NOT give you information\nabout any other exchanges, queues, ... in your RabbitMQ instance, you need to\nuse other tools to get that.\n\n## Schema definition file format\n\nSchema definition file is a JSON file. The schema JSON has 4 root keys:\n* `[exchanges]`: array of exchanges to create,\n* `[queues]`: array of queues to create,\n* `[queueBindings]`: array of queue-to-exchange bindings to define,\n* `[exchangeBindings]`: array of exchange-to-exchange bindings to define,\n* `[messages]`: array of messages to push into newly created exchanges and/or queues.\n\n### Exchanges\n\nEach exchange in the `exchanges` array of the schema JSON is described with an\nobject with following keys:\n* `name`: the name of exchange to create,\n* `type`: the type of exchange to create (`direct`, `fanout`, `topic`, or `headers`),\n* `[options]`: object passed to `assertExchange()` if provided (see [docs](http://www.squaremobius.net/amqp.node/channel_api.html#channel_assertExchange)).\n\nEach exchange name must be unique (can appear in the list of `exchanges` just once).\n\n### Queues\n\nEach queue in the `queues` array of the schema JSON is described with an object\nwith the following keys:\n* `name`: the name of queue to create,\n* `[options]`: object passed to `assertQueue()` if provided (see [docs](http://www.squaremobius.net/amqp.node/channel_api.html#channel_assertQueue)).\n\nEach queue name must be unique (can appear in the list of `queues` just once).\n\n### Queue-to-exchange bindings\n\nEach queue-to-exchange binding from `queueBindings` array of the schema JSON asserts\na routing path from an exchange to a queue. The binding is described with an\nobject with the following keys:\n* `queue`: the name of queue to which to route the messages,\n* `exchange`: the name of exchange from which to route the messages,\n* `pattern`: the routing pattern,\n* `[args]`: an object containing extra arguments that may be required\nfor the particular exchange type (see [docs](http://www.squaremobius.net/amqp.node/channel_api.html#channel_bindQueue)).\n\nYou are allowed to bind only queues to exchanges that are defined as part of the\nsame schema file.\n\n### Exchange-to-exchange bindings\n\nEach exchange-to-exchange binding from `exchangeBindings` array of the schema JSON \nasserts a routing path from one exchange to another one based on provided pattern.\nThe binding is described with an object with the following keys:\n* `destination`: the name of exchange where to route messages to,\n* `source`: the name of exchange where to route messages from,\n* `pattern`: the routing pattern,\n* `[args]`: an object containing extra arguments that may be required for the particular exchange type.\n\nYou are allowed to bind only exchanges that are defined as part of the same schema file.\n\n### Messages\n\nEach message from `messages` array of the schema JSON describes a message (or multiple of messages) that will be pushed\nto newly created exchange or queue. The message is described with an object with the following keys:\n* `exchange` or `queue`: name of the exchange or the queue to push the message to (only one of them must be used),\n* `key`: in case the message goes to an exchange, routing key must be specified,\n* `content`: string or object that will be pushed as content of the message; if object is provided, it is converted to string,\n* `[count]`: how many copies of the message to push to the exchange / the queue (default value: 1),\n* `[options]`: additional options passed to the `publish()` or `sendToQueue()` methods (see the\n[docs](http://www.squaremobius.net/amqp.node/channel_api.html#channel_publish) for more details).\n\nYou are allowed to push messages only to exchanges and/or queues that are defined as part of the same schema file.\n\n## Creating new schema instance\n\n```\n$ bunny-migrate add\n```\n\nParameters:\n* `uri`\n* `bunny-x`\n* `schema`\n* `prefix`\n* `[update-rule]`\n\nThis will add new RabbitMQ schema instance, as described in `schema` JSON file.\n\nAll exchanges and queues will be prefixed with `prefix-string` and a dot (`.`).\nFor example: if there is a queue `tasks` described in the schema file, and the provided\nprefix is `prefix`, then the name of the resulting queue created in RabbitMQ will be\n`prefix.tasks`. If the prefix is empty string, the dot is NOT prepended.\n\nBefore any exchanges and queues are created, the tool checks (from run-time information\nstored in Rabbit `\u003cbunny-x\u003e` queue) if provided `prefix` is not in use yet.\n\nIf the prefix can be used, an array of prefixed exchange and queue names is compiled and in turn\nthe tool verifies that none of the exchanges or queues with given names already exist in RabbitMQ.\n\nThen the tool creates all the entities in the following order:\n1. exchanges (as per `exchanges` schema array),\n2. queues (as per `queues` schema array),\n3. queue-to-exchange bindings (as per `queueBindings` schema array), and\n4. exchange-to-exchange bindings (as per `exchangeBindings` schema array).\n\nWhenever `options` or `args` object is to be passed, it is traversed (recursively) and all string values\nthat match name of exchange or queue (not prefixed) are replaced with string values of prefixed\nequivalent.\n\nOnce all the entities are created and bound properly, the tool pushes messages to exchanges and/or to queues according to\nthe `messages` schema array. In this case the optional `options` object is NOT traversed and no prefixing of exchange /\nqueue names takes place, as none of the keys of the `options` object should reference an exchange or a queue.\n\nAfter that the run-time information in `\u003cbunny-x\u003e` queue is updated with information about this schema instance.\n\nIf `update-rule` is set to `true`, the mandatory and optional parameters of the\ncommand are extended with the ones for `update-rule` command (that is\neffectively with parameters for `add-rule` command). If this parameter is\nprovided, the managed rule for provided routing key (e.g. with value `latest`)\nis updated to point to the just-added schema instance. For more details see\nthe `update-rule` command below.\n\n## Removing existing schema instance\n\n```\n$ bunny-migrate remove\n```\n\nParameters:\n* `uri`\n* `bunny-x`\n* `prefix`\n\nThis will remove existing RabbitMQ schema (i.e. queues and exchanges) for\nspecified prefix.\n\nBefore anything gets removed from RabbitMQ, the tool first checks if there is a\ncorresponding record for given prefix stored in its run-time information, and if\nthis prefix is NOT referenced from any of the managed rules (see below).\n\nIf all checks pass, the queues are removed first, then the exchanges.\nAll associated bindings are removed along with the entities.\n\n## Adding a managed rule\n\n```\n$ bunny-migrate add-rule\n```\n\nParameters:\n* `uri`\n* `bunny-x`\n* `prefix`\n* `destination`\n* `source`\n* `key`\n* `[args]`\n\nA managed rule is an exchange-to-exchange binding, specifying routing rule\nbetween existing exchange `source` (that might or might not be created as part of\nmanaged schema) and exchange `destination` (that must be part of a managed schema).\nThe name of `destination` exchange is provided unprefixed. \n\nParameter `key` is used to for creating the routing pattern between the exchanges.\nThe value of `key` is taken and appended with a dot (`.`) and a hash-sign (`#`)\nto form the routing pattern. E.g. from `key` value of `latest`, the routing pattern\n`latest.#` is created. The original value of `key` must not contain dot (`.`),\nspace (` `), asterisk (`*`) and hash (`#`) characters.\n\nFirst of all, the tool checks that:\n* the routing `key` is not used in any of the existing managed rules,\n* the `destination` exchange was created as part of `prefix` schema instance,\n* the prefixed `destination` exchange is still present in RabbitMQ,\n* the `source` exchange exists in RabbitMQ.\n\nIf all of above is met, the tool creates the expected binding and remembers it\nin its run-time information.\n\nNotes:\n* Multiple routing `keys` can be used to bind to the same \"entry-point\"\nexchange with the same `prefix`. After you add a new schema instance, you might\ncreate single rule for `latest` routing `key`, but after testing you may\nconsider it stable and you can route the other traffic there under\ndifferent routing `key` (e.g. called `stable`). Then you can recycle the routing\n`key` `latest` to a newer version of the schema (with another `prefix`) in the\nfuture and use it again for initial testing.\n* You might create the initial part of your RabbitMQ schema using this tool as\nwell. Use appropriate corresponding prefix, e.g. `main` or `master` for it\n(or you can even use empty string). When referencing the\n`source` exchange, you need to include that prefix into the name (as it should\nbe different prefix from what you are using to add the managed rule). So say you\ncreated exchange `router` as part of schema instance `main`. So here, as\n`destination` parameter, you need to pass name `main.router`.\n\n## Removing existing managed rule\n\n```\n$ bunny-migrate remove-rule\n```\n\nParameters:\n* `uri`\n* `bunny-x`\n* `key`\n\nThis command removes the exchange-to-exchange binding created previously with\n`add-rule` command for given routing `key`. It verifies that both (remembered)\nexchanges (`source` and `prefix`ed `destination`) still exist, and if so, it\nremoves the binding for given routing `key`. It removes only this one binding,\nother bindings (if there are any) are not affected.\n\n## Updating existing managed rule\n\n```\n$ bunny-migrate update-rule\n```\n\nParameters: see `add-rule` command.\n\nThis command (for given existing routing `key`) first removes existing managed\nrule (if there is one) and adds another in turn based on provided parameters.\n\nThe result is equal to the sequence of `remove-rule` and `add-rule` commands for\nthe same routing `key`. The only difference is that in case there is no existing\nrule for given routing `key` before envoking this command, the `update-rule`\ncommand does not fail, but creates the new rule. In such case the `update-rule`\ncommand is equivalent to `add-rule` command only.\n\n## Examples\n\nLet's say you have a web application that manages (big) data for its users, and the user can request some (bulk) data\nupdates in web interface. Let's say that the bulk update operation can take minutes or hours (e.g. there is some 3rd\nparty service involved, perhaps with some API rate limiter), so you decided to have dedicated workers processing these\nupdates. Each data bulk update can consist of hundreds or thousands of small operations, and you don't want to track them in\nworkers' memory (as if something bad happen to them, the progress is lost completely), nor in your main DB\n(as you prefer subscribe / notify approach to constant DB polling). So you have RabbitMQ in place to store the operation \nprogress there.\n\nYou have the DB with table with all information about the users, and all their data as well. Each user has a flag in the\nDB table indicating if they are regular user, beta-test user, or even alpha-test user. The request for data bulk update\nis pushed as a message from web-server to RabbitMQ exchange (let's call it `requests`). The message describes what user\nrequested what data bulk update, and workers (subscribed to RabbitMQ queue `requests`, where the exchange passes the\nmessages to) will take it from there. The end result is that the user's request for data bulk edit is processed and the\ndata is updated accordingly in the DB (and pushed to 3rd party services as well).\n\nNow let's assume you have RabbitMQ installed on your production  machine `machine`, user `user` with password `password` created,\nwith access to the RabbitMQ vhost `vhost`. (Also, you have `bunny-migrate` tool installed. ;-))\n\nFirst of all, since we'll be using only the above described RabbitMQ installation in our example,\nlet's create a config file with the following content:\n\n```\n{\n  \"uri\": \"amqp://user:password@machine:5672/vhost\",\n  \"bunny-x\": \"bunny-admin\"\n}\n```\n\nThe `uri` parameter is the RabbitMQ connection string, the second parameter is the name of exchange / queue to store the\nrun-time information of the `bunny-migrate` tool.\n\n#### Init run-time\n\n```\n$ bunny-migrate init\n```\n\nThis created `bunny-admin` exchange and queue where run-time information about the added schema instances and managed rules\nwill be stored.\n\n#### Initial schema\n\nAt the beginning, we will need to create the exchange and queue for the messages pushed by web-server(s), we called them\n`requests` in the example above. Also, we want to have our entry point to the processing world, this will be another\nexchange that we'll call e.g. `main`.\n\nThere will be a worker process subscribed to `requests` queue that will take the message, check (in DB) for what type of\nuser the message is, and push the same message to `main` exchange with routing key corresponding to the user type (let's\nsay `regular`, `beta`, or `alpha`).\n\nThe initial schema file (stored in file `schema-initial.json`) will be something like this:\n\n```\n{\n  \"exchanges\": [\n    { \"name\": \"requests\", \"type\": \"fanout\" },\n    { \"name\": \"main\", \"type\": \"topic\" }\n  ],\n  \"queues\": [\n    { \"name\": \"requests\" }\n  ],\n  \"queueBindings\": [\n    { \"queue\": \"requests\", \"exchange\": \"requests\", \"pattern\": \"\" }\n  ]\n}\n```\n\nTo add the above queue and exchanges, run\n\n```\n$ bunny-migrate add --schema schema-initial.json --prefix \"\"\n```\n\n#### Data-processing schema\n\nAt this point, there is no queue bound to the `main` exchange. We said there would be a process pushing messages to\nthis exchange with routing keys `regular`, `beta`, or `alpha`, based on the user types.\n\nSo let's say we want to have `bulk-changes` exchange bound to the `main` exchange. Then there would be a worker process\nreading messages from corresponding `bulk-changes` queue and figuring out what individual items are affected,\npushing one message per item to `items` exchange / queue.\n\nFrom there we'll for example need to push modified items to 3rd party API, but it has a rate limiter on server\nside, so we will get messages from the `items` queue and decide if we can push them to `api` exchange / queue\ndirectly, or if they need to be delayed (using dead-letter-queue). (Btw. we have\n[dripping-bucket](https://github.com/salsita/dripping-bucket) library for the API rate limiting with RabbitMQ, too!)\n\nThe worker getting messages from `api` queue performs the 3rd party communication and updates the DB based on the\nresponse it gets from 3rd party service. Also, it returns API token back to `dripping-bucket` rate limiter by pushing a\nmessage to `responses` exchange / queue (rate-limiter is subscribed to `items` queue as well as to `responses` queue).\n\nLet's say that is your processing pipeline, and you constantly work on improvements and new versions and want to deploy\nnew versions to production with *zero downtime* and to move slowly users (first `alpha`, then `beta`, and finally\n`regular` users) to newer versions.\n\nThe above example of schema can be coded as follows (and stored in `schema.json` file):\n\n```\n{\n  \"exchanges\": [\n    { \"name\": \"bulk-changes\", \"type\": \"topic\" },\n    { \"name\": \"items\", \"type\": \"topic\" },\n    { \"name\": \"api\", \"type\": \"topic\" },\n    { \"name\": \"api-wait\", \"type\": \"topic\" },\n    { \"name\": \"responses\", \"type\": \"topic\" }\n  ],\n  \"queues\": [\n    { \"name\": \"bulk-changes\" },\n    { \"name\": \"items\" },\n    { \"name\": \"api\" },\n    { \"name\": \"api-wait\", \"options\": { \"arguments\": { \"x-dead-letter-exchange\": \"items\" } } },\n    { \"name\": \"responses\" }\n  ],\n  \"queueBindings\": [\n    { \"queue\": \"bulk-changes\", \"exchange\": \"bulk-changes\", \"pattern\": \"#\" },\n    { \"queue\": \"items\", \"exchange\": \"items\", \"pattern\": \"#\" },\n    { \"queue\": \"api\", \"exchange\": \"api\", \"pattern\": \"#\" },\n    { \"queue\": \"api-wait\", \"exchange\": \"api-wait\", \"pattern\": \"#\" },\n    { \"queue\": \"responses\", \"exchange\": \"responses\", \"pattern\": \"#\" }\n  ],\n  \"messages\": [\n    { \"exchange\": \"bulk-changes\", \"key\": \"routing-key\", \"content\": { \"type\": \"via exchange\" } },\n    { \"queue\": \"bulk-changes\", \"content\": { \"type\": \"direct push\" }, \"count\": 7 }\n  ]\n}\n```\n\nNow you have a release with build number `1234` with all the message handlers ready. The handlers know the name of the\nmain entry exchange (i.e. `main`), and know the schema above they need to work with. Also, they know (e.g. through their\nconfig file) that the build number is `1234` and that all exchanges and queues that are part of data-processing pipeline\nwill be prefixed with this build number in RabbitMQ.\n\nNow you add your data-processing RabbitMQ pipeline, prefixed with the build number like this:\n\n```\n$ bunny-migrate add --schema schema.json --prefix 1234\n```\n\nYou can verify what you have just added to RabbitMQ with \n\n```\n$ bunny-migrate list\n```\n\nThe `messages` section of the above schema illustrates how to populate queues with messages (with token messages, for\ntesting, ...). In our example we push one message to `bulk-changes` exchange with routing key `routing-key` and with\ngiven content (payload). Taking into account the first binding defined in the `queueBindings` array (esp. routing\npattern `#`), this message ends up in the `bulk-changes` queue.\n\nThe second record in `messages` array demonstrates direct message push to specified queue (to `bulk-changes` queue\nagain). This time we added `count` option set to 7, so in the end you end up with 8 messages in total in that queue.\n\n#### Managed rules\n\nAs you can see from the `list` command output above, the rules section is now still empty. I.e. there is no routing\ndefined from your `main` exchange into the starting exchange of your data-processing pipeline.\n\nSince we installed just one instance of the schema for now, let's define rules that will route all\n`regular`, `beta`, and `alpha` users to this schema instance:\n\n```\n$ bunny-migrate add-rule --prefix 1234 --source main --destination bulk-changes --key regular\n```\n\nWe will be using source exchange `main` and destination exchange `bulk-changes` in the future as well, so no need to\nspecify that each time on the command line, let's extend our `bunny-migrate.cfg` file with these items, so the file now\nbecomes:\n\n```\n{\n  \"uri\": \"amqp://user:password@machine:5672/vhost\",\n  \"bunny-x\": \"bunny-admin\",\n  \"source\": \"main\",\n  \"destination\": \"bulk-changes\"\n}\n```\n\nAdding routing rules for `beta` and `alpha` users is then easier:\n\n```\n$ bunny-migrate add-rule --prefix 1234 --key beta\n$ bunny-migrate add-rule --prefix 1234 --key alpha\n```\n\nNow you can start all your workers and web-server(s) and everything will be routed / processed as expected, all user\ntraffic will be routed through the schema with prefix `1234` and processed by corresponding message handlers.\n\n#### Deploying new releases with *zero downtime*\n\nLater on you have a new release, `2345`, with updated message handlers and perhaps even the RabbitMQ schema (but still\nthe entry point to the data-processing part is the `bulk-changes` exchange).\n\nYou keep the existing infrastructure running as is (that is release `1234` and its workers / message handlers), as it\ncan take hours for all the messages there to be processed / drained.\n\nSo in parallel to release `1234` we can add RabbitMQ schema instance for release `2345`:\n\n```\n$ bunny-migrate add --schema schema.json --prefix 2345\n```\n\nAssuming you have also new worker(s) / message handlers deployed in parallel, you can start them now. Again, there is no\ntraffic routed to new pipeline `2345`, since all of it is still routed to previous pipeline `1234`.\n\nYou want to test with your `alpha` users first that the new pipeline is working fine, so let's route only `alpha`\nusers to the new pipeline:\n\n```\n$ bunny-migrate update-rule --prefix 2345 --key alpha\n```\n\nLater on you might route `beta` and `regular` users to new pipeline, too:\n\n```\n$ bunny-migrate update-rule --prefix 2345 --key beta\n$ bunny-migrate update-rule --prefix 2345 --key regular\n```\n\nThen eventually (with some delay) all the messages in pipeline `1234` are processed / drained, so you don't need the\nworkers / message handlers associated to it (so can turn them off and possibly release the boxes), and also you can\nremove the corresponding RabbitMQ schema instance:\n\n```\n$ bunny-migrate remove --prefix 1234\n```\n\n## Building from code\n\n```\n$ git clone git@github.com:salsita/bunny-migrate.git\n$ cd bunny-migrate\n$ npm i\n$ npm run build\n```\n\n### `package.json` npm scripts\n\n```\n$ npm run build\n```\n\nGenerate version file, lint the ES6 source code, transpile the ES6 source code into `dist` directory, and verify the\n(transpiled) tests pass on the (transpiled) code.\n\n```\n$ npm run babel\n```\n\nTranspile (using babel with `.babelrc` configuration file) the ES6 source code\nfile into `dist` directory, that is referenced from binary `bin/bunny-migrate`.\n\n```\n$ npm run gen-ver\n```\nGenerate `version.js` file exporting the current name and version of the tool, as taken from `package.json` itself.\n\n```\n$ npm run lint\n```\nLint the (ES6) source code, using `.eslintrc.json` configuration file.\n\n\n## Licence\n\nMIT License\n\nCopyright (c) 2017 -- 2019 Salsita Software\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalsita%2Fbunny-migrate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsalsita%2Fbunny-migrate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsalsita%2Fbunny-migrate/lists"}