diff --git a/src/cli/artemis.toit b/src/cli/artemis.toit index 8194236ad..3a9ccc66f 100644 --- a/src/cli/artemis.toit +++ b/src/cli/artemis.toit @@ -22,7 +22,6 @@ import .pod-specification import .utils import .auth-providers.auth-provider -import .brokers.broker import .sdk import .organization import .server-config diff --git a/src/cli/broker.toit b/src/cli/broker.toit index e6ecbb145..73e48d795 100644 --- a/src/cli/broker.toit +++ b/src/cli/broker.toit @@ -24,7 +24,9 @@ import .utils.patch-build show build-diff-patch build-trivial-patch import ..shared.version import ..shared.utils.patch show Patcher PatchObserver -import .brokers.broker +import .brokers.server +import .brokers.implementations +import .brokers.stores import .event import .firmware import .pod-registry @@ -77,16 +79,24 @@ class Broker: This field is only set if the fleet has devices. */ device-short-strings_/Map? + combined_/bool - broker-connection__/BrokerCli? := null + server__/Server? := null + artifact-store__/ArtifactStore? := null + update-broker__/UpdateBroker? := null + broker-state-reader__/BrokerStateReader? := null + broker-event-reader__/BrokerEventReader? := null + pod-store__/PodStore? := null constructor --.fleet-id/Uuid --.server-config + --combined/bool --cli/Cli --tmp-directory/string --short-strings/Map?: cli_ = cli + combined_ = combined tmp-directory_ = tmp-directory device-short-strings_ = short-strings @@ -95,12 +105,35 @@ class Broker: if network_: return network_ = net.open - broker-connection_ -> BrokerCli: - if not broker-connection__: - broker-connection__ = BrokerCli server-config --cli=cli_ - broker-connection__.ensure-authenticated: | error-message | - cli_.ui.abort "$error-message (broker)." - return broker-connection__ + server_ -> Server: + if not server__: + server__ = Server server-config --cli=cli_ + server__.ensure-authenticated: | error-message | + cli_.ui.abort "$error-message (server)." + return server__ + + artifact-store_ -> ArtifactStore: + if not artifact-store__: artifact-store__ = create-artifact-store server_ + return artifact-store__ + + update-broker_ -> UpdateBroker: + if not update-broker__: + update-broker__ = create-update-broker server_ --combined=combined_ + return update-broker__ + + broker-state-reader_ -> BrokerStateReader: + if not broker-state-reader__: + broker-state-reader__ = create-broker-state-reader server_ + return broker-state-reader__ + + broker-event-reader_ -> BrokerEventReader: + if not broker-event-reader__: + broker-event-reader__ = create-broker-event-reader server_ + return broker-event-reader__ + + pod-store_ -> PodStore: + if not pod-store__: pod-store__ = create-pod-store server_ + return pod-store__ short-string-for_ --device-id/Uuid -> string: if not device-short-strings_: throw "Access to device in non-device fleet." @@ -110,7 +143,7 @@ class Broker: Ensures that the broker is authenticated. */ ensure-authenticated: - broker-connection_ + server_ /** Closes the broker. @@ -118,9 +151,14 @@ class Broker: If the broker opened any connections, closes them as well. */ close: - if broker-connection__: - broker-connection__.close - broker-connection__ = null + artifact-store__ = null + update-broker__ = null + broker-state-reader__ = null + broker-event-reader__ = null + pod-store__ = null + if server__: + server__.close + server__ = null if network_: network_.close network_ = null @@ -145,24 +183,24 @@ class Broker: --broker-config=server-config --part-id=id cli_.cache.get-file-path key: | store/FileStore | - broker-connection_.pod-registry-upload-pod-part contents --part-id=id + pod-store_.pod-registry-upload-pod-part contents --part-id=id store.save contents key := cache-key-pod-manifest --broker-config=server-config --pod-id=pod.id cli_.cache.get-file-path key: | store/FileStore | encoded := ubjson.encode manifest - broker-connection_.pod-registry-upload-pod-manifest encoded --pod-id=pod.id + pod-store_.pod-registry-upload-pod-manifest encoded --pod-id=pod.id store.save encoded - description-ids := broker-connection_.pod-registry-descriptions + description-ids := pod-store_.pod-registry-descriptions --fleet-id=fleet-id --names=[pod.name] --create-if-absent description-id := (description-ids[0] as PodRegistryDescription).id - broker-connection_.pod-registry-add + pod-store_.pod-registry-add --pod-description-id=description-id --pod-id=pod.id @@ -170,7 +208,7 @@ class Broker: tags.do: | tag/string | force := force-tags or (tag == "latest") exception := catch --unwind=(: not is-existing-tag-error_ it): - broker-connection_.pod-registry-tag-set + pod-store_.pod-registry-tag-set --pod-description-id=description-id --pod-id=pod.id --tag=tag @@ -178,7 +216,7 @@ class Broker: if exception: tag-errors.add "Tag '$tag' already exists for pod $pod.name." - registered-pods := broker-connection_.pod-registry-pods --fleet-id=fleet-id --pod-ids=[pod.id] + registered-pods := pod-store_.pod-registry-pods --fleet-id=fleet-id --pod-ids=[pod.id] pod-entry/PodRegistryEntry := registered-pods[0] sorted-uploaded-tags := pod-entry.tags.sort @@ -215,7 +253,7 @@ class Broker: --patch-id=trivial-id cli_.cache.get cache-key: | store/FileStore | trivial := build-trivial-patch patch.bits_ - broker-connection_.upload-firmware trivial + artifact-store_.upload-firmware trivial --firmware-id=trivial-id store.save-via-writer: | writer/io.Writer | trivial.do: writer.write it @@ -230,7 +268,7 @@ class Broker: --patch-id=old-id trivial-old := cli_.cache.get cache-key: | store/FileStore | downloaded := null - catch: downloaded = broker-connection_.download-firmware + catch: downloaded = artifact-store_.download-firmware --id=old-id if not downloaded: cli_.ui.emit --warning "Failed to download old firmware for patch $old-id -> $trivial-id." @@ -267,7 +305,7 @@ class Broker: from64 := base64.encode patch.from_ --url-mode to64 := base64.encode patch.to_ --url-mode cli_.ui.emit --info "Uploading patch $from64 -> $to64 ($diff-size)." - broker-connection_.upload-firmware diff + artifact-store_.upload-firmware diff --firmware-id=diff-id store.save-via-writer: | writer/io.Writer | diff.do: writer.write it @@ -298,7 +336,7 @@ class Broker: --broker-config=server-config --pod-id=pod-id encoded-manifest := cli_.cache.get manifest-key: | store/FileStore | - bytes := broker-connection_.pod-registry-download-pod-manifest + bytes := pod-store_.pod-registry-download-pod-manifest --pod-id=pod-id store.save bytes manifest := ubjson.decode encoded-manifest @@ -310,27 +348,27 @@ class Broker: --broker-config=server-config --part-id=part-id cli_.cache.get key: | store/FileStore | - bytes := broker-connection_.pod-registry-download-pod-part + bytes := pod-store_.pod-registry-download-pod-part part-id store.save bytes list-pods --names/List -> Map: descriptions := ? if names.is-empty: - descriptions = broker-connection_.pod-registry-descriptions --fleet-id=fleet-id + descriptions = pod-store_.pod-registry-descriptions --fleet-id=fleet-id else: - descriptions = broker-connection_.pod-registry-descriptions + descriptions = pod-store_.pod-registry-descriptions --fleet-id=fleet-id --names=names --no-create-if-absent result := {:} descriptions.do: | description/PodRegistryDescription | - pods := broker-connection_.pod-registry-pods --pod-description-id=description.id + pods := pod-store_.pod-registry-pods --pod-description-id=description.id result[description] = pods return result delete --description-names/List: - descriptions := broker-connection_.pod-registry-descriptions + descriptions := pod-store_.pod-registry-descriptions --fleet-id=fleet-id --names=description-names --no-create-if-absent @@ -346,7 +384,7 @@ class Broker: quoted := unknown-pod-descriptions.map: "'$it'" joined := quoted.join ", " cli_.ui.abort "Unknown pods $joined." - broker-connection_.pod-registry-descriptions-delete + pod-store_.pod-registry-descriptions-delete --fleet-id=fleet-id --description-ids=descriptions.map: it.id @@ -355,7 +393,7 @@ class Broker: delete --pod-ids=pod-ids delete --pod-ids/List: - broker-connection_.pod-registry-delete + pod-store_.pod-registry-delete --fleet-id=fleet-id --pod-ids=pod-ids @@ -366,7 +404,7 @@ class Broker: : reference pod-ids := get-pod-ids references - pod-entries := broker-connection_.pod-registry-pods + pod-entries := pod-store_.pod-registry-pods --fleet-id=fleet-id --pod-ids=pod-ids @@ -379,7 +417,7 @@ class Broker: pod-entries.do: | pod-entry/PodRegistryEntry | print-on-stderr_ "pod-entry: $pod-entry.to-json" exception := catch --unwind=(: not is-existing-tag-error_ it): - broker-connection_.pod-registry-tag-set + pod-store_.pod-registry-tag-set --pod-description-id=pod-entry.pod-description-id --pod-id=pod-entry.id --tag=tag @@ -398,7 +436,7 @@ class Broker: assert: reference.is-name-only names.add reference.name - descriptions := broker-connection_.pod-registry-descriptions + descriptions := pod-store_.pod-registry-descriptions --fleet-id=fleet-id --names=names.to-list --no-create-if-absent @@ -406,7 +444,7 @@ class Broker: descriptions.do: | description/PodRegistryDescription | description-id := description.id tags.do: | tag/string | - broker-connection_.pod-registry-tag-remove + pod-store_.pod-registry-tag-remove --pod-description-id=description-id --tag=tag @@ -420,7 +458,7 @@ class Broker: missing-ids := references.filter: | reference/PodReference | not reference.id - pod-ids-response := broker-connection_.pod-registry-pod-ids + pod-ids-response := pod-store_.pod-registry-pod-ids --fleet-id=fleet-id --references=missing-ids @@ -439,12 +477,12 @@ class Broker: return result pod pod-id/Uuid -> PodBroker: - pod-entry := broker-connection_.pod-registry-pods + pod-entry := pod-store_.pod-registry-pods --fleet-id=fleet-id --pod-ids=[pod-id] if not pod-entry.is-empty: description-id := pod-entry[0].pod-description-id - description := broker-connection_.pod-registry-descriptions --ids=[description-id] + description := pod-store_.pod-registry-descriptions --ids=[description-id] if not description.is-empty: return PodBroker --id=pod-id --name=description[0].name --revision=pod-entry[0].revision --tags=pod-entry[0].tags @@ -458,7 +496,7 @@ class Broker: pod-exists reference/PodReference -> bool: pod-id := get-pod-id reference - pod-entry := broker-connection_.pod-registry-pods + pod-entry := pod-store_.pod-registry-pods --fleet-id=fleet-id --pod-ids=[pod-id] return not pod-entry.is-empty @@ -468,7 +506,7 @@ class Broker: Returns a map from id to $DeviceDetailed. */ get-devices --device-ids/List -> Map: - return broker-connection_.get-devices --device-ids=device-ids + return broker-state-reader_.get-devices --device-ids=device-ids update --device-id/Uuid --pod/Pod --base-firmwares/List=[]: update-bulk_ --devices=[device-for --id=device-id] --pods=[pod] --base-firmwares=base-firmwares @@ -552,7 +590,7 @@ class Broker: --warn-only-trivial=warn-only-trivial goals.add goal - broker-connection_.update-goals + update-broker_.update-goals --device-ids=devices.map: it.id --goals=goals @@ -634,7 +672,7 @@ class Broker: return goal get-goal-request-events --device-ids/List --limit/int -> Map: - return broker-connection_.get-events + return broker-event-reader_.get-events --device-ids=device-ids --limit=limit --types=["get-goal"] @@ -645,7 +683,7 @@ class Broker: Returns a map from device-id to $Event. */ get-last-events --device-ids/List -> Map: - result := broker-connection_.get-events + result := broker-event-reader_.get-events --device-ids=device-ids --limit=1 result.map --in-place: | _ events/List | events[0] @@ -658,7 +696,7 @@ class Broker: Returns a map from device-id to List of $Event. */ get-events --device-ids/List --limit/int --types/List? -> Map: - return broker-connection_.get-events + return broker-event-reader_.get-events --device-ids=device-ids --limit=limit --types=types @@ -669,7 +707,7 @@ class Broker: Returns a map from pod id to $PodRegistryEntry. */ get-pod-registry-entry-map --pod-ids/List -> Map: - pod-id-entries := broker-connection_.pod-registry-pods + pod-id-entries := pod-store_.pod-registry-pods --fleet-id=fleet-id --pod-ids=pod-ids pod-entry-map := {:} @@ -688,7 +726,7 @@ class Broker: (pod-registry-entries.map: | entry/PodRegistryEntry | entry.pod-description-id) description-ids := [] description-ids.add-all description-set - descriptions := broker-connection_.pod-registry-descriptions --ids=description-ids + descriptions := pod-store_.pod-registry-descriptions --ids=description-ids description-map := {:} descriptions.do: | description/PodRegistryDescription | description-map[description.id] = description @@ -699,12 +737,12 @@ class Broker: state := { "identity": identity, } - broker-connection_.notify-created + update-broker_.notify-created --device-id=device.id --state=state device-for --id/Uuid -> DeviceDetailed: - devices := broker-connection_.get-devices --device-ids=[id] + devices := broker-state-reader_.get-devices --device-ids=[id] if devices.is-empty: short := short-string-for_ --device-id=id cli_.ui.abort "Device $short does not exist on server." @@ -713,10 +751,10 @@ class Broker: /** Updates the goal state of the device with the given $device-id. - See $BrokerCli.update-goal. + See $UpdateBroker.update-goal. */ update-goal_ --device-id/Uuid [block]: - broker-connection_.update-goal --device-id=device-id block + update-broker_.update-goal --device-id=device-id block container-install -> none --device-id/Uuid @@ -744,11 +782,11 @@ class Broker: // Note: every device in this fleet uses the broker's scope. // device.organization-id is guaranteed to equal the broker's // configured scope, so we don't need to pass it explicitly. - broker-connection_.upload-image program.image32 + artifact-store_.upload-image program.image32 --app-id=id --word-size=32 file.write-contents program.image32 --path="$tmp-dir/image32.bin" - broker-connection_.upload-image program.image64 + artifact-store_.upload-image program.image64 --app-id=id --word-size=64 file.write-contents program.image64 --path="$tmp-dir/image64.bin" diff --git a/src/cli/brokers/broker.toit b/src/cli/brokers/broker.toit deleted file mode 100644 index 820657b5e..000000000 --- a/src/cli/brokers/broker.toit +++ /dev/null @@ -1,287 +0,0 @@ -// Copyright (C) 2022 Toitware ApS. All rights reserved. - -import cli show Cli -import host.file -import encoding.json -import net -import uuid show Uuid - -import ..auth -import ..config -import ..event -import ..device -import ..pod-registry -import ...shared.scope show Scope -import ...shared.server-config -import .supabase -import .http.base - -/** -Responsible for allowing the Artemis CLI to talk to Artemis services on devices. -*/ -interface BrokerCli implements Authenticatable: - // TODO(florian): we probably want to add a `connect` function to this interface. - // At the moment we require the connection to be open when artemis receives the - // broker. - - constructor server-config/ServerConfig --cli/Cli: - if server-config is ServerConfigSupabase: - return create-broker-cli-supabase-http (server-config as ServerConfigSupabase) --cli=cli - if server-config is ServerConfigHttp: - http-config := server-config as ServerConfigHttp - if http-config.tenancy == TENANCY-SHARED: - return create-broker-cli-http-toit-shared http-config - return create-broker-cli-http-toit http-config - throw "Unknown broker config type" - - /** Closes this broker. */ - close -> none - - /** Whether this broker is closed. */ - is-closed -> bool - - /** - A unique ID of the broker that can be used for caching. - May contain "/", in which case the cache will use subdirectories. - */ - id -> string - - /** - Ensures that the user is authenticated. - - If the user is not authenticated, the $block is called. - */ - ensure-authenticated [block] - - /** - Signs the user up with the given $email and $password. - */ - sign-up --email/string --password/string - - /** - Signs the user in with the given $email and $password. - */ - sign-in --email/string --password/string - - /** - Signs the user in using OAuth. - */ - sign-in --provider/string --cli/Cli --open-browser - - /** - Updates the user's email and/or password. - */ - update --email/string? --password/string? - - /** - Signs the user out. - */ - logout - - /** - Updates the goal state of the device with the given $device-id. - - The block is called with a $DeviceDetailed as argument: - - The $block must return a new goal state which replaces the actual goal state. - - The $block is allowed to modify the state maps of the $DeviceDetailed, but is - still required to return the new goal state. It is not enough to just - modify the goal map of the $DeviceDetailed. - */ - update-goal --device-id/Uuid [block] -> none - - /** - Updates the goal states of the devices with the given $device-ids. - - The two lists $device-ids and $goals must be of the same length. - The $device-ids list must be a list of UUIDs. - The $goals list must be a list of Maps, where each map is a goal state. - */ - update-goals --device-ids/List --goals/List -> none - - /** - Uploads an application image with the given $app-id. - - Each $BrokerCli instance is bound to a single $Scope (through its - server config); the image is uploaded for devices in that scope. - - There may be multiple images for the same $app-id, that differ in the $word-size. - Generally $word-size is either 32 or 64. - */ - upload-image - --app-id/Uuid - --word-size/int - contents/ByteArray -> none - - /** - Uploads a firmware with the given $firmware-id. - - The $chunks are a list of byte arrays. - */ - upload-firmware --firmware-id/string chunks/List -> none - - /** - Downloads a firmware chunk. - */ - download-firmware --id/string -> ByteArray - - /** - Informs the broker that a new device has been provisioned. - - The broker registers the device under the configured scope (from its - server-config). For a shared-tenancy broker, this also creates the - corresponding record on the auth side. The $state map is the initial - state of the device; until it connects to the broker there is - (probably) only identity information in it. - */ - notify-created --device-id/Uuid --state/Map -> none - - /** - Fetches all events of the given $types for all devices in the $device-ids list. - If no $types are given, all events are returned. - Returns a mapping from device-id to list of $Event s. - At most $limit events per device are returned. - If $since is not null, only events that are newer than $since are returned. - If there are no events for a device, the device is not included in the map. - */ - get-events -> Map - --types/List?=null - --device-ids/List - --limit/int=10 - --since/Time?=null - - /** - Fetches the device details for the given device ids. - Returns a map from id to $DeviceDetailed. - */ - get-devices --device-ids/List -> Map - - /** - Creates a new pod description. - */ - pod-registry-description-upsert -> int - --fleet-id/Uuid - --name/string - --description/string? - - /** - Deletes the pod descriptions with the given ids. - */ - pod-registry-descriptions-delete --fleet-id/Uuid --description-ids/List -> none - - /** - Adds a pod. - */ - pod-registry-add -> none - --pod-description-id/int - --pod-id/Uuid - - /** - Deletes the pods with the given ids. - */ - pod-registry-delete --fleet-id/Uuid --pod-ids/List -> none - - /** - Adds a tag. - */ - pod-registry-tag-set -> none - --pod-description-id/int - --pod-id/Uuid - --tag/string - --force/bool=false - - /** - Removes a tag. - - Does nothing if the tag is not set. - */ - pod-registry-tag-remove -> none - --pod-description-id/int - --tag/string - - /** - Lists pod descriptions. - - Returns a list of $PodRegistryDescription. - */ - pod-registry-descriptions --fleet-id/Uuid -> List - - /** - Returns a list of descriptions by their ids. - - Returns a list of $PodRegistryDescription. - */ - pod-registry-descriptions --ids/List -> List - - /** - Gets pod descriptions by name. - - If $create-if-absent is true, a new description is created if none - with the given name exists. - - Returns a list of $PodRegistryDescription. - */ - pod-registry-descriptions -> List - --fleet-id/Uuid - --names/List - --create-if-absent/bool - - /** - Returns the pods of a pod description. - - Returns a list of $PodRegistryEntry. - */ - pod-registry-pods --pod-description-id/int -> List - - /** - Returns the pods with the given $pod-ids. - - Returns a list of $PodRegistryEntry. - */ - pod-registry-pods --fleet-id/Uuid --pod-ids/List -> List - - /** - Returns the pod-id for the given name/tag combinations. - - Returns a map from $PodReference to pod ID. References that were not - found are not included in the map. - - The $references list must contain $PodReference objects. - */ - pod-registry-pod-ids --fleet-id/Uuid --references/List -> Map - - /** - Uploads a pod part to the registry. - */ - pod-registry-upload-pod-part -> none - --part-id/string - contents/ByteArray - - /** - Downloads a pod part from the registry. - */ - pod-registry-download-pod-part part-id/string -> ByteArray - - /** - Saves the manifest of a pod. - - The $contents is a binary blob (for example a UBJSON map) that can be used to recover - a pod from its parts. - */ - pod-registry-upload-pod-manifest -> none - --pod-id/Uuid - contents/ByteArray - - /** - Downloads the manifest of a pod. - */ - pod-registry-download-pod-manifest -> ByteArray - --pod-id/Uuid - -with-broker server-config/ServerConfig --cli/Cli [block]: - broker := BrokerCli server-config --cli=cli - try: - block.call broker - finally: - broker.close diff --git a/src/cli/brokers/http/base.toit b/src/cli/brokers/http/base.toit index 95912a633..6c14620e4 100644 --- a/src/cli/brokers/http/base.toit +++ b/src/cli/brokers/http/base.toit @@ -9,7 +9,8 @@ import net.x509 import tls import uuid show Uuid -import ..broker +import ..server +import ..stores import ...device import ...event import ...pod-registry @@ -18,15 +19,11 @@ import ....shared.server-config import ....shared.utils as utils import ....shared.constants show * -create-broker-cli-http-toit server-config/ServerConfigHttp -> BrokerCliHttp: +create-server-http-toit server-config/ServerConfigHttp -> ServerHttp: id := "toit-http/$server-config.host-$server-config.port" - return BrokerCliHttp server-config --id=id + return ServerHttp server-config --id=id -create-broker-cli-http-toit-shared server-config/ServerConfigHttp -> BrokerCliHttpShared: - id := "toit-http/$server-config.host-$server-config.port" - return BrokerCliHttpShared server-config --id=id - -class BrokerCliHttp implements BrokerCli: +class ServerHttp implements Server: network_/net.Interface? := ? id/string server-config_/ServerConfigHttp @@ -73,7 +70,10 @@ class BrokerCliHttp implements BrokerCli: // For simplicity do nothing. // This way we can use the same tests for all brokers. - send-request_ command/int data/any -> any: + scope -> Scope: + return server-config_.scope + + send-request command/int data/any -> any: if is-closed: throw "CLOSED" encoded/ByteArray := ? if command == COMMAND-UPLOAD_: @@ -83,7 +83,7 @@ class BrokerCliHttp implements BrokerCli: else: encoded = #[command] + (json.encode data) - send-request_ encoded: | response/http.Response | + send-encoded-request_ encoded: | response/http.Response | body := response.body // Teapot status codes are exceptions from our server code. // They are handled below. @@ -115,10 +115,10 @@ class BrokerCliHttp implements BrokerCli: return decoded unreachable - send-request_ encoded/ByteArray [block]: + send-encoded-request_ encoded/ByteArray [block]: MAX-ATTEMPTS ::= 3 MAX-ATTEMPTS.repeat: | attempt/int | - response := send-request_ encoded + response := send-encoded-request_ encoded // Cloudflare frequently rejects our requests with a 502, 520 or 546. // Just try again. status-code := response.status-code @@ -131,7 +131,7 @@ class BrokerCliHttp implements BrokerCli: block.call response return - send-request_ encoded/ByteArray -> http.Response: + send-encoded-request_ encoded/ByteArray -> http.Response: if not client_: if server-config_.use-tls or server-config_.root-certificate-ders: client_ = http.Client.tls network_ @@ -158,65 +158,54 @@ class BrokerCliHttp implements BrokerCli: extra-headers -> Map?: return null +class ArtifactStoreHttp implements ArtifactStore: + server/Server - update-goal --device-id/Uuid [block] -> none: - detailed-devices := get-devices --device-ids=[device-id] - if detailed-devices.size != 1: throw "Device not found: $device-id" - detailed-device := detailed-devices[device-id] - new-goal := block.call detailed-device - send-request_ COMMAND-UPDATE-GOAL_ { - "_device_id": "$device-id", - "_goal": new-goal - } + constructor .server: - update-goals --device-ids/List --goals/List -> none: - send-request_ COMMAND-UPDATE-GOALS_ { - "_device_ids": device-ids.map: "$it", - "_goals": goals - } - - get-devices --device-ids/List -> Map: - response := send-request_ COMMAND-GET-DEVICES_ { - "_device_ids": device-ids.map: "$it" - } - result := {:} - response.do: | row/Map | - device-id := Uuid.parse row["device_id"] - goal := row["goal"] - state := row["state"] - result[device-id] = DeviceDetailed --goal=goal --state=state - return result - - upload-image -> none - --app-id/Uuid - --word-size/int - contents/ByteArray: - scope := server-config_.scope.to-json - send-request_ COMMAND-UPLOAD_ { + upload-image --app-id/Uuid --word-size/int contents/ByteArray -> none: + scope := server.scope.to-json + server.send-request COMMAND-UPLOAD_ { "path": "/toit-artemis-assets/$scope/images/$app-id.$word-size", "content": contents, } upload-firmware --firmware-id/string chunks/List -> none: - scope := server-config_.scope.to-json + scope := server.scope.to-json firmware := #[] chunks.do: firmware += it - send-request_ COMMAND-UPLOAD_ { + server.send-request COMMAND-UPLOAD_ { "path": "/toit-artemis-assets/$scope/firmware/$firmware-id", "content": firmware, } download-firmware --id/string -> ByteArray: - scope := server-config_.scope.to-json - return send-request_ COMMAND-DOWNLOAD_ { + scope := server.scope.to-json + return server.send-request COMMAND-DOWNLOAD_ { "path": "/toit-artemis-assets/$scope/firmware/$id", } - notify-created --device-id/Uuid --state/Map -> none: - send-request_ COMMAND-NOTIFY-BROKER-CREATED_ { - "_device_id": "$device-id", - "_state": state, +class BrokerStateReaderHttp implements BrokerStateReader: + server/Server + + constructor .server: + + get-devices --device-ids/List -> Map: + response := server.send-request COMMAND-GET-DEVICES_ { + "_device_ids": device-ids.map: "$it" } + result := {:} + response.do: | row/Map | + device-id := Uuid.parse row["device_id"] + goal := row["goal"] + state := row["state"] + result[device-id] = DeviceDetailed --goal=goal --state=state + return result + +class BrokerEventReaderHttp implements BrokerEventReader: + server/Server + + constructor .server: get-events -> Map --types/List?=null @@ -229,7 +218,7 @@ class BrokerCliHttp implements BrokerCli: "_limit": limit, } if since: payload["_since"] = since.utc.to-iso8601-string - response := send-request_ COMMAND-GET-EVENTS_ payload + response := server.send-request COMMAND-GET-EVENTS_ payload result := {:} current-list/List? := null current-id/Uuid? := null @@ -245,85 +234,117 @@ class BrokerCliHttp implements BrokerCli: current-list.add (Event event-type time data) return result - /** See $BrokerCli.pod-registry-description-upsert. */ +class UpdateBrokerHttp implements UpdateBroker: + server/Server + state-reader_/BrokerStateReader + + constructor .server: + state-reader_ = BrokerStateReaderHttp server + + update-goal --device-id/Uuid [block] -> none: + detailed-devices := state-reader_.get-devices --device-ids=[device-id] + if detailed-devices.size != 1: throw "Device not found: $device-id" + detailed-device := detailed-devices[device-id] + new-goal := block.call detailed-device + server.send-request COMMAND-UPDATE-GOAL_ { + "_device_id": "$device-id", + "_goal": new-goal + } + + update-goals --device-ids/List --goals/List -> none: + server.send-request COMMAND-UPDATE-GOALS_ { + "_device_ids": device-ids.map: "$it", + "_goals": goals + } + + notify-created --device-id/Uuid --state/Map -> none: + server.send-request COMMAND-NOTIFY-BROKER-CREATED_ { + "_device_id": "$device-id", + "_state": state, + } + +class UpdateBrokerHttpCombined extends UpdateBrokerHttp: + constructor server/Server: + super server + + notify-created --device-id/Uuid --state/Map -> none: + server.send-request COMMAND-NOTIFY-BROKER-CREATED_ { + "_device_id": "$device-id", + "_organization_id": server.scope.to-json, + "_state": state, + } + +class PodStoreHttp implements PodStore: + server/Server + + constructor .server: + pod-registry-description-upsert -> int --fleet-id/Uuid --name/string --description/string?: - scope := server-config_.scope.to-json - return send-request_ COMMAND-POD-REGISTRY-DESCRIPTION-UPSERT_ { + scope := server.scope.to-json + return server.send-request COMMAND-POD-REGISTRY-DESCRIPTION-UPSERT_ { "_fleet_id": "$fleet-id", "_organization_id": scope, "_name": name, "_description": description, } - /** See $BrokerCli.pod-registry-descriptions-delete. */ pod-registry-descriptions-delete --fleet-id/Uuid --description-ids/List -> none: - send-request_ COMMAND-POD-REGISTRY-DELETE-DESCRIPTIONS_ { + server.send-request COMMAND-POD-REGISTRY-DELETE-DESCRIPTIONS_ { "_fleet_id": "$fleet-id", "_description_ids": description-ids, } - /** See $BrokerCli.pod-registry-add. */ - pod-registry-add -> none - --pod-description-id/int - --pod-id/Uuid: - send-request_ COMMAND-POD-REGISTRY-ADD_ { + pod-registry-add --pod-description-id/int --pod-id/Uuid -> none: + server.send-request COMMAND-POD-REGISTRY-ADD_ { "_pod_description_id": pod-description-id, "_pod_id": "$pod-id", } - /** See $BrokerCli.pod-registry-delete. */ pod-registry-delete --fleet-id/Uuid --pod-ids/List -> none: - send-request_ COMMAND-POD-REGISTRY-DELETE_ { + server.send-request COMMAND-POD-REGISTRY-DELETE_ { "_fleet_id": "$fleet-id", "_pod_ids": pod-ids.map: "$it", } - /** See $BrokerCli.pod-registry-tag-set. */ pod-registry-tag-set -> none --pod-description-id/int --pod-id/Uuid --tag/string --force/bool=false: - send-request_ COMMAND-POD-REGISTRY-TAG-SET_ { + server.send-request COMMAND-POD-REGISTRY-TAG-SET_ { "_pod_description_id": pod-description-id, "_pod_id": "$pod-id", "_tag": tag, "_force": force, } - /** See $BrokerCli.pod-registry-tag-remove. */ - pod-registry-tag-remove -> none - --pod-description-id/int - --tag/string: - send-request_ COMMAND-POD-REGISTRY-TAG-REMOVE_ { + pod-registry-tag-remove --pod-description-id/int --tag/string -> none: + server.send-request COMMAND-POD-REGISTRY-TAG-REMOVE_ { "_pod_description_id": pod-description-id, "_tag": tag, } - /** See $BrokerCli.pod-registry-descriptions. */ pod-registry-descriptions --fleet-id/Uuid -> List: - response := send-request_ COMMAND-POD-REGISTRY-DESCRIPTIONS_ { + response := server.send-request COMMAND-POD-REGISTRY-DESCRIPTIONS_ { "_fleet_id": "$fleet-id", } return response.map: PodRegistryDescription.from-map it - /** See $(BrokerCli.pod-registry-descriptions --ids). */ pod-registry-descriptions --ids/List -> List: - response := send-request_ COMMAND-POD-REGISTRY-DESCRIPTIONS-BY-IDS_ { + response := server.send-request COMMAND-POD-REGISTRY-DESCRIPTIONS-BY-IDS_ { "_description_ids": ids, } return response.map: PodRegistryDescription.from-map it - /** See $(BrokerCli.pod-registry-descriptions --fleet-id --names --create-if-absent). */ pod-registry-descriptions -> List --fleet-id/Uuid --names/List --create-if-absent/bool: - scope := server-config_.scope.to-json - response := send-request_ COMMAND-POD-REGISTRY-DESCRIPTIONS-BY-NAMES_ { + scope := server.scope.to-json + response := server.send-request COMMAND-POD-REGISTRY-DESCRIPTIONS-BY-NAMES_ { "_fleet_id": "$fleet-id", "_organization_id": scope, "_names": names, @@ -331,26 +352,23 @@ class BrokerCliHttp implements BrokerCli: } return response.map: PodRegistryDescription.from-map it - /** See $(BrokerCli.pod-registry-pods --pod-description-id). */ pod-registry-pods --pod-description-id/int -> List: - response := send-request_ COMMAND-POD-REGISTRY-PODS_ { + response := server.send-request COMMAND-POD-REGISTRY-PODS_ { "_pod_description_id": pod-description-id, "_limit": 1000, "_offset": 0, } return response.map: PodRegistryEntry.from-map it - /** See $(BrokerCli.pod-registry-pods --fleet-id --pod-ids). */ pod-registry-pods --fleet-id/Uuid --pod-ids/List -> List: - response := send-request_ COMMAND-POD-REGISTRY-PODS-BY-IDS_ { + response := server.send-request COMMAND-POD-REGISTRY-PODS-BY-IDS_ { "_fleet_id": "$fleet-id", - "_pod_ids": (pod-ids.map: "$it"), + "_pod_ids": pod-ids.map: "$it", } return response.map: PodRegistryEntry.from-map it - /** See $BrokerCli.pod-registry-pod-ids. */ pod-registry-pod-ids --fleet-id/Uuid --references/List -> Map: - response := send-request_ COMMAND-POD-REGISTRY-POD-IDS-BY-REFERENCE_ { + response := server.send-request COMMAND-POD-REGISTRY-POD-IDS-BY-REFERENCE_ { "_fleet_id": "$fleet-id", "_references": references.map: | reference/PodReference | ref := { @@ -370,54 +388,28 @@ class BrokerCliHttp implements BrokerCli: result[reference] = pod-id return result - /** See $BrokerCli.pod-registry-upload-pod-part. */ - pod-registry-upload-pod-part -> none - --part-id/string - contents/ByteArray: - scope := server-config_.scope.to-json - send-request_ COMMAND-UPLOAD_ { + pod-registry-upload-pod-part --part-id/string contents/ByteArray -> none: + scope := server.scope.to-json + server.send-request COMMAND-UPLOAD_ { "path": "/toit-artemis-pods/$scope/part/$part-id", "content": contents, } - /** See $BrokerCli.pod-registry-download-pod-part. */ pod-registry-download-pod-part part-id/string -> ByteArray: - scope := server-config_.scope.to-json - return send-request_ COMMAND-DOWNLOAD-PRIVATE_ { + scope := server.scope.to-json + return server.send-request COMMAND-DOWNLOAD-PRIVATE_ { "path": "/toit-artemis-pods/$scope/part/$part-id", } - /** See $BrokerCli.pod-registry-upload-pod-manifest. */ - pod-registry-upload-pod-manifest -> none - --pod-id/Uuid - contents/ByteArray: - scope := server-config_.scope.to-json - send-request_ COMMAND-UPLOAD_ { + pod-registry-upload-pod-manifest --pod-id/Uuid contents/ByteArray -> none: + scope := server.scope.to-json + server.send-request COMMAND-UPLOAD_ { "path": "/toit-artemis-pods/$scope/manifest/$pod-id", "content": contents, } - /** See $BrokerCli.pod-registry-download-pod-manifest. */ pod-registry-download-pod-manifest --pod-id/Uuid -> ByteArray: - scope := server-config_.scope.to-json - return send-request_ COMMAND-DOWNLOAD-PRIVATE_ { + scope := server.scope.to-json + return server.send-request COMMAND-DOWNLOAD-PRIVATE_ { "path": "/toit-artemis-pods/$scope/manifest/$pod-id", } - -/** -A $BrokerCliHttp specialisation for shared-tenancy HTTP deployments. - -In a shared-tenancy deployment the broker also owns the auth-side device - record; this override sends the configured scope (organization-id) so the - broker can populate that record alongside the broker-side state. -*/ -class BrokerCliHttpShared extends BrokerCliHttp: - constructor server-config/ServerConfigHttp --id/string: - super server-config --id=id - - notify-created --device-id/Uuid --state/Map -> none: - send-request_ COMMAND-NOTIFY-BROKER-CREATED_ { - "_device_id": "$device-id", - "_organization_id": server-config_.scope.to-json, - "_state": state, - } diff --git a/src/cli/brokers/implementations.toit b/src/cli/brokers/implementations.toit new file mode 100644 index 000000000..a5028f578 --- /dev/null +++ b/src/cli/brokers/implementations.toit @@ -0,0 +1,45 @@ +// Copyright (C) 2026 Toitware ApS. All rights reserved. + +import .server +import .stores +import .http.base +import .supabase + +/** Constructs the artifact store configured to use $server. */ +create-artifact-store server/Server -> ArtifactStore: + if server is ServerSupabase: + return ArtifactStoreSupabase (server as ServerSupabase) + return ArtifactStoreHttp server + +/** +Constructs the update broker configured to use $server. + +If $combined is true, the same server also provides the Artemis device + registry and provisioning updates both services. +*/ +create-update-broker server/Server --combined/bool -> UpdateBroker: + if server is ServerSupabase: + if combined: + return UpdateBrokerSupabaseCombined (server as ServerSupabase) + return UpdateBrokerSupabase (server as ServerSupabase) + if combined: + return UpdateBrokerHttpCombined server + return UpdateBrokerHttp server + +/** Constructs the optional broker-state reader configured to use $server. */ +create-broker-state-reader server/Server -> BrokerStateReader: + if server is ServerSupabase: + return BrokerStateReaderSupabase (server as ServerSupabase) + return BrokerStateReaderHttp server + +/** Constructs the optional broker-event reader configured to use $server. */ +create-broker-event-reader server/Server -> BrokerEventReader: + if server is ServerSupabase: + return BrokerEventReaderSupabase (server as ServerSupabase) + return BrokerEventReaderHttp server + +/** Constructs the pod store configured to use $server. */ +create-pod-store server/Server -> PodStore: + if server is ServerSupabase: + return PodStoreSupabase (server as ServerSupabase) + return PodStoreHttp server diff --git a/src/cli/brokers/server.toit b/src/cli/brokers/server.toit new file mode 100644 index 000000000..a1464b191 --- /dev/null +++ b/src/cli/brokers/server.toit @@ -0,0 +1,66 @@ +// Copyright (C) 2026 Toitware ApS. All rights reserved. + +import cli show Cli + +import ..auth +import ...shared.scope show Scope +import ...shared.server-config +import .supabase +import .http.base + +/** +Shared connection to a configured Artemis server. + +The server owns transport, authentication, and scope. Implementations of the + individual broker and storage interfaces use the same server without being + bundled into one backend object. +*/ +interface Server implements Authenticatable: + constructor server-config/ServerConfig --cli/Cli: + if server-config is ServerConfigSupabase: + return create-server-supabase-http + (server-config as ServerConfigSupabase) + --cli=cli + if server-config is ServerConfigHttp: + return create-server-http-toit (server-config as ServerConfigHttp) + throw "Unknown server config type" + + /** Closes this server connection. */ + close -> none + + /** Whether this server connection is closed. */ + is-closed -> bool + + /** A unique ID that can be used for caching. */ + id -> string + + /** Scope used by implementations connected to this server. */ + scope -> Scope + + /** Sends one request using this server's transport and authentication. */ + send-request command/int data/any -> any + + /** See $Authenticatable.ensure-authenticated. */ + ensure-authenticated [block] + + /** See $Authenticatable.sign-up. */ + sign-up --email/string --password/string + + /** See $(Authenticatable.sign-in --email --password). */ + sign-in --email/string --password/string + + /** See $(Authenticatable.sign-in --provider --cli --open-browser). */ + sign-in --provider/string --cli/Cli --open-browser + + /** See $Authenticatable.update. */ + update --email/string? --password/string? + + /** See $Authenticatable.logout. */ + logout + +with-server server-config/ServerConfig --cli/Cli [block]: + server := Server server-config --cli=cli + try: + block.call server + finally: + server.close diff --git a/src/cli/brokers/stores.toit b/src/cli/brokers/stores.toit new file mode 100644 index 000000000..5849fa6b5 --- /dev/null +++ b/src/cli/brokers/stores.toit @@ -0,0 +1,99 @@ +// Copyright (C) 2026 Toitware ApS. All rights reserved. + +import uuid show Uuid + +/** Stores firmware and application images consumed by devices. */ +interface ArtifactStore: + /** Uploads an application image for the configured scope. */ + upload-image --app-id/Uuid --word-size/int contents/ByteArray -> none + + /** Uploads firmware chunks under $firmware-id. */ + upload-firmware --firmware-id/string chunks/List -> none + + /** Downloads the firmware stored under $id. */ + download-firmware --id/string -> ByteArray + +/** Publishes desired update state for devices. */ +interface UpdateBroker: + /** Updates one device's goal using $block. */ + update-goal --device-id/Uuid [block] -> none + + /** Updates several device goals in one operation. */ + update-goals --device-ids/List --goals/List -> none + + /** Registers a newly provisioned device and its initial $state. */ + notify-created --device-id/Uuid --state/Map -> none + +/** Reads the broker's goal and reported-state snapshot for known devices. */ +interface BrokerStateReader: + /** Fetches device details keyed by device ID. */ + get-devices --device-ids/List -> Map + +/** Reads the optional event history recorded by a broker. */ +interface BrokerEventReader: + /** Fetches events for the selected devices. */ + get-events -> Map + --types/List?=null + --device-ids/List + --limit/int=10 + --since/Time?=null + +/** Stores pod metadata, manifests, and content-addressed parts. */ +interface PodStore: + /** Creates or updates a pod description. */ + pod-registry-description-upsert -> int + --fleet-id/Uuid + --name/string + --description/string? + + /** Deletes pod descriptions by ID. */ + pod-registry-descriptions-delete --fleet-id/Uuid --description-ids/List -> none + + /** Adds a pod to a description. */ + pod-registry-add --pod-description-id/int --pod-id/Uuid -> none + + /** Deletes pods by ID. */ + pod-registry-delete --fleet-id/Uuid --pod-ids/List -> none + + /** Sets a tag on a pod. */ + pod-registry-tag-set -> none + --pod-description-id/int + --pod-id/Uuid + --tag/string + --force/bool=false + + /** Removes a tag from a pod description. */ + pod-registry-tag-remove --pod-description-id/int --tag/string -> none + + /** Lists the pod descriptions in a fleet. */ + pod-registry-descriptions --fleet-id/Uuid -> List + + /** Returns pod descriptions by ID. */ + pod-registry-descriptions --ids/List -> List + + /** Gets pod descriptions by name. */ + pod-registry-descriptions -> List + --fleet-id/Uuid + --names/List + --create-if-absent/bool + + /** Returns the pods belonging to a description. */ + pod-registry-pods --pod-description-id/int -> List + + /** Returns pods by ID. */ + pod-registry-pods --fleet-id/Uuid --pod-ids/List -> List + + /** Resolves pod references to pod IDs. */ + pod-registry-pod-ids --fleet-id/Uuid --references/List -> Map + + /** Uploads a content-addressed pod part. */ + pod-registry-upload-pod-part --part-id/string contents/ByteArray -> none + + /** Downloads a content-addressed pod part. */ + pod-registry-download-pod-part part-id/string -> ByteArray + + /** Uploads a pod manifest. */ + pod-registry-upload-pod-manifest --pod-id/Uuid contents/ByteArray -> none + + /** Downloads a pod manifest. */ + pod-registry-download-pod-manifest --pod-id/Uuid -> ByteArray diff --git a/src/cli/brokers/supabase/supabase.toit b/src/cli/brokers/supabase/supabase.toit index 0af339002..10dbfeb52 100644 --- a/src/cli/brokers/supabase/supabase.toit +++ b/src/cli/brokers/supabase/supabase.toit @@ -7,11 +7,12 @@ import certificate-roots import uuid show Uuid import ..http.base +import ..server import ...config import ...utils.supabase import ....shared.server-config -create-broker-cli-supabase-http server-config/ServerConfigSupabase --cli/Cli -> BrokerCliSupabase: +create-server-supabase-http server-config/ServerConfigSupabase --cli/Cli -> ServerSupabase: local-storage := ConfigLocalStorage --cli=cli --auth-key="$(CONFIG-SERVER-AUTHS-KEY).$(server-config.name)" supabase-client := supabase.Client --server-config=server-config --local-storage=local-storage id := "supabase/$server-config.host" @@ -36,12 +37,11 @@ create-broker-cli-supabase-http server-config/ServerConfigSupabase --cli/Cli -> --root-certificate-ders=server-config.root-certificate-der ? [server-config.root-certificate-der] : null --poll-interval=server-config.poll-interval --scope=server-config.scope - --tenancy=server-config.tenancy - return BrokerCliSupabase --id=id supabase-client http-config + return ServerSupabase --id=id supabase-client http-config -class BrokerCliSupabase extends BrokerCliHttp: +class ServerSupabase extends ServerHttp: supabase-client_/supabase.Client? := null constructor --id/string .supabase-client_ http-config/ServerConfigHttp: @@ -72,22 +72,18 @@ class BrokerCliSupabase extends BrokerCliHttp: supabase-client_.auth.logout /** - Registers a newly provisioned device with the broker. + Creates the auth-side record for a newly provisioned device. - For a shared-tenancy deployment the broker and the auth provider live - in the same Supabase project; the broker is responsible for creating - the device row in the auth-side `devices` table as part of the - notify-created handshake. + $UpdateBrokerSupabaseCombined uses this before it notifies the broker about + the device's initial state. */ - notify-created --device-id/Uuid --state/Map -> none: - if server-config_.tenancy == TENANCY-SHARED: - // The existing schema keeps both columns; they now hold the same ID. - supabase-client_.rest.insert "devices" --no-return-inserted { - "id": "$device-id", - "alias": "$device-id", - "organization_id": server-config_.scope.to-json, - } - super --device-id=device-id --state=state + register-device --device-id/Uuid -> none: + // The existing schema keeps both columns; they now hold the same ID. + supabase-client_.rest.insert "devices" --no-return-inserted { + "id": "$device-id", + "alias": "$device-id", + "organization_id": scope.to-json, + } extra-headers -> Map: bearer/string := supabase-client_.session_ @@ -96,3 +92,33 @@ class BrokerCliSupabase extends BrokerCliHttp: return { "Authorization": "Bearer $bearer", } + +class UpdateBrokerSupabase extends UpdateBrokerHttp: + supabase-server_/ServerSupabase + + constructor .supabase-server_: + super supabase-server_ + +class UpdateBrokerSupabaseCombined extends UpdateBrokerSupabase: + constructor server/ServerSupabase: + super server + + notify-created --device-id/Uuid --state/Map -> none: + supabase-server_.register-device --device-id=device-id + super --device-id=device-id --state=state + +class ArtifactStoreSupabase extends ArtifactStoreHttp: + constructor server/ServerSupabase: + super server + +class BrokerStateReaderSupabase extends BrokerStateReaderHttp: + constructor server/ServerSupabase: + super server + +class BrokerEventReaderSupabase extends BrokerEventReaderHttp: + constructor server/ServerSupabase: + super server + +class PodStoreSupabase extends PodStoreHttp: + constructor server/ServerSupabase: + super server diff --git a/src/cli/cmds/auth.toit b/src/cli/cmds/auth.toit index 823dbfe42..0b3d7d048 100644 --- a/src/cli/cmds/auth.toit +++ b/src/cli/cmds/auth.toit @@ -9,7 +9,7 @@ import ..config import ..auth show Authenticatable import ..server-config import ..auth-providers.auth-provider show with-auth-provider AuthProvider -import ..brokers.broker show with-broker BrokerCli +import ..brokers.server show with-server Server SIGNIN-OPTIONS ::= [ OptionEnum "provider" ["github", "google"] @@ -177,8 +177,8 @@ with-authenticatable invocation/Invocation [block]: server-config = broker ? get-server-from-config --cli=cli --key=CONFIG-BROKER-DEFAULT-KEY : get-server-from-config --cli=cli --name=server - with-broker --cli=cli server-config: | broker/BrokerCli | - block.call server-config.name broker + with-server --cli=cli server-config: | configured-server/Server | + block.call server-config.name configured-server else: server-config = get-server-from-config --cli=cli --key=CONFIG-ARTEMIS-DEFAULT-KEY with-auth-provider server-config --cli=cli: | server/AuthProvider | diff --git a/src/cli/cmds/fleet.toit b/src/cli/cmds/fleet.toit index 45b3d91b5..08842f380 100644 --- a/src/cli/cmds/fleet.toit +++ b/src/cli/cmds/fleet.toit @@ -14,7 +14,7 @@ import .auth as auth-cmd import .serial show PARTITION-OPTION import .utils_ import ..artemis -import ..brokers.broker show BrokerCli +import ..brokers.server show Server import ..config import ..cache import ..device @@ -668,7 +668,7 @@ login invocation/Invocation: broker := fleet.broker broker-name := broker.server-config.name ui.emit --info "Logging in to broker '$broker-name'." - broker-authenticatable := BrokerCli broker.server-config --cli=cli + broker-authenticatable := Server broker.server-config --cli=cli auth-cmd.sign-in invocation --name=broker-name --authenticatable=broker-authenticatable add-devices invocation/Invocation: diff --git a/src/cli/fleet.toit b/src/cli/fleet.toit index a9b13422d..448123c5a 100644 --- a/src/cli/fleet.toit +++ b/src/cli/fleet.toit @@ -418,8 +418,11 @@ class Fleet: id = fleet-file.id broker-scope = fleet-file.broker-scope cli_ = cli + broker-config := fleet-file.broker-config + combined := broker-config == artemis.server-config broker = Broker - --server-config=fleet-file.broker-config + --server-config=broker-config + --combined=combined --fleet-id=id --tmp-directory=artemis.tmp-directory --short-strings=short-strings @@ -430,6 +433,10 @@ class Fleet: if not org: cli.ui.abort "Organization $organization-id does not exist or is not accessible." + /** Reports whether $server-config also provides this fleet's Artemis roles. */ + is-combined-server_ server-config/ServerConfig -> bool: + return server-config == artemis.server-config + /** The organization-id encoded inside $broker-scope. @@ -772,6 +779,7 @@ class FleetWithDevices extends Fleet: server-config := fleet-file_.servers.get server-name old-broker := Broker --server-config=server-config + --combined=is-combined-server_ server-config --short-strings=device-short-strings_ --fleet-id=id --tmp-directory=artemis.tmp-directory @@ -815,6 +823,7 @@ class FleetWithDevices extends Fleet: server-config := fleet-file_.servers.get server-name old-broker := Broker --server-config=server-config + --combined=is-combined-server_ server-config --short-strings=device-short-strings_ --fleet-id=id --tmp-directory=artemis.tmp-directory @@ -909,6 +918,7 @@ class FleetWithDevices extends Fleet: config := fleet-file.servers[name] Broker --server-config=config + --combined=is-combined-server_ config --fleet-id=id --short-strings=device-short-strings_ --cli=cli_ @@ -1094,9 +1104,9 @@ class FleetWithDevices extends Fleet: /** Provisions a device. - Registers the device with the broker. For a shared-tenancy deployment, - the broker also creates the corresponding row in the auth provider's - device table. + Registers the device with the broker. If the same server also provides the + Artemis device registry, provisioning creates the corresponding registry + row as well. Writes the identity file to $out-path. */ @@ -1138,6 +1148,7 @@ class FleetWithDevices extends Fleet: : new-broker-config.with --scope=fleet-file_.broker-scope new-broker := Broker --server-config=scoped-new-broker-config + --combined=is-combined-server_ scoped-new-broker-config --short-strings=device-short-strings_ --fleet-id=id --tmp-directory=artemis.tmp-directory @@ -1192,6 +1203,7 @@ class FleetWithDevices extends Fleet: broker-names.do: | name/string | current-broker := Broker --server-config=fleet-file.servers[name] + --combined=is-combined-server_ fleet-file.servers[name] --short-strings=device-short-strings_ --fleet-id=id --tmp-directory=artemis.tmp-directory diff --git a/src/shared/server-config.toit b/src/shared/server-config.toit index 542ac3210..98e15534c 100644 --- a/src/shared/server-config.toit +++ b/src/shared/server-config.toit @@ -8,19 +8,6 @@ import uuid show Uuid import .scope show Scope -/** -A multi-tenant deployment: the broker shares its underlying storage with - an auth provider (so creating a device on the broker also has to land - a row in the auth provider's device table). -*/ -TENANCY-SHARED ::= "shared" - -/** -A single-tenant deployment: the broker is self-contained and does not - need to coordinate with an auth provider's storage. -*/ -TENANCY-DEDICATED ::= "dedicated" - abstract class ServerConfig: name/string @@ -32,18 +19,10 @@ abstract class ServerConfig: */ scope/Scope? - /** - The deployment shape of this server. - - Either $TENANCY-SHARED, $TENANCY-DEDICATED, or null (caller hasn't - specified; treated as dedicated by consumers). - */ - tenancy/string? - cache-key_/string? := null ders-already-installed_/bool := false - constructor.from-sub_ .name --.scope/Scope?=null --.tenancy/string?=null: + constructor.from-sub_ .name --.scope/Scope?=null: /** Creates a new broker-config from a JSON map. @@ -105,10 +84,9 @@ abstract class ServerConfig: Returns a copy of this config with the non-null fields overridden. Used to attach a fleet's scope to a $ServerConfig that was loaded from - the global CLI config (which never carries a scope), or to tag the - config with a tenancy mode. + the global CLI config, which never carries a scope. */ - abstract with --scope/Scope?=null --tenancy/string?=null -> ServerConfig + abstract with --scope/Scope?=null -> ServerConfig /** A unique key that can be used for caching. @@ -164,8 +142,6 @@ class ServerConfigSupabase extends ServerConfig implements supabase.ServerConfig if use-tls == null: use-tls = json.contains "root_certificate_name" scope-value := json.get "scope" scope/Scope? := scope-value and (Scope scope-value) - tenancy/string? := json.get "tenancy" - return ServerConfigSupabase name --host=json["host"] --anon=json["anon"] @@ -173,7 +149,6 @@ class ServerConfigSupabase extends ServerConfig implements supabase.ServerConfig --use-tls=use-tls --root-certificate-der=root-der --scope=scope - --tenancy=tenancy constructor name/string --.host @@ -181,9 +156,8 @@ class ServerConfigSupabase extends ServerConfig implements supabase.ServerConfig --.use-tls=true --.root-certificate-der=null --.poll-interval=DEFAULT-POLL-INTERVAL - --scope/Scope?=null - --tenancy/string?=null: - super.from-sub_ name --scope=scope --tenancy=tenancy + --scope/Scope?=null: + super.from-sub_ name --scope=scope operator== other: if other is not ServerConfigSupabase: return false @@ -214,8 +188,6 @@ class ServerConfigSupabase extends ServerConfig implements supabase.ServerConfig result["root_certificate_der_id"] = serialized if scope: result["scope"] = scope.to-json - if tenancy: - result["tenancy"] = tenancy return result to-service-json [--der-serializer] --base64/bool=false -> Map: @@ -248,8 +220,7 @@ class ServerConfigSupabase extends ServerConfig implements supabase.ServerConfig with -> ServerConfigSupabase --host/string?=null - --scope/Scope?=null - --tenancy/string?=null: + --scope/Scope?=null: return ServerConfigSupabase name --host=(host or this.host) @@ -258,7 +229,6 @@ class ServerConfigSupabase extends ServerConfig implements supabase.ServerConfig --root-certificate-der=root-certificate-der --poll-interval=poll-interval --scope=(scope or this.scope) - --tenancy=(tenancy or this.tenancy) /** A broker configuration for an HTTP-based broker. @@ -287,7 +257,6 @@ class ServerConfigHttp extends ServerConfig: if use-tls == null: use-tls = config.contains "root_certificate_names" scope-value := config.get "scope" scope/Scope? := scope-value and (Scope scope-value) - tenancy/string? := config.get "tenancy" return ServerConfigHttp name --host=config["host"] --port=config.get "port" @@ -298,7 +267,6 @@ class ServerConfigHttp extends ServerConfig: --admin-headers=config.get "admin_headers" --poll-interval=Duration --us=config["poll_interval"] --scope=scope - --tenancy=tenancy constructor name/string --.host @@ -309,10 +277,9 @@ class ServerConfigHttp extends ServerConfig: --.device-headers --.admin-headers --.poll-interval=DEFAULT-POLL-INTERVAL - --scope/Scope?=null - --tenancy/string?=null: + --scope/Scope?=null: - super.from-sub_ name --scope=scope --tenancy=tenancy + super.from-sub_ name --scope=scope operator== other: if other is not ServerConfigHttp: return false @@ -342,23 +309,19 @@ class ServerConfigHttp extends ServerConfig: result["admin_headers"] = admin-headers if scope: result["scope"] = scope.to-json - if tenancy: - result["tenancy"] = tenancy return result to-service-json [--der-serializer] --base64/bool=false -> Map: result := to-json --der-serializer=der-serializer --base64=base64 result.remove "admin_headers" result.remove "scope" - result.remove "tenancy" return result compute-cache-key_ -> string: return "$host:$port:$path" with -> ServerConfigHttp - --scope/Scope?=null - --tenancy/string?=null: + --scope/Scope?=null: return ServerConfigHttp name --host=host @@ -370,4 +333,3 @@ class ServerConfigHttp extends ServerConfig: --admin-headers=admin-headers --poll-interval=poll-interval --scope=(scope or this.scope) - --tenancy=(tenancy or this.tenancy) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2b2075c7b..dd1c9d5d1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -61,7 +61,7 @@ set (SUPABASE_BROKER_FLAG "--supabase-local-broker") set (ARTEMIS_FLAGS ${SUPABASE_ARTEMIS_FLAG} "--http-server") set (BROKER_FLAGS "--http-toit-broker" - "--http-toit-shared-broker" + "--http-toit-combined-broker" ${SUPABASE_BROKER_FLAG} ${SUPABASE_ARTEMIS_BROKER_FLAG} ) diff --git a/tests/broker-test.toit b/tests/broker-test.toit index 2f4690dfd..40fdd1bc9 100644 --- a/tests/broker-test.toit +++ b/tests/broker-test.toit @@ -7,7 +7,9 @@ import io import log import monitor import net -import artemis.cli.brokers.broker +import artemis.cli.brokers.server +import artemis.cli.brokers.implementations +import artemis.cli.brokers.stores import artemis.cli.device show DeviceDetailed import artemis.service.device show Device import artemis.cli.event show Event @@ -45,10 +47,17 @@ run-test // We are going to reuse the cli for all tests (and only authenticate once). // However, we will need multiple services. - test-broker.with-cli: | broker-cli/broker.BrokerCli | + test-broker.with-cli: | configured-server/server.Server | // Make sure we are authenticated. - broker-cli.ensure-authenticated: - broker-cli.sign-in --email=TEST-EXAMPLE-COM-EMAIL --password=TEST-EXAMPLE-COM-PASSWORD + configured-server.ensure-authenticated: + configured-server.sign-in --email=TEST-EXAMPLE-COM-EMAIL --password=TEST-EXAMPLE-COM-PASSWORD + + artifact-store := implementations.create-artifact-store configured-server + update-broker := implementations.create-update-broker + configured-server + --combined=test-broker.combined + state-reader := implementations.create-broker-state-reader configured-server + event-reader := implementations.create-broker-event-reader configured-server [DEVICE1, DEVICE2].do: | device/Device | identity := { @@ -58,11 +67,11 @@ run-test state := { "identity": identity, } - broker-cli.notify-created --device-id=device.id --state=state + update-broker.notify-created --device-id=device.id --state=state - if broker-name == "http-toit-shared" or broker-name == "supabase-local-artemis": - // A shared-tenancy broker must populate the auth-side device record - // as part of notify-created. + if test-broker.combined: + // A combined broker must populate the auth-side device record as part + // of notify-created. [DEVICE1, DEVICE2].do: | device/Device | auth-record := test-broker.backdoor.get-auth-device --device-id=device.id expect-not-null auth-record @@ -72,23 +81,27 @@ run-test network := net.open try: - test-image --test-broker=test-broker broker-cli --network=network - test-firmware --test-broker=test-broker broker-cli --network=network - test-goal --test-broker=test-broker broker-cli --network=network - test-state-devices --test-broker=test-broker broker-cli --network=network + test-image --test-broker=test-broker artifact-store --network=network + test-firmware --test-broker=test-broker artifact-store --network=network + test-goal --test-broker=test-broker update-broker --network=network + test-state-devices + --test-broker=test-broker + update-broker + state-reader + --network=network // Test the events last, as it depends on test_goal to have run. // It also does state updates which could interfere with the other tests, // like the health test. - test-events --test-broker=test-broker broker-cli --network=network + test-events --test-broker=test-broker event-reader --network=network finally: network.close -test-goal --test-broker/TestBroker broker-cli/broker.BrokerCli --network/net.Client: +test-goal --test-broker/TestBroker broker-cli/stores.UpdateBroker --network/net.Client: test-broker.with-service: | broker-service/broker.BrokerService | test-goal broker-cli broker-service --network=network -test-goal broker-cli/broker.BrokerCli broker-service/broker.BrokerService --network/net.Client: +test-goal broker-cli/stores.UpdateBroker broker-service/broker.BrokerService --network/net.Client: 3.repeat: | test-iteration | if test-iteration == 2: // Send a config update while the service is not connected. @@ -137,11 +150,11 @@ test-goal broker-cli/broker.BrokerCli broker-service/broker.BrokerService --netw finally: broker-connection.close -test-image --test-broker/TestBroker broker-cli/broker.BrokerCli --network/net.Client: +test-image --test-broker/TestBroker broker-cli/stores.ArtifactStore --network/net.Client: test-broker.with-service: | broker-service/broker.BrokerService | test-image broker-cli broker-service --network=network -test-image broker-cli/broker.BrokerCli broker-service/broker.BrokerService --network/net.Client: +test-image broker-cli/stores.ArtifactStore broker-service/broker.BrokerService --network/net.Client: 2.repeat: | iteration | APP-ID ::= Uuid.uuid5 "app-$random" "test-app-$iteration-$Time.monotonic-us" contents-32 := ? @@ -172,11 +185,11 @@ test-image broker-cli/broker.BrokerCli broker-service/broker.BrokerService --net finally: broker-connection.close -test-firmware --test-broker/TestBroker broker-cli/broker.BrokerCli --network/net.Client: +test-firmware --test-broker/TestBroker broker-cli/stores.ArtifactStore --network/net.Client: test-broker.with-service: | broker-service/broker.BrokerService | test-firmware broker-cli broker-service --network=network -test-firmware broker-cli/broker.BrokerCli broker-service/broker.BrokerService --network/net.Client: +test-firmware broker-cli/stores.ArtifactStore broker-service/broker.BrokerService --network/net.Client: 3.repeat: | iteration | FIRMWARE-ID ::= "test-app-$iteration" contents := ? @@ -232,17 +245,25 @@ build-state_ device/Device token/string -> Map: "firmware": build-encoded-firmware --device=device, } -test-state-devices --test-broker/TestBroker broker-cli/broker.BrokerCli --network/net.Client: +test-state-devices + --test-broker/TestBroker + update-broker/stores.UpdateBroker + state-reader/stores.BrokerStateReader + --network/net.Client: test-broker.with-service: | broker-service/broker.BrokerService | - test-state-devices broker-cli broker-service --network=network - -test-state-devices broker-cli/broker.BrokerCli broker-service/broker.BrokerService --network/net.Client: - broker-cli.update-goal --device-id=DEVICE1.id: | device/DeviceDetailed | + test-state-devices update-broker state-reader broker-service --network=network + +test-state-devices + update-broker/stores.UpdateBroker + state-reader/stores.BrokerStateReader + broker-service/broker.BrokerService + --network/net.Client: + update-broker.update-goal --device-id=DEVICE1.id: | device/DeviceDetailed | { "state-test": "1234", } - broker-cli.update-goal --device-id=DEVICE2.id: | device/DeviceDetailed | + update-broker.update-goal --device-id=DEVICE2.id: | device/DeviceDetailed | { "state-test": "5678", } @@ -281,14 +302,14 @@ test-state-devices broker-cli/broker.BrokerCli broker-service/broker.BrokerServi device1/DeviceDetailed := ? device2/DeviceDetailed := ? if it == 0: - devices := broker-cli.get-devices --device-ids=[DEVICE1.id] + devices := state-reader.get-devices --device-ids=[DEVICE1.id] expect-equals 1 devices.size device1 = devices[DEVICE1.id] - devices = broker-cli.get-devices --device-ids=[DEVICE2.id] + devices = state-reader.get-devices --device-ids=[DEVICE2.id] expect-equals 1 devices.size device2 = devices[DEVICE2.id] else: - devices := broker-cli.get-devices --device-ids=[DEVICE1.id, DEVICE2.id] + devices := state-reader.get-devices --device-ids=[DEVICE1.id, DEVICE2.id] expect-equals 2 devices.size device1 = devices[DEVICE1.id] device2 = devices[DEVICE2.id] @@ -307,7 +328,7 @@ test-state-devices broker-cli/broker.BrokerCli broker-service/broker.BrokerServi expect-equals "goal2" device2.reported-state-goal["token"] expect-equals "pending-firmware2" device2.pending-firmware -test-events --test-broker/TestBroker broker-cli/broker.BrokerCli --network/net.Client: +test-events --test-broker/TestBroker event-reader/stores.BrokerEventReader --network/net.Client: test-broker.with-service: | broker-service1/broker.BrokerService | test-broker.with-service: | broker-service2/broker.BrokerService | broker-connection1 := null @@ -317,7 +338,7 @@ test-events --test-broker/TestBroker broker-cli/broker.BrokerCli --network/net.C broker-connection2 = broker-service2.connect --network=network --device=DEVICE2 test-events test-broker - broker-cli + event-reader broker-service1 broker-service2 broker-connection1 @@ -328,7 +349,7 @@ test-events --test-broker/TestBroker broker-cli/broker.BrokerCli --network/net.C test-events test-broker/TestBroker - broker-cli/broker.BrokerCli + event-reader/stores.BrokerEventReader broker-service1/broker.BrokerService broker-service2/broker.BrokerService broker-connection1/broker.BrokerConnection @@ -339,7 +360,7 @@ test-events // on the previous tests to do that for us. // Services poll for the goals, which lead to events. - events := broker-cli.get-events + events := event-reader.get-events --device-ids=[DEVICE1.id] --types=["get-goal"] --limit=1000 @@ -349,10 +370,10 @@ test-events test-broker.backdoor.clear-events start := Time.now - events = broker-cli.get-events --device-ids=[DEVICE1.id] --types=["test-event"] + events = event-reader.get-events --device-ids=[DEVICE1.id] --types=["test-event"] expect-not (events.contains DEVICE1.id) - events = broker-cli.get-events --device-ids=[DEVICE1.id, DEVICE2.id] --types=["test-event"] + events = event-reader.get-events --device-ids=[DEVICE1.id, DEVICE2.id] --types=["test-event"] expect-not (events.contains DEVICE1.id) expect-not (events.contains DEVICE2.id) @@ -362,7 +383,7 @@ test-events 2.repeat: device-ids := it == 0 ? [DEVICE1.id] : [DEVICE1.id, DEVICE2.id] - events = broker-cli.get-events + events = event-reader.get-events --device-ids=device-ids --types=["test-event"] expect (events.contains DEVICE1.id) @@ -373,7 +394,7 @@ test-events // Test the since parameter. now := Time.now - events = broker-cli.get-events + events = event-reader.get-events --device-ids=device-ids --types=["test-event"] --since=now @@ -384,7 +405,7 @@ test-events total-events1++ broker-connection2.report-event --type="test-event2" "test-data-$it" - events = broker-cli.get-events + events = event-reader.get-events --device-ids=[DEVICE1.id, DEVICE2.id] --types=["test-event2"] --limit=100 @@ -403,7 +424,7 @@ test-events expected-suffix-- // Limit to 5 per device. - events = broker-cli.get-events + events = event-reader.get-events --device-ids=[DEVICE1.id, DEVICE2.id] --types=["test-event2"] --limit=5 @@ -427,7 +448,7 @@ test-events // Limit to 20 per device. // Device 1 should have 10 events, device 2 should have 15 events. - events = broker-cli.get-events + events = event-reader.get-events --device-ids=[DEVICE1.id, DEVICE2.id] --types=["test-event2"] --limit=20 @@ -446,7 +467,7 @@ test-events expected-suffix-- // Make sure we test one of the most common use cases: getting just one event. - events = broker-cli.get-events + events = event-reader.get-events --device-ids=[DEVICE1.id, DEVICE2.id] --types=["test-event2"] --limit=1 @@ -466,7 +487,7 @@ test-events broker-connection2.report-event --type="test-event2" "test-data-$(it + 20)" // Limit to events since 'checkpoint'. - events = broker-cli.get-events + events = event-reader.get-events --device-ids=[DEVICE1.id, DEVICE2.id] --types=["test-event2"] --since=checkpoint @@ -485,7 +506,7 @@ test-events expected-suffix-- // Limit to events since 'checkpoint' and limit to 3. - events = broker-cli.get-events + events = event-reader.get-events --device-ids=[DEVICE1.id, DEVICE2.id] --types=["test-event2"] --since=checkpoint @@ -505,7 +526,7 @@ test-events expected-suffix-- // Limit to events since 'checkpoint' and limit to 10. - events = broker-cli.get-events + events = event-reader.get-events --device-ids=[DEVICE1.id, DEVICE2.id] --types=["test-event2"] --since=checkpoint @@ -534,7 +555,7 @@ test-events broker-connection2.report-event --type="test-event4" "test-data-$(it + 40)" // Get events for type 3 and 4. - events = broker-cli.get-events + events = event-reader.get-events --device-ids=[DEVICE1.id] --types=["test-event3", "test-event4"] expect (events.contains DEVICE1.id) @@ -553,7 +574,7 @@ test-events expect4 = not expect4 // Same for both devices at the same time. - events = broker-cli.get-events + events = event-reader.get-events --device-ids=[DEVICE1.id, DEVICE2.id] --types=["test-event3", "test-event4"] expect (events.contains DEVICE1.id) @@ -576,14 +597,14 @@ test-events expect4 = not expect4 // Get all events for device 1. - events = broker-cli.get-events + events = event-reader.get-events --device-ids=[DEVICE1.id] --limit=1000 expect (events.contains DEVICE1.id) expect-equals total-events1 events[DEVICE1.id].size // Only get the last event for device 1. - events = broker-cli.get-events + events = event-reader.get-events --device-ids=[DEVICE1.id] --limit=1 expect (events.contains DEVICE1.id) @@ -595,7 +616,7 @@ test-events total-events1++ // Get all events for device 1 again. - events = broker-cli.get-events + events = event-reader.get-events --device-ids=[DEVICE1.id] --limit=1000 expect (events.contains DEVICE1.id) diff --git a/tests/broker.toit b/tests/broker.toit index 649352079..500fe9bc2 100644 --- a/tests/broker.toit +++ b/tests/broker.toit @@ -12,7 +12,7 @@ import supabase.filter show equals import system import uuid show Uuid -import artemis.cli.brokers.broker show BrokerCli +import artemis.cli.brokers.server show Server import artemis.service.brokers.broker show BrokerService import .supabase-local-server @@ -23,27 +23,27 @@ import artemis.shared.server-config ServerConfig ServerConfigHttp ServerConfigSupabase - TENANCY-SHARED import .utils class TestBroker: server-config/ServerConfig backdoor/BrokerBackdoor + combined/bool - constructor .server-config .backdoor: + constructor .server-config .backdoor --.combined: with-cli [block]: with-tmp-config-cli: | cli/Cli | - broker-cli/BrokerCli? := null + server/Server? := null try: - // The BrokerCli operates inside a fleet's scope; attach + // The implementations operate inside a fleet's scope; attach // TEST-SCOPE here rather than on the bare server-config (which // is also reused as a global-config entry in the tests). scoped-config := server-config.with --scope=TEST-SCOPE - broker-cli = BrokerCli scoped-config --cli=cli - block.call broker-cli + server = Server scoped-config --cli=cli + block.call server finally: - if broker-cli: broker-cli.close + if server: server.close with-service [block]: logger := log.default.with-name "testing-service" @@ -58,8 +58,8 @@ interface BrokerBackdoor: /** Creates a new device with the given $device-id and initial $state. - For a shared-tenancy broker this also writes the device into the - auth-side devices table. + For a combined broker this also writes the device into the auth-side + devices table. */ create-device --device-id/Uuid --state/Map -> none @@ -98,34 +98,36 @@ with-broker server-config := get-supabase-config --sub-directory=sub-dir service-key := get-supabase-service-key --sub-directory=sub-dir server-config.poll-interval = Duration --ms=500 - // The artemis-supabase broker is the toit-hosted, shared-tenancy - // deployment that also owns the auth-side devices table; the - // public-supabase broker is dedicated. - if type == "supabase-local-artemis": - server-config = server-config.with --tenancy=TENANCY-SHARED + // The Artemis Supabase project provides both the device registry and + // broker, while the public Supabase broker only provides broker roles. + combined := type == "supabase-local-artemis" // The backdoor operates inside a fleet's scope (TEST-SCOPE in // tests); the TestBroker's server-config stays scope-less because // it's also reused as a global-config entry. - backdoor := SupabaseBackdoor (server-config.with --scope=TEST-SCOPE) service-key - test-server := TestBroker server-config backdoor + backdoor := SupabaseBackdoor + (server-config.with --scope=TEST-SCOPE) + service-key + --combined=combined + test-server := TestBroker server-config backdoor --combined=combined block.call test-server else if type == "http" or type == "http-toit": with-http-broker block - else if type == "http-toit-shared": - with-http-broker --tenancy=TENANCY-SHARED block + else if type == "http-toit-combined": + with-http-broker --combined block else: throw "Unknown broker type: $type" class ToitHttpBackdoor implements BrokerBackdoor: server/HttpBroker server-config_/ServerConfigHttp + combined_/bool - constructor .server .server-config_: + constructor .server .server-config_ --combined/bool: + combined_ = combined create-device --device-id/Uuid --state/Map: - if server-config_.tenancy == TENANCY-SHARED: - // Shared-tenancy: the broker also owns the auth-side devices - // record. Mirror what BrokerCliHttpShared does at notify-created. + if combined_: + // The combined server also owns the auth-side device record. server.insert-auth-device --device-id="$device-id" --organization-id=server-config_.scope.to-json @@ -146,7 +148,7 @@ class ToitHttpBackdoor implements BrokerBackdoor: stop -> none: server.stop -with-http-broker --name="test-broker" --tenancy/string?=null [block]: +with-http-broker --name="test-broker" --combined/bool=false [block]: server := HttpBroker 0 port-latch := monitor.Latch server-task := task:: server.start port-latch @@ -166,14 +168,15 @@ with-http-broker --name="test-broker" --tenancy/string?=null [block]: --device-headers={ "X-Artemis-Header": "true", } - if tenancy: server-config = server-config.with --tenancy=tenancy - // The backdoor operates inside a fleet's scope (TEST-SCOPE in // tests); the TestBroker's server-config stays scope-less because // it's also reused as a global-config entry. - backdoor/ToitHttpBackdoor := ToitHttpBackdoor server (server-config.with --scope=TEST-SCOPE) + backdoor/ToitHttpBackdoor := ToitHttpBackdoor + server + (server-config.with --scope=TEST-SCOPE) + --combined=combined - test-server := TestBroker server-config backdoor + test-server := TestBroker server-config backdoor --combined=combined try: block.call test-server finally: @@ -183,14 +186,15 @@ with-http-broker --name="test-broker" --tenancy/string?=null [block]: class SupabaseBackdoor implements BrokerBackdoor: server-config_/ServerConfigSupabase service-key_/string + combined_/bool - constructor .server-config_ .service-key_: + constructor .server-config_ .service-key_ --combined/bool: + combined_ = combined create-device --device-id/Uuid --state/Map: with-backdoor-client_: | client/supabase.Client | - if server-config_.tenancy == TENANCY-SHARED: - // Shared-tenancy: the broker also owns the auth-side devices - // table. Mirror what BrokerCliSupabase does at notify-created. + if combined_: + // The combined project also owns the auth-side devices table. client.rest.insert "devices" --no-return-inserted { "id": "$device-id", "alias": "$device-id", diff --git a/tests/pod-registry-test.toit b/tests/pod-registry-test.toit index 11c509a43..6be7b938a 100644 --- a/tests/pod-registry-test.toit +++ b/tests/pod-registry-test.toit @@ -6,7 +6,9 @@ import encoding.ubjson import expect show * import log import net -import artemis.cli.brokers.broker +import artemis.cli.brokers.server +import artemis.cli.brokers.implementations +import artemis.cli.brokers.stores import artemis.cli.pod-registry show * import artemis.service.brokers.broker @@ -22,15 +24,16 @@ run-test broker-name/string test-broker/TestBroker: - test-broker.with-cli: | broker-cli/broker.BrokerCli | + test-broker.with-cli: | configured-server/server.Server | // Make sure we are authenticated. - broker-cli.ensure-authenticated: - broker-cli.sign-in --email=TEST-EXAMPLE-COM-EMAIL --password=TEST-EXAMPLE-COM-PASSWORD + configured-server.ensure-authenticated: + configured-server.sign-in --email=TEST-EXAMPLE-COM-EMAIL --password=TEST-EXAMPLE-COM-PASSWORD - test-pod-registry --test-broker=test-broker broker-cli - test-pods --test-broker=test-broker broker-cli + pod-store := implementations.create-pod-store configured-server + test-pod-registry --test-broker=test-broker pod-store + test-pods --test-broker=test-broker pod-store -test-pod-registry --test-broker/TestBroker broker-cli/broker.BrokerCli: +test-pod-registry --test-broker/TestBroker broker-cli/stores.PodStore: fleet-id := random-uuid // Get the list of descriptions. Should be emtpy. @@ -296,7 +299,7 @@ test-pod-registry --test-broker/TestBroker broker-cli/broker.BrokerCli: expect-equals 0 pods.size -test-pods --test-broker/TestBroker broker-cli/broker.BrokerCli: +test-pods --test-broker/TestBroker broker-cli/stores.PodStore: 3.repeat: | iteration | pod-id := random-uuid id1 := "$random-uuid" diff --git a/tools/http_servers/public/broker/broker.toit b/tools/http_servers/public/broker/broker.toit index e981a6395..4f02d1f15 100644 --- a/tools/http_servers/public/broker/broker.toit +++ b/tools/http_servers/public/broker/broker.toit @@ -48,8 +48,8 @@ class HttpBroker extends HttpServer: device-goals_/Map := {:} events_/Map := {:} // Map from device-id to list of events. - // Shared-tenancy auth-side device records. Populated when notify-created - // includes an organization-id (i.e. the BrokerCliHttpShared wire shape). + // Auth-side device records for a combined server. Populated when + // notify-created includes an organization-id. // Maps device-id -> {id, alias, organization_id}. auth-devices_/Map := {:}