Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions .github/workflows/docker-milestone-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,37 @@ on:
env:
DOCKER_NAMESPACE: eclipsebasyx

permissions: {}

jobs:
guard:
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
should-run: ${{ steps.check.outputs.should-run }}
steps:
- name: Checkout Code
uses: actions/checkout@v7

- id: check
env:
EVENT_REPOSITORY_FORK: ${{ github.event.repository.fork }}
run: |
should_run=$(bash .github/workflows/scripts/check_docker_publication.sh "${GITHUB_REPOSITORY}" "${EVENT_REPOSITORY_FORK}")
echo "should-run=${should_run}" >> "$GITHUB_OUTPUT"
if [[ "${should_run}" == "true" ]]; then
echo "Running Docker publication in the upstream repository"
else
echo "Skipping Docker publication for ${GITHUB_REPOSITORY}"
fi

build-and-push-milestone-release:
needs: guard
if: ${{ needs.guard.outputs.should-run == 'true' }}
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
include:
Expand Down
58 changes: 58 additions & 0 deletions .github/workflows/docker-publication-guard-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
# /*******************************************************************************
# * Copyright (C) 2026 the Eclipse BaSyx Authors
# *
# * Permission is hereby granted, free of charge, to any person obtaining
# * a copy of this software and associated documentation files (the
# * "Software"), to deal in the Software without restriction, including
# * without limitation the rights to use, copy, modify, merge, publish,
# * distribute, sublicense, and/or sell copies of the Software, and to
# * permit persons to whom the Software is furnished to do so, subject to
# * the following conditions:
# *
# * The above copyright notice and this permission notice shall be
# * included in all copies or substantial portions of the Software.
# *
# * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# *
# * SPDX-License-Identifier: MIT
# ******************************************************************************/

name: Test Docker Publication Guard

on:
pull_request:
branches: [ main ]
paths:
- '.github/workflows/docker-milestone-release.yml'
- '.github/workflows/docker-snapshot-release.yml'
- '.github/workflows/docker-publication-guard-test.yml'
- '.github/workflows/scripts/check_docker_publication.sh'
- '.github/workflows/scripts/test_docker_publication_guard.sh'
push:
branches: [ main ]
paths:
- '.github/workflows/docker-milestone-release.yml'
- '.github/workflows/docker-snapshot-release.yml'
- '.github/workflows/docker-publication-guard-test.yml'
- '.github/workflows/scripts/check_docker_publication.sh'
- '.github/workflows/scripts/test_docker_publication_guard.sh'

permissions: {}

jobs:
test-docker-publication-guard:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- name: Checkout Code
uses: actions/checkout@v7

- name: Test publication guard
run: bash .github/workflows/scripts/test_docker_publication_guard.sh
28 changes: 28 additions & 0 deletions .github/workflows/docker-snapshot-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,37 @@ on:
env:
DOCKER_NAMESPACE: eclipsebasyx

permissions: {}

jobs:
guard:
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
should-run: ${{ steps.check.outputs.should-run }}
steps:
- name: Checkout Code
uses: actions/checkout@v7

- id: check
env:
EVENT_REPOSITORY_FORK: ${{ github.event.repository.fork }}
run: |
should_run=$(bash .github/workflows/scripts/check_docker_publication.sh "${GITHUB_REPOSITORY}" "${EVENT_REPOSITORY_FORK}")
echo "should-run=${should_run}" >> "$GITHUB_OUTPUT"
if [[ "${should_run}" == "true" ]]; then
echo "Running Docker publication in the upstream repository"
else
echo "Skipping Docker publication for ${GITHUB_REPOSITORY}"
fi

build-and-push-prerelease:
needs: guard
if: ${{ needs.guard.outputs.should-run == 'true' }}
runs-on: ubuntu-latest
permissions:
contents: read
strategy:
matrix:
include:
Expand Down
39 changes: 39 additions & 0 deletions .github/workflows/scripts/check_docker_publication.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env bash

# /*******************************************************************************
# * Copyright (C) 2026 the Eclipse BaSyx Authors
# *
# * Permission is hereby granted, free of charge, to any person obtaining
# * a copy of this software and associated documentation files (the
# * "Software"), to deal in the Software without restriction, including
# * without limitation the rights to use, copy, modify, merge, publish,
# * distribute, sublicense, and/or sell copies of the Software, and to
# * permit persons to whom the Software is furnished to do so, subject to
# * the following conditions:
# *
# * The above copyright notice and this permission notice shall be
# * included in all copies or substantial portions of the Software.
# *
# * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# *
# * SPDX-License-Identifier: MIT
# ******************************************************************************/

