From e9371a9060af80d6bc2733af5b0c2adf834accce Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Mon, 12 Jul 2021 19:37:12 +0200 Subject: [PATCH 01/91] ci: update global workflows --- .github/workflows/if-nodejs-version-bump.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/if-nodejs-version-bump.yml b/.github/workflows/if-nodejs-version-bump.yml index c1f5c860..42d0f499 100644 --- a/.github/workflows/if-nodejs-version-bump.yml +++ b/.github/workflows/if-nodejs-version-bump.yml @@ -10,7 +10,7 @@ on: jobs: version_bump: - name: Generate assets and bump + name: Bump version runs-on: ubuntu-latest steps: - name: Checkout repository @@ -26,9 +26,6 @@ jobs: - if: steps.packagejson.outputs.exists == 'true' name: Install dependencies run: npm install - - if: steps.packagejson.outputs.exists == 'true' - name: Assets generation - run: npm run generate:assets - if: steps.packagejson.outputs.exists == 'true' name: Bump version in package.json # There is no need to substract "v" from the tag as version script handles it From 277f0f6bfc95a963e4959b5bf9f28d52d3a25b04 Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Sat, 17 Jul 2021 19:04:50 +0200 Subject: [PATCH 02/91] ci: update global workflows --- .github/workflows/if-nodejs-version-bump.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/if-nodejs-version-bump.yml b/.github/workflows/if-nodejs-version-bump.yml index 42d0f499..c1f5c860 100644 --- a/.github/workflows/if-nodejs-version-bump.yml +++ b/.github/workflows/if-nodejs-version-bump.yml @@ -10,7 +10,7 @@ on: jobs: version_bump: - name: Bump version + name: Generate assets and bump runs-on: ubuntu-latest steps: - name: Checkout repository @@ -26,6 +26,9 @@ jobs: - if: steps.packagejson.outputs.exists == 'true' name: Install dependencies run: npm install + - if: steps.packagejson.outputs.exists == 'true' + name: Assets generation + run: npm run generate:assets - if: steps.packagejson.outputs.exists == 'true' name: Bump version in package.json # There is no need to substract "v" from the tag as version script handles it From f380502cc51d0cd629793d5ca4c25287ff714d8b Mon Sep 17 00:00:00 2001 From: Jonas Lagoni Date: Fri, 6 Aug 2021 10:14:33 +0200 Subject: [PATCH 03/91] docs: initial NATS bindings (#6) --- nats/README.md | 17 +++++----------- nats/json_schemas/operation.json | 33 ++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 12 deletions(-) create mode 100644 nats/json_schemas/operation.json diff --git a/nats/README.md b/nats/README.md index c8e726e1..24e0dfef 100644 --- a/nats/README.md +++ b/nats/README.md @@ -8,33 +8,26 @@ This document defines how to describe NATS-specific information on AsyncAPI. Current version is `0.1.0`. - ## Server Binding Object - This object MUST NOT contain any properties. Its name is reserved for future use. - - ## Channel Binding Object - This object MUST NOT contain any properties. Its name is reserved for future use. - - ## Operation Binding Object -This object MUST NOT contain any properties. Its name is reserved for future use. - - - - +Field Name | Type | Description +---|:---:|--- +| `queue` | string | Defines the name of the queue to use. It MUST NOT exceed 255 characters. | +| `bindingVersion` | string | The version of this binding. If omitted, "latest" MUST be assumed. | + ## Message Binding Object This object MUST NOT contain any properties. Its name is reserved for future use. diff --git a/nats/json_schemas/operation.json b/nats/json_schemas/operation.json new file mode 100644 index 00000000..d98851e8 --- /dev/null +++ b/nats/json_schemas/operation.json @@ -0,0 +1,33 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/nats/operation.json", + "title": "Operation Schema", + "description": "This object contains information about the operation representation in NATS.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.7.7/schemas/2.0.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "queue": { + "type": "string", + "description": "Defines the name of the queue to use. It MUST NOT exceed 255 characters.", + "maxLength": 255 + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "queue": "MyCustomQueue", + "bindingVersion": "0.1.0" + } + ] +} From 6be67c0894d419c50289b0fe3703dc7116439181 Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Tue, 10 Aug 2021 14:25:02 +0200 Subject: [PATCH 04/91] ci: update global workflows --- .github/workflows/automerge-orphans.yml | 8 +++++++- .github/workflows/automerge.yml | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/workflows/automerge-orphans.yml b/.github/workflows/automerge-orphans.yml index d712c13d..a053d0cb 100644 --- a/.github/workflows/automerge-orphans.yml +++ b/.github/workflows/automerge-orphans.yml @@ -38,7 +38,13 @@ jobs: }; const { repository: { pullRequests: { nodes } } } = await github.graphql(query, variables); - let orphans = nodes.filter((pr)=> pr.state === 'OPEN' && (pr.author.resourcePath === '/asyncapi-bot' || pr.author.resourcePath === '/apps/dependabot')) + let orphans = nodes.filter((pr)=> { + console.log('PR', pr.url); + console.log('State', pr.state); + console.log('Author resource path', pr.author.resourcePath); + console.log('Is orphan', pr.state === 'OPEN' && (pr.author.resourcePath === '/asyncapi-bot' || pr.author.resourcePath === '/apps/dependabot')); + return pr.state === 'OPEN' && (pr.author.resourcePath === '/asyncapi-bot' || pr.author.resourcePath === '/apps/dependabot') + }) if (orphans.length) { core.setOutput('found', 'true'); diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 19c8d189..1968cb80 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -26,7 +26,7 @@ jobs: steps: - name: Autoapproving uses: hmarr/auto-approve-action@v2 - if: github.actor == 'asyncapi-bot' || github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]' + if: github.actor == ('asyncapi-bot' || github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') && !contains(github.event.pull_request.labels.*.name, 'released') with: github-token: "${{ secrets.GITHUB_TOKEN }}" @@ -44,4 +44,4 @@ jobs: MERGE_METHOD: "squash" MERGE_COMMIT_MESSAGE: "pull-request-title" MERGE_RETRIES: "20" - MERGE_RETRY_SLEEP: "20000" \ No newline at end of file + MERGE_RETRY_SLEEP: "30000" \ No newline at end of file From 443755e77ae4545c3a27347d2276692aca1474a6 Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Thu, 12 Aug 2021 11:08:03 +0200 Subject: [PATCH 05/91] ci: update global workflows --- .github/workflows/bump.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/bump.yml diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml new file mode 100644 index 00000000..48a252ff --- /dev/null +++ b/.github/workflows/bump.yml @@ -0,0 +1,32 @@ +#This action is centrally managed in https://github.com/asyncapi/.github/ +#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + +#Purpose of this action is to update npm package in libraries that use it. It is like dependabot for asyncapi npm modules only. +#It runs in a repo after merge of release commit and searches for other packages that use released package. Every found package gets updated with lates version + +name: Bump package version in dependent repos - if Node project + +on: + #It cannot run on release event as when release is created then version is not yet bumped in package.json + #This means we cannot extract easily latest version and have a risk that package is not yet on npm + push: + branches: + - master + +jobs: + bump: + runs-on: ubuntu-latest + steps: + - name: Checkout repo + uses: actions/checkout@v2 + - name: Check if Node.js project and has package.json + id: packagejson + run: test -e ./package.json && echo "::set-output name=exists::true" || echo "::set-output name=exists::false" + - if: steps.packagejson.outputs.exists == 'true' && startsWith(github.event.commits[0].message, 'chore(release):') + name: Bumping latest version of this package in other repositories + uses: derberg/custom-dependabot-for-your-github-org@v2 + with: + github_token: ${{ secrets.GH_TOKEN }} + committer_username: asyncapi-bot + committer_email: info@asyncapi.io + repos_to_ignore: html-template #this is temporary until react component releases 1.0, then it can be removed \ No newline at end of file From db40084d9eb0316c84a8f6cfb2b93f1aa409bc37 Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Thu, 2 Sep 2021 09:03:28 +0200 Subject: [PATCH 06/91] ci: update global workflows --- .github/workflows/automerge-orphans.yml | 11 ++------- .github/workflows/bump.yml | 4 ++-- .../workflows/issues-prs-notifications.yml | 23 +++++++++++++++++++ 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/.github/workflows/automerge-orphans.yml b/.github/workflows/automerge-orphans.yml index a053d0cb..8b8c6c2d 100644 --- a/.github/workflows/automerge-orphans.yml +++ b/.github/workflows/automerge-orphans.yml @@ -20,14 +20,13 @@ jobs: script: | const query = `query($owner:String!, $name:String!) { repository(owner:$owner, name:$name){ - pullRequests(first: 100){ + pullRequests(first: 100, states: OPEN){ nodes{ title url author { resourcePath } - state } } } @@ -38,13 +37,7 @@ jobs: }; const { repository: { pullRequests: { nodes } } } = await github.graphql(query, variables); - let orphans = nodes.filter((pr)=> { - console.log('PR', pr.url); - console.log('State', pr.state); - console.log('Author resource path', pr.author.resourcePath); - console.log('Is orphan', pr.state === 'OPEN' && (pr.author.resourcePath === '/asyncapi-bot' || pr.author.resourcePath === '/apps/dependabot')); - return pr.state === 'OPEN' && (pr.author.resourcePath === '/asyncapi-bot' || pr.author.resourcePath === '/apps/dependabot') - }) + let orphans = nodes.filter( (pr) => pr.author.resourcePath === '/asyncapi-bot' || pr.author.resourcePath === '/apps/dependabot') if (orphans.length) { core.setOutput('found', 'true'); diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index 48a252ff..66af7a55 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -24,9 +24,9 @@ jobs: run: test -e ./package.json && echo "::set-output name=exists::true" || echo "::set-output name=exists::false" - if: steps.packagejson.outputs.exists == 'true' && startsWith(github.event.commits[0].message, 'chore(release):') name: Bumping latest version of this package in other repositories - uses: derberg/custom-dependabot-for-your-github-org@v2 + uses: derberg/npm-dependency-manager-for-your-github-org@v3 with: github_token: ${{ secrets.GH_TOKEN }} committer_username: asyncapi-bot committer_email: info@asyncapi.io - repos_to_ignore: html-template #this is temporary until react component releases 1.0, then it can be removed \ No newline at end of file + repos_to_ignore: html-template #this is temporary until react component releases 1.0, then it can be removed diff --git a/.github/workflows/issues-prs-notifications.yml b/.github/workflows/issues-prs-notifications.yml index 8577db6f..41035079 100644 --- a/.github/workflows/issues-prs-notifications.yml +++ b/.github/workflows/issues-prs-notifications.yml @@ -1,5 +1,7 @@ #This action is centrally managed in https://github.com/asyncapi/.github/ #Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + +#This action notifies community on slack whenever there is a new issue, PR or discussion started in given repository name: Notify slack on: @@ -10,6 +12,9 @@ on: pull_request_target: types: [opened, reopened, ready_for_review] + discussion: + types: [opened] + jobs: issue: @@ -47,3 +52,21 @@ jobs: SLACK_TITLE: 💪 New Pull Request 💪 SLACK_MESSAGE: ${{steps.prmarkdown.outputs.text}} MSG_MINIMAL: true + + discussion: + if: github.event_name == 'discussion' && github.actor != 'asyncapi-bot' && github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]' + name: On every new pull request + runs-on: ubuntu-latest + steps: + - name: Convert markdown to slack markdown for pull request + uses: LoveToKnow/slackify-markdown-action@v1.0.0 + id: discussionmarkdown + with: + text: "[${{github.event.discussion.title}}](${{github.event.discussion.html_url}}) \n ${{github.event.discussion.body}}" + - name: Send info about pull request + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{secrets.SLACK_GITHUB_NEWISSUEPR}} + SLACK_TITLE: 💬 New Discussion 💬 + SLACK_MESSAGE: ${{steps.discussionmarkdown.outputs.text}} + MSG_MINIMAL: true From 68f9b2fb32cc93f666b3fba6f8c3b74dc52c99cb Mon Sep 17 00:00:00 2001 From: Ludovic Dussart Date: Wed, 15 Sep 2021 06:18:21 +0200 Subject: [PATCH 07/91] feat(kafka): add AVRO type in message key binding (#81) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(kafka): add AVRO type in message key binding Refer to asyncapi/avro-schema-parser#67 * Update kafka/README.md Co-authored-by: Fran Méndez Co-authored-by: Fran Méndez --- kafka/README.md | 4 ++-- kafka/json_schemas/message.json | 6 ++++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/kafka/README.md b/kafka/README.md index c3ea466f..adc5606e 100644 --- a/kafka/README.md +++ b/kafka/README.md @@ -6,7 +6,7 @@ This document defines how to describe Kafka-specific information on AsyncAPI. ## Version -Current version is `0.1.0`. +Current version is `0.2.0`. @@ -69,7 +69,7 @@ This object contains information about the message representation in Kafka. Field Name | Type | Description ---|:---:|--- -`key` | [Schema Object][schemaObject] | The message key. +`key` | [Schema Object][schemaObject] \| [AVRO Schema Object](https://avro.apache.org/docs/current/spec.html) | The message key. **NOTE**: You can also use the [reference object](https://asyncapi.io/docs/specifications/v2.1.0#referenceObject) way. `bindingVersion` | string | The version of this binding. If omitted, "latest" MUST be assumed. This object MUST contain only the properties defined above. diff --git a/kafka/json_schemas/message.json b/kafka/json_schemas/message.json index 8509ebb7..701beaa4 100644 --- a/kafka/json_schemas/message.json +++ b/kafka/json_schemas/message.json @@ -31,6 +31,12 @@ ] }, "bindingVersion": "0.1.0" + }, + { + "key": { + "$ref": "path/to/user-create.avsc#/UserCreate" + }, + "bindingVersion": "0.2.0" } ] } From 70dad8210edc7e499c5838bba484f9862401cc52 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=20M=C3=A9ndez?= Date: Wed, 15 Sep 2021 11:16:31 +0200 Subject: [PATCH 08/91] chore: update copyright (#82) --- LICENSE | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 0bddb9b7..ebfe05f2 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright 2019 Francisco Méndez Vilas + Copyright The Linux Foundation Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. From 061b32d5ffda39545ba68cfb247f4b87b2757fb5 Mon Sep 17 00:00:00 2001 From: Gerald Loeffler Date: Wed, 15 Sep 2021 14:12:30 +0200 Subject: [PATCH 09/91] feat: create Anypoint MQ bindings (#63) --- anypointmq/README.md | 220 +++++++++++++++++++++++++++ anypointmq/json_schemas/channel.json | 40 +++++ anypointmq/json_schemas/message.json | 40 +++++ 3 files changed, 300 insertions(+) create mode 100644 anypointmq/README.md create mode 100644 anypointmq/json_schemas/channel.json create mode 100644 anypointmq/json_schemas/message.json diff --git a/anypointmq/README.md b/anypointmq/README.md new file mode 100644 index 00000000..196df062 --- /dev/null +++ b/anypointmq/README.md @@ -0,0 +1,220 @@ +# Anypoint MQ Bindings + +This document defines how to describe Anypoint MQ-specific information in AsyncAPI documents. + +[Anypoint MQ](https://docs.mulesoft.com/mq/) is MuleSoft's multi-tenant, cloud messaging service and is fully integrated with [Anypoint Platform](https://www.mulesoft.com/platform/enterprise-integration). + + +## Versions + +The version of this bindings specification is `0.0.1`. +This is also the `bindingVersion` for all binding objects defined by this specification. +In any given binding object, `latest` MAY alternatively be used to refer to the currently latest published version of this bindings specification. + +## Terminology + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this bindings specification are to be interpreted as described in IETF [RFC2119](https://www.ietf.org/rfc/rfc2119.txt). + +## Protocol + +These bindings use the `anypointmq` [protocol](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#definitionsProtocol) in AsyncAPI documents to denote connections to and interactions with Anypoint MQ message brokers. + +The Anypoint MQ protocol is based on invocations of the [Anypoint MQ Broker REST API](https://anypoint.mulesoft.com/exchange/portals/anypoint-platform/f1e97bc6-315a-4490-82a7-23abe036327a.anypoint-platform/anypoint-mq-broker/). + +## Server Object + +The fields of the standard [Server Object](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#serverObject) are constrained and interpreted as follows: + +Server Object Field Name | Values for Anypoint MQ Protocol | Description +---|:---|:--- +`protocol` | `anypointmq` | **Required**. MUST be `anypointmq` for the scope of this specification. +`url` | e.g., `https://mq-us-east-1.anypoint.mulesoft.com/api` | **Required**. MUST be the endpoint URL of the Anypoint MQ Broker REST API _excluding_ the final major version indicator (e.g., `v1`). Valid examples are `https://mq-us-east-1.anypoint.mulesoft.com/api` and `https://mq-eu-central-1.eu1.anypoint.mulesoft.com/api` (and _not_ `https://.../api/v1`). +`protocolVersion` | e.g., `v1` | **Optional**, defaults to `v1`. If present MUST be the major version indicator of the Anypoint MQ Broker REST API omitted from the `url`, e.g. `v1`. +`security` | suitably configured OAuth 2.0 client credentials grant type | **Required**. Authentication against the MuleSoft-hosted Anypoint MQ message brokers uses the OAuth 2.0 client credentials grant type. At runtime, the client ID and client secret values of an Anypoint MQ client app must be supplied. Also, the OAuth 2.0 scopes are currently not client-configurable. The `security` field of the server object MUST correctly match these constraints. + +Note that the choice of a particular Anypoint MQ client app (via its client ID and secret) decides the Anypoint Platform organization ID and environment (ID) to be those in which this client app is defined. See the [Anypoint MQ documentation](https://docs.mulesoft.com/mq/mq-client-apps) for details on how to configure Anypoint MQ client apps. + + +## Server Binding Object + +This object MUST NOT contain any properties. Its name is reserved for future use. + + +## Channel Binding Object + +The Anypoint MQ [Channel Binding Object](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#channel-bindings-object) is defined by a [JSON Schema](json_schemas/channel.json), which defines these fields: + +Field Name | Type | Description +---|:---:|--- +`destination` | string | **Optional**, defaults to the channel name. The destination (queue or exchange) name for this channel. SHOULD only be specified if the channel name differs from the actual destination name, such as when the channel name is not a valid destination name in Anypoint MQ. +`destinationType` | string | **Optional**, defaults to `queue`. The type of destination, which MUST be either `exchange` or `queue` or `fifo-queue`. SHOULD be specified to document the messaging model (publish/subscribe, point-to-point, strict message ordering) supported by this channel. +`bindingVersion` | string | **Optional**, defaults to `latest`. The version of this binding. + +Note that an Anypoint MQ exchange can only be sent to, not received from. To receive messages sent to an exchange, [an intermediary queue must be defined and bound to the exchange](https://docs.mulesoft.com/mq/mq-understanding#message-exchanges). In this bindings specification, these intermediary queues are not exposed in the AsyncAPI document. Instead, it is simply assumed that whenever messages must be received from an exchange, such an intermediary queue is involved yet invisible in the AsyncAPI document. + +### Examples + +The following example shows a `channels` object with two channels, the second having a channel binding object for `anypointmq`: + +```yaml +channels: + user/signup: + description: | + This application receives command messages from this channel about users to sign up. + Minimal configuration, omitting a channel binding object. + publish: + #... + user/signedup: + description: | + This application sends events to this channel about users that have signed up. + Explicitly provides a channel binding object. + bindings: + anypointmq: + destination: user-signup-exchg + destinationType: exchange + bindingVersion: '0.0.1' + subscribe: + #... +``` + + +## Operation Binding Object + +This object MUST NOT contain any properties. Its name is reserved for future use. + + +## Message Binding Object + +The Anypoint MQ [Message Binding Object](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#message-bindings-object) is defined by a [JSON Schema](json_schemas/message.json), which defines these fields: + +Field Name | Type | Description +---|:---:|--- +`headers` | [Schema Object](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#schemaObject) | **Optional**. A Schema object containing the definitions for Anypoint MQ-specific headers (so-called protocol headers). This schema MUST be of type `object` and have a `properties` key. Examples of Anypoint MQ protocol headers are `messageId` and `messageGroupId`. +`bindingVersion` | string | **Optional**, defaults to `latest`. The version of this binding. + +Note that application headers must be specified in the [`headers` field of the standard Message Object](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#messageObjectHeaders) and are transmitted in the [`properties` section of the Anypoint MQ message](https://anypoint.mulesoft.com/exchange/portals/anypoint-platform/f1e97bc6-315a-4490-82a7-23abe036327a.anypoint-platform/anypoint-mq-broker/). +In contrast, protocol headers such as `messageId` must be specified in the [`headers` field of the message binding object](#messageBindingObjectHeaders) and are transmitted in the [`headers` section of the Anypoint MQ message](https://anypoint.mulesoft.com/exchange/portals/anypoint-platform/f1e97bc6-315a-4490-82a7-23abe036327a.anypoint-platform/anypoint-mq-broker/). + +### Examples + +The following example shows a `channels` object with two channels, each having one operation (`subscribe` or `publish`) with one message. Only the message of the `subscribe` operation has a message binding object for `anypointmq`: + +```yaml +channels: + user/signup: + publish: + message: + #... + user/signedup: + subscribe: + message: + headers: + type: object + properties: + correlationId: + description: Correlation ID set by application + type: string + payload: + type: object + properties: + #... + correlationId: + description: Correlation ID is specified as a header and transmitted in the Anypoint MQ message properties section + location: $message.header#/correlationId + bindings: + anypointmq: + headers: + type: object + properties: + messageId: + type: string + bindingVersion: '0.0.1' +``` + +## Complete Example + +The following is a complete, simple AsyncAPI document illustrating the usage of all binding objects defined in this bindings specification, with all their fields. + +```yaml +asyncapi: '2.0.0' +info: + title: Example with Anypoint MQ + version: '0.0.1' + +servers: + development: + protocol: anypointmq + protocolVersion: v1 + url: https://mq-us-east-1.anypoint.mulesoft.com/api + description: | + Anypoint MQ broker for development, in the US East (N. Virginia) runtime plane + under management of the US control plane. + security: + - oauthDev: [] + production: + protocol: anypointmq + protocolVersion: v1 + url: https://mq-eu-central-1.eu1.anypoint.mulesoft.com/api + description: | + Anypoint MQ broker for production, in the EU Central (Frankfurt) runtime plane + under management of the EU control plane. + security: + - oauthProd: [] + bindings: + anypointmq: + bindingVersion: '0.0.1' + +channels: + user/signup: + description: | + This application receives command messages from this channel about users to sign up. + bindings: + anypointmq: + destination: user-signup-queue + destinationType: fifo-queue + bindingVersion: '0.0.1' + publish: + operationId: signUpUser + description: | + This application receives command messages via this operation about users to sign up. + message: + contentType: application/json + headers: + type: object + properties: + correlationId: + description: Correlation ID set by application + type: string + payload: + type: object + properties: + username: + type: string + minLength: 3 + correlationId: + description: Correlation ID is specified as a header and transmitted in the Anypoint MQ message properties section + location: $message.header#/correlationId + bindings: + anypointmq: + headers: + type: object + properties: + messageId: + type: string + bindingVersion: '0.0.1' + +components: + securitySchemes: + oauthDev: + type: oauth2 + flows: + clientCredentials: + tokenUrl: https://mq-us-east-1.anypoint.mulesoft.com/api/v1/authorize + scopes: {} + oauthProd: + type: oauth2 + flows: + clientCredentials: + tokenUrl: https://mq-eu-central-1.eu1.anypoint.mulesoft.com/api/v1/authorize + scopes: {} +``` diff --git a/anypointmq/json_schemas/channel.json b/anypointmq/json_schemas/channel.json new file mode 100644 index 00000000..eece0c50 --- /dev/null +++ b/anypointmq/json_schemas/channel.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/anypointmq/channel.json", + "title": "Channel Schema", + "description": "This object contains configuration for describing an Anypoint MQ exchange, queue, or FIFO queue as an AsyncAPI channel. This objects only contains configuration that can not be provided in the AsyncAPI standard channel object.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.7.7/schemas/2.0.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "destination": { + "type": "string", + "description": "The destination (queue or exchange) name for this channel. SHOULD only be specified if the channel name differs from the actual destination name, such as when the channel name is not a valid destination name in Anypoint MQ. Defaults to the channel name." + }, + "destinationType": { + "type": "string", + "enum": ["exchange", "queue", "fifo-queue"], + "default": "queue", + "description": "The type of destination. SHOULD be specified to document the messaging model (publish/subscribe, point-to-point, strict message ordering) supported by this channel." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.0.1" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + + }, + "examples": [ + { + "destination": "user-signup-exchg", + "destinationType": "exchange", + "bindingVersion": "0.0.1" + } + ] +} diff --git a/anypointmq/json_schemas/message.json b/anypointmq/json_schemas/message.json new file mode 100644 index 00000000..fa63cb5d --- /dev/null +++ b/anypointmq/json_schemas/message.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/anypointmq/message.json", + "title": "Message Schema", + "description": "This object contains configuration for describing an Anypoint MQ message as an AsyncAPI message. This objects only contains configuration that can not be provided in the AsyncAPI standard message object.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.7.7/schemas/2.0.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "headers": { + "$ref": "https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.7.7/schemas/2.0.0.json#/definitions/schema", + "description": "A Schema object containing the definitions for Anypoint MQ-specific headers (protocol headers). This schema MUST be of type 'object' and have a 'properties' key. Examples of Anypoint MQ protocol headers are 'messageId' and 'messageGroupId'." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.0.1" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + + }, + "examples": [ + { + "headers": { + "type": "object", + "properties": { + "messageId": { + "type": "string" + } + } + }, + "bindingVersion": "0.0.1" + } + ] +} From 90b55deacfb0aad762d1e10bf25be4e13bec5cb2 Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Wed, 22 Sep 2021 11:31:16 +0200 Subject: [PATCH 10/91] ci: update global workflows --- .../workflows/welcome-first-time-contrib.yml | 46 ++++++++++--------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/.github/workflows/welcome-first-time-contrib.yml b/.github/workflows/welcome-first-time-contrib.yml index e687e75a..7db8b019 100644 --- a/.github/workflows/welcome-first-time-contrib.yml +++ b/.github/workflows/welcome-first-time-contrib.yml @@ -1,30 +1,34 @@ #This action is centrally managed in https://github.com/asyncapi/.github/ #Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo -name: Welcome first time contributors +######### +#disabled because of https://github.com/asyncapi/.github/issues/73 +######### -on: - pull_request_target: - types: - - opened - issues: - types: - - opened +# name: Welcome first time contributors -jobs: - welcome: - runs-on: ubuntu-latest - steps: - - uses: actions/first-interaction@v1 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - issue-message: | - Welcome to AsyncAPI. Thanks a lot for reporting your first issue. Please check out our [contributors guide](https://github.com/asyncapi/.github/blob/master/CONTRIBUTING.md) and the instructions about a [basic recommended setup](https://github.com/asyncapi/.github/blob/master/git-workflow.md) useful for opening a pull request. +# on: +# pull_request_target: +# types: +# - opened +# issues: +# types: +# - opened - Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out [this issue](https://github.com/asyncapi/asyncapi/issues/115). +# jobs: +# welcome: +# runs-on: ubuntu-latest +# steps: +# - uses: actions/first-interaction@v1 +# with: +# repo-token: ${{ secrets.GITHUB_TOKEN }} +# issue-message: | +# Welcome to AsyncAPI. Thanks a lot for reporting your first issue. Please check out our [contributors guide](https://github.com/asyncapi/.github/blob/master/CONTRIBUTING.md) and the instructions about a [basic recommended setup](https://github.com/asyncapi/.github/blob/master/git-workflow.md) useful for opening a pull request. +# Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out [this issue](https://github.com/asyncapi/asyncapi/issues/115). - pr-message: | - Welcome to AsyncAPI. Thanks a lot for creating your first pull request. Please check out our [contributors guide](https://github.com/asyncapi/.github/blob/master/CONTRIBUTING.md) and the instructions about a [basic recommended setup](https://github.com/asyncapi/.github/blob/master/git-workflow.md) useful for opening a pull request. - Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out [this issue](https://github.com/asyncapi/asyncapi/issues/115). +# pr-message: | +# Welcome to AsyncAPI. Thanks a lot for creating your first pull request. Please check out our [contributors guide](https://github.com/asyncapi/.github/blob/master/CONTRIBUTING.md) and the instructions about a [basic recommended setup](https://github.com/asyncapi/.github/blob/master/git-workflow.md) useful for opening a pull request. + +# Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out [this issue](https://github.com/asyncapi/asyncapi/issues/115). From 8d97e83eb673f9d732a8eae058ac004af8f81a33 Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Mon, 4 Oct 2021 13:45:20 +0200 Subject: [PATCH 11/91] ci: update global workflows --- .github/workflows/if-nodejs-pr-testing.yml | 4 +++- .github/workflows/if-nodejs-release.yml | 6 ++++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/if-nodejs-pr-testing.yml b/.github/workflows/if-nodejs-pr-testing.yml index 21dd5b5a..8d4b0dba 100644 --- a/.github/workflows/if-nodejs-pr-testing.yml +++ b/.github/workflows/if-nodejs-pr-testing.yml @@ -24,9 +24,11 @@ jobs: shell: bash - if: steps.packagejson.outputs.exists == 'true' name: Setup Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: 14 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' - if: steps.packagejson.outputs.exists == 'true' name: Install dependencies id: first-installation diff --git a/.github/workflows/if-nodejs-release.yml b/.github/workflows/if-nodejs-release.yml index 793eccb0..c03d333f 100644 --- a/.github/workflows/if-nodejs-release.yml +++ b/.github/workflows/if-nodejs-release.yml @@ -29,9 +29,11 @@ jobs: shell: bash - if: steps.packagejson.outputs.exists == 'true' name: Setup Node.js - uses: actions/setup-node@v1 + uses: actions/setup-node@v2 with: node-version: 14 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' - if: steps.packagejson.outputs.exists == 'true' name: Install dependencies run: npm install @@ -67,4 +69,4 @@ jobs: GIT_AUTHOR_EMAIL: info@asyncapi.io GIT_COMMITTER_NAME: asyncapi-bot GIT_COMMITTER_EMAIL: info@asyncapi.io - run: npm run release \ No newline at end of file + run: npm run release From 3b3028f48eff40e64c7c89669ef0e70c6753ce78 Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Mon, 4 Oct 2021 14:55:40 +0200 Subject: [PATCH 12/91] ci: update global workflows --- .github/workflows/stale-issues-prs.yml | 26 +++++++++++++++++++------- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/.github/workflows/stale-issues-prs.yml b/.github/workflows/stale-issues-prs.yml index f1c6a836..00998afb 100644 --- a/.github/workflows/stale-issues-prs.yml +++ b/.github/workflows/stale-issues-prs.yml @@ -11,19 +11,31 @@ jobs: stale: runs-on: ubuntu-latest steps: - - uses: actions/stale@v1.1.0 + - uses: actions/stale@v4.0.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: | This issue has been automatically marked as stale because it has not had recent activity :sleeping: - It will be closed in 60 days if no further activity occurs. To unstale this issue, add a comment with detailed explanation. - Thank you for your contributions :heart: + + It will be closed in 120 days if no further activity occurs. To unstale this issue, add a comment with a detailed explanation. + + There can be many reasons why some specific issue has no activity. The most probable cause is lack of time, not lack of interest. AsyncAPI Initiative is a Linux Foundation project not owned by a single for-profit company. It is a community-driven initiative ruled under [open governance model](https://github.com/asyncapi/community/blob/master/CHARTER.md). + + Let us figure out together how to push this issue forward. Connect with us through [one of many communication channels](https://github.com/asyncapi/community/issues/1) we established here. + + Thank you for your patience :heart: stale-pr-message: | This pull request has been automatically marked as stale because it has not had recent activity :sleeping: - It will be closed in 60 days if no further activity occurs. To unstale this pull request, add a comment with detailed explanation. - Thank you for your contributions :heart: - days-before-stale: 60 - days-before-close: 60 + + It will be closed in 120 days if no further activity occurs. To unstale this pull request, add a comment with detailed explanation. + + There can be many reasons why some specific pull request has no activity. The most probable cause is lack of time, not lack of interest. AsyncAPI Initiative is a Linux Foundation project not owned by a single for-profit company. It is a community-driven initiative ruled under [open governance model](https://github.com/asyncapi/community/blob/master/CHARTER.md). + + Let us figure out together how to push this pull request forward. Connect with us through [one of many communication channels](https://github.com/asyncapi/community/issues/1) we established here. + + Thank you for your patience :heart: + days-before-stale: 120 + days-before-close: 120 stale-issue-label: stale stale-pr-label: stale exempt-issue-label: keep-open From 62c905953871dcf0399b01586d45f875c822f99c Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Tue, 5 Oct 2021 10:23:22 +0200 Subject: [PATCH 13/91] ci: update global workflows --- .github/workflows/stale-issues-prs.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale-issues-prs.yml b/.github/workflows/stale-issues-prs.yml index 00998afb..57c0f185 100644 --- a/.github/workflows/stale-issues-prs.yml +++ b/.github/workflows/stale-issues-prs.yml @@ -38,5 +38,5 @@ jobs: days-before-close: 120 stale-issue-label: stale stale-pr-label: stale - exempt-issue-label: keep-open - exempt-pr-label: keep-open \ No newline at end of file + exempt-issue-labels: keep-open + exempt-pr-labels: keep-open \ No newline at end of file From 8e7c1f681fc7963d1a4cd99f5c5844edc3e7be9d Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Thu, 7 Oct 2021 16:26:22 +0200 Subject: [PATCH 14/91] ci: update global workflows --- .github/workflows/issues-prs-notifications.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issues-prs-notifications.yml b/.github/workflows/issues-prs-notifications.yml index 41035079..e9840d6e 100644 --- a/.github/workflows/issues-prs-notifications.yml +++ b/.github/workflows/issues-prs-notifications.yml @@ -13,7 +13,7 @@ on: types: [opened, reopened, ready_for_review] discussion: - types: [opened] + types: [created] jobs: From 93f869d5c51e40d1c29112ffe065a9897306d335 Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Tue, 12 Oct 2021 15:56:45 +0200 Subject: [PATCH 15/91] ci: update global workflows --- .../workflows/welcome-first-time-contrib.yml | 108 +++++++++++++----- 1 file changed, 79 insertions(+), 29 deletions(-) diff --git a/.github/workflows/welcome-first-time-contrib.yml b/.github/workflows/welcome-first-time-contrib.yml index 7db8b019..d238a7a3 100644 --- a/.github/workflows/welcome-first-time-contrib.yml +++ b/.github/workflows/welcome-first-time-contrib.yml @@ -1,34 +1,84 @@ #This action is centrally managed in https://github.com/asyncapi/.github/ #Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo -######### -#disabled because of https://github.com/asyncapi/.github/issues/73 -######### +name: Welcome first time contributors -# name: Welcome first time contributors +on: + pull_request_target: + types: + - opened + issues: + types: + - opened -# on: -# pull_request_target: -# types: -# - opened -# issues: -# types: -# - opened - -# jobs: -# welcome: -# runs-on: ubuntu-latest -# steps: -# - uses: actions/first-interaction@v1 -# with: -# repo-token: ${{ secrets.GITHUB_TOKEN }} -# issue-message: | -# Welcome to AsyncAPI. Thanks a lot for reporting your first issue. Please check out our [contributors guide](https://github.com/asyncapi/.github/blob/master/CONTRIBUTING.md) and the instructions about a [basic recommended setup](https://github.com/asyncapi/.github/blob/master/git-workflow.md) useful for opening a pull request. - -# Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out [this issue](https://github.com/asyncapi/asyncapi/issues/115). - - -# pr-message: | -# Welcome to AsyncAPI. Thanks a lot for creating your first pull request. Please check out our [contributors guide](https://github.com/asyncapi/.github/blob/master/CONTRIBUTING.md) and the instructions about a [basic recommended setup](https://github.com/asyncapi/.github/blob/master/git-workflow.md) useful for opening a pull request. - -# Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out [this issue](https://github.com/asyncapi/asyncapi/issues/115). +jobs: + welcome: + runs-on: ubuntu-latest + steps: + - uses: actions/github-script@v3 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const issueMessage = `Welcome to AsyncAPI. Thanks a lot for reporting your first issue. Please check out our [contributors guide](https://github.com/asyncapi/community/blob/master/CONTRIBUTING.md) and the instructions about a [basic recommended setup](https://github.com/asyncapi/.github/blob/master/git-workflow.md) useful for opening a pull request.
Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out [this issue](https://github.com/asyncapi/asyncapi/issues/115).`; + const prMessage = `Welcome to AsyncAPI. Thanks a lot for creating your first pull request. Please check out our [contributors guide](https://github.com/asyncapi/community/blob/master/CONTRIBUTING.md) useful for opening a pull request.
Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out [this issue](https://github.com/asyncapi/asyncapi/issues/115).`; + if (!issueMessage && !prMessage) { + throw new Error('Action must have at least one of issue-message or pr-message set'); + } + const isIssue = !!context.payload.issue; + let isFirstContribution; + if (isIssue) { + const query = `query($owner:String!, $name:String!, $contributer:String!) { + repository(owner:$owner, name:$name){ + //since we are reading only totalCount there is no need to fetch more than 1. + issues(first: 1, filterBy: {createdBy:$contributer}){ + totalCount + } + } + }`; + const variables = { + owner: context.repo.owner, + name: context.repo.repo, + contributer: context.payload.sender.login + }; + const { repository: { issues: { totalCount } } } = await github.graphql(query, variables); + isFirstContribution = totalCount === 1; + } else { + const query = `query($qstr: String!) { + search(query: $qstr, type: ISSUE, first: 1) { + issueCount + } + }`; + const variables = { + "qstr": `repo:${context.repo.owner}/${context.repo.repo} type:pr author:${context.payload.sender.login}`, + }; + const { search: { issueCount } } = await github.graphql(query, variables); + isFirstContribution = issueCount === 1; + } + + if (!isFirstContribution) { + console.log(`Not the users first contribution.`); + return; + } + const message = isIssue ? issueMessage : prMessage; + // Add a comment to the appropriate place + if (isIssue) { + const issueNumber = context.payload.issue.number; + console.log(`Adding message: ${message} to issue #${issueNumber}`); + await github.issues.createComment({ + owner: context.payload.repository.owner.login, + repo: context.payload.repository.name, + issue_number: issueNumber, + body: message + }); + } + else { + const pullNumber = context.payload.pull_request.number; + console.log(`Adding message: ${message} to pull request #${pullNumber}`); + await github.pulls.createReview({ + owner: context.payload.repository.owner.login, + repo: context.payload.repository.name, + pull_number: pullNumber, + body: message, + event: 'COMMENT' + }); + } From cbbd6242ec25c22b526ec267977561a02ff7fe76 Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Fri, 15 Oct 2021 18:40:03 +0200 Subject: [PATCH 16/91] ci: update global workflows --- .github/workflows/welcome-first-time-contrib.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/welcome-first-time-contrib.yml b/.github/workflows/welcome-first-time-contrib.yml index d238a7a3..8eb2a993 100644 --- a/.github/workflows/welcome-first-time-contrib.yml +++ b/.github/workflows/welcome-first-time-contrib.yml @@ -29,7 +29,6 @@ jobs: if (isIssue) { const query = `query($owner:String!, $name:String!, $contributer:String!) { repository(owner:$owner, name:$name){ - //since we are reading only totalCount there is no need to fetch more than 1. issues(first: 1, filterBy: {createdBy:$contributer}){ totalCount } From bb935530bc820ccfe4541ed2823adba102c636c9 Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Thu, 21 Oct 2021 11:14:46 +0200 Subject: [PATCH 17/91] ci: update global workflows --- .github/workflows/if-nodejs-pr-testing.yml | 4 ++++ .github/workflows/if-nodejs-release.yml | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/.github/workflows/if-nodejs-pr-testing.yml b/.github/workflows/if-nodejs-pr-testing.yml index 8d4b0dba..812e73af 100644 --- a/.github/workflows/if-nodejs-pr-testing.yml +++ b/.github/workflows/if-nodejs-pr-testing.yml @@ -16,6 +16,10 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: + - name: Set git to use LF #to once and for all finish neverending fight between Unix and Windows + run: | + git config --global core.autocrlf false + git config --global core.eol lf - name: Checkout repository uses: actions/checkout@v2 - name: Check if Node.js project and has package.json diff --git a/.github/workflows/if-nodejs-release.yml b/.github/workflows/if-nodejs-release.yml index c03d333f..81a465d8 100644 --- a/.github/workflows/if-nodejs-release.yml +++ b/.github/workflows/if-nodejs-release.yml @@ -46,6 +46,10 @@ jobs: name: Publish to NPM and GitHub runs-on: ubuntu-latest steps: + - name: Set git to use LF #to once and for all finish neverending fight between Unix and Windows + run: | + git config --global core.autocrlf false + git config --global core.eol lf - name: Checkout repository uses: actions/checkout@v2 - name: Check if Node.js project and has package.json From 8e206e98fbe1c213e81b331951029378ebad5750 Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Sun, 31 Oct 2021 18:05:38 +0100 Subject: [PATCH 18/91] ci: update global workflows --- .github/workflows/automerge.yml | 16 ++++++++++++++-- .github/workflows/autoupdate.yml | 28 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/autoupdate.yml diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 1968cb80..6104f470 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -17,7 +17,7 @@ on: pull_request_review: types: - submitted - + jobs: autoapprove: @@ -44,4 +44,16 @@ jobs: MERGE_METHOD: "squash" MERGE_COMMIT_MESSAGE: "pull-request-title" MERGE_RETRIES: "20" - MERGE_RETRY_SLEEP: "30000" \ No newline at end of file + MERGE_RETRY_SLEEP: "30000" + + labelWhenApproved: + needs: [autoapprove] + name: Label when approved + runs-on: ubuntu-latest + steps: + - name: Label when approved + uses: pullreminders/label-when-approved-action@v1.0.7 + env: + APPROVALS: "1" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ADD_LABEL: "autoapproved" \ No newline at end of file diff --git a/.github/workflows/autoupdate.yml b/.github/workflows/autoupdate.yml new file mode 100644 index 00000000..d147580e --- /dev/null +++ b/.github/workflows/autoupdate.yml @@ -0,0 +1,28 @@ +#This action is centrally managed in https://github.com/asyncapi/.github/ +#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + +#This workflow is designed to work with: +# - autoapprove and automerge workflows for dependabot and asyncapibot. +# - special release branches that we from time to time create in upstream repos. If we open up PRs for them from the very beginning of the release, the release branch will constantly update with new things from the destination branch they are opened against + +# It uses GitHub Action that auto-updates pull requests branches, whenever changes are pushed to their destination branch. +#Autoupdating to latest destination branch works only in the context of upstream repo and not forks + +name: autoupdate + +on: + push: {} + +jobs: + + autoupdate: + runs-on: ubuntu-latest + steps: + - name: Autoupdating + uses: docker://chinthakagodawita/autoupdate-action:v1 + env: + GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + PR_FILTER: "labelled" + PR_LABELS: "autoapproved" + PR_READY_STATE: "ready_for_review" + MERGE_CONFLICT_ACTION: "ignore" From 08ae12a6ac18ed82265de5efe9145ece25ceec48 Mon Sep 17 00:00:00 2001 From: Dale Lane Date: Tue, 2 Nov 2021 10:46:21 +0000 Subject: [PATCH 19/91] docs: initial CODEOWNERS file (#98) Signed-off-by: Dale Lane --- CODEOWNERS | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 CODEOWNERS diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 00000000..7fb040ad --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,14 @@ +# This file provides an overview of code owners in this repository. + +# Each line is a file pattern followed by one or more owners. +# The last matching pattern has the most precedence. +# For more details, read the following article on GitHub: https://help.github.com/articles/about-codeowners/. + +# The default owners are automatically added as reviewers when you open a pull request unless different owners are specified in the file. + +/anypointmq @GeraldLoeffler +/ibmmq @rcoppen +/kafka @lbroudoux @dalelane +*.json @KhudaDad414 + +* @derberg @fmvilas @github-actions[bot] From 7dcae3ac9938ada9f3ff431a3f66c3859b5775f9 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Fri, 5 Nov 2021 04:31:46 -0400 Subject: [PATCH 20/91] feat: add solace binding (#85) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Maciej Urbańczyk Co-authored-by: Lukasz Gornicki --- CODEOWNERS | 1 + README.md | 3 +- solace/README.md | 123 +++++++++++++++++++++++++++++ solace/json_schemas/operation.json | 93 ++++++++++++++++++++++ solace/json_schemas/server.json | 32 ++++++++ 5 files changed, 251 insertions(+), 1 deletion(-) create mode 100644 solace/README.md create mode 100644 solace/json_schemas/operation.json create mode 100644 solace/json_schemas/server.json diff --git a/CODEOWNERS b/CODEOWNERS index 7fb040ad..59c8501c 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -9,6 +9,7 @@ /anypointmq @GeraldLoeffler /ibmmq @rcoppen /kafka @lbroudoux @dalelane +/solace @damaru-inc @CameronRushton *.json @KhudaDad414 * @derberg @fmvilas @github-actions[bot] diff --git a/README.md b/README.md index f39c8416..3891c577 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ This repository contains the specifications for each AsyncAPI protocol binding. * [NATS binding](./nats) * [Redis binding](./redis) * [SNS binding](./sns) +* [Solace binding](./solace) * [SQS binding](./sqs) * [STOMP binding](./stomp) -* [WebSockets binding](./websockets) \ No newline at end of file +* [WebSockets binding](./websockets) diff --git a/solace/README.md b/solace/README.md new file mode 100644 index 00000000..f56a0b02 --- /dev/null +++ b/solace/README.md @@ -0,0 +1,123 @@ +# Solace Bindings + +This document defines how to describe Solace-specific information with AsyncAPI. + + + +## Version + +Current version is `0.1.0`. + + + +## Server Binding Object + +Field Name | Type | Description +---|---|--- +`bindingVersion`|String|The current version is 0.1.0 +`msgVpn`|String|The Virtual Private Network name on the Solace broker. + + + + +## Channel Binding Object + +This object MUST NOT contain any properties. Its name is reserved for future use. + + + + + +## Operation Binding Object + +We need the ability to support several bindings for each operation, see the [Example](#example) section below for details. + +Field Name | Type | Description +---|---|--- +`bindingVersion`|String|The current version is 0.1.0 +`destinations`|List of Destination Objects|Destination Objects are described next. + +### Destination Object + +Each destination has the following structure. Note that bindings under a 'subscribe' operation define the behaviour of publishers, and those under a 'publish' operation define how subscribers are configured. + +Field Name | Type | Description | Applicable Operation +---|---|---|--- +`destinationType`|Enum|'queue' or 'topic'. If the type is queue, then the subscriber can bind to the queue, which in turn will subscribe to the topic as represented by the channel name.|publish +`deliveryMode`|String|'direct' or 'persistent'. This determines the quality of service for publishing messages as documented [here.](https://docs.solace.com/PubSub-Basics/Core-Concepts-Message-Delivery-Modes.htm) Default is 'persistent'.|subscribe +`queue.name`|String|The name of the queue, only applicable when destinationType is 'queue'.|publish +`queue.topicSubscriptions`|List of String|A list of topics that the queue subscribes to, only applicable when destinationType is 'queue'.|publish +`queue.accessType`|Enum|'exclusive' or 'nonExclusive'. This is documented [here.](https://docs.solace.com/PubSub-Basics/Endpoints.htm) Only applicable when destinationType is 'queue'.|publish + + + + +## Message Binding Object + +This object MUST NOT contain any properties. Its name is reserved for future use. + + + + + +## Example ## + +Here is an example of when we could need two Solace bindings. + +Imagine a system where there is a schema called Person, and there are topics: + +`person/{personId}/created` + +and + +`person/{personId}/updated` + +and you have one application that receive both events. We also want each to be on its own queue. The AsyncAPI file could look like this: + +```yaml +components: + schemas: + Person: + type: string + messages: + PersonEvent: + payload: + $ref: '#/components/schemas/Person' + schemaFormat: application/vnd.aai.asyncapi+json;version=2.0.0 + contentType: application/json +channels: + 'person/{personId}/{eventType}': + publish: + bindings: + solace: + bindingVersion: 0.1.0 + destinations: + - destinationType: queue + queue: + name: CreatedHREvents + topicSubscriptions: + - person/*/created + - destinationType: queue + queue: + name: UpdatedHREvents + topicSubscriptions: + - person/*/updated + message: + $ref: '#/components/messages/PersonEvent' + parameters: + personId: + schema: + type: string + eventType: + schema: + type: string +asyncapi: 2.1.0 +info: + title: HRApp + version: 0.0.1 +``` + +The expected behaviour would be that the application binds to both queues, and each queue has its own topic subscription, one to created and one to updated events. + + + diff --git a/solace/json_schemas/operation.json b/solace/json_schemas/operation.json new file mode 100644 index 00000000..edf9630d --- /dev/null +++ b/solace/json_schemas/operation.json @@ -0,0 +1,93 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/solace/operation.json", + "title": "Operation Schema", + "description": "This object contains information about the operation representation in Solace.", + "type": "object", + "additionalProperties": false, + "properties": { + "destinations": { + "description": "The list of Solace destinations referenced in the operation.", + "type": "array", + "items": { + "type": "object", + "properties": { + "deliveryMode": { + "type": "string", + "enum": [ + "direct", + "persistent" + ] + } + }, + "oneOf": [ + { + "properties": { + "destinationType": { + "type": "string", + "const": "queue", + "description": "If the type is queue, then the subscriber can bind to the queue, which in turn will subscribe to the topic as represented by the channel name." + }, + "queue": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The name of the queue" + }, + "topicSubscriptions": { + "type": "array", + "description": "The list of topics that the queue subscribes to.", + "items": { + "type": "string" + } + }, + "exclusive": { + "type": "boolean" + } + } + } + } + }, + { + "properties": { + "destinationType": { + "type": "string", + "const": "topic", + "description": "If the type is topic, then the subscriber subscribes to the topic as represented by the channel name." + } + } + } + ] + } + } + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding. If omitted, \"latest\" MUST be assumed." + }, + "examples": [ + { + "bindingVersion": "0.1.0", + "bindings": [ + { + "destinationType": "queue", + "queue": { + "name": "sampleQueue", + "topicSubscriptions": [ + "samples/*" + ], + "exclusive": false + } + }, + { + "destinationType": "topic", + "deliveryMode": "persistent" + } + ] + } + ] +} diff --git a/solace/json_schemas/server.json b/solace/json_schemas/server.json new file mode 100644 index 00000000..e561f292 --- /dev/null +++ b/solace/json_schemas/server.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/solace/server.json", + "title": "Server Schema", + "description": "This object contains server connection information about the Solace broker. This object contains additional connectivity information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.7.7/schemas/2.0.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "msvVpn": { + "type": "string", + "description": "The name of the Virtual Private Network to connect to on the Solace broker." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding." + } + }, + "examples": [ + { + "msgVpn": "ProdVPN", + "bindingVersion": "0.1.0" + } + ] +} From 9872bf23822a8ebbfa69906c6b41015ee9e61cfb Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Tue, 23 Nov 2021 12:25:44 +0100 Subject: [PATCH 21/91] ci: update global workflows --- .github/workflows/automerge.yml | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 6104f470..293acd7e 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -1,5 +1,5 @@ -#This action is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + # This action is centrally managed in https://github.com/asyncapi/.github/ + # Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo. name: Automerge release bump PR @@ -21,15 +21,27 @@ on: jobs: autoapprove: - if: github.event.pull_request.draft == false + if: (github.event.pull_request.draft == false) && (github.actor == 'asyncapi-bot' || github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') && !contains(github.event.pull_request.labels.*.name, 'released') runs-on: ubuntu-latest steps: - name: Autoapproving uses: hmarr/auto-approve-action@v2 - if: github.actor == ('asyncapi-bot' || github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') && !contains(github.event.pull_request.labels.*.name, 'released') with: github-token: "${{ secrets.GITHUB_TOKEN }}" + - name: Label autoapproved + uses: actions/github-script@v5 + with: + github-token: ${{ secrets.GH_TOKEN }} + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['autoapproved'] + }) + + automerge: needs: [autoapprove] runs-on: ubuntu-latest @@ -45,15 +57,3 @@ jobs: MERGE_COMMIT_MESSAGE: "pull-request-title" MERGE_RETRIES: "20" MERGE_RETRY_SLEEP: "30000" - - labelWhenApproved: - needs: [autoapprove] - name: Label when approved - runs-on: ubuntu-latest - steps: - - name: Label when approved - uses: pullreminders/label-when-approved-action@v1.0.7 - env: - APPROVALS: "1" - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - ADD_LABEL: "autoapproved" \ No newline at end of file From dfbb54cbc7a484f509b60367461d407abe511d3f Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Wed, 1 Dec 2021 15:49:11 +0100 Subject: [PATCH 22/91] ci: update global workflows --- .github/workflows/if-nodejs-release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/if-nodejs-release.yml b/.github/workflows/if-nodejs-release.yml index 81a465d8..71f76077 100644 --- a/.github/workflows/if-nodejs-release.yml +++ b/.github/workflows/if-nodejs-release.yml @@ -21,6 +21,10 @@ jobs: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: + - name: Set git to use LF #to once and for all finish neverending fight between Unix and Windows + run: | + git config --global core.autocrlf false + git config --global core.eol lf - name: Checkout repository uses: actions/checkout@v2 - name: Check if Node.js project and has package.json From add251b711db11c94421d2d1a0bc952173a3d816 Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Tue, 7 Dec 2021 16:28:03 +0100 Subject: [PATCH 23/91] ci: update global workflows --- ...d-ready-to-merge-or-do-not-merge-label.yml | 51 +++++++++++++++++++ .../automerge-for-humans-merging.yml | 32 ++++++++++++ ...ns-remove-ready-to-merge-label-on-edit.yml | 28 ++++++++++ .github/workflows/automerge.yml | 4 +- .github/workflows/help-command.yml | 25 +++++++++ 5 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml create mode 100644 .github/workflows/automerge-for-humans-merging.yml create mode 100644 .github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml create mode 100644 .github/workflows/help-command.yml diff --git a/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml b/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml new file mode 100644 index 00000000..c19c17fc --- /dev/null +++ b/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml @@ -0,0 +1,51 @@ +#This workflow is centrally managed in https://github.com/asyncapi/.github/ +#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + +#Purpose of this workflow is to enable anyone to label PR with `ready-to-merge` and `do-not-merge` labels to get stuff merged or blocked from merging +name: Add ready-to-merge or do-not-merge label # if proper comment added + +on: issue_comment + +jobs: + parse-comment-and-add-ready: # for handling cases when you want to mark as ready to merge + if: github.event.issue.pull_request && github.event.issue.state != 'closed' + runs-on: ubuntu-latest + steps: + - name: Check if PR is draft # such info is not available in the context of issue_comment event + uses: actions/github-script@v5 + id: checkDraft + with: + result-encoding: string + script: | + const prDetailsUrl = context.payload.issue.pull_request.url; + const response = await github.request(prDetailsUrl); + return response.data.draft; + - name: Add label + if: steps.checkDraft.outputs.result == 'false' && (contains(github.event.comment.body, '/ready-to-merge') || contains(github.event.comment.body, '/rtm' )) + uses: actions/github-script@v5 + with: + github-token: ${{ secrets.GH_TOKEN }} + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['ready-to-merge'] + }) + + parse-comment-and-add-block: # for handling cases when you want to mark as do-not-merge + if: github.event.issue.pull_request && github.event.issue.state != 'closed' + runs-on: ubuntu-latest + steps: + - name: Add label + if: contains(github.event.comment.body, '/do-not-merge') || contains(github.event.comment.body, '/dnm' ) + uses: actions/github-script@v5 + with: + github-token: ${{ secrets.GH_TOKEN }} + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['do-not-merge'] + }) \ No newline at end of file diff --git a/.github/workflows/automerge-for-humans-merging.yml b/.github/workflows/automerge-for-humans-merging.yml new file mode 100644 index 00000000..b91ef754 --- /dev/null +++ b/.github/workflows/automerge-for-humans-merging.yml @@ -0,0 +1,32 @@ +#This workflow is centrally managed in https://github.com/asyncapi/.github/ +#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + +#Purpose of this workflow is to allow people to merge PR without a need of maintainer doing it. If all checks are in place (including maintainers approval) - JUST MERGE IT! +name: Automerge For Humans + +on: + pull_request_target: + types: + - labeled + - unlabeled + - synchronize + - opened + - edited + - ready_for_review + - reopened + - unlocked + +jobs: + automerge-for-humans: + if: github.event.pull_request.draft == false && (github.event.pull_request.user.login != 'asyncapi-bot' || github.event.pull_request.user.login != 'dependabot[bot]' || github.event.pull_request.user.login != 'dependabot-preview[bot]') #it runs only if PR actor is not a bot, at least not a bot that we know + runs-on: ubuntu-latest + steps: + - name: Automerge PR + uses: pascalgn/automerge-action@v0.14.3 + env: + GITHUB_TOKEN: "${{ secrets.GH_TOKEN }}" + MERGE_LABELS: "!do-not-merge,ready-to-merge" + MERGE_METHOD: "squash" + MERGE_COMMIT_MESSAGE: "pull-request-title" + MERGE_RETRIES: "20" + MERGE_RETRY_SLEEP: "30000" \ No newline at end of file diff --git a/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml b/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml new file mode 100644 index 00000000..d47cf2fb --- /dev/null +++ b/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml @@ -0,0 +1,28 @@ +#This workflow is centrally managed in https://github.com/asyncapi/.github/ +#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + +# Defence from evil contributor that after adding `ready-to-merge` all suddenly makes evil commit or evil change in PR title +# Label is removed once above action is detected +name: Remove ready-to-merge label + +on: + pull_request_target: + types: + - synchronize + - edited + +jobs: + remove-ready-label: + runs-on: ubuntu-latest + steps: + - name: Remove label + uses: actions/github-script@v5 + with: + github-token: ${{ secrets.GH_TOKEN }} + script: | + github.rest.issues.removeLabel({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + name: 'ready-to-merge' + }) \ No newline at end of file diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 293acd7e..4b41128c 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -21,7 +21,7 @@ on: jobs: autoapprove: - if: (github.event.pull_request.draft == false) && (github.actor == 'asyncapi-bot' || github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') && !contains(github.event.pull_request.labels.*.name, 'released') + if: github.event.pull_request.draft == false && (github.event.pull_request.user.login == 'asyncapi-bot' || github.event.pull_request.user.login == 'dependabot[bot]' || github.event.pull_request.user.login == 'dependabot-preview[bot]') && !contains(github.event.pull_request.labels.*.name, 'released') runs-on: ubuntu-latest steps: - name: Autoapproving @@ -44,11 +44,11 @@ jobs: automerge: needs: [autoapprove] + if: github.event.pull_request.user.login == 'asyncapi-bot' || github.event.pull_request.user.login == 'dependabot[bot]' || github.event.pull_request.user.login == 'dependabot-preview[bot]' runs-on: ubuntu-latest steps: - name: Automerging uses: pascalgn/automerge-action@v0.13.0 - if: github.actor == 'asyncapi-bot' || github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]' env: GITHUB_TOKEN: "${{ secrets.GH_TOKEN }}" GITHUB_LOGIN: asyncapi-bot diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml new file mode 100644 index 00000000..4f1d64b2 --- /dev/null +++ b/.github/workflows/help-command.yml @@ -0,0 +1,25 @@ +#This workflow is centrally managed in https://github.com/asyncapi/.github/ +#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + +name: Create help comment + +on: [issue_comment] + +jobs: + create_help_comment: + if: github.event.issue.pull_request + runs-on: ubuntu-latest + steps: + - uses: actions-ecosystem/action-create-comment@v1 + if: contains(github.event.comment.body, '/help') + with: + github_token: ${{ secrets.GH_TOKEN }} + body: | + Hello, @${{ github.actor }}! 👋🏼 + + I'm Genie from the magic lamp. Looks like somebody needs a hand! 🆘 + + At the moment the following comments are supported in pull requests: + + - `/ready-to-merge` or `/rtm` - This comment will trigger automerge of PR in case all required checks are green, approvals in place and do-not-merge label is not added + - `/do-not-merge` or `/dnm` - This comment will block automerging event if all conditions are met and ready-to-merge label is added \ No newline at end of file From 3e986b41a95ff5e3f01aaea9027879d02e98c0f1 Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Wed, 8 Dec 2021 11:31:58 +0100 Subject: [PATCH 24/91] ci: update global workflows --- ...umans-add-ready-to-merge-or-do-not-merge-label.yml | 4 ++-- ...for-humans-remove-ready-to-merge-label-on-edit.yml | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml b/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml index c19c17fc..a606fed3 100644 --- a/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml +++ b/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml @@ -8,7 +8,7 @@ on: issue_comment jobs: parse-comment-and-add-ready: # for handling cases when you want to mark as ready to merge - if: github.event.issue.pull_request && github.event.issue.state != 'closed' + if: github.event.issue.pull_request && github.event.issue.state != 'closed' && github.actor != 'asyncapi-bot' runs-on: ubuntu-latest steps: - name: Check if PR is draft # such info is not available in the context of issue_comment event @@ -34,7 +34,7 @@ jobs: }) parse-comment-and-add-block: # for handling cases when you want to mark as do-not-merge - if: github.event.issue.pull_request && github.event.issue.state != 'closed' + if: github.event.issue.pull_request && github.event.issue.state != 'closed' && github.actor != 'asyncapi-bot' runs-on: ubuntu-latest steps: - name: Add label diff --git a/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml b/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml index d47cf2fb..3fe91579 100644 --- a/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml +++ b/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml @@ -20,9 +20,16 @@ jobs: with: github-token: ${{ secrets.GH_TOKEN }} script: | + const labelToRemove = 'ready-to-merge'; + const labels = context.payload.pull_request.labels; + + const isLabelPresent = labels.some(label => label.name === labelToRemove) + + if(!isLabelPresent) return; + github.rest.issues.removeLabel({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - name: 'ready-to-merge' - }) \ No newline at end of file + name: labelToRemove + }) From 64da88fb5b375ae331f2d2b1917416f913f0f7fb Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Thu, 9 Dec 2021 18:28:51 +0100 Subject: [PATCH 25/91] ci: update global workflows --- .github/workflows/help-command.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index 4f1d64b2..2544f073 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -22,4 +22,4 @@ jobs: At the moment the following comments are supported in pull requests: - `/ready-to-merge` or `/rtm` - This comment will trigger automerge of PR in case all required checks are green, approvals in place and do-not-merge label is not added - - `/do-not-merge` or `/dnm` - This comment will block automerging event if all conditions are met and ready-to-merge label is added \ No newline at end of file + - `/do-not-merge` or `/dnm` - This comment will block automerging even if all conditions are met and ready-to-merge label is added \ No newline at end of file From b67bd6c43f4887e40736e7a4ed754fd32e1d04bd Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Thu, 16 Dec 2021 14:39:18 +0100 Subject: [PATCH 26/91] ci: update global workflows --- ...e-for-humans-add-ready-to-merge-or-do-not-merge-label.yml | 5 ++++- .github/workflows/help-command.yml | 5 ++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml b/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml index a606fed3..68f39603 100644 --- a/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml +++ b/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml @@ -4,7 +4,10 @@ #Purpose of this workflow is to enable anyone to label PR with `ready-to-merge` and `do-not-merge` labels to get stuff merged or blocked from merging name: Add ready-to-merge or do-not-merge label # if proper comment added -on: issue_comment +on: + issue_comment: + types: + - created jobs: parse-comment-and-add-ready: # for handling cases when you want to mark as ready to merge diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index 2544f073..0fac866f 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -3,7 +3,10 @@ name: Create help comment -on: [issue_comment] +on: + issue_comment: + types: + - created jobs: create_help_comment: From 930eb2069b9291b6f2bfca4e30e7dd1af3af5d2a Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Mon, 10 Jan 2022 14:31:49 +0100 Subject: [PATCH 27/91] ci: update global workflows --- .../workflows/add-good-first-issue-labels.yml | 68 +++++++++++++++++++ .github/workflows/help-command.yml | 23 +++++-- 2 files changed, 87 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/add-good-first-issue-labels.yml diff --git a/.github/workflows/add-good-first-issue-labels.yml b/.github/workflows/add-good-first-issue-labels.yml new file mode 100644 index 00000000..3b816b63 --- /dev/null +++ b/.github/workflows/add-good-first-issue-labels.yml @@ -0,0 +1,68 @@ +#This workflow is centrally managed in https://github.com/asyncapi/.github/ +#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + +#Purpose of this workflow is to enable anyone to label issue with 'Good First Issue' and 'area/*' with a single command. +name: Add 'Good First Issue' and 'area/*' labels # if proper comment added + +on: + issue_comment: + types: + - created + +jobs: + add-labels: + if: github.event.issue && github.event.issue.state != 'closed' + runs-on: ubuntu-latest + steps: + - name: Add label + if: contains(github.event.comment.body, '/good-first-issue') || contains(github.event.comment.body, '/gfi' ) + uses: actions/github-script@v5 + with: + github-token: ${{ secrets.GH_TOKEN }} + script: | + const areas = ['javascript', 'typescript', 'java' , 'go', 'docs', 'ci-cd', 'design']; + const values = context.payload.comment.body.split(" "); + switch(values[1]){ + case 'ts': + values[1] = 'typescript'; + break; + case 'js': + values[1] = 'javascript'; + case 'markdown': + values[1] = 'docs'; + } + if(values.length != 2 || !areas.includes(values[1])){ + const message = `Hey @${context.payload.sender.login}, something is wrong with your command please use \`/help\` for help.` + + await github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: message + }) + } else { + + //remove complexity and areas if there are any before adding new labels; + const currentLabels = (await github.rest.issues.listLabelsOnIssue({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + })).data.map(label => label.name); + + const shouldBeRemoved = currentLabels.filter(label => (label.startsWith('area/') && !label.endsWith(values[1]))); + shouldBeRemoved.forEach(label => { + github.rest.issues.deleteLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + name: label, + }); + }); + + //add new labels + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['good first issue', `area/${values[1]}`] + }); + } diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index 0fac866f..eee77301 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -9,12 +9,11 @@ on: - created jobs: - create_help_comment: - if: github.event.issue.pull_request + create_help_comment_pr: + if: github.event.issue.pull_request && contains(github.event.comment.body, '/help') runs-on: ubuntu-latest steps: - uses: actions-ecosystem/action-create-comment@v1 - if: contains(github.event.comment.body, '/help') with: github_token: ${{ secrets.GH_TOKEN }} body: | @@ -25,4 +24,20 @@ jobs: At the moment the following comments are supported in pull requests: - `/ready-to-merge` or `/rtm` - This comment will trigger automerge of PR in case all required checks are green, approvals in place and do-not-merge label is not added - - `/do-not-merge` or `/dnm` - This comment will block automerging even if all conditions are met and ready-to-merge label is added \ No newline at end of file + - `/do-not-merge` or `/dnm` - This comment will block automerging even if all conditions are met and ready-to-merge label is added + create_help_comment_issue: + if: ${{ !github.event.issue.pull_request && contains(github.event.comment.body, '/help') }} + runs-on: ubuntu-latest + steps: + - uses: actions-ecosystem/action-create-comment@v1 + with: + github_token: ${{ secrets.GH_TOKEN }} + body: | + Hello, @${{ github.actor }}! 👋🏼 + + I'm Genie from the magic lamp. Looks like somebody needs a hand! 🆘 + + At the moment the following comments are supported in issues: + + - `/good-first-issue {js | ts | java | go | docs | design | ci-cd} ` or `/gfi {js | ts | java | go | docs | design | ci-cd} ` - label an issue as a `good first issue`. + example: `/gfi js` or `/good-first-issue ci-cd` From 966f6ace9bd6841ee52eeb104f4186c6cd0e063e Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Thu, 13 Jan 2022 19:49:36 +0100 Subject: [PATCH 28/91] ci: update global workflows --- .github/workflows/automerge-for-humans-merging.yml | 2 +- .github/workflows/automerge.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/automerge-for-humans-merging.yml b/.github/workflows/automerge-for-humans-merging.yml index b91ef754..37ee722f 100644 --- a/.github/workflows/automerge-for-humans-merging.yml +++ b/.github/workflows/automerge-for-humans-merging.yml @@ -27,6 +27,6 @@ jobs: GITHUB_TOKEN: "${{ secrets.GH_TOKEN }}" MERGE_LABELS: "!do-not-merge,ready-to-merge" MERGE_METHOD: "squash" - MERGE_COMMIT_MESSAGE: "pull-request-title" + MERGE_COMMIT_MESSAGE: "{pullRequest.title} (#{pullRequest.number})" MERGE_RETRIES: "20" MERGE_RETRY_SLEEP: "30000" \ No newline at end of file diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 4b41128c..161e89a1 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -54,6 +54,6 @@ jobs: GITHUB_LOGIN: asyncapi-bot MERGE_LABELS: "" MERGE_METHOD: "squash" - MERGE_COMMIT_MESSAGE: "pull-request-title" + MERGE_COMMIT_MESSAGE: "{pullRequest.title} (#{pullRequest.number})" MERGE_RETRIES: "20" MERGE_RETRY_SLEEP: "30000" From 2c9503a662ec5e3861f0e013c697c578eebd7bad Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Thu, 13 Jan 2022 23:13:45 +0100 Subject: [PATCH 29/91] ci: update global workflows (#110) --- .github/workflows/if-nodejs-release.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/if-nodejs-release.yml b/.github/workflows/if-nodejs-release.yml index 71f76077..2682e5b3 100644 --- a/.github/workflows/if-nodejs-release.yml +++ b/.github/workflows/if-nodejs-release.yml @@ -47,7 +47,7 @@ jobs: release: needs: test - name: Publish to NPM and GitHub + name: Publish to any of NPM, Github, and Docker Hub runs-on: ubuntu-latest steps: - name: Set git to use LF #to once and for all finish neverending fight between Unix and Windows @@ -68,11 +68,13 @@ jobs: name: Install dependencies run: npm install - if: steps.packagejson.outputs.exists == 'true' - name: Release to NPM and GitHub + name: Publish to any of NPM, Github, and Docker Hub id: release env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} NPM_TOKEN: ${{ secrets.NPM_TOKEN }} + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} GIT_AUTHOR_NAME: asyncapi-bot GIT_AUTHOR_EMAIL: info@asyncapi.io GIT_COMMITTER_NAME: asyncapi-bot From e56651459cb3fbcdcdbf76133b2c5f6c5d9a57fc Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Fri, 14 Jan 2022 08:57:28 +0100 Subject: [PATCH 30/91] ci: update global workflows (#111) --- .github/workflows/add-good-first-issue-labels.yml | 4 ++-- .github/workflows/help-command.yml | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/add-good-first-issue-labels.yml b/.github/workflows/add-good-first-issue-labels.yml index 3b816b63..b9cdd067 100644 --- a/.github/workflows/add-good-first-issue-labels.yml +++ b/.github/workflows/add-good-first-issue-labels.yml @@ -11,7 +11,7 @@ on: jobs: add-labels: - if: github.event.issue && github.event.issue.state != 'closed' + if: ${{!github.event.issue.pull_request && github.event.issue.state != 'closed' && github.actor != 'asyncapi-bot'}} runs-on: ubuntu-latest steps: - name: Add label @@ -32,7 +32,7 @@ jobs: values[1] = 'docs'; } if(values.length != 2 || !areas.includes(values[1])){ - const message = `Hey @${context.payload.sender.login}, something is wrong with your command please use \`/help\` for help.` + const message = `Hey @${context.payload.sender.login}, your message doesn't follow the requirements, you can try \`/help\`.` await github.rest.issues.createComment({ issue_number: context.issue.number, diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index eee77301..fd7ed399 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -10,7 +10,7 @@ on: jobs: create_help_comment_pr: - if: github.event.issue.pull_request && contains(github.event.comment.body, '/help') + if: github.event.issue.pull_request && contains(github.event.comment.body, '/help' && github.actor != 'asyncapi-bot') runs-on: ubuntu-latest steps: - uses: actions-ecosystem/action-create-comment@v1 @@ -26,7 +26,7 @@ jobs: - `/ready-to-merge` or `/rtm` - This comment will trigger automerge of PR in case all required checks are green, approvals in place and do-not-merge label is not added - `/do-not-merge` or `/dnm` - This comment will block automerging even if all conditions are met and ready-to-merge label is added create_help_comment_issue: - if: ${{ !github.event.issue.pull_request && contains(github.event.comment.body, '/help') }} + if: ${{ !github.event.issue.pull_request && contains(github.event.comment.body, '/help') && github.actor != 'asyncapi-bot'}} runs-on: ubuntu-latest steps: - uses: actions-ecosystem/action-create-comment@v1 From 1af8cb95f7a0262b881e4d7a10681a4cf146431b Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Thu, 20 Jan 2022 14:50:05 +0100 Subject: [PATCH 31/91] ci: update global workflows (#112) --- .github/workflows/help-command.yml | 4 ++-- .github/workflows/if-nodejs-release.yml | 5 ++++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index fd7ed399..f9d47735 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -10,7 +10,7 @@ on: jobs: create_help_comment_pr: - if: github.event.issue.pull_request && contains(github.event.comment.body, '/help' && github.actor != 'asyncapi-bot') + if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, '/help') && github.actor != 'asyncapi-bot' }} runs-on: ubuntu-latest steps: - uses: actions-ecosystem/action-create-comment@v1 @@ -26,7 +26,7 @@ jobs: - `/ready-to-merge` or `/rtm` - This comment will trigger automerge of PR in case all required checks are green, approvals in place and do-not-merge label is not added - `/do-not-merge` or `/dnm` - This comment will block automerging even if all conditions are met and ready-to-merge label is added create_help_comment_issue: - if: ${{ !github.event.issue.pull_request && contains(github.event.comment.body, '/help') && github.actor != 'asyncapi-bot'}} + if: ${{ !github.event.issue.pull_request && contains(github.event.comment.body, '/help') && github.actor != 'asyncapi-bot' }} runs-on: ubuntu-latest steps: - uses: actions-ecosystem/action-create-comment@v1 diff --git a/.github/workflows/if-nodejs-release.yml b/.github/workflows/if-nodejs-release.yml index 2682e5b3..6cdfa8e5 100644 --- a/.github/workflows/if-nodejs-release.yml +++ b/.github/workflows/if-nodejs-release.yml @@ -10,7 +10,10 @@ on: # below lines are not enough to have release supported for these branches # make sure configuration of `semantic-release` package mentiones these branches - next - - '**-release' + - next-major + - beta + - alpha + - '**-release' # custom jobs: From 6f80833bc7ff1aa2a4a81a5a24c87fc0d4dbab64 Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Thu, 27 Jan 2022 10:52:29 +0100 Subject: [PATCH 32/91] ci: update global workflows (#114) --- .github/workflows/automerge.yml | 10 ++++------ .github/workflows/autoupdate.yml | 14 +++++++++----- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 161e89a1..393e12f9 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -1,5 +1,5 @@ - # This action is centrally managed in https://github.com/asyncapi/.github/ - # Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo. +# This action is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo. name: Automerge release bump PR @@ -19,9 +19,8 @@ on: - submitted jobs: - autoapprove: - if: github.event.pull_request.draft == false && (github.event.pull_request.user.login == 'asyncapi-bot' || github.event.pull_request.user.login == 'dependabot[bot]' || github.event.pull_request.user.login == 'dependabot-preview[bot]') && !contains(github.event.pull_request.labels.*.name, 'released') + if: github.event.pull_request.draft == false && (github.actor == 'asyncapi-bot' || github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') && !contains(github.event.pull_request.labels.*.name, 'released') runs-on: ubuntu-latest steps: - name: Autoapproving @@ -39,8 +38,7 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, labels: ['autoapproved'] - }) - + }) automerge: needs: [autoapprove] diff --git a/.github/workflows/autoupdate.yml b/.github/workflows/autoupdate.yml index d147580e..b8faa428 100644 --- a/.github/workflows/autoupdate.yml +++ b/.github/workflows/autoupdate.yml @@ -2,7 +2,7 @@ #Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo #This workflow is designed to work with: -# - autoapprove and automerge workflows for dependabot and asyncapibot. +# - autoapprove and automerge workflows for dependabot and asyncapibot. # - special release branches that we from time to time create in upstream repos. If we open up PRs for them from the very beginning of the release, the release branch will constantly update with new things from the destination branch they are opened against # It uses GitHub Action that auto-updates pull requests branches, whenever changes are pushed to their destination branch. @@ -11,17 +11,21 @@ name: autoupdate on: - push: {} - -jobs: + push: + branches-ignore: + - 'version-bump/**' + - 'dependabot/**' + - 'bot/**' + - 'all-contributors/**' +jobs: autoupdate: runs-on: ubuntu-latest steps: - name: Autoupdating uses: docker://chinthakagodawita/autoupdate-action:v1 env: - GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}' + GITHUB_TOKEN: '${{ secrets.GH_TOKEN }}' PR_FILTER: "labelled" PR_LABELS: "autoapproved" PR_READY_STATE: "ready_for_review" From 3da632472bdbb14e4710d08fe521bb4f78ef4282 Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Tue, 8 Feb 2022 10:37:26 +0100 Subject: [PATCH 33/91] ci: update global workflows (#118) --- .github/workflows/if-go-pr-testing.yml | 24 +++++++++++++++------- .github/workflows/if-nodejs-pr-testing.yml | 14 +++++++++---- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/.github/workflows/if-go-pr-testing.yml b/.github/workflows/if-go-pr-testing.yml index 4a4078ff..8893baec 100644 --- a/.github/workflows/if-go-pr-testing.yml +++ b/.github/workflows/if-go-pr-testing.yml @@ -6,16 +6,21 @@ name: PR testing - if Go project on: pull_request: types: [opened, reopened, synchronize, ready_for_review] - + jobs: lint: - if: github.event.pull_request.draft == false name: lint runs-on: ubuntu-latest steps: - - name: Checkout repository + - if: "github.event.pull_request.draft == false &&!((github.actor == 'asyncapi-bot' && startsWith(github.event.pull_request.title, 'ci: update global workflows')) || (github.actor == 'asyncapi-bot' && startsWith(github.event.pull_request.title, 'chore(release):')) || (github.actor == 'allcontributors' && startsWith(github.event.pull_request.title, 'docs: add')))" + id: should_run + name: Should Run + run: echo "::set-output name=shouldrun::true" + - if: steps.should_run.outputs.shouldrun == 'true' + name: Checkout repository uses: actions/checkout@v2 - - name: Check if Go project and has go.mod + - if: steps.should_run.outputs.shouldrun == 'true' + name: Check if Go project and has go.mod id: gomod run: test -e ./go.mod && echo "::set-output name=exists::true" || echo "::set-output name=exists::false" shell: bash @@ -31,16 +36,21 @@ jobs: skip-go-installation: true # we wanna control the version of Go in use test: - if: github.event.pull_request.draft == false name: ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - name: Checkout repository + - if: "github.event.pull_request.draft == false &&!((github.actor == 'asyncapi-bot' && startsWith(github.event.pull_request.title, 'ci: update global workflows')) || (github.actor == 'asyncapi-bot' && startsWith(github.event.pull_request.title, 'chore(release):')) || (github.actor == 'allcontributors' && startsWith(github.event.pull_request.title, 'docs: add')))" + id: should_run + name: Should Run + run: echo "::set-output name=shouldrun::true" + - if: steps.should_run.outputs.shouldrun == 'true' + name: Checkout repository uses: actions/checkout@v2 - - name: Check if Go project and has go.mod + - if: steps.should_run.outputs.shouldrun == 'true' + name: Check if Go project and has go.mod id: gomod run: test -e ./go.mod && echo "::set-output name=exists::true" || echo "::set-output name=exists::false" shell: bash diff --git a/.github/workflows/if-nodejs-pr-testing.yml b/.github/workflows/if-nodejs-pr-testing.yml index 812e73af..6a9ad91c 100644 --- a/.github/workflows/if-nodejs-pr-testing.yml +++ b/.github/workflows/if-nodejs-pr-testing.yml @@ -9,20 +9,26 @@ on: jobs: test: - if: github.event.pull_request.draft == false name: ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - name: Set git to use LF #to once and for all finish neverending fight between Unix and Windows + - if: "github.event.pull_request.draft == false &&!((github.actor == 'asyncapi-bot' && startsWith(github.event.pull_request.title, 'ci: update global workflows')) || (github.actor == 'asyncapi-bot' && startsWith(github.event.pull_request.title, 'chore(release):')) || (github.actor == 'allcontributors' && startsWith(github.event.pull_request.title, 'docs: add')))" + id: should_run + name: Should Run + run: echo "::set-output name=shouldrun::true" + - if: steps.should_run.outputs.shouldrun == 'true' + name: Set git to use LF #to once and for all finish neverending fight between Unix and Windows run: | git config --global core.autocrlf false git config --global core.eol lf - - name: Checkout repository + - if: steps.should_run.outputs.shouldrun == 'true' + name: Checkout repository uses: actions/checkout@v2 - - name: Check if Node.js project and has package.json + - if: steps.should_run.outputs.shouldrun == 'true' + name: Check if Node.js project and has package.json id: packagejson run: test -e ./package.json && echo "::set-output name=exists::true" || echo "::set-output name=exists::false" shell: bash From c9c1b93f72a69444b22f52abedcaf0dccd4a81d1 Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Tue, 8 Feb 2022 17:35:11 +0100 Subject: [PATCH 34/91] ci: update global workflows (#119) --- .github/workflows/bump.yml | 5 +- .../workflows/notify-tsc-members-mention.yml | 142 ++++++++++++++++++ 2 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/notify-tsc-members-mention.yml diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index 66af7a55..a6016b93 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -15,6 +15,7 @@ on: jobs: bump: + if: startsWith(github.event.commits[0].message, 'chore(release):') runs-on: ubuntu-latest steps: - name: Checkout repo @@ -22,9 +23,9 @@ jobs: - name: Check if Node.js project and has package.json id: packagejson run: test -e ./package.json && echo "::set-output name=exists::true" || echo "::set-output name=exists::false" - - if: steps.packagejson.outputs.exists == 'true' && startsWith(github.event.commits[0].message, 'chore(release):') + - if: steps.packagejson.outputs.exists == 'true' name: Bumping latest version of this package in other repositories - uses: derberg/npm-dependency-manager-for-your-github-org@v3 + uses: derberg/npm-dependency-manager-for-your-github-org@v4 with: github_token: ${{ secrets.GH_TOKEN }} committer_username: asyncapi-bot diff --git a/.github/workflows/notify-tsc-members-mention.yml b/.github/workflows/notify-tsc-members-mention.yml new file mode 100644 index 00000000..5d342af8 --- /dev/null +++ b/.github/workflows/notify-tsc-members-mention.yml @@ -0,0 +1,142 @@ +#This action is centrally managed in https://github.com/asyncapi/.github/ +#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + +#This action notifies community on slack whenever there is a new issue, PR or discussion started in given repository +name: Notify slack whenever TSC members are mentioned in GitHub + +on: + issue_comment: + types: + - created + - edited + + discussion_comment: + types: + - created + - edited + + issues: + types: + - opened + - reopened + + pull_request_target: + types: + - opened + - reopened + - ready_for_review + + discussion: + types: + - created + - edited + +jobs: + + issue: + if: github.event_name == 'issues' && contains(github.event.issue.body, '@asyncapi/tsc_members') + name: On every new issue + runs-on: ubuntu-latest + steps: + - name: Convert markdown to slack markdown + uses: LoveToKnow/slackify-markdown-action@v1.0.0 + id: issuemarkdown + with: + text: "[${{github.event.issue.title}}](${{github.event.issue.html_url}}) \n ${{github.event.issue.body}}" + - name: Send info about issue + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} + SLACK_TITLE: 🆘 New issue that requires TSC Members attention 🆘 + SLACK_MESSAGE: ${{steps.issuemarkdown.outputs.text}} + MSG_MINIMAL: true + + pull_request: + if: github.event_name == 'pull_request_target' && contains(github.event.pull_request.body, '@asyncapi/tsc_members') + name: On every new pull request + runs-on: ubuntu-latest + steps: + - name: Convert markdown to slack markdown + uses: LoveToKnow/slackify-markdown-action@v1.0.0 + id: prmarkdown + with: + text: "[${{github.event.pull_request.title}}](${{github.event.pull_request.html_url}}) \n ${{github.event.pull_request.body}}" + - name: Send info about pull request + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} + SLACK_TITLE: 🆘 New PR that requires TSC Members attention 🆘 + SLACK_MESSAGE: ${{steps.prmarkdown.outputs.text}} + MSG_MINIMAL: true + + discussion: + if: github.event_name == 'discussion' && contains(github.event.discussion.body, '@asyncapi/tsc_members') + name: On every new discussion + runs-on: ubuntu-latest + steps: + - name: Convert markdown to slack markdown + uses: LoveToKnow/slackify-markdown-action@v1.0.0 + id: discussionmarkdown + with: + text: "[${{github.event.discussion.title}}](${{github.event.discussion.html_url}}) \n ${{github.event.discussion.body}}" + - name: Send info about pull request + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} + SLACK_TITLE: 🆘 New discussion that requires TSC Members attention 🆘 + SLACK_MESSAGE: ${{steps.discussionmarkdown.outputs.text}} + MSG_MINIMAL: true + + issue_comment: + if: ${{ github.event_name == 'issue_comment' && !github.event.issue.pull_request && contains(github.event.comment.body, '@asyncapi/tsc_members') }} + name: On every new comment in issue + runs-on: ubuntu-latest + steps: + - name: Convert markdown to slack markdown + uses: LoveToKnow/slackify-markdown-action@v1.0.0 + id: issuemarkdown + with: + text: "[${{github.event.issue.title}}](${{github.event.comment.html_url}}) \n ${{github.event.comment.body}}" + - name: Send info about issue comment + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} + SLACK_TITLE: 🆘 New comment under existing issue that requires TSC Members attention 🆘 + SLACK_MESSAGE: ${{steps.issuemarkdown.outputs.text}} + MSG_MINIMAL: true + + pr_comment: + if: github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@asyncapi/tsc_members') + name: On every new comment in pr + runs-on: ubuntu-latest + steps: + - name: Convert markdown to slack markdown + uses: LoveToKnow/slackify-markdown-action@v1.0.0 + id: prmarkdown + with: + text: "[${{github.event.issue.title}}](${{github.event.comment.html_url}}) \n ${{github.event.comment.body}}" + - name: Send info about PR comment + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} + SLACK_TITLE: 🆘 New comment under existing PR that requires TSC Members attention 🆘 + SLACK_MESSAGE: ${{steps.prmarkdown.outputs.text}} + MSG_MINIMAL: true + + discussion_comment: + if: github.event_name == 'discussion_comment' && contains(github.event.comment.body, '@asyncapi/tsc_members') + name: On every new comment in discussion + runs-on: ubuntu-latest + steps: + - name: Convert markdown to slack markdown + uses: LoveToKnow/slackify-markdown-action@v1.0.0 + id: discussionmarkdown + with: + text: "[${{github.event.discussion.title}}](${{github.event.comment.html_url}}) \n ${{github.event.comment.body}}" + - name: Send info about discussion comment + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} + SLACK_TITLE: 🆘 New comment under existing discussion that requires TSC Members attention 🆘 + SLACK_MESSAGE: ${{steps.discussionmarkdown.outputs.text}} + MSG_MINIMAL: true \ No newline at end of file From b1538ff3266356901c336abbcd275264b2dd27f3 Mon Sep 17 00:00:00 2001 From: asyncapi-bot <61865014+asyncapi-bot@users.noreply.github.com> Date: Fri, 11 Feb 2022 16:52:53 +0100 Subject: [PATCH 35/91] ci: update global workflows (#120) --- .github/workflows/automerge.yml | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 393e12f9..2d0e1c71 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -6,21 +6,15 @@ name: Automerge release bump PR on: pull_request_target: types: - - labeled - - unlabeled - - synchronize - opened - - edited - - ready_for_review - - reopened - - unlocked - pull_request_review: - types: - - submitted + - synchronize jobs: autoapprove: - if: github.event.pull_request.draft == false && (github.actor == 'asyncapi-bot' || github.actor == 'dependabot[bot]' || github.actor == 'dependabot-preview[bot]') && !contains(github.event.pull_request.labels.*.name, 'released') + if: > + contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]"]'), github.event.pull_request.user.login) && + contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]"]'), github.actor) && + !contains(github.event.pull_request.labels.*.name, 'released') runs-on: ubuntu-latest steps: - name: Autoapproving @@ -42,7 +36,6 @@ jobs: automerge: needs: [autoapprove] - if: github.event.pull_request.user.login == 'asyncapi-bot' || github.event.pull_request.user.login == 'dependabot[bot]' || github.event.pull_request.user.login == 'dependabot-preview[bot]' runs-on: ubuntu-latest steps: - name: Automerging From e3cffdaec541bfb5f0b9266db8678edd1e933d97 Mon Sep 17 00:00:00 2001 From: Dale Lane Date: Tue, 1 Mar 2022 14:11:10 +0000 Subject: [PATCH 36/91] chore: update CODEOWNERS (#122) Contributes to: asyncapi/community#275 Signed-off-by: Dale Lane --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 59c8501c..01c4dd57 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -12,4 +12,4 @@ /solace @damaru-inc @CameronRushton *.json @KhudaDad414 -* @derberg @fmvilas @github-actions[bot] +* @derberg @fmvilas @asyncapi-bot-eve From 3d4cf5185d828f506e74317793d84f2d522b1298 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Wed, 2 Mar 2022 10:35:31 +0100 Subject: [PATCH 37/91] ci: update global workflows (#121) --- .github/workflows/lint-pr-title.yml | 14 +++++++------- .github/workflows/sentiment-analysis.yml | 1 + .github/workflows/welcome-first-time-contrib.yml | 1 + 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml index fbee657e..d9ea1e82 100644 --- a/.github/workflows/lint-pr-title.yml +++ b/.github/workflows/lint-pr-title.yml @@ -1,19 +1,19 @@ -#This action is centrally managed in https://github.com/asyncapi/.github/ +#This action is centrally managed in https://github.com/asyncapi/.github/ #Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo name: Lint PR title on: - pull_request_target: types: [opened, reopened, synchronize, edited, ready_for_review] jobs: - - lint: - runs-on: ubuntu-latest - steps: - - uses: amannn/action-semantic-pull-request@v3.2.5 + lint: + runs-on: ubuntu-latest + steps: + # Since this workflow is REQUIRED for a PR to be mergable, we have to have this 'if' statement in step level instead of job level. + - if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors"]'), github.actor) }} + uses: amannn/action-semantic-pull-request@v3.2.5 env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} with: diff --git a/.github/workflows/sentiment-analysis.yml b/.github/workflows/sentiment-analysis.yml index 431d1937..c1cde5c3 100644 --- a/.github/workflows/sentiment-analysis.yml +++ b/.github/workflows/sentiment-analysis.yml @@ -26,6 +26,7 @@ on: - edited jobs: test: + if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors"]'), github.actor) }} name: Checking sentiments runs-on: ubuntu-latest steps: diff --git a/.github/workflows/welcome-first-time-contrib.yml b/.github/workflows/welcome-first-time-contrib.yml index 8eb2a993..e1a5faf0 100644 --- a/.github/workflows/welcome-first-time-contrib.yml +++ b/.github/workflows/welcome-first-time-contrib.yml @@ -13,6 +13,7 @@ on: jobs: welcome: + if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors"]'), github.actor) }} runs-on: ubuntu-latest steps: - uses: actions/github-script@v3 From 151bbaa4751fd533364bb56ecde54a6d84b48045 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Thu, 3 Mar 2022 16:08:31 +0100 Subject: [PATCH 38/91] ci: update global workflows (#123) --- .github/workflows/automerge.yml | 10 ++++++---- .github/workflows/autoupdate.yml | 3 ++- .github/workflows/bump.yml | 3 ++- .github/workflows/if-go-pr-testing.yml | 8 ++++---- .github/workflows/if-nodejs-pr-testing.yml | 4 ++-- .github/workflows/if-nodejs-release.yml | 6 +++--- .github/workflows/if-nodejs-version-bump.yml | 2 +- .github/workflows/issues-prs-notifications.yml | 12 ++++++------ .github/workflows/lint-pr-title.yml | 3 ++- .github/workflows/notify-tsc-members-mention.yml | 12 ++++++------ .github/workflows/release-announcements.yml | 4 ++-- .github/workflows/sentiment-analysis.yml | 2 +- .github/workflows/stale-issues-prs.yml | 1 + .github/workflows/welcome-first-time-contrib.yml | 1 + 14 files changed, 39 insertions(+), 32 deletions(-) diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 2d0e1c71..016cf872 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -10,7 +10,8 @@ on: - synchronize jobs: - autoapprove: + autoapprove-for-bot: + name: Autoapprove PR comming from a bot if: > contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]"]'), github.event.pull_request.user.login) && contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]"]'), github.actor) && @@ -20,7 +21,7 @@ jobs: - name: Autoapproving uses: hmarr/auto-approve-action@v2 with: - github-token: "${{ secrets.GITHUB_TOKEN }}" + github-token: "${{ secrets.GH_TOKEN_BOT_EVE }}" - name: Label autoapproved uses: actions/github-script@v5 @@ -34,8 +35,9 @@ jobs: labels: ['autoapproved'] }) - automerge: - needs: [autoapprove] + automerge-for-bot: + name: Automerge PR autoapproved by a bot + needs: [autoapprove-for-bot] runs-on: ubuntu-latest steps: - name: Automerging diff --git a/.github/workflows/autoupdate.yml b/.github/workflows/autoupdate.yml index b8faa428..5a23cce1 100644 --- a/.github/workflows/autoupdate.yml +++ b/.github/workflows/autoupdate.yml @@ -19,7 +19,8 @@ on: - 'all-contributors/**' jobs: - autoupdate: + autoupdate-for-bot: + name: Autoupdate autoapproved PR created in the upstream runs-on: ubuntu-latest steps: - name: Autoupdating diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index a6016b93..cf4c6a84 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -14,7 +14,8 @@ on: - master jobs: - bump: + bump-in-dependent-projects: + name: Bump this package in repositories that depend on it if: startsWith(github.event.commits[0].message, 'chore(release):') runs-on: ubuntu-latest steps: diff --git a/.github/workflows/if-go-pr-testing.yml b/.github/workflows/if-go-pr-testing.yml index 8893baec..b91e9578 100644 --- a/.github/workflows/if-go-pr-testing.yml +++ b/.github/workflows/if-go-pr-testing.yml @@ -8,8 +8,8 @@ on: types: [opened, reopened, synchronize, ready_for_review] jobs: - lint: - name: lint + lint-go-pr: + name: Lint Go PR runs-on: ubuntu-latest steps: - if: "github.event.pull_request.draft == false &&!((github.actor == 'asyncapi-bot' && startsWith(github.event.pull_request.title, 'ci: update global workflows')) || (github.actor == 'asyncapi-bot' && startsWith(github.event.pull_request.title, 'chore(release):')) || (github.actor == 'allcontributors' && startsWith(github.event.pull_request.title, 'docs: add')))" @@ -35,8 +35,8 @@ jobs: with: skip-go-installation: true # we wanna control the version of Go in use - test: - name: ${{ matrix.os }} + test-go-pr: + name: Test Go PR - ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: diff --git a/.github/workflows/if-nodejs-pr-testing.yml b/.github/workflows/if-nodejs-pr-testing.yml index 6a9ad91c..1894ed84 100644 --- a/.github/workflows/if-nodejs-pr-testing.yml +++ b/.github/workflows/if-nodejs-pr-testing.yml @@ -8,8 +8,8 @@ on: types: [opened, reopened, synchronize, ready_for_review] jobs: - test: - name: ${{ matrix.os }} + test-nodejs-pr: + name: Test NodeJS PR - ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: diff --git a/.github/workflows/if-nodejs-release.yml b/.github/workflows/if-nodejs-release.yml index 6cdfa8e5..75bd5de4 100644 --- a/.github/workflows/if-nodejs-release.yml +++ b/.github/workflows/if-nodejs-release.yml @@ -17,8 +17,8 @@ on: jobs: - test: - name: Test on ${{ matrix.os }} + test-nodejs: + name: Test NodeJS release on ${{ matrix.os }} runs-on: ${{ matrix.os }} strategy: matrix: @@ -49,7 +49,7 @@ jobs: run: npm test release: - needs: test + needs: [test-nodejs] name: Publish to any of NPM, Github, and Docker Hub runs-on: ubuntu-latest steps: diff --git a/.github/workflows/if-nodejs-version-bump.yml b/.github/workflows/if-nodejs-version-bump.yml index c1f5c860..4de5a7a0 100644 --- a/.github/workflows/if-nodejs-version-bump.yml +++ b/.github/workflows/if-nodejs-version-bump.yml @@ -10,7 +10,7 @@ on: jobs: version_bump: - name: Generate assets and bump + name: Generate assets and bump NodeJS runs-on: ubuntu-latest steps: - name: Checkout repository diff --git a/.github/workflows/issues-prs-notifications.yml b/.github/workflows/issues-prs-notifications.yml index e9840d6e..948abb87 100644 --- a/.github/workflows/issues-prs-notifications.yml +++ b/.github/workflows/issues-prs-notifications.yml @@ -19,7 +19,7 @@ jobs: issue: if: github.event_name == 'issues' && github.actor != 'asyncapi-bot' && github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]' - name: On every new issue + name: Notify slack on every new issue runs-on: ubuntu-latest steps: - name: Convert markdown to slack markdown for issue @@ -31,13 +31,13 @@ jobs: uses: rtCamp/action-slack-notify@v2 env: SLACK_WEBHOOK: ${{secrets.SLACK_GITHUB_NEWISSUEPR}} - SLACK_TITLE: 🐛 New Issue 🐛 + SLACK_TITLE: 🐛 New Issue in ${{github.repository}} 🐛 SLACK_MESSAGE: ${{steps.issuemarkdown.outputs.text}} MSG_MINIMAL: true pull_request: if: github.event_name == 'pull_request_target' && github.actor != 'asyncapi-bot' && github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]' - name: On every new pull request + name: Notify slack on every new pull request runs-on: ubuntu-latest steps: - name: Convert markdown to slack markdown for pull request @@ -49,13 +49,13 @@ jobs: uses: rtCamp/action-slack-notify@v2 env: SLACK_WEBHOOK: ${{secrets.SLACK_GITHUB_NEWISSUEPR}} - SLACK_TITLE: 💪 New Pull Request 💪 + SLACK_TITLE: 💪 New Pull Request in ${{github.repository}} 💪 SLACK_MESSAGE: ${{steps.prmarkdown.outputs.text}} MSG_MINIMAL: true discussion: if: github.event_name == 'discussion' && github.actor != 'asyncapi-bot' && github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]' - name: On every new pull request + name: Notify slack on every new pull request runs-on: ubuntu-latest steps: - name: Convert markdown to slack markdown for pull request @@ -67,6 +67,6 @@ jobs: uses: rtCamp/action-slack-notify@v2 env: SLACK_WEBHOOK: ${{secrets.SLACK_GITHUB_NEWISSUEPR}} - SLACK_TITLE: 💬 New Discussion 💬 + SLACK_TITLE: 💬 New Discussion in ${{github.repository}} 💬 SLACK_MESSAGE: ${{steps.discussionmarkdown.outputs.text}} MSG_MINIMAL: true diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml index d9ea1e82..1d6cbf61 100644 --- a/.github/workflows/lint-pr-title.yml +++ b/.github/workflows/lint-pr-title.yml @@ -8,7 +8,8 @@ on: types: [opened, reopened, synchronize, edited, ready_for_review] jobs: - lint: + lint-pr-title: + name: Lint PR title runs-on: ubuntu-latest steps: # Since this workflow is REQUIRED for a PR to be mergable, we have to have this 'if' statement in step level instead of job level. diff --git a/.github/workflows/notify-tsc-members-mention.yml b/.github/workflows/notify-tsc-members-mention.yml index 5d342af8..14db9eef 100644 --- a/.github/workflows/notify-tsc-members-mention.yml +++ b/.github/workflows/notify-tsc-members-mention.yml @@ -35,7 +35,7 @@ jobs: issue: if: github.event_name == 'issues' && contains(github.event.issue.body, '@asyncapi/tsc_members') - name: On every new issue + name: TSC notification on every new issue runs-on: ubuntu-latest steps: - name: Convert markdown to slack markdown @@ -53,7 +53,7 @@ jobs: pull_request: if: github.event_name == 'pull_request_target' && contains(github.event.pull_request.body, '@asyncapi/tsc_members') - name: On every new pull request + name: TSC notification on every new pull request runs-on: ubuntu-latest steps: - name: Convert markdown to slack markdown @@ -71,7 +71,7 @@ jobs: discussion: if: github.event_name == 'discussion' && contains(github.event.discussion.body, '@asyncapi/tsc_members') - name: On every new discussion + name: TSC notification on every new discussion runs-on: ubuntu-latest steps: - name: Convert markdown to slack markdown @@ -89,7 +89,7 @@ jobs: issue_comment: if: ${{ github.event_name == 'issue_comment' && !github.event.issue.pull_request && contains(github.event.comment.body, '@asyncapi/tsc_members') }} - name: On every new comment in issue + name: TSC notification on every new comment in issue runs-on: ubuntu-latest steps: - name: Convert markdown to slack markdown @@ -107,7 +107,7 @@ jobs: pr_comment: if: github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@asyncapi/tsc_members') - name: On every new comment in pr + name: TSC notification on every new comment in pr runs-on: ubuntu-latest steps: - name: Convert markdown to slack markdown @@ -125,7 +125,7 @@ jobs: discussion_comment: if: github.event_name == 'discussion_comment' && contains(github.event.comment.body, '@asyncapi/tsc_members') - name: On every new comment in discussion + name: TSC notification on every new comment in discussion runs-on: ubuntu-latest steps: - name: Convert markdown to slack markdown diff --git a/.github/workflows/release-announcements.yml b/.github/workflows/release-announcements.yml index 653ca286..5a0b29f2 100644 --- a/.github/workflows/release-announcements.yml +++ b/.github/workflows/release-announcements.yml @@ -9,7 +9,7 @@ on: jobs: - slack: + slack-announce: name: Slack - notify on every release runs-on: ubuntu-latest steps: @@ -26,7 +26,7 @@ jobs: SLACK_MESSAGE: ${{steps.markdown.outputs.text}} MSG_MINIMAL: true - twitter: + twitter-announce: name: Twitter - notify on minor and major releases runs-on: ubuntu-latest steps: diff --git a/.github/workflows/sentiment-analysis.yml b/.github/workflows/sentiment-analysis.yml index c1cde5c3..73b8b490 100644 --- a/.github/workflows/sentiment-analysis.yml +++ b/.github/workflows/sentiment-analysis.yml @@ -25,7 +25,7 @@ on: - created - edited jobs: - test: + sentiments: if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors"]'), github.actor) }} name: Checking sentiments runs-on: ubuntu-latest diff --git a/.github/workflows/stale-issues-prs.yml b/.github/workflows/stale-issues-prs.yml index 57c0f185..06d7d9e9 100644 --- a/.github/workflows/stale-issues-prs.yml +++ b/.github/workflows/stale-issues-prs.yml @@ -9,6 +9,7 @@ on: jobs: stale: + name: Mark issue or PR as stale runs-on: ubuntu-latest steps: - uses: actions/stale@v4.0.0 diff --git a/.github/workflows/welcome-first-time-contrib.yml b/.github/workflows/welcome-first-time-contrib.yml index e1a5faf0..0aabef99 100644 --- a/.github/workflows/welcome-first-time-contrib.yml +++ b/.github/workflows/welcome-first-time-contrib.yml @@ -13,6 +13,7 @@ on: jobs: welcome: + name: Post welcome message if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors"]'), github.actor) }} runs-on: ubuntu-latest steps: From 77f6f7744d79619e1e663bda65bf7a535c4c21fa Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Wed, 9 Mar 2022 10:58:12 -0500 Subject: [PATCH 39/91] feat: add topicSubscriptions to direct destinations. (#107) * Solace version 0.2.0: Added topicSubscriptions to the topic destination type. Fixed accessType in operation.json. * fix CODEOWNERS by adding trailing slashes to directory names. Co-authored-by: Michael Davis --- CODEOWNERS | 10 ++--- solace/README.md | 64 ++++++++++++++++++++++++------ solace/json_schemas/operation.json | 33 ++++++++++----- solace/json_schemas/server.json | 4 +- 4 files changed, 82 insertions(+), 29 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 01c4dd57..b248194e 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -6,10 +6,10 @@ # The default owners are automatically added as reviewers when you open a pull request unless different owners are specified in the file. -/anypointmq @GeraldLoeffler -/ibmmq @rcoppen -/kafka @lbroudoux @dalelane -/solace @damaru-inc @CameronRushton -*.json @KhudaDad414 +/anypointmq/ @GeraldLoeffler +/ibmmq/ @rcoppen +/kafka/ @lbroudoux @dalelane +/solace/ @damaru-inc @CameronRushton +*.json @KhudaDad414 * @derberg @fmvilas @asyncapi-bot-eve diff --git a/solace/README.md b/solace/README.md index f56a0b02..782a6aab 100644 --- a/solace/README.md +++ b/solace/README.md @@ -6,7 +6,7 @@ This document defines how to describe Solace-specific information with AsyncAPI. ## Version -Current version is `0.1.0`. +Current version is `0.2.0`. @@ -14,7 +14,7 @@ Current version is `0.1.0`. Field Name | Type | Description ---|---|--- -`bindingVersion`|String|The current version is 0.1.0 +`bindingVersion`|String|The current version is 0.2.0 `msgVpn`|String|The Virtual Private Network name on the Solace broker. @@ -34,7 +34,7 @@ We need the ability to support several bindings for each operation, see the [Exa Field Name | Type | Description ---|---|--- -`bindingVersion`|String|The current version is 0.1.0 +`bindingVersion`|String|The current version is 0.2.0 `destinations`|List of Destination Objects|Destination Objects are described next. ### Destination Object @@ -43,11 +43,13 @@ Each destination has the following structure. Note that bindings under a 'subscr Field Name | Type | Description | Applicable Operation ---|---|---|--- -`destinationType`|Enum|'queue' or 'topic'. If the type is queue, then the subscriber can bind to the queue, which in turn will subscribe to the topic as represented by the channel name.|publish -`deliveryMode`|String|'direct' or 'persistent'. This determines the quality of service for publishing messages as documented [here.](https://docs.solace.com/PubSub-Basics/Core-Concepts-Message-Delivery-Modes.htm) Default is 'persistent'.|subscribe +`destinationType`|Enum|'queue' or 'topic'. If the type is queue, then the subscriber can bind to the queue, which in turn will subscribe to the topic as represented by the channel name or to the provided topicSubscriptions.|publish +`deliveryMode`|Enum|'direct' or 'persistent'. This determines the quality of service for publishing messages as documented [here.](https://docs.solace.com/PubSub-Basics/Core-Concepts-Message-Delivery-Modes.htm) Default is 'persistent'.|subscribe `queue.name`|String|The name of the queue, only applicable when destinationType is 'queue'.|publish -`queue.topicSubscriptions`|List of String|A list of topics that the queue subscribes to, only applicable when destinationType is 'queue'.|publish -`queue.accessType`|Enum|'exclusive' or 'nonExclusive'. This is documented [here.](https://docs.solace.com/PubSub-Basics/Endpoints.htm) Only applicable when destinationType is 'queue'.|publish +`queue.topicSubscriptions`|List of String|A list of topics that the queue subscribes to, only applicable when destinationType is 'queue'. If none is given, the queue subscribes to the topic as represented by the channel name.|publish +`queue.accessType`|Enum|'exclusive' or 'nonexclusive'. This is documented [here.](https://docs.solace.com/PubSub-Basics/Endpoints.htm) Only applicable when destinationType is 'queue'.|publish +`topic.topicSubscriptions`|List of String|A list of topics that the client subscribes to, only applicable when destinationType is 'topic'. If none is given, the client subscribes to the topic as represented by the channel name.|publish + @@ -60,9 +62,9 @@ This object MUST NOT contain any properties. Its name is reserved for future use -## Example ## +## Example with two destinations ## -Here is an example of when we could need two Solace bindings. +Here is an example of when we could need two Solace destinations. Imagine a system where there is a schema called Person, and there are topics: @@ -72,7 +74,7 @@ and `person/{personId}/updated` -and you have one application that receive both events. We also want each to be on its own queue. The AsyncAPI file could look like this: +and you have one application that receives both events. We also want each to be on its own queue. The AsyncAPI file could look like this: ```yaml components: @@ -90,7 +92,7 @@ channels: publish: bindings: solace: - bindingVersion: 0.1.0 + bindingVersion: 0.2.0 destinations: - destinationType: queue queue: @@ -111,7 +113,7 @@ channels: eventType: schema: type: string -asyncapi: 2.1.0 +asyncapi: 2.0.0 info: title: HRApp version: 0.0.1 @@ -120,4 +122,42 @@ info: The expected behaviour would be that the application binds to both queues, and each queue has its own topic subscription, one to created and one to updated events. +## Example with a wildcard subscription ## + +This example shows how a client could receive all the topics under `person/` using a wildcard subscription: +```yaml +components: + schemas: + Person: + type: string + messages: + PersonEvent: + payload: + $ref: '#/components/schemas/Person' + schemaFormat: application/vnd.aai.asyncapi+json;version=2.0.0 + contentType: application/json +channels: + 'person/{personId}/{eventType}': + publish: + bindings: + solace: + bindingVersion: 0.2.0 + destinations: + - destinationType: topic + topicSubscriptions: + - person/> + message: + $ref: '#/components/messages/PersonEvent' + parameters: + personId: + schema: + type: string + eventType: + schema: + type: string +asyncapi: 2.0.0 +info: + title: HRApp + version: 0.0.1 +``` diff --git a/solace/json_schemas/operation.json b/solace/json_schemas/operation.json index edf9630d..f8382fc9 100644 --- a/solace/json_schemas/operation.json +++ b/solace/json_schemas/operation.json @@ -26,7 +26,7 @@ "destinationType": { "type": "string", "const": "queue", - "description": "If the type is queue, then the subscriber can bind to the queue, which in turn will subscribe to the topic as represented by the channel name." + "description": "If the type is queue, then the subscriber can bind to the queue. The queue subscribes to the given topicSubscriptions. If no topicSubscriptions are provied, the queue will subscribe to the topic as represented by the channel name." }, "queue": { "type": "object", @@ -42,8 +42,12 @@ "type": "string" } }, - "exclusive": { - "type": "boolean" + "accessType": { + "type": "string", + "enum": [ + "exclusive", + "nonexclusive" + ] } } } @@ -54,8 +58,15 @@ "destinationType": { "type": "string", "const": "topic", - "description": "If the type is topic, then the subscriber subscribes to the topic as represented by the channel name." - } + "description": "If the type is topic, then the subscriber subscribes to the given topicSubscriptions. If no topicSubscriptions are provided, the client will subscribe to the topic as represented by the channel name." + }, + "topicSubscriptions": { + "type": "array", + "description": "The list of topics that the client subscribes to.", + "items": { + "type": "string" + } + } } } ] @@ -65,14 +76,14 @@ "bindingVersion": { "type": "string", "enum": [ - "0.1.0" + "0.2.0" ], "description": "The version of this binding. If omitted, \"latest\" MUST be assumed." }, "examples": [ { - "bindingVersion": "0.1.0", - "bindings": [ + "bindingVersion": "0.2.0", + "destinations": [ { "destinationType": "queue", "queue": { @@ -80,12 +91,14 @@ "topicSubscriptions": [ "samples/*" ], - "exclusive": false + "accessType": "nonexclusive" } }, { "destinationType": "topic", - "deliveryMode": "persistent" + "topicSubscriptions": [ + "samples/*" + ] } ] } diff --git a/solace/json_schemas/server.json b/solace/json_schemas/server.json index e561f292..06f12acb 100644 --- a/solace/json_schemas/server.json +++ b/solace/json_schemas/server.json @@ -18,7 +18,7 @@ "bindingVersion": { "type": "string", "enum": [ - "0.1.0" + "0.2.0" ], "description": "The version of this binding." } @@ -26,7 +26,7 @@ "examples": [ { "msgVpn": "ProdVPN", - "bindingVersion": "0.1.0" + "bindingVersion": "0.2.0" } ] } From bbc282744c35f554bb1d9589cdf6ad640cad98f9 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Mon, 28 Mar 2022 09:49:18 +0200 Subject: [PATCH 40/91] ci: update global workflows (#124) --- .github/workflows/link-check-cron.yml | 29 +++++++++++++++++++++++++++ .github/workflows/link-check-pr.yml | 17 ++++++++++++++++ 2 files changed, 46 insertions(+) create mode 100644 .github/workflows/link-check-cron.yml create mode 100644 .github/workflows/link-check-pr.yml diff --git a/.github/workflows/link-check-cron.yml b/.github/workflows/link-check-cron.yml new file mode 100644 index 00000000..f7fabcd5 --- /dev/null +++ b/.github/workflows/link-check-cron.yml @@ -0,0 +1,29 @@ +name: Check Markdown links (Weekly) + +on: + workflow_dispatch: + schedule: + # At 00:00 UTC on every Monday + - cron: '0 0 * * 0' + +jobs: + External-link-validation-weekly: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + # Checks the status of hyperlinks in .md files + - name: Check links + uses: gaurav-nelson/github-action-markdown-link-check@v1 + with: + use-quiet-mode: 'yes' + use-verbose-mode: 'yes' + + - name: Report workflow run status to Slack + uses: 8398a7/action-slack@v3 + with: + status: ${{ job.status }} + fields: repo,message,action,eventName,ref,workflow + env: + SLACK_DOCS_CHANNEL: ${{ secrets.SLACK_DOCS_CHANNEL }} + if: failure() # Only, on failure, send a message on the Slack Docs Channel (if there are broken links) diff --git a/.github/workflows/link-check-pr.yml b/.github/workflows/link-check-pr.yml new file mode 100644 index 00000000..f678ed19 --- /dev/null +++ b/.github/workflows/link-check-pr.yml @@ -0,0 +1,17 @@ +name: Check Markdown links + +on: + pull_request_target: + types: [synchronize, ready_for_review, opened, reopened] + +jobs: + External-link-validation-on-PR: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Check links + uses: gaurav-nelson/github-action-markdown-link-check@v1 + with: + use-quiet-mode: 'yes' + use-verbose-mode: 'yes' + check-modified-files-only: 'yes' #Only modified files are checked on PRs From 42523f3e63a4aad515bd78b262428229eaec92a4 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Tue, 29 Mar 2022 08:00:55 +0200 Subject: [PATCH 41/91] ci: update global workflows (#125) --- .../workflows/add-good-first-issue-labels.yml | 6 +- ...d-ready-to-merge-or-do-not-merge-label.yml | 94 +++++++++++++++---- .../automerge-for-humans-merging.yml | 6 +- ...ns-remove-ready-to-merge-label-on-edit.yml | 4 +- .github/workflows/automerge-orphans.yml | 4 +- .github/workflows/automerge.yml | 2 +- .github/workflows/autoupdate.yml | 14 ++- .github/workflows/bump.yml | 14 +-- .github/workflows/help-command.yml | 5 +- .github/workflows/if-go-pr-testing.yml | 7 +- .github/workflows/if-nodejs-pr-testing.yml | 7 +- .github/workflows/if-nodejs-release.yml | 7 +- .github/workflows/if-nodejs-version-bump.yml | 7 +- .../workflows/issues-prs-notifications.yml | 6 +- .github/workflows/link-check-cron.yml | 3 + .github/workflows/link-check-pr.yml | 3 + .github/workflows/lint-pr-title.yml | 4 +- .../workflows/notify-tsc-members-mention.yml | 6 +- .github/workflows/release-announcements.yml | 5 +- .github/workflows/sentiment-analysis.yml | 4 +- .github/workflows/stale-issues-prs.yml | 4 +- .../workflows/welcome-first-time-contrib.yml | 4 +- 22 files changed, 147 insertions(+), 69 deletions(-) diff --git a/.github/workflows/add-good-first-issue-labels.yml b/.github/workflows/add-good-first-issue-labels.yml index b9cdd067..00acdb84 100644 --- a/.github/workflows/add-good-first-issue-labels.yml +++ b/.github/workflows/add-good-first-issue-labels.yml @@ -1,7 +1,7 @@ -#This workflow is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo +# This workflow is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo -#Purpose of this workflow is to enable anyone to label issue with 'Good First Issue' and 'area/*' with a single command. +# Purpose of this workflow is to enable anyone to label issue with 'Good First Issue' and 'area/*' with a single command. name: Add 'Good First Issue' and 'area/*' labels # if proper comment added on: diff --git a/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml b/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml index 68f39603..d35c3215 100644 --- a/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml +++ b/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml @@ -1,8 +1,11 @@ -#This workflow is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo +# This workflow is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo -#Purpose of this workflow is to enable anyone to label PR with `ready-to-merge` and `do-not-merge` labels to get stuff merged or blocked from merging -name: Add ready-to-merge or do-not-merge label # if proper comment added +# Purpose of this workflow is to enable anyone to label PR with the following labels: +# `ready-to-merge` and `do-not-merge` labels to get stuff merged or blocked from merging +# `autoupdate` to keep a branch up-to-date with the target branch + +name: Label PRs # if proper comment added on: issue_comment: @@ -10,21 +13,52 @@ on: - created jobs: - parse-comment-and-add-ready: # for handling cases when you want to mark as ready to merge - if: github.event.issue.pull_request && github.event.issue.state != 'closed' && github.actor != 'asyncapi-bot' + add-ready-to-merge-label: + if: > + github.event.issue.pull_request && + github.event.issue.state != 'closed' && + github.actor != 'asyncapi-bot' && + ( + contains(github.event.comment.body, '/ready-to-merge') || + contains(github.event.comment.body, '/rtm' ) + ) + runs-on: ubuntu-latest steps: - - name: Check if PR is draft # such info is not available in the context of issue_comment event + + - name: Check if PR is draft or is up-to-date # such info is not available in the context of issue_comment event uses: actions/github-script@v5 - id: checkDraft + id: checkPR with: result-encoding: string script: | + let isDraft = false; + let isUpToDate = true; const prDetailsUrl = context.payload.issue.pull_request.url; - const response = await github.request(prDetailsUrl); - return response.data.draft; - - name: Add label - if: steps.checkDraft.outputs.result == 'false' && (contains(github.event.comment.body, '/ready-to-merge') || contains(github.event.comment.body, '/rtm' )) + const { data: pull } = await github.request(prDetailsUrl); + isDraft = pull.draft; + + const { data: comparison } = + await github.rest.repos.compareCommitsWithBasehead({ + owner: pull.head.repo.owner.login, + repo: pull.head.repo.name, + basehead: `${pull.head.label}...${pull.base.label}`, + }); + if (comparison.behind_by !== 0) isUpToDate = false; + return { isDraft, isUpToDate }; + + - uses: actions-ecosystem/action-create-comment@v1 + if: ${{ !steps.checkPR.outputs.result.isUpToDate }} + with: + github_token: ${{ secrets.GH_TOKEN }} + body: | + Hello, @${{ github.actor }}! 👋🏼 + This PR is not up to date with the base branch and can't be merged. + You can add comment to this PR with text: `/autoupdate` or `/au`. This way you ask our bot to perform regular updates for you. The only requirement for this to work is to enable [Allow edits from maintainers](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork) option in your PR. Otherwise the only option that you have is to manually update your branch with latest version of the base branch. + Thanks 😄 + + - name: Add ready-to-merge label + if: ${{ !steps.checkPR.outputs.result.isDraft }} uses: actions/github-script@v5 with: github-token: ${{ secrets.GH_TOKEN }} @@ -36,12 +70,18 @@ jobs: labels: ['ready-to-merge'] }) - parse-comment-and-add-block: # for handling cases when you want to mark as do-not-merge - if: github.event.issue.pull_request && github.event.issue.state != 'closed' && github.actor != 'asyncapi-bot' + add-do-not-merge-label: + if: > + github.event.issue.pull_request && + github.event.issue.state != 'closed' && + github.actor != 'asyncapi-bot' && + ( + contains(github.event.comment.body, '/do-not-merge') || + contains(github.event.comment.body, '/dnm' ) + ) runs-on: ubuntu-latest steps: - - name: Add label - if: contains(github.event.comment.body, '/do-not-merge') || contains(github.event.comment.body, '/dnm' ) + - name: Add do-not-merge label uses: actions/github-script@v5 with: github-token: ${{ secrets.GH_TOKEN }} @@ -51,4 +91,26 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, labels: ['do-not-merge'] + }) + add-autoupdate-label: + if: > + github.event.issue.pull_request && + github.event.issue.state != 'closed' && + github.actor != 'asyncapi-bot' && + ( + contains(github.event.comment.body, '/autoupdate') || + contains(github.event.comment.body, '/au' ) + ) + runs-on: ubuntu-latest + steps: + - name: Add autoupdate label + uses: actions/github-script@v5 + with: + github-token: ${{ secrets.GH_TOKEN }} + script: | + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['autoupdate'] }) \ No newline at end of file diff --git a/.github/workflows/automerge-for-humans-merging.yml b/.github/workflows/automerge-for-humans-merging.yml index 37ee722f..e1b4deb4 100644 --- a/.github/workflows/automerge-for-humans-merging.yml +++ b/.github/workflows/automerge-for-humans-merging.yml @@ -1,7 +1,7 @@ -#This workflow is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo +# This workflow is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo -#Purpose of this workflow is to allow people to merge PR without a need of maintainer doing it. If all checks are in place (including maintainers approval) - JUST MERGE IT! +# Purpose of this workflow is to allow people to merge PR without a need of maintainer doing it. If all checks are in place (including maintainers approval) - JUST MERGE IT! name: Automerge For Humans on: diff --git a/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml b/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml index 3fe91579..f38296c8 100644 --- a/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml +++ b/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml @@ -1,5 +1,5 @@ -#This workflow is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo +# This workflow is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo # Defence from evil contributor that after adding `ready-to-merge` all suddenly makes evil commit or evil change in PR title # Label is removed once above action is detected diff --git a/.github/workflows/automerge-orphans.yml b/.github/workflows/automerge-orphans.yml index 8b8c6c2d..5c39ba92 100644 --- a/.github/workflows/automerge-orphans.yml +++ b/.github/workflows/automerge-orphans.yml @@ -1,5 +1,5 @@ -#This action is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo +# This action is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo name: 'Notify on failing automerge' diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 016cf872..052a19c3 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -32,7 +32,7 @@ jobs: issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - labels: ['autoapproved'] + labels: ['autoapproved', 'autoupdate'] }) automerge-for-bot: diff --git a/.github/workflows/autoupdate.yml b/.github/workflows/autoupdate.yml index 5a23cce1..c7635c14 100644 --- a/.github/workflows/autoupdate.yml +++ b/.github/workflows/autoupdate.yml @@ -1,12 +1,12 @@ -#This action is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo +# This action is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo -#This workflow is designed to work with: +# This workflow is designed to work with: # - autoapprove and automerge workflows for dependabot and asyncapibot. # - special release branches that we from time to time create in upstream repos. If we open up PRs for them from the very beginning of the release, the release branch will constantly update with new things from the destination branch they are opened against # It uses GitHub Action that auto-updates pull requests branches, whenever changes are pushed to their destination branch. -#Autoupdating to latest destination branch works only in the context of upstream repo and not forks +# Autoupdating to latest destination branch works only in the context of upstream repo and not forks name: autoupdate @@ -17,9 +17,13 @@ on: - 'dependabot/**' - 'bot/**' - 'all-contributors/**' + pull_request: + types: + - labeled jobs: autoupdate-for-bot: + if: ${{ !github.event.issue.pull_request || contains(github.event.pull_request.labels.*.name, 'autoupdate') }} name: Autoupdate autoapproved PR created in the upstream runs-on: ubuntu-latest steps: @@ -28,6 +32,6 @@ jobs: env: GITHUB_TOKEN: '${{ secrets.GH_TOKEN }}' PR_FILTER: "labelled" - PR_LABELS: "autoapproved" + PR_LABELS: "autoupdate" PR_READY_STATE: "ready_for_review" MERGE_CONFLICT_ACTION: "ignore" diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml index cf4c6a84..68daa7c0 100644 --- a/.github/workflows/bump.yml +++ b/.github/workflows/bump.yml @@ -1,14 +1,14 @@ -#This action is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo +# This action is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo -#Purpose of this action is to update npm package in libraries that use it. It is like dependabot for asyncapi npm modules only. -#It runs in a repo after merge of release commit and searches for other packages that use released package. Every found package gets updated with lates version +# Purpose of this action is to update npm package in libraries that use it. It is like dependabot for asyncapi npm modules only. +# It runs in a repo after merge of release commit and searches for other packages that use released package. Every found package gets updated with lates version name: Bump package version in dependent repos - if Node project on: - #It cannot run on release event as when release is created then version is not yet bumped in package.json - #This means we cannot extract easily latest version and have a risk that package is not yet on npm + # It cannot run on release event as when release is created then version is not yet bumped in package.json + # This means we cannot extract easily latest version and have a risk that package is not yet on npm push: branches: - master @@ -31,4 +31,4 @@ jobs: github_token: ${{ secrets.GH_TOKEN }} committer_username: asyncapi-bot committer_email: info@asyncapi.io - repos_to_ignore: html-template #this is temporary until react component releases 1.0, then it can be removed + repos_to_ignore: html-template # this is temporary until react component releases 1.0, then it can be removed diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index f9d47735..16f378cb 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -1,5 +1,5 @@ -#This workflow is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo +# This workflow is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo name: Create help comment @@ -25,6 +25,7 @@ jobs: - `/ready-to-merge` or `/rtm` - This comment will trigger automerge of PR in case all required checks are green, approvals in place and do-not-merge label is not added - `/do-not-merge` or `/dnm` - This comment will block automerging even if all conditions are met and ready-to-merge label is added + - `/autoupdate` or `/au` - This comment will add `autoupdate` label to the PR and keeps your PR up-to-date to the target branch. Unless there is a merge conflict. create_help_comment_issue: if: ${{ !github.event.issue.pull_request && contains(github.event.comment.body, '/help') && github.actor != 'asyncapi-bot' }} runs-on: ubuntu-latest diff --git a/.github/workflows/if-go-pr-testing.yml b/.github/workflows/if-go-pr-testing.yml index b91e9578..606e689b 100644 --- a/.github/workflows/if-go-pr-testing.yml +++ b/.github/workflows/if-go-pr-testing.yml @@ -1,6 +1,7 @@ -#This action is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo -#It does magic only if there is go.mod file in the root of the project +# This action is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + +# It does magic only if there is go.mod file in the root of the project name: PR testing - if Go project on: diff --git a/.github/workflows/if-nodejs-pr-testing.yml b/.github/workflows/if-nodejs-pr-testing.yml index 1894ed84..1dcccd32 100644 --- a/.github/workflows/if-nodejs-pr-testing.yml +++ b/.github/workflows/if-nodejs-pr-testing.yml @@ -1,6 +1,7 @@ -#This action is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo -#It does magic only if there is package.json file in the root of the project +# This action is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + +# It does magic only if there is package.json file in the root of the project name: PR testing - if Node project on: diff --git a/.github/workflows/if-nodejs-release.yml b/.github/workflows/if-nodejs-release.yml index 75bd5de4..bc5b5376 100644 --- a/.github/workflows/if-nodejs-release.yml +++ b/.github/workflows/if-nodejs-release.yml @@ -1,6 +1,7 @@ -#This action is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo -#It does magic only if there is package.json file in the root of the project +# This action is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + +# It does magic only if there is package.json file in the root of the project name: Release - if Node project on: diff --git a/.github/workflows/if-nodejs-version-bump.yml b/.github/workflows/if-nodejs-version-bump.yml index 4de5a7a0..721caa9d 100644 --- a/.github/workflows/if-nodejs-version-bump.yml +++ b/.github/workflows/if-nodejs-version-bump.yml @@ -1,6 +1,7 @@ -#This action is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo -#It does magic only if there is package.json file in the root of the project +# This action is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + +# It does magic only if there is package.json file in the root of the project name: Version bump - if Node.js project on: diff --git a/.github/workflows/issues-prs-notifications.yml b/.github/workflows/issues-prs-notifications.yml index 948abb87..576b2bac 100644 --- a/.github/workflows/issues-prs-notifications.yml +++ b/.github/workflows/issues-prs-notifications.yml @@ -1,7 +1,7 @@ -#This action is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo +# This action is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo -#This action notifies community on slack whenever there is a new issue, PR or discussion started in given repository +# This action notifies community on slack whenever there is a new issue, PR or discussion started in given repository name: Notify slack on: diff --git a/.github/workflows/link-check-cron.yml b/.github/workflows/link-check-cron.yml index f7fabcd5..2b7038ae 100644 --- a/.github/workflows/link-check-cron.yml +++ b/.github/workflows/link-check-cron.yml @@ -1,3 +1,6 @@ +# This workflow is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + name: Check Markdown links (Weekly) on: diff --git a/.github/workflows/link-check-pr.yml b/.github/workflows/link-check-pr.yml index f678ed19..2d7fec31 100644 --- a/.github/workflows/link-check-pr.yml +++ b/.github/workflows/link-check-pr.yml @@ -1,3 +1,6 @@ +# This workflow is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + name: Check Markdown links on: diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml index 1d6cbf61..87e2fa5e 100644 --- a/.github/workflows/lint-pr-title.yml +++ b/.github/workflows/lint-pr-title.yml @@ -1,5 +1,5 @@ -#This action is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo +# This action is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo name: Lint PR title diff --git a/.github/workflows/notify-tsc-members-mention.yml b/.github/workflows/notify-tsc-members-mention.yml index 14db9eef..e33b2625 100644 --- a/.github/workflows/notify-tsc-members-mention.yml +++ b/.github/workflows/notify-tsc-members-mention.yml @@ -1,7 +1,7 @@ -#This action is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo +# This action is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo -#This action notifies community on slack whenever there is a new issue, PR or discussion started in given repository +# This action notifies community on slack whenever there is a new issue, PR or discussion started in given repository name: Notify slack whenever TSC members are mentioned in GitHub on: diff --git a/.github/workflows/release-announcements.yml b/.github/workflows/release-announcements.yml index 5a0b29f2..b2f3ba76 100644 --- a/.github/workflows/release-announcements.yml +++ b/.github/workflows/release-announcements.yml @@ -1,5 +1,6 @@ -#This action is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo +# This action is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + name: 'Announce releases in different channels' on: diff --git a/.github/workflows/sentiment-analysis.yml b/.github/workflows/sentiment-analysis.yml index 73b8b490..cd8ab05f 100644 --- a/.github/workflows/sentiment-analysis.yml +++ b/.github/workflows/sentiment-analysis.yml @@ -1,5 +1,5 @@ -#This action is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo +# This action is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo name: 'Sentiment Analysis' diff --git a/.github/workflows/stale-issues-prs.yml b/.github/workflows/stale-issues-prs.yml index 06d7d9e9..76673183 100644 --- a/.github/workflows/stale-issues-prs.yml +++ b/.github/workflows/stale-issues-prs.yml @@ -1,5 +1,5 @@ -#This action is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo +# This action is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo name: Manage stale issues and PRs diff --git a/.github/workflows/welcome-first-time-contrib.yml b/.github/workflows/welcome-first-time-contrib.yml index 0aabef99..e72fecef 100644 --- a/.github/workflows/welcome-first-time-contrib.yml +++ b/.github/workflows/welcome-first-time-contrib.yml @@ -1,5 +1,5 @@ -#This action is centrally managed in https://github.com/asyncapi/.github/ -#Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo +# This action is centrally managed in https://github.com/asyncapi/.github/ +# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo name: Welcome first time contributors From d1f953aec69abc17336f383bf92fb1a2614ddebe Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Wed, 6 Apr 2022 11:49:28 +0200 Subject: [PATCH 42/91] ci: update global workflows (#126) --- ...s-add-ready-to-merge-or-do-not-merge-label.yml | 15 ++++++++++----- .github/workflows/autoupdate.yml | 4 ---- .github/workflows/help-command.yml | 2 +- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml b/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml index d35c3215..0a2e5d77 100644 --- a/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml +++ b/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml @@ -42,23 +42,28 @@ jobs: await github.rest.repos.compareCommitsWithBasehead({ owner: pull.head.repo.owner.login, repo: pull.head.repo.name, - basehead: `${pull.head.label}...${pull.base.label}`, + basehead: `${pull.base.label}...${pull.head.label}`, }); - if (comparison.behind_by !== 0) isUpToDate = false; + if (comparison.behind_by !== 0) { + console.log(`This branch is behind the target by ${comparison.behind_by} commits`) + isUpToDate = false; + } else console.log(`This branch is up-to-date.`) return { isDraft, isUpToDate }; - uses: actions-ecosystem/action-create-comment@v1 - if: ${{ !steps.checkPR.outputs.result.isUpToDate }} + if: ${{ !fromJson(steps.checkPR.outputs.result).isUpToDate }} with: github_token: ${{ secrets.GH_TOKEN }} body: | Hello, @${{ github.actor }}! 👋🏼 This PR is not up to date with the base branch and can't be merged. - You can add comment to this PR with text: `/autoupdate` or `/au`. This way you ask our bot to perform regular updates for you. The only requirement for this to work is to enable [Allow edits from maintainers](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork) option in your PR. Otherwise the only option that you have is to manually update your branch with latest version of the base branch. + Please update your branch manually with the latest version of the base branch. + + PRO-TIP: Add a comment to your PR with the text: `/au` or `/autoupdate` and our bot will take care of updating the branch in the future. The only requirement for this to work is to enable [Allow edits from maintainers](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork) option in your PR. Thanks 😄 - name: Add ready-to-merge label - if: ${{ !steps.checkPR.outputs.result.isDraft }} + if: ${{ !fromJson(steps.checkPR.outputs.result).isDraft }} uses: actions/github-script@v5 with: github-token: ${{ secrets.GH_TOKEN }} diff --git a/.github/workflows/autoupdate.yml b/.github/workflows/autoupdate.yml index c7635c14..f23ec3b8 100644 --- a/.github/workflows/autoupdate.yml +++ b/.github/workflows/autoupdate.yml @@ -17,13 +17,9 @@ on: - 'dependabot/**' - 'bot/**' - 'all-contributors/**' - pull_request: - types: - - labeled jobs: autoupdate-for-bot: - if: ${{ !github.event.issue.pull_request || contains(github.event.pull_request.labels.*.name, 'autoupdate') }} name: Autoupdate autoapproved PR created in the upstream runs-on: ubuntu-latest steps: diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index 16f378cb..69163816 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -25,7 +25,7 @@ jobs: - `/ready-to-merge` or `/rtm` - This comment will trigger automerge of PR in case all required checks are green, approvals in place and do-not-merge label is not added - `/do-not-merge` or `/dnm` - This comment will block automerging even if all conditions are met and ready-to-merge label is added - - `/autoupdate` or `/au` - This comment will add `autoupdate` label to the PR and keeps your PR up-to-date to the target branch. Unless there is a merge conflict. + - `/autoupdate` or `/au` - This comment will add `autoupdate` label to the PR and keeps your PR up-to-date to the target branch's future changes. Unless there is a merge conflict. create_help_comment_issue: if: ${{ !github.event.issue.pull_request && contains(github.event.comment.body, '/help') && github.actor != 'asyncapi-bot' }} runs-on: ubuntu-latest From c8bf1a0e6dff52dbfe90f10db77150bba2ecc46d Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Fri, 8 Apr 2022 19:56:26 +0200 Subject: [PATCH 43/91] ci: update global workflows (#127) --- ...d-ready-to-merge-or-do-not-merge-label.yml | 60 ++++++++----------- 1 file changed, 25 insertions(+), 35 deletions(-) diff --git a/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml b/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml index 0a2e5d77..c694c38f 100644 --- a/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml +++ b/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml @@ -26,17 +26,23 @@ jobs: runs-on: ubuntu-latest steps: - - name: Check if PR is draft or is up-to-date # such info is not available in the context of issue_comment event + - name: Add ready-to-merge label uses: actions/github-script@v5 - id: checkPR with: - result-encoding: string + github-token: ${{ secrets.GH_TOKEN }} script: | - let isDraft = false; - let isUpToDate = true; const prDetailsUrl = context.payload.issue.pull_request.url; const { data: pull } = await github.request(prDetailsUrl); - isDraft = pull.draft; + const { draft: isDraft} = pull; + if(!isDraft) { + console.log('adding ready-to-merge label...'); + github.rest.issues.addLabels({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + labels: ['ready-to-merge'] + }) + } const { data: comparison } = await github.rest.repos.compareCommitsWithBasehead({ @@ -46,35 +52,19 @@ jobs: }); if (comparison.behind_by !== 0) { console.log(`This branch is behind the target by ${comparison.behind_by} commits`) - isUpToDate = false; - } else console.log(`This branch is up-to-date.`) - return { isDraft, isUpToDate }; - - - uses: actions-ecosystem/action-create-comment@v1 - if: ${{ !fromJson(steps.checkPR.outputs.result).isUpToDate }} - with: - github_token: ${{ secrets.GH_TOKEN }} - body: | - Hello, @${{ github.actor }}! 👋🏼 - This PR is not up to date with the base branch and can't be merged. - Please update your branch manually with the latest version of the base branch. - - PRO-TIP: Add a comment to your PR with the text: `/au` or `/autoupdate` and our bot will take care of updating the branch in the future. The only requirement for this to work is to enable [Allow edits from maintainers](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork) option in your PR. - Thanks 😄 - - - name: Add ready-to-merge label - if: ${{ !fromJson(steps.checkPR.outputs.result).isDraft }} - uses: actions/github-script@v5 - with: - github-token: ${{ secrets.GH_TOKEN }} - script: | - github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels: ['ready-to-merge'] - }) - + console.log('adding out-of-date comment...'); + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `Hello, @${{ github.actor }}! 👋🏼 + This PR is not up to date with the base branch and can't be merged. + Please update your branch manually with the latest version of the base branch. + PRO-TIP: Add a comment to your PR with the text: \`/au\` or \`/autoupdate\` and our bot will take care of updating the branch in the future. The only requirement for this to work is to enable [Allow edits from maintainers](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork) option in your PR. + Thanks 😄` + }) + } + add-do-not-merge-label: if: > github.event.issue.pull_request && From 00ee1c5ce2dd5339a2ff740935694dbe8343f989 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Tue, 12 Apr 2022 01:25:02 +0200 Subject: [PATCH 44/91] ci: update global workflows (#128) --- .github/workflows/link-check-cron.yml | 6 +++++- .github/workflows/link-check-pr.yml | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/.github/workflows/link-check-cron.yml b/.github/workflows/link-check-cron.yml index 2b7038ae..e24b066b 100644 --- a/.github/workflows/link-check-cron.yml +++ b/.github/workflows/link-check-cron.yml @@ -21,6 +21,10 @@ jobs: with: use-quiet-mode: 'yes' use-verbose-mode: 'yes' + + # A configuration file can be included, indicating the properties of the link check action + # More information can be found here: https://github.com/tcort/markdown-link-check#config-file-format + # Create mlc_config.json file in the root of the directory - name: Report workflow run status to Slack uses: 8398a7/action-slack@v3 @@ -28,5 +32,5 @@ jobs: status: ${{ job.status }} fields: repo,message,action,eventName,ref,workflow env: - SLACK_DOCS_CHANNEL: ${{ secrets.SLACK_DOCS_CHANNEL }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_DOCS_CHANNEL }} if: failure() # Only, on failure, send a message on the Slack Docs Channel (if there are broken links) diff --git a/.github/workflows/link-check-pr.yml b/.github/workflows/link-check-pr.yml index 2d7fec31..6e9eb358 100644 --- a/.github/workflows/link-check-pr.yml +++ b/.github/workflows/link-check-pr.yml @@ -17,4 +17,8 @@ jobs: with: use-quiet-mode: 'yes' use-verbose-mode: 'yes' - check-modified-files-only: 'yes' #Only modified files are checked on PRs + check-modified-files-only: 'yes' # Only modified files are checked on PRs + + # A configuration file can be included, indicating the properties of the link check action + # More information can be found here: https://github.com/tcort/markdown-link-check#config-file-format + # Create mlc_config.json file in the root of the directory From 14fc8bd3a5053ca2da343a1629c014c53d5e907a Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Thu, 14 Apr 2022 11:40:33 +0200 Subject: [PATCH 45/91] ci: update generic workflows (#129) --- .github/workflows/link-check-cron.yml | 6 +++--- .github/workflows/link-check-pr.yml | 7 +++++-- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/.github/workflows/link-check-cron.yml b/.github/workflows/link-check-cron.yml index e24b066b..854c972e 100644 --- a/.github/workflows/link-check-cron.yml +++ b/.github/workflows/link-check-cron.yml @@ -17,7 +17,7 @@ jobs: # Checks the status of hyperlinks in .md files - name: Check links - uses: gaurav-nelson/github-action-markdown-link-check@v1 + uses: derberg/github-action-markdown-link-check@temporary-fix with: use-quiet-mode: 'yes' use-verbose-mode: 'yes' @@ -29,8 +29,8 @@ jobs: - name: Report workflow run status to Slack uses: 8398a7/action-slack@v3 with: - status: ${{ job.status }} - fields: repo,message,action,eventName,ref,workflow + status: ${{ job.status }} + fields: repo,action,workflow env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_DOCS_CHANNEL }} if: failure() # Only, on failure, send a message on the Slack Docs Channel (if there are broken links) diff --git a/.github/workflows/link-check-pr.yml b/.github/workflows/link-check-pr.yml index 6e9eb358..4bdbad22 100644 --- a/.github/workflows/link-check-pr.yml +++ b/.github/workflows/link-check-pr.yml @@ -6,14 +6,17 @@ name: Check Markdown links on: pull_request_target: types: [synchronize, ready_for_review, opened, reopened] + paths: + - '**.md' jobs: External-link-validation-on-PR: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - name: Checkout repo + uses: actions/checkout@v3 - name: Check links - uses: gaurav-nelson/github-action-markdown-link-check@v1 + uses: derberg/github-action-markdown-link-check@temporary-fix with: use-quiet-mode: 'yes' use-verbose-mode: 'yes' From 3dce357e92cbfa9805d41f090b64c9491a892f2e Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Wed, 20 Apr 2022 12:12:19 +0200 Subject: [PATCH 46/91] ci: update generic workflows (#130) --- ...ge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml | 4 ++-- .github/workflows/automerge-orphans.yml | 2 +- .github/workflows/link-check-cron.yml | 4 ++-- .github/workflows/link-check-pr.yml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml b/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml index c694c38f..e00a45fa 100644 --- a/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml +++ b/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml @@ -50,7 +50,7 @@ jobs: repo: pull.head.repo.name, basehead: `${pull.base.label}...${pull.head.label}`, }); - if (comparison.behind_by !== 0) { + if (comparison.behind_by !== 0 && pull.mergeable_state === 'behind') { console.log(`This branch is behind the target by ${comparison.behind_by} commits`) console.log('adding out-of-date comment...'); github.rest.issues.createComment({ @@ -108,4 +108,4 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, labels: ['autoupdate'] - }) \ No newline at end of file + }) diff --git a/.github/workflows/automerge-orphans.yml b/.github/workflows/automerge-orphans.yml index 5c39ba92..5e2ff6ed 100644 --- a/.github/workflows/automerge-orphans.yml +++ b/.github/workflows/automerge-orphans.yml @@ -57,7 +57,7 @@ jobs: name: Send info about orphan to slack uses: rtCamp/action-slack-notify@v2 env: - SLACK_WEBHOOK: ${{secrets.SLACK_GITHUB_NEWISSUEPR}} + SLACK_WEBHOOK: ${{secrets.SLACK_CI_FAIL_NOTIFY}} SLACK_TITLE: 🚨 Not merged PR that should be automerged 🚨 SLACK_MESSAGE: ${{steps.issuemarkdown.outputs.text}} MSG_MINIMAL: true \ No newline at end of file diff --git a/.github/workflows/link-check-cron.yml b/.github/workflows/link-check-cron.yml index 854c972e..cfc2cf01 100644 --- a/.github/workflows/link-check-cron.yml +++ b/.github/workflows/link-check-cron.yml @@ -17,7 +17,7 @@ jobs: # Checks the status of hyperlinks in .md files - name: Check links - uses: derberg/github-action-markdown-link-check@temporary-fix + uses: gaurav-nelson/github-action-markdown-link-check@v1 with: use-quiet-mode: 'yes' use-verbose-mode: 'yes' @@ -32,5 +32,5 @@ jobs: status: ${{ job.status }} fields: repo,action,workflow env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_DOCS_CHANNEL }} + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CI_FAIL_NOTIFY }} if: failure() # Only, on failure, send a message on the Slack Docs Channel (if there are broken links) diff --git a/.github/workflows/link-check-pr.yml b/.github/workflows/link-check-pr.yml index 4bdbad22..ecdf2b39 100644 --- a/.github/workflows/link-check-pr.yml +++ b/.github/workflows/link-check-pr.yml @@ -16,7 +16,7 @@ jobs: - name: Checkout repo uses: actions/checkout@v3 - name: Check links - uses: derberg/github-action-markdown-link-check@temporary-fix + uses: gaurav-nelson/github-action-markdown-link-check@v1 with: use-quiet-mode: 'yes' use-verbose-mode: 'yes' From 190a8d04a448e88523bc4f7206f575f77c178905 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Tue, 26 Apr 2022 19:11:09 +0200 Subject: [PATCH 47/91] ci: update generic workflows (#132) --- .github/workflows/add-good-first-issue-labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/add-good-first-issue-labels.yml b/.github/workflows/add-good-first-issue-labels.yml index 00acdb84..dee10e72 100644 --- a/.github/workflows/add-good-first-issue-labels.yml +++ b/.github/workflows/add-good-first-issue-labels.yml @@ -21,7 +21,7 @@ jobs: github-token: ${{ secrets.GH_TOKEN }} script: | const areas = ['javascript', 'typescript', 'java' , 'go', 'docs', 'ci-cd', 'design']; - const values = context.payload.comment.body.split(" "); + const values = context.payload.comment.body.trim().split(" "); switch(values[1]){ case 'ts': values[1] = 'typescript'; From a9c430bf997153132ebf4b967dd705095d21f4c5 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Tue, 10 May 2022 17:54:08 +0200 Subject: [PATCH 48/91] ci: update generic workflows (#133) --- .github/workflows/autoupdate.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/autoupdate.yml b/.github/workflows/autoupdate.yml index f23ec3b8..1c83da44 100644 --- a/.github/workflows/autoupdate.yml +++ b/.github/workflows/autoupdate.yml @@ -26,7 +26,7 @@ jobs: - name: Autoupdating uses: docker://chinthakagodawita/autoupdate-action:v1 env: - GITHUB_TOKEN: '${{ secrets.GH_TOKEN }}' + GITHUB_TOKEN: '${{ secrets.GH_TOKEN_BOT_EVE }}' PR_FILTER: "labelled" PR_LABELS: "autoupdate" PR_READY_STATE: "ready_for_review" From a9070246a36b4b602b3438e4feaae7982c97163b Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Thu, 12 May 2022 19:23:38 +0200 Subject: [PATCH 49/91] ci: update global contribution guide (#134) --- CONTRIBUTING.md | 79 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..1334921c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,79 @@ +# Contributing to AsyncAPI +We love your input! We want to make contributing to this project as easy and transparent as possible. + +## Contribution recogniton + +We use [All Contributors](https://allcontributors.org/docs/en/specification) specification to handle recognitions. For more details read [this](https://github.com/asyncapi/community/blob/master/recognize-contributors.md) document. + +## Summary of the contribution flow + +The following is a summary of the ideal contribution flow. Please, note that Pull Requests can also be rejected by the maintainers when appropriate. + +``` + ┌───────────────────────┐ + │ │ + │ Open an issue │ + │ (a bug report or a │ + │ feature request) │ + │ │ + └───────────────────────┘ + ⇩ + ┌───────────────────────┐ + │ │ + │ Open a Pull Request │ + │ (only after issue │ + │ is approved) │ + │ │ + └───────────────────────┘ + ⇩ + ┌───────────────────────┐ + │ │ + │ Your changes will │ + │ be merged and │ + │ published on the next │ + │ release │ + │ │ + └───────────────────────┘ +``` + +## Code of Conduct +AsyncAPI has adopted a Code of Conduct that we expect project participants to adhere to. Please [read the full text](./CODE_OF_CONDUCT.md) so that you can understand what sort of behaviour is expected. + +## Our Development Process +We use Github to host code, to track issues and feature requests, as well as accept pull requests. + +## Issues +[Open an issue](https://github.com/asyncapi/asyncapi/issues/new) **only** if you want to report a bug or a feature. Don't open issues for questions or support, instead join our [Slack workspace](https://www.asyncapi.com/slack-invite) and ask there. Don't forget to follow our [Slack Etiquette](https://github.com/asyncapi/community/blob/master/slack-etiquette.md) while interacting with community members! It's more likely you'll get help, and much faster! + +## Bug Reports and Feature Requests + +Please use our issues templates that provide you with hints on what information we need from you to help you out. + +## Pull Requests + +**Please, make sure you open an issue before starting with a Pull Request, unless it's a typo or a really obvious error.** Pull requests are the best way to propose changes to the specification. Get familiar with our document that explains [Git workflow](https://github.com/asyncapi/community/blob/master/git-workflow.md) used in our repositories. + +## Conventional commits + +Our repositories follow [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/#summary) specification. Releasing to GitHub and NPM is done with the support of [semantic-release](https://semantic-release.gitbook.io/semantic-release/). + +Pull requests should have a title that follows the specification, otherwise, merging is blocked. If you are not familiar with the specification simply ask maintainers to modify. You can also use this cheatsheet if you want: + +- `fix: ` prefix in the title indicates that PR is a bug fix and PATCH release must be triggered. +- `feat: ` prefix in the title indicates that PR is a feature and MINOR release must be triggered. +- `docs: ` prefix in the title indicates that PR is only related to the documentation and there is no need to trigger release. +- `chore: ` prefix in the title indicates that PR is only related to cleanup in the project and there is no need to trigger release. +- `test: ` prefix in the title indicates that PR is only related to tests and there is no need to trigger release. +- `refactor: ` prefix in the title indicates that PR is only related to refactoring and there is no need to trigger release. + +What about MAJOR release? just add `!` to the prefix, like `fix!: ` or `refactor!: ` + +Prefix that follows specification is not enough though. Remember that the title must be clear and descriptive with usage of [imperative mood](https://chris.beams.io/posts/git-commit/#imperative). + +Happy contributing :heart: + +## License +When you submit changes, your submissions are understood to be under the same [Apache 2.0 License](https://github.com/asyncapi/asyncapi/blob/master/LICENSE) that covers the project. Feel free to [contact the maintainers](https://www.asyncapi.com/slack-invite) if that's a concern. + +## References +This document was adapted from the open-source contribution guidelines for [Facebook's Draft](https://github.com/facebook/draft-js/blob/master/CONTRIBUTING.md). \ No newline at end of file From b4f05e8182e5b1b7972508d023c5f27fbafdaec5 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Tue, 24 May 2022 10:41:32 +0200 Subject: [PATCH 50/91] chore: update code of conduct (#136) --- CODE_OF_CONDUCT.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..638f7334 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,46 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to make participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. + +## Our Standards + +Examples of behavior that contributes to creating a positive environment include: + +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members + +Examples of unacceptable behavior by participants include: + +- The use of sexualized language or imagery and unwelcome sexual attention or advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission +- Other conduct which could reasonably be considered inappropriate in a professional setting + +## Our Responsibilities + +Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +## Scope + +This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fmvilas@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. + +Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] + +[homepage]: http://contributor-covenant.org +[version]: http://contributor-covenant.org/version/1/4/ From cb064d952b5fc3ce6585fb475c6b910bc1a06c73 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Tue, 31 May 2022 07:43:27 +0200 Subject: [PATCH 51/91] ci: update generic workflows (#137) --- .github/workflows/automerge-orphans.yml | 1 + .github/workflows/autoupdate.yml | 1 + .github/workflows/help-command.yml | 2 +- .github/workflows/link-check-cron.yml | 1 + .github/workflows/stale-issues-prs.yml | 1 + 5 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/automerge-orphans.yml b/.github/workflows/automerge-orphans.yml index 5e2ff6ed..20322ecb 100644 --- a/.github/workflows/automerge-orphans.yml +++ b/.github/workflows/automerge-orphans.yml @@ -9,6 +9,7 @@ on: jobs: identify-orphans: + if: startsWith(github.repository, 'asyncapi/') name: Find orphans and notify runs-on: ubuntu-latest steps: diff --git a/.github/workflows/autoupdate.yml b/.github/workflows/autoupdate.yml index 1c83da44..ad8e0198 100644 --- a/.github/workflows/autoupdate.yml +++ b/.github/workflows/autoupdate.yml @@ -20,6 +20,7 @@ on: jobs: autoupdate-for-bot: + if: startsWith(github.repository, 'asyncapi/') name: Autoupdate autoapproved PR created in the upstream runs-on: ubuntu-latest steps: diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index 69163816..03f891eb 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -25,7 +25,7 @@ jobs: - `/ready-to-merge` or `/rtm` - This comment will trigger automerge of PR in case all required checks are green, approvals in place and do-not-merge label is not added - `/do-not-merge` or `/dnm` - This comment will block automerging even if all conditions are met and ready-to-merge label is added - - `/autoupdate` or `/au` - This comment will add `autoupdate` label to the PR and keeps your PR up-to-date to the target branch's future changes. Unless there is a merge conflict. + - `/autoupdate` or `/au` - This comment will add `autoupdate` label to the PR and keeps your PR up-to-date to the target branch's future changes. Unless there is a merge conflict or it is a draft PR. create_help_comment_issue: if: ${{ !github.event.issue.pull_request && contains(github.event.comment.body, '/help') && github.actor != 'asyncapi-bot' }} runs-on: ubuntu-latest diff --git a/.github/workflows/link-check-cron.yml b/.github/workflows/link-check-cron.yml index cfc2cf01..44e1a5cb 100644 --- a/.github/workflows/link-check-cron.yml +++ b/.github/workflows/link-check-cron.yml @@ -11,6 +11,7 @@ on: jobs: External-link-validation-weekly: + if: startsWith(github.repository, 'asyncapi/') runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 diff --git a/.github/workflows/stale-issues-prs.yml b/.github/workflows/stale-issues-prs.yml index 76673183..c1c0c61d 100644 --- a/.github/workflows/stale-issues-prs.yml +++ b/.github/workflows/stale-issues-prs.yml @@ -9,6 +9,7 @@ on: jobs: stale: + if: startsWith(github.repository, 'asyncapi/') name: Mark issue or PR as stale runs-on: ubuntu-latest steps: From db5307e762a4d3600a5c94940797c905f3a83ce5 Mon Sep 17 00:00:00 2001 From: Paul <32060402+Polo2@users.noreply.github.com> Date: Mon, 13 Jun 2022 13:35:21 +0200 Subject: [PATCH 52/91] docs: adapt AsyncAPI version in Solace example (#135) --- CODEOWNERS | 4 ++-- solace/README.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index b248194e..a4eb1bc7 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -6,10 +6,10 @@ # The default owners are automatically added as reviewers when you open a pull request unless different owners are specified in the file. +* @derberg @fmvilas @asyncapi-bot-eve + /anypointmq/ @GeraldLoeffler /ibmmq/ @rcoppen /kafka/ @lbroudoux @dalelane /solace/ @damaru-inc @CameronRushton *.json @KhudaDad414 - -* @derberg @fmvilas @asyncapi-bot-eve diff --git a/solace/README.md b/solace/README.md index 782a6aab..b9cbbbeb 100644 --- a/solace/README.md +++ b/solace/README.md @@ -113,7 +113,7 @@ channels: eventType: schema: type: string -asyncapi: 2.0.0 +asyncapi: 2.4.0 info: title: HRApp version: 0.0.1 @@ -156,7 +156,7 @@ channels: eventType: schema: type: string -asyncapi: 2.0.0 +asyncapi: 2.4.0 info: title: HRApp version: 0.0.1 From c6a766c0b3e22eb40be7dd342ac0893caeb54c2e Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Wed, 22 Jun 2022 20:11:14 +0200 Subject: [PATCH 53/91] ci: update generic workflows (#138) --- .github/workflows/link-check-cron.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/link-check-cron.yml b/.github/workflows/link-check-cron.yml index 44e1a5cb..2d1e1fc0 100644 --- a/.github/workflows/link-check-cron.yml +++ b/.github/workflows/link-check-cron.yml @@ -34,4 +34,4 @@ jobs: fields: repo,action,workflow env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CI_FAIL_NOTIFY }} - if: failure() # Only, on failure, send a message on the Slack Docs Channel (if there are broken links) + if: failure() # Only, on failure, send a message on the 94_bot-failing-ci slack channel From 68e81e13001f47f9fac78f46e791072410b70535 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Thu, 28 Jul 2022 19:46:02 +0200 Subject: [PATCH 54/91] ci: update generic workflows (#140) --- .github/workflows/stale-issues-prs.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale-issues-prs.yml b/.github/workflows/stale-issues-prs.yml index c1c0c61d..5dc053a4 100644 --- a/.github/workflows/stale-issues-prs.yml +++ b/.github/workflows/stale-issues-prs.yml @@ -13,7 +13,7 @@ jobs: name: Mark issue or PR as stale runs-on: ubuntu-latest steps: - - uses: actions/stale@v4.0.0 + - uses: actions/stale@v5.1.0 with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: | @@ -41,4 +41,5 @@ jobs: stale-issue-label: stale stale-pr-label: stale exempt-issue-labels: keep-open - exempt-pr-labels: keep-open \ No newline at end of file + exempt-pr-labels: keep-open + close-issue-reason: not_planned From 0dcef46499747b1b22170def503e0193b14eba10 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Mon, 15 Aug 2022 12:24:06 -0400 Subject: [PATCH 55/91] docs: Remove wording about which type of operation a binding applies to, as that was too restrictive. (#142) --- solace/README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/solace/README.md b/solace/README.md index b9cbbbeb..a231620e 100644 --- a/solace/README.md +++ b/solace/README.md @@ -39,16 +39,16 @@ Field Name | Type | Description ### Destination Object -Each destination has the following structure. Note that bindings under a 'subscribe' operation define the behaviour of publishers, and those under a 'publish' operation define how subscribers are configured. +Each destination has the following structure: -Field Name | Type | Description | Applicable Operation +Field Name | Type | Description ---|---|---|--- -`destinationType`|Enum|'queue' or 'topic'. If the type is queue, then the subscriber can bind to the queue, which in turn will subscribe to the topic as represented by the channel name or to the provided topicSubscriptions.|publish -`deliveryMode`|Enum|'direct' or 'persistent'. This determines the quality of service for publishing messages as documented [here.](https://docs.solace.com/PubSub-Basics/Core-Concepts-Message-Delivery-Modes.htm) Default is 'persistent'.|subscribe -`queue.name`|String|The name of the queue, only applicable when destinationType is 'queue'.|publish -`queue.topicSubscriptions`|List of String|A list of topics that the queue subscribes to, only applicable when destinationType is 'queue'. If none is given, the queue subscribes to the topic as represented by the channel name.|publish -`queue.accessType`|Enum|'exclusive' or 'nonexclusive'. This is documented [here.](https://docs.solace.com/PubSub-Basics/Endpoints.htm) Only applicable when destinationType is 'queue'.|publish -`topic.topicSubscriptions`|List of String|A list of topics that the client subscribes to, only applicable when destinationType is 'topic'. If none is given, the client subscribes to the topic as represented by the channel name.|publish +`destinationType`|Enum|'queue' or 'topic'. If the type is queue, then the subscriber can bind to the queue, which in turn will subscribe to the topic as represented by the channel name or to the provided topicSubscriptions. +`deliveryMode`|Enum|'direct' or 'persistent'. This determines the quality of service for publishing messages as documented [here.](https://docs.solace.com/PubSub-Basics/Core-Concepts-Message-Delivery-Modes.htm) Default is 'persistent'. +`queue.name`|String|The name of the queue, only applicable when destinationType is 'queue'. +`queue.topicSubscriptions`|List of String|A list of topics that the queue subscribes to, only applicable when destinationType is 'queue'. If none is given, the queue subscribes to the topic as represented by the channel name. +`queue.accessType`|Enum|'exclusive' or 'nonexclusive'. This is documented [here.](https://docs.solace.com/PubSub-Basics/Endpoints.htm) Only applicable when destinationType is 'queue'. +`topic.topicSubscriptions`|List of String|A list of topics that the client subscribes to, only applicable when destinationType is 'topic'. If none is given, the client subscribes to the topic as represented by the channel name. From 8a6c4ecc932512cb0ee3228d321c2f4f2e3b8c4c Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Thu, 15 Sep 2022 22:34:25 +0200 Subject: [PATCH 56/91] ci: update generic workflows (#145) --- .../automerge-for-humans-merging.yml | 27 +++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/.github/workflows/automerge-for-humans-merging.yml b/.github/workflows/automerge-for-humans-merging.yml index e1b4deb4..1d9ed7d1 100644 --- a/.github/workflows/automerge-for-humans-merging.yml +++ b/.github/workflows/automerge-for-humans-merging.yml @@ -21,12 +21,35 @@ jobs: if: github.event.pull_request.draft == false && (github.event.pull_request.user.login != 'asyncapi-bot' || github.event.pull_request.user.login != 'dependabot[bot]' || github.event.pull_request.user.login != 'dependabot-preview[bot]') #it runs only if PR actor is not a bot, at least not a bot that we know runs-on: ubuntu-latest steps: + - name: Get list of authors + uses: sergeysova/jq-action@v2 + id: authors + with: + # This cmd does following (line by line): + # 1. CURL querying the list of commits of the current PR via GH API. Why? Because the current event payload does not carry info about the commits. + # 2. Iterates over the previous returned payload, and creates an array with the filtered results (see below) so we can work wit it later. An example of payload can be found in https://docs.github.com/en/developers/webhooks-and-events/webhooks/webhook-events-and-payloads#webhook-payload-example-34. + # 3. Grabs the data we need for adding the `Co-authored-by: ...` lines later and puts it into objects to be used later on. + # 4. Filters the results by excluding the current PR sender. We don't need to add it as co-author since is the PR creator and it will become by default the main author. + # 5. Removes repeated authors (authors can have more than one commit in the PR). + # 6. Builds the `Co-authored-by: ...` lines with actual info. + # 7. Transforms the array into plain text. Thanks to this, the actual stdout of this step can be used by the next Workflow step (wich is basically the automerge). + cmd: | + curl -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.GH_TOKEN }}" "${{github.event.pull_request._links.commits.href}}?per_page=100" + | jq -r '[.[] + | {name: .commit.author.name, email: .commit.author.email, login: .author.login}] + | map(select(.login != "${{github.event.pull_request.user.login}}")) + | unique + | map("Co-authored-by: " + .name + " <" + .email + ">") + | join("\n")' + multiline: true - name: Automerge PR uses: pascalgn/automerge-action@v0.14.3 env: GITHUB_TOKEN: "${{ secrets.GH_TOKEN }}" MERGE_LABELS: "!do-not-merge,ready-to-merge" MERGE_METHOD: "squash" - MERGE_COMMIT_MESSAGE: "{pullRequest.title} (#{pullRequest.number})" + # Using the output of the previous step (`Co-authored-by: ...` lines) as commit description. + # Important to keep 2 empty lines as https://docs.github.com/en/pull-requests/committing-changes-to-your-project/creating-and-editing-commits/creating-a-commit-with-multiple-authors#creating-co-authored-commits-on-the-command-line mentions + MERGE_COMMIT_MESSAGE: "{pullRequest.title} (#{pullRequest.number})\n\n\n${{ steps.authors.outputs.value }}" MERGE_RETRIES: "20" - MERGE_RETRY_SLEEP: "30000" \ No newline at end of file + MERGE_RETRY_SLEEP: "30000" From 8d1b35f6d02326dd3836da92121b156e0648afe1 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Tue, 20 Sep 2022 07:38:17 +0200 Subject: [PATCH 57/91] ci: update generic workflows (#146) --- .github/workflows/automerge-for-humans-merging.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/automerge-for-humans-merging.yml b/.github/workflows/automerge-for-humans-merging.yml index 1d9ed7d1..4bce61da 100644 --- a/.github/workflows/automerge-for-humans-merging.yml +++ b/.github/workflows/automerge-for-humans-merging.yml @@ -34,8 +34,8 @@ jobs: # 6. Builds the `Co-authored-by: ...` lines with actual info. # 7. Transforms the array into plain text. Thanks to this, the actual stdout of this step can be used by the next Workflow step (wich is basically the automerge). cmd: | - curl -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.GH_TOKEN }}" "${{github.event.pull_request._links.commits.href}}?per_page=100" - | jq -r '[.[] + curl -H "Accept: application/vnd.github+json" -H "Authorization: Bearer ${{ secrets.GH_TOKEN }}" "${{github.event.pull_request._links.commits.href}}?per_page=100" | + jq -r '[.[] | {name: .commit.author.name, email: .commit.author.email, login: .author.login}] | map(select(.login != "${{github.event.pull_request.user.login}}")) | unique From c95a4e5a5e1be6f55aadb0b13f02f06c6c54d66d Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Thu, 22 Sep 2022 08:55:05 +0200 Subject: [PATCH 58/91] ci: update generic workflows (#148) --- .github/workflows/link-check-cron.yml | 2 +- .github/workflows/link-check-pr.yml | 2 +- .github/workflows/stale-issues-prs.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/link-check-cron.yml b/.github/workflows/link-check-cron.yml index 2d1e1fc0..e992e2d6 100644 --- a/.github/workflows/link-check-cron.yml +++ b/.github/workflows/link-check-cron.yml @@ -18,7 +18,7 @@ jobs: # Checks the status of hyperlinks in .md files - name: Check links - uses: gaurav-nelson/github-action-markdown-link-check@v1 + uses: gaurav-nelson/github-action-markdown-link-check@0a51127e9955b855a9bbfa1ff5577f1d1338c9a5 #1.0.14 but pointing to commit for security reasons with: use-quiet-mode: 'yes' use-verbose-mode: 'yes' diff --git a/.github/workflows/link-check-pr.yml b/.github/workflows/link-check-pr.yml index ecdf2b39..deb425cb 100644 --- a/.github/workflows/link-check-pr.yml +++ b/.github/workflows/link-check-pr.yml @@ -16,7 +16,7 @@ jobs: - name: Checkout repo uses: actions/checkout@v3 - name: Check links - uses: gaurav-nelson/github-action-markdown-link-check@v1 + uses: gaurav-nelson/github-action-markdown-link-check@0a51127e9955b855a9bbfa1ff5577f1d1338c9a5 #1.0.14 but pointing to commit for security reasons with: use-quiet-mode: 'yes' use-verbose-mode: 'yes' diff --git a/.github/workflows/stale-issues-prs.yml b/.github/workflows/stale-issues-prs.yml index 5dc053a4..ed1fcf19 100644 --- a/.github/workflows/stale-issues-prs.yml +++ b/.github/workflows/stale-issues-prs.yml @@ -13,7 +13,7 @@ jobs: name: Mark issue or PR as stale runs-on: ubuntu-latest steps: - - uses: actions/stale@v5.1.0 + - uses: actions/stale@99b6c709598e2b0d0841cd037aaf1ba07a4410bd #v5.2.0 but pointing to commit for security reasons with: repo-token: ${{ secrets.GITHUB_TOKEN }} stale-issue-message: | From 6c31d45b26ba31193b7044e68ebb50fd5d80151b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Urba=C5=84czyk?= Date: Fri, 23 Sep 2022 10:35:24 +0200 Subject: [PATCH 59/91] docs: add ibmmq operation binding object (#149) --- ibmmq/README.md | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/ibmmq/README.md b/ibmmq/README.md index 57cc75e5..f3496f7e 100644 --- a/ibmmq/README.md +++ b/ibmmq/README.md @@ -142,6 +142,8 @@ servers: bindingVersion: 0.1.0 ``` + + ## Channel Binding Object @@ -202,8 +204,17 @@ channels: bindingVersion: 0.1.0 ``` + + +## Operation Binding Object + +This object MUST NOT contain any properties. Its name is reserved for future use. + + + + ## Message Binding Object @@ -252,7 +263,7 @@ channels: bindingVersion: 0.1.0 ``` -# AsyncAPI example with IBM MQ binding +# AsyncAPI example with IBM MQ binding ##### Example for AsyncAPI user signup From bc06e78ad3aebe5a40526866b023bada2cbe96f8 Mon Sep 17 00:00:00 2001 From: Jeremy Whitlock Date: Tue, 27 Sep 2022 01:31:49 -0600 Subject: [PATCH 60/91] feat: add googlepubsub bindings (#141) Co-authored-by: Lukasz Gornicki --- CODEOWNERS | 11 +- README.md | 1 + googlepubsub/README.md | 170 +++++++++++++++++++++++++ googlepubsub/json_schemas/channel.json | 82 ++++++++++++ googlepubsub/json_schemas/message.json | 55 ++++++++ 5 files changed, 314 insertions(+), 5 deletions(-) create mode 100644 googlepubsub/README.md create mode 100644 googlepubsub/json_schemas/channel.json create mode 100644 googlepubsub/json_schemas/message.json diff --git a/CODEOWNERS b/CODEOWNERS index a4eb1bc7..502a20b0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -8,8 +8,9 @@ * @derberg @fmvilas @asyncapi-bot-eve -/anypointmq/ @GeraldLoeffler -/ibmmq/ @rcoppen -/kafka/ @lbroudoux @dalelane -/solace/ @damaru-inc @CameronRushton -*.json @KhudaDad414 +/anypointmq/ @GeraldLoeffler +/ibmmq/ @rcoppen +/kafka/ @lbroudoux @dalelane +/googlepubsub/ @whitlockjc +/solace/ @damaru-inc @CameronRushton +*.json @KhudaDad414 diff --git a/README.md b/README.md index 3891c577..849447a7 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ This repository contains the specifications for each AsyncAPI protocol binding. * [AMQP binding](./amqp) * [AMQP 1.0 binding](./amqp1) +* [Google Cloud Pub/Sub binding](./googlepubsub) * [HTTP binding](./http) * [IBM MQ binding](./ibmmq) * [JMS binding](./jms) diff --git a/googlepubsub/README.md b/googlepubsub/README.md new file mode 100644 index 00000000..6b691d09 --- /dev/null +++ b/googlepubsub/README.md @@ -0,0 +1,170 @@ +# Google Cloud Pub/Sub Bindings + +This document defines how to describe Google Cloud Pub/Sub specific information with AsyncAPI. + + + +## Version + +Current version is `0.1.0`. + + + +## Channel Binding Object + +The `Channel Bindings Object` is used to describe the Google Cloud Pub/Sub specific +[Topic](https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.topics/create) details with AsyncAPI. + +Field Name | Type | Description +---|---|--- +`bindingVersion`|String|The current version is `0.1.0` +`labels`|Object|An object of key-value pairs _(These are used to categorize Cloud Resources like Cloud Pub/Sub Topics.)_ +`messageRetentionDuration`|String|Indicates the minimum duration to retain a message after it is published to the topic _(Must be a valid [Duration](https://developers.google.com/protocol-buffers/docs/reference/google.protobuf#google.protobuf.Duration).)_ +`messageStoragePolicy`|[Message Storage Policy Object](#message-storage-policy-object)|Policy constraining the set of Google Cloud Platform regions where messages published to the topic may be stored +`schemaSettings`|[Schema Settings Object](#schema-settings-object)|Settings for validating messages published against a schema +`topic`|String|The Google Cloud Pub/Sub Topic name + + + +### Message Storage Policy Object + +The `Message Storage Policy Object` is used to describe the Google Cloud Pub/Sub +[MessageStoragePolicy](https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.topics#MessageStoragePolicy) +Object with AsyncAPI. + +Field Name | Type | Description +---|---|--- +`allowedPersistenceRegions`|String[]|A list of IDs of GCP regions where messages that are published to the topic may be persisted in storage + + + +### Schema Settings Object + +The `Schema Settings Object` is used to describe the Google Cloud Pub/Sub +[SchemaSettings](https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.topics#SchemaSettings) Object with +AsyncAPI. + +Field Name | Type | Description +---|---|--- +`encoding`|String|The encoding of the message _(Must be one of the possible [Encoding](https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.topics#encoding) values.)_ +`firstRevisionId`|String|The minimum _(inclusive)_ revision allowed for validating messages +`lastRevisionId`|String|The maximum _(inclusive)_ revision allowed for validating messages +`name`|String|The name of the schema that messages published should be validated against _(The format is `projects/{project}/schemas/{schema}`.)_ + + + +### Example + +```yaml +# ... +channels: + topic-avro-schema: + bindings: + googlepubsub: + topic: projects/your-project/topics/topic-avro-schema + schemaSettings: + encoding: json + name: projects/your-project/schemas/message-avro +# ... + topic-proto-schema: + bindings: + googlepubsub: + topic: projects/your-project/topics/topic-proto-schema + messageRetentionDuration: 86400s + messageStoragePolicy: + allowedPersistenceRegions: + - us-central1 + - us-central2 + - us-east1 + - us-east4 + - us-east5 + - us-east7 + - us-south1 + - us-west1 + - us-west2 + - us-west3 + - us-west4 + schemaSettings: + encoding: binary + name: projects/your-project/schemas/message-proto +# ... +``` + + + +## Message Binding Object + +The `Message Binding Object` is used to describe the Google Cloud Pub/Sub specific +[PubsubMessage](https://cloud.google.com/pubsub/docs/reference/rest/v1/PubsubMessage) details, alongside with pertintent +parts of the Google Cloud Pub/Sub +[Schema](https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.schemas#Schema) +Object, with AsyncAPI. + +Field Name | Type | Description +---|---|--- +`bindingVersion`|String|The current version is `0.1.0` +`attributes`|Object|Attributes for this message _(If this field is empty, the message must contain non-empty data. This can be used to filter messages on the subscription.)_ +`orderingKey`|String|If non-empty, identifies related messages for which publish order should be respected _(For more information, see [ordering messages](https://cloud.google.com/pubsub/docs/ordering).)_ +`schema`|[Schema Definition Object](#schema-definition-object)|Describes the schema used to validate the payload of this message + + + +### Schema Definition Object + +The `Schema Definition Object` is used to describe the Google Cloud Pub/Sub +[Schema]([Schema](https://cloud.google.com/pubsub/docs/reference/rest/v1/projects.schemas#Schema)) Object with AsyncAPI. +While some of this information could be, or is, described using native AsyncAPI, for consistency it makes sense to +provide this information here at all times, especially for cases where AsyncAPI does not natively support describing +payloads using a supported Google Cloud Pub/Sub schema format like Protobuf. + +Field Name | Type | Description +---|---|--- +`name`|String|The name of the schema +`type`|String|The type of the schema + + + +### Example + +```yaml +# ... +components: + messages: + messageAvro: + bindings: + googlepubsub: + schema: + name: projects/your-project/schemas/message-avro + type: avro + contentType: application/json + name: MessageAvro + payload: + fields: + - name: message + type: string + name: Message + type: record + schemaFormat: application/vnd.apache.avro+yaml;version=1.9.0 + messageProto: + bindings: + googlepubsub: + schema: + name: projects/your-project/schemas/message-proto + type: protobuf + contentType: application/octet-stream + name: MessageProto + payload: true +# ... +``` + + + +## Operation Binding Object + +This object MUST NOT contain any properties. Its name is reserved for future use. + + + +## Server Binding Object + +This object MUST NOT contain any properties. Its name is reserved for future use. diff --git a/googlepubsub/json_schemas/channel.json b/googlepubsub/json_schemas/channel.json new file mode 100644 index 00000000..02596c39 --- /dev/null +++ b/googlepubsub/json_schemas/channel.json @@ -0,0 +1,82 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/googlepubsub/channel.json", + "title": "Cloud Pub/Sub Channel Schema", + "description": "This object contains information about the channel representation for Google Cloud Pub/Sub.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding." + }, + "labels": { + "type": "object" + }, + "messageRetentionDuration": { + "type": "string" + }, + "messageStoragePolicy": { + "type": "object", + "additionalProperties": false, + "properties": { + "allowedPersistenceRegions": { + "type": "array", + "items": { + "type": "string" + } + } + } + }, + "schemaSettings": { + "type": "object", + "additionalItems": false, + "properties": { + "encoding": { + "type": "string" + }, + "firstRevisionId": { + "type": "string" + }, + "lastRevisionId": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": ["encoding", "name"] + }, + "topic": { + "type": "string" + } + }, + "required": ["schemaSettings", "topic"], + "examples": [ + { + "labels": { + "label1": "value1", + "label2": "value2" + }, + "messageRetentionDuration": "86400s", + "messageStoragePolicy": { + "allowedPersistenceRegions": [ + "us-central1", + "us-east1" + ] + }, + "schemaSettings": { + "encoding": "json", + "name": "projects/your-project-id/schemas/your-schema" + } + } + ] +} \ No newline at end of file diff --git a/googlepubsub/json_schemas/message.json b/googlepubsub/json_schemas/message.json new file mode 100644 index 00000000..a647d743 --- /dev/null +++ b/googlepubsub/json_schemas/message.json @@ -0,0 +1,55 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/googlepubsub/message.json", + "title": "Cloud Pub/Sub Channel Schema", + "description": "This object contains information about the message representation for Google Cloud Pub/Sub.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding." + }, + "attributes": { + "type": "object" + }, + "orderingKey": { + "type": "string" + }, + "schema": { + "type": "object", + "additionalItems": false, + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": ["name", "type"] + } + }, + "examples": [ + { + "schema": { + "name": "projects/your-project-id/schemas/your-avro-schema-id", + "type": "avro" + } + }, + { + "schema": { + "name": "projects/your-project-id/schemas/your-protobuf-schema-id", + "type": "protobuf" + } + } + ] +} \ No newline at end of file From 876cb8b36ed1b40e8997c21aac68488f6543030a Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Wed, 28 Sep 2022 19:46:15 +0200 Subject: [PATCH 61/91] ci: update generic workflows (#152) --- .github/workflows/lint-pr-title.yml | 38 ++++++++++++++++++++++++----- 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml index 87e2fa5e..61835288 100644 --- a/.github/workflows/lint-pr-title.yml +++ b/.github/workflows/lint-pr-title.yml @@ -1,23 +1,49 @@ # This action is centrally managed in https://github.com/asyncapi/.github/ # Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + + name: Lint PR title on: pull_request_target: types: [opened, reopened, synchronize, edited, ready_for_review] - + jobs: lint-pr-title: - name: Lint PR title - runs-on: ubuntu-latest - steps: + name: Lint PR title + runs-on: ubuntu-latest + steps: # Since this workflow is REQUIRED for a PR to be mergable, we have to have this 'if' statement in step level instead of job level. - if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors"]'), github.actor) }} - uses: amannn/action-semantic-pull-request@v3.2.5 + uses: amannn/action-semantic-pull-request@505e44b4f33b4c801f063838b3f053990ee46ea7 #version 4.6.0 + id: lint_pr_title env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} + GITHUB_TOKEN: ${{ secrets.GH_TOKEN}} with: subjectPattern: ^(?![A-Z]).+$ subjectPatternError: | The subject "{subject}" found in the pull request title "{title}" should start with a lowercase character. + + # Comments the error message from the above lint_pr_title action + - if: ${{always() && !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors"]'), github.actor)}} + name: Comment on PR + uses: marocchino/sticky-pull-request-comment@39c5b5dc7717447d0cba270cd115037d32d28443 #version 2.2 + with: + header: pr-title-lint-error + GITHUB_TOKEN: ${{ secrets.GH_TOKEN}} + message: | + + We require all PRs to follow [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/). + More details 👇🏼 + ``` + ${{ steps.lint_pr_title.outputs.error_message}} + ``` + # deletes the error comment if the title is correct + - if: ${{ steps.lint_pr_title.outputs.error_message == null }} + name: delete the comment + uses: marocchino/sticky-pull-request-comment@39c5b5dc7717447d0cba270cd115037d32d28443 #version 2.2 + with: + header: pr-title-lint-error + delete: true + GITHUB_TOKEN: ${{ secrets.GH_TOKEN}} From 9fba04893bb7885d95d0e74338ea3ea01ee05ca1 Mon Sep 17 00:00:00 2001 From: Laurent Broudoux Date: Wed, 28 Sep 2022 21:24:42 +0200 Subject: [PATCH 62/91] feat: add schema registry infos to Kafka binding (#115) * feat: Add schema registry infos to Kafka binding * feat: add schema registry infos to Kafka binding - update to latest spec * feat: Move schema encoding to message and Add topic, partitions and replicas at the channel level * typo Co-authored-by: Khuda Dad Nomani <32505158+KhudaDad414@users.noreply.github.com> --- kafka/README.md | 95 ++++++++++++++++++++++++++----- kafka/json_schemas/channel.json | 46 +++++++++++++++ kafka/json_schemas/message.json | 29 ++++++++-- kafka/json_schemas/operation.json | 10 ++-- kafka/json_schemas/server.json | 37 ++++++++++++ 5 files changed, 193 insertions(+), 24 deletions(-) create mode 100644 kafka/json_schemas/channel.json create mode 100644 kafka/json_schemas/server.json diff --git a/kafka/README.md b/kafka/README.md index adc5606e..6efcf74e 100644 --- a/kafka/README.md +++ b/kafka/README.md @@ -6,38 +6,81 @@ This document defines how to describe Kafka-specific information on AsyncAPI. ## Version -Current version is `0.2.0`. +Current version is `0.3.0`. ## Server Binding Object -This object MUST NOT contain any properties. Its name is reserved for future use. +This object contains information about the server representation in Kafka. +##### Fixed Fields + +Field Name | Type | Description | Applicability [default] | Constraints +---|:---:|:---:|:---:|--- +`schemaRegistryUrl` | string (url) | API URL for the Schema Registry used when producing Kafka messages (if a Schema Registry was used) | OPTIONAL | - +`schemaRegistryVendor` | string | The vendor of Schema Registry and Kafka serdes library that should be used (e.g. `apicurio`, `confluent`, `ibm`, or `karapace`) | OPTIONAL | MUST NOT be specified if `schemaRegistryUrl` is not specified +`bindingVersion` | string | The version of this binding. | OPTIONAL [`latest`] +##### Example + +```yaml +servers: + production: + bindings: + kafka: + schemaRegistryUrl: 'https://my-schema-registry.com' + schemaRegistryVendor: 'confluent' + bindingVersion: '0.3.0' +``` ## Channel Binding Object -This object MUST NOT contain any properties. Its name is reserved for future use. +This object contains information about the channel representation in Kafka (eg. a Kafka topic). + +##### Fixed Fields + +Field Name | Type | Description | Applicability [default] | Constraints +---|:---:|:---:|:---:|--- +`topic` | string | Kafka topic name if different from channel name. | OPTIONAL | - +`partitions` | integer | Number of partitions configured on this topic (useful to know how many parallel consumers you may run). | OPTIONAL | Must be positive +`replicas` | integer | Number of replicas configured on this topic. | OPTIONAL | MUST be positive +`bindingVersion` | string | The version of this binding. If omitted, "latest" MUST be assumed. | OPTIONAL [`latest`] | - +This object MUST contain only the properties defined above. + +##### Example + +This example is valid for any Confluent compatible schema registry. Here we describe the implementation using the first 4 bytes in payload to store schema identifier. + +```yaml +channels: + user-signedup: + bindings: + kafka: + topic: 'my-specific-topic-name' + partitions: 20 + replicas: 3 + bindingVersion: '0.3.0' +``` ## Operation Binding Object -This object contains information about the operation representation in Kafka. +This object contains information about the operation representation in Kafka (eg. the way to consume messages) ##### Fixed Fields -Field Name | Type | Description ----|:---:|--- -`groupId` | [Schema Object][schemaObject] | Id of the consumer group. -`clientId` | [Schema Object][schemaObject] | Id of the consumer inside a consumer group. -`bindingVersion` | string | The version of this binding. If omitted, "latest" MUST be assumed. +Field Name | Type | Description | Applicability [default] | Constraints +---|:---:|:---:|:---:|--- +`groupId` | [Schema Object][schemaObject] | Id of the consumer group. | OPTIONAL | - +`clientId` | [Schema Object][schemaObject] | Id of the consumer inside a consumer group. | OPTIONAL | - +`bindingVersion` | string | The version of this binding. If omitted, "latest" MUST be assumed. | OPTIONAL [`latest`] | - This object MUST contain only the properties defined above. @@ -46,7 +89,7 @@ This object MUST contain only the properties defined above. ```yaml channels: user-signedup: - publish: + subscribe: bindings: kafka: groupId: @@ -55,7 +98,7 @@ channels: clientId: type: string enum: ['myClientId'] - bindingVersion: '0.1.0' + bindingVersion: '0.3.0' ``` @@ -69,11 +112,32 @@ This object contains information about the message representation in Kafka. Field Name | Type | Description ---|:---:|--- -`key` | [Schema Object][schemaObject] \| [AVRO Schema Object](https://avro.apache.org/docs/current/spec.html) | The message key. **NOTE**: You can also use the [reference object](https://asyncapi.io/docs/specifications/v2.1.0#referenceObject) way. +`key` | [Schema Object][schemaObject] \| [AVRO Schema Object](https://avro.apache.org/docs/current/spec.html) | The message key. **NOTE**: You can also use the [reference object](https://asyncapi.io/docs/specifications/v2.4.0#referenceObject) way. +`schemaIdLocation` | string | If a Schema Registry is used when performing this operation, tells where the id of schema is stored (e.g. `header` or `payload`). | OPTIONAL | MUST NOT be specified if `schemaRegistryUrl` is not specified at the Server level +`schemaIdPayloadEncoding` | string | Number of bytes or vendor specific values when schema id is encoded in payload (e.g `confluent`/ `apicurio-legacy` / `apicurio-new`). | OPTIONAL | MUST NOT be specified if `schemaRegistryUrl` is not specified at the Server level +`schemaLookupStrategy` | string | Freeform string for any naming strategy class to use. Clients should default to the vendor default if not supplied. | OPTIONAL | MUST NOT be specified if `schemaRegistryUrl` is not specified at the Server level `bindingVersion` | string | The version of this binding. If omitted, "latest" MUST be assumed. This object MUST contain only the properties defined above. +This example is valid for any Confluent compatible schema registry. Here we describe the implementation using the first 4 bytes in payload to store schema identifier. + +```yaml +channels: + test: + publish: + message: + bindings: + kafka: + key: + type: string + enum: ['myKey'] + schemaIdLocation: 'payload' + schemaIdPayloadEncoding: '4' + bindingVersion: '0.3.0' +``` + +This is another example that describes the use if Apicurio schema registry. We describe the `apicurio-new` way of serializing without details on how it's implemented. We reference a [specific lookup strategy](https://www.apicur.io/registry/docs/apicurio-registry/2.2.x/getting-started/assembly-using-kafka-client-serdes.html#registry-serdes-concepts-strategy_registry) that may be used to retrieve schema Id from registry during serialization. ```yaml channels: @@ -85,7 +149,10 @@ channels: key: type: string enum: ['myKey'] - bindingVersion: '0.1.0' + schemaIdLocation: 'payload' + schemaIdPayloadEncoding: 'apicurio-new' + schemaLookupStrategy: 'TopicIdStrategy' + bindingVersion: '0.3.0' ``` -[schemaObject]: https://www.asyncapi.com/docs/specifications/2.0.0/#schemaObject +[schemaObject]: https://www.asyncapi.com/docs/specifications/2.4.0/#schemaObject diff --git a/kafka/json_schemas/channel.json b/kafka/json_schemas/channel.json new file mode 100644 index 00000000..4db52eb5 --- /dev/null +++ b/kafka/json_schemas/channel.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/kafka/channel.json", + "title": "Channel Schema", + "description": "This object contains information about the channel representation in Kafka.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "topic": { + "type": "string", + "description": "Kafka topic name if different from channel name." + }, + "partitions": { + "type": "integer", + "minimum": 1, + "description": "Number of partitions configured on this topic." + }, + "replicas": { + "type": "integer", + "minimum": 1, + "description": "Number of replicas configured on this topic." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + + }, + "examples": [ + { + "topic": "my-specific-topic", + "partitions": 20, + "replicas": 3, + "bindingVersion": "0.3.0" + } + ] + } + \ No newline at end of file diff --git a/kafka/json_schemas/message.json b/kafka/json_schemas/message.json index 701beaa4..f1428c9c 100644 --- a/kafka/json_schemas/message.json +++ b/kafka/json_schemas/message.json @@ -6,21 +6,35 @@ "additionalProperties": false, "patternProperties": { "^x-[\\w\\d\\.\\-\\_]+$": { - "$ref": "https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.7.7/schemas/2.0.0.json#/definitions/specificationExtension" + "$ref": "https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" } }, "properties": { "key": { - "$ref": "https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.7.7/schemas/2.0.0.json#/definitions/schema", + "$ref": "https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.14.0/schemas/2.4.0.json#/definitions/schema", "description": "The message key." }, + "schemaIdLocation": { + "type": "string", + "description": "If a Schema Registry is used when performing this operation, tells where the id of schema is stored.", + "enum": ["header", "payload"] + }, + "schemaIdPayloadEncoding": { + "type": "string", + "description": "Number of bytes or vendor specific values when schema id is encoded in payload." + }, + "schemaLookupStrategy": { + "type": "string", + "description": "Freeform string for any naming strategy class to use. Clients should default to the vendor default if not supplied." + }, "bindingVersion": { "type": "string", "enum": [ - "0.1.0" + "0.3.0" ], "description": "The version of this binding. If omitted, 'latest' MUST be assumed." } + }, "examples": [ { @@ -30,13 +44,18 @@ "myKey" ] }, - "bindingVersion": "0.1.0" + "schemaIdLocation": "payload", + "schemaIdPayloadEncoding": "apicurio-new", + "schemaLookupStrategy": "TopicIdStrategy", + "bindingVersion": "0.3.0" }, { "key": { "$ref": "path/to/user-create.avsc#/UserCreate" }, - "bindingVersion": "0.2.0" + "schemaIdLocation": "payload", + "schemaIdPayloadEncoding": "4", + "bindingVersion": "0.3.0" } ] } diff --git a/kafka/json_schemas/operation.json b/kafka/json_schemas/operation.json index 500744ca..ea54cdab 100644 --- a/kafka/json_schemas/operation.json +++ b/kafka/json_schemas/operation.json @@ -7,22 +7,22 @@ "additionalProperties": false, "patternProperties": { "^x-[\\w\\d\\.\\-\\_]+$": { - "$ref": "https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.7.7/schemas/2.0.0.json#/definitions/specificationExtension" + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" } }, "properties": { "groupId": { - "$ref": "https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.7.7/schemas/2.0.0.json#/definitions/schema", + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/schema", "description": "Id of the consumer group." }, "clientId": { - "$ref": "https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.7.7/schemas/2.0.0.json#/definitions/schema", + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/schema", "description": "Id of the consumer inside a consumer group." }, "bindingVersion": { "type": "string", "enum": [ - "0.1.0" + "0.3.0" ], "description": "The version of this binding. If omitted, 'latest' MUST be assumed." } @@ -42,7 +42,7 @@ "myClientId" ] }, - "bindingVersion": "0.1.0" + "bindingVersion": "0.3.0" } ] } diff --git a/kafka/json_schemas/server.json b/kafka/json_schemas/server.json new file mode 100644 index 00000000..f29e3d33 --- /dev/null +++ b/kafka/json_schemas/server.json @@ -0,0 +1,37 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/kafka/server.json", + "title": "Server Schema", + "description": "This object contains server connection information to a Kafka broker. This object contains additional information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "schemaRegistryUrl": { + "type": "string", + "description": "API URL for the Schema Registry used when producing Kafka messages (if a Schema Registry was used)." + }, + "schemaRegistryVendor": { + "type": "string", + "description": "The vendor of the Schema Registry and Kafka serdes library that should be used." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.3.0" + ], + "description": "The version of this binding." + } + }, + "examples": [ + { + "schemaRegistryUrl": "https://my-schema-registry.com", + "schemaRegistryVendor": "confluent", + "bindingVersion": "0.3.0" + } + ] +} From e80a8a373b316adad430061d4e5f36b370351d69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fran=20M=C3=A9ndez?= Date: Tue, 11 Oct 2022 17:26:45 +0200 Subject: [PATCH 63/91] chore: add @smoya, @dalelane, and @char0n as code owners (#156) --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 502a20b0..18d11328 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -6,7 +6,7 @@ # The default owners are automatically added as reviewers when you open a pull request unless different owners are specified in the file. -* @derberg @fmvilas @asyncapi-bot-eve +* @derberg @fmvilas @dalelane @smoya @char0n @asyncapi-bot-eve /anypointmq/ @GeraldLoeffler /ibmmq/ @rcoppen From 2e583fb4bfec45c9f39514fbd51c09fddadbba48 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Thu, 13 Oct 2022 09:35:34 +0200 Subject: [PATCH 64/91] ci: update generic workflows (#159) --- .github/workflows/lint-pr-title.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml index 61835288..77813660 100644 --- a/.github/workflows/lint-pr-title.yml +++ b/.github/workflows/lint-pr-title.yml @@ -26,7 +26,7 @@ jobs: The subject "{subject}" found in the pull request title "{title}" should start with a lowercase character. # Comments the error message from the above lint_pr_title action - - if: ${{always() && !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors"]'), github.actor)}} + - if: ${{steps.lint_pr_title.outputs.error_message != null && !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors"]'), github.actor)}} name: Comment on PR uses: marocchino/sticky-pull-request-comment@39c5b5dc7717447d0cba270cd115037d32d28443 #version 2.2 with: From 0eadb078dd4fee4a687d546c62629f5fc89cf8c1 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Thu, 20 Oct 2022 09:42:09 +0200 Subject: [PATCH 65/91] ci: update generic workflows (#161) --- .github/workflows/lint-pr-title.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml index 77813660..0440d87e 100644 --- a/.github/workflows/lint-pr-title.yml +++ b/.github/workflows/lint-pr-title.yml @@ -26,7 +26,7 @@ jobs: The subject "{subject}" found in the pull request title "{title}" should start with a lowercase character. # Comments the error message from the above lint_pr_title action - - if: ${{steps.lint_pr_title.outputs.error_message != null && !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors"]'), github.actor)}} + - if: ${{ always() && steps.lint_pr_title.outputs.error_message != null && !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors"]'), github.actor)}} name: Comment on PR uses: marocchino/sticky-pull-request-comment@39c5b5dc7717447d0cba270cd115037d32d28443 #version 2.2 with: From 4f8c9c34cdbf8c56a1231a0ded831e0fd6f5eac5 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Mon, 24 Oct 2022 10:08:46 +0200 Subject: [PATCH 66/91] ci: update generic workflows (#162) --- .github/workflows/add-good-first-issue-labels.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/add-good-first-issue-labels.yml b/.github/workflows/add-good-first-issue-labels.yml index dee10e72..20234526 100644 --- a/.github/workflows/add-good-first-issue-labels.yml +++ b/.github/workflows/add-good-first-issue-labels.yml @@ -28,8 +28,10 @@ jobs: break; case 'js': values[1] = 'javascript'; + break; case 'markdown': values[1] = 'docs'; + break; } if(values.length != 2 || !areas.includes(values[1])){ const message = `Hey @${context.payload.sender.login}, your message doesn't follow the requirements, you can try \`/help\`.` From f5d096248df7fc90db5ac24b29126f09d99c9eb5 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Fri, 28 Oct 2022 06:54:31 +0200 Subject: [PATCH 67/91] ci: update generic workflows (#166) --- .../workflows/notify-tsc-members-mention.yml | 166 ++++- .github/workflows/scripts/README.md | 1 + .../scripts/mailchimp/htmlContent.js | 495 +++++++++++++++ .github/workflows/scripts/mailchimp/index.js | 73 +++ .../scripts/mailchimp/package-lock.json | 597 ++++++++++++++++++ .../workflows/scripts/mailchimp/package.json | 9 + 6 files changed, 1339 insertions(+), 2 deletions(-) create mode 100644 .github/workflows/scripts/README.md create mode 100644 .github/workflows/scripts/mailchimp/htmlContent.js create mode 100644 .github/workflows/scripts/mailchimp/index.js create mode 100644 .github/workflows/scripts/mailchimp/package-lock.json create mode 100644 .github/workflows/scripts/mailchimp/package.json diff --git a/.github/workflows/notify-tsc-members-mention.yml b/.github/workflows/notify-tsc-members-mention.yml index e33b2625..1cdb371c 100644 --- a/.github/workflows/notify-tsc-members-mention.yml +++ b/.github/workflows/notify-tsc-members-mention.yml @@ -2,7 +2,7 @@ # Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo # This action notifies community on slack whenever there is a new issue, PR or discussion started in given repository -name: Notify slack whenever TSC members are mentioned in GitHub +name: Notify slack and email subscribers whenever TSC members are mentioned in GitHub on: issue_comment: @@ -38,6 +38,9 @@ jobs: name: TSC notification on every new issue runs-on: ubuntu-latest steps: + ######### + # Handling Slack notifications + ######### - name: Convert markdown to slack markdown uses: LoveToKnow/slackify-markdown-action@v1.0.0 id: issuemarkdown @@ -50,12 +53,39 @@ jobs: SLACK_TITLE: 🆘 New issue that requires TSC Members attention 🆘 SLACK_MESSAGE: ${{steps.issuemarkdown.outputs.text}} MSG_MINIMAL: true + ######### + # Handling Mailchimp notifications + ######### + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - name: Install deps + run: npm install + working-directory: ./.github/workflows/scripts/mailchimp + - name: Send email with MailChimp + uses: actions/github-script@v4 + env: + CALENDAR_ID: ${{ secrets.CALENDAR_ID }} + CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} + MAILCHIMP_API_KEY: ${{ secrets.MAILCHIMP_API_KEY }} + with: + script: | + const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); + sendEmail('${{github.event.issue.html_url}}'); pull_request: if: github.event_name == 'pull_request_target' && contains(github.event.pull_request.body, '@asyncapi/tsc_members') name: TSC notification on every new pull request runs-on: ubuntu-latest steps: + ######### + # Handling Slack notifications + ######### - name: Convert markdown to slack markdown uses: LoveToKnow/slackify-markdown-action@v1.0.0 id: prmarkdown @@ -68,12 +98,39 @@ jobs: SLACK_TITLE: 🆘 New PR that requires TSC Members attention 🆘 SLACK_MESSAGE: ${{steps.prmarkdown.outputs.text}} MSG_MINIMAL: true + ######### + # Handling Mailchimp notifications + ######### + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - name: Install deps + run: npm install + working-directory: ./.github/workflows/scripts/mailchimp + - name: Send email with MailChimp + uses: actions/github-script@v4 + env: + CALENDAR_ID: ${{ secrets.CALENDAR_ID }} + CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} + MAILCHIMP_API_KEY: ${{ secrets.MAILCHIMP_API_KEY }} + with: + script: | + const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); + sendEmail('${{github.event.pull_request.html_url}}'); discussion: if: github.event_name == 'discussion' && contains(github.event.discussion.body, '@asyncapi/tsc_members') name: TSC notification on every new discussion runs-on: ubuntu-latest steps: + ######### + # Handling Slack notifications + ######### - name: Convert markdown to slack markdown uses: LoveToKnow/slackify-markdown-action@v1.0.0 id: discussionmarkdown @@ -86,12 +143,39 @@ jobs: SLACK_TITLE: 🆘 New discussion that requires TSC Members attention 🆘 SLACK_MESSAGE: ${{steps.discussionmarkdown.outputs.text}} MSG_MINIMAL: true + ######### + # Handling Mailchimp notifications + ######### + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - name: Install deps + run: npm install + working-directory: ./.github/workflows/scripts/mailchimp + - name: Send email with MailChimp + uses: actions/github-script@v4 + env: + CALENDAR_ID: ${{ secrets.CALENDAR_ID }} + CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} + MAILCHIMP_API_KEY: ${{ secrets.MAILCHIMP_API_KEY }} + with: + script: | + const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); + sendEmail('${{github.event.discussion.html_url}}'); issue_comment: if: ${{ github.event_name == 'issue_comment' && !github.event.issue.pull_request && contains(github.event.comment.body, '@asyncapi/tsc_members') }} name: TSC notification on every new comment in issue runs-on: ubuntu-latest steps: + ######### + # Handling Slack notifications + ######### - name: Convert markdown to slack markdown uses: LoveToKnow/slackify-markdown-action@v1.0.0 id: issuemarkdown @@ -104,12 +188,39 @@ jobs: SLACK_TITLE: 🆘 New comment under existing issue that requires TSC Members attention 🆘 SLACK_MESSAGE: ${{steps.issuemarkdown.outputs.text}} MSG_MINIMAL: true + ######### + # Handling Mailchimp notifications + ######### + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - name: Install deps + run: npm install + working-directory: ./.github/workflows/scripts/mailchimp + - name: Send email with MailChimp + uses: actions/github-script@v4 + env: + CALENDAR_ID: ${{ secrets.CALENDAR_ID }} + CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} + MAILCHIMP_API_KEY: ${{ secrets.MAILCHIMP_API_KEY }} + with: + script: | + const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); + sendEmail('${{github.event.comment.html_url}}'); pr_comment: if: github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@asyncapi/tsc_members') name: TSC notification on every new comment in pr runs-on: ubuntu-latest steps: + ######### + # Handling Slack notifications + ######### - name: Convert markdown to slack markdown uses: LoveToKnow/slackify-markdown-action@v1.0.0 id: prmarkdown @@ -122,12 +233,39 @@ jobs: SLACK_TITLE: 🆘 New comment under existing PR that requires TSC Members attention 🆘 SLACK_MESSAGE: ${{steps.prmarkdown.outputs.text}} MSG_MINIMAL: true + ######### + # Handling Mailchimp notifications + ######### + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - name: Install deps + run: npm install + working-directory: ./.github/workflows/scripts/mailchimp + - name: Send email with MailChimp + uses: actions/github-script@v4 + env: + CALENDAR_ID: ${{ secrets.CALENDAR_ID }} + CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} + MAILCHIMP_API_KEY: ${{ secrets.MAILCHIMP_API_KEY }} + with: + script: | + const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); + sendEmail('${{github.event.comment.html_url}}'); discussion_comment: if: github.event_name == 'discussion_comment' && contains(github.event.comment.body, '@asyncapi/tsc_members') name: TSC notification on every new comment in discussion runs-on: ubuntu-latest steps: + ######### + # Handling Slack notifications + ######### - name: Convert markdown to slack markdown uses: LoveToKnow/slackify-markdown-action@v1.0.0 id: discussionmarkdown @@ -139,4 +277,28 @@ jobs: SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} SLACK_TITLE: 🆘 New comment under existing discussion that requires TSC Members attention 🆘 SLACK_MESSAGE: ${{steps.discussionmarkdown.outputs.text}} - MSG_MINIMAL: true \ No newline at end of file + MSG_MINIMAL: true + ######### + # Handling Mailchimp notifications + ######### + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - name: Install deps + run: npm install + working-directory: ./.github/workflows/scripts/mailchimp + - name: Send email with MailChimp + uses: actions/github-script@v4 + env: + CALENDAR_ID: ${{ secrets.CALENDAR_ID }} + CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} + MAILCHIMP_API_KEY: ${{ secrets.MAILCHIMP_API_KEY }} + with: + script: | + const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); + sendEmail('${{github.event.comment.html_url}}'); \ No newline at end of file diff --git a/.github/workflows/scripts/README.md b/.github/workflows/scripts/README.md new file mode 100644 index 00000000..ba97dca0 --- /dev/null +++ b/.github/workflows/scripts/README.md @@ -0,0 +1 @@ +The entire `scripts` directory is centrally managed in [.github](https://github.com/asyncapi/.github/) repository. Any changes in this folder should be done in central repository. \ No newline at end of file diff --git a/.github/workflows/scripts/mailchimp/htmlContent.js b/.github/workflows/scripts/mailchimp/htmlContent.js new file mode 100644 index 00000000..e3eadc97 --- /dev/null +++ b/.github/workflows/scripts/mailchimp/htmlContent.js @@ -0,0 +1,495 @@ +/** + * This code is centrally managed in https://github.com/asyncapi/.github/ + * Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + */ +module.exports = (link) => { + + return ` + + + + + + + + *|MC:SUBJECT|* + + + + + + +
+ + + + +
+ + + + + + + + + + + + +
+ + + + + +
+ + + + + + + + +
+ + Hey *|FNAME|*,
+
+There is a new topic at AsyncAPI Initiative that requires Technical Steering Committee attention. +
+Please have a look if it is just something you need to be aware of, or maybe your vote is needed. +
+Click here to see more details on GitHub. +
+ + + +
+ + + + + + + +
+ + + + + + + + +
+ + Cheers,
+AsyncAPI Initiative +
+ + + +
+ + + + + +
+ + + + + + + + +
+ + Want to change how you receive these emails?
+You can update your preferences or unsubscribe from this list.
+  +
+ + + +
+ + +
+
+ + +` +} \ No newline at end of file diff --git a/.github/workflows/scripts/mailchimp/index.js b/.github/workflows/scripts/mailchimp/index.js new file mode 100644 index 00000000..a7b6f78f --- /dev/null +++ b/.github/workflows/scripts/mailchimp/index.js @@ -0,0 +1,73 @@ +/** + * This code is centrally managed in https://github.com/asyncapi/.github/ + * Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo + */ +const mailchimp = require('@mailchimp/mailchimp_marketing'); +const core = require('@actions/core'); +const htmlContent = require('./htmlContent.js'); + +/** + * Sending API request to mailchimp to schedule email to subscribers + * Input is the URL to issue/discussion or other resource + */ +module.exports = async (link) => { + + let newCampaign; + + mailchimp.setConfig({ + apiKey: process.env.MAILCHIMP_API_KEY, + server: 'us12' + }); + + /* + * First we create campaign + */ + try { + newCampaign = await mailchimp.campaigns.create({ + type: 'regular', + recipients: { + list_id: '6e3e437abe', + segments_opts: { + saved_segment_id: 'tsc-voting-email' + } + }, + settings: { + subject_line: 'AsyncAPI TSC members attention required', + preview_text: 'Check out the latest topic that TSC members have to be aware of', + title: `New topic info - ${ new Date(Date.now()).toUTCString()}`, + from_name: 'AsyncAPI Initiative', + reply_to: 'info@asyncapi.io', + } + }); + } catch (error) { + return core.setFailed(`Failed creating campaign: ${ JSON.stringify(error) }`); + } + + /* + * Content of the email is added separately after campaign creation + */ + try { + await mailchimp.campaigns.setContent(newCampaign.id, { html: htmlContent(link) }); + } catch (error) { + return core.setFailed(`Failed adding content to campaign: ${ JSON.stringify(error) }`); + } + + /* + * We schedule an email to send it immediately + */ + try { + //schedule for next hour + //so if this code was created by new issue creation at 9:46, the email is scheduled for 10:00 + //is it like this as schedule has limitiations and you cannot schedule email for 9:48 + const scheduleDate = new Date(Date.parse(new Date(Date.now()).toISOString()) + 1 * 1 * 60 * 60 * 1000); + scheduleDate.setUTCMinutes(00); + + await mailchimp.campaigns.schedule(newCampaign.id, { + schedule_time: scheduleDate.toISOString(), + }); + } catch (error) { + return core.setFailed(`Failed scheduling email: ${ JSON.stringify(error) }`); + } + + core.info(`New email campaign created`); +} \ No newline at end of file diff --git a/.github/workflows/scripts/mailchimp/package-lock.json b/.github/workflows/scripts/mailchimp/package-lock.json new file mode 100644 index 00000000..7ee7d84f --- /dev/null +++ b/.github/workflows/scripts/mailchimp/package-lock.json @@ -0,0 +1,597 @@ +{ + "name": "schedule-email", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "schedule-email", + "license": "Apache 2.0", + "dependencies": { + "@actions/core": "1.6.0", + "@mailchimp/mailchimp_marketing": "3.0.74" + } + }, + "node_modules/@actions/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz", + "integrity": "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw==", + "dependencies": { + "@actions/http-client": "^1.0.11" + } + }, + "node_modules/@actions/http-client": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz", + "integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==", + "dependencies": { + "tunnel": "0.0.6" + } + }, + "node_modules/@mailchimp/mailchimp_marketing": { + "version": "3.0.74", + "resolved": "https://registry.npmjs.org/@mailchimp/mailchimp_marketing/-/mailchimp_marketing-3.0.74.tgz", + "integrity": "sha512-039iu4GRr7wpXqweBLuS05wvOBtPxSa31cjxgftBYSt7031f0sHEi8Up2DicfbSuQK0AynPDeVyuxrb31Lx+yQ==", + "dependencies": { + "dotenv": "^8.2.0", + "superagent": "3.8.1" + }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "node_modules/cookiejar": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", + "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==", + "engines": { + "node": ">=10" + } + }, + "node_modules/extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "node_modules/form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" + } + }, + "node_modules/formidable": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", + "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==", + "deprecated": "Please upgrade to latest, formidable@v2 or formidable@v3! Check these notes: https://bit.ly/2ZEqIau", + "funding": { + "url": "https://ko-fi.com/tunnckoCore/commissions" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "node_modules/qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "node_modules/superagent": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.1.tgz", + "integrity": "sha512-VMBFLYgFuRdfeNQSMLbxGSLfmXL/xc+OO+BZp41Za/NRDBet/BNbkRJrYzCUu0u4GU0i/ml2dtT8b9qgkw9z6Q==", + "deprecated": "Please upgrade to v7.0.2+ of superagent. We have fixed numerous issues with streams, form-data, attach(), filesystem errors not bubbling up (ENOENT on attach()), and all tests are now passing. See the releases tab for more information at .", + "dependencies": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.1.1", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.0.5" + }, + "engines": { + "node": ">= 4.0" + } + }, + "node_modules/superagent/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + } + }, + "dependencies": { + "@actions/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.6.0.tgz", + "integrity": "sha512-NB1UAZomZlCV/LmJqkLhNTqtKfFXJZAUPcfl/zqG7EfsQdeUJtaWO98SGbuQ3pydJ3fHl2CvI/51OKYlCYYcaw==", + "requires": { + "@actions/http-client": "^1.0.11" + } + }, + "@actions/http-client": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz", + "integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==", + "requires": { + "tunnel": "0.0.6" + } + }, + "@mailchimp/mailchimp_marketing": { + "version": "3.0.74", + "resolved": "https://registry.npmjs.org/@mailchimp/mailchimp_marketing/-/mailchimp_marketing-3.0.74.tgz", + "integrity": "sha512-039iu4GRr7wpXqweBLuS05wvOBtPxSa31cjxgftBYSt7031f0sHEi8Up2DicfbSuQK0AynPDeVyuxrb31Lx+yQ==", + "requires": { + "dotenv": "^8.2.0", + "superagent": "3.8.1" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==" + }, + "cookiejar": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz", + "integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ==" + }, + "core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "dotenv": { + "version": "8.6.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-8.6.0.tgz", + "integrity": "sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g==" + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "formidable": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/formidable/-/formidable-1.2.6.tgz", + "integrity": "sha512-KcpbcpuLNOwrEjnbpMC0gS+X8ciDoZE1kkqzat4a8vrprf+s9pKNQ/QIwWfbfs4ltgmFl3MD177SNTkve3BwGQ==" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "object-inspect": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.0.tgz", + "integrity": "sha512-Ho2z80bVIvJloH+YzRmpZVQe87+qASmBUKZDWgx9cu+KDrX2ZDH/3tMy+gXbZETVGs2M8YdxObOh7XAtim9Y0g==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" + }, + "qs": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz", + "integrity": "sha512-wr7M2E0OFRfIfJZjKGieI8lBKb7fRCH4Fv5KNPEs7gJ8jadvotdsS08PzOKR7opXhZ/Xkjtt3WF9g38drmyRqQ==", + "requires": { + "side-channel": "^1.0.4" + } + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } + } + }, + "superagent": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/superagent/-/superagent-3.8.1.tgz", + "integrity": "sha512-VMBFLYgFuRdfeNQSMLbxGSLfmXL/xc+OO+BZp41Za/NRDBet/BNbkRJrYzCUu0u4GU0i/ml2dtT8b9qgkw9z6Q==", + "requires": { + "component-emitter": "^1.2.0", + "cookiejar": "^2.1.0", + "debug": "^3.1.0", + "extend": "^3.0.0", + "form-data": "^2.3.1", + "formidable": "^1.1.1", + "methods": "^1.1.1", + "mime": "^1.4.1", + "qs": "^6.5.1", + "readable-stream": "^2.0.5" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "tunnel": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", + "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + } + } +} \ No newline at end of file diff --git a/.github/workflows/scripts/mailchimp/package.json b/.github/workflows/scripts/mailchimp/package.json new file mode 100644 index 00000000..cc50e43e --- /dev/null +++ b/.github/workflows/scripts/mailchimp/package.json @@ -0,0 +1,9 @@ +{ + "name": "schedule-email", + "description": "This code is responsible for scheduling an email campaign. This file is centrally managed in https://github.com/asyncapi/.github/", + "license": "Apache 2.0", + "dependencies": { + "@actions/core": "1.6.0", + "@mailchimp/mailchimp_marketing": "3.0.74" + } +} \ No newline at end of file From 37cdb1db7f1377d4b9f347dd68cf2fcb9b8b58cc Mon Sep 17 00:00:00 2001 From: Dharina Hanumunthadu Date: Fri, 4 Nov 2022 08:59:39 -0400 Subject: [PATCH 68/91] feat: solace bindings new properties (#165) --- solace/README.md | 30 +++++++++++++++--------------- solace/json_schemas/operation.json | 8 ++++++++ solace/json_schemas/server.json | 2 +- 3 files changed, 24 insertions(+), 16 deletions(-) diff --git a/solace/README.md b/solace/README.md index a231620e..56d1a404 100644 --- a/solace/README.md +++ b/solace/README.md @@ -6,7 +6,7 @@ This document defines how to describe Solace-specific information with AsyncAPI. ## Version -Current version is `0.2.0`. +Current version is `0.3.0`. @@ -14,7 +14,7 @@ Current version is `0.2.0`. Field Name | Type | Description ---|---|--- -`bindingVersion`|String|The current version is 0.2.0 +`bindingVersion`|String|The current version is 0.3.0 `msgVpn`|String|The Virtual Private Network name on the Solace broker. @@ -34,23 +34,23 @@ We need the ability to support several bindings for each operation, see the [Exa Field Name | Type | Description ---|---|--- -`bindingVersion`|String|The current version is 0.2.0 +`bindingVersion`|String|The current version is 0.3.0 `destinations`|List of Destination Objects|Destination Objects are described next. ### Destination Object Each destination has the following structure: -Field Name | Type | Description ----|---|---|--- -`destinationType`|Enum|'queue' or 'topic'. If the type is queue, then the subscriber can bind to the queue, which in turn will subscribe to the topic as represented by the channel name or to the provided topicSubscriptions. -`deliveryMode`|Enum|'direct' or 'persistent'. This determines the quality of service for publishing messages as documented [here.](https://docs.solace.com/PubSub-Basics/Core-Concepts-Message-Delivery-Modes.htm) Default is 'persistent'. -`queue.name`|String|The name of the queue, only applicable when destinationType is 'queue'. -`queue.topicSubscriptions`|List of String|A list of topics that the queue subscribes to, only applicable when destinationType is 'queue'. If none is given, the queue subscribes to the topic as represented by the channel name. -`queue.accessType`|Enum|'exclusive' or 'nonexclusive'. This is documented [here.](https://docs.solace.com/PubSub-Basics/Endpoints.htm) Only applicable when destinationType is 'queue'. -`topic.topicSubscriptions`|List of String|A list of topics that the client subscribes to, only applicable when destinationType is 'topic'. If none is given, the client subscribes to the topic as represented by the channel name. - - +| Field Name | Type | Description | +| -------------------------- | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| `destinationType` | Enum | 'queue' or 'topic'. If the type is queue, then the subscriber can bind to the queue, which in turn will subscribe to the topic as represented by the channel name or to the provided topicSubscriptions. | +| `deliveryMode` | Enum | 'direct' or 'persistent'. This determines the quality of service for publishing messages as documented [here.](https://docs.solace.com/Get-Started/Core-Concepts-Message-Delivery-Modes.htm) Default is 'persistent'. | +| `queue.name` | String | The name of the queue, only applicable when destinationType is 'queue'. | +| `queue.topicSubscriptions` | List of String | A list of topics that the queue subscribes to, only applicable when destinationType is 'queue'. If none is given, the queue subscribes to the topic as represented by the channel name. | +| `queue.accessType` | Enum | 'exclusive' or 'nonexclusive'. This is documented [here.](https://docs.solace.com/Messaging/Guaranteed-Msg/Endpoints.htm#Queues) Only applicable when destinationType is 'queue'. | +| `queue.maxMsgSpoolSize` | String | The maximum amount of message spool that the given queue may use. This is documented [here.](https://docs.solace.com/Messaging/Guaranteed-Msg/Message-Spooling.htm#max-spool-usage) Only applicable when destinationType is 'queue'. | +| `queue.maxTtl` | String | The maximum TTL to apply to messages to be spooled. This is documented [here.](https://docs.solace.com/Messaging/Guaranteed-Msg/Configuring-Queues.htm) Only applicable when destinationType is 'queue'. | +| `topic.topicSubscriptions` | List of String | A list of topics that the client subscribes to, only applicable when destinationType is 'topic'. If none is given, the client subscribes to the topic as represented by the channel name. | @@ -92,7 +92,7 @@ channels: publish: bindings: solace: - bindingVersion: 0.2.0 + bindingVersion: 0.3.0 destinations: - destinationType: queue queue: @@ -142,7 +142,7 @@ channels: publish: bindings: solace: - bindingVersion: 0.2.0 + bindingVersion: 0.3.0 destinations: - destinationType: topic topicSubscriptions: diff --git a/solace/json_schemas/operation.json b/solace/json_schemas/operation.json index f8382fc9..60e59fc3 100644 --- a/solace/json_schemas/operation.json +++ b/solace/json_schemas/operation.json @@ -48,6 +48,14 @@ "exclusive", "nonexclusive" ] + }, + "maxTtl": { + "type": "string", + "description": "The maximum TTL to apply to messages to be spooled." + }, + "maxMsgSpoolUsage": { + "type": "string", + "description": "The maximum amount of message spool that the given queue may use" } } } diff --git a/solace/json_schemas/server.json b/solace/json_schemas/server.json index 06f12acb..e42c9d55 100644 --- a/solace/json_schemas/server.json +++ b/solace/json_schemas/server.json @@ -11,7 +11,7 @@ } }, "properties": { - "msvVpn": { + "msgVpn": { "type": "string", "description": "The name of the Virtual Private Network to connect to on the Solace broker." }, From 24d3f7467350fc9f194864eb89ad2ff86eb7d517 Mon Sep 17 00:00:00 2001 From: Tenshi Codes <116377630+codingtenshi@users.noreply.github.com> Date: Tue, 8 Nov 2022 15:51:39 +0100 Subject: [PATCH 69/91] ci: remove not needed workflow (#168) --- .github/workflows/bump.yml | 34 -------- .github/workflows/if-go-pr-testing.yml | 69 ---------------- .github/workflows/if-nodejs-pr-testing.yml | 61 -------------- .github/workflows/if-nodejs-release.yml | 86 -------------------- .github/workflows/if-nodejs-version-bump.yml | 49 ----------- 5 files changed, 299 deletions(-) delete mode 100644 .github/workflows/bump.yml delete mode 100644 .github/workflows/if-go-pr-testing.yml delete mode 100644 .github/workflows/if-nodejs-pr-testing.yml delete mode 100644 .github/workflows/if-nodejs-release.yml delete mode 100644 .github/workflows/if-nodejs-version-bump.yml diff --git a/.github/workflows/bump.yml b/.github/workflows/bump.yml deleted file mode 100644 index 68daa7c0..00000000 --- a/.github/workflows/bump.yml +++ /dev/null @@ -1,34 +0,0 @@ -# This action is centrally managed in https://github.com/asyncapi/.github/ -# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo - -# Purpose of this action is to update npm package in libraries that use it. It is like dependabot for asyncapi npm modules only. -# It runs in a repo after merge of release commit and searches for other packages that use released package. Every found package gets updated with lates version - -name: Bump package version in dependent repos - if Node project - -on: - # It cannot run on release event as when release is created then version is not yet bumped in package.json - # This means we cannot extract easily latest version and have a risk that package is not yet on npm - push: - branches: - - master - -jobs: - bump-in-dependent-projects: - name: Bump this package in repositories that depend on it - if: startsWith(github.event.commits[0].message, 'chore(release):') - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v2 - - name: Check if Node.js project and has package.json - id: packagejson - run: test -e ./package.json && echo "::set-output name=exists::true" || echo "::set-output name=exists::false" - - if: steps.packagejson.outputs.exists == 'true' - name: Bumping latest version of this package in other repositories - uses: derberg/npm-dependency-manager-for-your-github-org@v4 - with: - github_token: ${{ secrets.GH_TOKEN }} - committer_username: asyncapi-bot - committer_email: info@asyncapi.io - repos_to_ignore: html-template # this is temporary until react component releases 1.0, then it can be removed diff --git a/.github/workflows/if-go-pr-testing.yml b/.github/workflows/if-go-pr-testing.yml deleted file mode 100644 index 606e689b..00000000 --- a/.github/workflows/if-go-pr-testing.yml +++ /dev/null @@ -1,69 +0,0 @@ -# This action is centrally managed in https://github.com/asyncapi/.github/ -# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo - -# It does magic only if there is go.mod file in the root of the project -name: PR testing - if Go project - -on: - pull_request: - types: [opened, reopened, synchronize, ready_for_review] - -jobs: - lint-go-pr: - name: Lint Go PR - runs-on: ubuntu-latest - steps: - - if: "github.event.pull_request.draft == false &&!((github.actor == 'asyncapi-bot' && startsWith(github.event.pull_request.title, 'ci: update global workflows')) || (github.actor == 'asyncapi-bot' && startsWith(github.event.pull_request.title, 'chore(release):')) || (github.actor == 'allcontributors' && startsWith(github.event.pull_request.title, 'docs: add')))" - id: should_run - name: Should Run - run: echo "::set-output name=shouldrun::true" - - if: steps.should_run.outputs.shouldrun == 'true' - name: Checkout repository - uses: actions/checkout@v2 - - if: steps.should_run.outputs.shouldrun == 'true' - name: Check if Go project and has go.mod - id: gomod - run: test -e ./go.mod && echo "::set-output name=exists::true" || echo "::set-output name=exists::false" - shell: bash - - if: steps.gomod.outputs.exists == 'true' - name: Setup Go - uses: actions/setup-go@v2.1.3 - with: - go-version: 1.16 - - if: steps.gomod.outputs.exists == 'true' - name: golangci-lint - uses: golangci/golangci-lint-action@v2 # golangci-lint version extracted from go.mod. `latest` if missing. - with: - skip-go-installation: true # we wanna control the version of Go in use - - test-go-pr: - name: Test Go PR - ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - steps: - - if: "github.event.pull_request.draft == false &&!((github.actor == 'asyncapi-bot' && startsWith(github.event.pull_request.title, 'ci: update global workflows')) || (github.actor == 'asyncapi-bot' && startsWith(github.event.pull_request.title, 'chore(release):')) || (github.actor == 'allcontributors' && startsWith(github.event.pull_request.title, 'docs: add')))" - id: should_run - name: Should Run - run: echo "::set-output name=shouldrun::true" - - if: steps.should_run.outputs.shouldrun == 'true' - name: Checkout repository - uses: actions/checkout@v2 - - if: steps.should_run.outputs.shouldrun == 'true' - name: Check if Go project and has go.mod - id: gomod - run: test -e ./go.mod && echo "::set-output name=exists::true" || echo "::set-output name=exists::false" - shell: bash - - if: steps.gomod.outputs.exists == 'true' - name: Setup Go - uses: actions/setup-go@v2.1.3 - with: - go-version: 1.16 - - if: steps.gomod.outputs.exists == 'true' - name: Build - run: go build -v ./... - - if: steps.gomod.outputs.exists == 'true' - name: Test - run: go test -v ./... - diff --git a/.github/workflows/if-nodejs-pr-testing.yml b/.github/workflows/if-nodejs-pr-testing.yml deleted file mode 100644 index 1dcccd32..00000000 --- a/.github/workflows/if-nodejs-pr-testing.yml +++ /dev/null @@ -1,61 +0,0 @@ -# This action is centrally managed in https://github.com/asyncapi/.github/ -# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo - -# It does magic only if there is package.json file in the root of the project -name: PR testing - if Node project - -on: - pull_request: - types: [opened, reopened, synchronize, ready_for_review] - -jobs: - test-nodejs-pr: - name: Test NodeJS PR - ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - steps: - - if: "github.event.pull_request.draft == false &&!((github.actor == 'asyncapi-bot' && startsWith(github.event.pull_request.title, 'ci: update global workflows')) || (github.actor == 'asyncapi-bot' && startsWith(github.event.pull_request.title, 'chore(release):')) || (github.actor == 'allcontributors' && startsWith(github.event.pull_request.title, 'docs: add')))" - id: should_run - name: Should Run - run: echo "::set-output name=shouldrun::true" - - if: steps.should_run.outputs.shouldrun == 'true' - name: Set git to use LF #to once and for all finish neverending fight between Unix and Windows - run: | - git config --global core.autocrlf false - git config --global core.eol lf - - if: steps.should_run.outputs.shouldrun == 'true' - name: Checkout repository - uses: actions/checkout@v2 - - if: steps.should_run.outputs.shouldrun == 'true' - name: Check if Node.js project and has package.json - id: packagejson - run: test -e ./package.json && echo "::set-output name=exists::true" || echo "::set-output name=exists::false" - shell: bash - - if: steps.packagejson.outputs.exists == 'true' - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: 14 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - - if: steps.packagejson.outputs.exists == 'true' - name: Install dependencies - id: first-installation - run: npm install --loglevel verbose - continue-on-error: true - - if: steps.first-installation.outputs.status == 'failure' && steps.packagejson.outputs.exists == 'true' - name: Clear NPM cache and install deps again - run: | - npm cache clean --force - npm install --loglevel verbose - - if: steps.packagejson.outputs.exists == 'true' - name: Test - run: npm test - - if: steps.packagejson.outputs.exists == 'true' - name: Run linter - run: npm run lint - - if: steps.packagejson.outputs.exists == 'true' - name: Run release assets generation to make sure PR does not break it - run: npm run generate:assets diff --git a/.github/workflows/if-nodejs-release.yml b/.github/workflows/if-nodejs-release.yml deleted file mode 100644 index bc5b5376..00000000 --- a/.github/workflows/if-nodejs-release.yml +++ /dev/null @@ -1,86 +0,0 @@ -# This action is centrally managed in https://github.com/asyncapi/.github/ -# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo - -# It does magic only if there is package.json file in the root of the project -name: Release - if Node project - -on: - push: - branches: - - master - # below lines are not enough to have release supported for these branches - # make sure configuration of `semantic-release` package mentiones these branches - - next - - next-major - - beta - - alpha - - '**-release' # custom - -jobs: - - test-nodejs: - name: Test NodeJS release on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, macos-latest, windows-latest] - steps: - - name: Set git to use LF #to once and for all finish neverending fight between Unix and Windows - run: | - git config --global core.autocrlf false - git config --global core.eol lf - - name: Checkout repository - uses: actions/checkout@v2 - - name: Check if Node.js project and has package.json - id: packagejson - run: test -e ./package.json && echo "::set-output name=exists::true" || echo "::set-output name=exists::false" - shell: bash - - if: steps.packagejson.outputs.exists == 'true' - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: 14 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - - if: steps.packagejson.outputs.exists == 'true' - name: Install dependencies - run: npm install - - if: steps.packagejson.outputs.exists == 'true' - name: Run test - run: npm test - - release: - needs: [test-nodejs] - name: Publish to any of NPM, Github, and Docker Hub - runs-on: ubuntu-latest - steps: - - name: Set git to use LF #to once and for all finish neverending fight between Unix and Windows - run: | - git config --global core.autocrlf false - git config --global core.eol lf - - name: Checkout repository - uses: actions/checkout@v2 - - name: Check if Node.js project and has package.json - id: packagejson - run: test -e ./package.json && echo "::set-output name=exists::true" || echo "::set-output name=exists::false" - - if: steps.packagejson.outputs.exists == 'true' - name: Setup Node.js - uses: actions/setup-node@v1 - with: - node-version: 14 - - if: steps.packagejson.outputs.exists == 'true' - name: Install dependencies - run: npm install - - if: steps.packagejson.outputs.exists == 'true' - name: Publish to any of NPM, Github, and Docker Hub - id: release - env: - GITHUB_TOKEN: ${{ secrets.GH_TOKEN }} - NPM_TOKEN: ${{ secrets.NPM_TOKEN }} - DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} - DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} - GIT_AUTHOR_NAME: asyncapi-bot - GIT_AUTHOR_EMAIL: info@asyncapi.io - GIT_COMMITTER_NAME: asyncapi-bot - GIT_COMMITTER_EMAIL: info@asyncapi.io - run: npm run release diff --git a/.github/workflows/if-nodejs-version-bump.yml b/.github/workflows/if-nodejs-version-bump.yml deleted file mode 100644 index 721caa9d..00000000 --- a/.github/workflows/if-nodejs-version-bump.yml +++ /dev/null @@ -1,49 +0,0 @@ -# This action is centrally managed in https://github.com/asyncapi/.github/ -# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo - -# It does magic only if there is package.json file in the root of the project -name: Version bump - if Node.js project - -on: - release: - types: - - published - -jobs: - version_bump: - name: Generate assets and bump NodeJS - runs-on: ubuntu-latest - steps: - - name: Checkout repository - uses: actions/checkout@v2 - with: - # target branch of release. More info https://docs.github.com/en/rest/reference/repos#releases - # in case release is created from release branch then we need to checkout from given branch - # if @semantic-release/github is used to publish, the minimum version is 7.2.0 for proper working - ref: ${{ github.event.release.target_commitish }} - - name: Check if Node.js project and has package.json - id: packagejson - run: test -e ./package.json && echo "::set-output name=exists::true" || echo "::set-output name=exists::false" - - if: steps.packagejson.outputs.exists == 'true' - name: Install dependencies - run: npm install - - if: steps.packagejson.outputs.exists == 'true' - name: Assets generation - run: npm run generate:assets - - if: steps.packagejson.outputs.exists == 'true' - name: Bump version in package.json - # There is no need to substract "v" from the tag as version script handles it - # When adding "bump:version" script in package.json, make sure no tags are added by default (--no-git-tag-version) as they are already added by release workflow - # When adding "bump:version" script in package.json, make sure --allow-same-version is set in case someone forgot and updated package.json manually and we want to avoide this action to fail and raise confusion - run: VERSION=${{github.event.release.tag_name}} npm run bump:version - - if: steps.packagejson.outputs.exists == 'true' - name: Create Pull Request with updated asset files including package.json - uses: peter-evans/create-pull-request@v3 - with: - token: ${{ secrets.GH_TOKEN }} - commit-message: 'chore(release): ${{github.event.release.tag_name}}' - committer: asyncapi-bot - author: asyncapi-bot - title: 'chore(release): ${{github.event.release.tag_name}}' - body: 'Version bump in package.json for release [${{github.event.release.tag_name}}](${{github.event.release.html_url}})' - branch: version-bump/${{github.event.release.tag_name}} \ No newline at end of file From f3bfe694ac2354e746929ce002441ceb7bef50f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Gorej?= Date: Wed, 9 Nov 2022 15:26:55 +0100 Subject: [PATCH 70/91] docs: align requirement wording with RFC 2119 (#139) Co-authored-by: Lukasz Gornicki --- anypointmq/README.md | 18 +++++++++--------- http/README.md | 2 +- mqtt/README.md | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/anypointmq/README.md b/anypointmq/README.md index 196df062..a56540a4 100644 --- a/anypointmq/README.md +++ b/anypointmq/README.md @@ -27,10 +27,10 @@ The fields of the standard [Server Object](https://github.com/asyncapi/spec/blob Server Object Field Name | Values for Anypoint MQ Protocol | Description ---|:---|:--- -`protocol` | `anypointmq` | **Required**. MUST be `anypointmq` for the scope of this specification. -`url` | e.g., `https://mq-us-east-1.anypoint.mulesoft.com/api` | **Required**. MUST be the endpoint URL of the Anypoint MQ Broker REST API _excluding_ the final major version indicator (e.g., `v1`). Valid examples are `https://mq-us-east-1.anypoint.mulesoft.com/api` and `https://mq-eu-central-1.eu1.anypoint.mulesoft.com/api` (and _not_ `https://.../api/v1`). -`protocolVersion` | e.g., `v1` | **Optional**, defaults to `v1`. If present MUST be the major version indicator of the Anypoint MQ Broker REST API omitted from the `url`, e.g. `v1`. -`security` | suitably configured OAuth 2.0 client credentials grant type | **Required**. Authentication against the MuleSoft-hosted Anypoint MQ message brokers uses the OAuth 2.0 client credentials grant type. At runtime, the client ID and client secret values of an Anypoint MQ client app must be supplied. Also, the OAuth 2.0 scopes are currently not client-configurable. The `security` field of the server object MUST correctly match these constraints. +`protocol` | `anypointmq` | **REQUIRED**. MUST be `anypointmq` for the scope of this specification. +`url` | e.g., `https://mq-us-east-1.anypoint.mulesoft.com/api` | **REQUIRED**. MUST be the endpoint URL of the Anypoint MQ Broker REST API _excluding_ the final major version indicator (e.g., `v1`). Valid examples are `https://mq-us-east-1.anypoint.mulesoft.com/api` and `https://mq-eu-central-1.eu1.anypoint.mulesoft.com/api` (and _not_ `https://.../api/v1`). +`protocolVersion` | e.g., `v1` | **OPTIONAL**, defaults to `v1`. If present MUST be the major version indicator of the Anypoint MQ Broker REST API omitted from the `url`, e.g. `v1`. +`security` | suitably configured OAuth 2.0 client credentials grant type | **REQUIRED**. Authentication against the MuleSoft-hosted Anypoint MQ message brokers uses the OAuth 2.0 client credentials grant type. At runtime, the client ID and client secret values of an Anypoint MQ client app must be supplied. Also, the OAuth 2.0 scopes are currently not client-configurable. The `security` field of the server object MUST correctly match these constraints. Note that the choice of a particular Anypoint MQ client app (via its client ID and secret) decides the Anypoint Platform organization ID and environment (ID) to be those in which this client app is defined. See the [Anypoint MQ documentation](https://docs.mulesoft.com/mq/mq-client-apps) for details on how to configure Anypoint MQ client apps. @@ -46,9 +46,9 @@ The Anypoint MQ [Channel Binding Object](https://github.com/asyncapi/spec/blob/m Field Name | Type | Description ---|:---:|--- -`destination` | string | **Optional**, defaults to the channel name. The destination (queue or exchange) name for this channel. SHOULD only be specified if the channel name differs from the actual destination name, such as when the channel name is not a valid destination name in Anypoint MQ. -`destinationType` | string | **Optional**, defaults to `queue`. The type of destination, which MUST be either `exchange` or `queue` or `fifo-queue`. SHOULD be specified to document the messaging model (publish/subscribe, point-to-point, strict message ordering) supported by this channel. -`bindingVersion` | string | **Optional**, defaults to `latest`. The version of this binding. +`destination` | string | **OPTIONAL**, defaults to the channel name. The destination (queue or exchange) name for this channel. SHOULD only be specified if the channel name differs from the actual destination name, such as when the channel name is not a valid destination name in Anypoint MQ. +`destinationType` | string | **OPTIONAL**, defaults to `queue`. The type of destination, which MUST be either `exchange` or `queue` or `fifo-queue`. SHOULD be specified to document the messaging model (publish/subscribe, point-to-point, strict message ordering) supported by this channel. +`bindingVersion` | string | **OPTIONAL**, defaults to `latest`. The version of this binding. Note that an Anypoint MQ exchange can only be sent to, not received from. To receive messages sent to an exchange, [an intermediary queue must be defined and bound to the exchange](https://docs.mulesoft.com/mq/mq-understanding#message-exchanges). In this bindings specification, these intermediary queues are not exposed in the AsyncAPI document. Instead, it is simply assumed that whenever messages must be received from an exchange, such an intermediary queue is involved yet invisible in the AsyncAPI document. @@ -89,8 +89,8 @@ The Anypoint MQ [Message Binding Object](https://github.com/asyncapi/spec/blob/m Field Name | Type | Description ---|:---:|--- -`headers` | [Schema Object](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#schemaObject) | **Optional**. A Schema object containing the definitions for Anypoint MQ-specific headers (so-called protocol headers). This schema MUST be of type `object` and have a `properties` key. Examples of Anypoint MQ protocol headers are `messageId` and `messageGroupId`. -`bindingVersion` | string | **Optional**, defaults to `latest`. The version of this binding. +`headers` | [Schema Object](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#schemaObject) | **OPTIONAL**. A Schema object containing the definitions for Anypoint MQ-specific headers (so-called protocol headers). This schema MUST be of type `object` and have a `properties` key. Examples of Anypoint MQ protocol headers are `messageId` and `messageGroupId`. +`bindingVersion` | string | **OPTIONAL**, defaults to `latest`. The version of this binding. Note that application headers must be specified in the [`headers` field of the standard Message Object](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#messageObjectHeaders) and are transmitted in the [`properties` section of the Anypoint MQ message](https://anypoint.mulesoft.com/exchange/portals/anypoint-platform/f1e97bc6-315a-4490-82a7-23abe036327a.anypoint-platform/anypoint-mq-broker/). In contrast, protocol headers such as `messageId` must be specified in the [`headers` field of the message binding object](#messageBindingObjectHeaders) and are transmitted in the [`headers` section of the Anypoint MQ message](https://anypoint.mulesoft.com/exchange/portals/anypoint-platform/f1e97bc6-315a-4490-82a7-23abe036327a.anypoint-platform/anypoint-mq-broker/). diff --git a/http/README.md b/http/README.md index e036b4b5..fbdda833 100644 --- a/http/README.md +++ b/http/README.md @@ -33,7 +33,7 @@ This object MUST NOT contain any properties. Its name is reserved for future use Field Name | Type | Description ---|:---:|--- -`type` | string | **Required**. Type of operation. Its value MUST be either `request` or `response`. +`type` | string | **REQUIRED**. Type of operation. Its value MUST be either `request` or `response`. `method` | string | When `type` is `request`, this is the HTTP method, otherwise it MUST be ignored. Its value MUST be one of `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`, `CONNECT`, and `TRACE`. `query` | [Schema Object][schemaObject] | A Schema object containing the definitions for each query parameter. This schema MUST be of type `object` and have a `properties` key. `bindingVersion` | string | The version of this binding. If omitted, "latest" MUST be assumed. diff --git a/mqtt/README.md b/mqtt/README.md index f1bed8a3..0161fdb9 100644 --- a/mqtt/README.md +++ b/mqtt/README.md @@ -20,7 +20,7 @@ This object contains information about the server representation in MQTT. Field Name | Type | Description ---|:---:|--- `clientId` | string | The client identifier. -`cleanSession` | boolean | Whether to create a persisten connection or not. When `false`, the connection will be persistent. +`cleanSession` | boolean | Whether to create a persistent connection or not. When `false`, the connection will be persistent. `lastWill` | object | Last Will and Testament configuration. `lastWill.topic` | string | The topic where the Last Will and Testament message will be sent. `lastWill.qos` | integer | Defines how hard the broker/client will try to ensure that the Last Will and Testament message is received. Its value MUST be either 0, 1 or 2. From 775b3dc8db9b94afd13ce2e87145d3f9411348b5 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Wed, 9 Nov 2022 19:42:09 +0100 Subject: [PATCH 71/91] ci: update generic workflows (#170) --- ...d-ready-to-merge-or-do-not-merge-label.yml | 17 +- ...ns-remove-ready-to-merge-label-on-edit.yml | 5 +- .../workflows/issues-prs-notifications.yml | 104 ++-- .github/workflows/link-check-cron.yml | 38 +- .github/workflows/link-check-pr.yml | 27 +- .github/workflows/lint-pr-title.yml | 36 +- .../workflows/notify-tsc-members-mention.yml | 575 +++++++++--------- 7 files changed, 397 insertions(+), 405 deletions(-) diff --git a/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml b/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml index e00a45fa..79c8079e 100644 --- a/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml +++ b/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml @@ -7,14 +7,14 @@ name: Label PRs # if proper comment added -on: - issue_comment: - types: - - created +on: + issue_comment: + types: + - created jobs: add-ready-to-merge-label: - if: > + if: > github.event.issue.pull_request && github.event.issue.state != 'closed' && github.actor != 'asyncapi-bot' && @@ -25,7 +25,6 @@ jobs: runs-on: ubuntu-latest steps: - - name: Add ready-to-merge label uses: actions/github-script@v5 with: @@ -64,7 +63,7 @@ jobs: Thanks 😄` }) } - + add-do-not-merge-label: if: > github.event.issue.pull_request && @@ -86,7 +85,7 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, labels: ['do-not-merge'] - }) + }) add-autoupdate-label: if: > github.event.issue.pull_request && @@ -108,4 +107,4 @@ jobs: owner: context.repo.owner, repo: context.repo.repo, labels: ['autoupdate'] - }) + }) diff --git a/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml b/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml index f38296c8..b8fc9904 100644 --- a/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml +++ b/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml @@ -22,14 +22,11 @@ jobs: script: | const labelToRemove = 'ready-to-merge'; const labels = context.payload.pull_request.labels; - const isLabelPresent = labels.some(label => label.name === labelToRemove) - if(!isLabelPresent) return; - github.rest.issues.removeLabel({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, name: labelToRemove - }) + }) diff --git a/.github/workflows/issues-prs-notifications.yml b/.github/workflows/issues-prs-notifications.yml index 576b2bac..ca665404 100644 --- a/.github/workflows/issues-prs-notifications.yml +++ b/.github/workflows/issues-prs-notifications.yml @@ -5,7 +5,6 @@ name: Notify slack on: - issues: types: [opened, reopened] @@ -16,57 +15,56 @@ on: types: [created] jobs: + issue: + if: github.event_name == 'issues' && github.actor != 'asyncapi-bot' && github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]' + name: Notify slack on every new issue + runs-on: ubuntu-latest + steps: + - name: Convert markdown to slack markdown for issue + uses: LoveToKnow/slackify-markdown-action@v1.0.0 + id: issuemarkdown + with: + text: "[${{github.event.issue.title}}](${{github.event.issue.html_url}}) \n ${{github.event.issue.body}}" + - name: Send info about issue + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{secrets.SLACK_GITHUB_NEWISSUEPR}} + SLACK_TITLE: 🐛 New Issue in ${{github.repository}} 🐛 + SLACK_MESSAGE: ${{steps.issuemarkdown.outputs.text}} + MSG_MINIMAL: true - issue: - if: github.event_name == 'issues' && github.actor != 'asyncapi-bot' && github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]' - name: Notify slack on every new issue - runs-on: ubuntu-latest - steps: - - name: Convert markdown to slack markdown for issue - uses: LoveToKnow/slackify-markdown-action@v1.0.0 - id: issuemarkdown - with: - text: "[${{github.event.issue.title}}](${{github.event.issue.html_url}}) \n ${{github.event.issue.body}}" - - name: Send info about issue - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_WEBHOOK: ${{secrets.SLACK_GITHUB_NEWISSUEPR}} - SLACK_TITLE: 🐛 New Issue in ${{github.repository}} 🐛 - SLACK_MESSAGE: ${{steps.issuemarkdown.outputs.text}} - MSG_MINIMAL: true + pull_request: + if: github.event_name == 'pull_request_target' && github.actor != 'asyncapi-bot' && github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]' + name: Notify slack on every new pull request + runs-on: ubuntu-latest + steps: + - name: Convert markdown to slack markdown for pull request + uses: LoveToKnow/slackify-markdown-action@v1.0.0 + id: prmarkdown + with: + text: "[${{github.event.pull_request.title}}](${{github.event.pull_request.html_url}}) \n ${{github.event.pull_request.body}}" + - name: Send info about pull request + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{secrets.SLACK_GITHUB_NEWISSUEPR}} + SLACK_TITLE: 💪 New Pull Request in ${{github.repository}} 💪 + SLACK_MESSAGE: ${{steps.prmarkdown.outputs.text}} + MSG_MINIMAL: true - pull_request: - if: github.event_name == 'pull_request_target' && github.actor != 'asyncapi-bot' && github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]' - name: Notify slack on every new pull request - runs-on: ubuntu-latest - steps: - - name: Convert markdown to slack markdown for pull request - uses: LoveToKnow/slackify-markdown-action@v1.0.0 - id: prmarkdown - with: - text: "[${{github.event.pull_request.title}}](${{github.event.pull_request.html_url}}) \n ${{github.event.pull_request.body}}" - - name: Send info about pull request - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_WEBHOOK: ${{secrets.SLACK_GITHUB_NEWISSUEPR}} - SLACK_TITLE: 💪 New Pull Request in ${{github.repository}} 💪 - SLACK_MESSAGE: ${{steps.prmarkdown.outputs.text}} - MSG_MINIMAL: true - - discussion: - if: github.event_name == 'discussion' && github.actor != 'asyncapi-bot' && github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]' - name: Notify slack on every new pull request - runs-on: ubuntu-latest - steps: - - name: Convert markdown to slack markdown for pull request - uses: LoveToKnow/slackify-markdown-action@v1.0.0 - id: discussionmarkdown - with: - text: "[${{github.event.discussion.title}}](${{github.event.discussion.html_url}}) \n ${{github.event.discussion.body}}" - - name: Send info about pull request - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_WEBHOOK: ${{secrets.SLACK_GITHUB_NEWISSUEPR}} - SLACK_TITLE: 💬 New Discussion in ${{github.repository}} 💬 - SLACK_MESSAGE: ${{steps.discussionmarkdown.outputs.text}} - MSG_MINIMAL: true + discussion: + if: github.event_name == 'discussion' && github.actor != 'asyncapi-bot' && github.actor != 'dependabot[bot]' && github.actor != 'dependabot-preview[bot]' + name: Notify slack on every new pull request + runs-on: ubuntu-latest + steps: + - name: Convert markdown to slack markdown for pull request + uses: LoveToKnow/slackify-markdown-action@v1.0.0 + id: discussionmarkdown + with: + text: "[${{github.event.discussion.title}}](${{github.event.discussion.html_url}}) \n ${{github.event.discussion.body}}" + - name: Send info about pull request + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{secrets.SLACK_GITHUB_NEWISSUEPR}} + SLACK_TITLE: 💬 New Discussion in ${{github.repository}} 💬 + SLACK_MESSAGE: ${{steps.discussionmarkdown.outputs.text}} + MSG_MINIMAL: true diff --git a/.github/workflows/link-check-cron.yml b/.github/workflows/link-check-cron.yml index e992e2d6..873d4297 100644 --- a/.github/workflows/link-check-cron.yml +++ b/.github/workflows/link-check-cron.yml @@ -8,30 +8,30 @@ on: schedule: # At 00:00 UTC on every Monday - cron: '0 0 * * 0' - + jobs: External-link-validation-weekly: if: startsWith(github.repository, 'asyncapi/') runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v3 + + # Checks the status of hyperlinks in .md files + - name: Check links + uses: gaurav-nelson/github-action-markdown-link-check@0a51127e9955b855a9bbfa1ff5577f1d1338c9a5 #1.0.14 but pointing to commit for security reasons + with: + use-quiet-mode: 'yes' + use-verbose-mode: 'yes' - # Checks the status of hyperlinks in .md files - - name: Check links - uses: gaurav-nelson/github-action-markdown-link-check@0a51127e9955b855a9bbfa1ff5577f1d1338c9a5 #1.0.14 but pointing to commit for security reasons - with: - use-quiet-mode: 'yes' - use-verbose-mode: 'yes' - - # A configuration file can be included, indicating the properties of the link check action - # More information can be found here: https://github.com/tcort/markdown-link-check#config-file-format - # Create mlc_config.json file in the root of the directory + # A configuration file can be included, indicating the properties of the link check action + # More information can be found here: https://github.com/tcort/markdown-link-check#config-file-format + # Create mlc_config.json file in the root of the directory - - name: Report workflow run status to Slack - uses: 8398a7/action-slack@v3 - with: - status: ${{ job.status }} - fields: repo,action,workflow - env: + - name: Report workflow run status to Slack + uses: 8398a7/action-slack@v3 + with: + status: ${{ job.status }} + fields: repo,action,workflow + env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CI_FAIL_NOTIFY }} - if: failure() # Only, on failure, send a message on the 94_bot-failing-ci slack channel + if: failure() # Only, on failure, send a message on the 94_bot-failing-ci slack channel diff --git a/.github/workflows/link-check-pr.yml b/.github/workflows/link-check-pr.yml index deb425cb..51f6cf78 100644 --- a/.github/workflows/link-check-pr.yml +++ b/.github/workflows/link-check-pr.yml @@ -3,7 +3,7 @@ name: Check Markdown links -on: +on: pull_request_target: types: [synchronize, ready_for_review, opened, reopened] paths: @@ -13,15 +13,16 @@ jobs: External-link-validation-on-PR: runs-on: ubuntu-latest steps: - - name: Checkout repo - uses: actions/checkout@v3 - - name: Check links - uses: gaurav-nelson/github-action-markdown-link-check@0a51127e9955b855a9bbfa1ff5577f1d1338c9a5 #1.0.14 but pointing to commit for security reasons - with: - use-quiet-mode: 'yes' - use-verbose-mode: 'yes' - check-modified-files-only: 'yes' # Only modified files are checked on PRs - - # A configuration file can be included, indicating the properties of the link check action - # More information can be found here: https://github.com/tcort/markdown-link-check#config-file-format - # Create mlc_config.json file in the root of the directory + - name: Checkout repo + uses: actions/checkout@v3 + - name: Check links + uses: gaurav-nelson/github-action-markdown-link-check@0a51127e9955b855a9bbfa1ff5577f1d1338c9a5 #1.0.14 but pointing to commit for security reasons + with: + use-quiet-mode: 'yes' + use-verbose-mode: 'yes' + check-modified-files-only: 'yes' # Only modified files are checked on PRs + + + # A configuration file can be included, indicating the properties of the link check action + # More information can be found here: https://github.com/tcort/markdown-link-check#config-file-format + # Create mlc_config.json file in the root of the directory diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml index 0440d87e..c4a942a9 100644 --- a/.github/workflows/lint-pr-title.yml +++ b/.github/workflows/lint-pr-title.yml @@ -1,22 +1,20 @@ # This action is centrally managed in https://github.com/asyncapi/.github/ # Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo - - name: Lint PR title on: pull_request_target: types: [opened, reopened, synchronize, edited, ready_for_review] - + jobs: lint-pr-title: - name: Lint PR title - runs-on: ubuntu-latest - steps: + name: Lint PR title + runs-on: ubuntu-latest + steps: # Since this workflow is REQUIRED for a PR to be mergable, we have to have this 'if' statement in step level instead of job level. - if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors"]'), github.actor) }} - uses: amannn/action-semantic-pull-request@505e44b4f33b4c801f063838b3f053990ee46ea7 #version 4.6.0 + uses: amannn/action-semantic-pull-request@505e44b4f33b4c801f063838b3f053990ee46ea7 #version 4.6.0 id: lint_pr_title env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN}} @@ -24,26 +22,26 @@ jobs: subjectPattern: ^(?![A-Z]).+$ subjectPatternError: | The subject "{subject}" found in the pull request title "{title}" should start with a lowercase character. - - # Comments the error message from the above lint_pr_title action + + # Comments the error message from the above lint_pr_title action - if: ${{ always() && steps.lint_pr_title.outputs.error_message != null && !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors"]'), github.actor)}} name: Comment on PR - uses: marocchino/sticky-pull-request-comment@39c5b5dc7717447d0cba270cd115037d32d28443 #version 2.2 + uses: marocchino/sticky-pull-request-comment@39c5b5dc7717447d0cba270cd115037d32d28443 #version 2.2 with: header: pr-title-lint-error GITHUB_TOKEN: ${{ secrets.GH_TOKEN}} message: | - - We require all PRs to follow [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/). - More details 👇🏼 - ``` - ${{ steps.lint_pr_title.outputs.error_message}} - ``` - # deletes the error comment if the title is correct + + We require all PRs to follow [Conventional Commits specification](https://www.conventionalcommits.org/en/v1.0.0/). + More details 👇🏼 + ``` + ${{ steps.lint_pr_title.outputs.error_message}} + ``` + # deletes the error comment if the title is correct - if: ${{ steps.lint_pr_title.outputs.error_message == null }} name: delete the comment - uses: marocchino/sticky-pull-request-comment@39c5b5dc7717447d0cba270cd115037d32d28443 #version 2.2 - with: + uses: marocchino/sticky-pull-request-comment@39c5b5dc7717447d0cba270cd115037d32d28443 #version 2.2 + with: header: pr-title-lint-error delete: true GITHUB_TOKEN: ${{ secrets.GH_TOKEN}} diff --git a/.github/workflows/notify-tsc-members-mention.yml b/.github/workflows/notify-tsc-members-mention.yml index 1cdb371c..f7abce31 100644 --- a/.github/workflows/notify-tsc-members-mention.yml +++ b/.github/workflows/notify-tsc-members-mention.yml @@ -5,300 +5,299 @@ name: Notify slack and email subscribers whenever TSC members are mentioned in GitHub on: - issue_comment: - types: - - created - - edited - - discussion_comment: - types: - - created - - edited - - issues: - types: - - opened - - reopened + issue_comment: + types: + - created + - edited - pull_request_target: - types: - - opened - - reopened - - ready_for_review + discussion_comment: + types: + - created + - edited - discussion: - types: - - created - - edited + issues: + types: + - opened + - reopened + + pull_request_target: + types: + - opened + - reopened + - ready_for_review + + discussion: + types: + - created + - edited jobs: + issue: + if: github.event_name == 'issues' && contains(github.event.issue.body, '@asyncapi/tsc_members') + name: TSC notification on every new issue + runs-on: ubuntu-latest + steps: + ######### + # Handling Slack notifications + ######### + - name: Convert markdown to slack markdown + uses: LoveToKnow/slackify-markdown-action@v1.0.0 + id: issuemarkdown + with: + text: "[${{github.event.issue.title}}](${{github.event.issue.html_url}}) \n ${{github.event.issue.body}}" + - name: Send info about issue + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} + SLACK_TITLE: 🆘 New issue that requires TSC Members attention 🆘 + SLACK_MESSAGE: ${{steps.issuemarkdown.outputs.text}} + MSG_MINIMAL: true + ######### + # Handling Mailchimp notifications + ######### + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - name: Install deps + run: npm install + working-directory: ./.github/workflows/scripts/mailchimp + - name: Send email with MailChimp + uses: actions/github-script@v4 + env: + CALENDAR_ID: ${{ secrets.CALENDAR_ID }} + CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} + MAILCHIMP_API_KEY: ${{ secrets.MAILCHIMP_API_KEY }} + with: + script: | + const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); + sendEmail('${{github.event.issue.html_url}}'); - issue: - if: github.event_name == 'issues' && contains(github.event.issue.body, '@asyncapi/tsc_members') - name: TSC notification on every new issue - runs-on: ubuntu-latest - steps: - ######### - # Handling Slack notifications - ######### - - name: Convert markdown to slack markdown - uses: LoveToKnow/slackify-markdown-action@v1.0.0 - id: issuemarkdown - with: - text: "[${{github.event.issue.title}}](${{github.event.issue.html_url}}) \n ${{github.event.issue.body}}" - - name: Send info about issue - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} - SLACK_TITLE: 🆘 New issue that requires TSC Members attention 🆘 - SLACK_MESSAGE: ${{steps.issuemarkdown.outputs.text}} - MSG_MINIMAL: true - ######### - # Handling Mailchimp notifications - ######### - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - - name: Install deps - run: npm install - working-directory: ./.github/workflows/scripts/mailchimp - - name: Send email with MailChimp - uses: actions/github-script@v4 - env: - CALENDAR_ID: ${{ secrets.CALENDAR_ID }} - CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} - MAILCHIMP_API_KEY: ${{ secrets.MAILCHIMP_API_KEY }} - with: - script: | - const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); - sendEmail('${{github.event.issue.html_url}}'); + pull_request: + if: github.event_name == 'pull_request_target' && contains(github.event.pull_request.body, '@asyncapi/tsc_members') + name: TSC notification on every new pull request + runs-on: ubuntu-latest + steps: + ######### + # Handling Slack notifications + ######### + - name: Convert markdown to slack markdown + uses: LoveToKnow/slackify-markdown-action@v1.0.0 + id: prmarkdown + with: + text: "[${{github.event.pull_request.title}}](${{github.event.pull_request.html_url}}) \n ${{github.event.pull_request.body}}" + - name: Send info about pull request + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} + SLACK_TITLE: 🆘 New PR that requires TSC Members attention 🆘 + SLACK_MESSAGE: ${{steps.prmarkdown.outputs.text}} + MSG_MINIMAL: true + ######### + # Handling Mailchimp notifications + ######### + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - name: Install deps + run: npm install + working-directory: ./.github/workflows/scripts/mailchimp + - name: Send email with MailChimp + uses: actions/github-script@v4 + env: + CALENDAR_ID: ${{ secrets.CALENDAR_ID }} + CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} + MAILCHIMP_API_KEY: ${{ secrets.MAILCHIMP_API_KEY }} + with: + script: | + const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); + sendEmail('${{github.event.pull_request.html_url}}'); - pull_request: - if: github.event_name == 'pull_request_target' && contains(github.event.pull_request.body, '@asyncapi/tsc_members') - name: TSC notification on every new pull request - runs-on: ubuntu-latest - steps: - ######### - # Handling Slack notifications - ######### - - name: Convert markdown to slack markdown - uses: LoveToKnow/slackify-markdown-action@v1.0.0 - id: prmarkdown - with: - text: "[${{github.event.pull_request.title}}](${{github.event.pull_request.html_url}}) \n ${{github.event.pull_request.body}}" - - name: Send info about pull request - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} - SLACK_TITLE: 🆘 New PR that requires TSC Members attention 🆘 - SLACK_MESSAGE: ${{steps.prmarkdown.outputs.text}} - MSG_MINIMAL: true - ######### - # Handling Mailchimp notifications - ######### - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - - name: Install deps - run: npm install - working-directory: ./.github/workflows/scripts/mailchimp - - name: Send email with MailChimp - uses: actions/github-script@v4 - env: - CALENDAR_ID: ${{ secrets.CALENDAR_ID }} - CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} - MAILCHIMP_API_KEY: ${{ secrets.MAILCHIMP_API_KEY }} - with: - script: | - const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); - sendEmail('${{github.event.pull_request.html_url}}'); - - discussion: - if: github.event_name == 'discussion' && contains(github.event.discussion.body, '@asyncapi/tsc_members') - name: TSC notification on every new discussion - runs-on: ubuntu-latest - steps: - ######### - # Handling Slack notifications - ######### - - name: Convert markdown to slack markdown - uses: LoveToKnow/slackify-markdown-action@v1.0.0 - id: discussionmarkdown - with: - text: "[${{github.event.discussion.title}}](${{github.event.discussion.html_url}}) \n ${{github.event.discussion.body}}" - - name: Send info about pull request - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} - SLACK_TITLE: 🆘 New discussion that requires TSC Members attention 🆘 - SLACK_MESSAGE: ${{steps.discussionmarkdown.outputs.text}} - MSG_MINIMAL: true - ######### - # Handling Mailchimp notifications - ######### - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - - name: Install deps - run: npm install - working-directory: ./.github/workflows/scripts/mailchimp - - name: Send email with MailChimp - uses: actions/github-script@v4 - env: - CALENDAR_ID: ${{ secrets.CALENDAR_ID }} - CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} - MAILCHIMP_API_KEY: ${{ secrets.MAILCHIMP_API_KEY }} - with: - script: | - const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); - sendEmail('${{github.event.discussion.html_url}}'); + discussion: + if: github.event_name == 'discussion' && contains(github.event.discussion.body, '@asyncapi/tsc_members') + name: TSC notification on every new discussion + runs-on: ubuntu-latest + steps: + ######### + # Handling Slack notifications + ######### + - name: Convert markdown to slack markdown + uses: LoveToKnow/slackify-markdown-action@v1.0.0 + id: discussionmarkdown + with: + text: "[${{github.event.discussion.title}}](${{github.event.discussion.html_url}}) \n ${{github.event.discussion.body}}" + - name: Send info about pull request + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} + SLACK_TITLE: 🆘 New discussion that requires TSC Members attention 🆘 + SLACK_MESSAGE: ${{steps.discussionmarkdown.outputs.text}} + MSG_MINIMAL: true + ######### + # Handling Mailchimp notifications + ######### + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - name: Install deps + run: npm install + working-directory: ./.github/workflows/scripts/mailchimp + - name: Send email with MailChimp + uses: actions/github-script@v4 + env: + CALENDAR_ID: ${{ secrets.CALENDAR_ID }} + CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} + MAILCHIMP_API_KEY: ${{ secrets.MAILCHIMP_API_KEY }} + with: + script: | + const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); + sendEmail('${{github.event.discussion.html_url}}'); - issue_comment: - if: ${{ github.event_name == 'issue_comment' && !github.event.issue.pull_request && contains(github.event.comment.body, '@asyncapi/tsc_members') }} - name: TSC notification on every new comment in issue - runs-on: ubuntu-latest - steps: - ######### - # Handling Slack notifications - ######### - - name: Convert markdown to slack markdown - uses: LoveToKnow/slackify-markdown-action@v1.0.0 - id: issuemarkdown - with: - text: "[${{github.event.issue.title}}](${{github.event.comment.html_url}}) \n ${{github.event.comment.body}}" - - name: Send info about issue comment - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} - SLACK_TITLE: 🆘 New comment under existing issue that requires TSC Members attention 🆘 - SLACK_MESSAGE: ${{steps.issuemarkdown.outputs.text}} - MSG_MINIMAL: true - ######### - # Handling Mailchimp notifications - ######### - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - - name: Install deps - run: npm install - working-directory: ./.github/workflows/scripts/mailchimp - - name: Send email with MailChimp - uses: actions/github-script@v4 - env: - CALENDAR_ID: ${{ secrets.CALENDAR_ID }} - CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} - MAILCHIMP_API_KEY: ${{ secrets.MAILCHIMP_API_KEY }} - with: - script: | - const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); - sendEmail('${{github.event.comment.html_url}}'); + issue_comment: + if: ${{ github.event_name == 'issue_comment' && !github.event.issue.pull_request && contains(github.event.comment.body, '@asyncapi/tsc_members') }} + name: TSC notification on every new comment in issue + runs-on: ubuntu-latest + steps: + ######### + # Handling Slack notifications + ######### + - name: Convert markdown to slack markdown + uses: LoveToKnow/slackify-markdown-action@v1.0.0 + id: issuemarkdown + with: + text: "[${{github.event.issue.title}}](${{github.event.comment.html_url}}) \n ${{github.event.comment.body}}" + - name: Send info about issue comment + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} + SLACK_TITLE: 🆘 New comment under existing issue that requires TSC Members attention 🆘 + SLACK_MESSAGE: ${{steps.issuemarkdown.outputs.text}} + MSG_MINIMAL: true + ######### + # Handling Mailchimp notifications + ######### + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - name: Install deps + run: npm install + working-directory: ./.github/workflows/scripts/mailchimp + - name: Send email with MailChimp + uses: actions/github-script@v4 + env: + CALENDAR_ID: ${{ secrets.CALENDAR_ID }} + CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} + MAILCHIMP_API_KEY: ${{ secrets.MAILCHIMP_API_KEY }} + with: + script: | + const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); + sendEmail('${{github.event.comment.html_url}}'); - pr_comment: - if: github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@asyncapi/tsc_members') - name: TSC notification on every new comment in pr - runs-on: ubuntu-latest - steps: - ######### - # Handling Slack notifications - ######### - - name: Convert markdown to slack markdown - uses: LoveToKnow/slackify-markdown-action@v1.0.0 - id: prmarkdown - with: - text: "[${{github.event.issue.title}}](${{github.event.comment.html_url}}) \n ${{github.event.comment.body}}" - - name: Send info about PR comment - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} - SLACK_TITLE: 🆘 New comment under existing PR that requires TSC Members attention 🆘 - SLACK_MESSAGE: ${{steps.prmarkdown.outputs.text}} - MSG_MINIMAL: true - ######### - # Handling Mailchimp notifications - ######### - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - - name: Install deps - run: npm install - working-directory: ./.github/workflows/scripts/mailchimp - - name: Send email with MailChimp - uses: actions/github-script@v4 - env: - CALENDAR_ID: ${{ secrets.CALENDAR_ID }} - CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} - MAILCHIMP_API_KEY: ${{ secrets.MAILCHIMP_API_KEY }} - with: - script: | - const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); - sendEmail('${{github.event.comment.html_url}}'); + pr_comment: + if: github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@asyncapi/tsc_members') + name: TSC notification on every new comment in pr + runs-on: ubuntu-latest + steps: + ######### + # Handling Slack notifications + ######### + - name: Convert markdown to slack markdown + uses: LoveToKnow/slackify-markdown-action@v1.0.0 + id: prmarkdown + with: + text: "[${{github.event.issue.title}}](${{github.event.comment.html_url}}) \n ${{github.event.comment.body}}" + - name: Send info about PR comment + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} + SLACK_TITLE: 🆘 New comment under existing PR that requires TSC Members attention 🆘 + SLACK_MESSAGE: ${{steps.prmarkdown.outputs.text}} + MSG_MINIMAL: true + ######### + # Handling Mailchimp notifications + ######### + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - name: Install deps + run: npm install + working-directory: ./.github/workflows/scripts/mailchimp + - name: Send email with MailChimp + uses: actions/github-script@v4 + env: + CALENDAR_ID: ${{ secrets.CALENDAR_ID }} + CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} + MAILCHIMP_API_KEY: ${{ secrets.MAILCHIMP_API_KEY }} + with: + script: | + const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); + sendEmail('${{github.event.comment.html_url}}'); - discussion_comment: - if: github.event_name == 'discussion_comment' && contains(github.event.comment.body, '@asyncapi/tsc_members') - name: TSC notification on every new comment in discussion - runs-on: ubuntu-latest - steps: - ######### - # Handling Slack notifications - ######### - - name: Convert markdown to slack markdown - uses: LoveToKnow/slackify-markdown-action@v1.0.0 - id: discussionmarkdown - with: - text: "[${{github.event.discussion.title}}](${{github.event.comment.html_url}}) \n ${{github.event.comment.body}}" - - name: Send info about discussion comment - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} - SLACK_TITLE: 🆘 New comment under existing discussion that requires TSC Members attention 🆘 - SLACK_MESSAGE: ${{steps.discussionmarkdown.outputs.text}} - MSG_MINIMAL: true - ######### - # Handling Mailchimp notifications - ######### - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - - name: Install deps - run: npm install - working-directory: ./.github/workflows/scripts/mailchimp - - name: Send email with MailChimp - uses: actions/github-script@v4 - env: - CALENDAR_ID: ${{ secrets.CALENDAR_ID }} - CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} - MAILCHIMP_API_KEY: ${{ secrets.MAILCHIMP_API_KEY }} - with: - script: | - const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); - sendEmail('${{github.event.comment.html_url}}'); \ No newline at end of file + discussion_comment: + if: github.event_name == 'discussion_comment' && contains(github.event.comment.body, '@asyncapi/tsc_members') + name: TSC notification on every new comment in discussion + runs-on: ubuntu-latest + steps: + ######### + # Handling Slack notifications + ######### + - name: Convert markdown to slack markdown + uses: LoveToKnow/slackify-markdown-action@v1.0.0 + id: discussionmarkdown + with: + text: "[${{github.event.discussion.title}}](${{github.event.comment.html_url}}) \n ${{github.event.comment.body}}" + - name: Send info about discussion comment + uses: rtCamp/action-slack-notify@v2 + env: + SLACK_WEBHOOK: ${{secrets.SLACK_TSC_MEMBERS_NOTIFY}} + SLACK_TITLE: 🆘 New comment under existing discussion that requires TSC Members attention 🆘 + SLACK_MESSAGE: ${{steps.discussionmarkdown.outputs.text}} + MSG_MINIMAL: true + ######### + # Handling Mailchimp notifications + ######### + - name: Checkout repository + uses: actions/checkout@v2 + - name: Setup Node.js + uses: actions/setup-node@v2 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' + - name: Install deps + run: npm install + working-directory: ./.github/workflows/scripts/mailchimp + - name: Send email with MailChimp + uses: actions/github-script@v4 + env: + CALENDAR_ID: ${{ secrets.CALENDAR_ID }} + CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} + MAILCHIMP_API_KEY: ${{ secrets.MAILCHIMP_API_KEY }} + with: + script: | + const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); + sendEmail('${{github.event.comment.html_url}}'); From da0480e1c9604c4d21827a8cc10504f2b6db6fe3 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Wed, 23 Nov 2022 18:48:54 +0100 Subject: [PATCH 72/91] ci: update generic workflows (#174) --- .../workflows/notify-tsc-members-mention.yml | 18 ++++++------------ .../workflows/scripts/mailchimp/htmlContent.js | 4 ++-- .github/workflows/scripts/mailchimp/index.js | 6 +++--- 3 files changed, 11 insertions(+), 17 deletions(-) diff --git a/.github/workflows/notify-tsc-members-mention.yml b/.github/workflows/notify-tsc-members-mention.yml index f7abce31..e681c18a 100644 --- a/.github/workflows/notify-tsc-members-mention.yml +++ b/.github/workflows/notify-tsc-members-mention.yml @@ -8,28 +8,22 @@ on: issue_comment: types: - created - - edited discussion_comment: types: - created - - edited issues: types: - opened - - reopened pull_request_target: types: - opened - - reopened - - ready_for_review discussion: types: - created - - edited jobs: issue: @@ -75,7 +69,7 @@ jobs: with: script: | const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); - sendEmail('${{github.event.issue.html_url}}'); + sendEmail('${{github.event.issue.html_url}}', '${{github.event.issue.title}}'); pull_request: if: github.event_name == 'pull_request_target' && contains(github.event.pull_request.body, '@asyncapi/tsc_members') @@ -120,7 +114,7 @@ jobs: with: script: | const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); - sendEmail('${{github.event.pull_request.html_url}}'); + sendEmail('${{github.event.pull_request.html_url}}', '${{github.event.pull_request.title}}'); discussion: if: github.event_name == 'discussion' && contains(github.event.discussion.body, '@asyncapi/tsc_members') @@ -165,7 +159,7 @@ jobs: with: script: | const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); - sendEmail('${{github.event.discussion.html_url}}'); + sendEmail('${{github.event.discussion.html_url}}', '${{github.event.discussion.title}}'); issue_comment: if: ${{ github.event_name == 'issue_comment' && !github.event.issue.pull_request && contains(github.event.comment.body, '@asyncapi/tsc_members') }} @@ -210,7 +204,7 @@ jobs: with: script: | const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); - sendEmail('${{github.event.comment.html_url}}'); + sendEmail('${{github.event.comment.html_url}}', '${{github.event.issue.title}}'); pr_comment: if: github.event_name == 'issue_comment' && github.event.issue.pull_request && contains(github.event.comment.body, '@asyncapi/tsc_members') @@ -255,7 +249,7 @@ jobs: with: script: | const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); - sendEmail('${{github.event.comment.html_url}}'); + sendEmail('${{github.event.comment.html_url}}', '${{github.event.issue.title}}'); discussion_comment: if: github.event_name == 'discussion_comment' && contains(github.event.comment.body, '@asyncapi/tsc_members') @@ -300,4 +294,4 @@ jobs: with: script: | const sendEmail = require('./.github/workflows/scripts/mailchimp/index.js'); - sendEmail('${{github.event.comment.html_url}}'); + sendEmail('${{github.event.comment.html_url}}', '${{github.event.discussion.title}}'); diff --git a/.github/workflows/scripts/mailchimp/htmlContent.js b/.github/workflows/scripts/mailchimp/htmlContent.js index e3eadc97..d132c72f 100644 --- a/.github/workflows/scripts/mailchimp/htmlContent.js +++ b/.github/workflows/scripts/mailchimp/htmlContent.js @@ -2,7 +2,7 @@ * This code is centrally managed in https://github.com/asyncapi/.github/ * Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo */ -module.exports = (link) => { +module.exports = (link, title) => { return ` @@ -386,7 +386,7 @@ There is a new topic at AsyncAPI Initiative that requires Technical Steering Com
Please have a look if it is just something you need to be aware of, or maybe your vote is needed.
-Click here to see more details on GitHub. +Topic: ${ title }. diff --git a/.github/workflows/scripts/mailchimp/index.js b/.github/workflows/scripts/mailchimp/index.js index a7b6f78f..387176b4 100644 --- a/.github/workflows/scripts/mailchimp/index.js +++ b/.github/workflows/scripts/mailchimp/index.js @@ -10,7 +10,7 @@ const htmlContent = require('./htmlContent.js'); * Sending API request to mailchimp to schedule email to subscribers * Input is the URL to issue/discussion or other resource */ -module.exports = async (link) => { +module.exports = async (link, title) => { let newCampaign; @@ -32,7 +32,7 @@ module.exports = async (link) => { } }, settings: { - subject_line: 'AsyncAPI TSC members attention required', + subject_line: `TSC attention required: ${ title }`, preview_text: 'Check out the latest topic that TSC members have to be aware of', title: `New topic info - ${ new Date(Date.now()).toUTCString()}`, from_name: 'AsyncAPI Initiative', @@ -47,7 +47,7 @@ module.exports = async (link) => { * Content of the email is added separately after campaign creation */ try { - await mailchimp.campaigns.setContent(newCampaign.id, { html: htmlContent(link) }); + await mailchimp.campaigns.setContent(newCampaign.id, { html: htmlContent(link, title) }); } catch (error) { return core.setFailed(`Failed adding content to campaign: ${ JSON.stringify(error) }`); } From 62c0762bde9865d1fc1604e11c4642ae3eab31d8 Mon Sep 17 00:00:00 2001 From: Thorsten Hake Date: Fri, 25 Nov 2022 10:29:49 +0100 Subject: [PATCH 73/91] feat: add topicConfiguration property to channel object (#171) Co-authored-by: Laurent Broudoux Co-authored-by: Thorsten Hake --- kafka/README.md | 57 ++++++++++++++++++++++++------- kafka/json_schemas/channel.json | 37 +++++++++++++++++++- kafka/json_schemas/message.json | 2 +- kafka/json_schemas/operation.json | 2 +- kafka/json_schemas/server.json | 2 +- 5 files changed, 84 insertions(+), 16 deletions(-) diff --git a/kafka/README.md b/kafka/README.md index 6efcf74e..cc75c77c 100644 --- a/kafka/README.md +++ b/kafka/README.md @@ -6,7 +6,7 @@ This document defines how to describe Kafka-specific information on AsyncAPI. ## Version -Current version is `0.3.0`. +Current version is `0.4.0`. @@ -32,7 +32,7 @@ servers: kafka: schemaRegistryUrl: 'https://my-schema-registry.com' schemaRegistryVendor: 'confluent' - bindingVersion: '0.3.0' + bindingVersion: '0.4.0' ``` @@ -44,12 +44,14 @@ This object contains information about the channel representation in Kafka (eg. ##### Fixed Fields -Field Name | Type | Description | Applicability [default] | Constraints ----|:---:|:---:|:---:|--- -`topic` | string | Kafka topic name if different from channel name. | OPTIONAL | - -`partitions` | integer | Number of partitions configured on this topic (useful to know how many parallel consumers you may run). | OPTIONAL | Must be positive -`replicas` | integer | Number of replicas configured on this topic. | OPTIONAL | MUST be positive -`bindingVersion` | string | The version of this binding. If omitted, "latest" MUST be assumed. | OPTIONAL [`latest`] | - +Field Name | Type | Description | Applicability [default] | Constraints +---|:------------------------------------------------:|:-------------------------------------------------------------------------------------------------------:|:-----------------------:|--- +`topic` | string | Kafka topic name if different from channel name. | OPTIONAL | - +`partitions` | integer | Number of partitions configured on this topic (useful to know how many parallel consumers you may run). | OPTIONAL | Must be positive +`replicas` | integer | Number of replicas configured on this topic. | OPTIONAL | MUST be positive +`topicConfiguration` | [TopicConfiguration Object](#topicConfiguration) | Topic configuration properties that are relevant for the API. | OPTIONAL | - +`bindingVersion` | string | The version of this binding. If omitted, "latest" MUST be assumed. | OPTIONAL [`latest`] | - + This object MUST contain only the properties defined above. @@ -65,7 +67,38 @@ channels: topic: 'my-specific-topic-name' partitions: 20 replicas: 3 - bindingVersion: '0.3.0' + topicConfiguration: + cleanup.policy: ["delete", "compact"] + retention.ms: 604800000 + retention.bytes: 1000000000 + delete.retention.ms: 86400000 + max.message.bytes: 1048588 + bindingVersion: '0.4.0' +``` + +## TopicConfiguration Object + +This objects contains information about the API relevant topic configuration in Kafka. + +Field Name | Type | Description | Applicability [default] | Constraints +---|:-------:|:--------------------------------------------------------------------------------------------------------------------------------------------------------------------:|:-----------------------:|--- +`cleanup.policy` | array | The [`cleanup.policy`](https://kafka.apache.org/documentation/#topicconfigs_cleanup.policy) configuration option. | OPTIONAL | array may only contain `delete` and/or `compact` +`retention.ms` | integer | The [`retention.ms`](https://kafka.apache.org/documentation/#topicconfigs_retention.ms) configuration option. | OPTIONAL | see kafka documentation +`retention.bytes` | integer | The [`retention.bytes`](https://kafka.apache.org/documentation/#topicconfigs_retention.bytes) configuration option. | OPTIONAL | see kafka documentation +`delete.retention.ms` | integer | The [`delete.retention.ms`](https://kafka.apache.org/documentation/#topicconfigs_delete.retention.ms) configuration option. | OPTIONAL | see kafka documentation +`max.message.bytes` | integer | The [`max.message.bytes`](https://kafka.apache.org/documentation/#topicconfigs_max.message.bytes) configuration option. | OPTIONAL | see kafka documentation + +This object MUST contain only the properties defined above. + +##### Example + +```yaml +topicConfiguration: + cleanup.policy: ["delete", "compact"] + retention.ms: 604800000 + retention.bytes: 1000000000 + delete.retention.ms: 86400000 + max.message.bytes: 1048588 ``` @@ -98,7 +131,7 @@ channels: clientId: type: string enum: ['myClientId'] - bindingVersion: '0.3.0' + bindingVersion: '0.4.0' ``` @@ -134,7 +167,7 @@ channels: enum: ['myKey'] schemaIdLocation: 'payload' schemaIdPayloadEncoding: '4' - bindingVersion: '0.3.0' + bindingVersion: '0.4.0' ``` This is another example that describes the use if Apicurio schema registry. We describe the `apicurio-new` way of serializing without details on how it's implemented. We reference a [specific lookup strategy](https://www.apicur.io/registry/docs/apicurio-registry/2.2.x/getting-started/assembly-using-kafka-client-serdes.html#registry-serdes-concepts-strategy_registry) that may be used to retrieve schema Id from registry during serialization. @@ -152,7 +185,7 @@ channels: schemaIdLocation: 'payload' schemaIdPayloadEncoding: 'apicurio-new' schemaLookupStrategy: 'TopicIdStrategy' - bindingVersion: '0.3.0' + bindingVersion: '0.4.0' ``` [schemaObject]: https://www.asyncapi.com/docs/specifications/2.4.0/#schemaObject diff --git a/kafka/json_schemas/channel.json b/kafka/json_schemas/channel.json index 4db52eb5..b20a1452 100644 --- a/kafka/json_schemas/channel.json +++ b/kafka/json_schemas/channel.json @@ -25,10 +25,45 @@ "minimum": 1, "description": "Number of replicas configured on this topic." }, + "topicConfiguration" : { + "description": "Topic configuration properties that are relevant for the API.", + "type": "object", + "additionalProperties": false, + "properties": { + "cleanup.policy": { + "description": "The [`cleanup.policy`](https://kafka.apache.org/documentation/#topicconfigs_cleanup.policy) configuration option.", + "type": "array", + "items":{ + "type": "string", + "enum": ["compact", "delete"] + } + }, + "retention.ms": { + "description": "The [`retention.ms`](https://kafka.apache.org/documentation/#topicconfigs_retention.ms) configuration option.", + "type": "integer", + "minimum": -1 + }, + "retention.bytes": { + "description": "The [`retention.bytes`](https://kafka.apache.org/documentation/#topicconfigs_retention.bytes) configuration option.", + "type": "integer", + "minimum": -1 + }, + "delete.retention.ms": { + "description": "The [`delete.retention.ms`](https://kafka.apache.org/documentation/#topicconfigs_delete.retention.ms) configuration option.", + "type": "integer", + "minimum": 0 + }, + "max.message.bytes": { + "description": "The [`max.message.bytes`](https://kafka.apache.org/documentation/#topicconfigs_max.message.bytes) configuration option.", + "type": "integer", + "minimum": 0 + } + } + }, "bindingVersion": { "type": "string", "enum": [ - "0.3.0" + "0.4.0" ], "description": "The version of this binding. If omitted, 'latest' MUST be assumed." } diff --git a/kafka/json_schemas/message.json b/kafka/json_schemas/message.json index f1428c9c..04a58158 100644 --- a/kafka/json_schemas/message.json +++ b/kafka/json_schemas/message.json @@ -30,7 +30,7 @@ "bindingVersion": { "type": "string", "enum": [ - "0.3.0" + "0.4.0" ], "description": "The version of this binding. If omitted, 'latest' MUST be assumed." } diff --git a/kafka/json_schemas/operation.json b/kafka/json_schemas/operation.json index ea54cdab..60d14eda 100644 --- a/kafka/json_schemas/operation.json +++ b/kafka/json_schemas/operation.json @@ -22,7 +22,7 @@ "bindingVersion": { "type": "string", "enum": [ - "0.3.0" + "0.4.0" ], "description": "The version of this binding. If omitted, 'latest' MUST be assumed." } diff --git a/kafka/json_schemas/server.json b/kafka/json_schemas/server.json index f29e3d33..2a09755f 100644 --- a/kafka/json_schemas/server.json +++ b/kafka/json_schemas/server.json @@ -22,7 +22,7 @@ "bindingVersion": { "type": "string", "enum": [ - "0.3.0" + "0.4.0" ], "description": "The version of this binding." } From f8d17d8905560589a59927ea59424fb23a5d89db Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Tue, 6 Dec 2022 11:01:42 +0100 Subject: [PATCH 74/91] ci: update generic workflows (#175) --- .github/workflows/scripts/mailchimp/index.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/scripts/mailchimp/index.js b/.github/workflows/scripts/mailchimp/index.js index 387176b4..4a200c6c 100644 --- a/.github/workflows/scripts/mailchimp/index.js +++ b/.github/workflows/scripts/mailchimp/index.js @@ -27,8 +27,14 @@ module.exports = async (link, title) => { type: 'regular', recipients: { list_id: '6e3e437abe', - segments_opts: { - saved_segment_id: 'tsc-voting-email' + segment_opts: { + match: 'any', + conditions: [{ + condition_type: 'Interests', + field: 'interests-2801e38b9f', + op: 'interestcontains', + value: ['f7204f9b90'] + }] } }, settings: { From 44f7b5835528abc4140ad45a89bb2b4819743a6d Mon Sep 17 00:00:00 2001 From: Alex Wichmann Date: Fri, 9 Dec 2022 11:39:38 +0100 Subject: [PATCH 75/91] feat: apache Pulsar bindings (#173) --- CODEOWNERS | 1 + pulsar/README.md | 86 ++++++++++++++++++++++++++++++ pulsar/json_schemas/channel.json | 91 ++++++++++++++++++++++++++++++++ pulsar/json_schemas/server.json | 32 +++++++++++ 4 files changed, 210 insertions(+) create mode 100644 pulsar/README.md create mode 100644 pulsar/json_schemas/channel.json create mode 100644 pulsar/json_schemas/server.json diff --git a/CODEOWNERS b/CODEOWNERS index 18d11328..2feaec97 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -13,4 +13,5 @@ /kafka/ @lbroudoux @dalelane /googlepubsub/ @whitlockjc /solace/ @damaru-inc @CameronRushton +/pulsar/ @VisualBean *.json @KhudaDad414 diff --git a/pulsar/README.md b/pulsar/README.md new file mode 100644 index 00000000..0a48c71f --- /dev/null +++ b/pulsar/README.md @@ -0,0 +1,86 @@ +# Pulsar Bindings +This document defines how to describe Apache Pulsar specific information with AsyncAPI. + + + +## Version + +Current version is `0.1.0`. + + +## Server Binding Object + +This object contains information about the server representation in Pulsar. + +##### Fixed Fields + +Field Name | Type | Required | Description | Default value | +---|:---:|:---:|:---|:---| +`tenant` | String | No | The pulsar tenant. If omitted, "public" MUST be assumed. | `public` | +`bindingVersion` | String | No | The version of this binding. If omitted, "latest" MUST be assumed. | `latest` | + +##### Example + +```yaml +servers: + production: + bindings: + pulsar: + tenant: contoso + bindingVersion: '0.1.0' +``` + + +## Channel Binding Object +This object contains information about the channel representation in Pulsar + +##### Fixed Fields + +Field Name | Type | Required | Description | Default value | +---|:---:|:---:|:---|:---| +`namespace` | String | Yes | The namespace the channel is associated with. | N/A | +`persistence` | String | Yes | Persistence of the topic in Pulsar. It MUST be either `persistent` or `non-persistent`. | N/A | +`compaction`| Integer | No | Topic compaction threshold given in Megabytes. | N/A | +`geo-replication` | String[] | No | A list of clusters the topic is replicated to. | N/A | +`retention` | [Retention Definition Object](#retention-definition-object) | No | Topic retention policy. | N/A | +`ttl` | Integer | No | Message time-to-live in seconds. | N/A | +`deduplication` | Boolean | No | Message deduplication. When true, it ensures that each message produced on Pulsar topics is persisted to disk only once. | N/A | +`bindingVersion` | String | No | The version of this binding. If omitted, "latest" MUST be assumed. | `latest` | + + +### Retention Definition Object +The `Retention Definition Object` is used to describe the Pulsar [Retention](https://pulsar.apache.org/docs/cookbooks-retention-expiry/) policy. + +Field Name | Type | Required | Description | Default value | +---|:---:|:---:|:---|:---| +`time`|Integer| No | Time given in Minutes. | `0` | +`size`|Integer| No |Size given in MegaBytes. | `0` | + +##### Example + +```yaml +channels: + user-signedup: + bindings: + pulsar: + namespace: 'staging' + persistence: 'persistent' + compaction: 1000 + geo-replication: + - 'us-east1' + - 'us-west1' + retention: + time: 7 + size: 1000 + ttl: 360 + deduplication: false + bindingVersion: '0.1.0' +``` + + +## Operation binding fields +This object MUST NOT contain any properties. Its name is reserved for future use. + + +## Message binding fields +This object MUST NOT contain any properties. Its name is reserved for future use. \ No newline at end of file diff --git a/pulsar/json_schemas/channel.json b/pulsar/json_schemas/channel.json new file mode 100644 index 00000000..021bf3a2 --- /dev/null +++ b/pulsar/json_schemas/channel.json @@ -0,0 +1,91 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/pulsar/channel.json", + "title": "Channel Schema", + "description": "This object contains information about the channel representation in Pulsar, which covers namespace and topic level admin configuration. This object contains additional information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v4.0.0/schemas/2.5.0.json#/definitions/http://asyncapi.com/definitions/2.5.0/specificationExtension.json" + } + }, + "required": [ "namespace", "persistence" ], + "properties": { + "namespace": { + "type": "string", + "description": "The namespace, the channel is associated with." + }, + "persistence": { + "type": "string", + "enum": [ + "persistent", + "non-persistent" + ], + "description": "persistence of the topic in Pulsar." + }, + "compaction": { + "type": "integer", + "minimum": 0, + "description": "Topic compaction threshold given in MB" + }, + "geo-replication": { + "type": "array", + "description": "A list of clusters the topic is replicated to.", + "items": { + "type": "string" + } + }, + "retention": { + "type": "object", + "additionalProperties": false, + "properties": { + "time": { + "type": "integer", + "minimum": 0, + "description": "Time given in Minutes. `0` = Disable message retention." + }, + "size": { + "type": "integer", + "minimum": 0, + "description": "Size given in MegaBytes. `0` = Disable message retention." + } + } + }, + "ttl": { + "type": "integer", + "description": "TTL in seconds for the specified topic" + }, + "deduplication": { + "type": "boolean", + "description": "Whether deduplication of events is enabled or not." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + + }, + "examples": [ + { + "namespace": "ns1", + "persistence": "persistent", + "compaction": 1000, + "retention": { + "time": 15, + "size": 1000 + }, + "ttl": 360, + "geo-replication": [ + "us-west", + "us-east" + ], + "deduplication": true, + "bindingVersion": "0.1.0" + } + ] + } + \ No newline at end of file diff --git a/pulsar/json_schemas/server.json b/pulsar/json_schemas/server.json new file mode 100644 index 00000000..4167d2d9 --- /dev/null +++ b/pulsar/json_schemas/server.json @@ -0,0 +1,32 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/pulsar/server.json", + "title": "Server Schema", + "description": "This object contains server information of Pulsar broker, which covers cluster and tenant admin configuration. This object contains additional information not possible to represent within the core AsyncAPI specification.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "tenant": { + "type": "string", + "description": "The pulsar tenant. If omitted, 'public' MUST be assumed." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.1.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "tenant": "contoso", + "bindingVersion": "0.1.0" + } + ] +} From df85a91aea0640d09d1d89acff41a92680bd864c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicolai=20S=C3=B8ndergaard?= Date: Thu, 12 Jan 2023 15:28:30 +0100 Subject: [PATCH 76/91] docs: add Pulsar to the overview in readme.md (#177) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 849447a7..96e67ce1 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ This repository contains the specifications for each AsyncAPI protocol binding. * [MQTT binding](./mqtt) * [MQTT5 binding](./mqtt5) * [NATS binding](./nats) +* [Pulsar](./pulsar) * [Redis binding](./redis) * [SNS binding](./sns) * [Solace binding](./solace) From de3fe35c076c6d2bc0c6a4c7a9125c6a290fe8dc Mon Sep 17 00:00:00 2001 From: Shreyansh Jain Date: Thu, 12 Jan 2023 20:47:34 +0530 Subject: [PATCH 77/91] feat: session expiry interval to mqtt5 bindings (#157) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fran Méndez --- mqtt5/README.md | 41 +++++++++++++++++++++++---- mqtt5/json_schemas/server.json | 52 ++++++++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 5 deletions(-) create mode 100644 mqtt5/json_schemas/server.json diff --git a/mqtt5/README.md b/mqtt5/README.md index 66e961b9..3e794ba0 100644 --- a/mqtt5/README.md +++ b/mqtt5/README.md @@ -6,16 +6,44 @@ This document defines how to describe MQTT 5-specific information on AsyncAPI. ## Version -Current version is `0.1.0`. +Current version is `0.2.0`. ## Server Binding Object -This object MUST NOT contain any properties. Its name is reserved for future use. - - +This object contains information about the server representation in MQTT5. + +##### Fixed Fields + +Field Name | Type | Description +---|:---:|--- +`sessionExpiryInterval` | [Schema Object][schemaObject] \| [Reference Object](referenceObject) \| integer | Session Expiry Interval in seconds or a Schema Object containing the definition of the interval. +`bindingVersion` | string | The version of this binding. If omitted, "latest" MUST be assumed. + +This object MUST contain only the properties defined above. + +##### Example + +```yaml +servers: + production: + bindings: + mqtt5: + sessionExpiryInterval: 60 + bindingVersion: 0.2.0 +``` +```yaml +servers: + production: + bindings: + mqtt5: + sessionExpiryInterval: + type: integer + minimum: 100 + bindingVersion: 0.2.0 +``` @@ -38,4 +66,7 @@ This object MUST NOT contain any properties. Its name is reserved for future use ## Message Binding Object -This object MUST NOT contain any properties. Its name is reserved for future use. \ No newline at end of file +This object MUST NOT contain any properties. Its name is reserved for future use. + +[schemaObject]: https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#schemaObject +[referenceObject]: https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#referenceObject diff --git a/mqtt5/json_schemas/server.json b/mqtt5/json_schemas/server.json new file mode 100644 index 00000000..22241a49 --- /dev/null +++ b/mqtt5/json_schemas/server.json @@ -0,0 +1,52 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/mqtt5/server.json", + "title": "Server Schema", + "description": "This object contains information about the server representation in MQTT5.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.7.7/schemas/2.0.0.json#/definitions/specificationExtension" + } + }, + "properties": { + + "sessionExpiryInterval": { + "oneOf": [ + { + "type": "integer", + "minimum": 0 + }, + { + "$ref": "https://asyncapi.com/definitions/2.4.0/schema.json" + }, + { + "$ref": "https://asyncapi.com/definitions/2.4.0/Reference.json" + } + ], + "description": "Session Expiry Interval in seconds or a Schema Object containing the definition of the interval." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.2.0" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + }, + "examples": [ + { + "sessionExpiryInterval": 60, + "bindingVersion": "0.2.0" + }, + { + "sessionExpiryInterval": { + "type": "integer", + "minimum": 100 + }, + "bindingVersion": "0.2.0" + } + ] +} + From c175a704fd3e5a4d28ed029c2403a8e3a0567546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Gorej?= Date: Mon, 16 Jan 2023 12:15:58 +0100 Subject: [PATCH 78/91] fix(ibmmq): make topic field optional (#176) Co-authored-by: Lukasz Gornicki --- ibmmq/json_schemas/channel.json | 3 --- 1 file changed, 3 deletions(-) diff --git a/ibmmq/json_schemas/channel.json b/ibmmq/json_schemas/channel.json index 5075486c..62154b9c 100644 --- a/ibmmq/json_schemas/channel.json +++ b/ibmmq/json_schemas/channel.json @@ -84,9 +84,6 @@ "properties": { "destinationType": { "const": "topic" } }, - "required": [ - "topic" - ], "not": { "required": [ "queue" From 18c040072079b7016abc5b4d3aaa43221cd95ce8 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Mon, 30 Jan 2023 17:31:49 +0100 Subject: [PATCH 79/91] ci: update generic workflows (#181) --- .../workflows/add-good-first-issue-labels.yml | 33 ++++++++++--------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/.github/workflows/add-good-first-issue-labels.yml b/.github/workflows/add-good-first-issue-labels.yml index 20234526..e70a7001 100644 --- a/.github/workflows/add-good-first-issue-labels.yml +++ b/.github/workflows/add-good-first-issue-labels.yml @@ -4,36 +4,37 @@ # Purpose of this workflow is to enable anyone to label issue with 'Good First Issue' and 'area/*' with a single command. name: Add 'Good First Issue' and 'area/*' labels # if proper comment added -on: - issue_comment: - types: - - created +on: + issue_comment: + types: + - created jobs: add-labels: - if: ${{!github.event.issue.pull_request && github.event.issue.state != 'closed' && github.actor != 'asyncapi-bot'}} + if: ${{(!github.event.issue.pull_request && github.event.issue.state != 'closed' && github.actor != 'asyncapi-bot') && (contains(github.event.comment.body, '/good-first-issue') || contains(github.event.comment.body, '/gfi' ))}} runs-on: ubuntu-latest steps: - name: Add label - if: contains(github.event.comment.body, '/good-first-issue') || contains(github.event.comment.body, '/gfi' ) uses: actions/github-script@v5 with: github-token: ${{ secrets.GH_TOKEN }} script: | const areas = ['javascript', 'typescript', 'java' , 'go', 'docs', 'ci-cd', 'design']; - const values = context.payload.comment.body.trim().split(" "); - switch(values[1]){ + const words = context.payload.comment.body.trim().split(" "); + const areaIndex = words.findIndex((word)=> word === '/gfi' || word === '/good-first-issue') + 1 + let area = words[areaIndex]; + switch(area){ case 'ts': - values[1] = 'typescript'; + area = 'typescript'; break; case 'js': - values[1] = 'javascript'; + area = 'javascript'; break; case 'markdown': - values[1] = 'docs'; + area = 'docs'; break; } - if(values.length != 2 || !areas.includes(values[1])){ + if(!areas.includes(area)){ const message = `Hey @${context.payload.sender.login}, your message doesn't follow the requirements, you can try \`/help\`.` await github.rest.issues.createComment({ @@ -44,14 +45,14 @@ jobs: }) } else { - //remove complexity and areas if there are any before adding new labels; + // remove area if there is any before adding new labels. const currentLabels = (await github.rest.issues.listLabelsOnIssue({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, })).data.map(label => label.name); - const shouldBeRemoved = currentLabels.filter(label => (label.startsWith('area/') && !label.endsWith(values[1]))); + const shouldBeRemoved = currentLabels.filter(label => (label.startsWith('area/') && !label.endsWith(area)); shouldBeRemoved.forEach(label => { github.rest.issues.deleteLabel({ owner: context.repo.owner, @@ -60,11 +61,11 @@ jobs: }); }); - //add new labels + // Add new labels. github.rest.issues.addLabels({ issue_number: context.issue.number, owner: context.repo.owner, repo: context.repo.repo, - labels: ['good first issue', `area/${values[1]}`] + labels: ['good first issue', `area/${area}`] }); } From b9cf60c76d89eccc2a702daeda01aa5442297d65 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Mon, 13 Feb 2023 20:32:01 +0100 Subject: [PATCH 80/91] ci: update generic workflows (#186) --- .github/workflows/add-good-first-issue-labels.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/add-good-first-issue-labels.yml b/.github/workflows/add-good-first-issue-labels.yml index e70a7001..06e470d8 100644 --- a/.github/workflows/add-good-first-issue-labels.yml +++ b/.github/workflows/add-good-first-issue-labels.yml @@ -52,7 +52,7 @@ jobs: repo: context.repo.repo, })).data.map(label => label.name); - const shouldBeRemoved = currentLabels.filter(label => (label.startsWith('area/') && !label.endsWith(area)); + const shouldBeRemoved = currentLabels.filter(label => (label.startsWith('area/') && !label.endsWith(area))); shouldBeRemoved.forEach(label => { github.rest.issues.deleteLabel({ owner: context.repo.owner, From 685b181f872f95ff2b432e2ddf71a284d0dd4204 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Tue, 14 Mar 2023 10:16:09 +0100 Subject: [PATCH 81/91] ci: update generic workflows (#190) --- .github/workflows/welcome-first-time-contrib.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/welcome-first-time-contrib.yml b/.github/workflows/welcome-first-time-contrib.yml index e72fecef..1eab8fd7 100644 --- a/.github/workflows/welcome-first-time-contrib.yml +++ b/.github/workflows/welcome-first-time-contrib.yml @@ -21,7 +21,7 @@ jobs: with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | - const issueMessage = `Welcome to AsyncAPI. Thanks a lot for reporting your first issue. Please check out our [contributors guide](https://github.com/asyncapi/community/blob/master/CONTRIBUTING.md) and the instructions about a [basic recommended setup](https://github.com/asyncapi/.github/blob/master/git-workflow.md) useful for opening a pull request.
Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out [this issue](https://github.com/asyncapi/asyncapi/issues/115).`; + const issueMessage = `Welcome to AsyncAPI. Thanks a lot for reporting your first issue. Please check out our [contributors guide](https://github.com/asyncapi/community/blob/master/CONTRIBUTING.md) and the instructions about a [basic recommended setup](https://github.com/asyncapi/community/blob/master/git-workflow.md) useful for opening a pull request.
Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out [this issue](https://github.com/asyncapi/asyncapi/issues/115).`; const prMessage = `Welcome to AsyncAPI. Thanks a lot for creating your first pull request. Please check out our [contributors guide](https://github.com/asyncapi/community/blob/master/CONTRIBUTING.md) useful for opening a pull request.
Keep in mind there are also other channels you can use to interact with AsyncAPI community. For more details check out [this issue](https://github.com/asyncapi/asyncapi/issues/115).`; if (!issueMessage && !prMessage) { throw new Error('Action must have at least one of issue-message or pr-message set'); From d9795550ab6f888e132344984f1055fe11bd47ee Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Tue, 28 Mar 2023 09:42:50 +0200 Subject: [PATCH 82/91] ci: update generic workflows (#191) --- .github/workflows/link-check-cron.yml | 37 --------------------------- .github/workflows/link-check-pr.yml | 28 -------------------- 2 files changed, 65 deletions(-) delete mode 100644 .github/workflows/link-check-cron.yml delete mode 100644 .github/workflows/link-check-pr.yml diff --git a/.github/workflows/link-check-cron.yml b/.github/workflows/link-check-cron.yml deleted file mode 100644 index 873d4297..00000000 --- a/.github/workflows/link-check-cron.yml +++ /dev/null @@ -1,37 +0,0 @@ -# This workflow is centrally managed in https://github.com/asyncapi/.github/ -# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo - -name: Check Markdown links (Weekly) - -on: - workflow_dispatch: - schedule: - # At 00:00 UTC on every Monday - - cron: '0 0 * * 0' - -jobs: - External-link-validation-weekly: - if: startsWith(github.repository, 'asyncapi/') - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - # Checks the status of hyperlinks in .md files - - name: Check links - uses: gaurav-nelson/github-action-markdown-link-check@0a51127e9955b855a9bbfa1ff5577f1d1338c9a5 #1.0.14 but pointing to commit for security reasons - with: - use-quiet-mode: 'yes' - use-verbose-mode: 'yes' - - # A configuration file can be included, indicating the properties of the link check action - # More information can be found here: https://github.com/tcort/markdown-link-check#config-file-format - # Create mlc_config.json file in the root of the directory - - - name: Report workflow run status to Slack - uses: 8398a7/action-slack@v3 - with: - status: ${{ job.status }} - fields: repo,action,workflow - env: - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_CI_FAIL_NOTIFY }} - if: failure() # Only, on failure, send a message on the 94_bot-failing-ci slack channel diff --git a/.github/workflows/link-check-pr.yml b/.github/workflows/link-check-pr.yml deleted file mode 100644 index 51f6cf78..00000000 --- a/.github/workflows/link-check-pr.yml +++ /dev/null @@ -1,28 +0,0 @@ -# This workflow is centrally managed in https://github.com/asyncapi/.github/ -# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo - -name: Check Markdown links - -on: - pull_request_target: - types: [synchronize, ready_for_review, opened, reopened] - paths: - - '**.md' - -jobs: - External-link-validation-on-PR: - runs-on: ubuntu-latest - steps: - - name: Checkout repo - uses: actions/checkout@v3 - - name: Check links - uses: gaurav-nelson/github-action-markdown-link-check@0a51127e9955b855a9bbfa1ff5577f1d1338c9a5 #1.0.14 but pointing to commit for security reasons - with: - use-quiet-mode: 'yes' - use-verbose-mode: 'yes' - check-modified-files-only: 'yes' # Only modified files are checked on PRs - - - # A configuration file can be included, indicating the properties of the link check action - # More information can be found here: https://github.com/tcort/markdown-link-check#config-file-format - # Create mlc_config.json file in the root of the directory From 53efd818c3118ab0bf2d6f0c6d4e5606d66a2cf9 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Tue, 4 Apr 2023 20:42:48 +0200 Subject: [PATCH 83/91] ci: update generic workflows (#194) --- .github/workflows/lint-pr-title.yml | 4 ++-- .github/workflows/sentiment-analysis.yml | 2 +- .github/workflows/welcome-first-time-contrib.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml index c4a942a9..b3015a45 100644 --- a/.github/workflows/lint-pr-title.yml +++ b/.github/workflows/lint-pr-title.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: # Since this workflow is REQUIRED for a PR to be mergable, we have to have this 'if' statement in step level instead of job level. - - if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors"]'), github.actor) }} + - if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors[bot]"]'), github.actor) }} uses: amannn/action-semantic-pull-request@505e44b4f33b4c801f063838b3f053990ee46ea7 #version 4.6.0 id: lint_pr_title env: @@ -24,7 +24,7 @@ jobs: The subject "{subject}" found in the pull request title "{title}" should start with a lowercase character. # Comments the error message from the above lint_pr_title action - - if: ${{ always() && steps.lint_pr_title.outputs.error_message != null && !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors"]'), github.actor)}} + - if: ${{ always() && steps.lint_pr_title.outputs.error_message != null && !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors[bot]"]'), github.actor)}} name: Comment on PR uses: marocchino/sticky-pull-request-comment@39c5b5dc7717447d0cba270cd115037d32d28443 #version 2.2 with: diff --git a/.github/workflows/sentiment-analysis.yml b/.github/workflows/sentiment-analysis.yml index cd8ab05f..f1f845a0 100644 --- a/.github/workflows/sentiment-analysis.yml +++ b/.github/workflows/sentiment-analysis.yml @@ -26,7 +26,7 @@ on: - edited jobs: sentiments: - if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors"]'), github.actor) }} + if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors[bot]"]'), github.actor) }} name: Checking sentiments runs-on: ubuntu-latest steps: diff --git a/.github/workflows/welcome-first-time-contrib.yml b/.github/workflows/welcome-first-time-contrib.yml index 1eab8fd7..34fb25d2 100644 --- a/.github/workflows/welcome-first-time-contrib.yml +++ b/.github/workflows/welcome-first-time-contrib.yml @@ -14,7 +14,7 @@ on: jobs: welcome: name: Post welcome message - if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors"]'), github.actor) }} + if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors[bot]"]'), github.actor) }} runs-on: ubuntu-latest steps: - uses: actions/github-script@v3 From 86f7d3ebbd06de4e03b0a48fc84e0e280a660ab7 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Thu, 13 Apr 2023 09:10:26 +0200 Subject: [PATCH 84/91] ci: update generic workflows (#195) --- .../workflows/add-good-first-issue-labels.yml | 2 +- ...d-ready-to-merge-or-do-not-merge-label.yml | 6 +- .../automerge-for-humans-merging.yml | 2 +- ...ns-remove-ready-to-merge-label-on-edit.yml | 2 +- .github/workflows/automerge-orphans.yml | 8 +- .github/workflows/automerge.yml | 6 +- .github/workflows/help-command.yml | 60 +++++--- .../workflows/issues-prs-notifications.yml | 18 ++- .github/workflows/lint-pr-title.yml | 6 +- .../workflows/notify-tsc-members-mention.yml | 132 +++++++++--------- .github/workflows/release-announcements.yml | 16 ++- .github/workflows/sentiment-analysis.yml | 45 ------ .../workflows/welcome-first-time-contrib.yml | 6 +- 13 files changed, 145 insertions(+), 164 deletions(-) delete mode 100644 .github/workflows/sentiment-analysis.yml diff --git a/.github/workflows/add-good-first-issue-labels.yml b/.github/workflows/add-good-first-issue-labels.yml index 06e470d8..2ae3a056 100644 --- a/.github/workflows/add-good-first-issue-labels.yml +++ b/.github/workflows/add-good-first-issue-labels.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Add label - uses: actions/github-script@v5 + uses: actions/github-script@v6 with: github-token: ${{ secrets.GH_TOKEN }} script: | diff --git a/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml b/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml index 79c8079e..66606fc1 100644 --- a/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml +++ b/.github/workflows/automerge-for-humans-add-ready-to-merge-or-do-not-merge-label.yml @@ -26,7 +26,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Add ready-to-merge label - uses: actions/github-script@v5 + uses: actions/github-script@v6 with: github-token: ${{ secrets.GH_TOKEN }} script: | @@ -76,7 +76,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Add do-not-merge label - uses: actions/github-script@v5 + uses: actions/github-script@v6 with: github-token: ${{ secrets.GH_TOKEN }} script: | @@ -98,7 +98,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Add autoupdate label - uses: actions/github-script@v5 + uses: actions/github-script@v6 with: github-token: ${{ secrets.GH_TOKEN }} script: | diff --git a/.github/workflows/automerge-for-humans-merging.yml b/.github/workflows/automerge-for-humans-merging.yml index 4bce61da..9ba0be90 100644 --- a/.github/workflows/automerge-for-humans-merging.yml +++ b/.github/workflows/automerge-for-humans-merging.yml @@ -43,7 +43,7 @@ jobs: | join("\n")' multiline: true - name: Automerge PR - uses: pascalgn/automerge-action@v0.14.3 + uses: pascalgn/automerge-action@22948e0bc22f0aa673800da838595a3e7347e584 #v0.15.6 https://github.com/pascalgn/automerge-action/releases/tag/v0.15.6 env: GITHUB_TOKEN: "${{ secrets.GH_TOKEN }}" MERGE_LABELS: "!do-not-merge,ready-to-merge" diff --git a/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml b/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml index b8fc9904..00e7f993 100644 --- a/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml +++ b/.github/workflows/automerge-for-humans-remove-ready-to-merge-label-on-edit.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Remove label - uses: actions/github-script@v5 + uses: actions/github-script@v6 with: github-token: ${{ secrets.GH_TOKEN }} script: | diff --git a/.github/workflows/automerge-orphans.yml b/.github/workflows/automerge-orphans.yml index 20322ecb..a1776853 100644 --- a/.github/workflows/automerge-orphans.yml +++ b/.github/workflows/automerge-orphans.yml @@ -13,8 +13,10 @@ jobs: name: Find orphans and notify runs-on: ubuntu-latest steps: + - name: Checkout repository + uses: actions/checkout@v3 - name: Get list of orphans - uses: actions/github-script@v3 + uses: actions/github-script@v6 id: orphans with: github-token: ${{ secrets.GITHUB_TOKEN }} @@ -50,10 +52,10 @@ jobs: } - if: steps.orphans.outputs.found == 'true' name: Convert markdown to slack markdown - uses: LoveToKnow/slackify-markdown-action@v1.0.0 + uses: asyncapi/.github/.github/actions/slackify-markdown@master id: issuemarkdown with: - text: "-> [${{steps.orphans.outputs.title}}](${{steps.orphans.outputs.url}})" + markdown: "-> [${{steps.orphans.outputs.title}}](${{steps.orphans.outputs.url}})" - if: steps.orphans.outputs.found == 'true' name: Send info about orphan to slack uses: rtCamp/action-slack-notify@v2 diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 052a19c3..6dab6094 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -19,12 +19,12 @@ jobs: runs-on: ubuntu-latest steps: - name: Autoapproving - uses: hmarr/auto-approve-action@v2 + uses: hmarr/auto-approve-action@44888193675f29a83e04faf4002fa8c0b537b1e4 # v3.2.1 is used https://github.com/hmarr/auto-approve-action/releases/tag/v3.2.1 with: github-token: "${{ secrets.GH_TOKEN_BOT_EVE }}" - name: Label autoapproved - uses: actions/github-script@v5 + uses: actions/github-script@v6 with: github-token: ${{ secrets.GH_TOKEN }} script: | @@ -41,7 +41,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Automerging - uses: pascalgn/automerge-action@v0.13.0 + uses: pascalgn/automerge-action@22948e0bc22f0aa673800da838595a3e7347e584 #v0.15.6 https://github.com/pascalgn/automerge-action/releases/tag/v0.15.6 env: GITHUB_TOKEN: "${{ secrets.GH_TOKEN }}" GITHUB_LOGIN: asyncapi-bot diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index 03f891eb..f4955c22 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -13,32 +13,48 @@ jobs: if: ${{ github.event.issue.pull_request && contains(github.event.comment.body, '/help') && github.actor != 'asyncapi-bot' }} runs-on: ubuntu-latest steps: - - uses: actions-ecosystem/action-create-comment@v1 + - name: Add comment to PR + uses: actions/github-script@v6 with: - github_token: ${{ secrets.GH_TOKEN }} - body: | - Hello, @${{ github.actor }}! 👋🏼 + github-token: ${{ secrets.GH_TOKEN }} + script: | + //Yes to add comment to PR the same endpoint is use that we use to create a comment in issue + //For more details http://developer.github.com/v3/issues/comments/ + //Also proved by this action https://github.com/actions-ecosystem/action-create-comment/blob/main/src/main.ts + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `Hello, @${{ github.actor }}! 👋🏼 + + I'm Genie from the magic lamp. Looks like somebody needs a hand! 🆘 + + At the moment the following comments are supported in pull requests: + + - `/ready-to-merge` or `/rtm` - This comment will trigger automerge of PR in case all required checks are green, approvals in place and do-not-merge label is not added + - `/do-not-merge` or `/dnm` - This comment will block automerging even if all conditions are met and ready-to-merge label is added + - `/autoupdate` or `/au` - This comment will add `autoupdate` label to the PR and keeps your PR up-to-date to the target branch's future changes. Unless there is a merge conflict or it is a draft PR.` + }) - I'm Genie from the magic lamp. Looks like somebody needs a hand! 🆘 - - At the moment the following comments are supported in pull requests: - - - `/ready-to-merge` or `/rtm` - This comment will trigger automerge of PR in case all required checks are green, approvals in place and do-not-merge label is not added - - `/do-not-merge` or `/dnm` - This comment will block automerging even if all conditions are met and ready-to-merge label is added - - `/autoupdate` or `/au` - This comment will add `autoupdate` label to the PR and keeps your PR up-to-date to the target branch's future changes. Unless there is a merge conflict or it is a draft PR. create_help_comment_issue: if: ${{ !github.event.issue.pull_request && contains(github.event.comment.body, '/help') && github.actor != 'asyncapi-bot' }} runs-on: ubuntu-latest steps: - - uses: actions-ecosystem/action-create-comment@v1 + - name: Add comment to Issue + uses: actions/github-script@v6 with: - github_token: ${{ secrets.GH_TOKEN }} - body: | - Hello, @${{ github.actor }}! 👋🏼 - - I'm Genie from the magic lamp. Looks like somebody needs a hand! 🆘 - - At the moment the following comments are supported in issues: - - - `/good-first-issue {js | ts | java | go | docs | design | ci-cd} ` or `/gfi {js | ts | java | go | docs | design | ci-cd} ` - label an issue as a `good first issue`. - example: `/gfi js` or `/good-first-issue ci-cd` + github-token: ${{ secrets.GH_TOKEN }} + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: `Hello, @${{ github.actor }}! 👋🏼 + + I'm Genie from the magic lamp. Looks like somebody needs a hand! 🆘 + + At the moment the following comments are supported in issues: + + - `/good-first-issue {js | ts | java | go | docs | design | ci-cd} ` or `/gfi {js | ts | java | go | docs | design | ci-cd} ` - label an issue as a `good first issue`. + example: `/gfi js` or `/good-first-issue ci-cd` + }) diff --git a/.github/workflows/issues-prs-notifications.yml b/.github/workflows/issues-prs-notifications.yml index ca665404..78ebe960 100644 --- a/.github/workflows/issues-prs-notifications.yml +++ b/.github/workflows/issues-prs-notifications.yml @@ -20,11 +20,13 @@ jobs: name: Notify slack on every new issue runs-on: ubuntu-latest steps: + - name: Checkout repository + uses: actions/checkout@v3 - name: Convert markdown to slack markdown for issue - uses: LoveToKnow/slackify-markdown-action@v1.0.0 + uses: asyncapi/.github/.github/actions/slackify-markdown@master id: issuemarkdown with: - text: "[${{github.event.issue.title}}](${{github.event.issue.html_url}}) \n ${{github.event.issue.body}}" + markdown: "[${{github.event.issue.title}}](${{github.event.issue.html_url}}) \n ${{github.event.issue.body}}" - name: Send info about issue uses: rtCamp/action-slack-notify@v2 env: @@ -38,11 +40,13 @@ jobs: name: Notify slack on every new pull request runs-on: ubuntu-latest steps: + - name: Checkout repository + uses: actions/checkout@v3 - name: Convert markdown to slack markdown for pull request - uses: LoveToKnow/slackify-markdown-action@v1.0.0 + uses: asyncapi/.github/.github/actions/slackify-markdown@master id: prmarkdown with: - text: "[${{github.event.pull_request.title}}](${{github.event.pull_request.html_url}}) \n ${{github.event.pull_request.body}}" + markdown: "[${{github.event.pull_request.title}}](${{github.event.pull_request.html_url}}) \n ${{github.event.pull_request.body}}" - name: Send info about pull request uses: rtCamp/action-slack-notify@v2 env: @@ -56,11 +60,13 @@ jobs: name: Notify slack on every new pull request runs-on: ubuntu-latest steps: + - name: Checkout repository + uses: actions/checkout@v3 - name: Convert markdown to slack markdown for pull request - uses: LoveToKnow/slackify-markdown-action@v1.0.0 + uses: asyncapi/.github/.github/actions/slackify-markdown@master id: discussionmarkdown with: - text: "[${{github.event.discussion.title}}](${{github.event.discussion.html_url}}) \n ${{github.event.discussion.body}}" + markdown: "[${{github.event.discussion.title}}](${{github.event.discussion.html_url}}) \n ${{github.event.discussion.body}}" - name: Send info about pull request uses: rtCamp/action-slack-notify@v2 env: diff --git a/.github/workflows/lint-pr-title.yml b/.github/workflows/lint-pr-title.yml index b3015a45..77aa1c6e 100644 --- a/.github/workflows/lint-pr-title.yml +++ b/.github/workflows/lint-pr-title.yml @@ -14,7 +14,7 @@ jobs: steps: # Since this workflow is REQUIRED for a PR to be mergable, we have to have this 'if' statement in step level instead of job level. - if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors[bot]"]'), github.actor) }} - uses: amannn/action-semantic-pull-request@505e44b4f33b4c801f063838b3f053990ee46ea7 #version 4.6.0 + uses: amannn/action-semantic-pull-request@c3cd5d1ea3580753008872425915e343e351ab54 #version 5.2.0 https://github.com/amannn/action-semantic-pull-request/releases/tag/v5.2.0 id: lint_pr_title env: GITHUB_TOKEN: ${{ secrets.GH_TOKEN}} @@ -26,7 +26,7 @@ jobs: # Comments the error message from the above lint_pr_title action - if: ${{ always() && steps.lint_pr_title.outputs.error_message != null && !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors[bot]"]'), github.actor)}} name: Comment on PR - uses: marocchino/sticky-pull-request-comment@39c5b5dc7717447d0cba270cd115037d32d28443 #version 2.2 + uses: marocchino/sticky-pull-request-comment@3d60a5b2dae89d44e0c6ddc69dd7536aec2071cd #use 2.5.0 https://github.com/marocchino/sticky-pull-request-comment/releases/tag/v2.5.0 with: header: pr-title-lint-error GITHUB_TOKEN: ${{ secrets.GH_TOKEN}} @@ -40,7 +40,7 @@ jobs: # deletes the error comment if the title is correct - if: ${{ steps.lint_pr_title.outputs.error_message == null }} name: delete the comment - uses: marocchino/sticky-pull-request-comment@39c5b5dc7717447d0cba270cd115037d32d28443 #version 2.2 + uses: marocchino/sticky-pull-request-comment@3d60a5b2dae89d44e0c6ddc69dd7536aec2071cd #use 2.5.0 https://github.com/marocchino/sticky-pull-request-comment/releases/tag/v2.5.0 with: header: pr-title-lint-error delete: true diff --git a/.github/workflows/notify-tsc-members-mention.yml b/.github/workflows/notify-tsc-members-mention.yml index e681c18a..d72fd85b 100644 --- a/.github/workflows/notify-tsc-members-mention.yml +++ b/.github/workflows/notify-tsc-members-mention.yml @@ -31,14 +31,22 @@ jobs: name: TSC notification on every new issue runs-on: ubuntu-latest steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' ######### # Handling Slack notifications ######### - name: Convert markdown to slack markdown - uses: LoveToKnow/slackify-markdown-action@v1.0.0 + uses: asyncapi/.github/.github/actions/slackify-markdown@master id: issuemarkdown with: - text: "[${{github.event.issue.title}}](${{github.event.issue.html_url}}) \n ${{github.event.issue.body}}" + markdown: "[${{github.event.issue.title}}](${{github.event.issue.html_url}}) \n ${{github.event.issue.body}}" - name: Send info about issue uses: rtCamp/action-slack-notify@v2 env: @@ -49,19 +57,11 @@ jobs: ######### # Handling Mailchimp notifications ######### - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - name: Install deps run: npm install working-directory: ./.github/workflows/scripts/mailchimp - name: Send email with MailChimp - uses: actions/github-script@v4 + uses: actions/github-script@v6 env: CALENDAR_ID: ${{ secrets.CALENDAR_ID }} CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} @@ -76,14 +76,22 @@ jobs: name: TSC notification on every new pull request runs-on: ubuntu-latest steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' ######### # Handling Slack notifications ######### - name: Convert markdown to slack markdown - uses: LoveToKnow/slackify-markdown-action@v1.0.0 + uses: asyncapi/.github/.github/actions/slackify-markdown@master id: prmarkdown with: - text: "[${{github.event.pull_request.title}}](${{github.event.pull_request.html_url}}) \n ${{github.event.pull_request.body}}" + markdown: "[${{github.event.pull_request.title}}](${{github.event.pull_request.html_url}}) \n ${{github.event.pull_request.body}}" - name: Send info about pull request uses: rtCamp/action-slack-notify@v2 env: @@ -94,19 +102,11 @@ jobs: ######### # Handling Mailchimp notifications ######### - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - name: Install deps run: npm install working-directory: ./.github/workflows/scripts/mailchimp - name: Send email with MailChimp - uses: actions/github-script@v4 + uses: actions/github-script@v6 env: CALENDAR_ID: ${{ secrets.CALENDAR_ID }} CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} @@ -121,14 +121,22 @@ jobs: name: TSC notification on every new discussion runs-on: ubuntu-latest steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' ######### # Handling Slack notifications ######### - name: Convert markdown to slack markdown - uses: LoveToKnow/slackify-markdown-action@v1.0.0 + uses: asyncapi/.github/.github/actions/slackify-markdown@master id: discussionmarkdown with: - text: "[${{github.event.discussion.title}}](${{github.event.discussion.html_url}}) \n ${{github.event.discussion.body}}" + markdown: "[${{github.event.discussion.title}}](${{github.event.discussion.html_url}}) \n ${{github.event.discussion.body}}" - name: Send info about pull request uses: rtCamp/action-slack-notify@v2 env: @@ -139,19 +147,11 @@ jobs: ######### # Handling Mailchimp notifications ######### - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - name: Install deps run: npm install working-directory: ./.github/workflows/scripts/mailchimp - name: Send email with MailChimp - uses: actions/github-script@v4 + uses: actions/github-script@v6 env: CALENDAR_ID: ${{ secrets.CALENDAR_ID }} CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} @@ -166,14 +166,22 @@ jobs: name: TSC notification on every new comment in issue runs-on: ubuntu-latest steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' ######### # Handling Slack notifications ######### - name: Convert markdown to slack markdown - uses: LoveToKnow/slackify-markdown-action@v1.0.0 + uses: asyncapi/.github/.github/actions/slackify-markdown@master id: issuemarkdown with: - text: "[${{github.event.issue.title}}](${{github.event.comment.html_url}}) \n ${{github.event.comment.body}}" + markdown: "[${{github.event.issue.title}}](${{github.event.comment.html_url}}) \n ${{github.event.comment.body}}" - name: Send info about issue comment uses: rtCamp/action-slack-notify@v2 env: @@ -184,19 +192,11 @@ jobs: ######### # Handling Mailchimp notifications ######### - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - name: Install deps run: npm install working-directory: ./.github/workflows/scripts/mailchimp - name: Send email with MailChimp - uses: actions/github-script@v4 + uses: actions/github-script@v6 env: CALENDAR_ID: ${{ secrets.CALENDAR_ID }} CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} @@ -211,14 +211,22 @@ jobs: name: TSC notification on every new comment in pr runs-on: ubuntu-latest steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' ######### # Handling Slack notifications ######### - name: Convert markdown to slack markdown - uses: LoveToKnow/slackify-markdown-action@v1.0.0 + uses: asyncapi/.github/.github/actions/slackify-markdown@master id: prmarkdown with: - text: "[${{github.event.issue.title}}](${{github.event.comment.html_url}}) \n ${{github.event.comment.body}}" + markdown: "[${{github.event.issue.title}}](${{github.event.comment.html_url}}) \n ${{github.event.comment.body}}" - name: Send info about PR comment uses: rtCamp/action-slack-notify@v2 env: @@ -229,19 +237,11 @@ jobs: ######### # Handling Mailchimp notifications ######### - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - name: Install deps run: npm install working-directory: ./.github/workflows/scripts/mailchimp - name: Send email with MailChimp - uses: actions/github-script@v4 + uses: actions/github-script@v6 env: CALENDAR_ID: ${{ secrets.CALENDAR_ID }} CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} @@ -256,14 +256,22 @@ jobs: name: TSC notification on every new comment in discussion runs-on: ubuntu-latest steps: + - name: Checkout repository + uses: actions/checkout@v3 + - name: Setup Node.js + uses: actions/setup-node@v3 + with: + node-version: 16 + cache: 'npm' + cache-dependency-path: '**/package-lock.json' ######### # Handling Slack notifications ######### - name: Convert markdown to slack markdown - uses: LoveToKnow/slackify-markdown-action@v1.0.0 + uses: asyncapi/.github/.github/actions/slackify-markdown@master id: discussionmarkdown with: - text: "[${{github.event.discussion.title}}](${{github.event.comment.html_url}}) \n ${{github.event.comment.body}}" + markdown: "[${{github.event.discussion.title}}](${{github.event.comment.html_url}}) \n ${{github.event.comment.body}}" - name: Send info about discussion comment uses: rtCamp/action-slack-notify@v2 env: @@ -274,19 +282,11 @@ jobs: ######### # Handling Mailchimp notifications ######### - - name: Checkout repository - uses: actions/checkout@v2 - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: 16 - cache: 'npm' - cache-dependency-path: '**/package-lock.json' - name: Install deps run: npm install working-directory: ./.github/workflows/scripts/mailchimp - name: Send email with MailChimp - uses: actions/github-script@v4 + uses: actions/github-script@v6 env: CALENDAR_ID: ${{ secrets.CALENDAR_ID }} CALENDAR_SERVICE_ACCOUNT: ${{ secrets.CALENDAR_SERVICE_ACCOUNT }} diff --git a/.github/workflows/release-announcements.yml b/.github/workflows/release-announcements.yml index b2f3ba76..9587cace 100644 --- a/.github/workflows/release-announcements.yml +++ b/.github/workflows/release-announcements.yml @@ -14,11 +14,13 @@ jobs: name: Slack - notify on every release runs-on: ubuntu-latest steps: + - name: Checkout repository + uses: actions/checkout@v3 - name: Convert markdown to slack markdown for issue - uses: LoveToKnow/slackify-markdown-action@v1.0.0 + uses: asyncapi/.github/.github/actions/slackify-markdown@master id: markdown with: - text: "[${{github.event.release.tag_name}}](${{github.event.release.html_url}}) \n ${{ github.event.release.body }}" + markdown: "[${{github.event.release.tag_name}}](${{github.event.release.html_url}}) \n ${{ github.event.release.body }}" - name: Send info about release to Slack uses: rtCamp/action-slack-notify@v2 env: @@ -32,9 +34,9 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repo - uses: actions/checkout@v2 + uses: actions/checkout@v3 - name: Get version of last and previous release - uses: actions/github-script@v3 + uses: actions/github-script@v6 id: versions with: github-token: ${{ secrets.GITHUB_TOKEN }} @@ -60,15 +62,15 @@ jobs: - name: Identify release type id: releasetype # if previousver is not provided then this steps just logs information about missing version, no errors - run: echo "::set-output name=type::$(npx -q -p semver-diff-cli semver-diff ${{steps.versions.outputs.previousver}} ${{steps.versions.outputs.lastver}})" + run: echo "type=$(npx -q -p semver-diff-cli semver-diff ${{steps.versions.outputs.previousver}} ${{steps.versions.outputs.lastver}})" >> $GITHUB_OUTPUT - name: Get name of the person that is behind the newly released version id: author - run: echo "::set-output name=name::$(git log -1 --pretty=format:'%an')" + run: echo "name=$(git log -1 --pretty=format:'%an')" >> $GITHUB_OUTPUT - name: Publish information about the release to Twitter # tweet only if detected version change is not a patch # tweet goes out even if the type is not major or minor but "You need provide version number to compare." # it is ok, it just means we did not identify previous version as we are tweeting out information about the release for the first time if: steps.releasetype.outputs.type != 'null' && steps.releasetype.outputs.type != 'patch' # null means that versions are the same - uses: m1ner79/Github-Twittction@v1.0.1 + uses: m1ner79/Github-Twittction@d1e508b6c2170145127138f93c49b7c46c6ff3a7 # using 2.0.0 https://github.com/m1ner79/Github-Twittction/releases/tag/v2.0.0 with: twitter_status: "Release ${{github.event.release.tag_name}} for ${{github.repository}} is out in the wild 😱💪🍾🎂\n\nThank you for the contribution ${{ steps.author.outputs.name }} ${{github.event.release.html_url}}" twitter_consumer_key: ${{ secrets.TWITTER_CONSUMER_KEY }} diff --git a/.github/workflows/sentiment-analysis.yml b/.github/workflows/sentiment-analysis.yml deleted file mode 100644 index f1f845a0..00000000 --- a/.github/workflows/sentiment-analysis.yml +++ /dev/null @@ -1,45 +0,0 @@ -# This action is centrally managed in https://github.com/asyncapi/.github/ -# Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo - -name: 'Sentiment Analysis' - -on: - issue_comment: - types: - - created - - edited - issues: - types: - - opened - - edited - pull_request: - types: - - opened - - edited - pull_request_review: - types: - - submitted - - edited - pull_request_review_comment: - types: - - created - - edited -jobs: - sentiments: - if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors[bot]"]'), github.actor) }} - name: Checking sentiments - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Check sentiment - uses: derberg/code-of-conduct-sentiment-analysis-github-action@v1 - id: sentiments - with: - gcp_key: ${{ secrets.GCP_KEY_SENTIMENT }} - - uses: someimportantcompany/github-actions-slack-message@v1 - # this step runs only if sentiment is a negative number - if: steps.sentiments.outputs.sentiment < -0.6 - with: - webhook-url: ${{ secrets.SLACK_SENTIMENTS }} - text: Here ${{steps.sentiments.outputs.source}} you can find a potential negative text that requires your attention as the sentiment analysis score is ${{steps.sentiments.outputs.sentiment}} - color: orange \ No newline at end of file diff --git a/.github/workflows/welcome-first-time-contrib.yml b/.github/workflows/welcome-first-time-contrib.yml index 34fb25d2..cbc23ce7 100644 --- a/.github/workflows/welcome-first-time-contrib.yml +++ b/.github/workflows/welcome-first-time-contrib.yml @@ -17,7 +17,7 @@ jobs: if: ${{ !contains(fromJson('["asyncapi-bot", "dependabot[bot]", "dependabot-preview[bot]", "allcontributors[bot]"]'), github.actor) }} runs-on: ubuntu-latest steps: - - uses: actions/github-script@v3 + - uses: actions/github-script@v6 with: github-token: ${{ secrets.GITHUB_TOKEN }} script: | @@ -65,7 +65,7 @@ jobs: if (isIssue) { const issueNumber = context.payload.issue.number; console.log(`Adding message: ${message} to issue #${issueNumber}`); - await github.issues.createComment({ + await github.rest.issues.createComment({ owner: context.payload.repository.owner.login, repo: context.payload.repository.name, issue_number: issueNumber, @@ -75,7 +75,7 @@ jobs: else { const pullNumber = context.payload.pull_request.number; console.log(`Adding message: ${message} to pull request #${pullNumber}`); - await github.pulls.createReview({ + await github.rest.pulls.createReview({ owner: context.payload.repository.owner.login, repo: context.payload.repository.name, pull_number: pullNumber, From 8a86a0235ffc809852a4c431f7dfc04dcc5e6915 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Thu, 13 Apr 2023 16:48:02 +0200 Subject: [PATCH 85/91] ci: update generic workflows (#196) Co-authored-by: asyncapi-bot --- .github/workflows/automerge.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/automerge.yml b/.github/workflows/automerge.yml index 6dab6094..9253675c 100644 --- a/.github/workflows/automerge.yml +++ b/.github/workflows/automerge.yml @@ -1,7 +1,7 @@ # This action is centrally managed in https://github.com/asyncapi/.github/ # Don't make changes to this file in this repo as they will be overwritten with changes made to the same file in above mentioned repo. -name: Automerge release bump PR +name: Automerge PRs from bots on: pull_request_target: @@ -41,7 +41,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Automerging - uses: pascalgn/automerge-action@22948e0bc22f0aa673800da838595a3e7347e584 #v0.15.6 https://github.com/pascalgn/automerge-action/releases/tag/v0.15.6 + uses: pascalgn/automerge-action@22948e0bc22f0aa673800da838595a3e7347e584 #v0.15.6 https://github.com/pascalgn/automerge-action/releases/tag/v0.15.6 env: GITHUB_TOKEN: "${{ secrets.GH_TOKEN }}" GITHUB_LOGIN: asyncapi-bot From 42a10eddd5c88a009a005aa3c2970561e20deef9 Mon Sep 17 00:00:00 2001 From: Adam Retter Date: Mon, 26 Jun 2023 19:48:34 +0200 Subject: [PATCH 86/91] feat: first draft of JMS Binding Objects (#193) --- CODEOWNERS | 1 + jms/README.md | 132 ++++++++++++++++++++++++++++++++-- jms/json_schemas/channel.json | 40 +++++++++++ jms/json_schemas/message.json | 73 +++++++++++++++++++ jms/json_schemas/server.json | 69 ++++++++++++++++++ 5 files changed, 308 insertions(+), 7 deletions(-) create mode 100644 jms/json_schemas/channel.json create mode 100644 jms/json_schemas/message.json create mode 100644 jms/json_schemas/server.json diff --git a/CODEOWNERS b/CODEOWNERS index 2feaec97..3ba67600 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -10,6 +10,7 @@ /anypointmq/ @GeraldLoeffler /ibmmq/ @rcoppen +/jms/ @rcoppen @SrfHead /kafka/ @lbroudoux @dalelane /googlepubsub/ @whitlockjc /solace/ @damaru-inc @CameronRushton diff --git a/jms/README.md b/jms/README.md index 4e4d327d..28f2b0d6 100644 --- a/jms/README.md +++ b/jms/README.md @@ -3,25 +3,97 @@ This document defines how to describe JMS-specific information on AsyncAPI. +## Versions -## Version +The version of this bindings specification is `0.0.1`. +This is also the `bindingVersion` for all binding objects defined by this specification. +In any given binding object, `latest` MAY alternatively be used to refer to the currently latest published version of this bindings specification. -Current version is `0.1.0`. +## Terminology + +The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this bindings specification are to be interpreted as described in IETF [RFC2119](https://www.ietf.org/rfc/rfc2119.txt). + +## Protocol + +These bindings use the `jms` [protocol](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#definitionsProtocol) in AsyncAPI documents to denote connections to and interactions with JMS message brokers. + +JMS is not technically a protocol, rather it is an API. A JMS Provider implements the JMS API and may define a protocol for implementing JMS API operations. Regardless, for the purposes of AsyncAPI we can treat it like a "protocol" to enable AsyncAPI definitions that are somewhat portable between various JMS Providers. If necessary, the user is free to combine this binding with other bindings that implement a JMS Provider (e.g. [Apache Pulsar](https://github.com/asyncapi/bindings/tree/master/pulsar), [Amazon SQS](https://github.com/asyncapi/bindings/tree/master/sqs), [IBM MQ](https://github.com/asyncapi/bindings/tree/master/ibmmq), etc.) to detail JMS Provider specific configuration. + +**NOTE** that from protocol version 3.0, this binding is compatible with [Jakarta Messaging](https://jakarta.ee/specifications/messaging). + +## Server Object + +The fields of the standard [Server Object](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#serverObject) are constrained and interpreted as follows: + +Server Object Field Name | Values for JMS Protocol | Description +---|:---|:--- +`protocol` | `jms` | **REQUIRED**. MUST be `jms` for the scope of this specification. +`url` | e.g., `jms://host:port` | **REQUIRED**. MUST be a URL containing the hostname and port of a JMS Broker. +`protocolVersion` | e.g., `3.1` | **OPTIONAL**, defaults to `3.1`. If present MUST be the version indicator of the JMS API. Valid values are `1.0`, `1.0.1`, `1.0.1a`, `1.0.2`, `1.0.2a`, `1.0.2b`, `1.1`, `2.0`, `2.0a`, `2.1`, or `3.0`, `3.1.`. ## Server Binding Object -This object MUST NOT contain any properties. Its name is reserved for future use. - +The JMS [Server Binding Object](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#server-bindings-object) is defined by a [JSON Schema](json_schemas/server.json), which defines these fields: + +Field Name | Type | Description +---|:---:|--- +`jmsConnectionFactory` | string | **REQUIRED**. The classname of the [ConnectionFactory](https://docs.oracle.com/javaee/7/api/javax/jms/ConnectionFactory.html) implementation for the JMS Provider. +`properties` | [Schema Array](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#schemaArray) | **OPTIONAL**. Additional properties to set on the JMS ConnectionFactory implementation for the JMS Provider. +`clientID` | string | **OPTIONAL**. A client identifier for applications that use this JMS connection factory. If the Client ID Policy is set to 'Restricted' (the default), then configuring a Client ID on the [ConnectionFactory](https://docs.oracle.com/javaee/7/api/javax/jms/ConnectionFactory.html) prevents more than one JMS client from using a connection from this factory. +`bindingVersion` | string | **OPTIONAL**, defaults to `latest`. The version of this binding. + +### Examples + +The following example shows a `servers` object with a server binding object for `jms` with JMS specific properties: + +```yaml +servers: + production: + url: jms://my-activemq-broker:61616 + protocol: jms + protocolVersion: '1.1' + description: The production ActiveMQ broker accessed via JMS. + bindings: + jms: + # JMS protocol specific server details + jmsConnectionFactory: org.apache.activemq.ActiveMQConnectionFactory + properties: + - name: disableTimeStampsByDefault + value: false + clientID: my-application-1 +``` - ## Channel Binding Object -This object MUST NOT contain any properties. Its name is reserved for future use. +The JMS [Channel Binding Object](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#channel-bindings-object) is defined by a [JSON Schema](json_schemas/channel.json), which defines these fields: + +Field Name | Type | Description +---|:---:|--- +`destination` | string | **OPTIONAL**, defaults to the channel name. The destination (queue) name for this channel. SHOULD only be specified if the channel name differs from the actual destination name, such as when the channel name is not a valid destination name according to the JMS Provider. +`destinationType` | string | **OPTIONAL**, defaults to `queue`. The type of destination, which MUST be either `queue`, or `fifo-queue`. SHOULD be specified to document the messaging model (point-to-point, or strict message ordering) supported by this channel. +`bindingVersion` | string | **OPTIONAL**, defaults to `latest`. The version of this binding. + +### Examples + +The following example shows a `channels` object with two channels, the second having a channel binding object for `jms`: + +```yaml +channels: + user.signup: + description: This application receives command messages from this channel about users to sign up. + bindings: + jms: + destination: user-sign-up + destinationType: fifo-queue + bindingVersion: '0.0.1' + publish: + #... +``` @@ -37,4 +109,50 @@ This object MUST NOT contain any properties. Its name is reserved for future use ## Message Binding Object -This object MUST NOT contain any properties. Its name is reserved for future use. +The JMS [Message Binding Object](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#message-bindings-object) is defined by a [JSON Schema](json_schemas/message.json), which defines these fields: + +Field Name | Type | Description +---|:---:|--- +`headers` | [Schema Object](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#schemaObject) | **OPTIONAL**. A Schema object containing the definitions for JMS specific headers (so-called protocol headers). This schema MUST be of type `object` and have a `properties` key. Examples of JMS protocol headers are `JMSMessageID`, `JMSTimestamp`, and `JMSCorrelationID`. +`bindingVersion` | string | **OPTIONAL**, defaults to `latest`. The version of this binding. + +Note that application headers must be specified in the [`headers` field of the standard Message Object](https://github.com/asyncapi/spec/blob/master/spec/asyncapi.md#messageObjectHeaders) and are set as [Message Properties](https://docs.oracle.com/javaee/7/api/javax/jms/Message.html#Message%20Properties) of the JMS Message; how they are transmitted is defined by the JMS Provider and need not be considered here. +In contrast, protocol headers such as `JMSMessageID` must be specified in the [`headers` field of the message binding object](#messageBindingObjectHeaders) and are transmitted in the [`headers` section of the JMS message](https://docs.oracle.com/javaee/7/api/javax/jms/Message.html#Message%20Headers). + +### Examples + +The following example shows a `message` object with both application specific headers, and a message binding object for `jms` with JMS specific headers: + +```yaml +message: + messageId: my-message-1 + bindings: + jms: + headers: + # JMS protocol specific message headers + required: + - JMSMessageID + properties: + JMSMessageID: + name: JMSMessageID + description: A unique message identifier. This may be set by your JMS Provider on your behalf. + type: string + JMSReplyTo: + name: JMSReplyTo + description: The queue or topic that the message sender expects replies to. + type: string + headers: + # Application specific message headers + required: + - MyToken + - MyOperationID + properties: + MyToken: + name: MyToken + description: Some sort of identificaton token for the publishing application. + type: string + MyOperationID: + name: MyOperationID + description: Some sort of unique identifier for the application operation to perform. + type: string +``` diff --git a/jms/json_schemas/channel.json b/jms/json_schemas/channel.json new file mode 100644 index 00000000..4f441cc4 --- /dev/null +++ b/jms/json_schemas/channel.json @@ -0,0 +1,40 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/jms/channel.json", + "title": "Channel Schema", + "description": "This object contains configuration for describing a JMS queue, or FIFO queue as an AsyncAPI channel. This objects only contains configuration that can not be provided in the AsyncAPI standard channel object.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.7.7/schemas/2.0.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "destination": { + "type": "string", + "description": "The destination (queue) name for this channel. SHOULD only be specified if the channel name differs from the actual destination name, such as when the channel name is not a valid destination name according to the JMS Provider. Defaults to the channel name." + }, + "destinationType": { + "type": "string", + "enum": ["queue", "fifo-queue"], + "default": "queue", + "description": "The type of destination. SHOULD be specified to document the messaging model (point-to-point, or strict message ordering) supported by this channel." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.0.1" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + + }, + "examples": [ + { + "destination": "user-signed-up", + "destinationType": "fifo-queue", + "bindingVersion": "0.0.1" + } + ] +} diff --git a/jms/json_schemas/message.json b/jms/json_schemas/message.json new file mode 100644 index 00000000..4c14347e --- /dev/null +++ b/jms/json_schemas/message.json @@ -0,0 +1,73 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/jms/message.json", + "title": "Message Schema", + "description": "This object contains configuration for describing a JMS message as an AsyncAPI message. This objects only contains configuration that can not be provided in the AsyncAPI standard message object.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.7.7/schemas/2.0.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "headers": { + "$ref": "https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.7.7/schemas/2.0.0.json#/definitions/schema", + "description": "A Schema object containing the definitions for JMS headers (protocol headers). This schema MUST be of type 'object' and have a 'properties' key. Examples of JMS protocol headers are 'JMSMessageID', 'JMSTimestamp', and 'JMSCorrelationID'." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.0.1" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + + }, + "examples": [ + { + "headers": { + "type": "object", + "required": ["JMSMessageID"], + "properties": { + "JMSMessageID": { + "type": ["string", "null"], + "description": "A unique message identifier. This may be set by your JMS Provider on your behalf." + }, + "JMSTimestamp": { + "type": "integer", + "description": "The time the message was sent. This may be set by your JMS Provider on your behalf. The time the message was sent. The value of the timestamp is the amount of time, measured in milliseconds, that has elapsed since midnight, January 1, 1970, UTC." + }, + "JMSDeliveryMode": { + "type": "string", + "enum": ["PERSISTENT", "NON_PERSISTENT"], + "default": "PERSISTENT", + "description": "Denotes the delivery mode for the message. This may be set by your JMS Provider on your behalf." + }, + "JMSPriority": { + "type": "integer", + "default": 4, + "description": "The priority of the message. This may be set by your JMS Provider on your behalf." + }, + "JMSExpires": { + "type": "integer", + "description": "The time at which the message expires. This may be set by your JMS Provider on your behalf. A value of zero means that the message does not expire. Any non-zero value is the amount of time, measured in milliseconds, that has elapsed since midnight, January 1, 1970, UTC, at which the message will expire." + }, + "JMSType": { + "type": ["string", "null"], + "description": "The type of message. Some JMS providers use a message repository that contains the definitions of messages sent by applications. The 'JMSType' header field may reference a message's definition in the provider's repository. The JMS API does not define a standard message definition repository, nor does it define a naming policy for the definitions it contains. Some messaging systems require that a message type definition for each application message be created and that each message specify its type. In order to work with such JMS providers, JMS clients should assign a value to 'JMSType', whether the application makes use of it or not. This ensures that the field is properly set for those providers that require it." + }, + "JMSCorrelationID": { + "type": ["string", "null"], + "description": "The correlation identifier of the message. A client can use the 'JMSCorrelationID' header field to link one message with another. A typical use is to link a response message with its request message. Since each message sent by a JMS provider is assigned a message ID value, it is convenient to link messages via message ID, such message ID values must start with the 'ID:' prefix. Conversely, application-specified values must not start with the 'ID:' prefix; this is reserved for provider-generated message ID values." + }, + "JMSReplyTo": { + "type": "string", + "description": "The queue or topic that the message sender expects replies to." + } + } + }, + "bindingVersion": "0.0.1" + } + ] +} diff --git a/jms/json_schemas/server.json b/jms/json_schemas/server.json new file mode 100644 index 00000000..c2810f7e --- /dev/null +++ b/jms/json_schemas/server.json @@ -0,0 +1,69 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/jms/server.json", + "title": "Server Schema", + "description": "This object contains configuration for describing a JMS broker as an AsyncAPI server. This objects only contains configuration that can not be provided in the AsyncAPI standard server object.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/asyncapi-node/v2.7.7/schemas/2.0.0.json#/definitions/specificationExtension" + } + }, + "required": ["jmsConnectionFactory"], + "properties": { + "jmsConnectionFactory": { + "type": "string", + "description": "The classname of the ConnectionFactory implementation for the JMS Provider." + }, + "properties": { + "type": "array", + "items": { + "$ref": "#/schemas/property" + }, + "description": "Additional properties to set on the JMS ConnectionFactory implementation for the JMS Provider." + }, + "clientID": { + "type": "string", + "description": "A client identifier for applications that use this JMS connection factory. If the Client ID Policy is set to 'Restricted' (the default), then configuring a Client ID on the ConnectionFactory prevents more than one JMS client from using a connection from this factory." + }, + "bindingVersion": { + "type": "string", + "enum": [ + "0.0.1" + ], + "description": "The version of this binding. If omitted, 'latest' MUST be assumed." + } + + }, + "schemas": { + "property": { + "type": "object", + "required": ["name", "value"], + "properties": { + "name": { + "type": "string", + "description": "The name of a property" + }, + "value": { + "type": ["string", "boolean", "number", "null"], + "description": "The name of a property" + } + } + } + }, + "examples": [ + { + "jmsConnectionFactory": "org.apache.activemq.ActiveMQConnectionFactory", + "properties": [ + { + "name": "disableTimeStampsByDefault", + "value": false + } + ], + "clientID": "my-application-1", + "bindingVersion": "0.0.1" + } + ] + } + \ No newline at end of file From 676efb9af806be477912421c1796f33904bcb701 Mon Sep 17 00:00:00 2001 From: asyncapi-bot Date: Thu, 29 Jun 2023 15:18:21 +0200 Subject: [PATCH 87/91] ci: update of files from global .github repo (#210) --- .github/workflows/help-command.yml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/.github/workflows/help-command.yml b/.github/workflows/help-command.yml index f4955c22..d4ba4a44 100644 --- a/.github/workflows/help-command.yml +++ b/.github/workflows/help-command.yml @@ -27,13 +27,13 @@ jobs: repo: context.repo.repo, body: `Hello, @${{ github.actor }}! 👋🏼 - I'm Genie from the magic lamp. Looks like somebody needs a hand! 🆘 + I'm 🧞🧞🧞 Genie 🧞🧞🧞 from the magic lamp. Looks like somebody needs a hand! At the moment the following comments are supported in pull requests: - - `/ready-to-merge` or `/rtm` - This comment will trigger automerge of PR in case all required checks are green, approvals in place and do-not-merge label is not added - - `/do-not-merge` or `/dnm` - This comment will block automerging even if all conditions are met and ready-to-merge label is added - - `/autoupdate` or `/au` - This comment will add `autoupdate` label to the PR and keeps your PR up-to-date to the target branch's future changes. Unless there is a merge conflict or it is a draft PR.` + - \`/ready-to-merge\` or \`/rtm\` - This comment will trigger automerge of PR in case all required checks are green, approvals in place and do-not-merge label is not added + - \`/do-not-merge\` or \`/dnm\` - This comment will block automerging even if all conditions are met and ready-to-merge label is added + - \`/autoupdate\` or \`/au\` - This comment will add \`autoupdate\` label to the PR and keeps your PR up-to-date to the target branch's future changes. Unless there is a merge conflict or it is a draft PR.` }) create_help_comment_issue: @@ -51,10 +51,10 @@ jobs: repo: context.repo.repo, body: `Hello, @${{ github.actor }}! 👋🏼 - I'm Genie from the magic lamp. Looks like somebody needs a hand! 🆘 + I'm 🧞🧞🧞 Genie 🧞🧞🧞 from the magic lamp. Looks like somebody needs a hand! At the moment the following comments are supported in issues: - - `/good-first-issue {js | ts | java | go | docs | design | ci-cd} ` or `/gfi {js | ts | java | go | docs | design | ci-cd} ` - label an issue as a `good first issue`. - example: `/gfi js` or `/good-first-issue ci-cd` - }) + - \`/good-first-issue {js | ts | java | go | docs | design | ci-cd}\` or \`/gfi {js | ts | java | go | docs | design | ci-cd}\` - label an issue as a \`good first issue\`. + example: \`/gfi js\` or \`/good-first-issue ci-cd\`` + }) \ No newline at end of file From a7f7f64587377d9fd45cbc4c8d09de96c7280dab Mon Sep 17 00:00:00 2001 From: Ian Cooper Date: Wed, 19 Jul 2023 15:23:06 +0100 Subject: [PATCH 88/91] docs: proposed set of bindings for AWS - SNS and SQS (#84) Co-authored-by: Dec Kolakowski <51292634+dpwdec@users.noreply.github.com>%0ACo-authored-by: Dec Kolakowski %0ACo-authored-by: Lukasz Gornicki %0ACo-authored-by: ssemyonov --- CODEOWNERS | 2 + sns/README.md | 273 ++++++++++++++++++++++++++++++- sns/SNS-HTTP.png | Bin 0 -> 50765 bytes sns/SNS-SQS-Pub-Sub.png | Bin 0 -> 47278 bytes sns/json_schemas/channel.json | 150 +++++++++++++++++ sns/json_schemas/operation.json | 277 ++++++++++++++++++++++++++++++++ sqs/README.md | 216 ++++++++++++++++++++++++- sqs/SNS-SQS-Pub-Sub.png | Bin 0 -> 47278 bytes sqs/SQS-Point-To-Point.png | Bin 0 -> 37204 bytes sqs/json_schemas/channel.json | 246 ++++++++++++++++++++++++++++ sqs/json_schemas/operation.json | 239 +++++++++++++++++++++++++++ 11 files changed, 1391 insertions(+), 12 deletions(-) create mode 100644 sns/SNS-HTTP.png create mode 100644 sns/SNS-SQS-Pub-Sub.png create mode 100644 sns/json_schemas/channel.json create mode 100644 sns/json_schemas/operation.json create mode 100644 sqs/SNS-SQS-Pub-Sub.png create mode 100644 sqs/SQS-Point-To-Point.png create mode 100644 sqs/json_schemas/channel.json create mode 100644 sqs/json_schemas/operation.json diff --git a/CODEOWNERS b/CODEOWNERS index 3ba67600..b97cdeea 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -15,4 +15,6 @@ /googlepubsub/ @whitlockjc /solace/ @damaru-inc @CameronRushton /pulsar/ @VisualBean +/sns/ @dpwdec @iancooper +/sqs/ @dpwdec @iancooper *.json @KhudaDad414 diff --git a/sns/README.md b/sns/README.md index 6a0f81d9..26a78576 100644 --- a/sns/README.md +++ b/sns/README.md @@ -15,23 +15,288 @@ Current version is `0.1.0`. This object MUST NOT contain any properties. Its name is reserved for future use. - - ## Channel Binding Object -This object MUST NOT contain any properties. Its name is reserved for future use. +This object contains information about the channel representation in SNS. + +We represent an AsyncAPI Channel with a Topic in SNS. The bindings here allow definition of a topic within SNS. We provide properties on the binding that allow creation of a topic in infrastructure-as-code scenarios. Be aware that although the binding offers that flexibility, it may be more maintainable to specify properties such as SNS Access Control Policy outside of AsyncAPI. + +SNS supports many optional properties. To mark a channel as SNS, but use default values for the channel properties, just use an empty object {}. + +### Fields + +|Field Name | Type | Description| +|---|:---:|---| +| `name` | string | **Required.** The name of the topic. Can be different from the channel name to allow flexibility around AWS resource naming limitations.| +| `ordering` | [ordering](#ordering)| **Optional.** By default, we assume an unordered SNS topic. This field allows configuration of a FIFO SNS Topic. | +| `policy` |[policy](#policy) | **Optional.** The security policy for the SNS Topic | +| `tags` |Object | **Optional.** Key-value pairs that represent AWS tags on the topic. | +|`bindingVersion` | string | **Optional**, defaults to `latest`. The version of this binding.| + +### Schemas + +#### Ordering +|Field Name | Type | Description| +|---|:---:|---| +| `type` | string | **Required.** Defines the type of SNS Topic. Can be either `standard` or `FIFO`. | +| `contentBasedDeduplication` | boolean | **Optional.** Whether the de-duplication of messages should be turned on. Defaults to `false`| + +#### Policy +|Field Name | Type | Description| +|---|:---:|---| +| `statements` | [[Statement](#statement)] | **Required.** An array of Statement objects, each of which controls a permission for this topic | + +#### Statement +|Field Name | Type | Description| +|---|:---:|---| +| `effect` | string |**Required.** Either "Allow" or "Deny"| +| `principal` | string or array of string |**Required.** The AWS account or resource ARN that this statement applies to| +| `action` | string or array of string |**Required.** The SNS permission being allowed or denied e.g. sns:Publish| + +##### Examples +Just use defaults +```yaml +channels: + user-signedup: + description: A user has signed up to our service + bindings: + sns: {} +``` + +Minimal definition, just policy + +```yaml +channels: + user-signedup: + description: A user has signed up to our service + bindings: + sns: + policy: + statements: + - effect : Allow + principal: * + action: SNS:Publish +``` ## Operation Binding Object -This object MUST NOT contain any properties. Its name is reserved for future use. +This object contains information operation binding in SNS. + +We represent SNS producers via a **subscribe** Operation Object. In simple cases this may not require configuration, and can be shown as an empty SNS Binding Object i.e. {} if you need to explicitly indicate how a producer publishes to the channel. + +We represent SNS consumers via a **publish** Operation Object. These consumers need an SNS Subscription that defines how they consume from SNS i.e. the protocol that they use, and any filters applied. + +The SNS binding does not describe the receiver.If you wish to define the receiver, add a **publish** Operation Binding Object for that receiver. For example, if you send message to an SQS queue from an SNS Topic, you would add a protocol of 'sqs' and an Identifier object for the queue. That identifier could be an ARN of a queue defined outside of the scope of AsyncAPI, but if you wanted to define the receiver you would use the name of a queue defined in an SQS Binding on the **publish** Operation Binding Object. + +We support an array of consumers via the **consumers** field. This allows you to represent multiple protocols consuming an SNS Topic in one file. You may also use it for multiple consumers with the same protocol, instead of representing each consumer in a separate file. + +### Fields + +| Field Name | Type | Applies To | Description | +|---|:---:|:---:|---| +| `topic` | [identifier](#identifier) |Publish, Subscribe| **Optional.** Often we can assume that the SNS Topic is the channel name-we provide this field in case the you need to supply the ARN, or the Topic name is not the channel name in the AsyncAPI document.| +| `consumers` | [[Consumer](#consumer)] |Publish| **Required.** The protocols that listen to this topic and their endpoints.| +| `deliveryPolicy` | [deliveryPolicy](#delivery-policy) |Subscribe| **Optional.** Policy for retries to HTTP. The field is the default for HTTP receivers of the [SNS Topic](https://docs.aws.amazon.com/sns/latest/api/API_CreateTopic.html) which may be overridden by a specific consumer.| +|`bindingVersion` | string |Publish, Subscribe| **Optional**, defaults to `latest`. The version of this binding.| + +### Schemas + +#### Consumer + +| Field Name | Type | Description | +|---|:---:|---| +| `protocol` | string | **Required.** The protocol that this endpoint receives messages by. Can be `http`, `https`, `email`, `email-json`, `sms`, `sqs`, `application`, `lambda` or `firehose` | +| `endpoint` |[identifier](#identifier)| **Required.** The endpoint messages are delivered to. | +| `filterPolicy` | [filterPolicy](#filter-policy) | **Optional.** Only receive a subset of messages from the channel, determined by this policy. | +| `rawMessageDelivery` | boolean | **Required.** If *true* AWS SNS attributes are removed from the body, and for SQS, SNS message attributes are copied to SQS message attributes. If *false* the SNS attributes are included in the body. | +| `redrivePolicy` | [redrivePolicy](#redrive-policy) | **Optional.** Prevent poison pill messages by moving un-processable messages to an SQS dead letter queue. | +| `deliveryPolicy` | [deliveryPolicy](#delivery-policy) | **Optional.** Policy for retries to HTTP. The parameter is for that [SNS Subscription](https://docs.aws.amazon.com/sns/latest/api/API_Subscribe.html) and overrides any policy on the [SNS Topic](https://docs.aws.amazon.com/sns/latest/api/API_CreateTopic.html). | +| `displayName` | string |**Optional.** The display name to use with an SMS subscription | + + +#### Delivery Policy +|Field Name | Type | Description| +|---|:---:|---| +| `minDelayTarget` | integer | **Optional.** The minimum delay for a retry in seconds | +| `maxDelayTarget` | integer | **Optional.** The maximum delay for a retry in seconds | +| `numRetries` | integer | **Optional.** The total number of retries, including immediate, pre-backoff, backoff, and post-backoff retries | +| `numNoDelayRetries` | integer | **Optional.** The number of immediate retries (with no delay) | +| `numMinDelayRetries` | integer | **Optional.** The number of immediate retries (with delay) | +| `numMaxDelayRetries` | integer | **Optional.** The number of post-backoff phase retries, with the maximum delay between retries | +| `backoffFunction` | string, one of: arithmetic, exponential, geometric or linear | **Optional.** The algorithm for backoff between retries | +| `maxReceivesPerSecond` | integer | **Optional.** The maximum number of deliveries per second, per subscription | + +#### Filter Policy +|Field Name | Type | Description| +|---|:---:|---| +| `attributes` |Map(string, array or string or object) | **Required.** A map of a message attribute to an array of possible matches. The match may be a simple string for an exact match, but it may also be an object that represents a constraint and values for that constraint | + +#### Identifier +|Field Name | Type | Description| +|---|:---:|---| +|`url` |string| **Optional.** The endpoint is a URL | +|`email` |string| **Optional.** The endpoint is an email address | +|`phone` |string| **Optional.** The endpoint is a phone number| +|`arn` |string| **Optional.** The target is an [ARN](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html). For example, for SQS, the identifier may be an ARN, which will be of the form: ["arn:aws:sqs:{region}:{account-id}:{queueName}"](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html)| +|`name` |string| **Optional.** The endpoint is identified by a name, which corresponds to an identifying field called 'name' of a binding for that protocol on this **publish** Operation Object. For example, if the protocol is 'sqs' then the name refers to the name field **sqs** binding. We don't use $ref because we are referring, not including. | + +We provide an Identifer Object to support providing the identifier of an externally defined endpoint for this SNS *publication* to target, or an endpoint on another binding against this Operation Object (via the name field). + +#### Redrive Policy + +|Field Name | Type | Description| +|---|:---:|---| +| `deadLetterQueue` |[Identifier](#identifier)| **Required.** The SQS queue to use as a dead letter queue (DLQ). Note that you may have a Redrive Policy to put messages that cannot be delivered to an SQS queue, even if you use another protocol to consume messages from the queue, so it is defined at the level of the SNS Operation Binding Object in a Consumer Object (and is applied as part of an [SNS Subscription](https://docs.aws.amazon.com/sns/latest/dg/sns-create-subscribe-endpoint-to-topic.html)). The SQS Binding describes how to define an SQS Binding that supports defining the target SQS of the Redrive Policy. | +| `maxReceiveCount` |integer| **Optional.** The number of times a message is delivered to the source queue before being moved to the dead-letter queue. Defaults to 10. | + +### Examples + +#### SNS to SQS Pub-Sub + +[](SNS-SQS-Pub-Sub.png) + +We are producing to an SNS channel + +```yaml +channels: + user-signedup: + description: A user has signed up for our service + binding : + sns: {} # Indicates that the channel is an SNS Topic + subscribe: + operationId: sendMessage + description: send messages to the topic + bindings: + sns: + consumers: + - protocol: sqs + endpoint: + name: myQueue + rawMessageDelivery: false +``` + +We are consuming an SNS channel, using an SQS queue. A separate file specifies the producer, and has the SNS Bindings for the channel. For this reason we do not repeat the SNS binding information for the channel here, to avoid duplicated definitions diverging. Instead we just define the **publish** Operation Binding. + +In this version, the SQS queue is defined elsewhere, and we just reference via its ARN. It is worth noting that this couples the specification to the AWS *region* and *account*, which are part of the ARN, and if we moved the queue to a new region or account was this specification would need to be updated to reflect that. + + +```yaml +channels: + user-signedup: + description: A user has signed up for our service + publish: + operationId: receiveMessage + description: receive messages from the topic + bindings: + sns: + consumers: + - protocol: sqs + endpoint: + arn: arn:aws:sqs:us-west-2:123456789012:UserSignedUpQueue + rawMessageDelivery: true +``` + +We are consuming an SNS channel, using an SQS queue. A separate file specifies the producer, and has the SNS Bindings for the channel. For this reason we do not repeat the SNS binding information for the channel here, to avoid duplicated definitions diverging. Instead we just define the **publish** Operation Binding. + +In this version, the SQS queue is defined in this file, and we reference it by name. For brevity that definition is not shown here. See the SQS Binding Object for more. + +```yaml +channels: + user-signedup: + description: A user has signed up for our service + publish: + operationId: receiveMessage + description: receive messages from the topic + bindings: + sns: + consumers: + - protocol: sqs + endpoint: + name: user-signedup-queue # refers to a queue defined in this file, but not shown in this example + rawMessageDelivery: true + filterPolicy: + attributes: + reason: + anything-but: password-reset + redrivePolicy: + deadLetterQueue: + name: user-signedup-queue-dlq # refers toa queue defined in this file, but not show in this example +``` + +#### SNS to HTTP Pub Sub + +[](SNS-HTTP.png) + +We are producing to an SNS channel. + +In this version, we define a default delivery policy for any HTTP based consumers + +```yaml +channels: + user-signedup: + description: A user has signed up for our service + bindings: + sns: + policy: + statements: + - effect : Allow + principal: * + action: SNS:Publish + subscribe: + operationId: sendMessage + description: send messages to the topic + bindings: + sns: + deliveryPolicy: + minDelayTarget: 1 + maxDelayTarget: 60 + numRetries: 50 + numNoDelayRetries: 3 + numMinDelayRetries: 2 + numMaxDelayRetries: 35 + backoffFunction: exponential + maxReceivesPerSecond: 10 +``` +We are consuming an SNS channel, using an HTTP endpoint, which is defined in this AsyncAPI file. For brevity we do not show an http endpoint here. The delivery policy here is defined for the http consumer and overrides any policy set by the producer +```yaml +channels: + user-signedup: + description: A user has signed up for our service + bindings: + sns: {} # Indicates that the channel is an SNS Topic, but assumes defined by producer + publish: + operationId: receiveMessage + description: receive messages from the topic + bindings: + sns: + - protocol: http + endpoint: + url: http://login.my.com/user/new + filterPolicy: + attributes: + reason: + anything-but: password-reset + deliveryPolicy: + minDelayTarget: 1 + maxDelayTarget: 120 + numRetries: 30 + numNoDelayRetries: 3 + numMinDelayRetries: 2 + numMaxDelayRetries: 25 + backoffFunction: exponential + maxReceivesPerSecond: 20 + redrivePolicy: + deadLetterQueue: + name: user-signedup-queue-dlq # refers toa queue defined in this file, but not show in this example +``` diff --git a/sns/SNS-HTTP.png b/sns/SNS-HTTP.png new file mode 100644 index 0000000000000000000000000000000000000000..72e52c730be93cbf230db6336eb611958fc3adf2 GIT binary patch literal 50765 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 Q7T||5IcxkFeI@Dt02psn>Hq)$ literal 0 HcmV?d00001 diff --git a/sns/json_schemas/channel.json b/sns/json_schemas/channel.json new file mode 100644 index 00000000..6ab5e2f6 --- /dev/null +++ b/sns/json_schemas/channel.json @@ -0,0 +1,150 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/sns/channel.json", + "title": "Channel Schema", + "description": "This object contains information about the channel representation in SNS.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "name": { + "type": "string", + "description": "The name of the topic. Can be different from the channel name to allow flexibility around AWS resource naming limitations." + }, + "ordering": { + "$ref": "#/definitions/ordering" + }, + "policy": { + "$ref": "#/definitions/policy" + }, + "tags": { + "type": "object", + "description": "Key-value pairs that represent AWS tags on the topic." + }, + "bindingVersion": { + "type": "string", + "description": "The version of this binding.", + "default": "latest" + } + }, + "required": [ + "name" + ], + "definitions": { + "ordering": { + "type": "object", + "description": "By default, we assume an unordered SNS topic. This field allows configuration of a FIFO SNS Topic.", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "type": { + "type": "string", + "description": "Defines the type of SNS Topic.", + "enum": [ + "standard", + "FIFO" + ] + }, + "contentBasedDeduplication": { + "type": "boolean", + "description": "True to turn on de-duplication of messages for a channel." + } + }, + "required": [ + "type" + ] + }, + "policy": { + "type": "object", + "description": "The security policy for the SNS Topic.", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "statements": { + "type": "array", + "description": "An array of statement objects, each of which controls a permission for this topic", + "items": { + "$ref": "#/definitions/statement" + } + } + }, + "required": [ + "statements" + ] + }, + "statement": { + "type": "object", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "effect": { + "type": "string", + "enum": [ + "Allow", + "Deny" + ] + }, + "principal": { + "description": "The AWS account or resource ARN that this statement applies to.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "action": { + "description": "The SNS permission being allowed or denied e.g. sns:Publish", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "effect", + "principal", + "action" + ] + } + }, + "examples": [ + { + "name": "my-sns-topic", + "policy": { + "statements": [ + { + "effect": "Allow", + "principal": "*", + "action": "SNS:Publish" + } + ] + } + } + ] +} diff --git a/sns/json_schemas/operation.json b/sns/json_schemas/operation.json new file mode 100644 index 00000000..df59a327 --- /dev/null +++ b/sns/json_schemas/operation.json @@ -0,0 +1,277 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/sns/operation.json", + "title": "Operation Schema", + "description": "This object contains information about the operation representation in SNS.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "topic": { + "$ref": "#/definitions/identifier", + "description": "Often we can assume that the SNS Topic is the channel name-we provide this field in case the you need to supply the ARN, or the Topic name is not the channel name in the AsyncAPI document." + }, + "consumers": { + "type": "array", + "description": "The protocols that listen to this topic and their endpoints.", + "items": { + "$ref": "#/definitions/consumer" + }, + "minItems": 1 + }, + "deliveryPolicy": { + "$ref": "#/definitions/deliveryPolicy", + "description": "Policy for retries to HTTP. The field is the default for HTTP receivers of the SNS Topic which may be overridden by a specific consumer." + }, + "bindingVersion": { + "type": "string", + "description": "The version of this binding.", + "default": "latest" + } + }, + "required": [ + "consumers" + ], + "definitions": { + "identifier": { + "type": "object", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "url": { + "type": "string", + "description": "The endpoint is a URL." + }, + "email": { + "type": "string", + "description": "The endpoint is an email adress." + }, + "phone": { + "type": "string", + "description": "The endpoint is a phone number." + }, + "arn": { + "type": "string", + "description": "The target is an ARN. For example, for SQS, the identifier may be an ARN, which will be of the form: arn:aws:sqs:{region}:{account-id}:{queueName}" + }, + "name": { + "type": "string", + "description": "The endpoint is identified by a name, which corresponds to an identifying field called 'name' of a binding for that protocol on this publish Operation Object. For example, if the protocol is 'sqs' then the name refers to the name field sqs binding. We don't use $ref because we are referring, not including." + } + } + }, + "consumer": { + "type": "object", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "protocol": { + "description": "The protocol that this endpoint receives messages by.", + "type": "string", + "enum": [ + "http", + "https", + "email", + "email-json", + "sms", + "sqs", + "application", + "lambda", + "firehose" + ] + }, + "endpoint": { + "description": "The endpoint messages are delivered to.", + "$ref": "#/definitions/identifier" + }, + "filterPolicy": { + "$ref": "#/definitions/filterPolicy" + }, + "rawMessageDelivery": { + "type": "boolean", + "description": "If true AWS SNS attributes are removed from the body, and for SQS, SNS message attributes are copied to SQS message attributes. If false the SNS attributes are included in the body." + }, + "redrivePolicy": { + "$ref": "#/definitions/redrivePolicy" + }, + "deliveryPolicy": { + "$ref": "#/definitions/deliveryPolicy", + "description": "Policy for retries to HTTP. The parameter is for that SNS Subscription and overrides any policy on the SNS Topic." + }, + "displayName": { + "type": "string", + "description": "The display name to use with an SNS subscription" + } + }, + "required": [ + "protocol", + "endpoint", + "rawMessageDelivery" + ] + }, + "deliveryPolicy": { + "type": "object", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "minDelayTarget": { + "type": "integer", + "description": "The minimum delay for a retry in seconds." + }, + "maxDelayTarget": { + "type": "integer", + "description": "The maximum delay for a retry in seconds." + }, + "numRetries": { + "type": "integer", + "description": "The total number of retries, including immediate, pre-backoff, backoff, and post-backoff retries." + }, + "numNoDelayRetries": { + "type": "integer", + "description": "The number of immediate retries (with no delay)." + }, + "numMinDelayRetries": { + "type": "integer", + "description": "The number of immediate retries (with delay)." + }, + "numMaxDelayRetries": { + "type": "integer", + "description": "The number of post-backoff phase retries, with the maximum delay between retries." + }, + "backoffFunction": { + "type": "string", + "description": "The algorithm for backoff between retries.", + "enum": [ + "arithmetic", + "exponential", + "geometric", + "linear" + ] + }, + "maxReceivesPerSecond": { + "type": "integer", + "description": "The maximum number of deliveries per second, per subscription." + } + } + }, + "filterPolicy": { + "type": "object", + "description": "Only receive a subset of messages from the channel, determined by this policy.", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "attributes": { + "type": "object", + "description": "A map of a message attribute to an array of possible matches. The match may be a simple string for an exact match, but it may also be an object that represents a constraint and values for that constraint.", + "additionalProperties": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + } + } + }, + "required": [ + "attributes" + ] + }, + "redrivePolicy": { + "type": "object", + "description": "Prevent poison pill messages by moving un-processable messages to an SQS dead letter queue.", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "deadLetterQueue": { + "$ref": "#/definitions/identifier", + "description": "The SQS queue to use as a dead letter queue (DLQ)." + }, + "maxReceiveCount": { + "type": "integer", + "description": "The number of times a message is delivered to the source queue before being moved to the dead-letter queue.", + "default": 10 + } + }, + "required": [ + "deadLetterQueue" + ] + } + }, + "examples": [ + { + "topic": { + "name": "someTopic" + }, + "consumers": [ + { + "protocol": "sqs", + "endpoint": { + "name": "someQueue" + }, + "filterPolicy": { + "attributes": { + "store": [ + "asyncapi_corp" + ], + "event": [ + { + "anything-but": "order_cancelled" + } + ], + "customer_interests": [ + "rugby", + "football", + "baseball" + ] + } + }, + "rawMessageDelivery": false, + "redrivePolicy": { + "deadLetterQueue": { + "arn": "arn:aws:SQS:eu-west-1:0000000:123456789" + }, + "maxReceiveCount": 25 + }, + "deliveryPolicy": { + "minDelayTarget": 10, + "maxDelayTarget": 100, + "numRetries": 5, + "numNoDelayRetries": 2, + "numMinDelayRetries": 3, + "numMaxDelayRetries": 5, + "backoffFunction": "linear", + "maxReceivesPerSecond": 2 + } + } + ] + } + ] +} \ No newline at end of file diff --git a/sqs/README.md b/sqs/README.md index 8bccca3f..1475139d 100644 --- a/sqs/README.md +++ b/sqs/README.md @@ -2,36 +2,236 @@ This document defines how to describe SQS-specific information on AsyncAPI. +SQS can be used both stand-alone as a point-to-point and paired with SNS and as a publish-subscribe channel (where SQS is the endpoint that SNS delivers messages to). For this reason we define a Queue schema, and reference that schema from both a Channel Binding Object and a **publish** Operation Binding Object. + +For point-to-point scenarios, use the Channel Binding Object, as producers send to the queue and consumers receive from it directly. + +For publish-subscribe scenarios, use as a **publish** Operation Binding Object, as the producer sends to SNS and the consumer receives via SQS. + ## Version Current version is `0.1.0`. - ## Server Binding Object This object MUST NOT contain any properties. Its name is reserved for future use. - - -## Channel Binding Object - -This object MUST NOT contain any properties. Its name is reserved for future use. - +## Channel Binding Object + +Use the Channel Binding Operation for Point-to-Point SQS channels. + +There are three likely scenarios for use of the Channel Binding Object: + +- One file defines both publish and subscribe operations, for example if we were implementing the work queue pattern to offload work from an HTTP API endpoint to a worker process. In this case the channel would be defined on the Channel Object in that single file. +- The producer and consumer both have an AsyncAPI specification file, and the producer is raising an event, for example interop between microservices, and the producer 'owns' the channel definition and thus has the SQS Binding on its Channel Object. +- The producer and consumer both have an AsyncAPI specification file, and the consumer receives commands, for example interop between microservices, and the consumer 'owns' the channel definition and thus has the SQS Binding on its Channel Object. + +An SQS queue can set up a Dead Letter Queue as part of a Redelivery Policy. To support this requirement, the Channel Binding Object allows you to define both a Queue Object to use as the Channel or target in a *publish* Operation and a Dead Letter Queue. You can then refer to the Dead letter Queue in the Redrive Policy using the Identifier Object and setting the *name* field to match the *name* field of your Dead Letter Queue Object. (If you define the DLQ externally, the Identifier also supports an ARN). + +### Fields +|Field Name | Type | Description| +|---|:---:|---| +| `queue` | [Queue](#queue)| **Required.** A definition of the queue that will be used as the channel. | +| `deadLetterQueue` | [Queue](#queue)| **Optional.** A definition of the queue that will be used for un-processable messages. | +|`bindingVersion` | string | **Optional**, defaults to `latest`. The version of this binding.| + +### Schemas + +#### Queue +|Field Name | Type | Description| +|---|:---:|---| +| `name` | string | **Required.** The name of the queue. When an [SNS Operation Binding Object]() references an SQS queue by name, the identifier should be the one in this field.| +| `fifoQueue` | boolean | **Required.** Is this a FIFO queue? | +| `deliveryDelay` | integer | **Optional.** The number of seconds to delay before a message sent to the queue can be received. Used to create a *delay queue*. Range is 0 to 15 minutes. Defaults to 0. | +| `visibilityTimeout` |integer| **Optional.** The length of time, in seconds, that a consumer locks a message - hiding it from reads - before it is unlocked and can be read again. Range from 0 to 12 hours (43200 seconds). Defaults to 30 seconds. | +| `receiveMessageWaitTime` |integer| **Optional.** Determines if the queue uses [short polling](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-short-and-long-polling.html) or [long polling](https://docs.aws.amazon.com/AWSSimpleQueueService/latest/SQSDeveloperGuide/sqs-short-and-long-polling.html). Set to zero (the default) the queue reads available messages and returns immediately. Set to a non-zero integer, long polling waits the specified number of seconds for messages to arrive before returning. | +| `messageRetentionPeriod` |integer| **Optional.** How long to retain a message on the queue in seconds, unless deleted. The range is 60 (1 minute) to 1,209,600 (14 days). The default is 345,600 (4 days). | +| `redrivePolicy` | [Redrive Policy](#redrive-policy) | **Optional.** Prevent poison pill messages by moving un-processable messages to an SQS dead letter queue.| +| `policy` |[Policy](#policy) | **Optional.** The security policy for the SQS Queue | +| `tags` |Object | **Optional.** Key-value pairs that represent AWS tags on the queue. | + +#### Identifier +|Field Name | Type | Description| +|---|:---:|---| +|`arn` |string| **Optional.** The target is an [ARN](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html). For example, for SQS, the identifier may be an ARN, which will be of the form: ["arn:aws:sqs:{region}:{account-id}:{queueName}"](https://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html)| +|`name` |string| **Optional.** The endpoint is identified by a name, which corresponds to an identifying field called 'name' of a binding for that protocol on this **publish** Operation Object. For example, if the protocol is 'sqs' then the name refers to the name field **sqs** binding| + +#### Policy +|Field Name | Type | Description| +|---|:---:|---| +| `Statements` | [Statement](#statement) | **Required.** An array of Statement objects, each of which controls a permission for this queue. | + +#### Redrive Policy +|Field Name | Type | Description| +|---|:---:|---| +| `deadLetterQueue` |[Identifier](#identifier)| The SQS queue to use as a dead letter queue (DLQ) | +| `maxReceiveCount` |integer| **Optional.** The number of times a message is delivered to the source queue before being moved to the dead-letter queue. Default is 10. | + +#### Statement +|Field Name | Type | Description| +|---|:---:|---| +| `effect` | string |**Required.** Either "Allow" or "Deny"| +| `principal` | string or array of string |**Required.** The AWS account or resource ARN that this statement applies to| +| `action` | string or array of string |**Required.** The SQS permission being allowed or denied e.g. sqs:ReceiveMessage | ## Operation Binding Object -This object MUST NOT contain any properties. Its name is reserved for future use. +### SQS Point-To-Point + +Because we have defined Queue as part of the Channel Binding Binding object, we do not require Binding information for the **publish** Operation Object of the **subscribe** Operation Object. You can use an empty Queue object ({}) to denote the Binding on the Operation Object, if you want to indicate the protocol used to send or receive for generation purposes such as Infrastructure As Code. + +### SNS to SQS Pub-Sub + +Use the Operation Binding Object when SQS is listening to an SNS Topic. In this case we need to define both an SQS Operation Binding Objects on the receiver **publish** Operation Object to represent the queue definition and we need to define an SNS Operation Binding Object to define the Subscription to SNS that makes your queue a receiver of that endpoint. + +Assuming you have separate AsyncAPI specifications for the producer and the consumer, we would assume the following bindings would appear for an SNS producer and an SQS consumer. +Producer: SNS Channel Binding Object, SNS **subscribe** Operation Binding Object [if required] +Consumer: SNS **publish** Operation Binding Object, SQS **publish** Operation Binding Object +- We assume that the SNS binding information only needs to be present in the producer file (although defining it in both is allowable) and any infrastructure as code dependencies can recognize this. + + +On an Operation Binding Object we support an array of Queue objects. Members of this array may be Queue Objects that define the *endpoint* field required by an [SNS Operation Object]() delivering by the SQS protocol or Queue Objects that define the Dead Letter Queue used by either the Redrive Policy of the SNS Subscription (see the SNS Binding Object) or the [Redrive Policy of the SQS Queue](#redrive-policy). The name of the Queue Object is used by an Identifier field on either the *endpoint* field of the SNS Operation Object of *deadLetterQueue* on the Redrive Policy to identify the required member of this array. + + +### Fields +|Field Name | Type | Description| +|---|:---:|---| +| `queues` | [[Queue](#queue)]| **Required.** Queue objects that are either the *endpoint* for an SNS Operation Binding Object, or the *deadLetterQueue* of the SQS Operation Binding Object | +|`bindingVersion` | string | **Optional**, defaults to `latest`. The version of this binding.| + +### Examples + +#### SQS Point-To-Point + +[](SQS-Point-To-Point.png) + +In this example, we are using SQS for a point-to-point channel. For this example, we assume that we are defining two microservices that communicate over a shared SQS channel, with the consumer receiving events over that channel and the producer owning the channel definition. + +The producer file would look like this: + +```yaml +channels: + user-signedup: + bindings: + sqs: + queue: + name: user-signedup-queue + type: standard + receiveMessageWaitTime: 4 + redrivePolicy: + deadLetterQueue: + name: user-signedup-dlq + policy: + statements: + - effect : Allow + principal: * + action: Sqs:SendMessage + - effect : Allow + principal: * + action: Sqs:ReceiveMessage + deadLetterQueue: + name: user-signedup-dlq + messageRetentionPeriod: 1209600 + type: standard + subscribe: + operationId: sendMessage + description: sends messages when a user has signed up + bindings: + sqs: {} + +``` +In this case we can minimize duplicated information by omitting the binding in our specification, and assume it is picked up from the producer file. We can use an empty object to indicate the SQS Binding on the **publish** Operation Object, if need a marker for generation, otherwise we could omit the Operation Binding Object. + +```yaml +channels: + user-signedup: + publish: + operationId: receiveMessage + description: receives a messages when a user has signed up + bindings: + sqs: {} + +``` + +#### SNS to SQS Pub-Sub + +[](SNS-SQS-Pub-Sub.png) + +In this example, we are using SNS for the channel, and SQS to receive from SNS. + + +The producer files looks like this (see the [SNS Binding]() for more). + +```yaml +channels: + user-signedup: + description: A user has signed up for our service + binding : + sns: {} # Indicates that the channel is an SNS Topic + subscribe: + operationId: sendMessage + description: send messages to the topic + bindings: + sns: + policy: + statements: + - effect : Allow + principal: * + action: SNS:Publish +``` + +And the consumer file would look like this. Note that for simplicity, we choose not to repeat the SNS Binding on the Consumer as it does not 'own' the channel. + + +```yaml +channels: + user-signedup: + description: A user has signed up for our service + publish: + operationId: receiveMessage + description: receive messages from the topic + bindings: + sns: + consumers: + - protocol: sqs + endpoint: + name: user-signedup-queue + rawMessageDelivery: true + filterPolicy: + attributes: + reason: + anything-but: password-reset + redrivePolicy: + deadLetterQueue: + name: user-signedup-queue-dlq + sqs: + queues: + - name: user-signedup-queue + type: standard + receiveMessageWaitTime: 4 + policy: + statements: + - effect : Allow + principal: * + action: Sqs:SendMessage + - effect : Allow + principal: * + action: Sqs:ReceiveMessage + - name: user-signedup-dlq + messageRetentionPeriod: 1209600 + type: standard +``` diff --git a/sqs/SNS-SQS-Pub-Sub.png b/sqs/SNS-SQS-Pub-Sub.png new file mode 100644 index 0000000000000000000000000000000000000000..033d6de7f4b827bc0f53502991fb486a087b3931 GIT binary patch literal 47278 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 Q7T||5IcxkFeI@Dt02psn>Hq)$ literal 0 HcmV?d00001 diff --git a/sqs/SQS-Point-To-Point.png b/sqs/SQS-Point-To-Point.png new file mode 100644 index 0000000000000000000000000000000000000000..a94cfc7b95d2781110ce82cd453c21bf43e30499 GIT binary patch literal 37204 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 literal 0 HcmV?d00001 diff --git a/sqs/json_schemas/channel.json b/sqs/json_schemas/channel.json new file mode 100644 index 00000000..3930780a --- /dev/null +++ b/sqs/json_schemas/channel.json @@ -0,0 +1,246 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/sqs/channel.json", + "title": "Channel Schema", + "description": "This object contains information about the channel representation in SQS.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "queue": { + "description": "A definition of the queue that will be used as the channel.", + "$ref": "#/definitions/queue" + }, + "deadLetterQueue": { + "description": "A definition of the queue that will be used for un-processable messages.", + "$ref": "#/definitions/queue" + }, + "bindingVersion": { + "type": "string", + "description": "The version of this binding.", + "default": "latest" + } + }, + "required": [ + "queue" + ], + "definitions": { + "queue": { + "type": "object", + "description": "A definition of a queue.", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "name": { + "type": "string", + "description": "The name of the queue. When an SNS Operation Binding Object references an SQS queue by name, the identifier should be the one in this field." + }, + "fifoQueue": { + "type": "boolean", + "description": "Is this a FIFO queue?", + "default": false + }, + "deliveryDelay": { + "type": "integer", + "description": "The number of seconds to delay before a message sent to the queue can be received. used to create a delay queue.", + "minimum": 0, + "maximum": 15, + "default": 0 + }, + "visibilityTimeout": { + "type": "integer", + "description": "The length of time, in seconds, that a consumer locks a message - hiding it from reads - before it is unlocked and can be read again.", + "minimum": 0, + "maximum": 43200, + "default": 30 + }, + "receiveMessageWaitTime": { + "type": "integer", + "description": "Determines if the queue uses short polling or long polling. Set to zero the queue reads available messages and returns immediately. Set to a non-zero integer, long polling waits the specified number of seconds for messages to arrive before returning.", + "default": 0 + }, + "messageRetentionPeriod": { + "type": "integer", + "description": "How long to retain a message on the queue in seconds, unless deleted.", + "minimum": 60, + "maximum": 1209600, + "default": 345600 + }, + "redrivePolicy": { + "$ref": "#/definitions/redrivePolicy" + }, + "policy": { + "$ref": "#/definitions/policy" + }, + "tags": { + "type": "object", + "description": "Key-value pairs that represent AWS tags on the queue." + } + }, + "required": [ + "name", + "fifoQueue" + ] + }, + "redrivePolicy": { + "type": "object", + "description": "Prevent poison pill messages by moving un-processable messages to an SQS dead letter queue.", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "deadLetterQueue": { + "$ref": "#/definitions/identifier" + }, + "maxReceiveCount": { + "type": "integer", + "description": "The number of times a message is delivered to the source queue before being moved to the dead-letter queue.", + "default": 10 + } + }, + "required": [ + "deadLetterQueue" + ] + }, + "identifier": { + "type": "object", + "description": "The SQS queue to use as a dead letter queue (DLQ).", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "arn": { + "type": "string", + "description": "The target is an ARN. For example, for SQS, the identifier may be an ARN, which will be of the form: arn:aws:sqs:{region}:{account-id}:{queueName}" + }, + "name": { + "type": "string", + "description": "The endpoint is identified by a name, which corresponds to an identifying field called 'name' of a binding for that protocol on this publish Operation Object. For example, if the protocol is 'sqs' then the name refers to the name field sqs binding." + } + } + }, + "policy": { + "type": "object", + "description": "The security policy for the SQS Queue", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "statements": { + "type": "array", + "description": "An array of statement objects, each of which controls a permission for this queue.", + "items": { + "$ref": "#/definitions/statement" + } + } + }, + "required": [ + "statements" + ] + }, + "statement": { + "type": "object", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "effect": { + "type": "string", + "enum": [ + "Allow", + "Deny" + ] + }, + "principal": { + "description": "The AWS account or resource ARN that this statement applies to.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "action": { + "description": "The SQS permission being allowed or denied e.g. sqs:ReceiveMessage", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "effect", + "principal", + "action" + ] + } + }, + "examples": [ + { + "queue": { + "name": "myQueue", + "fifoQueue": true, + "deliveryDelay": 15, + "visibilityTimeout": 60, + "receiveMessageWaitTime": 0, + "messageRetentionPeriod": 86400, + "redrivePolicy": { + "deadLetterQueue": { + "arn": "arn:aws:SQS:eu-west-1:0000000:123456789" + }, + "maxReceiveCount": 15 + }, + "policy": { + "statements": [ + { + "effect": "Deny", + "principal": "arn:aws:iam::123456789012:user/dec.kolakowski", + "action": [ + "sqs:SendMessage", + "sqs:ReceiveMessage" + ] + } + ] + }, + "tags": { + "owner": "AsyncAPI.NET", + "platform": "AsyncAPIOrg" + } + }, + "deadLetterQueue": { + "name": "myQueue_error", + "deliveryDelay": 0, + "visibilityTimeout": 0, + "receiveMessageWaitTime": 0, + "messageRetentionPeriod": 604800 + } + } + ] +} \ No newline at end of file diff --git a/sqs/json_schemas/operation.json b/sqs/json_schemas/operation.json new file mode 100644 index 00000000..9f26e977 --- /dev/null +++ b/sqs/json_schemas/operation.json @@ -0,0 +1,239 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://asyncapi.com/bindings/sqs/operation.json", + "title": "Operation Schema", + "description": "This object contains information about the operation representation in SQS.", + "type": "object", + "additionalProperties": false, + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "queues": { + "type": "array", + "description": "Queue objects that are either the endpoint for an SNS Operation Binding Object, or the deadLetterQueue of the SQS Operation Binding Object.", + "items": { + "$ref": "#/definitions/queue" + } + }, + "bindingVersion": { + "type": "string", + "description": "The version of this binding.", + "default": "latest" + } + }, + "required": [ + "queues" + ], + "definitions": { + "queue": { + "type": "object", + "description": "A definition of a queue.", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "$ref": { + "type": "string", + "description": "Allows for an external definition of a queue. The referenced structure MUST be in the format of a Queue. If there are conflicts between the referenced definition and this Queue's definition, the behavior is undefined." + }, + "name": { + "type": "string", + "description": "The name of the queue. When an SNS Operation Binding Object references an SQS queue by name, the identifier should be the one in this field." + }, + "fifoQueue": { + "type": "boolean", + "description": "Is this a FIFO queue?", + "default": false + }, + "deliveryDelay": { + "type": "integer", + "description": "The number of seconds to delay before a message sent to the queue can be received. Used to create a delay queue.", + "minimum": 0, + "maximum": 15, + "default": 0 + }, + "visibilityTimeout": { + "type": "integer", + "description": "The length of time, in seconds, that a consumer locks a message - hiding it from reads - before it is unlocked and can be read again.", + "minimum": 0, + "maximum": 43200, + "default": 30 + }, + "receiveMessageWaitTime": { + "type": "integer", + "description": "Determines if the queue uses short polling or long polling. Set to zero the queue reads available messages and returns immediately. Set to a non-zero integer, long polling waits the specified number of seconds for messages to arrive before returning.", + "default": 0 + }, + "messageRetentionPeriod": { + "type": "integer", + "description": "How long to retain a message on the queue in seconds, unless deleted.", + "minimum": 60, + "maximum": 1209600, + "default": 345600 + }, + "redrivePolicy": { + "$ref": "#/definitions/redrivePolicy" + }, + "policy": { + "$ref": "#/definitions/policy" + }, + "tags": { + "type": "object", + "description": "Key-value pairs that represent AWS tags on the queue." + } + }, + "required": [ + "name" + ] + }, + "redrivePolicy": { + "type": "object", + "description": "Prevent poison pill messages by moving un-processable messages to an SQS dead letter queue.", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "deadLetterQueue": { + "$ref": "#/definitions/identifier" + }, + "maxReceiveCount": { + "type": "integer", + "description": "The number of times a message is delivered to the source queue before being moved to the dead-letter queue.", + "default": 10 + } + }, + "required": [ + "deadLetterQueue" + ] + }, + "identifier": { + "type": "object", + "description": "The SQS queue to use as a dead letter queue (DLQ).", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "arn": { + "type": "string", + "description": "The target is an ARN. For example, for SQS, the identifier may be an ARN, which will be of the form: arn:aws:sqs:{region}:{account-id}:{queueName}" + }, + "name": { + "type": "string", + "description": "The endpoint is identified by a name, which corresponds to an identifying field called 'name' of a binding for that protocol on this publish Operation Object. For example, if the protocol is 'sqs' then the name refers to the name field sqs binding." + } + } + }, + "policy": { + "type": "object", + "description": "The security policy for the SQS Queue", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "statements": { + "type": "array", + "description": "An array of statement objects, each of which controls a permission for this queue.", + "items": { + "$ref": "#/definitions/statement" + } + } + }, + "required": [ + "statements" + ] + }, + "statement": { + "type": "object", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "properties": { + "effect": { + "type": "string", + "enum": [ + "Allow", + "Deny" + ] + }, + "principal": { + "description": "The AWS account or resource ARN that this statement applies to.", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + }, + "action": { + "description": "The SQS permission being allowed or denied e.g. sqs:ReceiveMessage", + "oneOf": [ + { + "type": "string" + }, + { + "type": "array", + "items": { + "type": "string" + } + } + ] + } + }, + "required": [ + "effect", + "principal", + "action" + ] + } + }, + "examples": [ + { + "queues": [ + { + "name": "myQueue", + "deliveryDelay": 10, + "redrivePolicy": { + "deadLetterQueue": { + "name": "myQueue_error" + }, + "maxReceiveCount": 15 + }, + "policy": { + "statements": [ + { + "effect": "Deny", + "principal": "arn:aws:iam::123456789012:user/dec.kolakowski", + "action": [ + "sqs:SendMessage", + "sqs:ReceiveMessage" + ] + } + ] + } + }, + { + "name": "myQueue_error", + "deliveryDelay": 10 + } + ] + } + ] +} From 85b00377193b8ffd3c8f565db25981bbd2e4dde9 Mon Sep 17 00:00:00 2001 From: Goker Akce Date: Mon, 18 Sep 2023 16:16:44 +0100 Subject: [PATCH 89/91] docs: updated sqs binding readme file (#214) --- sqs/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sqs/README.md b/sqs/README.md index 1475139d..f632b66d 100644 --- a/sqs/README.md +++ b/sqs/README.md @@ -127,7 +127,7 @@ channels: sqs: queue: name: user-signedup-queue - type: standard + fifoQueue: false receiveMessageWaitTime: 4 redrivePolicy: deadLetterQueue: @@ -143,7 +143,7 @@ channels: deadLetterQueue: name: user-signedup-dlq messageRetentionPeriod: 1209600 - type: standard + fifoQueue: false subscribe: operationId: sendMessage description: sends messages when a user has signed up @@ -218,7 +218,7 @@ channels: sqs: queues: - name: user-signedup-queue - type: standard + fifoQueue: false receiveMessageWaitTime: 4 policy: statements: @@ -230,7 +230,7 @@ channels: action: Sqs:ReceiveMessage - name: user-signedup-dlq messageRetentionPeriod: 1209600 - type: standard + fifoQueue: false ``` From 6a6cd4cf576b5dc0748228e59c9915f503de64ef Mon Sep 17 00:00:00 2001 From: Gadam8 <44494964+Gadam8@users.noreply.github.com> Date: Mon, 25 Sep 2023 14:03:13 +0100 Subject: [PATCH 90/91] docs: update FilterPolicy config to match AWS API (#215) * Update FilterPolicy config to match AWS API Currently, filterPolicy configuration outlined in this file does not match what is outlined in the AWS documentation and API - https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-sns-subscription.html. The `filterPolicy` should only contain the JSON policy, so no need for the `attributes` property; which in itself was confusing as the policy can apply to the message attributes or body. Further to this, this definition file was missing the `filterPolicyScope` which is needed to determine whether the user wishes to filter on the message attributes or the body. * Minor: remove redundant ref * Minor: match Pascal casing * Minor: amend casing * Match AWS API casing and fix example * Update SNS README --------- Co-authored-by: Dec Kolakowski <51292634+dpwdec@users.noreply.github.com> --- sns/README.md | 19 +++---- sns/json_schemas/operation.json | 98 ++++++++++++++++----------------- 2 files changed, 54 insertions(+), 63 deletions(-) diff --git a/sns/README.md b/sns/README.md index 26a78576..4ef43ec5 100644 --- a/sns/README.md +++ b/sns/README.md @@ -113,7 +113,8 @@ We support an array of consumers via the **consumers** field. This allows you to |---|:---:|---| | `protocol` | string | **Required.** The protocol that this endpoint receives messages by. Can be `http`, `https`, `email`, `email-json`, `sms`, `sqs`, `application`, `lambda` or `firehose` | | `endpoint` |[identifier](#identifier)| **Required.** The endpoint messages are delivered to. | -| `filterPolicy` | [filterPolicy](#filter-policy) | **Optional.** Only receive a subset of messages from the channel, determined by this policy. | +| `filterPolicy` | object | **Optional.** Only receive a subset of messages from the channel, determined by this policy. | +| `filterPolicyScope` | string | **Optional.** Determines whether the FilterPolicy applies to MessageAttributes (default) or MessageBody. | | `rawMessageDelivery` | boolean | **Required.** If *true* AWS SNS attributes are removed from the body, and for SQS, SNS message attributes are copied to SQS message attributes. If *false* the SNS attributes are included in the body. | | `redrivePolicy` | [redrivePolicy](#redrive-policy) | **Optional.** Prevent poison pill messages by moving un-processable messages to an SQS dead letter queue. | | `deliveryPolicy` | [deliveryPolicy](#delivery-policy) | **Optional.** Policy for retries to HTTP. The parameter is for that [SNS Subscription](https://docs.aws.amazon.com/sns/latest/api/API_Subscribe.html) and overrides any policy on the [SNS Topic](https://docs.aws.amazon.com/sns/latest/api/API_CreateTopic.html). | @@ -132,11 +133,6 @@ We support an array of consumers via the **consumers** field. This allows you to | `backoffFunction` | string, one of: arithmetic, exponential, geometric or linear | **Optional.** The algorithm for backoff between retries | | `maxReceivesPerSecond` | integer | **Optional.** The maximum number of deliveries per second, per subscription | -#### Filter Policy -|Field Name | Type | Description| -|---|:---:|---| -| `attributes` |Map(string, array or string or object) | **Required.** A map of a message attribute to an array of possible matches. The match may be a simple string for an exact match, but it may also be an object that represents a constraint and values for that constraint | - #### Identifier |Field Name | Type | Description| |---|:---:|---| @@ -221,9 +217,8 @@ channels: name: user-signedup-queue # refers to a queue defined in this file, but not shown in this example rawMessageDelivery: true filterPolicy: - attributes: - reason: - anything-but: password-reset + reason: + anything-but: password-reset redrivePolicy: deadLetterQueue: name: user-signedup-queue-dlq # refers toa queue defined in this file, but not show in this example @@ -281,9 +276,9 @@ channels: endpoint: url: http://login.my.com/user/new filterPolicy: - attributes: - reason: - anything-but: password-reset + reason: + anything-but: password-reset + filterPolicyScope: MessageBody deliveryPolicy: minDelayTarget: 1 maxDelayTarget: 120 diff --git a/sns/json_schemas/operation.json b/sns/json_schemas/operation.json index df59a327..25eb5935 100644 --- a/sns/json_schemas/operation.json +++ b/sns/json_schemas/operation.json @@ -95,7 +95,38 @@ "$ref": "#/definitions/identifier" }, "filterPolicy": { - "$ref": "#/definitions/filterPolicy" + "type": "object", + "description": "Only receive a subset of messages from the channel, determined by this policy. Depending on the FilterPolicyScope, a map of either a message attribute or message body to an array of possible matches. The match may be a simple string for an exact match, but it may also be an object that represents a constraint and values for that constraint.", + "patternProperties": { + "^x-[\\w\\d\\.\\-\\_]+$": { + "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" + } + }, + "additionalProperties": { + "oneOf": [ + { + "type": "array", + "items": { + "type": "string" + } + }, + { + "type": "string" + }, + { + "type": "object" + } + ] + } + }, + "filterPolicyScope": { + "type": "string", + "description": "Determines whether the FilterPolicy applies to MessageAttributes or MessageBody.", + "enum": [ + "MessageAttributes", + "MessageBody" + ], + "default": "MessageAttributes" }, "rawMessageDelivery": { "type": "boolean", @@ -167,40 +198,6 @@ } } }, - "filterPolicy": { - "type": "object", - "description": "Only receive a subset of messages from the channel, determined by this policy.", - "patternProperties": { - "^x-[\\w\\d\\.\\-\\_]+$": { - "$ref": "https://raw.githubusercontent.com/asyncapi/spec-json-schemas/v2.14.0/schemas/2.4.0.json#/definitions/specificationExtension" - } - }, - "properties": { - "attributes": { - "type": "object", - "description": "A map of a message attribute to an array of possible matches. The match may be a simple string for an exact match, but it may also be an object that represents a constraint and values for that constraint.", - "additionalProperties": { - "oneOf": [ - { - "type": "array", - "items": { - "type": "string" - } - }, - { - "type": "string" - }, - { - "type": "object" - } - ] - } - } - }, - "required": [ - "attributes" - ] - }, "redrivePolicy": { "type": "object", "description": "Prevent poison pill messages by moving un-processable messages to an SQS dead letter queue.", @@ -237,22 +234,21 @@ "name": "someQueue" }, "filterPolicy": { - "attributes": { - "store": [ - "asyncapi_corp" - ], - "event": [ - { - "anything-but": "order_cancelled" - } - ], - "customer_interests": [ - "rugby", - "football", - "baseball" - ] - } + "store": [ + "asyncapi_corp" + ], + "event": [ + { + "anything-but": "order_cancelled" + } + ], + "customer_interests": [ + "rugby", + "football", + "baseball" + ] }, + "filterPolicyScope": "MessageAttributes", "rawMessageDelivery": false, "redrivePolicy": { "deadLetterQueue": { @@ -274,4 +270,4 @@ ] } ] -} \ No newline at end of file +} From 6a3537d4151179632843b6333234bcf01d07eebb Mon Sep 17 00:00:00 2001 From: Gerald Loeffler Date: Wed, 11 Oct 2023 18:28:40 +0200 Subject: [PATCH 91/91] chore: changing codeowners of anypointmq binding (#185) Co-authored-by: Lukasz Gornicki --- CODEOWNERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index b97cdeea..aa30dea0 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -8,7 +8,7 @@ * @derberg @fmvilas @dalelane @smoya @char0n @asyncapi-bot-eve -/anypointmq/ @GeraldLoeffler +/anypointmq/ @mboss37 @GeraldLoeffler /ibmmq/ @rcoppen /jms/ @rcoppen @SrfHead /kafka/ @lbroudoux @dalelane