Interface connection from gadget in firstboot

I’m struggling to get this working also.

In my gadget.yaml I have:

connections:
 - plug: JMOa0xipSr8QRguwZNbWsIg3RQTNDC3v:hardware-observe
 - plug: JMOa0xipSr8QRguwZNbWsIg3RQTNDC3v:network-observe
 - plug: JMOa0xipSr8QRguwZNbWsIg3RQTNDC3v:serial-port
 - plug: JMOa0xipSr8QRguwZNbWsIg3RQTNDC3v:system-observe
 - plug: JMOa0xipSr8QRguwZNbWsIg3RQTNDC3v:log-observe

(full file at https://github.com/svet-b/pi3-gadget/blob/master/gadget.yaml)

Based on the documentation (Gadget snaps), I followed the recommendation that

Omitting "slot" in an instruction is allowed and equivalent then to: slot: system:<plug>

The gadget is created fine, but when trying to build an image with it using ubuntu-image (version 1.3+16.04ubuntu2) I get the following, with debug mode on:

DEBUG:ubuntu-image:-> [ 3] load_gadget_yaml
ERROR:ubuntu-image:uncaught exception in state machine step: [3] load_gadget_yaml
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/ubuntu_image/parser.py", line 304, in parse
    validated = GadgetYAML(yaml)
  File "/usr/lib/python3/dist-packages/voluptuous.py", line 337, in __call__
    return self._compiled([], data)
  File "/usr/lib/python3/dist-packages/voluptuous.py", line 635, in validate_dict
    return base_validate(path, iteritems(data), out)
  File "/usr/lib/python3/dist-packages/voluptuous.py", line 471, in validate_mapping
    raise MultipleInvalid(errors)
voluptuous.MultipleInvalid: extra keys not allowed @ data['connections']

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/ubuntu_image/state.py", line 82, in __next__
    step()
  File "/usr/lib/python3/dist-packages/ubuntu_image/common_builder.py", line 132, in load_gadget_yaml
    self.gadget = parse_yaml(fp)
  File "/usr/lib/python3/dist-packages/ubuntu_image/parser.py", line 312, in parse
    raise GadgetSpecificationError('Invalid gadget.yaml @ {}'.format(path))
ubuntu_image.parser.GadgetSpecificationError: Invalid gadget.yaml @ connections
ERROR:ubuntu-image:gadget.yaml parse error
Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/ubuntu_image/parser.py", line 304, in parse
    validated = GadgetYAML(yaml)
  File "/usr/lib/python3/dist-packages/voluptuous.py", line 337, in __call__
    return self._compiled([], data)
  File "/usr/lib/python3/dist-packages/voluptuous.py", line 635, in validate_dict
    return base_validate(path, iteritems(data), out)
  File "/usr/lib/python3/dist-packages/voluptuous.py", line 471, in validate_mapping
    raise MultipleInvalid(errors)
voluptuous.MultipleInvalid: extra keys not allowed @ data['connections']

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3/dist-packages/ubuntu_image/__main__.py", line 341, in main
    list(state_machine)
  File "/usr/lib/python3/dist-packages/ubuntu_image/state.py", line 82, in __next__
    step()
  File "/usr/lib/python3/dist-packages/ubuntu_image/common_builder.py", line 132, in load_gadget_yaml
    self.gadget = parse_yaml(fp)
  File "/usr/lib/python3/dist-packages/ubuntu_image/parser.py", line 312, in parse
    raise GadgetSpecificationError('Invalid gadget.yaml @ {}'.format(path))
ubuntu_image.parser.GadgetSpecificationError: Invalid gadget.yaml @ connections

Clearly I’m doing something wrong, and maybe it’s just bad yaml, but I’m not sure what to make of the error. Any pointers?

I’m on snapd version 2.34.3.

@svet This is a missing support in ubuntu-image. Please try to install the latest ubuntu-image snap (sudo snap refresh ubuntu-image). It should have the necessary support now.

Thanks for the quick response, @mvo that is indeed the issue!

More specifically, I was using ubuntu-image from the Ubuntu apt repository, and even the latest version there was outdated. I installed ubuntu-image 1.4 using snap and that appears to run well.

Follow-up question: one of the interface connections I need to auto-establish is actually to a slot on my custom gadget, not to the core system (namely, a serial port).

Since my custom gadget is not in the store, I’m assuming it has no ID - so I’m not sure what to put in the gadget.yaml in order to realize this. What’s the best way to get this to work? Should I upload the gadget to the store (and request manual review, etc)?

Answering my own question, the following appeared to do the trick for me:

  • Push the custom gadget snap to the store. Although it’s not possible to release it, it’s assigned a snap ID which is available in the store dashboard
  • Edit gadget.yaml to refer to the gadget slot using the assigned snap ID. Create new version of gadget snap (which uses the updated gadget.yaml) and push to store again
  • From store dashboard, download the latest gadget snap file [snap-id]_[revision].snap
  • Use that gadget snap file to build the image

I’m not claiming all these specific steps are necessary (there may well be a more straightforward way to do it), but as I said this got me the desired result.

Hi @svet, I am in a similar situation. I also have to auto connect different snaps to plugs. I can get the snap-id of custom snaps using snapcraft sign-build --local --key-name mykey ./my-custom-snap.snap.

This works on custom snaps but doesn’t appear to work for a gadget snap that is forked from Repository: https://github.com/snapcore/pc-amd64-gadget. The following error arises:

Your account lacks permission to assert builds for this snap. Make sure you are logged in as the publisher of 'my-custom-gadget' for series '16'.

Has any one had luck with this?

snap info my-custom-gadget more simply shows its snap-id as well, without involving signing. Assuming my-custom-gadget is in the store/brand store, only store snaps have snap-ids.

Is it possible to connect without a snap ID and only by name just like the command snap connect <snap>:<plug> <snap>:<plug>.

The custom snaps are offline snaps, and are intended to be offline snaps so I can’t easily get a snap-id.

2 Likes

Did you ever get past this? I can’t even use snapcraft sign-build since it appears to be broken.

snapcraft sign-build --local --key-name someKey ./my-app_2.0.3_arm64.snap

Shows the following error:

Usage: snapcraft [options] command [args]...
Try 'snapcraft sign-build -h' for help.

Error: unrecognized arguments:  my-app_2.0.3_arm64.snap

snap info does not provide an ID…

I believe in your case you need to register the snap to the snapcraft store before you can sign a snap and get a snap-id

What I ended up doing was mounting the *.img file, creating a systemd service file and created a script that runs across reboots (or can manually restart this service). See https://github.com/snapcore/snapweb/blob/master/spread/image/create-image.sh for example on how to do this. Below is the script I came up with by hand but only connects snaps’ plugs to the core snap slots

set +e #Do not immediately error out
IDENTIFIER="auto-connect"
snaps_to_restart=()

# prevent the creation of subshells for variable manipulation
shopt -s lastpipe

while ! snap changes >/dev/null; do
    echo "INFO: No changes yet, waiting..." | systemd-cat -t $IDENTIFIER
    sleep 5
done

while snap changes | grep -qE '(Do|Doing) .*Initialize system state' >/dev/null; do
    sleep 1
done

snap connections --all | while read snap_connection; do
    #Extract snap plugs that have not been connected
    snap_interface=$(echo ${snap_connection} | head -n1 | cut -d " " -f1)
    snap_plug=$(echo ${snap_connection} | head -n1 | cut -d " " -f2)
    snap_plug_name=$(cut -d ":" -f 2 <<<"${snap_plug}")
    snap_name=$(cut -d: -f1 <<<$snap_plug)
    snap_slot=$(echo ${snap_connection} | head -n1 | cut -d " " -f3)
    snap_slot_name=$(cut -d ":" -f 2 <<<"${snap_plug}")

    snap_notes=$(echo ${snap_connection} | head -n1 | cut -d " " -f4)

    #Check for snap plugs that have a valid name
    if [[ "${snap_plug}" != "-" && "${snap_slot}" == "-" ]]; then
        #Update slot to generic core slot
        snap_slot=":${snap_interface}"
        snap connect ${snap_plug} ${snap_slot}
        echo "INFO: Connected ${snap_plug} ${snap_slot}" | systemd-cat -t $IDENTIFIER
        snaps_to_restart+=($snap_name)
    fi
done

# Restart the snaps
# Change array to unique array
snaps_to_restart=$(echo "${snaps_to_restart[@]}" | tr ' ' '\n' | sort -u | tr '\n' ' ')
if [[ -z "${snaps_to_restart// /}" ]]; then
    echo "INFO: No new snap plugs to connect" | systemd-cat -t $IDENTIFIER
else
    snap restart $snaps_to_restart
    echo "INFO: Restarted snaps: $snaps_to_restart" | systemd-cat -t $IDENTIFIER
fi
1 Like

Thank you for the response. I used the code you posted as inspiration, and I was able to get this working with a simple solution. No store registration, no private snaps, and no one shot service.

I was attempting to create the service with a cloud.config, and for the most part, it would work. I ran into an easier solution!

Module Reference — cloud-init 22.3 documentation (cloudinit.readthedocs.io)

If you use cloud-init, then you can use the snap plugin to register your snap connections:

snap:
  commands:
    - ["connect", "fl-mini-app:process-control", ":process-control"]
    - ["connect", "fl-mini-app:network-manager", "network-manager:service"]
    - ["connect", "fl-mini-app:network-control", ":network-control"]
    - ["connect", "fl-mini-app:can-bus", ":can-bus"]
    - ["connect", "fl-mini-app:network-observe"]
    - ["connect", "fl-mini-app:network-bind"]

And just like that, it’s done. You can run any snap command, like ‘install’ etc.

Basically if you build a gadget snap from something like this as a starter:

snapcore/pi-gadget: Universal pi (pi2,pi3,pi4,cm3,cm4 and future) gadget snap for Ubuntu Core and classic systems. (github.com)

Then jsut add a cloud.conf in the root directory, make sure to add this to the top or it wont work:

#cloud-config
datasource_list: [NoCloud]

Now when I use ubuntu-image snap, it will roll the config into my image and its ready to go. If you have trouble with the example ubuntu image example, and it is not working with the cloud-init, then make sure to add the cloud.config file to your snap file, and ensure it is copied in the makefile.

I did not register my snaps in the store, I passed them in using the ubuntu-image command and it worked fine. I did try to register a private snap, but ubuntu-image will not work with a private snap, and I don’t have the $70,000.00 a year that Canonical’s sales reps are asking for a private store. Updates are done via my own update service. Since this is a small project, the cost is completely unreasonable for my needs. I am ok with this, and it is fair, it’s an ok product for large scale problems, that I just don’t have. Local snap with cloud.conf works for now.

If anyone has questions, feel free to ask, I will pass on what I can!

Hello there,

So, you mean that I can just add a cloud.config file to the root directory of my custom gadget snap, with the contents of: (example below)

#cloud-config
datasource_list: [NoCloud]
snap:
  commands:
    - ["connect", "blablabla", ":process-control"]

etc?

And then fire off ubuntu image with no cloud-init parameter?

This is correct.

I highly recommend that you check log output of the init process if you run into trouble. I sometimes work on an RPI and need to connect a serial cable to the GPIO header so that I can see where the cloud-init process fails.

For example, if there is a problem installing your dependent snaps or something, this will fail as well. Or if for some reason your config isn’t loaded into the gadget snap build.

Also a problem your going to run into is where the cloud-init just does not run, like it isn’t in the gadget snap. This is because you need to include the file in your make file

In the MakeFile, for the version of the pi-gadget I am using, I had to add:

cp -a cloud.conf $(DESTDIR)/cloud.conf

to the config-core stage.

Also I use the following:

datasource_list: [ NoCloud, None]

If everything is wired up, it should just run the config, and off you go.

Note though that cloud-init is rather a hack in UbuntuCore context (it is totally fine for development or home use, but i would not use it in production on actual deployed devices).

Using your brand store to set auto-connections should be the preferred method, with using the gadget.yaml as a first fallback and only as last resort (or for development/test images) you should use cloud-init …

indeed i should have said, “if you want a secure product with full control and can (or do already) pay for a brand store” above :slight_smile:

for home-use projects etc you can/should use the gadget.yaml and as a last resort you can use cloud-init but keep in mind that it is not written or designed in context of UbuntuCore and does not really integrate with the concept (it is simply a cloud image configuration tool) of snaps or the design of the UC images …

if you use a brand store and for example use re-modeling (i.e. in-place upgrades to the next release etc) at any point in time it might even do harmful things (trying o apply old stuff that is not there anymore etc) …

it can at any time pull out the carpet underneath your snaps (i.e. changing files directly on the image instead of using the right snap commands/interfaces etc) and completely work around the security concept…

and no, there is no free brand store atm (but many users here in the forum are actually paying customers :wink: which is why i tend to point out such warnings)

While I know that Core16/Core18 could have been modified a little bit with injecting the user assertion to a certain place, in the image before flashing.

AFAIK, there’s no option to do the same on Core20, hence the cloud init. I will try out these modifications, @thepenguinmaster and see if it works. If not, then I can always fall back to a user assertion on a USB drive.

You are missing an important point here, the customers that pay the annual $15k for a brand store (not sure where you got the 75 from) actually pay for this forum, for the time I spend here answering, for further development of snapd and UbuntuCore and for fixing bugs… all to your benefit …

paying customers with big deployments make it possible that you as a non-paying user with a small business and small deployment can actually use the stuff for free and that you can enjoy this forum without having to see annoying ads…

UbuntuCore is designed around its default use-case which is in context of a store that does come with management tools and features directly hooking into the OS… while we try to keep alternatives and workarounds in place for people that can not afford a store, I don’t think it is wrong to point out in a discussion that these are not the designed or desired defaults…

1 Like

This feels quite far off topic.

The topic and reply is related to snap configs and the question is specific to not using a brand store.

In reply to your comment, the brand store is only part of the overall cost.

Either way, this thread needs moderation. I hope you will take it upon yourself as a responsible patron of this forum to clean up your posts.