set -euo pipefail

readonly UPSTREAM_REPOSITORY="eclipse-basyx/basyx-java-server-sdk"

repository="${1:-}"
is_fork="${2:-}"

if [[ "${repository}" == "${UPSTREAM_REPOSITORY}" && "${is_fork}" == "false" ]]; then
printf 'true\n'
else
printf 'false\n'
fi
76 changes: 76 additions & 0 deletions .github/workflows/scripts/test_docker_publication_guard.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#!/usr/bin/env bash

# /*******************************************************************************
# * Copyright (C) 2026 the Eclipse BaSyx Authors
# *
# * Permission is hereby granted, free of charge, to any person obtaining
# * a copy of this software and associated documentation files (the
# * "Software"), to deal in the Software without restriction, including
# * without limitation the rights to use, copy, modify, merge, publish,
# * distribute, sublicense, and/or sell copies of the Software, and to
# * permit persons to whom the Software is furnished to do so, subject to
# * the following conditions:
# *
# * The above copyright notice and this permission notice shall be
# * included in all copies or substantial portions of the Software.
# *
# * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
# * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
# * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
# * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
# *
# * SPDX-License-Identifier: MIT
# ******************************************************************************/

set -euo pipefail

readonly SCRIPT_DIRECTORY="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
readonly GUARD_SCRIPT="${SCRIPT_DIRECTORY}/check_docker_publication.sh"
readonly WORKFLOW_DIRECTORY="${SCRIPT_DIRECTORY}/.."

assert_guard_result() {
local description="$1"
local repository="$2"
local is_fork="$3"
local expected="$4"
local actual

actual="$(bash "${GUARD_SCRIPT}" "${repository}" "${is_fork}")"
if [[ "${actual}" != "${expected}" ]]; then
echo "${description}: expected ${expected}, got ${actual}" >&2
exit 1
fi
}

assert_workflow_uses_guard() {
local workflow="$1"

if [[ "$(grep -c 'check_docker_publication.sh' "${workflow}")" -ne 1 ]]; then
echo "${workflow} must invoke the shared Docker publication guard exactly once" >&2
exit 1
fi
}

# Release workflow cases
assert_guard_result "release in upstream" "eclipse-basyx/basyx-java-server-sdk" "false" "true"
assert_guard_result "release in upstream marked as fork" "eclipse-basyx/basyx-java-server-sdk" "true" "false"
assert_guard_result "release in fork" "contributor/basyx-java-server-sdk" "true" "false"
assert_guard_result "release in renamed repository" "eclipse-basyx/basyx-java-server-sdk-renamed" "false" "false"

# Snapshot push workflow cases
assert_guard_result "push in upstream" "eclipse-basyx/basyx-java-server-sdk" "false" "true"
assert_guard_result "push in upstream marked as fork" "eclipse-basyx/basyx-java-server-sdk" "true" "false"
assert_guard_result "push in fork" "contributor/basyx-java-server-sdk" "true" "false"
assert_guard_result "push in renamed repository" "eclipse-basyx/basyx-java-server-sdk-renamed" "false" "false"

# Missing event metadata must fail closed.
assert_guard_result "missing fork metadata" "eclipse-basyx/basyx-java-server-sdk" "" "false"
assert_guard_result "missing repository metadata" "" "false" "false"

assert_workflow_uses_guard "${WORKFLOW_DIRECTORY}/docker-milestone-release.yml"
assert_workflow_uses_guard "${WORKFLOW_DIRECTORY}/docker-snapshot-release.yml"

echo "Docker publication guard tests passed"
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,9 @@ This feature provides hierarchical MQTT eventing for a multitude of events:
| AAS Created | aas-repository/\$repoId/shells/created| Created AAS JSON |
| AAS Updated | aas-repository/\$repoId/shells/updated| Updated AAS JSON|
| AAS Deleted | aas-repository/\$repoId/shells/deleted| Deleted AAS JSON|
| Submodel Reference Created | aas-repository/\$repoId/shells/submodels/\$submodelId/created| Created AAS JSON |
| Submodel Reference Deleted | aas-repository/\$repoId/shells/submodels/\$submodelId/deleted| Deleted AAS JSON|
| Submodel Reference Created | aas-repository/\$repoId/shells/submodels/\$submodelIdBase64Url/created| Created AAS JSON |
| Submodel Reference Deleted | aas-repository/\$repoId/shells/submodels/\$submodelIdBase64Url/deleted| Deleted AAS JSON|

