Completion id from tenantoptions go-c8y-cli

I would like to write an extension that uses keys/data from the tenantoptions.
Eventually these key should be used for completion:

I try to extract the ids ca6vyp, fr0zqp, g1epb7 from :

# c8y tenantoptions getForCategory --category dynamic.mapper.service  --raw
{
  "credentials.connection.configuration.ca6vyp": "<<Encrypted>>",
  "credentials.connection.configuration.fr0zqp": "<<Encrypted>>",
  "credentials.connection.configuration.g1epb7": "<<Encrypted>>",
  "service.configuration": "{\"logPayload\":false,\"logSubstitution\":false,\"logConnectorErrorInBackend\":false,\"sendConnectorLifecycle\":false,\"sendMappingStatus\":true,\"sendSubscriptionEvents\":false,\"sendNotificationLifecycle\":false,\"externalExtensionEnabled\":true,\"outboundMappingEnabled\":true,\"inboundExternalIdCacheSize\":100000,\"inboundExternalIdCacheRetention\":1}"
}
# c8y tenantoptions getForCategory --category dynamic.mapper.service --select 'credentials.connection.configuration.\w+'  
| credentials.connection.configuration.\w+ |
|------------------------------------------|
|                                          |

Q1: When I define a wildcard expression is does not return anything. How should the pattern lokk like?:

% c8y tenantoptions getForCategory --category dynamic.mapper.service --select 'credentials.connection.configuration.*'  
| credentials.connection.configuration.* |
|----------------------------------------|
|                                        |

Q2: How can I then extract ca6vyp from credentials.connection.configuration.ca6vyp?

Is it required to achieve this with go-c8y-cli solely or can you utilize tools like jq and sed?

If jq is an option, that would do it:

$ c8y tenantoptions getForCategory --category dynamic.mapper.service \
    | jq "to_entries | .[] | select(.key|test(\"credentials.connection.configuration\")) | .key" -r \
    | sed "s/credentials.connection.configuration.//g"
ca6vyp
fr0zqp
g1epb7

