diff --git a/.github/workflows/snap.yaml b/.github/workflows/snap.yaml new file mode 100644 index 00000000..e278f886 --- /dev/null +++ b/.github/workflows/snap.yaml @@ -0,0 +1,30 @@ +name: Snap Testing + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + # allow manual trigger + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Build and upload snap + id: build + uses: canonical/edgex-snap-testing/build@v2 + outputs: + snap: ${{steps.build.outputs.snap}} + + test: + needs: build + runs-on: ubuntu-latest + steps: + - name: Download and test snap + uses: canonical/edgex-snap-testing/test@v2 + with: + name: device-virtual + + diff --git a/.gitignore b/.gitignore index 616ce30a..ac5cefbe 100644 --- a/.gitignore +++ b/.gitignore @@ -19,3 +19,12 @@ internal/driver/db/ cmd/device-virtual cmd/db/ VERSION + +# snap files +*.snap +*.assert +prime/ +stage/ +parts/ +squashfs-root/ + diff --git a/snap/README.md b/snap/README.md new file mode 100644 index 00000000..c0869317 --- /dev/null +++ b/snap/README.md @@ -0,0 +1,38 @@ +# EdgeX Device Virtual Service Snap +[![edgex-device-virtual](https://snapcraft.io/edgex-device-virtual/badge.svg)](https://snapcraft.io/edgex-device-virtual) + +This directory contains the snap packaging of the EdgeX Device Virtual device service. + +The snap is built automatically and published on the Snap Store as [edgex-device-virtual]. + +For usage instructions, please refer to Device Virtual section in [Getting Started using Snaps][docs]. + +## Build from source +Execute the following command from the top-level directory of this repo: +``` +snapcraft +``` + +This will create a snap package file with `.snap` extension. It can be installed locally by setting the `--dangerous` flag: +```bash +sudo snap install --dangerous +``` + +The [snapcraft overview](https://snapcraft.io/docs/snapcraft-overview) provides additional details. + +### Obtain a Secret Store token +The `edgex-secretstore-token` snap slot makes it possible to automatically receive a token from a locally installed platform snap. Note that the **auto connection does NOT happen right** now because the snap publisher isn't same as the `edgexfoundry` platrform snap (i.e. Canonical). + +If the snap is built and installed locally, the interface will not auto-connect. You can check the status of the connections by running the `snap connections edgex-device-virtual` command. + +To manually connect and obtain a token: +```bash +sudo snap connect edgexfoundry:edgex-secretstore-token edgex-device-virtual:edgex-secretstore-token +``` + +Please refer [here][secret-store-token] for further information. + +[edgex-device-virtual]: https://snapcraft.io/edgex-device-virtual +[docs]: https://docs.edgexfoundry.org/2.2/getting-started/Ch-GettingStartedSnapUsers/#device-virtual +[secret-store-token]: https://docs.edgexfoundry.org/2.2/getting-started/Ch-GettingStartedSnapUsers/#secret-store-token + diff --git a/snap/local/hooks/Makefile b/snap/local/hooks/Makefile new file mode 100644 index 00000000..d29ba20d --- /dev/null +++ b/snap/local/hooks/Makefile @@ -0,0 +1,18 @@ +GO=go +HOOKS=cmd/configure/configure cmd/install/install +GOFLAGS=-ldflags="-s -w" + +build: $(HOOKS) + +cmd/configure/configure: + $(GO) build $(GOFLAGS) -o $@ ./cmd/configure + +cmd/install/install: + $(GO) build $(GOFLAGS) -o $@ ./cmd/install + +tidy: + go mod tidy -compat=1.17 + +clean: + rm -f $(HOOKS) + diff --git a/snap/local/hooks/cmd/configure/configure.go b/snap/local/hooks/cmd/configure/configure.go new file mode 100644 index 00000000..d9bc21f5 --- /dev/null +++ b/snap/local/hooks/cmd/configure/configure.go @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2022 Canonical Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * + * SPDX-License-Identifier: Apache-2.0' + */ + +package main + +import ( + "os" + "strings" + + "github.com/canonical/edgex-snap-hooks/v2/log" + "github.com/canonical/edgex-snap-hooks/v2/options" + "github.com/canonical/edgex-snap-hooks/v2/snapctl" +) + +func main() { + log.SetComponentName("configure") + + log.Info("Enabling config options") + err := snapctl.Set("app-options", "true").Run() + if err != nil { + log.Errorf("could not enable config options: %v", err) + os.Exit(1) + } + + log.Info("Processing options") + err = options.ProcessAppConfig("device-virtual") + if err != nil { + log.Errorf("could not process options: %v", err) + os.Exit(1) + } + + // If autostart is not explicitly set, default to "no" + // as only example service configuration and profiles + // are provided by default. + autostart, err := snapctl.Get("autostart").Run() + if err != nil { + log.Errorf("Reading config 'autostart' failed: %v", err) + os.Exit(1) + } + if autostart == "" { + log.Debug("autostart is NOT set, initializing to 'no'") + autostart = "no" + } + autostart = strings.ToLower(autostart) + log.Debugf("autostart=%s", autostart) + + // services are stopped/disabled by default in the install hook + switch autostart { + case "true", "yes": + err = snapctl.Start("device-virtual").Enable().Run() + if err != nil { + log.Errorf("Can't start service: %s", err) + os.Exit(1) + } + case "false", "no": + // no action necessary + default: + log.Errorf("Invalid value for 'autostart': %s", autostart) + os.Exit(1) + } +} diff --git a/snap/local/hooks/cmd/install/install.go b/snap/local/hooks/cmd/install/install.go new file mode 100644 index 00000000..67aeef5e --- /dev/null +++ b/snap/local/hooks/cmd/install/install.go @@ -0,0 +1,105 @@ +/* + * Copyright (C) 2022 Canonical Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + * + * SPDX-License-Identifier: Apache-2.0' + */ + +package main + +import ( + "os" + + hooks "github.com/canonical/edgex-snap-hooks/v2" + "github.com/canonical/edgex-snap-hooks/v2/env" + "github.com/canonical/edgex-snap-hooks/v2/log" +) + +// installProfiles copies the profile configuration.toml files from $SNAP to $SNAP_DATA. +func installConfig() error { + resPath := "/config/device-virtual/res" + err := os.MkdirAll(env.SnapData+resPath, 0755) + if err != nil { + return err + } + + path := resPath + "/configuration.toml" + err = hooks.CopyFile( + env.Snap+path, + env.SnapData+path) + if err != nil { + return err + } + + return nil +} + +func installDevices() error { + devicesDir := "/config/device-virtual/res/devices" + + err := os.MkdirAll(env.SnapData+devicesDir, 0755) + if err != nil { + return err + } + + err = hooks.CopyFile( + hooks.Snap+devicesDir+"/devices.toml", + hooks.SnapData+devicesDir+"/devices.toml") + if err != nil { + return err + } + + return nil +} + +func installDevProfiles() error { + profs := [...]string{"binary", "bool", "float", "int", "uint"} + profilesDir := "/config/device-virtual/res/profiles/" + + err := os.MkdirAll(env.SnapData+profilesDir, 0755) + if err != nil { + return err + } + + for _, v := range profs { + err = hooks.CopyFile( + hooks.Snap+profilesDir+"device.virtual."+v+".yaml", + hooks.SnapData+profilesDir+"device.virtual."+v+".yaml") + if err != nil { + return err + } + } + + return nil +} + +func main() { + log.SetComponentName("install") + + err := installConfig() + if err != nil { + log.Errorf("error installing config file: %s", err) + os.Exit(1) + } + + err = installDevices() + if err != nil { + log.Errorf("error installing devices config: %s", err) + os.Exit(1) + } + + err = installDevProfiles() + if err != nil { + log.Errorf("error installing device profiles config: %s", err) + os.Exit(1) + } +} diff --git a/snap/local/hooks/go.mod b/snap/local/hooks/go.mod new file mode 100644 index 00000000..0d841440 --- /dev/null +++ b/snap/local/hooks/go.mod @@ -0,0 +1,6 @@ +module github.com/edgexfoundry/device-virtual-go/hooks + +go 1.17 + +require github.com/canonical/edgex-snap-hooks/v2 v2.2.0 + diff --git a/snap/local/hooks/go.sum b/snap/local/hooks/go.sum new file mode 100644 index 00000000..09e91747 --- /dev/null +++ b/snap/local/hooks/go.sum @@ -0,0 +1,13 @@ +github.com/canonical/edgex-snap-hooks/v2 v2.2.0 h1:4pDnikrtyrxiynTM49+ppH7hXBx5C7dWUOjEEisCXmI= +github.com/canonical/edgex-snap-hooks/v2 v2.2.0/go.mod h1:rOxrwdYL7hJDhxFH3uV+nVgLPjWOhJWgM5PRD5YG1jI= +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= + diff --git a/snap/local/runtime-helpers/bin/startup-env-var.sh b/snap/local/runtime-helpers/bin/startup-env-var.sh new file mode 100755 index 00000000..83dbd8d7 --- /dev/null +++ b/snap/local/runtime-helpers/bin/startup-env-var.sh @@ -0,0 +1,19 @@ +#!/bin/bash -e + +# convert cmdline to string array +ARGV=($@) + +# grab binary path +BINPATH="${ARGV[0]}" + +# binary name == service name/key +SERVICE=$(basename "$BINPATH") +SERVICE_ENV="$SNAP_DATA/config/$SERVICE/res/$SERVICE.env" + +if [ -f "$SERVICE_ENV" ]; then + logger "edgex service override: : sourcing $SERVICE_ENV" + source "$SERVICE_ENV" +fi + +exec "$@" + diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml new file mode 100644 index 00000000..e021a2b4 --- /dev/null +++ b/snap/snapcraft.yaml @@ -0,0 +1,108 @@ +name: edgex-device-virtual +base: core20 +license: Apache-2.0 +adopt-info: metadata + +architectures: + - build-on: amd64 + - build-on: arm64 + +grade: stable +confinement: strict + +slots: + edgex-secretstore-token: + interface: content + source: + write: [$SNAP_DATA/device-virtual] + +plugs: + device-config: + interface: content + target: $SNAP_DATA/config/device-virtual + +apps: + device-virtual: + command: bin/device-virtual $CONFIG_PRO_ARG $CONF_ARG $REGISTRY_ARG + command-chain: + - bin/startup-env-var.sh + environment: + CONFIG_PRO_ARG: "--cp=consul.http://localhost:8500" + CONF_ARG: "--confdir=$SNAP_DATA/config/device-virtual/res" + REGISTRY_ARG: "--registry" + DEVICE_PROFILESDIR: "$SNAP_DATA/config/device-virtual/res/profiles" + DEVICE_DEVICESDIR: "$SNAP_DATA/config/device-virtual/res/devices" + SECRETSTORE_TOKENFILE: $SNAP_DATA/device-virtual/secrets-token.json + daemon: simple + install-mode: disable + plugs: [network, network-bind] + +parts: + hooks: + source: snap/local/hooks + plugin: make + build-snaps: [go/1.17/stable] + override-build: | + cd $SNAPCRAFT_PART_SRC + make build + install -DT ./cmd/configure/configure $SNAPCRAFT_PART_INSTALL/snap/hooks/configure + install -DT ./cmd/install/install $SNAPCRAFT_PART_INSTALL/snap/hooks/install + + device-virtual: + after: [metadata] + source: . + plugin: make + build-packages: [git, libzmq3-dev, pkg-config] + build-snaps: [go/1.17/stable] + stage-packages: [libzmq5] + override-build: | + cd $SNAPCRAFT_PART_SRC + + # the version is needed for the build + cat VERSION + + make tidy + make build + + install -DT "./cmd/device-virtual" "$SNAPCRAFT_PART_INSTALL/bin/device-virtual" + + RES=$SNAPCRAFT_PART_INSTALL/config/device-virtual/res/ + mkdir -p $RES + cp cmd/res/configuration.toml $RES + cp -r cmd/res/devices $RES + cp -r cmd/res/profiles $RES + + DOC=$SNAPCRAFT_PART_INSTALL/usr/share/doc/device-virtual + mkdir -p $DOC + cp Attribution.txt $DOC/Attribution.txt + cp LICENSE $DOC/LICENSE + + runtime-helpers: + plugin: dump + source: snap/local/runtime-helpers + + metadata: + plugin: nil + source: https://github.com/canonical/edgex-snap-metadata.git + source-branch: appstream + source-depth: 1 + override-build: | + # install the icon at the default internal path + install -DT edgex-snap-icon.png \ + $SNAPCRAFT_PART_INSTALL/meta/gui/icon.png + + # change to this project's repo to get the version + cd $SNAPCRAFT_PROJECT_DIR + if git describe ; then + VERSION=$(git describe --tags --abbrev=0 | sed 's/v//') + else + VERSION="0.0.0" + fi + + # write version to file for the build + echo $VERSION > ./VERSION + # set the version of this snap + snapcraftctl set-version $VERSION + parse-info: [edgex-device-virtual.metainfo.xml] + + \ No newline at end of file