diff --git a/.github/workflows/create-release.yml b/.github/workflows/create-release.yml index 7fc9704a8..5a6b2ca18 100644 --- a/.github/workflows/create-release.yml +++ b/.github/workflows/create-release.yml @@ -217,3 +217,109 @@ jobs: KUSTOMIZE_VERSION: "v4.5.6" run: | ./hack/ci/render_and_upload_manifests.sh + + bump-sec-scanners-config-main: + name: Bump sec-scanners-config.yaml on main branch + needs: create-draft + runs-on: ubuntu-latest + env: + VERSION: ${{ needs.generate-version.outputs.VERSION }} + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + ref: main + + - uses: actions/setup-go@v5 + with: + go-version: "stable" + + - name: "Setup yq" # Required for rendering the files. + shell: bash + run: | + go install github.com/mikefarah/yq/v4@latest + echo "$(go env GOPATH)/bin" >> $GITHUB_PATH + + - name: Render sec-scanners-config.yaml + shell: bash + run: | + yq --version + ./hack/ci/render-sec-scanners-config.sh "${VERSION}" + FILE="sec-scanners-config.yaml" + echo "******* ${FILE} *******" + [ -f "${FILE}" ] && cat "${FILE}" || echo "${FILE} not found." + + # Check if there are changes, so we can determine if all following steps can be skipped. + - name: Check for changes + shell: bash + run: | + if [ -z "$(git status --porcelain)" ]; then + echo "No changes found. No need to create a PR" + else + echo "Changes found. Creating a PR and waiting for it to be merged." + echo "CREATE_PR=true" >> $GITHUB_ENV + fi + + - name: Set up git + if: ${{ env.CREATE_PR == 'true' }} + env: + GH_TOKEN: ${{ secrets.BOT_PAT }} + REPO: ${{ github.repository }} + shell: bash + run: | + # set git username + ghusername=$(curl -s -H "Authorization: token ${GH_TOKEN}" https://api.github.com/user | jq '.login') + git config user.name "${ghusername}" + # set git mail address + ghemailaddress="${ghusername}@users.noreply.github.com" + git config user.email "${ghemailaddress}" + # set remote url + git remote set-url origin "https://x-access-token:${GH_TOKEN}@github.com/${REPO}.git" + + - name: Set all variables + if: ${{ env.CREATE_PR == 'true' }} + shell: bash + run: | + PR_DATE="$(date '+%Y-%m-%d-%H-%M-%S')" + echo "pr date: ${PR_DATE}" + echo "PR_DATE=${PR_DATE}" >> $GITHUB_ENV + + BRANCH_NAME="sec-scanners-bump-main-${PR_DATE}" + echo "name of the new branch: ${BRANCH_NAME}" + echo "BRANCH_NAME=${BRANCH_NAME}" >> $GITHUB_ENV + + - name: Create a pull request + if: ${{ env.CREATE_PR == 'true' }} + env: + REPO: ${{ github.repository }} + PR_DATE: ${{ env.PR_DATE }} + BRANCH_NAME: ${{ env.BRANCH_NAME }} + GH_TOKEN: ${{ secrets.BOT_PAT }} + shell: bash + run: | + # Create a new branch for our changes. + git checkout -b "${BRANCH_NAME}" + + # Stage the changes to sec-scanner-config.yaml and create a commit. + git add sec-scanners-config.yaml + git commit -m "auto-bump sec-scanners-config: ${PR_DATE}" + + # Push the changes to origin, as defined earlier. + git push origin "$BRANCH_NAME" + + # Create a PR. + BODY="This is an auto-generated PR to bump the sec-scanners-config.yml on ${REPO}." + PR_URL=$(gh pr create --base "main" --head "${BRANCH_NAME}" --title "chore: bump sec-scanners-config on main" --body "${BODY}") + echo "PR_URL=${PR_URL}" >> $GITHUB_ENV + + - name: USER INTERACTION REQUIRED + if: ${{ env.CREATE_PR == 'true' }} + shell: bash + timeout-minutes: 60 + env: + PR_URL: ${{ env.PR_URL }} + GH_TOKEN: ${{ secrets.BOT_PAT }} + run: | + echo "please review: ${PR_URL}" + ./hack/ci/await-pr-merge.sh diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 000000000..06b40ceca --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,28 @@ +# This is a configuration file for the markdownlint. You can use this file to overwrite the default settings. +# MD013 is set to false by default because many files include lines longer than the conventional 80 character limit +MD013: false +# Disable the Multiple headings with the same content rule +MD024: false +# MD029 is set to false because it generated some issues with longer lists +MD029: false +# Checks if there some inline HTML-elements +MD033: false +# MD044 is used to set capitalization for the particular words. You can determine whether it should be used also for code blocks and html elements +MD044: + code_blocks: false + html_elements: false + names: + - Kyma + - Kubernetes + - ConfigMap + - CronJob + - CustomResourceDefinition + - Ingress + - Node + - PodPreset + - Pod + - ProwJob + - Secret + - ServiceBinding + - ServiceClass + - ServiceInstance \ No newline at end of file diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 000000000..58ce07ebf --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1 @@ +_sidebar.md diff --git a/api/operator/v1alpha1/eventing_types.go b/api/operator/v1alpha1/eventing_types.go index aa454928b..f05654bf8 100644 --- a/api/operator/v1alpha1/eventing_types.go +++ b/api/operator/v1alpha1/eventing_types.go @@ -18,6 +18,8 @@ limitations under the License. package v1alpha1 import ( + "strings" + kcorev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" kmetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" @@ -39,12 +41,10 @@ const ( ConditionSubscriptionManagerReady ConditionType = "SubscriptionManagerReady" ConditionDeleted ConditionType = "Deleted" - // common reasons. ConditionReasonProcessing ConditionReason = "Processing" ConditionReasonDeleted ConditionReason = "Deleted" ConditionReasonStopped ConditionReason = "Stopped" - // publisher proxy reasons. ConditionReasonDeployed ConditionReason = "Deployed" ConditionReasonDeployedFailed ConditionReason = "DeployFailed" ConditionReasonDeploymentStatusSyncFailed ConditionReason = "DeploymentStatusSyncFailed" @@ -55,8 +55,8 @@ const ( ConditionReasonWebhookFailed ConditionReason = "WebhookFailed" ConditionReasonWebhookReady ConditionReason = "Ready" ConditionReasonDeletionError ConditionReason = "DeletionError" + ConditionReasonEventMeshConfigAvailable ConditionReason = "EventMeshConfigAvailable" - // message for conditions. ConditionPublisherProxyReadyMessage = "Publisher proxy is deployed" ConditionPublisherProxyDeletedMessage = "Publisher proxy is deleted" ConditionNATSAvailableMessage = "NATS is available" @@ -65,8 +65,8 @@ const ( ConditionSubscriptionManagerReadyMessage = "Subscription manager is ready" ConditionSubscriptionManagerStoppedMessage = "Subscription manager is stopped" ConditionBackendNotSpecifiedMessage = "Backend config is not provided. Please specify a backend." + ConditionEventMeshConfigAvailableMessage = "EventMesh config is available" - // subscription manager reasons. ConditionReasonEventMeshSubManagerReady ConditionReason = "EventMeshSubscriptionManagerReady" ConditionReasonEventMeshSubManagerFailed ConditionReason = "EventMeshSubscriptionManagerFailed" ConditionReasonEventMeshSubManagerStopFailed ConditionReason = "EventMeshSubscriptionManagerStopFailed" @@ -243,3 +243,7 @@ func (e *Eventing) IsPreviousBackendEmpty() bool { func (e *Eventing) IsSpecBackendTypeChanged() bool { return e.Status.ActiveBackend != e.Spec.Backend.Type } + +func (es EventingSpec) HasEmptyBackend() bool { + return es.Backend == nil || len(strings.TrimSpace(string(es.Backend.Type))) == 0 +} diff --git a/api/operator/v1alpha1/eventing_types_test.go b/api/operator/v1alpha1/eventing_types_test.go index 6f0dcde30..c69c78ba6 100644 --- a/api/operator/v1alpha1/eventing_types_test.go +++ b/api/operator/v1alpha1/eventing_types_test.go @@ -1,6 +1,7 @@ package v1alpha1 import ( + "github.com/stretchr/testify/assert" "testing" "github.com/stretchr/testify/require" @@ -109,3 +110,52 @@ func Test_getSupportedConditionsTypes(t *testing.T) { got := getSupportedConditionsTypes() require.Equal(t, want, got) } + +func TestHasEmptyBackend(t *testing.T) { + tests := []struct { + name string + givenEventingSpec EventingSpec + wantHasEmptyBackend bool + }{ + { + name: "with nil backend", + givenEventingSpec: EventingSpec{ + Backend: nil, + }, + wantHasEmptyBackend: true, + }, + { + name: "with empty backend type", + givenEventingSpec: EventingSpec{ + Backend: &Backend{ + Type: "", + }, + }, + wantHasEmptyBackend: true, + }, + { + name: "with non-empty backend type all whitespaces", + givenEventingSpec: EventingSpec{ + Backend: &Backend{ + Type: " ", + }, + }, + wantHasEmptyBackend: true, + }, + { + name: "with non-empty backend type", + givenEventingSpec: EventingSpec{ + Backend: &Backend{ + Type: "any", + }, + }, + wantHasEmptyBackend: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := tt.givenEventingSpec.HasEmptyBackend() + assert.Equal(t, tt.wantHasEmptyBackend, got) + }) + } +} diff --git a/api/operator/v1alpha1/status.go b/api/operator/v1alpha1/status.go index e63e0667f..f1d74d18c 100644 --- a/api/operator/v1alpha1/status.go +++ b/api/operator/v1alpha1/status.go @@ -93,6 +93,12 @@ func (es *EventingStatus) SetNATSAvailableConditionToTrue() { es.UpdateConditionBackendAvailable(kmetav1.ConditionTrue, ConditionReasonNATSAvailable, ConditionNATSAvailableMessage) } +func (es *EventingStatus) SetEventMeshAvailableConditionToTrue() { + es.UpdateConditionBackendAvailable( + kmetav1.ConditionTrue, ConditionReasonEventMeshConfigAvailable, ConditionEventMeshConfigAvailableMessage, + ) +} + func (es *EventingStatus) SetSubscriptionManagerReadyConditionToFalse(reason ConditionReason, message string) { es.UpdateConditionSubscriptionManagerReady(kmetav1.ConditionFalse, reason, message) diff --git a/api/operator/v1alpha1/status_test.go b/api/operator/v1alpha1/status_test.go index 5efe4a90d..bf240ffe1 100644 --- a/api/operator/v1alpha1/status_test.go +++ b/api/operator/v1alpha1/status_test.go @@ -4,6 +4,7 @@ import ( "testing" "time" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" kmetav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) @@ -285,3 +286,168 @@ func TestRemoveUnsupportedConditions(t *testing.T) { }) } } + +func TestSetEventMeshAvailableConditionToTrue(t *testing.T) { + var ( + anyCondition0 = kmetav1.Condition{ + Type: "any-type-0", + Status: kmetav1.ConditionStatus("any-status-0"), + Reason: "any-reason-0", + Message: "any-message-0", + } + + anyCondition1 = kmetav1.Condition{ + Type: "any-type-1", + Status: kmetav1.ConditionStatus("any-status-1"), + Reason: "any-reason-1", + Message: "any-message-1", + } + + anyCondition2 = kmetav1.Condition{ + Type: "any-type-2", + Status: kmetav1.ConditionStatus("any-status-2"), + Reason: "any-reason-2", + Message: "any-message-2", + } + + backendAvailableConditionFalse = kmetav1.Condition{ + Type: string(ConditionBackendAvailable), + Status: kmetav1.ConditionFalse, + Reason: string(ConditionReasonBackendNotSpecified), + Message: ConditionBackendNotSpecifiedMessage, + } + + backendAvailableConditionTrue = kmetav1.Condition{ + Type: string(ConditionBackendAvailable), + Status: kmetav1.ConditionTrue, + Reason: string(ConditionReasonEventMeshConfigAvailable), + Message: ConditionEventMeshConfigAvailableMessage, + } + ) + + tests := []struct { + name string + givenEventingStatus EventingStatus + wantEventingStatus EventingStatus + }{ + // add new condition + { + name: "should add a new condition in case of nil conditions", + givenEventingStatus: EventingStatus{ + Conditions: nil, + }, + wantEventingStatus: EventingStatus{ + Conditions: []kmetav1.Condition{ + backendAvailableConditionTrue, + }, + }, + }, + { + name: "should add a new condition in case of empty conditions", + givenEventingStatus: EventingStatus{ + Conditions: []kmetav1.Condition{}, + }, + wantEventingStatus: EventingStatus{ + Conditions: []kmetav1.Condition{ + backendAvailableConditionTrue, + }, + }, + }, + { + name: "should add a new condition and preserve existing ones", + givenEventingStatus: EventingStatus{ + Conditions: []kmetav1.Condition{ + anyCondition0, + anyCondition1, + anyCondition2, + }, + }, + wantEventingStatus: EventingStatus{ + Conditions: []kmetav1.Condition{ + anyCondition0, + anyCondition1, + anyCondition2, + backendAvailableConditionTrue, + }, + }, + }, + // update existing condition + { + name: "should update existing condition", + givenEventingStatus: EventingStatus{ + Conditions: []kmetav1.Condition{ + backendAvailableConditionFalse, + }, + }, + wantEventingStatus: EventingStatus{ + Conditions: []kmetav1.Condition{ + backendAvailableConditionTrue, + }, + }, + }, + { + name: "should update condition and preserve existing ones", + givenEventingStatus: EventingStatus{ + Conditions: []kmetav1.Condition{ + anyCondition0, + anyCondition1, + backendAvailableConditionTrue, + anyCondition2, + }, + }, + wantEventingStatus: EventingStatus{ + Conditions: []kmetav1.Condition{ + anyCondition0, + anyCondition1, + backendAvailableConditionTrue, + anyCondition2, + }, + }, + }, + { + name: "should update condition from false to true and preserve existing ones", + givenEventingStatus: EventingStatus{ + Conditions: []kmetav1.Condition{ + anyCondition0, + backendAvailableConditionFalse, + anyCondition1, + anyCondition2, + }, + }, + wantEventingStatus: EventingStatus{ + Conditions: []kmetav1.Condition{ + anyCondition0, + backendAvailableConditionTrue, + anyCondition1, + anyCondition2, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tt.givenEventingStatus.SetEventMeshAvailableConditionToTrue() + assertConditionsEqual(t, tt.wantEventingStatus.Conditions, tt.givenEventingStatus.Conditions) + }) + } +} + +// assertConditionsEqual ensures conditions are equal. +// Note: This function takes into consideration the order of conditions while doing the equality check. +func assertConditionsEqual(t *testing.T, expected, actual []kmetav1.Condition) { + t.Helper() + + assert.Equal(t, len(expected), len(actual)) + for i := 0; i < len(expected); i++ { + assertConditionEqual(t, expected[i], actual[i]) + } +} + +func assertConditionEqual(t *testing.T, expected, actual kmetav1.Condition) { + t.Helper() + + assert.Equal(t, expected.Type, actual.Type) + assert.Equal(t, expected.Status, actual.Status) + assert.Equal(t, expected.Reason, actual.Reason) + assert.Equal(t, expected.Message, actual.Message) +} diff --git a/docs/contributor/installation.md b/docs/contributor/installation.md index ee68bdbcb..d078a4819 100644 --- a/docs/contributor/installation.md +++ b/docs/contributor/installation.md @@ -81,12 +81,12 @@ This step depends on your desired backend: NATS or EventMesh. ``` - `spec.backend.type`: set to `EventMesh` - - `spec.backend.config.eventMeshSecret`: set it to the `/` where you applied the secret + - `spec.backend.config.eventMeshSecret`: set it to the `/` where you applied the Secret - `spec.backend.config.eventTypePrefix`: change to your desired value or leave as is - `spec.backend.config.domain`: set to the cluster public domain If the Kyma Kubernetes cluster is managed by Gardener, Eventing Manager reads the cluster public domain automatically from the ConfigMap `kube-system/shoot-info`. - Otherwise, you need to additionally set `spec.backend.config.domain` in the configuration. + Otherwise, you need to additionally set `spec.backend.config.domain` in the configuration. ```sh spec: diff --git a/docs/contributor/testing.md b/docs/contributor/testing.md index 3a08b1ace..684733fb2 100644 --- a/docs/contributor/testing.md +++ b/docs/contributor/testing.md @@ -25,7 +25,7 @@ If changes to the source code were made, or if this is your first time to execut ```sh make test - ``` + ``` ### E2E Tests @@ -50,8 +50,6 @@ Because E2E tests need a Kubernetes cluster to run on, they are separate from th The E2E test consists of four consecutive steps. If desired, you can run them individually. For more information, read the [E2E documentation](https://github.com/kyma-project/eventing-manager/blob/main/hack/e2e/README.md). - - ## CI/CD This project uses [Prow](https://docs.prow.k8s.io/docs/) and [GitHub Actions](https://docs.github.com/en/actions) as part of the development cycle. diff --git a/docs/user/02-configuration.md b/docs/user/02-configuration.md index 2019ba85f..52788f49f 100644 --- a/docs/user/02-configuration.md +++ b/docs/user/02-configuration.md @@ -14,8 +14,10 @@ The validation rules provide guidance when you edit the CR. For example, you are You can override the defaults. However, you must make sure that a backend is set; otherwise the Eventing Manager does not create any resources and goes into warning state. -### Impact of the Backend to the Eventing CR State and Event Flow -The following table provides more details on the overall state of the Eventing CR: +## Impact of the Backend to the Eventing CR State and Event Flow + +The following table provides more details on the overall state of the Eventing CR: + - The Backend State column describes the state of the NATS backend or EventMesh Secret existence. - The Backend Config describes whether the Eventing CR that has been specified by a user or is not available. - The Eventing State describes the overall state of the Eventing CR. @@ -29,7 +31,7 @@ Warnings indicate that user action is required, that is, the user must install t | NATS | exists | Warning (NATS deletion blocked) | Ready | Events will be accepted or dispatched | | NATS | exists | Error (NATS ist unavailable or cannot be connected) | Warning | No events will be accepted or dispatched | | NATS | exists | Missing | Warning | No events will be accepted or dispatched | -| EventMesh | exists | Error (secret for EventMesh missing) | Warning | No events will be accepted or dispatched | +| EventMesh | exists | Error (Secret for EventMesh missing) | Warning | No events will be accepted or dispatched | | NATS/EventMesh | exists | Error (cases not caused by a user, such as cannot create EPP deployment or cannot start subscription manager although backend is available) | Error | No events will be accepted or dispatched | ## Examples @@ -73,8 +75,8 @@ Use the following sample CRs as guidance. Each can be applied immediately when y | **publisher.​resources** | object | Resources defines resources for eventing-publisher-proxy. | | **publisher.​resources.​claims** | \[\]object | Claims lists the names of resources, defined in spec.resourceClaims, that are used by this container. This is an alpha field and requires enabling the DynamicResourceAllocation feature gate. This field is immutable. It can only be set for containers. | | **publisher.​resources.​claims.​name** (required) | string | Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container. | -| **publisher.​resources.​limits** | map\[string\]\{integer or string\} | Limits describes the maximum amount of compute resources allowed. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ | -| **publisher.​resources.​requests** | map\[string\]\{integer or string\} | Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. More info: https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ | +| **publisher.​resources.​limits** | map\[string\]\{integer or string\} | Limits describes the maximum amount of compute resources allowed. For more information, check [Resource Management for Pods and Containers](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/). | +| **publisher.​resources.​requests** | map\[string\]\{integer or string\} | Requests describes the minimum amount of compute resources required. If Requests is omitted for a container, it defaults to Limits if that is explicitly specified, otherwise to an implementation-defined value. Requests cannot exceed Limits. For more information, check [Resource Management for Pods and Containers](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/). | **Status:** @@ -91,6 +93,6 @@ Use the following sample CRs as guidance. Each can be applied immediately when y | **conditions.​type** (required) | string | type of condition in CamelCase or in foo.example.com/CamelCase. --- Many .condition.type values are consistent across resources like Available, but because arbitrary conditions can be useful (see .node.status.conditions), the ability to deconflict is important. The regex it matches is (dns1123SubdomainFmt/)?(qualifiedNameFmt) | | **publisherService** | string | | | **specHash** (required) | integer | | -| **state** (required) | string | Can have one of the following values: `Ready`, `Error`, `Processing`, `Warning`. `Ready` state is set when all the resources are deployed successfully and backend is connected. It gets `Warning` state if backend is not specified, NATS module is not installed or EventMesh secret is missing in the cluster. `Error` state is set when there is an error not caused by a user. `Processing` state is set when eventing is initiliazed or backend is being switched. | +| **state** (required) | string | Can have one of the following values: `Ready`, `Error`, `Processing`, `Warning`. `Ready` state is set when all the resources are deployed successfully and backend is connected. It gets `Warning` state if backend is not specified, NATS module is not installed or EventMesh Secret is missing in the cluster. `Error` state is set when there is an error not caused by a user. `Processing` state is set when eventing is initiliazed or backend is being switched. | diff --git a/docs/user/troubleshooting/README.md b/docs/user/troubleshooting/README.md index f51b6ad31..180ac5340 100644 --- a/docs/user/troubleshooting/README.md +++ b/docs/user/troubleshooting/README.md @@ -2,4 +2,4 @@ Learn more about solutions to the most common recurring problems that may occur with Kyma Eventing. -If you can't find a solution, don't hesitate to create a [GitHub](https://github.com/kyma-project/kyma/issues) issue or reach out to our [Slack channel](http://slack.kyma-project.io/) to get direct support from the community. +If you can't find a solution, don't hesitate to create a [GitHub](https://github.com/kyma-project/kyma/issues) issue or reach out to our [Slack channel](https://kyma-community.slack.com/) to get direct support from the community. diff --git a/docs/user/troubleshooting/evnt-01-eventing-troubleshooting.md b/docs/user/troubleshooting/evnt-01-eventing-troubleshooting.md index 9e6882c20..f29b17b11 100644 --- a/docs/user/troubleshooting/evnt-01-eventing-troubleshooting.md +++ b/docs/user/troubleshooting/evnt-01-eventing-troubleshooting.md @@ -112,5 +112,4 @@ Follow these steps to detect the source of the problem: 2. Check if the stream and consumers exist in NATS JetStream by following the [JetStream troubleshooting guide](evnt-02-jetstream-troubleshooting.md). - If you can't find a solution, don't hesitate to create a [GitHub](https://github.com/kyma-project/kyma/issues) issue or reach out to our [Slack channel](https://kyma-community.slack.com/) to get direct support from the community. diff --git a/docs/user/troubleshooting/evnt-02-jetstream-troubleshooting.md b/docs/user/troubleshooting/evnt-02-jetstream-troubleshooting.md index 998c82b93..e2449a263 100644 --- a/docs/user/troubleshooting/evnt-02-jetstream-troubleshooting.md +++ b/docs/user/troubleshooting/evnt-02-jetstream-troubleshooting.md @@ -1,5 +1,4 @@ -# NATS JetStream backend troubleshooting - +# NATS JetStream Backend Troubleshooting ## Symptom @@ -12,10 +11,13 @@ Events were not received by the consumers. 2. Use the [NATS CLI](https://github.com/nats-io/natscli) to check if the stream was created: 1. Port forward the Kyma Eventing NATS Service to localhost. Use port `4222`. Run: + ```bash kubectl -n kyma-system port-forward svc/eventing-nats 4222 ``` + 2. Use the [NATS CLI](https://github.com/nats-io/natscli) to list the streams: + ```bash $ nats stream ls ╭────────────────────────────────────────────────────────────────────────────╮ @@ -25,14 +27,16 @@ Events were not received by the consumers. ├──────┼─────────────┼─────────────────────┼──────────┼───────┼──────────────┤ │ sap │ │ 2022-05-03 00:00:00 │ 0 │ 318 B │ 5.80s │ ╰──────┴─────────────┴─────────────────────┴──────────┴───────┴──────────────╯ - ``` + ``` 3. If the stream exists, check the timestamp of the `Last Message` that the stream received. A recent timestamp would mean that the event was published correctly. - + 4. Check if the consumers were created and have the expected configurations. + ```bash nats consumer info ``` + To correlate the consumer to the Subscription and the specific event type, check the `description` field of the consumer. 5. If the PVC storage is fully consumed and matches the stream size as shown above, the stream can no longer receive messages. Either increase the PVC storage size or set the `MaxBytes` property which removes the old messages. diff --git a/docs/user/troubleshooting/evnt-03-type-collision.md b/docs/user/troubleshooting/evnt-03-type-collision.md index 8337df4f6..85303443f 100644 --- a/docs/user/troubleshooting/evnt-03-type-collision.md +++ b/docs/user/troubleshooting/evnt-03-type-collision.md @@ -1,8 +1,8 @@ -# Subscriber receives irrelevant events +# Subscriber Receives Irrelevant Events ## Symptom -Subscriber receives irrelevant events. +Subscriber receives irrelevant events. ## Cause @@ -14,15 +14,15 @@ In some cases, it can lead to a naming collision, which can cause subscribers to Follow these steps to detect if naming collision is the source of the problem: 1. Get the clean types from the status of the Subscription. - + ```bash kubectl -n {NAMESPACE} get subscriptions.eventing.kyma-project.io {NAME} -o jsonpath='{.status.types}' ``` 2. Search for any other Subscription using the same `CleanType` as in your Subscription. - + ```bash kubectl get subscriptions.eventing.kyma-project.io -A | grep {CLEAN_TYPE} ``` - -3. If you find that the `CleanType` collides with some other Subscription, a solution for this is to use a different event type. + +3. If you find that the `CleanType` collides with some other Subscription, a solution for this is to use a different event type. diff --git a/docs/user/troubleshooting/evnt-05-fix-pending-messages.md b/docs/user/troubleshooting/evnt-05-fix-pending-messages.md index 879a74cc5..dbd42659b 100644 --- a/docs/user/troubleshooting/evnt-05-fix-pending-messages.md +++ b/docs/user/troubleshooting/evnt-05-fix-pending-messages.md @@ -1,5 +1,5 @@ -# Published events are pending in the stream +# Published Events are Pending in the Stream ## Symptom @@ -19,7 +19,7 @@ To fix the "broken" consumers with pending messages, trigger a leader reelection You need the latest version of NATS CLI installed on your machine. -### Consumer leader reelection +### Consumer Leader Reelection First, find out which consumer(s) have pending messages. You can find the broken consumer with the NATS CLI command. @@ -48,7 +48,7 @@ First, find out which consumer(s) have pending messages. You can find the broken In this example, the consumer `ebcabfe5c902612f0ba3ebde7653f30b` has 25 pending messages and has the leader. The other one has no pending message and is successfully processing events. -#### Trigger the consumer leader reelection +#### Trigger the Consumer Leader Reelection Knowing the name of the broken consumer and its leader, you can trigger the reelection: @@ -74,7 +74,7 @@ Knowing the name of the broken consumer and its leader, you can trigger the reel 3. Check the consumer and confirm that the pending messages started to be dispatched. -### Stream leader reelection +### Stream Leader Reelection Sometimes triggering the leader reelection on the broken consumers doesn't work. In that case, you must restart the NATS Pods to trigger leader reelection on the stream level. diff --git a/docs/user/tutorials/evnt-03-type-cleanup.md b/docs/user/tutorials/evnt-03-type-cleanup.md index 5b3d63523..91b662821 100644 --- a/docs/user/tutorials/evnt-03-type-cleanup.md +++ b/docs/user/tutorials/evnt-03-type-cleanup.md @@ -116,7 +116,7 @@ Note that the returned event type `["order.paymentsuccess.v1"]` does not contain ## Trigger the Workload With an Event -You created the `lastorder` Function, and subscribed to the `order.payment*success.v1` events by creating a Subscription CR. +You created the `lastorder` Function, and subscribed to the `order.payment*success.v1` events by creating a Subscription CR. Next, you see that you can still publish events with the original Event name (i.e. `order.payment*success.v1`) even though it contains the prohibited character, and it triggers the Function. 1. Port-forward the [Eventing Publisher Proxy](../evnt-architecture.md) Service to localhost, using port `3000`. Run: @@ -192,7 +192,7 @@ Note that the `Event Type` of the received event is not the same as defined in t ## Conclusion -You see that Kyma Eventing modifies the event names to filter out prohibited characters to conform to Cloud Event specifications. +You see that Kyma Eventing modifies the event names to filter out prohibited characters to conform to Cloud Event specifications. > [!WARNING] > This cleanup modification is abstract; you can still publish and subscribe to the original Event names. However, in some cases, it can lead to a naming collision as explained in [Event names](../evnt-event-names.md). diff --git a/docs/user/tutorials/evnt-04-change-max-in-flight-in-sub.md b/docs/user/tutorials/evnt-04-change-max-in-flight-in-sub.md index da521bf65..4c9b989a6 100644 --- a/docs/user/tutorials/evnt-04-change-max-in-flight-in-sub.md +++ b/docs/user/tutorials/evnt-04-change-max-in-flight-in-sub.md @@ -1,7 +1,7 @@ # Changing Events Max-In-Flight in Subscriptions In this tutorial, you learn how to set idle "in-flight messages" limit in Kyma Subscriptions. -The "in-flight messages" config defines the number of events that Kyma Eventing forwards in parallel to the sink, without waiting for a response. +The "in-flight messages" config defines the number of events that Kyma Eventing forwards in parallel to the sink, without waiting for a response. ## Prerequisites diff --git a/hack/ci/await-pr-merge.sh b/hack/ci/await-pr-merge.sh new file mode 100755 index 000000000..87ed3e510 --- /dev/null +++ b/hack/ci/await-pr-merge.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +# standard bash error handling +set -o nounset # treat unset variables as an error and exit immediately. +set -o errexit # exit immediately when a command fails. +set -E # needs to be set if we want the ERR trap +set -o pipefail # prevents errors in a pipeline from being masked + +# Expected environment variables: +# PR_URL - Number of the PR with the changes to be merged + +# wait until the PR is merged. +while true ; do + pr_state=$(gh pr view ${PR_URL} --json state --jq '.state') + if [ "$pr_state" == "CLOSED" ]; then + echo "ERROR! PR has been closed!" + exit 1 + elif [ "$pr_state" == "MERGED" ]; then + echo "PR has been merged!" + exit 0 + fi + echo "Waiting for ${PR_URL} to be merged" + sleep 10 +done diff --git a/internal/controller/operator/eventing/controller.go b/internal/controller/operator/eventing/controller.go index d1012ad8c..5843776ff 100644 --- a/internal/controller/operator/eventing/controller.go +++ b/internal/controller/operator/eventing/controller.go @@ -464,8 +464,10 @@ func (r *Reconciler) handleEventingReconcile(ctx context.Context, // set state processing if not set yet r.InitStateProcessing(eventing) - if eventing.Spec.Backend == nil { - return kctrl.Result{Requeue: true}, r.syncStatusForEmptyBackend(ctx, + + // Handle empty backend. + if eventing.Spec.HasEmptyBackend() { + return kctrl.Result{Requeue: false}, r.syncStatusForEmptyBackend(ctx, operatorv1alpha1.ConditionBackendNotSpecifiedMessage, eventing, log) } @@ -677,6 +679,9 @@ func (r *Reconciler) reconcileEventMeshBackend(ctx context.Context, eventing *op } return kctrl.Result{}, r.syncStatusWithSubscriptionManagerErr(ctx, eventing, err, log) } + + // Set the Eventing CR status accordingly. + eventing.Status.SetEventMeshAvailableConditionToTrue() eventing.Status.SetSubscriptionManagerReadyConditionToTrue() deployment, err := r.handlePublisherProxy(ctx, eventing, eventing.Spec.Backend.Type) diff --git a/internal/controller/operator/eventing/integrationtests/controller/integration_test.go b/internal/controller/operator/eventing/integrationtests/controller/integration_test.go index 61450ea0d..3fe390d0b 100644 --- a/internal/controller/operator/eventing/integrationtests/controller/integration_test.go +++ b/internal/controller/operator/eventing/integrationtests/controller/integration_test.go @@ -721,6 +721,109 @@ func Test_CreateEventingCR_EventMesh(t *testing.T) { } } +func TestUpdateEventingCRFromEmptyToNonEmptyBackend(t *testing.T) { + tests := []struct { + name string + givenBackendTypeToUse operatorv1alpha1.BackendType + wantMatchesBeforeUpdate gomegatypes.GomegaMatcher + wantMatchesAfterUpdate gomegatypes.GomegaMatcher + }{ + { + name: "update Eventing CR from empty backend to NATS", + givenBackendTypeToUse: operatorv1alpha1.NatsBackendType, + wantMatchesBeforeUpdate: gomega.And( + matchers.HaveBackendNotAvailableConditionWith( + operatorv1alpha1.ConditionBackendNotSpecifiedMessage, + operatorv1alpha1.ConditionReasonBackendNotSpecified, + ), + ), + wantMatchesAfterUpdate: gomega.And( + matchers.HaveFinalizer(), + matchers.HaveBackendAvailableConditionWith( + operatorv1alpha1.ConditionNATSAvailableMessage, + operatorv1alpha1.ConditionReasonNATSAvailable, + ), + ), + }, + { + name: "update Eventing CR from empty backend to EventMesh", + givenBackendTypeToUse: operatorv1alpha1.EventMeshBackendType, + wantMatchesBeforeUpdate: gomega.And( + matchers.HaveBackendNotAvailableConditionWith( + operatorv1alpha1.ConditionBackendNotSpecifiedMessage, + operatorv1alpha1.ConditionReasonBackendNotSpecified, + ), + ), + wantMatchesAfterUpdate: gomega.And( + matchers.HaveFinalizer(), + matchers.HaveBackendAvailableConditionWith( + operatorv1alpha1.ConditionEventMeshConfigAvailableMessage, + operatorv1alpha1.ConditionReasonEventMeshConfigAvailable, + ), + ), + }, + } + + for _, test := range tests { + test := test + + t.Run(test.name, func(t *testing.T) { + t.Parallel() + g := gomega.NewWithT(t) + + ///// + // Create the Eventing CR with an empty backend + ///// + + eventingCR := utils.NewEventingCR(utils.WithEmptyBackend()) + namespace := eventingCR.Namespace + testEnvironment.EnsureNamespaceCreation(t, namespace) + testEnvironment.EnsureK8sResourceCreated(t, eventingCR) + defer func() { + testEnvironment.EnsureEventingResourceDeletion(t, eventingCR.Name, namespace) + testEnvironment.EnsureNamespaceDeleted(t, namespace) + }() + + testEnvironment.GetEventingAssert(g, eventingCR).Should(test.wantMatchesBeforeUpdate) + + ///// + // Update the Eventing CR with a non-empty backend + ///// + + eventingCR, err := testEnvironment.GetEventingFromK8s(eventingCR.Name, namespace) + g.Expect(err).ShouldNot(gomega.HaveOccurred()) + eventingCR = eventingCR.DeepCopy() + + switch test.givenBackendTypeToUse { + case operatorv1alpha1.NatsBackendType: + { + natsCR := natstestutils.NewNATSCR( + natstestutils.WithNATSCRNamespace(namespace), + natstestutils.WithNATSCRDefaults(), + natstestutils.WithNATSStateReady(), + ) + testEnvironment.EnsureK8sResourceCreated(t, natsCR) + defer func() { testEnvironment.EnsureK8sResourceDeleted(t, natsCR) }() + + g.Expect(utils.WithNATSBackend()(eventingCR)).ShouldNot(gomega.HaveOccurred()) + testEnvironment.EnsureK8sResourceUpdated(t, eventingCR) + } + case operatorv1alpha1.EventMeshBackendType: + { + g.Expect(utils.WithEventingDomain(utils.Domain)(eventingCR)).ShouldNot(gomega.HaveOccurred()) + g.Expect(utils.WithEventMeshBackend("test-eventmesh-secret")(eventingCR)).ShouldNot(gomega.HaveOccurred()) + + testEnvironment.EnsureEventMeshSecretCreated(t, eventingCR) + testEnvironment.EnsureOAuthSecretCreated(t, eventingCR) + testEnvironment.EnsureK8sResourceUpdated(t, eventingCR) + } + } + + testEnvironment.GetEventingAssert(g, eventingCR).Should(test.wantMatchesAfterUpdate) + }) + } +} + func Test_DeleteEventingCR(t *testing.T) { t.Parallel() testCases := []struct { diff --git a/markdown_heading_capitalization.js b/markdown_heading_capitalization.js new file mode 100644 index 000000000..2fdede35e --- /dev/null +++ b/markdown_heading_capitalization.js @@ -0,0 +1,30 @@ +// This file is used to trigger the custom rule that checks if all markdown headings (words longer than 4 characters) are written in the title case. To run this check, you must include the check in the markdownlint command. +// For example, if you want to run the check on the `docs` folder, run the following command: `markdownlint -r ./markdown_heading_capitalization.js docs/`. +module.exports = [{ + "names": [ "custom/capitalize-headings" ], + "description": "Heading words longer than 4 characters should be capitalized", + "tags": [ "formatting" ], + "function": function rule(params, onError) { + params.tokens.filter(function filterToken(token) { + return token.type === "heading_open"; + }).forEach(function forToken(heading) { + var headingTokenContent = heading.line.trim(); + var wordsInHeading = headingTokenContent.split(' '); + + for (var i = 0; i < wordsInHeading.length; i++) { + if (wordsInHeading[i].length > 4 && wordsInHeading[i] && + wordsInHeading[i].charAt(0) !== wordsInHeading[i].charAt(0).toUpperCase()) { + var capitalizedWord = wordsInHeading[i].charAt(0).toUpperCase() + wordsInHeading[i].slice(1); + var detailMessage = "Change " + "'" + wordsInHeading[i] + "'" + " to " + "'" + capitalizedWord + "'"; + + onError({ + "lineNumber": heading.lineNumber, + "detail": detailMessage, + "context": headingTokenContent, // Show the whole heading as context + "range": [headingTokenContent.indexOf(wordsInHeading[i]), wordsInHeading[i].length] // Underline the word which needs a change + }); + } + } + }); + } + }]; \ No newline at end of file diff --git a/test/matchers/matchers.go b/test/matchers/matchers.go index e7cd7fed0..a3c761f3e 100644 --- a/test/matchers/matchers.go +++ b/test/matchers/matchers.go @@ -106,6 +106,15 @@ func HaveBackendNotAvailableConditionWith(message string, reason v1alpha1.Condit }) } +func HaveBackendAvailableConditionWith(message string, reason v1alpha1.ConditionReason) gomegatypes.GomegaMatcher { + return HaveCondition(kmetav1.Condition{ + Type: string(v1alpha1.ConditionBackendAvailable), + Status: kmetav1.ConditionTrue, + Reason: string(reason), + Message: message, + }) +} + func HaveNATSNotAvailableConditionWith(message string) gomegatypes.GomegaMatcher { return HaveCondition(kmetav1.Condition{ Type: string(v1alpha1.ConditionBackendAvailable), diff --git a/test/utils/options.go b/test/utils/options.go index be8ae7fb7..5f3cb7d81 100644 --- a/test/utils/options.go +++ b/test/utils/options.go @@ -116,12 +116,18 @@ func WithEventingLogLevel(logLevel string) EventingOption { func WithEventMeshBackend(eventMeshSecretName string) EventingOption { return func(e *v1alpha1.Eventing) error { - e.Spec.Backend = &v1alpha1.Backend{ - Type: v1alpha1.EventMeshBackendType, - Config: v1alpha1.BackendConfig{ - EventMeshSecret: e.Namespace + "/" + eventMeshSecretName, - }, + if e.Spec.Backend == nil { + e.Spec.Backend = &v1alpha1.Backend{} } + e.Spec.Backend.Type = v1alpha1.EventMeshBackendType + e.Spec.Backend.Config.EventMeshSecret = e.Namespace + "/" + eventMeshSecretName + return nil + } +} + +func WithEmptyBackend() EventingOption { + return func(e *v1alpha1.Eventing) error { + e.Spec.Backend = &v1alpha1.Backend{} return nil } } diff --git a/test/utils/utils.go b/test/utils/utils.go index c47c8879d..cb2aa60de 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -109,9 +109,17 @@ func NewAPIRuleCRD() *kapiextensionsv1.CustomResourceDefinition { return result } +func GetRandomName() string { + return fmt.Sprintf(NameFormat, GetRandString(randomNameLen)) +} + +func GetRandomNamespaceName() string { + return fmt.Sprintf(NamespaceFormat, GetRandString(randomNameLen)) +} + func NewEventingCR(opts ...EventingOption) *v1alpha1.Eventing { - name := fmt.Sprintf(NameFormat, GetRandString(randomNameLen)) - namespace := fmt.Sprintf(NamespaceFormat, GetRandString(randomNameLen)) + name := GetRandomName() + namespace := GetRandomNamespaceName() eventing := &v1alpha1.Eventing{ TypeMeta: kmetav1.TypeMeta{