From c7d058f7228a41f3f83cf491eb9bbb910b5f2b99 Mon Sep 17 00:00:00 2001 From: lenny Date: Fri, 5 Feb 2021 13:29:50 -0700 Subject: [PATCH] feat: Back port service template from hanoi branch Top level `test` target builds and tests the new template closes #674 Signed-off-by: lenny --- Makefile | 8 +- app-service-template/.dockerignore | 12 ++ app-service-template/.gitignore | 10 + app-service-template/Attribution.txt | 160 ++++++++++++++++ app-service-template/Dockerfile | 56 ++++++ app-service-template/LICENSE | 177 ++++++++++++++++++ app-service-template/Makefile | 53 ++++++ app-service-template/README.md | 41 ++++ .../bin/test-attribution-txt.sh | 59 ++++++ app-service-template/bin/test-go-mod-tidy.sh | 55 ++++++ app-service-template/functions/sample.go | 136 ++++++++++++++ app-service-template/functions/sample_test.go | 95 ++++++++++ app-service-template/go.mod | 14 ++ app-service-template/main.go | 71 +++++++ app-service-template/res/configuration.toml | 103 ++++++++++ 15 files changed, 1049 insertions(+), 1 deletion(-) create mode 100644 app-service-template/.dockerignore create mode 100644 app-service-template/.gitignore create mode 100644 app-service-template/Attribution.txt create mode 100644 app-service-template/Dockerfile create mode 100644 app-service-template/LICENSE create mode 100644 app-service-template/Makefile create mode 100644 app-service-template/README.md create mode 100644 app-service-template/bin/test-attribution-txt.sh create mode 100644 app-service-template/bin/test-go-mod-tidy.sh create mode 100644 app-service-template/functions/sample.go create mode 100644 app-service-template/functions/sample_test.go create mode 100644 app-service-template/go.mod create mode 100644 app-service-template/main.go create mode 100644 app-service-template/res/configuration.toml diff --git a/Makefile b/Makefile index b1ac36e1e..b8a45524d 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,13 @@ GO=CGO_ENABLED=1 GO111MODULE=on go -test: +build: + make -C ./app-service-template build + +test-template: + make -C ./app-service-template test + +test: build test-template $(GO) test ./... -coverprofile=coverage.out ./... $(GO) vet ./... gofmt -l . diff --git a/app-service-template/.dockerignore b/app-service-template/.dockerignore new file mode 100644 index 000000000..ba23da962 --- /dev/null +++ b/app-service-template/.dockerignore @@ -0,0 +1,12 @@ +node_modules +npm-debug.log +Dockerfile* +docker-compose* +.dockerignore +.git +.gitignore +.env +*/bin +*/obj +README.md +.vscode diff --git a/app-service-template/.gitignore b/app-service-template/.gitignore new file mode 100644 index 000000000..182205dae --- /dev/null +++ b/app-service-template/.gitignore @@ -0,0 +1,10 @@ +test.txt +debug +*.exe +new-app-service +.vscode +go.sum +coverage.out +logs/ +.idea +VERSION diff --git a/app-service-template/Attribution.txt b/app-service-template/Attribution.txt new file mode 100644 index 000000000..a53d174e9 --- /dev/null +++ b/app-service-template/Attribution.txt @@ -0,0 +1,160 @@ +The following open source projects are referenced by app-service-configurable: + +bertimus9/systemstat (MIT) https://bitbucket.org/bertimus9/systemstat +https://bitbucket.org/bertimus9/systemstat/src/master/LICENSE + +armon/go-metrics (MIT) https://github.com/armon/go-metrics +https://github.com/armon/go-metrics/blob/master/LICENSE + +cenkalti/backoff (MIT) https://github.com/cenkalti/backoff +https://github.com/cenkalti/backoff/blob/master/LICENSE + +eclipse/paho.mqtt.golang (Eclipse Public License 1.0) https://github.com/eclipse/paho.mqtt.golang +https://github.com/eclipse/paho.mqtt.golang/blob/master/LICENSE + +edgexfoundry/app-functions-sdk-go (Apache 2.0) https://github.com/edgexfoundry/app-functions-sdk-go/v2 +https://github.com/edgexfoundry/app-functions-sdk-go/blob/master/LICENSE + +edgexfoundry/go-mod-core-contracts (Apache 2.0) https://github.com/edgexfoundry/go-mod-core-contracts/v2 +https://github.com/edgexfoundry/go-mod-core-contracts/blob/master/LICENSE + +edgexfoundry/go-mod-registry (Apache 2.0) https://github.com/edgexfoundry/go-mod-registry/v2 +https://github.com/edgexfoundry/go-mod-registry/blob/master/LICENSE + +edgexfoundry/go-mod-messaging (Apache 2.0) https://github.com/edgexfoundry/go-mod-messaging/v2 +https://github.com/edgexfoundry/go-mod-messaging/blob/master/LICENSE + +edgexfoundry/go-mod-secrets (Apache 2.0) https://github.com/edgexfoundry/go-mod-secrets/v2 +https://github.com/edgexfoundry/go-mod-secrets/blob/master/LICENSE + +edgexfoundry/go-mod-secrets (Apache 2.0) https://github.com/edgexfoundry/go-mod-bootstrap/v2 +https://github.com/edgexfoundry/go-mod-bootstrap/blob/master/LICENSE + +edgexfoundry/go-mod-secrets (Apache 2.0) https://github.com/edgexfoundry/go-mod-configuration/v2 +https://github.com/edgexfoundry/go-mod-configuration/blob/master/LICENSE + +BurntSushi/toml (MIT) https://github.com/BurntSushi/toml +https://github.com/BurntSushi/toml/blob/master/COPYING + +go-kit/kit (MIT) github.com/go-kit/kit +https://github.com/go-kit/kit/blob/master/LICENSE + +go-logfmt/logfmt (MIT) https://github.com/go-logfmt/logfmt +https://github.com/go-logfmt/logfmt/blob/master/LICENSE + +google/uuid (BSD-3) https://github.com/google/uuid +https://github.com/google/uuid/blob/master/LICENSE + +gorilla/mux (BSD-3) https://github.com/gorilla/mux +https://github.com/gorilla/mux/blob/master/LICENSE + +hashicorp/consul/api (Mozilla Public License 2.0) - https://github.com/hashicorp/consul/api +https://github.com/hashicorp/consul/blob/master/LICENSE + +hashicorp/go-cleanhttp (Mozilla Public License 2.0) - https://github.com/hashicorp/go-cleanhttp +https://github.com/hashicorp/go-cleanhttp/blob/master/LICENSE + +hashicorp/go-immutable-radix (Mozilla Public License 2.0) https://github.com/hashicorp/go-immutable-radix +https://github.com/hashicorp/go-immutable-radix/blob/master/LICENSE + +hashicorp/go-rootcerts (Mozilla Public License 2.0) https://github.com/hashicorp/go-rootcerts +https://github.com/hashicorp/go-rootcerts/blob/master/LICENSE + +hashicorp/golang-lru (Mozilla Public License 2.0) https://github.com/hashicorp/golang-lru +https://github.com/hashicorp/golang-lru/blob/master/LICENSE + +hashicorp/serf (Mozilla Public License 2.0) https://github.com/hashicorp/serf +https://github.com/hashicorp/serf/blob/master/LICENSE + +kr/logfmt (Unspecified) https://github.com/kr/logfmt +https://github.com/kr/logfmt/blob/master/Readme + +mitchellh/consulstructure (MIT) https://github.com/mitchellh/consulstructure +https://github.com/mitchellh/consulstructure/blob/master/LICENSE + +mitchellh/mapstructure (MIT) https://github.com/mitchellh/mapstructure +https://github.com/mitchellh/mapstructure/blob/master/LICENSE + +mitchellh/copystructure (MIT) https://github.com/mitchellh/copystructure +https://github.com/mitchellh/copystructure/blob/master/LICENSE + +mitchellh/reflectwalk (MIT) https://github.com/mitchellh/reflectwalk +https://github.com/mitchellh/reflectwalk/blob/master/LICENSE + +mitchellh/go-homedir (MIT) https://github.com/mitchellh/go-homedir +https://github.com/mitchellh/go-homedir/blob/master/LICENSE + +pebbe/zmq4 (BSD-2) https://github.com/pebbe/zmq4 +https://github.com/pebbe/zmq4/blob/master/LICENSE.txt + +pelletier/go-toml (MIT) https://github.com/pelletier/go-toml +https://github.com/pelletier/go-toml/blob/master/LICENSE + +pkg/errors (BSD-2) https://github.com/pkg/errors +https://github.com/pkg/errors/blob/master/LICENSE + +fxamacker/cbor/v2 (MIT) https://github.com/fxamacker/cbor/v2 +https://github.com/fxamacker/cbor/blob/master/LICENSE + +x448/float16 (MIT) https://github.com/x448/float16 +https://github.com/x448/float16/blob/master/LICENSE + +golang.org/x/net (Unspecified) https://github.com/golang/net +https://github.com/golang/net/blob/master/LICENSE + +gomodule/redigo (Apache 2.0) https://github.com/gomodule/redigo +https://github.com/gomodule/redigo/blob/master/LICENSE + +github.com/go-stack/stack (MIT) https://github.com/go-stack/stack +https://github.com/go-stack/stack/blob/master/LICENSE.md + +github.com/golang/snappy (BSD-3) https://github.com/golang/snappy +https://github.com/golang/snappy/blob/master/LICENSE + +github.com/xdg/scram (Apache 2.0) https://github.com/xdg-go/scram +https://github.com/xdg-go/scram/blob/master/LICENSE + +github.com/xdg/stringprep (Apache 2.0) https://github.com/xdg-go/stringprep +https://github.com/xdg-go/stringprep/blob/master/LICENSE + +go.mongodb.org/mongo-driver (Apache 2.0) https://github.com/mongodb/mongo-go-driver +https://github.com/mongodb/mongo-go-driver/blob/master/LICENSE + +golang.org/x/crypto (Unspecified) https://github.com/golang/crypto +https://github.com/golang/crypto/blob/master/LICENSE + +golang.org/x/sync (Unspecified) https://github.com/golang/sync +https://github.com/golang/sync/blob/master/LICENSE + +golang.org/x/text (Unspecified) https://github.com/golang/text +https://github.com/golang/text/blob/master/LICENSE + +github.com/diegoholiveira/jsonlogic (MIT) https://github.com/diegoholiveira/ +https://github.com/diegoholiveira/jsonlogic/blob/master/LICENSE + +github.com/go-redis/redis/v7 (BSD-2) https://github.com/go-redis/redis +https://github.com/go-redis/redis/blob/master/LICENSE + +go-playground/locales (MIT) https://github.com/go-playground/locales +https://github.com/go-playground/locales/blob/master/LICENSE + +go-playground/universal-translator (MIT) https://github.com/go-playground/universal-translator +https://github.com/go-playground/universal-translator/blob/master/LICENSE + +github.com/go-playground/validator/v10 (MIT) https://github.com/go-playground/validator +https://github.com/go-playground/validator/blob/master/LICENSE + +leodido/go-urn (MIT) https://github.com/leodido/go-urn +https://github.com/leodido/go-urn + +stretchr/testify (MIT) https://github.com/stretchr/testify +https://github.com/stretchr/testify/blob/master/LICENSE + +pmezard/go-difflib (Unspecified) https://github.com/pmezard/go-difflib +https://github.com/pmezard/go-difflib/blob/master/LICENSE + +davecgh/go-spew (ISC) https://github.com/davecgh/go-spew +https://github.com/davecgh/go-spew/blob/master/LICENSE + +gopkg.in/yaml.v3 (MIT) https://github.com/go-yaml/yaml/ +https://github.com/go-yaml/yaml/blob/v3/LICENSE diff --git a/app-service-template/Dockerfile b/app-service-template/Dockerfile new file mode 100644 index 000000000..26124258a --- /dev/null +++ b/app-service-template/Dockerfile @@ -0,0 +1,56 @@ +# TODO: Change Copyright to your company if open sourcing or remove header +# +# Copyright (c) 2021 Intel Corporation +# +# 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. +# + +#build stage +ARG BASE=golang:1.15-alpine3.12 +FROM ${BASE} AS builder + +ARG ALPINE_PKG_BASE="make git gcc libc-dev libsodium-dev zeromq-dev" +ARG ALPINE_PKG_EXTRA="" + +RUN sed -e 's/dl-cdn[.]alpinelinux.org/nl.alpinelinux.org/g' -i~ /etc/apk/repositories +RUN apk add --update --no-cache ${ALPINE_PKG_BASE} ${ALPINE_PKG_EXTRA} +WORKDIR /app + +COPY go.mod . +RUN go mod download + +COPY . . + +ARG MAKE="make build" +RUN $MAKE + +#final stage +FROM alpine:3.12 +# TODO: Change Copyright to your company if open sourcing or remove label +LABEL license='SPDX-License-Identifier: Apache-2.0' \ + copyright='Copyright (c) 2021: Intel' +LABEL Name=new-app-service Version=${VERSION} + +# dumb-init is required as security-bootstrapper uses it in the entrypoint script +RUN apk add --update --no-cache ca-certificates zeromq dumb-init + +COPY --from=builder /app/Attribution.txt /Attribution.txt +COPY --from=builder /app/LICENSE /LICENSE +COPY --from=builder /app/res/ /res/ +COPY --from=builder /app/new-app-service /new-app-service + +# TODO: set this port appropriatly as it is in the configuation.toml +EXPOSE 49600 + +ENTRYPOINT ["/new-app-service"] +CMD ["-cp=consul.http://edgex-core-consul:8500", "--registry", "--confdir=/res"] diff --git a/app-service-template/LICENSE b/app-service-template/LICENSE new file mode 100644 index 000000000..b8cc0444e --- /dev/null +++ b/app-service-template/LICENSE @@ -0,0 +1,177 @@ + + Apache License + Version 2.0, January 2004 + https://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS \ No newline at end of file diff --git a/app-service-template/Makefile b/app-service-template/Makefile new file mode 100644 index 000000000..05b6700e9 --- /dev/null +++ b/app-service-template/Makefile @@ -0,0 +1,53 @@ +.PHONY: build test clean docker + +GO=CGO_ENABLED=1 go + +# VERSION file is not needed for local development, In the CI/CD pipeline, a temporary VERSION file is written +# if you need a specific version, just override below +# TODO: If your service is not being upstreamed to Edgex Foundry, you need to determine the best approach for +# setting your service's version for non-development builds. +APPVERSION=$(shell cat ./VERSION 2>/dev/null || echo 0.0.0) + +# This pulls the version of the SDK from the go.mod file. If the SDK is the only required module, +# it must first remove the word 'required' so the offset of $2 is the same if there are multiple required modules +SDKVERSION=$(shell cat ./go.mod | grep 'github.com/edgexfoundry/app-functions-sdk-go v' | sed 's/require//g' | awk '{print $$2}') + +MICROSERVICE=new-app-service +GOFLAGS=-ldflags "-X github.com/edgexfoundry/app-functions-sdk-go/v2/internal.SDKVersion=$(SDKVERSION) -X github.com/edgexfoundry/app-functions-sdk-go/v2/internal.ApplicationVersion=$(APPVERSION)" + +# TODO: uncomment and remove default once files are in a Github repository or +# remove totally including usage below +#GIT_SHA=$(shell git rev-parse HEAD) +GIT_SHA=no-sha + +build: + $(GO) build $(GOFLAGS) -o $(MICROSERVICE) + +# TODO: Change the registries (edgexfoundry, nexus3.edgexfoundry.org:10004) below as needed. +# Leave them as is if service is to be upstreamed to EdgeX Foundry +# NOTE: This is only used for local development. Jenkins CI does not use this make target +docker: + docker build \ + --build-arg http_proxy \ + --build-arg https_proxy \ + -f Dockerfile \ + --label "git_sha=$(GIT_SHA)" \ + -t edgexfoundry/new-app-service:$(GIT_SHA) \ + -t edgexfoundry/new-app-service:master-dev \ + -t nexus3.edgexfoundry.org:10004/new-app-service:master-dev \ + . + +# The test-go-mod-tidy.sh & test-attribution-txt.sh scripts are required for upstreaming to EdgeX Foundry. +# TODO: Remove bin folder and reference to scripts below if NOT upstreaming to EdgeX Foundry. +test: + $(GO) test -coverprofile=coverage.out ./... + $(GO) vet ./... + gofmt -l . + [ "`gofmt -l .`" = "" ] + ./bin/test-go-mod-tidy.sh + ./bin/test-attribution-txt.sh + +clean: + rm -f $(MICROSERVICE) + + diff --git a/app-service-template/README.md b/app-service-template/README.md new file mode 100644 index 000000000..3aec4798e --- /dev/null +++ b/app-service-template/README.md @@ -0,0 +1,41 @@ +# Application Service Template + +This folder contains a buildable/runnable template for a new custom application service based on the Hanoi 1.3.1 release of the App Functions SDK. + +> **Note**: If you only need to use the built-in pipeline functions, then it is advisable that you use `App Service Configurable` rather then create a new custom application service. See [here](https://docs.edgexfoundry.org/1.3/microservices/application/AppServiceConfigurable/) for more details on `App Service Configurable` + +Follow the instructions below to create your new customer application service: + +1. Copy contents of this folder to your new folder + +2. Change name `new-app-service` in go.mod to an appropriate Go Module name for your service + + - Typically this is the URL to the repository for your service + +3. Do a global search and replace on `new-app-service` to replace it with the name of your service + + - Note that this name is used as the service key, so it needs to use dashes rather than spaces in the name and avoid other special characters + +4. Adjust your local import statements to match the name you selected in the go.mod file + + - Only needed in `main.go` if the Go Module name changed to a URL + +5. Run unit tests to verify changes didn't break the code + + - `make test` + +6. Verify you are able to build the executable + + - `make build` + +7. Update the `makefile` docker build to adjust image name appropriately + +8. Verify the docker image still builds with your new image name + + - `make docker` + +9. Address all the TODO's in the source files and add your custom code + +10. Build and test your new custom application service + + \ No newline at end of file diff --git a/app-service-template/bin/test-attribution-txt.sh b/app-service-template/bin/test-attribution-txt.sh new file mode 100644 index 000000000..c200a663f --- /dev/null +++ b/app-service-template/bin/test-attribution-txt.sh @@ -0,0 +1,59 @@ +#!/bin/bash -e + +# get the directory of this script +# snippet from https://stackoverflow.com/a/246128/10102404 +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +GIT_ROOT=$(dirname "$SCRIPT_DIR") + +EXIT_CODE=0 + +cd "$GIT_ROOT" + +if [ -d vendor.bk ]; then + echo "vendor.bk exits - remove before continuing" + exit 1 +fi + +trap cleanup 1 2 3 6 + +cleanup() +{ + cd "$GIT_ROOT" + # restore the vendor dir + rm -r vendor + if [ -d vendor.bk ]; then + mv vendor.bk vendor + fi + exit $EXIT_CODE +} + +# if the vendor directory exists, back it up so we can build a fresh one +if [ -d vendor ]; then + mv vendor vendor.bk +fi + +# create a vendor dir with the mod dependencies +GO111MODULE=on go mod vendor + +# turn on nullglobbing so if there is nothing in cmd dir then we don't do +# anything in this loop +shopt -s nullglob + + +if [ ! -f Attribution.txt ]; then + echo "An Attribution.txt file for $cmd is missing, please add" + EXIT_CODE=1 +else + # loop over every library in the modules.txt file in vendor + while IFS= read -r lib; do + if ! grep -q "$lib" Attribution.txt && [ "$lib" != "explicit" ]; then + echo "An attribution for $lib is missing from in $cmd Attribution.txt, please add" + # need to do this in a bash subshell, see SC2031 + (( EXIT_CODE=1 )) + fi + done < <(grep '#' < "$GIT_ROOT/vendor/modules.txt" | awk '{print $2}') +fi + +cd "$GIT_ROOT" + +cleanup diff --git a/app-service-template/bin/test-go-mod-tidy.sh b/app-service-template/bin/test-go-mod-tidy.sh new file mode 100644 index 000000000..8599e5346 --- /dev/null +++ b/app-service-template/bin/test-go-mod-tidy.sh @@ -0,0 +1,55 @@ +#!/bin/bash -e + +# get the directory of this script +# snippet from https://stackoverflow.com/a/246128/10102404 +SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )" +GIT_ROOT=$(dirname "$SCRIPT_DIR") + +EXIT_CODE=0 + +cd "$GIT_ROOT" + +if [ -f go.mod.bk ]; then + echo "go.mod.bk exits - remove before continuing" + exit 1 +fi + +# backup go.mod +cp go.mod go.mod.bk + +trap cleanup 1 2 3 6 + +cleanup() +{ + cd "$GIT_ROOT" + # restore the go.mod file dir + rm go.mod + if [ -f go.mod.bk ]; then + mv go.mod.bk go.mod + fi + exit $EXIT_CODE +} + +# if go.mod doesn't exist then fail +if [ ! -f go.mod ]; then + echo "missing go.mod, please fix" + EXIT_CODE=1 + cleanup +fi + +GO111MODULE=on go mod tidy + +# check if go.mod and go.mod.bk are the same + +set +e +changes=$(diff -u go.mod go.mod.bk) +set -e + +if [ -n "$changes" ]; then + echo "go.mod is not tidy, please run \"go mod tidy\"" + echo "changes from running \"go mod tidy:\"" + echo "$changes" + EXIT_CODE=1 +fi + +cleanup diff --git a/app-service-template/functions/sample.go b/app-service-template/functions/sample.go new file mode 100644 index 000000000..dbe7a3a7f --- /dev/null +++ b/app-service-template/functions/sample.go @@ -0,0 +1,136 @@ +// TODO: Change Copyright to your company if open sourcing or remove header +// +// Copyright (c) 2021 Intel Corporation +// +// 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. + +package functions + +import ( + "errors" + "fmt" + "strings" + + "github.com/edgexfoundry/app-functions-sdk-go/v2/appcontext" + "github.com/edgexfoundry/go-mod-core-contracts/v2/v2" + "github.com/edgexfoundry/go-mod-core-contracts/v2/v2/dtos" +) + +// TODO: Create your custom type and function(s) and remove these samples + +// TODO: Add parameters that the function(s) will need each time one is executed +func NewSample() Sample { + return Sample{} +} + +type Sample struct { + // TODO: Add properties that the function(s) will need each time one is executed +} + +// LogEventDetails is example of processing an Event and passing the original Event to to next function in the pipeline +// For more details on the Context API got here: https://docs.edgexfoundry.org/1.3/microservices/application/ContextAPI/ +func (s *Sample) LogEventDetails(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) { + edgexcontext.LoggingClient.Debug("LogEventDetails called") + + if len(params) < 1 { + // Go here for details on Error Handle: https://docs.edgexfoundry.org/1.3/microservices/application/ErrorHandling/ + return false, errors.New("no Event Received") + } + + event, ok := params[0].(dtos.Event) + if !ok { + return false, errors.New("type received is not an Event") + } + + edgexcontext.LoggingClient.Infof("Event received: ID=%s, Device=%s, and ReadingCount=%d", + event.Id, + event.DeviceName, + len(event.Readings)) + for index, reading := range event.Readings { + switch strings.ToLower(reading.ValueType) { + case strings.ToLower(v2.ValueTypeBinary): + edgexcontext.LoggingClient.Infof( + "Reading #%d received with ID=%s, Resource=%s, ValueType=%s, MediaType=%s and BinaryValue of size=`%d`", + index+1, + reading.Id, + reading.ResourceName, + reading.ValueType, + reading.MediaType, + len(reading.BinaryValue)) + default: + edgexcontext.LoggingClient.Infof("Reading #%d received with ID=%s, Resource=%s, ValueType=%s, Value=`%s`", + index+1, + reading.Id, + reading.ResourceName, + reading.ValueType, + reading.Value) + } + } + + // Returning true indicates that the pipeline execution should continue with the next function + // using the event passed as input in this case. + return true, event +} + +// ConvertEventToXML is example of transforming an Event and passing the transformed data to to next function in the pipeline +func (s *Sample) ConvertEventToXML(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) { + edgexcontext.LoggingClient.Debug("ConvertEventToXML called") + + if len(params) < 1 { + return false, errors.New("no Event Received") + } + + event, ok := params[0].(dtos.Event) + if !ok { + return false, errors.New("type received is not an Event") + } + + xml, err := event.ToXML() + if err != nil { + return false, errors.New("failed to convert event to XML") + } + + // Example of DEBUG message which by default you don't want to be logged. + // To see debug log messages, Set WRITABLE_LOGLEVEL=DEBUG environment variable or + // change LogLevel in configuration.toml before running app service. + edgexcontext.LoggingClient.Debug("Event converted to XML: " + xml) + + // Returning true indicates that the pipeline execution should continue with the next function + // using the event passed as input in this case. + return true, xml +} + +// OutputXML is an example of processing transformed data +func (s *Sample) OutputXML(edgexcontext *appcontext.Context, params ...interface{}) (bool, interface{}) { + edgexcontext.LoggingClient.Debug("OutputXML called") + + if len(params) < 1 { + return false, errors.New("no XML Received") + } + + xml, ok := params[0].(string) + if !ok { + return false, errors.New("type received is not an string") + } + + edgexcontext.LoggingClient.Debug(fmt.Sprintf("Outputting the following XML: %s", xml)) + + // This sends the XML as a response. i.e. publish for MessageBus/MQTT triggers as configured or + // HTTP response to for the HTTP Trigger + // For more details on the Complete() function go here: https://docs.edgexfoundry.org/1.3/microservices/application/ContextAPI/#complete + edgexcontext.Complete([]byte(xml)) + + // Returning false terminates the pipeline execution, so this should be last function specified in the pipeline, + // which is typical in conjunction with usage of .Complete() function. + return false, nil +} diff --git a/app-service-template/functions/sample_test.go b/app-service-template/functions/sample_test.go new file mode 100644 index 000000000..e72901ca7 --- /dev/null +++ b/app-service-template/functions/sample_test.go @@ -0,0 +1,95 @@ +// TODO: Change Copyright to your company if open sourcing or remove header +// +// Copyright (c) 2021 Intel Corporation +// +// 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. + +package functions + +import ( + "testing" + + "github.com/edgexfoundry/app-functions-sdk-go/v2/appcontext" + "github.com/edgexfoundry/go-mod-core-contracts/v2/clients/logger" + "github.com/edgexfoundry/go-mod-core-contracts/v2/v2" + "github.com/edgexfoundry/go-mod-core-contracts/v2/v2/dtos" + "github.com/stretchr/testify/require" + + "github.com/google/uuid" + "github.com/stretchr/testify/assert" +) + +// This file contains example of how to unit test pipeline functions +// TODO: Change these sample unit tests to test your custom type and function(s) + +func TestSample_LogEventDetails(t *testing.T) { + expectedEvent := createTestEvent(t) + expectedContinuePipeline := true + + target := NewSample() + actualContinuePipeline, actualEvent := target.LogEventDetails(creatTestAppSdkContext(), expectedEvent) + + assert.Equal(t, expectedContinuePipeline, actualContinuePipeline) + assert.Equal(t, expectedEvent, actualEvent) +} + +func TestSample_ConvertEventToXML(t *testing.T) { + event := createTestEvent(t) + expectedXml, _ := event.ToXML() + expectedContinuePipeline := true + + target := NewSample() + actualContinuePipeline, actualXml := target.ConvertEventToXML(creatTestAppSdkContext(), event) + + assert.Equal(t, expectedContinuePipeline, actualContinuePipeline) + assert.Equal(t, expectedXml, actualXml) + +} + +func TestSample_OutputXML(t *testing.T) { + testEvent := createTestEvent(t) + expectedXml, _ := testEvent.ToXML() + expectedContinuePipeline := false + appContext := creatTestAppSdkContext() + + target := NewSample() + actualContinuePipeline, result := target.OutputXML(appContext, expectedXml) + actualXml := string(appContext.OutputData) + + assert.Equal(t, expectedContinuePipeline, actualContinuePipeline) + assert.Nil(t, result) + assert.Equal(t, expectedXml, actualXml) +} + +func createTestEvent(t *testing.T) dtos.Event { + deviceName := "MyDevice" + profileName := "MyProfile" + resourceName := "MyResource" + + event := dtos.NewEvent(profileName, deviceName) + err := event.AddSimpleReading(resourceName, v2.ValueTypeInt32, int32(1234)) + require.NoError(t, err) + + event.Tags = map[string]string{ + "WhereAmI": "NotKansas", + } + + return event +} + +func creatTestAppSdkContext() *appcontext.Context { + return &appcontext.Context{ + CorrelationID: uuid.New().String(), + LoggingClient: logger.NewMockClient(), + } +} diff --git a/app-service-template/go.mod b/app-service-template/go.mod new file mode 100644 index 000000000..1b623e513 --- /dev/null +++ b/app-service-template/go.mod @@ -0,0 +1,14 @@ +// TODO: Update the Attrbuition.txt file when adding/removing dependencies + +module new-app-service + +go 1.15 + +require ( + github.com/edgexfoundry/app-functions-sdk-go/v2 v2.0.0-dev.14 + github.com/edgexfoundry/go-mod-core-contracts/v2 v2.0.0-dev.23 + github.com/google/uuid v1.2.0 + github.com/stretchr/testify v1.7.0 +) + +replace github.com/edgexfoundry/app-functions-sdk-go/v2 => ../../app-functions-sdk-go diff --git a/app-service-template/main.go b/app-service-template/main.go new file mode 100644 index 000000000..10e7d2278 --- /dev/null +++ b/app-service-template/main.go @@ -0,0 +1,71 @@ +// TODO: Change Copyright to your company if open sourcing or remove header +// +// Copyright (c) 2021 Intel Corporation +// +// 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. +// + +package main + +import ( + "os" + + "github.com/edgexfoundry/app-functions-sdk-go/v2/appsdk" + "github.com/edgexfoundry/app-functions-sdk-go/v2/pkg/transforms" + + "new-app-service/functions" +) + +const ( + serviceKey = "new-app-service" +) + +func main() { + // TODO: See https://docs.edgexfoundry.org/1.3/microservices/application/ApplicationServices/ + // for documentation on application services. + + edgexSdk := &appsdk.AppFunctionsSDK{ServiceKey: serviceKey} + if err := edgexSdk.Initialize(); err != nil { + edgexSdk.LoggingClient.Errorf("SDK initialization failed: %s", err.Error()) + os.Exit(-1) + } + + // TODO: Replace with retrieving your custom ApplicationSettings from configuration + deviceNames, err := edgexSdk.GetAppSettingStrings("DeviceNames") + if err != nil { + edgexSdk.LoggingClient.Errorf("failed to retrieve DeviceNames from configuration: %s", err.Error()) + os.Exit(-1) + } + + // TODO: Replace below functions with built in and/or your custom functions for your use case. + // See https://docs.edgexfoundry.org/1.3/microservices/application/BuiltIn/ for list of built-in functions + sample := functions.NewSample() + err = edgexSdk.SetFunctionsPipeline( + transforms.NewFilterFor(deviceNames).FilterByDeviceName, + sample.LogEventDetails, + sample.ConvertEventToXML, + sample.OutputXML) + if err != nil { + edgexSdk.LoggingClient.Errorf("SetFunctionsPipeline returned error: %s", err.Error()) + os.Exit(-1) + } + + if err := edgexSdk.MakeItRun(); err != nil { + edgexSdk.LoggingClient.Errorf("MakeItRun returned error: %s", err.Error()) + os.Exit(-1) + } + + // TODO: Do any required cleanup here, if needed + + os.Exit(0) +} diff --git a/app-service-template/res/configuration.toml b/app-service-template/res/configuration.toml new file mode 100644 index 000000000..f0eb6cafc --- /dev/null +++ b/app-service-template/res/configuration.toml @@ -0,0 +1,103 @@ +# TODO: Go here for detailed information on Application Service configuation: +# https://docs.edgexfoundry.org/1.3/microservices/application/GeneralAppServiceConfig/ +[Writable] +LogLevel = 'INFO' + [Writable.StoreAndForward] + Enabled = false + RetryInterval = '5m' + MaxRetryCount = 10 + + # TODO: if not running in secure mode, but do have secrets then add them here. + [Writable.InsecureSecrets] + [Writable.InsecureSecrets.DB] + path = "redisdb" + [Writable.InsecureSecrets.DB.Secrets] + username = "" + password = "" + +[Service] +BootTimeout = '30s' +ClientMonitor = '15s' +CheckInterval = '10s' +Host = 'localhost' +Port = 49600 # TODO: set this port appropriately +Protocol = 'http' +ReadMaxLimit = 100 +StartupMsg = 'new-app-service Application Service has started' +Timeout = '30s' + +[Registry] +Host = 'localhost' +Port = 8500 +Type = 'consul' + +[Database] +Type = "redisdb" +Host = "localhost" +Port = 6379 +Timeout = "30s" + +# TODO: Determine if your service will use secrets in secure mode, i.e. Vault. +# if not this secion can be removed, but you must make sure EDGEX_SECURITY_SECRET_STORE is set to false +# Note is database is running in secure more and you have Store and Forward enable you will need to run this +# service in secure mode. +# For more deatils about SecretStore: https://docs.edgexfoundry.org/1.3/microservices/security/Ch-SecretStore/ +[SecretStore] +Host = 'localhost' +Port = 8200 +Path = '/v1/secret/edgex/appservice/' +Protocol = 'http' +RootCaCertPath = '' +ServerName = '' +TokenFile = '/vault/config/assets/resp-init.json' +AdditionalRetryAttempts = 10 +RetryWaitPeriod = "1s" + [SecretStore.Authentication] + AuthType = 'X-Vault-Token' + +[Clients] + [Clients.CoreData] + Protocol = 'http' + Host = 'localhost' + Port = 48080 + +[Binding] +Type="edgex-messagebus" +SubscribeTopics="events, edgex/events" +PublishTopic="event-xml" #TODO: remove if service is NOT publishing back to the message bus + +[MessageBus] +Type = 'zero' + [MessageBus.SubscribeHost] + Host = 'localhost' + Port = 5563 + Protocol = 'tcp' + [MessageBus.PublishHost] # TODO: Remove if service is NOT publishing back to the message bus + Host = '*' + Port = 5564 + Protocol = 'tcp' + +# TODO: If using mqtt messagebus, Uncomment this section and remove above [Binding] & [MessageBus], +# Otherwise remove this commentedout block +#[Binding] +#Type="edgex-messagebus" +#SubscribeTopics="events, edgex/events/#" +#PublishTopic="event-xml" # TODO: Remove if service is NOT publishing back to the message bus +# +#[MessageBus] +# Type = 'mqtt' +# [MessageBus.SubscribeHost] +# Host = 'localhost' +# Port = 1883 +# Protocol = 'tcp' +# [MessageBus.PublishHost] # TODO: Remove if service is NOT publishing back to the message bus +# Host = 'localhost' +# Port = 1883 +# Protocol = 'tcp' + +[ApplicationSettings] +# TODO: Add custom settings needed by your app service +# This can be any Key/Value pair you need. +# For more details see: https://docs.edgexfoundry.org/1.3/microservices/application/GeneralAppServiceConfig/#application-settings +# Example that works with devices from the Virtual Device service: +DeviceNames = "Random-Boolean-Device, Random-Integer-Device, Random-UnsignedInteger-Device, Random-Float-Device, Random-Binary-Device"