Identifier segments use unpadded UTF-8 Base64URL encoding. Repository names are not encoded.

> **Breaking topic migration:** Submodel-reference events are published only on the encoded canonical topics. Existing subscribers using raw submodel IDs must update their subscriptions.
9 changes: 8 additions & 1 deletion basyx.aasrepository/basyx.aasrepository-feature-mqtt/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,13 @@
<groupId>org.eclipse.digitaltwin.basyx</groupId>
<artifactId>basyx.mqttcore</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.digitaltwin.basyx</groupId>
<artifactId>basyx.mqttcore</artifactId>
<version>${revision}</version>
<classifier>tests</classifier>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.digitaltwin.basyx</groupId>
<artifactId>basyx.aasrepository-core</artifactId>
Expand Down Expand Up @@ -55,4 +62,4 @@
<scope>test</scope>
</dependency>
</dependencies>
</project>
</project>
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,12 @@
import org.eclipse.digitaltwin.aas4j.v3.model.Reference;
import org.eclipse.digitaltwin.aas4j.v3.model.SpecificAssetId;
import org.eclipse.digitaltwin.basyx.aasrepository.AasRepository;
import org.eclipse.digitaltwin.basyx.common.mqttcore.MqttEventPublisher;
import org.eclipse.digitaltwin.basyx.core.exceptions.CollidingIdentifierException;
import org.eclipse.digitaltwin.basyx.core.exceptions.ElementDoesNotExistException;
import org.eclipse.digitaltwin.basyx.core.pagination.CursorResult;
import org.eclipse.digitaltwin.basyx.core.pagination.PaginationInfo;
import org.eclipse.paho.client.mqttv3.IMqttClient;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
import org.eclipse.paho.client.mqttv3.MqttPersistenceException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -170,24 +168,7 @@ private String serializePayload(AssetAdministrationShell shell) {
* the actual message
*/
private void sendMqttMessage(String topic, String payload) {
MqttMessage msg = createMqttMessage(payload);

try {
logger.debug("Send MQTT message to " + topic + ": " + payload);
mqttClient.publish(topic, msg);
} catch (MqttPersistenceException e) {
logger.error("Could not persist mqtt message", e);
} catch (MqttException e) {
logger.error("Could not send mqtt message", e);
}
}

private MqttMessage createMqttMessage(String payload) {
if (payload == null) {
return new MqttMessage();
} else {
return new MqttMessage(payload.getBytes());
}
MqttEventPublisher.publish(mqttClient, topic, payload, logger);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,54 +26,11 @@

package org.eclipse.digitaltwin.basyx.aasrepository.feature.mqtt;

import org.eclipse.paho.client.mqttv3.IMqttClient;
import org.eclipse.paho.client.mqttv3.MqttClient;
import org.eclipse.paho.client.mqttv3.MqttConnectOptions;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.persist.MemoryPersistence;
import org.springframework.beans.factory.annotation.Value;
import org.eclipse.digitaltwin.basyx.common.mqttcore.MqttClientConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@ConditionalOnExpression("#{${" + MqttAasRepositoryFeature.FEATURENAME + ".enabled:false} or ${basyx.feature.mqtt.enabled:false}}")
@Configuration
public class MqttAasRepositoryConfiguration {

@ConditionalOnMissingBean
@Bean
public IMqttClient mqttClient(
@Value("${mqtt.clientId}") String clientId,
@Value("${mqtt.hostname}") String hostname,
@Value("${mqtt.port}") int port,
@Value("${mqtt.protocol:tcp}") String protocol,
MqttConnectOptions mqttConnectOptions)
throws MqttException {
IMqttClient mqttClient = new MqttClient(protocol + "://" + hostname + ":" + port, clientId,
new MemoryPersistence());

mqttClient.connect(mqttConnectOptions);

return mqttClient;
}

@ConditionalOnMissingBean
@Bean
@ConfigurationProperties(prefix = "mqtt")
public MqttConnectOptions mqttConnectOptions(
@Value("${mqtt.username:}") String username,
@Value("${mqtt.password:}") String password) {
MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
mqttConnectOptions.setAutomaticReconnect(true);

if (username.isBlank() || password.isBlank())
return mqttConnectOptions;

mqttConnectOptions.setUserName(username);
mqttConnectOptions.setPassword(password.toCharArray());

return mqttConnectOptions;
}
public class MqttAasRepositoryConfiguration extends MqttClientConfiguration {
}
Loading
Loading