diff --git a/.github/workflows/build-publish.yaml b/.github/workflows/build-publish.yaml new file mode 100644 index 00000000..c6862a5c --- /dev/null +++ b/.github/workflows/build-publish.yaml @@ -0,0 +1,63 @@ +name: Build and publish image +on: + push: + tags: + - "*" + pull_request: + branches: + - main + +env: + REGISTRY: ghcr.io + + +jobs: + build-and-publish-image: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + submodules: recursive + - name: build-web + run: | + mkdir -p `pwd`/.tmp + make build-web UI_FILE=`pwd`/.tmp/ui.tar.gz + - uses: docker/setup-qemu-action@v2 + - uses: docker/setup-buildx-action@v2 + - uses: docker/login-action@v2 + with: + registry: ${{ env.REGISTRY }} + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + - name: Extract metadata (tags, labels) for docker image + id: meta + uses: docker/metadata-action@v4 + with: + images: ${{ env.REGISTRY }}/${{ github.repository }} + tags: | + type=raw,enable={{is_default_branch}},value=vnext + type=sha,enable={{is_default_branch}},prefix=vnext-,format=short + type=ref,event=pr,prefix=vnext-pr,suffix=-{{sha}} + type=ref,event=pr,prefix=vnext-pr + type=ref,enable={{is_default_branch}},event=branch + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=semver,pattern={{major}} + - name: Build and publish image + uses: docker/build-push-action@v3 + with: + context: . + file: ./docker/Dockerfile + platforms: linux/amd64,linux/arm64 + builder: ${{ steps.buildx.outputs.name }} + build-args: | + UI_FILE=.tmp/ui.tar.gz + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + - name: Clean up + if: ${{ always() }} + run: | + shopt -s dotglob + sudo rm -r * \ No newline at end of file diff --git a/.github/workflows/buildTestBinaries.yml b/.github/workflows/buildTestBinaries.yml index c10eccd6..71b7451a 100644 --- a/.github/workflows/buildTestBinaries.yml +++ b/.github/workflows/buildTestBinaries.yml @@ -27,11 +27,12 @@ jobs: with: go-version: "^1.18" # The Go version to download (if necessary) and use. - - name: Set version + - name: Set version and ui_file id: vars run: | git tag $(git describe --tags --abbrev=0)-$(git rev-parse --short HEAD) echo "::set-output name=version::$(git describe --tags --abbrev=0)" + echo "::set-output name=ui_file::$(pwd)/.tmp/ui.tar.gz" - name: Build client uses: goreleaser/goreleaser-action@v3 @@ -41,6 +42,7 @@ jobs: args: release --rm-dist --skip-publish --skip-announce env: UI_SEPARATOR: "--------UI--------" + UI_FILE: ${{ steps.vars.outputs.ui_file }} - name: Upload assets uses: actions/upload-artifact@v3 diff --git a/.github/workflows/ghcr-cleanup.yaml b/.github/workflows/ghcr-cleanup.yaml new file mode 100644 index 00000000..d65d0f79 --- /dev/null +++ b/.github/workflows/ghcr-cleanup.yaml @@ -0,0 +1,37 @@ +name: Delete old ghcr images +on: + schedule: + - cron: "15 1 * * *" # every day at 1:15am + pull_request: + types: [closed] + +jobs: + pull-request-ghcr-cleanup: + if: ${{ github.event_name == 'pull_request' }} + name: Delete images related to closed PR + runs-on: ubuntu-latest + steps: + - name: Delete images related to closed PR + uses: snok/container-retention-policy@v1 + with: + image-names: client-application + cut-off: now UTC + account-type: org + org-name: plgd-dev + filter-tags: vnext-pr${{ github.event.pull_request.number }}* + token: ${{ secrets.GHCR_CLEANUP_PAT }} + nightly-ghcr-cleanup: + if: ${{ github.event_name == 'schedule' }} + name: Delete old vnext images + runs-on: ubuntu-latest + steps: + - name: Delete older than a month vnext images + uses: snok/container-retention-policy@v1 + with: + image-names: client-application + cut-off: One month ago UTC + account-type: org + org-name: plgd-dev + filter-tags: vnext-* + skip-tags: vnext-pr*, main + token: ${{ secrets.GHCR_CLEANUP_PAT }} \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index eec2675a..295c3fb1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,6 +29,11 @@ jobs: uses: actions/setup-go@v2 with: go-version: 1.18 + + - name: Set ui_file + id: vars + run: | + echo "::set-output name=ui_file::$(pwd)/.tmp/ui.tar.gz" - name: Run GoReleaser uses: goreleaser/goreleaser-action@v2 @@ -40,5 +45,6 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} UI_SEPARATOR: "--------UI--------" + UI_FILE: ${{ steps.vars.outputs.ui_file }} # Your GoReleaser Pro key, if you are using the 'goreleaser-pro' distribution # GORELEASER_KEY: ${{ secrets.GORELEASER_KEY }} \ No newline at end of file diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 49212654..76e60d13 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -2,7 +2,7 @@ # Make sure to check the documentation at https://goreleaser.com before: hooks: - - make build-web + - bash -c "test -f {{.Env.UI_FILE}} || make build-web UI_FILE={{.Env.UI_FILE}}" dist: .tmp/dist builds: - binary: client-application @@ -33,7 +33,7 @@ builds: hooks: post: - bash -c "test {{.Os}} == "windows" || test {{.Os}} == "darwin" || upx --best --lzma {{.Path}}" - - make inject-web CLIENT_APPLICATION_BINARY_PATH={{.Path}} UI_SEPARATOR={{.Env.UI_SEPARATOR}} + - make inject-web CLIENT_APPLICATION_BINARY_PATH={{.Path}} UI_SEPARATOR={{.Env.UI_SEPARATOR}} UI_FILE={{.Env.UI_FILE}} archives: - replacements: darwin: macOS diff --git a/Makefile b/Makefile index 8531e4d9..b1930e35 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ WWW_PATH=$(TMP_PATH)/www CLIENT_APPLICATION_VERSION_PATH_VARIABLE = main.Version CLIENT_APPLICATION_UI_SEPARATOR_PATH_VARIABLE = main.UISeparator CLIENT_APPLICATION_BINARY_PATH ?= +UI_FILE ?= $(TMP_PATH)/ui.tar.gz CERT_TOOL_IMAGE ?= ghcr.io/plgd-dev/hub/cert-tool:vnext DEVSIM_IMAGE ?= ghcr.io/iotivity/iotivity-lite/cloud-server-discovery-resource-observable-debug:master @@ -93,17 +94,17 @@ build-web: mkdir -p $(WWW_PATH) docker build --tag build-web:latest --target build-web -f ./web/Dockerfile ./web CONTAINER_ID=`docker create build-web:latest` && docker cp $$CONTAINER_ID:/web/build/ $(WWW_PATH)/ && docker rm -f $$CONTAINER_ID - cd $(WWW_PATH)/build && tar -czf $(TMP_PATH)/ui.tar.gz . + cd $(WWW_PATH)/build && tar -czf $(UI_FILE) . .PHONY: build-web inject-web: $(CLIENT_APPLICATION_BINARY_PATH) printf "\n$(UI_SEPARATOR)\n" >> $(CLIENT_APPLICATION_BINARY_PATH) wc -c $(CLIENT_APPLICATION_BINARY_PATH) - cat $(TMP_PATH)/ui.tar.gz >> $(CLIENT_APPLICATION_BINARY_PATH) + cat $(UI_FILE) >> $(CLIENT_APPLICATION_BINARY_PATH) .PHONY: inject-web -build: clean - UI_SEPARATOR=$(UI_SEPARATOR) goreleaser build --rm-dist +build: + UI_FILE=$(UI_FILE) UI_SEPARATOR=$(UI_SEPARATOR) goreleaser build --rm-dist --single-target --skip-validate .PHONY: build test: env diff --git a/README.md b/README.md index 52a51027..b55bc5f4 100644 --- a/README.md +++ b/README.md @@ -32,6 +32,16 @@ git tag -f v0.0.1-myversion make build ``` +## Docker + +You can run the client application in a docker container by executing the following command. + +```sh +docker run --rm -it -e NUM_DEVICES=1 -p 8080:8080 ghcr.io/plgd-dev/client-application:latest +``` + +Via environment variable NUM_DEVICES you can specify the number of devices simulators. The default value is 1. The client application will be available at [http://locahost:8080](http://locahost:8080). + ## YAML Configuration A configuration template is available on [config.yaml](./config.yaml). diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 00000000..73086f07 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,16 @@ +FROM goreleaser/goreleaser AS builder +ARG UI_FILE + +RUN apk add upx +WORKDIR /go/src/github.com/plgd-dev/client-application +COPY go.mod go.sum ./ +COPY ${UI_FILE} ${UI_FILE} +COPY . . +RUN make build BUILD_UI=false UI_FILE=${UI_FILE} + +FROM ghcr.io/iotivity/iotivity-lite/cloud-server-discovery-resource-observable:latest AS service +COPY ./docker/run.sh /usr/local/bin/run.sh +COPY --from=builder /go/src/github.com/plgd-dev/client-application/.tmp/dist/*/client-application /usr/local/bin/client-application +RUN mkdir -p /plgd +WORKDIR /plgd +ENTRYPOINT [ "/usr/local/bin/run.sh" ] \ No newline at end of file diff --git a/docker/run.sh b/docker/run.sh new file mode 100755 index 00000000..e664cd0d --- /dev/null +++ b/docker/run.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -e + +PREFIX_EXEC="" +echo Spawning $NUM_DEVICES devices + +umask 0000 +pids=() +for ((i=0;i<$NUM_DEVICES;i++)); do + LD_PRELOAD=/usr/local/lib/faketime/libfaketimeMT.so.1 /iotivity-lite/port/linux/service "device-$i" > /tmp/$i.log 2>&1 & + pids+=($!) +done + +/usr/local/bin/client-application --config /plgd/config.yaml $@ > /tmp/client-application.log 2>&1 & +pids+=($!) + +terminate() +{ + echo "Terminate" + for (( i=0; i<${#pids[@]}; i++ )); do + kill -SIGTERM ${pids[$i]} + done +} + +trap terminate SIGTERM + +# Naive check runs checks once a minute to see if either of the processes exited. +# This illustrates part of the heavy lifting you need to do if you want to run +# more than one service in a container. The container exits with an error +# if it detects that either of the processes has exited. +# Otherwise it loops forever, waking up every 60 seconds +while sleep 10; do +for (( i=0; i<${#pids[@]}; i++ )); +do + if ! kill -0 ${pids[$i]} 2>/dev/null; then + echo "service[$i] with pid=${pids[$i]} is dead" + exit 1 + fi +done +echo checking running devices +done \ No newline at end of file diff --git a/tools/validate/validateYaml.py b/tools/validate/validateYaml.py index ac7802dd..b98d7883 100755 --- a/tools/validate/validateYaml.py +++ b/tools/validate/validateYaml.py @@ -74,7 +74,7 @@ def find_and_validate_yaml_files(dir = ROOT_DIRECTORY): """Find all yaml files in directory and validate them.""" valid = True - exclude_dirs = set(["dependency", "templates"]) + exclude_dirs = set(["dependency", "templates", ".github"]) exclude_filenames = set(["swagger.yaml"]) for root, dirnames, filenames in os.walk(dir, topdown=True): dirnames[:] = [d for d in dirnames if d not in exclude_dirs]