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
119 changes: 119 additions & 0 deletions docs/spec/library/ipc_commands/getconfiguration_ipc.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# `GetConfiguration` IPC command

The `GetConfiguration` IPC command retrieves a component's configuration stored
at the nucleus level using the `gg_config` core-bus interface.

- [get-config-1] It can be invoked with the topic `GetConfiguration`.
- [get-config-2] It can get any component's configurations under the `services`
section.
- [get-config-3] It does not require access control policy in recipe to work.
- [get-config-4] It cannot retrieve any values under the `system` section of
config.

## Parameters

- [get-config-params-1] `componentName` is an optional parameter of type buffer.
- [get-config-params-1.1] If not provided, the calling component's name is
used.
- [get-config-params-2] `keyPath` is an optional parameter of type list.
- [get-config-params-2.1] List elements are buffers containing a single level
in the key hierarchy.
- [get-config-params-2.2] If not provided, will assume the entire component
configuration was requested.

## Response

- [get-config-resp-1] On success, returns a map containing `componentName` and
`value` keys.
- [get-config-resp-1.1] For non-map values, the `value` contains the key
itself with the retrieved value.
- [get-config-resp-1.2] For map values, the `value` contains the map contents
without the key itself.
- [get-config-resp-2] On failure, returns a map containing `message`,
`_service`, `_message` and `_errorCode`.
- [get-config-resp-2.1] `ResourceNotFoundError` is returned if the key path
does not exist.

## Examples

Following are a few examples of how the request and response could look like in
different scenarios based on the configuration as follows:

```
"DefaultConfiguration": {
"test_str": "hi",
"sample_map": {
"key1": "value1",
"key2_list": [
"subkey1",
"subkey2"
],
"key10": 10.0123456,
"key4": null,
"key5_map": {
"subkey1":"subvalue1",
"subkey2":"subvalue2"
}
}
}
```

Case 1: Get Configuration key path with float

- Request: `{"keyPath": ["sample_map", "key10"]}`
- Response:
`{"componentName":"com.example.ConfigUpdater","value":{"key10":10.0123456}}`

Case 2: Get Configuration key path with list

- Request: `{"keyPath": ["sample_map", "key2_list"]}`
- Response:
`{"componentName":"com.example.ConfigUpdater","value":{"key2_list":["subkey1","subkey2"]}}`

Case 3: Get Configuration key path with map

- Request: `{"keyPath": ["sample_map", "key5_map"]}`
- Response:
`{"componentName":"com.example.ConfigUpdater","value":{"subkey1":"subvalue1","subkey2":"subvalue2"}}`

> Note that the key isn't included with the response

Case 4: Get Configuration key path with string

- Request: `{"keyPath": ["sample_map", "key5_map", "subkey1"]}`
- Response:
`{"componentName":"com.example.ConfigUpdater","value":{"subkey1":"subvalue1"}}`

Case 5: Get Configuration key path with null

- Request:`{"keyPath": ["sample_map", "key4"]}`
- Response:`{"componentName":"com.example.ConfigUpdater","value":{"key4":null}}`

Case 6: Get Configuration key path does not exist.

- Request:
`{"componentName": "com.example.ConfigUpdater", "keyPath": ["randomKey"]}`
- Response:
`{"message":"Key not found","_service":"aws.greengrass#GreengrassCoreIPC","_message":"Key not found","_errorCode":"ResourceNotFoundError"}`

RAW:

```shell
Packet from client 9:
Headers:
[:content-type] => String(StrBytes { bytes: b"application/json" })
[service-model-type] => String(StrBytes { bytes: b"aws.greengrass#GetConfigurationRequest" })
[:message-type] => Int32(0)
[:message-flags] => Int32(0)
[:stream-id] => Int32(1)
[operation] => String(StrBytes { bytes: b"aws.greengrass#GetConfiguration" })
Value: {"componentName": "com.example.ConfigUpdater", "keyPath": ["randomKey"]}
Packet from server:
Headers:
[:content-type] => String(StrBytes { bytes: b"application/json" })
[service-model-type] => String(StrBytes { bytes: b"aws.greengrass#ResourceNotFoundError" })
[:message-type] => Int32(1)
[:message-flags] => Int32(2)
[:stream-id] => Int32(1)
Value: {"message":"Key not found","_service":"aws.greengrass#GreengrassCoreIPC","_message":"Key not found","_errorCode":"ResourceNotFoundError"}
```
4 changes: 2 additions & 2 deletions fc_deps.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
},
"ggl_sdk": {
"url": "https://github.com/aws-greengrass/aws-greengrass-sdk-lite.git",
"rev": "9f9b95363d6de4c68ee5d34ba5c6065b4c1b31fb",
"hash": "sha256-tIFIKvZI10WDHBfdFBYAbgHVpNaoUUhzsO8tshk5o3A="
"rev": "b83773450bccf6974cded2005792a2400ce5e018",
"hash": "sha256-2bvRk23DnGQe9DqbZBDDWt1JzikIbwD4fF8yFr6j+HE="
}
}
77 changes: 77 additions & 0 deletions misc/db_to_yaml.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#!/bin/bash

