Skip to content

Commit

Permalink
init. commit
Browse files Browse the repository at this point in the history
  • Loading branch information
reubenmiller committed Feb 20, 2023
1 parent d73ab00 commit 5f64839
Show file tree
Hide file tree
Showing 9 changed files with 385 additions and 2 deletions.
57 changes: 57 additions & 0 deletions .github/workflows/release.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
name: release
permissions:
contents: write
on:
push:
tags:
- "*"
workflow_dispatch:
jobs:
release:
name: Package and release
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
with:
fetch-depth: 0

- uses: actions/setup-go@v3
with:
go-version: '>=1.17.0'
- run: go install github.com/goreleaser/nfpm/v2/cmd/nfpm@latest
name: Install dependencies

- name: Set version
run: echo "RELEASE_VERSION=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV

- name: Package
run: ./ci/build.sh
env:
SEMVER: ${{ env.RELEASE_VERSION }}

- name: Upload artifacts
uses: actions/upload-artifact@v3
with:
name: packages
path: dist/*

- name: Release
uses: softprops/action-gh-release@v1
if: startsWith(github.ref, 'refs/tags/')
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
generate_release_notes: true
draft: true
files: |
./dist/*
- name: Publish
if: startsWith(github.ref, 'refs/tags/') && env.PUBLISH_TOKEN
env:
PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
PUBLISH_REPO: ${{ secrets.PUBLISH_REPO }}
PUBLISH_OWNER: ${{ secrets.PUBLISH_OWNER }}
run: |
./ci/publish.sh ./dist --repo "$PUBLISH_REPO" --owner "$PUBLISH_OWNER"
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
dist/
57 changes: 55 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,55 @@
# c8y-shell-plugin
thin-edge.io Cumulocity shell plugin
# c8y-command-plugin

thin-edge.io Cumulocity IoT shell plugin to process the `c8y_Command` operation.

## Plugin summary

### What will be deployed to the device?

* Cumulocity IoT command handler (binary) that allows users to execute a command in a shell

**Technical summary**

The following details the technical aspects of the plugin to get an idea what systems it supports.

|||
|--|--|
|**Languages**|`shell` (posix compatible)|
|**CPU Architectures**|`all/noarch`. Not CPU specific|
|**Supported init systems**|`N/A`|
|**Required Dependencies**|-|
|**Optional Dependencies (feature specific)**|-|

### How to do I get it?

The following linux package formats are provided on the releases page and also in the [tedge-community](https://cloudsmith.io/~thinedge/repos/community/packages/) repository:

|Operating System|Repository link|
|--|--|
|Debian/Raspian (deb)|[![Latest version of 'c8y-command-plugin' @ Cloudsmith](https://api-prd.cloudsmith.io/v1/badges/version/thinedge/community/deb/c8y-command-plugin/latest/a=all;d=any-distro%252Fany-version;t=binary/?render=true&show_latest=true)](https://cloudsmith.io/~thinedge/repos/community/packages/detail/deb/c8y-command-plugin/latest/a=all;d=any-distro%252Fany-version;t=binary/)|
|Alpine Linux (apk)|[![Latest version of 'c8y-command-plugin' @ Cloudsmith](https://api-prd.cloudsmith.io/v1/badges/version/thinedge/community/alpine/c8y-command-plugin/latest/a=noarch;d=alpine%252Fany-version/?render=true&show_latest=true)](https://cloudsmith.io/~thinedge/repos/community/packages/detail/alpine/c8y-command-plugin/latest/a=noarch;d=alpine%252Fany-version/)|
|RHEL/CentOS/Fedora (rpm)|[![Latest version of 'c8y-command-plugin' @ Cloudsmith](https://api-prd.cloudsmith.io/v1/badges/version/thinedge/community/rpm/c8y-command-plugin/latest/a=noarch;d=any-distro%252Fany-version;t=binary/?render=true&show_latest=true)](https://cloudsmith.io/~thinedge/repos/community/packages/detail/rpm/c8y-command-plugin/latest/a=noarch;d=any-distro%252Fany-version;t=binary/)|

#### Configuration

The Cumulocity IoT shell plugin can be configured with the following properties.

|Property|Value|Description|
|--|--|--|
|`SHELL_BIN`|`string`|Default shell to be used to execute the received command. If left blank, then the shell will be auto-detected. If a non-empty value is used. If the shell does not exist, then an error will be raised. The shell will be used using `<shell> -c "<COMMAND>"`.|
|`SHELL_OPTIONS`|Whitespace separated list|List of shells to check if they exist. The plugin will use the first detected shell|

The configuration is managed from the following file, and an example of the contents are shown below.

**File**

```sh
/etc/c8y-command-plugin/env
```

**Contents**

```sh
SHELL_BIN=""
SHELL_OPTIONS="bash zsh ash dash sh /my/custom/shell/interpreter"
```
33 changes: 33 additions & 0 deletions ci/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
#!/bin/bash
# -------------------------------------------
# Build linux packages
# -------------------------------------------
set -e

# clean dist
if [ -d dist ]; then
rm -rf dist
fi

mkdir -p dist

if [ $# -gt 0 ]; then
export SEMVER="$1"
fi

if [ -n "$SEMVER" ]; then
echo "Using version: $SEMVER"
fi

packages=(
deb
apk
rpm
)

for package_type in "${packages[@]}"; do
echo ""
nfpm package --packager "$package_type" --target ./dist/
done

echo "Created all linux packages"
130 changes: 130 additions & 0 deletions ci/publish.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#!/bin/bash
# -----------------------------------------------
# Publish package to Cloudsmith.io
# -----------------------------------------------
help() {
cat <<EOF
Publish packages from a path to a package repository
All the necessary dependencies will be downloaded automatically if they are not already present
Usage:
$0
Flags:
--token <string> Debian access token used to authenticate the commands
--owner <string> Debian repository owner
--repo <string> Name of the debian repository to publish to
--help|-h Show this help
Optional Environment variables (instead of flags)
PUBLISH_TOKEN Equivalent to --token flag
PUBLISH_OWNER Equivalent to --owner flag
PUBLISH_REPO Equivalent to --repo flag
Examples:
$0 \\
--token "mywonderfultoken" \\
--repo "community" \\
--path ./dist
\$ Publish all debian/alpine/rpm packages found under ./dist
EOF
}

PUBLISH_TOKEN="${PUBLISH_TOKEN:-}"
PUBLISH_OWNER="${PUBLISH_OWNER:-thinedge}"
PUBLISH_REPO="${PUBLISH_REPO:-community}"
SOURCE_PATH="./"

#
# Argument parsing
#
POSITIONAL=()
while [[ $# -gt 0 ]]
do
case "$1" in
# Repository owner
--owner)
PUBLISH_OWNER="$2"
shift
;;

# Token used to authenticate publishing commands
--token)
PUBLISH_TOKEN="$2"
shift
;;

# Where to look for the debian files to publish
--path)
SOURCE_PATH="$2"
shift
;;

# Which debian repo to publish to (under the given host url)
--repo)
PUBLISH_REPO="$2"
shift
;;

--help|-h)
help
exit 0
;;

-*)
echo "Unrecognized flag" >&2
help
exit 1
;;

*)
POSITIONAL+=("$1")
;;
esac
shift
done
set -- "${POSITIONAL[@]}"

# Add local tools path
LOCAL_TOOLS_PATH="$HOME/.local/bin"
export PATH="$LOCAL_TOOLS_PATH:$PATH"

# Install tooling if missing
if ! [ -x "$(command -v cloudsmith)" ]; then
echo 'Install cloudsmith cli' >&2
if command -v pip3 &>/dev/null; then
pip3 install --upgrade cloudsmith-cli
elif command -v pip &>/dev/null; then
pip install --upgrade cloudsmith-cli
else
echo "Could not install cloudsmith cli. Reason: pip3/pip is not installed"
exit 2
fi
fi


publish() {
local sourcedir="$1"
local pattern="$2"
local package_type="$3"
local distribution="$4"
local distribution_version="$5"

# Notes: Currently Cloudsmith does not support the following (this might change in the future)
# * distribution and distribution_version must be selected from values in the list. use `cloudsmith list distros` to get the list
# * The component can not be set and is currently fixed to 'main'
find "$sourcedir" -name "$pattern" -print0 | while read -r -d $'\0' file
do
cloudsmith upload "$package_type" "${PUBLISH_OWNER}/${PUBLISH_REPO}/${distribution}/${distribution_version}" "$file" \
--no-wait-for-sync \
--api-key "${PUBLISH_TOKEN}"
done
}


publish "$SOURCE_PATH" "*.deb" deb "any-distro" "any-version"
publish "$SOURCE_PATH" "*.rpm" rpm "any-distro" "any-version"
publish "$SOURCE_PATH" "*.apk" alpine "alpine" "any-version"
36 changes: 36 additions & 0 deletions nfpm.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: c8y-command-plugin
arch: all
platform: linux
version: ${SEMVER}
section: misc
priority: optional
maintainer: Reuben Miller <reuben.d.miller@gmail.com>
description: thin-edge.io Cumulocity IoT Shell/Command operation plugin
vendor: thin-edge.io
homepage: https://github.com/reubenmiller/c8y-command-plugin
license: MIT
apk:
# Use noarch instead of "all"
arch: noarch
contents:
- src: ./src/c8y_Command
dst: /etc/tedge/operations/c8y/
file_info:
mode: 0644
owner: tedge
group: tedge

- src: ./src/c8y-command
dst: /usr/bin/c8y-command
file_info:
mode: 0755
owner: tedge
group: tedge

- src: ./src/env
dst: /etc/c8y-command-plugin/env
type: config|noreplace
file_info:
mode: 0644
owner: tedge
group: tedge
64 changes: 64 additions & 0 deletions src/c8y-command
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
#!/bin/sh
set -e

info() {
echo "$(date --iso-8601=seconds 2>/dev/null || date -Iseconds) INFO $*" >&2
}

info "Received message: $*"

# Parse the smart rest message, ignore the first two field, and everything afterwards is the command
COMMAND="${1#*,*,}"

# Check if command is wrapped with quotes, if so then remove them
# Use a case statement, as it is posix compatiable
case "$COMMAND" in
'"'*'"')
# Remove the first char
COMMAND="${COMMAND#?}"
# Remove the last char
COMMAND="${COMMAND%?}"
;;
esac

# Default values (can be overriden by the settings file)
SHELL_OPTIONS="bash sh"
SHELL_BIN=

# Load settings file
SETTINGS_FILE=/etc/c8y-command-plugin/env
if [ -f "$SETTINGS_FILE" ]; then
FOUND_FILE=$(find "$SETTINGS_FILE" -perm 644 | head -1)

if [ -n "$FOUND_FILE" ]; then
info "Loading settings: $FOUND_FILE"
# shellcheck disable=SC1090
. "$FOUND_FILE" ||:
fi
fi

# Auto detect the shell. Match on the first available shell
# If the shell bin is invalid, then just let it fail (this might be useful to disable the shell function on the device)
if [ -z "$SHELL_BIN" ]; then
for NAME in $SHELL_OPTIONS; do
if command -V "$NAME" >/dev/null 2>&1; then
SHELL_BIN="$NAME"
break
fi
done
fi

if [ -z "$SHELL_BIN" ]; then
SHELL_BIN="sh"
fi

info "Using shell: $SHELL_BIN"

EXIT_CODE=0
"$SHELL_BIN" -c "$COMMAND" || EXIT_CODE=$?

if [ "${EXIT_CODE}" -ne 0 ]; then
info "Command returned a non-zero exit code. code=$EXIT_CODE"
fi

exit "$EXIT_CODE"
4 changes: 4 additions & 0 deletions src/c8y_Command
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[exec]
topic = "c8y/s/ds"
on_message = "511"
command = "/usr/bin/c8y-command"
5 changes: 5 additions & 0 deletions src/env
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Which shell to use. Blank means that the first shell option found will be used
#SHELL_BIN=""

# List of shells to check. Use the first available shell. This is only used if SHELL_BIN is not defined
#SHELL_OPTIONS="bash sh"

0 comments on commit 5f64839

Please sign in to comment.