Explanation:

  • to_entries | .[] converts your payload towards:
{"key":"credentials.connection.configuration.ca6vyp","value":"<<Encrypted>>"}
{"key":"credentials.connection.configuration.fr0zqp","value":"<<Encrypted>>"}
{"key":"credentials.connection.configuration.g1epb7","value":"<<Encrypted>>"}
  • select(.key|test(\"credentials.connection.configuration\")) | .key filters your “key” property against a regexp, only the one’s matching are passing. The | .key extracts the “key” property. Then you’ll have:
credentials.connection.configuration.ca6vyp
credentials.connection.configuration.fr0zqp
credentials.connection.configuration.g1epb7
  • and the sed removes your static prefix

// Edit:
A second option would be using awk:

$ c8y tenantoptions getForCategory --category dynamic.mapper.service \
    | jq 'keys | .[]' -r  \
    | awk '/credentials.connection.configuration/' \
    | awk -F "." '{print $4}'
ca6vyp
fr0zqp
g1epb7
  • | jq keys extracts the keys of your json
  • | awk '/credentials.connection.configuration/' filters the input to start with your string
  • | awk -F "." '{print $4}' tokenizes your string by using “.” as separator. The fourth element is then what you’re searching for

Just adding some additional options to what @kobu already stated, but these options only use go-c8y-cli commands for maximum portability.

Though please note the examples provided are using bash/shell escaping so the one-liners more readible (e.g. using multi-line commands by using a backslash \ to join lines). If you are using Powershell then you’ll have to replace any trailing \ with backtick `.

Q1: When I define a wildcard expression is does not return anything. How should the pattern look like?:

It looks like the . in the key names is causing problems for the go-c8y-cli --select <path> flag.

So you can escape the . character, however depending on your shell, the number of slashes will vary (as in shell you have to also escape the \ character with a \…so it can get complicated for users)…but it is possible.

c8y tenantoptions getForCategory \
  --category dynamic.mapper.service \
  --select "credentials\\\\.connection\\\\.configuration\\\\.*"

Alternatively you could use a client side filter (--filter) in combination with c8y tenantoptions list

c8y tenantoptions list \
  --filter "category like dynamic.mapper.service" \
  --filter "key like credentials.connection.configuration.*" \
  --includeAll

This output will be different to the getForCategory output because the c8y tenantoptions list returns an array of configuration items, where as getForCategory return an object with the configuration as keys of that object.

Q2: How can I then extract ca6vyp from credentials.connection.configuration.ca6vyp?

Option 1: Using c8y tenantoptions list and client side filtering

c8y tenantoptions list \
    --filter "category like dynamic.mapper.service" \
    --filter "key like credentials.connection.configuration.*" \
    --includeAll \
    --select key \
    -o csv \
| c8y template execute --template "
local parts = std.split(input.value, '.');
parts[std.length(parts)-1]
"

Option 2: Using getForCategory and jsonnet templating

c8y tenantoptions getForCategory \
    --category dynamic.mapper.service \
    --outputTemplate "[item.key for item in std.objectKeysValues(output)"] \
| c8y template execute --template "
local parts = std.split(input.value, '.');
parts[std.length(parts)-1]
"

Extra reading

go-c8y-cli supports the jsonnet templating language for --template and --outputTemplate flags, so it might be worthwhile reading up on the language by checking out their website. Their website is good, and it even has a playground where you can experiment with jsonnet templates to get used to it.

But it might be worthwhile creating an endpoint in the microservice to return a list of the connections, as doing this logic on the client side won’t be so easy, as the user is not given any context about what the id even means…so having a dedicated API endpoint you could even return the connection id and a human readable name (if one exists).

I try to implement the approach with the endpoint.
My endpoint returns:

 c8y api --url /service/dynamic-mapping-service/configuration/connector/instances --select=name,ident
| name             | ident       |
|------------------|-------------|
| MQTT - emqx      | ca6vyp      |
| MQTT - sap       | g1epb7      |
| KAFKA - 03       | fr0zqp      |

and has a query parameter name.

How can I combine this in a GET with tab completion?

  - name: get
    description: Get connector
    descriptionLong: Get a connector using a simple GET request
    method: GET
    path: service/dynamic-mapping-service/configuration/connector/instance/{id}
    pathParameters:
      - name: id
        type: string
        description: Ident of the connector
        completion:
          type: external
          command:
            - c8y
            - api
            - --url /service/dynamic-mapping-service/configuration/connector/instances
            - --select=name,ident
            - --query
            - "name='%s.*'"
        #<<: *type-connector
    exampleList:
      - command: c8y %[1]s connectors get --id 1234
        description: Get a connector by id

I get an error:

 % c8y mapper connectors get --id exit\ status\ 100

on tab completion.

It looks to be a copy/paste error when you copied the tutorial’s example which uses c8y devices list --query "...", but you changed it to c8y api ... which does not support the --query flag. I’m assuming you meant to use the --customQueryParam name='%s.*' flag instead.

It is always helpful to run the command yourself to verify they are correct before using them in the tab completion. The tab completion is a bit hard to debug as the output format does not really give you a lot of possibilities to increase the log level (as that will effect the tab completion functionality on the console).

Running your command locally shows the user error (notice I just used some static value in-place of %s:

$ c8y api --url /service/dynamic-mapping-service/configuration/connector/instances --select=name,ident --query "name='foo.*'"
2024-12-02T09:13:55.085+0100	ERROR	commandError: unknown flag: --query

However I would approach your problem slightly differently.

  1. Add a new command which lists the connector instances

    c8y dynamicmapper connector instances list [--name <name>]
    

    This would just do your API call to

    /service/dynamic-mapping-service/configuration/connector/instances?name={name}
    
  2. Use the connector instances list command in the tab completion for the connector instances get command

    command:
      - c8y
      - dynamicmapper
      - connector
      - instances
      - list
      - --name='%s.*'
      - --select=name,ident
    
  3. optional: Add a lookup command to the connector instances get command to allow the user to use names instead of the ident field (only if the names are unique!). See step-6 of the go-c8y-cli extensions tutorial

Generally speaking it is good to give the users a more consistent command-line experience by providing the following commands.

  • get
  • list
  • delete
  • update

By implementing those basic commands, you can provide a much better UX as it is more inline with other go-c8y-cli commands, so you don’t have to give so much guidance to the users, they will intuitively know how they can use the extension in more complex workflows like pipelines.

# get single value
c8y dynamicmapper connector instances get --id "<id_or_name>"

# or use list and pipe to the update command
c8y dynamicmapper connector instances list | c8y dynamicmapper connector instances update

Btw, I think you should find a better word for ident. Either use id (if it is not already taken), or be more explicit about it and use something like instanceId…but then again I don’t know too much about the application’s context, so if ident is correct in the domain you’re working in, then ignore this advice.

1 Like

After some additional advice, I could complete completion for the connectors command: connectors.yaml