if [ "$1" = "--help" ] || [ "$1" = "-h" ]; then
echo "Usage: $0 <path_to_config.db>"
echo "Convert Greengrass configuration database to YAML format"
echo ""
echo "Arguments:"
echo " <path_to_config.db> Path to the configuration database file"
echo ""
echo "Options:"
echo " -h, --help Show this help message and exit"
exit 0
fi

if [ $# -ne 1 ]; then
echo "Usage: $0 <path_to_config.db>"
exit 1
fi

DB_PATH="$1"

if [ ! -f "$DB_PATH" ]; then
echo "Error: Database file $DB_PATH not found"
exit 1
fi

# Create a temporary file for processing
TEMP_FILE=$(mktemp)

# Function to process hierarchy recursively
process_node() {
local parent_id="$1"
local indent="$2"
sqlite3 "$DB_PATH" "SELECT k.keyvalue, k.keyid, v.value FROM keyTable k LEFT JOIN relationTable r ON k.keyid = r.keyid LEFT JOIN valueTable v ON k.keyid = v.keyid WHERE r.parentid = $parent_id ORDER BY k.keyid;" | while IFS='|' read -r keyvalue keyid value; do
child_count=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM relationTable WHERE parentid = $keyid;")

if [ "$child_count" -gt 0 ]; then
echo "${indent}${keyvalue}:" >> "$TEMP_FILE"
process_node "$keyid" "$indent "
elif [ -n "$value" ]; then
clean_value=$(echo "$value" | sed 's/^"//; s/"$//')
if [[ "$clean_value" =~ ^[0-9]+$ ]]; then
echo "${indent}${keyvalue}: $clean_value" >> "$TEMP_FILE"
else
echo "${indent}${keyvalue}: \"$clean_value\"" >> "$TEMP_FILE"
fi
else
echo "${indent}${keyvalue}: {}" >> "$TEMP_FILE"
fi
done
}

# Start output
echo "---" > "$TEMP_FILE"

# Process root nodes
sqlite3 "$DB_PATH" "SELECT k.keyvalue, k.keyid, v.value FROM keyTable k LEFT JOIN relationTable r ON k.keyid = r.keyid LEFT JOIN valueTable v ON k.keyid = v.keyid WHERE r.parentid IS NULL ORDER BY k.keyid;" | while IFS='|' read -r keyvalue keyid value; do
child_count=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM relationTable WHERE parentid = $keyid;")

if [ "$child_count" -gt 0 ]; then
echo "${keyvalue}:" >> "$TEMP_FILE"
process_node "$keyid" " "
elif [ -n "$value" ]; then
clean_value=$(echo "$value" | sed 's/^"//; s/"$//')
if [[ "$clean_value" =~ ^[0-9]+$ ]]; then
echo "${keyvalue}: $clean_value" >> "$TEMP_FILE"
else
echo "${keyvalue}: \"$clean_value\"" >> "$TEMP_FILE"
fi
else
echo "${keyvalue}: {}" >> "$TEMP_FILE"
fi
done

# Output the result and clean up
cat "$TEMP_FILE"
rm "$TEMP_FILE"
24 changes: 24 additions & 0 deletions modules/ggipcd/src/services/config/get_configuration.c
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,30 @@ GglError ggl_handle_get_configuration(
return ret;
}

// According to the IPC spec, if keyPath has a valid value,
// For MAP values a map without the keyPath leaf is returned.
// For non-MAP values, a map with the keyPath leaf and the value is
// returned.
GglKV wrapped_result = { 0 };
GglObjectType read_type = ggl_obj_type(read_value);
if (read_type != GGL_TYPE_MAP) {
if (key_path.len > 0) {
wrapped_result = ggl_kv(
ggl_obj_into_buf(key_path.items[key_path.len - 1]), read_value
);
read_value
= ggl_obj_map((GglMap) { .pairs = &wrapped_result, .len = 1 });
} else {
// A state where the whole configuration is requested but the result
// is not a map then error.
*ipc_error
= (GglIpcError) { .error_code = GGL_IPC_ERR_INVALID_ARGUMENTS,
.message = GGL_STR("Key is not valid.") };

return GGL_ERR_CONFIG;
}
}

return ggl_ipc_response_send(
handle,
stream_id,
Expand Down