Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add new wot-models definition update endpoint #1843 #2103

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
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,11 @@ public boolean isDryRun() {
return isExpectedBoolean(DittoHeaderDefinition.DRY_RUN, Boolean.TRUE);
}

@Override
public boolean isExternalDryRun() {
return isExpectedBoolean(DittoHeaderDefinition.EXTERNAL_DRY_RUN, Boolean.TRUE);
}

@Override
public boolean isSudo() {
return isExpectedBoolean(DittoHeaderDefinition.DITTO_SUDO, Boolean.TRUE);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,16 @@ public enum DittoHeaderDefinition implements HeaderDefinition {
*/
DRY_RUN("ditto-dry-run", boolean.class, false, false, HeaderValueValidators.getBooleanValidator()),

/**
* Header definition for specifying dry-run behavior from external clients.
* <p>
* Key: {@code "dry-run"}, Java type: {@code boolean}.
* </p>
*
* @since 3.7.0
*/
EXTERNAL_DRY_RUN("dry-run", boolean.class, true, true, HeaderValueValidators.getBooleanValidator()),

/**
* Header definition for read subjects value.
* <p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,14 @@ static DittoHeadersBuilder newBuilder(final JsonObject jsonObject) {
*/
boolean isDryRun();

/**
* Returns whether a command is to be executed as a dry-run from an external request.
*
* @return {@code true} if dry-run is specified externally, {@code false} otherwise.
* @since 3.7.0
*/
boolean isExternalDryRun();

/**
* Indicates whether this command is flagged as sudo command which should ignore some preventions.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ enum Category {
*/
MERGE,

/**
* Category of commands that change the state of entities.
*
* @since 3.7.0
*/
MIGRATE,

/**
* Category of commands that delete entities.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ public final class ImmutableDittoHeadersTest {
private static final String KNOWN_TRACEPARENT = "00-dfca0d990402884d22e909a87ac677ec-94fc4da95e842f96-01";
private static final String KNOWN_TRACESTATE = "eclipse=ditto";
private static final boolean KNOWN_DITTO_RETRIEVE_DELETED = true;
private static final boolean KNOWN_DITTO_EXTERNAL_DRY_RUN = true;
private static final String KNOWN_DITTO_ACKREGATOR_ADDRESS = "here!";

private static final String KNOWN_DITTO_GET_METADATA = "attributes/*/key";
Expand Down Expand Up @@ -224,6 +225,7 @@ public void settingAllKnownHeadersWorksAsExpected() {
KNOWN_PRE_DEFINED_EXTRA_FIELDS_READ_GRANT_OBJECT.formatAsString())
.putHeader(DittoHeaderDefinition.PRE_DEFINED_EXTRA_FIELDS_OBJECT.getKey(),
KNOWN_PRE_DEFINED_EXTRA_FIELDS_OBJECT.formatAsString())
.putHeader(DittoHeaderDefinition.EXTERNAL_DRY_RUN.getKey(), String.valueOf(KNOWN_DITTO_EXTERNAL_DRY_RUN))
.build();

assertThat(underTest).isEqualTo(expectedHeaderMap);
Expand Down Expand Up @@ -534,7 +536,6 @@ public void toJsonReturnsExpected() {
.set(DittoHeaderDefinition.CONNECTION_ID.getKey(), KNOWN_CONNECTION_ID)
.set(DittoHeaderDefinition.EXPECTED_RESPONSE_TYPES.getKey(),
charSequencesToJsonArray(KNOWN_EXPECTED_RESPONSE_TYPES))
.set(DittoHeaderDefinition.PUT_METADATA.getKey(), KNOWN_METADATA_HEADERS.toJson())
.set(DittoHeaderDefinition.ALLOW_POLICY_LOCKOUT.getKey(), KNOWN_ALLOW_POLICY_LOCKOUT)
.set(DittoHeaderDefinition.WEAK_ACK.getKey(), KNOWN_IS_WEAK_ACK)
.set(DittoHeaderDefinition.EVENT_JOURNAL_TAGS.getKey(),
Expand All @@ -548,8 +549,6 @@ public void toJsonReturnsExpected() {
.set(DittoHeaderDefinition.LIVE_CHANNEL_CONDITION.getKey(), KNOWN_LIVE_CHANNEL_CONDITION)
.set(DittoHeaderDefinition.LIVE_CHANNEL_CONDITION_MATCHED.getKey(),
KNOWN_LIVE_CHANNEL_CONDITION_MATCHED)
.set(DittoHeaderDefinition.GET_METADATA.getKey(), KNOWN_DITTO_GET_METADATA)
.set(DittoHeaderDefinition.DELETE_METADATA.getKey(), KNOWN_DITTO_DELETE_METADATA)
.set(DittoHeaderDefinition.DITTO_METADATA.getKey(), KNOWN_DITTO_METADATA)
.set(DittoHeaderDefinition.AT_HISTORICAL_REVISION.getKey(), KNOWN_AT_HISTORICAL_REVISION)
.set(DittoHeaderDefinition.AT_HISTORICAL_TIMESTAMP.getKey(), KNOWN_AT_HISTORICAL_TIMESTAMP.toString())
Expand All @@ -560,6 +559,10 @@ public void toJsonReturnsExpected() {
KNOWN_PRE_DEFINED_EXTRA_FIELDS_READ_GRANT_OBJECT)
.set(DittoHeaderDefinition.PRE_DEFINED_EXTRA_FIELDS_OBJECT.getKey(),
KNOWN_PRE_DEFINED_EXTRA_FIELDS_OBJECT)
.set(DittoHeaderDefinition.EXTERNAL_DRY_RUN.getKey(), KNOWN_DITTO_EXTERNAL_DRY_RUN)
.set(DittoHeaderDefinition.PUT_METADATA.getKey(), KNOWN_METADATA_HEADERS.toJson())
.set(DittoHeaderDefinition.GET_METADATA.getKey(), KNOWN_DITTO_GET_METADATA)
.set(DittoHeaderDefinition.DELETE_METADATA.getKey(), KNOWN_DITTO_DELETE_METADATA)
.build();

final Map<String, String> allKnownHeaders = createMapContainingAllKnownHeaders();
Expand Down Expand Up @@ -806,6 +809,8 @@ private static Map<String, String> createMapContainingAllKnownHeaders() {
KNOWN_PRE_DEFINED_EXTRA_FIELDS_READ_GRANT_OBJECT.formatAsString());
result.put(DittoHeaderDefinition.PRE_DEFINED_EXTRA_FIELDS_OBJECT.getKey(),
KNOWN_PRE_DEFINED_EXTRA_FIELDS_OBJECT.formatAsString());
result.put(DittoHeaderDefinition.EXTERNAL_DRY_RUN.getKey(),
String.valueOf(KNOWN_DITTO_EXTERNAL_DRY_RUN));

return result;
}
Expand Down
183 changes: 183 additions & 0 deletions documentation/src/main/resources/openapi/ditto-api-2.yml
Original file line number Diff line number Diff line change
Expand Up @@ -739,6 +739,101 @@ paths:
$ref: '#/components/responses/PreconditionFailed'
'424':
$ref: '#/components/responses/DependencyFailed'
'/api/2/things/{thingId}/migrateDefinition':
post:
summary: Update the definition of an existing Thing
description: |-
Updates the definition of the specified thing by providing a new definition URL along with an optional migration payload.

The request body allows specifying:
- A new Thing definition URL.
- A migration payload containing updates to attributes and features.
- Patch conditions to ensure consistent updates.
- Whether properties should be initialized if missing.

If the `dry-run` query parameter or header is set to `true`, the request will return the calculated migration result without applying any changes.

### Example:
```json
{
"thingDefinitionUrl": "https://example.com/new-thing-definition.json",
"migrationPayload": {
"attributes": {
"manufacturer": "New Corp"
},
"features": {
"sensor": {
"properties": {
"status": {
"temperature": {
"value": 25.0
}
}
}
}
}
},
"patchConditions": {
"thing:/features/sensor": "not(exists(/features/sensor))"
},
"initializeMissingPropertiesFromDefaults": true
}
```
tags:
- Things
parameters:
- $ref: '#/components/parameters/ThingIdPathParam'
- name: dry-run
in: query
description: 'If set to `true`, performs a dry-run and returns the migration result without applying changes.'
required: false
schema:
type: boolean
default: false
requestBody:
description: 'JSON payload containing the new definition URL, migration payload, patch conditions, and initialization flag.'
required: true
content:
application/json:
schema:
$ref: '#/components/requestBodies/MigrateThingDefinitionRequest/content/application~1json/schema'
responses:
'200':
description: 'The thing definition was successfully updated, and the updated Thing is returned.'
content:
application/json:
schema:
$ref: '#/components/responses/MigrateThingDefinitionResponse/content/application~1json/schema'
'202':
description: Dry-run successful. The migration result is returned without applying changes.
content:
application/json:
schema:
$ref: '#/components/responses/MigrateThingDefinitionResponse/content/application~1json/schema'
'400':
description: The request could not be processed due to invalid input.
content:
application/json:
schema:
$ref: '#/components/schemas/AdvancedError'
'401':
description: Unauthorized request due to missing authentication.
content:
application/json:
schema:
$ref: '#/components/schemas/AdvancedError'
'404':
description: The specified thing could not be found.
content:
application/json:
schema:
$ref: '#/components/schemas/AdvancedError'
'412':
description: The update conditions were not met.
content:
application/json:
schema:
$ref: '#/components/schemas/AdvancedError'
'/api/2/things/{thingId}/definition':
get:
summary: Retrieve the definition of a specific thing
Expand Down Expand Up @@ -8212,6 +8307,59 @@ components:
randomizationInterval: 5m
description: Optional request payload for `activateTokenIntegration` policy action.
required: false
MigrateThingDefinitionRequest:
content:
application/json:
schema:
type: object
description: JSON payload to migrate the definition of a Thing.
properties:
thingDefinitionUrl:
type: string
format: uri
description: The URL of the new Thing definition to be applied.
example: 'https://models.example.com/thing-definition-1.0.0.tm.jsonld'
migrationPayload:
type: object
description: Optional migration payload with updates to attributes and features.
properties:
attributes:
type: object
additionalProperties: true
description: Attributes to be updated in the thing.
example:
manufacturer: New Corp
location: 'Berlin, main floor'
features:
type: object
additionalProperties:
type: object
properties:
properties:
type: object
additionalProperties: true
description: Features to be updated in the thing.
example:
thermostat:
properties:
status:
temperature:
value: 23.5
unit: DEGREE_CELSIUS
patchConditions:
type: object
description: Optional conditions to apply the migration only if the existing thing matches the specified values.
additionalProperties:
type: string
example:
'thing:/features/thermostat': not(exists(/features/thermostat))
initializeMissingPropertiesFromDefaults:
type: boolean
description: Flag indicating whether missing properties should be initialized with default values.
example: true
default: false
required:
- thingDefinitionUrl
responses:
EntityTooLarge:
description: The created or modified entity is larger than the accepted limit of 100 kB.
Expand Down Expand Up @@ -8300,6 +8448,41 @@ components:
application/json:
schema:
$ref: '#/components/schemas/ModuleUpdatedLogLevel'
MigrateThingDefinitionResponse:
description: 'The thing definition was successfully updated, and the updated Thing is returned.'
content:
application/json:
schema:
type: object
description: Response payload after applying or simulating a migration to a Thing.
properties:
thingId:
type: string
description: Unique identifier representing the migrated Thing.
patch:
type: object
description: The patch containing updates to the Thing.
properties:
definition:
$ref: '#/components/schemas/Definition'
attributes:
$ref: '#/components/schemas/Attributes'
features:
$ref: '#/components/schemas/Features'
mergeStatus:
type: string
description: |
Indicates the result of the migration process.
- `APPLIED`: The migration was successfully applied.
- `DRY_RUN`: The migration result was calculated but not applied.
enum:
- APPLIED
- DRY_RUN
example: APPLIED
required:
- thingId
- patch
- mergeStatus
parameters:
AllowPolicyLockoutParam:
name: allow-policy-lockout
Expand Down
13 changes: 13 additions & 0 deletions documentation/src/main/resources/openapi/sources/api-2-index.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ paths:
$ref: "./paths/things/index.yml"
'/api/2/things/{thingId}':
$ref: "./paths/things/thing.yml"
'/api/2/things/{thingId}/migrateDefinition':
$ref: "./paths/things/migrateDefinition.yml"
'/api/2/things/{thingId}/definition':
$ref: "./paths/things/definition.yml"
'/api/2/things/{thingId}/policyId':
Expand Down Expand Up @@ -214,6 +216,11 @@ components:
$ref: "./requests/patchValue.yml"
ActivateTokenIntegration:
$ref: "./requests/policies/actions/activateTokenIntegration.yml"
MigrateThingDefinitionRequest:
content:
application/json:
schema:
$ref: "./requests/things/migrateThingDefinitionRequest.yml"

responses:
EntityTooLarge:
Expand All @@ -230,6 +237,12 @@ components:
$ref: "./responses/successUpdateLogLevel.yml"
SuccessUpdateLogLevelSinglePod:
$ref: "./responses/successUpdateLogLevelSinglePod.yml"
MigrateThingDefinitionResponse:
description: The thing definition was successfully updated, and the updated Thing is returned.
content:
application/json:
schema:
$ref: "./responses/things/migrateThingDefinitionResponse.yml"

parameters:
AllowPolicyLockoutParam:
Expand Down
Loading
Loading