From 1b3752f9eedab71face5dc8f3636b977d9b9aed0 Mon Sep 17 00:00:00 2001 From: Siggi Skulason Date: Fri, 2 Jul 2021 13:55:23 +0100 Subject: [PATCH] feat: add support for common v2 endpoints (ping/metrics/version/config) This commit adds v2 (Ireland/Jakarta) support for four common endpoints: - version - metrics - ping - config For information about the available commands, use `edgex-cli -h` There are a number of ways to use the commands: 1. `edgex-cli version` displays version of each active microservice. The version commands just shows the version string. The other commands use a pretty-printed JSON structure. 2. `edgex-cli version -d` shows debug output - the URL used and the result 3. `edgex-cli version -j` gets the raw JSON output for one service (by default core-data) It's also possible to specify the service to use. : `-m/--metadata`, `-c/--command`, `-n/--notification`, `-s/--scheduler` or `--data` (which is the default). This can also be combined with `-j` or `-d` Example: - `$ edgex-cli metrics -j -m` - `$ edgex-cli ping -d --command` Help is available with ``` $ edgex-cli help $ edgex-cli version --help $ edgex-cli version --h ``` Signed-off-by: Siggi Skulason --- .gitignore | 3 +- Makefile | 21 +- README.md | 223 +--------- cmd/edgex-cli/main.go | 25 ++ cmd/root.go | 175 -------- cmd/status/status.go | 76 ---- cmd/version/version.go | 29 -- config/configuration.go | 114 ----- config/configuration_test.go | 123 ------ config/env.go | 43 -- config/mocks/Environment.go | 43 -- config/mocks/File.go | 265 ------------ config/mocks/Fs.go | 220 ---------- config/testdata/invalidConfig.toml | 2 - go.mod | 11 +- internal/cmd/common.go | 71 ++++ internal/cmd/config.go | 72 ++++ internal/cmd/metrics.go | 75 ++++ internal/cmd/ping.go | 69 +++ internal/cmd/root.go | 36 ++ internal/cmd/version.go | 72 ++++ internal/config/config.go | 67 +++ internal/service/service.go | 92 ++++ main.go | 21 - pkg/cmd/purge/coredata.go | 57 --- pkg/cmd/purge/interface.go | 5 - pkg/cmd/purge/metadata.go | 114 ----- pkg/cmd/purge/scheduler.go | 69 --- pkg/cmd/version/version.go | 32 -- pkg/confirmation/confirm.go | 74 ---- pkg/const.go | 5 - pkg/editor/interactive.go | 125 ------ pkg/formatters/emptyformatter.go | 20 - pkg/formatters/interface.go | 30 -- pkg/formatters/jsonformatter.go | 34 -- pkg/formatters/templateformatter.go | 56 --- pkg/pager/pager.go | 109 ----- pkg/request.go | 94 ----- pkg/request_test.go | 394 ------------------ pkg/utils/utils.go | 68 --- run_cmds.sh | 113 ----- samples/createDP.json | 54 --- samples/createDP.yaml | 133 ------ samples/createDevice.json | 27 -- samples/createInterval.json | 14 - samples/createNotification.json | 13 - samples/updateInterval.json | 14 - .../runtime-helpers/bin/edgex-cli-wrapper.sh | 7 - snap/snapcraft.yaml | 60 +-- version.go | 7 + 50 files changed, 616 insertions(+), 3060 deletions(-) create mode 100644 cmd/edgex-cli/main.go delete mode 100644 cmd/root.go delete mode 100644 cmd/status/status.go delete mode 100644 cmd/version/version.go delete mode 100644 config/configuration.go delete mode 100644 config/configuration_test.go delete mode 100644 config/env.go delete mode 100644 config/mocks/Environment.go delete mode 100644 config/mocks/File.go delete mode 100644 config/mocks/Fs.go delete mode 100644 config/testdata/invalidConfig.toml create mode 100644 internal/cmd/common.go create mode 100644 internal/cmd/config.go create mode 100644 internal/cmd/metrics.go create mode 100644 internal/cmd/ping.go create mode 100644 internal/cmd/root.go create mode 100644 internal/cmd/version.go create mode 100644 internal/config/config.go create mode 100644 internal/service/service.go delete mode 100644 main.go delete mode 100644 pkg/cmd/purge/coredata.go delete mode 100644 pkg/cmd/purge/interface.go delete mode 100644 pkg/cmd/purge/metadata.go delete mode 100644 pkg/cmd/purge/scheduler.go delete mode 100644 pkg/cmd/version/version.go delete mode 100644 pkg/confirmation/confirm.go delete mode 100644 pkg/const.go delete mode 100644 pkg/editor/interactive.go delete mode 100644 pkg/formatters/emptyformatter.go delete mode 100644 pkg/formatters/interface.go delete mode 100644 pkg/formatters/jsonformatter.go delete mode 100644 pkg/formatters/templateformatter.go delete mode 100644 pkg/pager/pager.go delete mode 100644 pkg/request.go delete mode 100644 pkg/request_test.go delete mode 100644 pkg/utils/utils.go delete mode 100755 run_cmds.sh delete mode 100644 samples/createDP.json delete mode 100644 samples/createDP.yaml delete mode 100644 samples/createDevice.json delete mode 100644 samples/createInterval.json delete mode 100644 samples/createNotification.json delete mode 100644 samples/updateInterval.json create mode 100644 version.go diff --git a/.gitignore b/.gitignore index 9f200546..91ba68a5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ -edgex-cli -edgex +edgex-cli go.sum coverage.out diff --git a/Makefile b/Makefile index 799747ca..ffed2818 100644 --- a/Makefile +++ b/Makefile @@ -15,23 +15,27 @@ ifndef GOBIN endif BINARY=edgex-cli - + VERSION=$(shell cat ./VERSION 2>/dev/null || echo 0.0.0) -GOFLAGS=-ldflags "-X github.com/edgexfoundry/edgex-cli/cmd/version.Version=$(VERSION)" +TIME=$(shell date) +GOFLAGS=-ldflags "-X 'github.com/edgexfoundry/edgex-cli.BuildVersion=$(VERSION)' -X 'github.com/edgexfoundry/edgex-cli.BuildTime=$(TIME)'" ARTIFACT_ROOT?=bin build: @echo "GOPATH=$(GOPATH)" - $(GO) build -o $(BINARY) $(GOFLAGS) + $(GO) build -o ${ARTIFACT_ROOT}/$(BINARY) $(GOFLAGS) ./cmd/edgex-cli + +tidy: + go mod tidy # initial impl. Feel free to override. Please keep ARTIFACT_ROOT coming from env though. CI/CD pipeline relies on this build-all: @echo "GOPATH=$(GOPATH)" - GOOS=linux GOARCH=amd64 $(GO) build -o ${ARTIFACT_ROOT}/$(BINARY)-linux-amd64 $(GOFLAGS) - GOOS=linux GOARCH=arm64 $(GO) build -o ${ARTIFACT_ROOT}/$(BINARY)-linux-arm64 $(GOFLAGS) - GOOS=darwin GOARCH=amd64 $(GO) build -o ${ARTIFACT_ROOT}/$(BINARY)-mac $(GOFLAGS) - GOOS=windows GOARCH=amd64 $(GO) build -o ${ARTIFACT_ROOT}/$(BINARY).exe $(GOFLAGS) + GOOS=linux GOARCH=amd64 $(GO) build -o ${ARTIFACT_ROOT}/$(BINARY)-linux-amd64 $(GOFLAGS) ./cmd/edgex-cli + GOOS=linux GOARCH=arm64 $(GO) build -o ${ARTIFACT_ROOT}/$(BINARY)-linux-arm64 $(GOFLAGS) ./cmd/edgex-cli + GOOS=darwin GOARCH=amd64 $(GO) build -o ${ARTIFACT_ROOT}/$(BINARY)-mac $(GOFLAGS) ./cmd/edgex-cli + GOOS=windows GOARCH=amd64 $(GO) build -o ${ARTIFACT_ROOT}/$(BINARY).exe $(GOFLAGS) ./cmd/edgex-cli tar -czvf ${ARTIFACT_ROOT}/$(BINARY)-linux-amd64-$(VERSION).tar.gz Attribution.txt LICENSE res/sample-configuration.toml ${ARTIFACT_ROOT}/$(BINARY)-linux-amd64 --transform 's/-linux-amd64//' tar -czvf ${ARTIFACT_ROOT}/$(BINARY)-linux-arm64-$(VERSION).tar.gz Attribution.txt LICENSE res/sample-configuration.toml ${ARTIFACT_ROOT}/$(BINARY)-linux-arm64 --transform 's/-linux-arm64//' @@ -49,9 +53,6 @@ test: install: @echo "GOBIN=$(GOBIN)" $(GO) install $(GOFLAGS) - mkdir -p $(HOME)/.edgex-cli - cp ./res/sample-configuration.toml $(HOME)/.edgex-cli/configuration.toml - @echo "Configuration file $(HOME)/.edgex-cli/configuration.toml created" uninstall: @echo "GOBIN=$(GOBIN)" diff --git a/README.md b/README.md index 05f1d742..c22a2722 100644 --- a/README.md +++ b/README.md @@ -5,225 +5,4 @@ A command line interface to interact with EdgeX microservices. Replaces the need to manually construct complex CURL commands and/or maintain developer scripts. -``` - ______ _ __ __ _____ _ _____ -| ____| | | \ \ / / / ____|| | |_ _| -| |__ __| | __ _ ___ \ V / ______ | | | | | | -| __| / _` | / _` | / _ \ > < |______| | | | | | | -| |____| (_| || (_| || __/ / . \ | |____ | |____ _| |_ -|______|\__,_| \__, | \___|/_/ \_\ \_____||______||_____| - __/ | - |___/ - -EdgeX CLI version: 0.0.1 -https://www.edgexfoundry.org/ - -Usage: - edgex-cli [command] - -Available Commands: - addressable Addressable command command - command `Command` command - db Purges entire EdgeX Database. [USE WITH CAUTION] - device Device command - deviceservice Device service command - event Event command - help Help about any command - interval Interval command - notification Notification command - profile Device profile command. - reading Reading command - status Checks the current status of each microservice. - subscription Subscription command - version Version command - -Flags: - --config-file string configuration file - -h, --help help for edgex-cli - --no-pager Do not pipe output into a pager. - -u, --url Print URL(s) used by the entered command. - -v, --verbose Print entire HTTP response. - -Use "edgex-cli [command] --help" for more information about a command. -``` - -## Installation - -In order to run this tool, you will need a **locally running EdgeX instance**, accessible via localhost, -and Go 1.12 or higher installed on your machine. - -* Clone the git repo: - -``` -$ git clone https://github.com/edgexfoundry/edgex-cli -``` - -* Change directory: - -``` -$ cd edgex-cli -``` - -* Install the CLI: - -``` -$ make install -``` -Install also makes a copy of the default configuration and copies it to $HOME/.edgex-cli/configuration.toml. -If your EdgeX instance is not running on localhost, minimally you will need to replace localhost with the correct IP address. -You can now use the CLI by entering `edgex-cli` anywhere on your machine provided your $GOBIN is on your $PATH. - - -#### Interactive Mode - -Some commands leverage interactive-mode which opens an editor and allows you to provide information that would -normally be difficult with just command line arguments. For example, creating an Event requires a lot of information, -also Events contain zero or more readings. Using interactive mode, you can easily create an Event with many readings and -customize each reading. You can choose the editor that is used by setting the environment variable `EDITOR`. The default -editor is `Vi` for `Unix` operating systems(MacOS, Linux, etc), `Notepad` for Windows OS. The default editor is used if -no `EDITOR` is specified. Some examples of editors: - -- vi -- vim -- nano -- emacs -- notepad -- vscode -- atom - - -### CLI Developers - -To try out your changes you have two options, one using 'make build', the other 'go run'. Also, we share how to launch tests. - -* Build and run: - -``` -$ make build -$ ./edgex-cli -``` - -* Use `go run`: - -``` -$ go run main.go [COMMAND] -``` -* Build artifacts and create tar files for different OS -``` -make build-all -``` -* Running tests: - -``` -$ make test -``` - -This will generate the file coverage.out in the repository root directory. To view the results, execute: - -``` -$ go tool cover -html=coverage.out -``` - -#### Code Organization -All CLI go code lives under the "cmd" directory. Its sub-directories map to the supported toplevel commands, such as -addressable, command, db, device, ... version. To obtain a full list of supported commands type 'edgex-cli --help'. - -Our convention has been to use the -f flag to pass in a file argument. -n is typically used to provide a name. -Both command specific and global flags exist. -Refer to the Cobra (https://godoc.org/github.com/spf13/cobra) and Viper (https://godoc.org/github.com/spf13/viper) documentation for additional help. - - -#### Sample Templates -The "samples" directory holds templates for device profile (createDP.json and yaml), -device (createDevice.toml), intervals (createInterval.toml and json), and for updateInterval. -CLI in Interactive mode opens the relevant template in the configured editor. - -Edit these should data structures change. -For legacy reasons we support multiple formats in the case of some objects. Going forward most likely only json format will be supported. - -## Supported commands and sub-commands - -``` - addressable - list A list of all addressable -``` -``` - command - get Issue GET command - list A list of device supported commands - put Issue PUT command -``` -``` - db - purge - Purges entire EdgeX Database. [USE WITH CAUTION] -``` - -``` - device - add Add devices - adminstate Device admin state - list A list of all devices - operstate Update deviceName operating state - rm Removes device by name or ID - update Update a device -``` - -``` - deviceservice - add Add a device service - list Lists existing devices services - rm Removes device service by name or ID - update Update device service -``` -``` - event - add Create an event - count Returns the count of core-data events - list A list of Events - rm Removes event by its id or removes all events generated by given device - scrub Remove all (pushed) events and their associated readings [USE WITH CAUTION] - ``` -``` - interval - add Add interval - list A list of all intervals - rm Removes interval by name or id - update Update interval - -``` -``` - notification - add Add notification - list A list of all notifications - rm Removes notification by slug or age -``` -``` - profile - add Add profiles - list Returns a list of device profiles - rm Remove profile by name or ID - update Update device profile -``` -``` - reading - count Returns the count of core-data readings - list A list of readings across devices or pertaining to a specified device -``` -``` - status Checks the current status of each microservice -``` -``` - subscription - add Add subscription - list A list of all subscriptions - rm Removes subscription by --slug or id. -``` -``` - version Version command -``` -``` - watcher - add Add watcher(s) - list A list of watchers - rm Remove watcher(s) by ID(s) -``` +`edgex-cli` is being updated to support the V2 API and the documentation will be updated in due course. See the [EdgeXFoundry CLI documentation](https://docs.edgexfoundry.org/1.3/getting-started/tools/Ch-CommandLineInterface/) for more information about the V1.3 (Hanoi) client. \ No newline at end of file diff --git a/cmd/edgex-cli/main.go b/cmd/edgex-cli/main.go new file mode 100644 index 00000000..540905b1 --- /dev/null +++ b/cmd/edgex-cli/main.go @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2021 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 ( + "github.com/edgexfoundry/edgex-cli/internal/cmd" +) + +func main() { + cmd.Execute() +} diff --git a/cmd/root.go b/cmd/root.go deleted file mode 100644 index 6f907bba..00000000 --- a/cmd/root.go +++ /dev/null @@ -1,175 +0,0 @@ -// Copyright © 2019 VMware, INC -// -// 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 cmd - -import ( - "context" - "fmt" - "io" - "os" - - // TODO (jpw) - put back in - // "github.com/edgexfoundry/edgex-cli/cmd/addressable" - // "github.com/edgexfoundry/edgex-cli/cmd/command" - // "github.com/edgexfoundry/edgex-cli/cmd/db" - // "github.com/edgexfoundry/edgex-cli/cmd/device" - // "github.com/edgexfoundry/edgex-cli/cmd/deviceservice" - // "github.com/edgexfoundry/edgex-cli/cmd/event" - // "github.com/edgexfoundry/edgex-cli/cmd/interval" - // "github.com/edgexfoundry/edgex-cli/cmd/notification" - // "github.com/edgexfoundry/edgex-cli/cmd/profile" - // "github.com/edgexfoundry/edgex-cli/cmd/reading" - "github.com/edgexfoundry/edgex-cli/cmd/status" - "github.com/edgexfoundry/edgex-cli/config" - "github.com/edgexfoundry/edgex-cli/pkg/pager" - - // "github.com/edgexfoundry/edgex-cli/cmd/subscription" - "github.com/edgexfoundry/edgex-cli/cmd/version" - // "github.com/edgexfoundry/edgex-cli/cmd/watcher" - // "github.com/edgexfoundry/edgex-cli/config" - // "github.com/edgexfoundry/edgex-cli/pkg/pager" - - "github.com/edgexfoundry/go-mod-core-contracts/v2/clients" - - "github.com/google/uuid" - "github.com/spf13/cobra" - "github.com/spf13/viper" -) - -// NewCommand returns rootCmd which represents the base command when called without any subcommands -func NewCommand() *cobra.Command { - cmd := &cobra.Command{ - PersistentPreRun: func(cmd *cobra.Command, args []string) { - // set flags - noPager, err := cmd.Flags().GetBool("no-pager") - if err != nil { - fmt.Println("couldn't get no-pager flag") - } - - verbose, err := cmd.Flags().GetBool("verbose") - if err != nil { - fmt.Println("couldn't get verbose flag") - } - viper.Set("verbose", verbose) - if verbose { - noPager = true - } - - url, err := cmd.Flags().GetBool("url") - if err != nil { - fmt.Println("couldn't get url flag") - } - viper.Set("url", url) - - configFile, err := cmd.Flags().GetString("config-file") - if err != nil { - fmt.Println("couldn't get config-file flag") - } - viper.Set("config-file", configFile) - - viper.Set("writer", os.Stdout) - if !noPager { - w, err := pager.NewWriter() - if err == nil { - viper.Set("writer", w) - viper.Set("writerShouldClose", true) // This flag prevents us from calling close on stdout - } - } - - }, - SilenceUsage: true, - Use: "edgex-cli", - Short: "EdgeX command line interface", - Long: "\n" + - - " ______ _ __ __ _____ _ _____ \n" + - "| ____| | | \\ \\ / / / ____|| | |_ _| \n" + - "| |__ __| | __ _ ___ \\ V / ______ | | | | | | \n" + - "| __| / _` | / _` | / _ \\ > < |______| | | | | | | \n" + - "| |____| (_| || (_| || __/ / . \\ | |____ | |____ _| |_ \n" + - "|______|\\__,_| \\__, | \\___|/_/ \\_\\ \\_____||______||_____| \n" + - " __/ | \n" + - " |___/ \n" + - - ` -EdgeX CLI version: ` + version.Version + - ` -https://www.edgexfoundry.org/ - `, - } - - // TODO (jpw) - put back in - // Add all subcommands below: - // cmd.AddCommand(device.NewCommand()) - // cmd.AddCommand(deviceservice.NewCommand()) - // cmd.AddCommand(profile.NewCommand()) - // cmd.AddCommand(event.NewCommand()) - // cmd.AddCommand(reading.NewCommand()) - cmd.AddCommand(status.NewCommand()) - // cmd.AddCommand(db.NewCommand()) - // cmd.AddCommand(addressable.NewCommand()) - // cmd.AddCommand(command.NewCommand()) - // --- Support Services Commands --- - // cmd.AddCommand(notification.NewCommand()) - // cmd.AddCommand(subscription.NewCommand()) - // cmd.AddCommand(interval.NewCommand()) - // cmd.AddCommand(watcher.NewCommand()) - cmd.AddCommand(version.NewCommand()) - - // global flags - Verbose := false - URL := false - NoPager := false - Configfile := "" - // get flags values - cmd.PersistentFlags().BoolVarP(&URL, "url", "u", false, "Print URL(s) used by the entered command.") - cmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "Print request body & response body.") - cmd.PersistentFlags().BoolVarP(&NoPager, "no-pager", "", false, "Do not pipe output into a pager.") - cmd.PersistentFlags().StringVar(&Configfile, "config-file", "", "configuration file") - return cmd -} - -// Execute adds all child commands to the root command and sets flags appropriately. -// This is called by main.main(). It only needs to happen once to the rootCmd. -func Execute() { - //var ConfigFile string - //flag.StringVar(&ConfigFile, "config-file", config.DefaultConfigFile, "Specify local configuration directory") - var env = config.NewViperEnv() - // should we really be reading config file later? when does the cmd persistent flags get read .. after that to pull in any config-file flag argument? - env.SetConfigFile(config.DefaultConfigFile) - if err := config.LoadConfig(env); err != nil { - os.Exit(1) - } - - ctx := context.WithValue(context.Background(), clients.CorrelationHeader, uuid.New().String()) - if err := NewCommand().ExecuteContext(ctx); err != nil { - defer os.Exit(1) - } - defer closeWriter() -} - -//closeWriter function will be executed first and then os.Exit(1) will be executed in case of err -func closeWriter() { - shouldClose := viper.GetBool("writerShouldClose") - if shouldClose { - pw := viper.Get("writer").(io.Closer) - if pw != os.Stdout { - err := pw.Close() - if err != nil { - _ = fmt.Errorf(err.Error()) - } - } - } -} diff --git a/cmd/status/status.go b/cmd/status/status.go deleted file mode 100644 index 56aa8872..00000000 --- a/cmd/status/status.go +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright © 2019 VMware, INC -// -// 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 status - -import ( - "fmt" - "io/ioutil" - "net/http" - - "github.com/edgexfoundry/edgex-cli/config" - "github.com/edgexfoundry/edgex-cli/pkg/formatters" - - "github.com/edgexfoundry/go-mod-core-contracts/v2/clients" - - "github.com/spf13/cobra" -) - -const ( - NotConnected = "Not Connected" - OK = "Ok" -) - -const statusTemplate = "Service Name\tURL\tStatus\n" + - "{{range .}}" + - "{{.Name}}\t{{.Url}}\t{{.Status}}\n" + - "{{end}}" - -// NewCommand returns the status command -func NewCommand() *cobra.Command { - cmd := &cobra.Command{ - Use: "status", - Short: "Checks the current status of each microservice", - Long: fmt.Sprintf(`Status - -This command pings each edgex microservice defined in CLI '%s' file placed within '$HOME/.edgex-cli' directory and prints their status. -`, config.ConfigFileName), - Run: func(cmd *cobra.Command, args []string) { - var clientStatuses []clientStatus - for clientName, client := range config.Conf.Clients { - resp, err := http.Get(client.Url() + clients.ApiPingRoute) - if err != nil { - clientStatuses = append(clientStatuses, clientStatus{clientName, client.Url(), NotConnected}) - } else { - data, err := ioutil.ReadAll(resp.Body) - if err != nil { - clientStatuses = append(clientStatuses, clientStatus{clientName, client.Url(), fmt.Sprintf("%s \t Unexpected error: %v\n", clientName, err)}) - } - if string(data) == "pong" { - clientStatuses = append(clientStatuses, clientStatus{clientName, client.Url(), OK}) - } - resp.Body.Close() - } - } - formatters.NewHtmlTemplateFormatter(statusTemplate, nil).Write(clientStatuses) - }, - } - return cmd -} - -type clientStatus struct { - Name string - Url string - Status string -} diff --git a/cmd/version/version.go b/cmd/version/version.go deleted file mode 100644 index c12b926b..00000000 --- a/cmd/version/version.go +++ /dev/null @@ -1,29 +0,0 @@ -package version - -import ( - "github.com/edgexfoundry/edgex-cli/pkg/cmd/version" - - "github.com/spf13/cobra" -) - -var Version = "dev" - -// NewCommand returns the version command -func NewCommand() *cobra.Command { - var cmd = &cobra.Command{ - Use: "version", - Short: "Version command", - Long: `Outputs the current versions of EdgeX CLI and EdgeX Foundry.`, - RunE: func(cmd *cobra.Command, args []string) (err error) { - cmd.Println("EdgeX CLI version: ", Version) - edgeXVersion, err := version.GetEdgeXVersion() - if err != nil { - return err - } - cmd.Println("EdgeX Foundry version: ", edgeXVersion.Version) - return - }, - } - - return cmd -} diff --git a/config/configuration.go b/config/configuration.go deleted file mode 100644 index 71b97166..00000000 --- a/config/configuration.go +++ /dev/null @@ -1,114 +0,0 @@ -/******************************************************************************* - * Copyright 2019 VMware Inc. - * - * 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 config - -import ( - "errors" - "fmt" - "github.com/BurntSushi/toml" - "io" - "os" - "path/filepath" -) - -const PathId = "/id/" -const PathName = "/name/" -const SampleConfigFileName = "sample-configuration.toml" -const ConfigFileName = "configuration.toml" - -var DefaultConfigFile = filepath.Join(os.Getenv("HOME"), ".edgex-cli", ConfigFileName) -var Conf Configuration - -// Configuration struct will use this to write config file eventually -type Configuration struct { - Clients ClientInfo -} - -type ClientInfo map[string]Client - -type Client struct { - Host string - Protocol string - Port int -} - -func (c Client) Url() string { - url := fmt.Sprintf("%s://%s:%v", c.Protocol, c.Host, c.Port) - return url -} - -// Security struct for security related config -type Security struct { - Enabled bool - Token string -} - -func LoadConfig(env Environment) error { - var configFilePath string - if env.IsSet("config-file") { - configFilePath = env.GetString("config-file") - } else { - configFilePath = DefaultConfigFile - } - - _, err := os.Stat(configFilePath) - //Relative path differs depending on if the application is run from IDE or from distributed archive - if os.IsNotExist(err) { - _, err := copy("../res/"+SampleConfigFileName, configFilePath) - if err != nil { - _, err1 := copy("./res/"+SampleConfigFileName, configFilePath) - if err1 != nil { - fmt.Printf("failed to create configuration file '%s'. \n "+ - "%s\n %s\n ", configFilePath, err, err1) - return errors.New(err.Error()) - } - } - fmt.Printf("Configuration file %s created\n", configFilePath) - } - - if _, err := toml.DecodeFile(configFilePath, &Conf); err != nil { - fmt.Printf("Error occurred while parsing %s: %s", configFilePath, err) - return errors.New(err.Error()) - } - return nil -} - -func copy(src, dst string) (int64, error) { - sourceFileStat, err := os.Stat(src) - if err != nil { - return 0, err - } - - if !sourceFileStat.Mode().IsRegular() { - return 0, fmt.Errorf("%s is not a regular file", src) - } - - source, err := os.Open(src) - if err != nil { - return 0, err - } - defer source.Close() - - if _, err := os.Stat(dst); os.IsNotExist(err) { - os.MkdirAll(filepath.Dir(dst), 0744) - } - destination, err := os.Create(dst) - if err != nil { - return 0, err - } - defer destination.Close() - nBytes, err := io.Copy(destination, source) - return nBytes, err -} diff --git a/config/configuration_test.go b/config/configuration_test.go deleted file mode 100644 index 9ecce18d..00000000 --- a/config/configuration_test.go +++ /dev/null @@ -1,123 +0,0 @@ -/******************************************************************************* - * Copyright 2019 VMware Inc. - * - * 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 config - -import ( - "errors" - "fmt" - "github.com/stretchr/testify/mock" - "os" - "path/filepath" - "reflect" - "testing" - - "github.com/edgexfoundry/edgex-cli/config/mocks" -) - -var workDir, _ = os.Getwd() -var ValidConfigFile = filepath.Join(workDir, "..", "res", "sample-configuration.toml") -var NonExistentConfigFile = filepath.Join(workDir, "testdata", "nonExistentConfig.toml") -var InvalidTomlConfigFile = filepath.Join(workDir, "testdata", "invalidConfig.toml") -var Error = errors.New("test error") - -var TestConf = Configuration{ - Clients: ClientInfo{ - "Clients.Metadata": Client{Host: "localhost", Protocol: "http", Port: 48081}, - "Clients.CoreData": Client{Host: "localhost", Protocol: "http", Port: 48080}, - "Clients.Scheduler": Client{Host: "localhost", Protocol: "http", Port: 48085}, - "Clients.Notification": Client{Host: "localhost", Protocol: "http", Port: 48060}, - "Clients.Logging": Client{Host: "localhost", Protocol: "http", Port: 48061}, - }, -} - -func TestGetConfig(t *testing.T) { - tests := []struct { - name string - env Environment - configFilePath string - //result Configuration - expectError bool - expectedErrorType error - }{ - { - name: "Successful GetDefaultConfig", - env: getDefaultConfigFileMockEnvSuccess(), - configFilePath: DefaultConfigFile, - expectError: false, - expectedErrorType: nil, - }, - { - name: "Successful GetConfig", - env: getConfigFileMockEnvSuccess(), - configFilePath: ValidConfigFile, - expectError: false, - expectedErrorType: nil, - }, - { - name: "Unsuccessful decode Config", - env: getConfigFileMockDecodeError(), - configFilePath: InvalidTomlConfigFile, - expectError: true, - expectedErrorType: Error, - }, - } - - for _, test := range tests { - t.Run(test.name, func(t *testing.T) { - var err = LoadConfig(test.env) - fmt.Println(err) - if test.expectError && err == nil { - t.Error("We expected an error but did not get one") - } - - if !test.expectError && err != nil { - t.Errorf("We did not expect an error but got one. %s", err.Error()) - } - - if test.expectError { - eet := reflect.TypeOf(test.expectedErrorType) - aet := reflect.TypeOf(err) - if !aet.AssignableTo(eet) { - t.Errorf("Expected error of type %v, but got an error of type %v", eet, aet) - } - } - }) - } -} - -func getDefaultConfigFileMockEnvSuccess() Environment { - dbMock := mocks.Environment{} - dbMock.On("SetConfigFile", DefaultConfigFile).Return(nil) - dbMock.On("GetString", "config-file").Return(DefaultConfigFile) - dbMock.On("IsSet", mock.Anything).Return(false) - return &dbMock -} -func getConfigFileMockEnvSuccess() Environment { - dbMock := mocks.Environment{} - - dbMock.On("SetConfigFile", ValidConfigFile).Return(nil) - dbMock.On("GetString", "config-file").Return(ValidConfigFile) - dbMock.On("IsSet", mock.Anything).Return(true) - return &dbMock -} - -func getConfigFileMockDecodeError() Environment { - dbMock := mocks.Environment{} - - dbMock.On("SetConfigFile", InvalidTomlConfigFile).Return(nil) - dbMock.On("GetString", "config-file").Return(InvalidTomlConfigFile) - dbMock.On("IsSet", mock.Anything).Return(true) - return &dbMock -} diff --git a/config/env.go b/config/env.go deleted file mode 100644 index 0a22b138..00000000 --- a/config/env.go +++ /dev/null @@ -1,43 +0,0 @@ -/******************************************************************************* - * Copyright 2019 VMware Inc. - * - * 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 config - -import ( - "github.com/spf13/viper" -) - -type Environment interface { - SetConfigFile(string) - GetString(string) string - IsSet(string) bool -} - -type ViperEnv struct{} - -func (ViperEnv) SetConfigFile(configFilePath string) { - viper.SetConfigFile(configFilePath) -} - -func (ViperEnv) IsSet(aStr string) bool { - return viper.IsSet(aStr) -} - -func (ViperEnv) GetString(key string) string { - return viper.GetString(key) -} - -func NewViperEnv() Environment { - return ViperEnv{} -} diff --git a/config/mocks/Environment.go b/config/mocks/Environment.go deleted file mode 100644 index 57e2a21a..00000000 --- a/config/mocks/Environment.go +++ /dev/null @@ -1,43 +0,0 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. - -package mocks - -import mock "github.com/stretchr/testify/mock" - -// Environment is an autogenerated mock type for the Environment type -type Environment struct { - mock.Mock -} - -// GetString provides a mock function with given fields: _a0 -func (_m *Environment) GetString(_a0 string) string { - ret := _m.Called(_a0) - - var r0 string - if rf, ok := ret.Get(0).(func(string) string); ok { - r0 = rf(_a0) - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// IsSet provides a mock function with given fields: _a0 -func (_m *Environment) IsSet(_a0 string) bool { - ret := _m.Called(_a0) - - var r0 bool - if rf, ok := ret.Get(0).(func(string) bool); ok { - r0 = rf(_a0) - } else { - r0 = ret.Get(0).(bool) - } - - return r0 -} - -// SetConfigFile provides a mock function with given fields: _a0 -func (_m *Environment) SetConfigFile(_a0 string) { - _m.Called(_a0) -} diff --git a/config/mocks/File.go b/config/mocks/File.go deleted file mode 100644 index e67e0aef..00000000 --- a/config/mocks/File.go +++ /dev/null @@ -1,265 +0,0 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. - -package mocks - -import ( - os "os" - - mock "github.com/stretchr/testify/mock" -) - -// File is an autogenerated mock type for the File type -type File struct { - mock.Mock -} - -// Close provides a mock function with given fields: -func (_m *File) Close() error { - ret := _m.Called() - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Name provides a mock function with given fields: -func (_m *File) Name() string { - ret := _m.Called() - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// Read provides a mock function with given fields: p -func (_m *File) Read(p []byte) (int, error) { - ret := _m.Called(p) - - var r0 int - if rf, ok := ret.Get(0).(func([]byte) int); ok { - r0 = rf(p) - } else { - r0 = ret.Get(0).(int) - } - - var r1 error - if rf, ok := ret.Get(1).(func([]byte) error); ok { - r1 = rf(p) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// ReadAt provides a mock function with given fields: p, off -func (_m *File) ReadAt(p []byte, off int64) (int, error) { - ret := _m.Called(p, off) - - var r0 int - if rf, ok := ret.Get(0).(func([]byte, int64) int); ok { - r0 = rf(p, off) - } else { - r0 = ret.Get(0).(int) - } - - var r1 error - if rf, ok := ret.Get(1).(func([]byte, int64) error); ok { - r1 = rf(p, off) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Readdir provides a mock function with given fields: count -func (_m *File) Readdir(count int) ([]os.FileInfo, error) { - ret := _m.Called(count) - - var r0 []os.FileInfo - if rf, ok := ret.Get(0).(func(int) []os.FileInfo); ok { - r0 = rf(count) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]os.FileInfo) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(int) error); ok { - r1 = rf(count) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Readdirnames provides a mock function with given fields: n -func (_m *File) Readdirnames(n int) ([]string, error) { - ret := _m.Called(n) - - var r0 []string - if rf, ok := ret.Get(0).(func(int) []string); ok { - r0 = rf(n) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).([]string) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(int) error); ok { - r1 = rf(n) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Seek provides a mock function with given fields: offset, whence -func (_m *File) Seek(offset int64, whence int) (int64, error) { - ret := _m.Called(offset, whence) - - var r0 int64 - if rf, ok := ret.Get(0).(func(int64, int) int64); ok { - r0 = rf(offset, whence) - } else { - r0 = ret.Get(0).(int64) - } - - var r1 error - if rf, ok := ret.Get(1).(func(int64, int) error); ok { - r1 = rf(offset, whence) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Stat provides a mock function with given fields: -func (_m *File) Stat() (os.FileInfo, error) { - ret := _m.Called() - - var r0 os.FileInfo - if rf, ok := ret.Get(0).(func() os.FileInfo); ok { - r0 = rf() - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(os.FileInfo) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func() error); ok { - r1 = rf() - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Sync provides a mock function with given fields: -func (_m *File) Sync() error { - ret := _m.Called() - - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Truncate provides a mock function with given fields: size -func (_m *File) Truncate(size int64) error { - ret := _m.Called(size) - - var r0 error - if rf, ok := ret.Get(0).(func(int64) error); ok { - r0 = rf(size) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Write provides a mock function with given fields: p -func (_m *File) Write(p []byte) (int, error) { - ret := _m.Called(p) - - var r0 int - if rf, ok := ret.Get(0).(func([]byte) int); ok { - r0 = rf(p) - } else { - r0 = ret.Get(0).(int) - } - - var r1 error - if rf, ok := ret.Get(1).(func([]byte) error); ok { - r1 = rf(p) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// WriteAt provides a mock function with given fields: p, off -func (_m *File) WriteAt(p []byte, off int64) (int, error) { - ret := _m.Called(p, off) - - var r0 int - if rf, ok := ret.Get(0).(func([]byte, int64) int); ok { - r0 = rf(p, off) - } else { - r0 = ret.Get(0).(int) - } - - var r1 error - if rf, ok := ret.Get(1).(func([]byte, int64) error); ok { - r1 = rf(p, off) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// WriteString provides a mock function with given fields: s -func (_m *File) WriteString(s string) (int, error) { - ret := _m.Called(s) - - var r0 int - if rf, ok := ret.Get(0).(func(string) int); ok { - r0 = rf(s) - } else { - r0 = ret.Get(0).(int) - } - - var r1 error - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(s) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} diff --git a/config/mocks/Fs.go b/config/mocks/Fs.go deleted file mode 100644 index f7ae97df..00000000 --- a/config/mocks/Fs.go +++ /dev/null @@ -1,220 +0,0 @@ -// Code generated by mockery v1.0.0. DO NOT EDIT. - -package mocks - -import ( - os "os" - time "time" - - afero "github.com/spf13/afero" - mock "github.com/stretchr/testify/mock" -) - -// Fs is an autogenerated mock type for the Fs type -type Fs struct { - mock.Mock -} - -// Chmod provides a mock function with given fields: name, mode -func (_m *Fs) Chmod(name string, mode os.FileMode) error { - ret := _m.Called(name, mode) - - var r0 error - if rf, ok := ret.Get(0).(func(string, os.FileMode) error); ok { - r0 = rf(name, mode) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Chtimes provides a mock function with given fields: name, atime, mtime -func (_m *Fs) Chtimes(name string, atime time.Time, mtime time.Time) error { - ret := _m.Called(name, atime, mtime) - - var r0 error - if rf, ok := ret.Get(0).(func(string, time.Time, time.Time) error); ok { - r0 = rf(name, atime, mtime) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Create provides a mock function with given fields: name -func (_m *Fs) Create(name string) (afero.File, error) { - ret := _m.Called(name) - - var r0 afero.File - if rf, ok := ret.Get(0).(func(string) afero.File); ok { - r0 = rf(name) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(afero.File) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(name) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Mkdir provides a mock function with given fields: name, perm -func (_m *Fs) Mkdir(name string, perm os.FileMode) error { - ret := _m.Called(name, perm) - - var r0 error - if rf, ok := ret.Get(0).(func(string, os.FileMode) error); ok { - r0 = rf(name, perm) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// MkdirAll provides a mock function with given fields: path, perm -func (_m *Fs) MkdirAll(path string, perm os.FileMode) error { - ret := _m.Called(path, perm) - - var r0 error - if rf, ok := ret.Get(0).(func(string, os.FileMode) error); ok { - r0 = rf(path, perm) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Name provides a mock function with given fields: -func (_m *Fs) Name() string { - ret := _m.Called() - - var r0 string - if rf, ok := ret.Get(0).(func() string); ok { - r0 = rf() - } else { - r0 = ret.Get(0).(string) - } - - return r0 -} - -// Open provides a mock function with given fields: name -func (_m *Fs) Open(name string) (afero.File, error) { - ret := _m.Called(name) - - var r0 afero.File - if rf, ok := ret.Get(0).(func(string) afero.File); ok { - r0 = rf(name) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(afero.File) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(name) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// OpenFile provides a mock function with given fields: name, flag, perm -func (_m *Fs) OpenFile(name string, flag int, perm os.FileMode) (afero.File, error) { - ret := _m.Called(name, flag, perm) - - var r0 afero.File - if rf, ok := ret.Get(0).(func(string, int, os.FileMode) afero.File); ok { - r0 = rf(name, flag, perm) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(afero.File) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(string, int, os.FileMode) error); ok { - r1 = rf(name, flag, perm) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} - -// Remove provides a mock function with given fields: name -func (_m *Fs) Remove(name string) error { - ret := _m.Called(name) - - var r0 error - if rf, ok := ret.Get(0).(func(string) error); ok { - r0 = rf(name) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// RemoveAll provides a mock function with given fields: path -func (_m *Fs) RemoveAll(path string) error { - ret := _m.Called(path) - - var r0 error - if rf, ok := ret.Get(0).(func(string) error); ok { - r0 = rf(path) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Rename provides a mock function with given fields: oldname, newname -func (_m *Fs) Rename(oldname string, newname string) error { - ret := _m.Called(oldname, newname) - - var r0 error - if rf, ok := ret.Get(0).(func(string, string) error); ok { - r0 = rf(oldname, newname) - } else { - r0 = ret.Error(0) - } - - return r0 -} - -// Stat provides a mock function with given fields: name -func (_m *Fs) Stat(name string) (os.FileInfo, error) { - ret := _m.Called(name) - - var r0 os.FileInfo - if rf, ok := ret.Get(0).(func(string) os.FileInfo); ok { - r0 = rf(name) - } else { - if ret.Get(0) != nil { - r0 = ret.Get(0).(os.FileInfo) - } - } - - var r1 error - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(name) - } else { - r1 = ret.Error(1) - } - - return r0, r1 -} diff --git a/config/testdata/invalidConfig.toml b/config/testdata/invalidConfig.toml deleted file mode 100644 index ff3f1d24..00000000 --- a/config/testdata/invalidConfig.toml +++ /dev/null @@ -1,2 +0,0 @@ - -Hello world! \ No newline at end of file diff --git a/go.mod b/go.mod index 5d85f943..b8b8494f 100644 --- a/go.mod +++ b/go.mod @@ -1,17 +1,8 @@ module github.com/edgexfoundry/edgex-cli require ( - github.com/BurntSushi/toml v0.3.1 - github.com/edgexfoundry/go-mod-core-contracts/v2 v2.0.0-dev.89 - github.com/google/uuid v1.2.0 - github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect - github.com/pelletier/go-toml v1.8.1 // indirect - github.com/spf13/afero v1.5.1 + github.com/edgexfoundry/go-mod-core-contracts/v2 v2.0.0 github.com/spf13/cobra v0.0.7 - github.com/spf13/viper v1.7.1 - github.com/stretchr/objx v0.2.0 // indirect - github.com/stretchr/testify v1.7.0 - gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect ) go 1.16 diff --git a/internal/cmd/common.go b/internal/cmd/common.go new file mode 100644 index 00000000..aa0a7824 --- /dev/null +++ b/internal/cmd/common.go @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2021 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 cmd + +import ( + "github.com/edgexfoundry/edgex-cli/internal/config" + "github.com/edgexfoundry/edgex-cli/internal/service" + "github.com/edgexfoundry/go-mod-core-contracts/v2/common" + "github.com/spf13/cobra" +) + +var verbose, metadata, data, command, notifications, scheduler, json bool + +func getSelectedServiceKey() string { + if metadata { + return common.CoreMetaDataServiceKey + } else if data { + return common.CoreDataServiceKey + } else if command { + return common.CoreCommandServiceKey + } else if notifications { + return common.SupportNotificationsServiceKey + } else if scheduler { + return common.SupportSchedulerServiceKey + } else { + return "" + } +} + +func getSelectedServices() map[string]service.Service { + key := getSelectedServiceKey() + if key == "" { + if json { + key = common.CoreMetaDataServiceKey + } else { + return config.GetCoreServices() + } + } + return map[string]service.Service{key: config.GetCoreService(key)} + +} + +func addFormatFlags(cmd *cobra.Command) { + cmd.Flags().BoolVarP(&json, "json", "j", false, "show the raw JSON response") + cmd.Flags().BoolVarP(&verbose, "debug", "d", false, "show verbose/debug output") + +} + +func addStandardFlags(cmd *cobra.Command) { + addFormatFlags(cmd) + cmd.Flags().BoolVarP(&data, "data", "", false, "use core-data service endpoint") + cmd.Flags().BoolVarP(&command, "command", "c", false, "use core-command service endpoint") + cmd.Flags().BoolVarP(&metadata, "metadata", "m", false, "use core-metadata service endpoint") + cmd.Flags().BoolVarP(&scheduler, "scheduler", "s", false, "use support-scheduler service endpoint") + cmd.Flags().BoolVarP(¬ifications, "notifications", "n", false, "use support-notifications service endpoint") + +} diff --git a/internal/cmd/config.go b/internal/cmd/config.go new file mode 100644 index 00000000..44441104 --- /dev/null +++ b/internal/cmd/config.go @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 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 cmd + +import ( + jsonpkg "encoding/json" + + "github.com/spf13/cobra" +) + +func init() { + var cmd = &cobra.Command{ + Use: "config", + Short: "Returns the current configuration of all EdgeX core/support microservices", + Long: ``, + + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) (err error) { + err = showConfig(cmd) + return err + }} + + rootCmd.AddCommand(cmd) + addStandardFlags(cmd) + +} + +func showConfig(cmd *cobra.Command) error { + services := getSelectedServices() + + for serviceName, service := range services { + jsonValue, url, err := service.GetConfigJSON() + if err != nil { + if json { + return err + } else if verbose { + cmd.Printf("%s: %s: %s\n", serviceName, url, err.Error()) + } + } else { + if json { + cmd.Println(jsonValue) + } else if verbose { + cmd.Printf("%s: %s: %s\n", serviceName, url, jsonValue) + } else { + cmd.Println(serviceName + ":") + var result map[string]interface{} + jsonpkg.Unmarshal([]byte(jsonValue), &result) + b, err := jsonpkg.MarshalIndent(result["config"], "", " ") + if err != nil { + return err + } + cmd.Println(string(b)) + } + } + } + + return nil +} diff --git a/internal/cmd/metrics.go b/internal/cmd/metrics.go new file mode 100644 index 00000000..ca8ddde7 --- /dev/null +++ b/internal/cmd/metrics.go @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2021 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 cmd + +import ( + "fmt" + "os" + "text/tabwriter" + + "github.com/spf13/cobra" +) + +func init() { + var cmd = &cobra.Command{ + Use: "metrics", + Short: "Outputs the CPU/memory usage stats for all EdgeX core/support microservices", + Long: ``, + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) (err error) { + + err = showMetrics(cmd) + return err + }} + + rootCmd.AddCommand(cmd) + addStandardFlags(cmd) + +} + +func showMetrics(cmd *cobra.Command) error { + services := getSelectedServices() + + if json || verbose { + for serviceName, service := range services { + jsonValue, url, err := service.GetMetricsJSON() + if err != nil { + return err + } else { + if json { + cmd.Println(jsonValue) + } else { + cmd.Printf("%s: %s: %s\n", serviceName, url, jsonValue) + } + } + } + } else { + w := tabwriter.NewWriter(os.Stdout, 1, 1, 1, ' ', 0) + fmt.Fprintln(w, "Service\tCpuBusyAvg\tMemAlloc\tMemFrees\tMemLiveObjects\tMemMallocs\tMemSys\tMemTotalAlloc") + for serviceName, service := range services { + metrics, err := service.GetMetrics() + if err == nil { + fmt.Fprintf(w, "%s\t%v\t%v\t%v\t%v\t%v\t%v\t%v\n", serviceName, metrics.CpuBusyAvg, metrics.MemAlloc, metrics.MemFrees, + metrics.MemLiveObjects, metrics.MemMallocs, metrics.MemSys, metrics.MemTotalAlloc) + + } + } + w.Flush() + + } + return nil +} diff --git a/internal/cmd/ping.go b/internal/cmd/ping.go new file mode 100644 index 00000000..4e1b8921 --- /dev/null +++ b/internal/cmd/ping.go @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2021 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 cmd + +import ( + jsonpkg "encoding/json" + + "github.com/spf13/cobra" +) + +func init() { + var cmd = &cobra.Command{ + Use: "ping", + Short: "The ping (health check) response for all EdgeX core/support microservices", + Long: ``, + + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) (err error) { + err = showPing(cmd) + + return err + }} + + rootCmd.AddCommand(cmd) + addStandardFlags(cmd) + +} + +func showPing(cmd *cobra.Command) error { + services := getSelectedServices() + + for serviceName, service := range services { + jsonValue, url, err := service.GetPingJSON() + if err != nil { + if json { + return err + } else if verbose { + cmd.Printf("%s: %s: %s\n", serviceName, url, err.Error()) + } + } else { + if json { + cmd.Println(jsonValue) + } else if verbose { + cmd.Printf("%s: %s: %s\n", serviceName, url, jsonValue) + } else { + var result map[string]interface{} + jsonpkg.Unmarshal([]byte(jsonValue), &result) + cmd.Println(serviceName + ": " + result["timestamp"].(string)) + + } + } + } + + return nil +} diff --git a/internal/cmd/root.go b/internal/cmd/root.go new file mode 100644 index 00000000..65fb5041 --- /dev/null +++ b/internal/cmd/root.go @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2021 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 cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +var rootCmd = &cobra.Command{ + Use: "edgex-cli", + Short: "EdgeX-CLI", + ValidArgs: []string{"ping", "version"}, +} + +// Execute the commands +func Execute() { + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } +} diff --git a/internal/cmd/version.go b/internal/cmd/version.go new file mode 100644 index 00000000..27e6d9d2 --- /dev/null +++ b/internal/cmd/version.go @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2021 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 cmd + +import ( + jsonpkg "encoding/json" + + "github.com/edgexfoundry/edgex-cli" + "github.com/spf13/cobra" +) + +func init() { + var cmd = &cobra.Command{ + Use: "version", + Short: "Outputs the current versions of EdgeX CLI and EdgeX microservices", + Long: ``, + + SilenceUsage: true, + RunE: func(cmd *cobra.Command, args []string) (err error) { + if !json { + cmd.Println("EdgeX CLI version: ", edgex.BuildVersion) + } + err = showVersion(cmd) + + return err + }} + + rootCmd.AddCommand(cmd) + addStandardFlags(cmd) + +} + +func showVersion(cmd *cobra.Command) error { + services := getSelectedServices() + + for serviceName, service := range services { + jsonValue, url, err := service.GetVersionJSON() + if err != nil { + if json { + return err + } else if verbose { + cmd.Printf("%s: %s: %s\n", serviceName, url, err.Error()) + } + } else { + if json { + cmd.Println(jsonValue) + } else if verbose { + cmd.Printf("%s: %s: %s\n", serviceName, url, jsonValue) + } else { + var result map[string]interface{} + jsonpkg.Unmarshal([]byte(jsonValue), &result) + cmd.Println(serviceName + ": " + result["version"].(string)) + } + } + } + + return nil +} diff --git a/internal/config/config.go b/internal/config/config.go new file mode 100644 index 00000000..e2559625 --- /dev/null +++ b/internal/config/config.go @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2021 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 config + +import ( + "github.com/edgexfoundry/edgex-cli/internal/service" + "github.com/edgexfoundry/go-mod-core-contracts/v2/common" +) + +var configuration HostConfiguration + +type HostConfiguration struct { + // CoreServices is a map of the core EdgeX microservices + CoreServices Services +} + +type Services map[string]service.Service + +// GetCoreService returns the configuration of a core service +func GetCoreService(name string) service.Service { + return configuration.CoreServices[name] +} + +// GetCoreServices returns a map of the core EdgeX microservices +func GetCoreServices() Services { + return configuration.CoreServices +} + +func init() { + configuration.CoreServices = Services{ + common.CoreMetaDataServiceKey: { + Host: "localhost", + Port: 59881, + }, + common.CoreDataServiceKey: { + Host: "localhost", + Port: 59880, + }, + common.CoreCommandServiceKey: { + Host: "localhost", + Port: 59882, + }, + common.SupportSchedulerServiceKey: { + Host: "localhost", + Port: 59861, + }, + common.SupportNotificationsServiceKey: { + Host: "localhost", + Port: 59860, + }, + } + +} diff --git a/internal/service/service.go b/internal/service/service.go new file mode 100644 index 00000000..f9d1c88c --- /dev/null +++ b/internal/service/service.go @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2021 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 service + +import ( + "context" + "fmt" + "io/ioutil" + gohttp "net/http" + + "github.com/edgexfoundry/go-mod-core-contracts/v2/clients/http" + "github.com/edgexfoundry/go-mod-core-contracts/v2/common" + dtoCommon "github.com/edgexfoundry/go-mod-core-contracts/v2/dtos/common" +) + +// Service defines the hostname and port of a EdgeX microservice +type Service struct { + + // Host is the hostname + Host string + // Port number used by service + Port int +} + +//GetVersionJSON returns the request URL and response for the 'version' endpoint. +func (c Service) GetVersionJSON() (json string, url string, err error) { + json, url, err = c.callEndpoint(common.ApiVersionRoute) + return +} + +//GetPingJSON returns the request URL and response for the 'ping' endpoint. +func (c Service) GetPingJSON() (json string, url string, err error) { + json, url, err = c.callEndpoint(common.ApiPingRoute) + return +} + +//GetConfigJSON returns the request URL and response for the 'config' endpoint. +func (c Service) GetConfigJSON() (json string, url string, err error) { + json, url, err = c.callEndpoint(common.ApiConfigRoute) + return +} + +//GetMetricsJSON returns the request URL and response for the 'metrics' endpoint. +func (c Service) GetMetricsJSON() (json string, url string, err error) { + json, url, err = c.callEndpoint(common.ApiMetricsRoute) + return +} + +//GetMetrics returns the metrics for this service. +func (c Service) GetMetrics() (result dtoCommon.Metrics, err error) { + url := fmt.Sprintf("http://%s:%v", c.Host, c.Port) + client := http.NewGeneralClient(url) + response, err := client.FetchMetrics(context.Background()) + if err != nil { + return result, err + } + + return response.Metrics, nil + +} + +//callEndpoint calls an endpoint on this service and returns the result and the URL used +func (c Service) callEndpoint(endpoint string) (string, string, error) { + url := fmt.Sprintf("http://%s:%v%s", c.Host, c.Port, endpoint) + + resp, err := gohttp.Get(url) + if err != nil { + return "", "", err + } + data, err := ioutil.ReadAll(resp.Body) + defer resp.Body.Close() + if err != nil { + return "", "", err + } + + return string(data), url, nil + +} diff --git a/main.go b/main.go deleted file mode 100644 index b0d11fe9..00000000 --- a/main.go +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright © 2019 VMware, INC -// -// 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 "github.com/edgexfoundry/edgex-cli/cmd" - -func main() { - cmd.Execute() -} diff --git a/pkg/cmd/purge/coredata.go b/pkg/cmd/purge/coredata.go deleted file mode 100644 index 5ad8c531..00000000 --- a/pkg/cmd/purge/coredata.go +++ /dev/null @@ -1,57 +0,0 @@ -package purge - -import ( - "context" - "fmt" - - "github.com/edgexfoundry/edgex-cli/config" - request "github.com/edgexfoundry/edgex-cli/pkg" - - "github.com/edgexfoundry/go-mod-core-contracts/v2/clients" -) - -type coredataCleaner struct { - baseUrl string - ctx context.Context -} - -// NewCoredataCleaner creates an instance of CoreDataCleaner -func NewCoredataCleaner(ctx context.Context) Purgeable { - fmt.Println("\n * core-data") - return &coredataCleaner{ - baseUrl: config.Conf.Clients["CoreData"].Url(), - ctx: ctx, - } -} -func (d *coredataCleaner) Purge() { - d.cleanEventsAndReadings() - //d.cleanValueDescriptors() -} - -// TODO (jpw) - rid -// func (d *coredataCleaner) cleanValueDescriptors() { -// url := d.baseUrl + clients.ApiValueDescriptorRoute -// var valueDescriptors []models.ValueDescriptor -// err := request.Get(d.ctx, url, valueDescriptors) -// if err != nil { -// fmt.Printf("Error: %s\n", err.Error()) -// return -// } - -// var count int -// for _, valueDescriptor := range valueDescriptors { -// err = request.Delete(d.ctx, url+config.PathId+valueDescriptor.Id) -// if err == nil { -// count = count + 1 -// } -// } -// fmt.Printf("Removed %d Value Descriptors from %d \n", count, len(valueDescriptors)) -// } - -func (d *coredataCleaner) cleanEventsAndReadings() { - url := d.baseUrl + clients.ApiEventRoute + "/scruball" - err := request.Delete(d.ctx, url) - if err == nil { - fmt.Print("All Events and Readings have been removed \n") - } -} diff --git a/pkg/cmd/purge/interface.go b/pkg/cmd/purge/interface.go deleted file mode 100644 index 1bd8bb53..00000000 --- a/pkg/cmd/purge/interface.go +++ /dev/null @@ -1,5 +0,0 @@ -package purge - -type Purgeable interface { - Purge() -} diff --git a/pkg/cmd/purge/metadata.go b/pkg/cmd/purge/metadata.go deleted file mode 100644 index dc177737..00000000 --- a/pkg/cmd/purge/metadata.go +++ /dev/null @@ -1,114 +0,0 @@ -package purge - -import ( - "context" - "fmt" - - "github.com/edgexfoundry/edgex-cli/config" - request "github.com/edgexfoundry/edgex-cli/pkg" - - "github.com/edgexfoundry/go-mod-core-contracts/v2/clients" - "github.com/edgexfoundry/go-mod-core-contracts/v2/clients/metadata" - "github.com/edgexfoundry/go-mod-core-contracts/v2/clients/urlclient/local" - "github.com/edgexfoundry/go-mod-core-contracts/v2/v2/models" -) - -type metadataCleaner struct { - baseUrl string - ctx context.Context -} - -// NewMetadataCleaner creates an instance of MetadataCleaner -func NewMetadataCleaner(ctx context.Context) Purgeable { - fmt.Println("\n * core-metadata") - return &metadataCleaner{ - baseUrl: config.Conf.Clients["Metadata"].Url(), - ctx: ctx, - } -} - -func (d *metadataCleaner) Purge() { - d.cleanDevices() - d.cleanDeviceServices() - d.cleanDeviceProfiles() - //d.cleanAddressables() -} - -func (d *metadataCleaner) cleanDevices() { - url := d.baseUrl + clients.ApiDeviceRoute - mdc := metadata.NewDeviceClient(local.New(url)) - - devices, err := mdc.Devices(context.Background()) - if err != nil { - fmt.Println(err) - return - } - - var count int - for _, device := range devices { - err = request.Delete(d.ctx, url+config.PathId+device.Id) - if err == nil { - count = count + 1 - } - } - fmt.Printf("Removed %d Devices from %d \n", count, len(devices)) -} - -func (d *metadataCleaner) cleanDeviceServices() { - url := d.baseUrl + clients.ApiDeviceServiceRoute - var deviceServices []models.DeviceService - err := request.Get(d.ctx, url, &deviceServices) - if err != nil { - fmt.Printf("Error: %s\n", err.Error()) - return - } - - var count int - for _, deviceService := range deviceServices { - err = request.Delete(d.ctx, url+config.PathId+deviceService.Id) - if err == nil { - count = count + 1 - } - } - - fmt.Printf("Removed %d Device Services from %d \n", count, len(deviceServices)) -} - -func (d *metadataCleaner) cleanDeviceProfiles() { - url := d.baseUrl + clients.ApiDeviceProfileRoute - var deviceProfiles []models.DeviceProfile - err := request.Get(d.ctx, url, &deviceProfiles) - if err != nil { - fmt.Printf("Error: %s\n", err.Error()) - return - } - - var count int - for _, deviceProfile := range deviceProfiles { - err = request.Delete(d.ctx, url+config.PathId+deviceProfile.Id) - if err == nil { - count = count + 1 - } - } - fmt.Printf("Removed %d Device Profiles from %d \n", count, len(deviceProfiles)) -} - -// TODO (jpw) - rid -// func (d *metadataCleaner) cleanAddressables() { -// url := d.baseUrl + clients.ApiAddressableRoute -// var addressables []models.Addressable -// err := request.Get(d.ctx, url, &addressables) -// if err != nil { -// fmt.Printf("Error: %s\n", err.Error()) -// return -// } - -// var count int -// for _, addr := range addressables { -// err = request.Delete(d.ctx, url+config.PathId+addr.Id) -// if err == nil { -// count = count + 1 -// } -// } -// fmt.Printf("Removed %d Addressables from %d \n", count, len(addressables)) -// } diff --git a/pkg/cmd/purge/scheduler.go b/pkg/cmd/purge/scheduler.go deleted file mode 100644 index e7c2084e..00000000 --- a/pkg/cmd/purge/scheduler.go +++ /dev/null @@ -1,69 +0,0 @@ -package purge - -import ( - "context" - "fmt" - - "github.com/edgexfoundry/edgex-cli/config" - request "github.com/edgexfoundry/edgex-cli/pkg" - - "github.com/edgexfoundry/go-mod-core-contracts/v2/clients" - "github.com/edgexfoundry/go-mod-core-contracts/v2/v2/models" -) - -type schedulerCleaner struct { - baseUrl string - ctx context.Context -} - -// NewSchedulerCleaner creates an instance of SchedulerCleaner -func NewSchedulerCleaner(ctx context.Context) Purgeable { - fmt.Println("\n * Scheduler") - return &schedulerCleaner{ - baseUrl: config.Conf.Clients["Scheduler"].Url(), - ctx: ctx, - } -} - -func (d *schedulerCleaner) Purge() { - d.cleanIntervals() - d.cleanIntervalActions() -} - -func (d *schedulerCleaner) cleanIntervals() { - url := d.baseUrl + clients.ApiIntervalRoute - var intervals []models.Interval - err := request.Get(d.ctx, url, &intervals) - if err != nil { - fmt.Printf("Error: %s\n", err.Error()) - return - } - - var count int - for _, interval := range intervals { - err = request.Delete(d.ctx, url+"/"+interval.Id) - if err == nil { - count = count + 1 - } - } - fmt.Printf("Removed %d Intervals from %d \n", count, len(intervals)) -} - -func (d *schedulerCleaner) cleanIntervalActions() { - url := d.baseUrl + clients.ApiIntervalActionRoute - var intervalActions []models.IntervalAction - err := request.Get(d.ctx, url, &intervalActions) - if err != nil { - fmt.Printf("Error: %s\n", err.Error()) - return - } - - var count int - for _, intervalAction := range intervalActions { - err = request.Delete(d.ctx, url+"/"+intervalAction.Id) - if err == nil { - count = count + 1 - } - } - fmt.Printf("Removed %d Interval Actions from %d \n", count, len(intervalActions)) -} diff --git a/pkg/cmd/version/version.go b/pkg/cmd/version/version.go deleted file mode 100644 index 0836329e..00000000 --- a/pkg/cmd/version/version.go +++ /dev/null @@ -1,32 +0,0 @@ -package version - -import ( - "encoding/json" - "net/http" - - "github.com/edgexfoundry/edgex-cli/config" - - "github.com/edgexfoundry/go-mod-core-contracts/v2/clients" -) - -type Version struct { - Version string `json:"version" yaml:"version,omitempty"` -} - -// GetEdgeXVersion returns the version of core-data microservice -func GetEdgeXVersion() (version Version, err error) { - url := config.Conf.Clients["CoreData"].Url() + clients.ApiVersionRoute - resp, err := http.Get(url) - if err != nil { - return version, err - } - - defer resp.Body.Close() - - err = json.NewDecoder(resp.Body).Decode(&version) - if err != nil { - return Version{}, err - } - - return version, nil -} diff --git a/pkg/confirmation/confirm.go b/pkg/confirmation/confirm.go deleted file mode 100644 index 010b45f5..00000000 --- a/pkg/confirmation/confirm.go +++ /dev/null @@ -1,74 +0,0 @@ -package confirmation - -import ( - "fmt" - "log" - "strings" -) - -const defaultConfirmMsg = "Are you sure? This cannot be undone: [y/n]" -const defaultAbortMsg = "Aborting" - -type UserConfirmation struct { - confirmMsg string - abortMsg string -} - -//UserConfirmation creates new UserConfirmation using default values for confirmMsg and abortMsg -func New() UserConfirmation { - return UserConfirmation{ - confirmMsg: defaultConfirmMsg, - abortMsg: defaultAbortMsg, - } -} - -//NewCustom creates new UserConfirmation using custom confirmMsg and abortMsg -func NewCustom(confirm, abort string) UserConfirmation { - confirmMsg := defaultConfirmMsg - if confirm != "" { - confirmMsg = confirm - } - - abortMsg := defaultAbortMsg - if abort != "" { - abortMsg = abort - } - return UserConfirmation{ - confirmMsg: confirmMsg, - abortMsg: abortMsg, - } -} - -// askForConfirmation uses Scanln to parse user input. A user must type in "yes" or "no" and -// then press enter. It has fuzzy matching, so "y", "Y", "yes", "YES", and "Yes" all count as -// confirmations. If the input is not recognized, it will ask again. The function does not return -// until it gets a valid response from the user. Typically, you should use fmt to print out a question -// before calling askForConfirmation. E.g. fmt.Println("WARNING: Are you sure? (yes/no)") -func askForConfirmation() bool { - var response string - - _, err := fmt.Scanln(&response) - if err != nil { - log.Fatal(err) - } - - switch strings.ToLower(response) { - case "y", "yes": - return true - case "n", "no": - return false - default: - fmt.Println("I'm sorry but I didn't get what you meant, please type (y)es or (n)o and then press enter:") - return askForConfirmation() - } -} - -//Ask user to confirm the execution -func (c UserConfirmation) Confirm() bool { - fmt.Println(c.confirmMsg) - if !askForConfirmation() { - fmt.Println(c.abortMsg) - return false - } - return true -} diff --git a/pkg/const.go b/pkg/const.go deleted file mode 100644 index cec39536..00000000 --- a/pkg/const.go +++ /dev/null @@ -1,5 +0,0 @@ -package client - -const ( - SUCCESSFUL_DELETE = "true" -) diff --git a/pkg/editor/interactive.go b/pkg/editor/interactive.go deleted file mode 100644 index 87d7972c..00000000 --- a/pkg/editor/interactive.go +++ /dev/null @@ -1,125 +0,0 @@ -/******************************************************************************* - * Copyright 2020 Dell Inc. - * - * 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 editor contains functionality which allows the user to provide information in the editor of their choice. -package editor - -import ( - "bytes" - "encoding/json" - "html/template" - "io/ioutil" - "os" - "os/exec" - "runtime" -) - -const ( - UnixDefaultEditor = "vi" - WindowsDefaultEditor = "notepad" - EnvironmentVariableName = "EDITOR" - InteractiveModeLabel = "interactive mode" -) - -// OpenFileInEditor opens filename in a text editor. -func OpenFileInEditor(filename string) error { - editor := os.Getenv(EnvironmentVariableName) - if editor == "" { - - if runtime.GOOS == "windows" { - editor = WindowsDefaultEditor - } else { - editor = UnixDefaultEditor - } - } - - editorExecutable, err := exec.LookPath(editor) - if err != nil { - return err - } - - cmd := exec.Command(editorExecutable, filename) - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - - return cmd.Run() -} - -// CaptureInputFromEditor opens a temporary file in a text editor and returns -// the written bytes on success or an error on failure. It handles deletion -// of the temporary file behind the scenes. -func CaptureInputFromEditor(template []byte) ([]byte, error) { - file, err := ioutil.TempFile(os.TempDir(), "*") - if err != nil { - return []byte{}, err - } - - filename := file.Name() - defer func() { _ = os.Remove(filename) }() - - _, err = file.Write(template) - if err != nil { - return nil, err - } - - if err = file.Close(); err != nil { - return []byte{}, err - } - - if err = OpenFileInEditor(filename); err != nil { - return []byte{}, err - } - - updatedBytes, err := ioutil.ReadFile(filename) - if err != nil { - return []byte{}, err - } - - return updatedBytes, nil -} - -// OpenInteractiveEditor opens users default editor populated with a JSON representation of a structure -func OpenInteractiveEditor(o interface{}, temp string, funcMap template.FuncMap) ([]byte, error) { - t := template.New("Template") - if funcMap != nil { - t.Funcs(funcMap) - } - dsJsonTemplate, err := t.Parse(temp) - if err != nil { - return nil, err - } - buff := bytes.NewBuffer([]byte{}) - err = dsJsonTemplate.Execute(buff, o) - if err != nil { - return nil, err - } - - return CaptureInputFromEditor(buff.Bytes()) -} - -// isLastElementOfSlice is a function which is used in HTML templates to determine the last element in a slice -func IsLastElementOfSlice(index int, lenght int) bool { - return index == lenght-1 -} - -// EscapeHTML is a function which marshal `s` applying Intent and returns template.HTML. This makes hmtl.Template to -// escape HTML codes -func EscapeHTML(s interface{}) (template.HTML, error) { - data, err := json.MarshalIndent(s, "", " ") - if err != nil { - return "", err - } - return template.HTML(data), nil -} diff --git a/pkg/formatters/emptyformatter.go b/pkg/formatters/emptyformatter.go deleted file mode 100644 index 87ec8bc9..00000000 --- a/pkg/formatters/emptyformatter.go +++ /dev/null @@ -1,20 +0,0 @@ -/******************************************************************************* - * Copyright 2020 VMWare. - * - * 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 formatters - -type EmptyFormatter struct{} - -func (f *EmptyFormatter) Write(obj interface{}) (err error) { - return nil -} diff --git a/pkg/formatters/interface.go b/pkg/formatters/interface.go deleted file mode 100644 index 1ba96586..00000000 --- a/pkg/formatters/interface.go +++ /dev/null @@ -1,30 +0,0 @@ -/******************************************************************************* - * Copyright 2020 VMWare. - * - * 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 formatters - -import ( - "github.com/spf13/viper" - "html/template" -) - -type FormatWriter interface { - Write(obj interface{}) error -} - -func NewFormatter(template string, funcMaps template.FuncMap) FormatWriter { - if viper.GetBool("verbose") { - return &JsonFormatter{} - } - return NewHtmlTemplateFormatter(template, funcMaps) -} diff --git a/pkg/formatters/jsonformatter.go b/pkg/formatters/jsonformatter.go deleted file mode 100644 index daa036ae..00000000 --- a/pkg/formatters/jsonformatter.go +++ /dev/null @@ -1,34 +0,0 @@ -/******************************************************************************* - * Copyright 2020 VMWare. - * - * 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 formatters - -import ( - "encoding/json" - "fmt" - "github.com/spf13/viper" - "io" -) - -type JsonFormatter struct{} - -func (f *JsonFormatter) Write(obj interface{}) (err error) { - json, err := json.Marshal(obj) - if err != nil { - return err - } - pw := viper.Get("writer").(io.WriteCloser) - defer pw.Close() - _, err = fmt.Fprintf(pw, "%s\n", json) - return -} diff --git a/pkg/formatters/templateformatter.go b/pkg/formatters/templateformatter.go deleted file mode 100644 index ed6a6b83..00000000 --- a/pkg/formatters/templateformatter.go +++ /dev/null @@ -1,56 +0,0 @@ -/******************************************************************************* - * Copyright 2020 VMWare. - * - * 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 formatters - -import ( - "github.com/spf13/viper" - "html/template" - "io" - "text/tabwriter" -) - -type HtmlTemplateFormatter struct { - Name string - Format string - FuncMaps template.FuncMap -} - -func NewHtmlTemplateFormatter(format string, funcMaps template.FuncMap) *HtmlTemplateFormatter { - return &HtmlTemplateFormatter{ - //use one common name for all templates - Name: "cliTemplate", - Format: format, - FuncMaps: funcMaps, - } -} - -func (f *HtmlTemplateFormatter) Write(obj interface{}) (err error) { - pw := viper.Get("writer").(io.WriteCloser) - w := new(tabwriter.Writer) - w.Init(pw, 0, 8, 2, '\t', 0) - tmpl := template.New(f.Name) - if f.FuncMaps != nil { - tmpl.Funcs(f.FuncMaps) - } - tmpl, err = tmpl.Parse(f.Format) - if err != nil { - return err - } - err = tmpl.Execute(w, obj) - if err != nil { - return err - } - w.Flush() - return nil -} diff --git a/pkg/pager/pager.go b/pkg/pager/pager.go deleted file mode 100644 index 12065616..00000000 --- a/pkg/pager/pager.go +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright © 2019 Dell Technologies -// -// 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 pager - -import ( - "fmt" - "io" - "os" - "os/exec" - "strings" - "sync" -) - -// Writer is used to write text to a terminal using the terminal's pager (less). -// It implements the WriteCloser interface and should be closed when writing is finished. -type Writer struct { - w io.WriteCloser - c chan struct{} - o sync.Once -} - -// getPagerCommand inspects the PAGER environment variable for a pager command, -// otherwise returns a reasonable default ("more) -func getPagerCommand() (string, []string) { - fromEnv := os.Getenv("PAGER") - if fromEnv == "" { - return "more", nil - } - - split := strings.Split(fromEnv, " ") - return split[0], split[1:] -} - -// NewWriter returns a new pager.Writer to be used to write -// paged text to the terminal -func NewWriter() (*Writer, error) { - name, args := getPagerCommand() - pager := exec.Command(name, args...) - - // Create an os Pipe to allow clients to write into our pager - r, w, err := os.Pipe() - if err != nil { - fmt.Println(err.Error()) - return nil, err - } - - pager.Stdin = r - pager.Stdout = os.Stdout - pager.Stderr = os.Stderr - - c := make(chan struct{}) - - writer := Writer{ - w: w, - c: c, - o: sync.Once{}, - } - - // run pager in goroutine - go func() { - defer func() { - close(c) - // Defer a close on the writer so potentially blocked clients will - // become unblocked. w has potentially already been closed by the - // Writer's Close method, but without this Close call (or a ReadAll call, - // alternatively) writers can be blocked forever. - _ = writer.close() - }() - _ = pager.Run() - }() - - return &writer, nil -} - -// Write writes a slice of bytes to the Writer -func (w *Writer) Write(p []byte) (n int, err error) { - return w.w.Write(p) -} - -// close ensures the Close() method of our pipe is called only once, regardless of whether -// the client calls Close first or the pager terminates first. -func (w *Writer) close() error { - var err error - w.o.Do(func() { - err = w.w.Close() - }) - - return err -} - -// Close is potentially a blocking call, as it will wait -// until the pager command terminates before returning. -func (w *Writer) Close() error { - err := w.close() - <-w.c - return err -} diff --git a/pkg/request.go b/pkg/request.go deleted file mode 100644 index c99b2191..00000000 --- a/pkg/request.go +++ /dev/null @@ -1,94 +0,0 @@ -package client - -import ( - "context" - "encoding/json" - "fmt" - "net/http" - "reflect" - - "github.com/edgexfoundry/go-mod-core-contracts/v2/clients" - "github.com/edgexfoundry/go-mod-core-contracts/v2/clients/urlclient/local" - - "github.com/spf13/viper" -) - -func Get(ctx context.Context, url string, items interface{}) (err error) { - printURL(url, http.MethodGet) - resp, err := clients.GetRequest(ctx, "", local.New(url)) - if err != nil { - return err - } - return json.Unmarshal(resp, &items) -} -func Delete(ctx context.Context, url string) error { - printURL(url, http.MethodDelete) - return clients.DeleteRequest(ctx, "", local.New(url)) -} -func DeletePrt(ctx context.Context, url string, deletedBy string) error { - err := Delete(ctx, url) - if err == nil && deletedBy != "" { - fmt.Printf("Removed: %s \n", deletedBy) - return nil - } - return err -} - -func Post(ctx context.Context, url string, item interface{}) { - printURL(url, http.MethodPost) - printData(item) - resp, err := clients.PostJSONRequest(ctx, "", item, local.New(url)) - name := getType(item) - if err != nil { - fmt.Printf("Failed to create %s because of error: %s", name, err) - } else { - fmt.Printf("%s successfully created: %s ", name, resp) - } -} - -func printURL(url string, method string) { - if viper.GetBool("url") { - fmt.Printf("> %s: %s \n", method, url) - } -} - -func printData(item interface{}) { - if viper.GetBool("verbose") { - body := reflect.ValueOf(item).MethodByName("String").Call([]reflect.Value{})[0].Interface().(string) - fmt.Printf("> Request data: %s\n", body) - } -} - -func getType(item interface{}) string { - if t := reflect.TypeOf(item); t.Kind() == reflect.Ptr { - return t.Elem().Name() - } else { - return t.Name() - } -} - -func DeleteByIds(ctx context.Context, i interface{}, ids []string) error { - methodVal := reflect.ValueOf(i).MethodByName("Delete") - if !methodVal.IsValid() { - return fmt.Errorf("unsupported method: %s", "Delete") - } else if methodVal.Type().NumIn() != 2 { - return fmt.Errorf("client method has %q input parameters, want 2", methodVal.Type().NumIn()) - } else if methodVal.Type().In(0) != reflect.TypeOf((*context.Context)(nil)).Elem() { - return fmt.Errorf("client method's first input parameter is %q, want `context.Context`", methodVal.Type().In(0)) - } else if methodVal.Type().In(1) != reflect.TypeOf((*string)(nil)).Elem() { - return fmt.Errorf("client method's first input parameter is %q, want `string`", methodVal.Type().In(1)) - } else if methodVal.Type().NumOut() != 1 { - return fmt.Errorf("client method has %q output parameters, want 1", methodVal.Type().NumOut()) - } - - for _, id := range ids { - out := methodVal.Call([]reflect.Value{reflect.ValueOf(ctx), reflect.ValueOf(id)}) - err := out[0] - if !err.IsNil() { - fmt.Printf("Error: %s", err.Interface().(error)) - } else { - fmt.Printf("Removed: %s \n", id) - } - } - return nil -} diff --git a/pkg/request_test.go b/pkg/request_test.go deleted file mode 100644 index f6893734..00000000 --- a/pkg/request_test.go +++ /dev/null @@ -1,394 +0,0 @@ -///******************************************************************************* -// * Copyright 2019 VMware Inc. -// * -// * 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 client - -//TODO Rewrite the test following the new logic - use "net/http/httptest" -// -//import ( -// "context" -// "errors" -// "net" -// "net/http" -// "net/http/httptest" -// "reflect" -// "testing" -//) -// -//var TestID = "testid" -//var TestPathID = "id/" -//var TestInvalidPathID = []rune{ -// 0x7f, -//} -//var TestInvalidPathName = []rune{ -// 0x7f, -//} -//var TestPathName = "name/" -////var TestPort = 1234 -////var TestVerboseTrue = true -////var TestVerboseFalse = false -//// -////TO:DO Test verbose -////func TestGetAllItems(t *testing.T) { -//// tests := []struct { -//// name string -//// mockClient *http.Client -//// isVerbose bool -//// expectedResult []byte -//// expectedError bool -//// }{ -//// { -//// name: "Successful call", -//// mockClient: mockClientGetAllSuccess(), -//// isVerbose: true, -//// expectedResult: []byte(SUCCESSFUL_DELETE), -//// expectedError: false, -//// }, -//// { -//// name: "Unsuccessful http Get", -//// mockClient: mockClientGetAllErr(), -//// isVerbose: true, -//// expectedResult: nil, -//// expectedError: true, -//// }, -//// } -//// -//// for _, test := range tests { -//// t.Run(test.name, func(t *testing.T) { -//// client = test.mockClient -//// unexistingClient := config.Client{Host:"localhost",Port:1312, Protocol:"http"} -//// actual, err := GetAllItems(unexistingClient.Url()+"/blabla") -//// -//// if test.expectedError && err == nil { -//// t.Error("Expected an error") -//// return -//// } -//// -//// if !test.expectedError && err != nil { -//// t.Errorf("Unexpectedly encountered error: %s", err) -//// return -//// } -//// -//// if !reflect.DeepEqual(test.expectedResult, actual) { -//// t.Errorf("Expected result does not match the observed.\nExpected: %v\nObserved: %v\n", test.expectedResult, actual) -//// return -//// } -//// }) -//// } -////} -// -//func mockClientGetAllSuccess() *http.Client { -// h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { -// w.Write([]byte(SUCCESSFUL_DELETE)) -// }) -// httpclient, _ := testingHTTPClient(h) -// return httpclient -//} -// -//func mockClientGetAllErr() *http.Client { -// h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { -// w.WriteHeader(http.StatusInternalServerError) -// w.Write([]byte{}) -// }) -// httpclient, _ := testingHTTPClientErr(h) -// return httpclient -//} -// -//func testingHTTPClient(handler http.Handler) (*http.Client, func()) { -// s := httptest.NewServer(handler) -// -// cli := &http.Client{ -// Transport: &http.Transport{ -// DialContext: func(_ context.Context, network, _ string) (net.Conn, error) { -// return net.Dial(network, s.Listener.Addr().String()) -// }, -// }, -// } -// return cli, s.Close -//} -// -//func testingHTTPClientErr(handler http.Handler) (*http.Client, func()) { -// s := httptest.NewServer(handler) -// -// cli := NewTestClient(func(req *http.Request) *http.Response { -// return nil -// }) -// return cli, s.Close -//} -// -//// RoundTripFunc . -//type RoundTripFunc func(req *http.Request) *http.Response -// -//// RoundTrip . -//func (f RoundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { -// return f(req), errors.New("error test") -//} -// -////NewTestClient returns *http.Client with Transport replaced to avoid making real calls -//func NewTestClient(fn RoundTripFunc) *http.Client { -// return &http.Client{ -// Transport: RoundTripFunc(fn), -// } -//} -// -//func mockClientDeleteByIDSuccess() *http.Client { -// h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { -// w.Write([]byte(SUCCESSFUL_DELETE)) -// }) -// httpclient, _ := testingHTTPClient(h) -// return httpclient -//} -// -//func mockClientDeleteByIDErr() *http.Client { -// h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { -// w.Write([]byte{}) -// }) -// httpclient, _ := testingHTTPClientErr(h) -// return httpclient -//} -// -//func mockClientDeleteByIDBufferErr() *http.Client { -// h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { -// w.Header().Set("Content-Length", "1") -// }) -// httpclient, _ := testingHTTPClient(h) -// return httpclient -//} -// -//func TestDeleteItemByID(t *testing.T) { -// tests := []struct { -// name string -// mockClient *http.Client -// testID string -// testPathID string -// expectedResult []byte -// expectedError bool -// }{ -// { -// name: "Successful call", -// mockClient: mockClientDeleteByIDSuccess(), -// testID: TestID, -// testPathID: TestPathID, -// expectedResult: []byte(SUCCESSFUL_DELETE), -// expectedError: false, -// }, -// { -// name: "Unsuccessful http new Request", -// mockClient: mockClientDeleteByIDErr(), -// testID: TestID, -// testPathID: string(TestInvalidPathID), -// expectedResult: nil, -// expectedError: true, -// }, -// { -// name: "Unsuccessful http Do", -// mockClient: mockClientDeleteByIDErr(), -// testID: TestID, -// testPathID: TestPathID, -// expectedResult: nil, -// expectedError: true, -// }, -// { -// name: "Unsuccessful ioutil ReadAll", -// mockClient: mockClientDeleteByIDBufferErr(), -// testID: TestID, -// testPathID: TestPathID, -// expectedResult: nil, -// expectedError: true, -// }, -// } -// -// for _, test := range tests { -// t.Run(test.name, func(t *testing.T) { -// client = test.mockClient -// url:="http://localhost:2222/api/v1/testurl" -// actual, err := DeleteItemByIdOrName(test.testID, test.testPathID, "/name/", url) -// -// if test.expectedError && err == nil { -// t.Error("Expected an error") -// return -// } -// -// if !test.expectedError && err != nil { -// t.Errorf("Unexpectedly encountered error: %s", err) -// return -// } -// -// if !reflect.DeepEqual(test.expectedResult, actual) { -// t.Errorf("Expected result does not match the observed.\nExpected: %v\nObserved: %v\n", test.expectedResult, actual) -// return -// } -// }) -// } -//} -// -//func mockClientDeleteByNameSuccess() *http.Client { -// h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { -// w.Write([]byte(SUCCESSFUL_DELETE)) -// }) -// httpclient, _ := testingHTTPClient(h) -// return httpclient -//} -// -//func mockClientDeleteByNameBufferErr() *http.Client { -// h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { -// w.Header().Set("Content-Length", "1") -// }) -// httpclient, _ := testingHTTPClient(h) -// return httpclient -//} -// -//func mockClientDeleteByNameErr() *http.Client { -// h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { -// w.WriteHeader(http.StatusInternalServerError) -// w.Write([]byte{}) -// }) -// httpclient, _ := testingHTTPClientErr(h) -// return httpclient -//} -// -////TODO both `TestDeleteItemByName` and 'TestDeleteItemByID' now call one and the same method Delete(url) -////no need to have two tests -//func TestDeleteItemByName(t *testing.T) { -// tests := []struct { -// name string -// mockClient *http.Client -// testID string -// testPathName string -// expectedResult []byte -// expectedError bool -// }{ -// { -// name: "Successful call", -// mockClient: mockClientDeleteByNameSuccess(), -// testID: TestID, -// testPathName: TestPathName, -// expectedResult: []byte(SUCCESSFUL_DELETE), -// expectedError: false, -// }, -// { -// name: "Unsuccessful http new Request", -// mockClient: mockClientDeleteByNameErr(), -// testID: TestID, -// testPathName: string(TestInvalidPathID), -// expectedResult: nil, -// expectedError: true, -// }, -// { -// name: "Unsuccessful http Do", -// mockClient: mockClientDeleteByNameErr(), -// testID: TestID, -// testPathName: TestPathName, -// expectedResult: nil, -// expectedError: true, -// }, -// { -// name: "Unsuccessful ioutil ReadAll", -// mockClient: mockClientDeleteByNameBufferErr(), -// testID: TestID, -// testPathName: TestPathName, -// expectedResult: nil, -// expectedError: true, -// }, -// } -// -// for _, test := range tests { -// t.Run(test.name, func(t *testing.T) { -// client = test.mockClient -// url:="http://localhost:2222/api/v1/testurl/"+ test.testPathName+ test.testID -// actual, err := Delete(url) -// -// if test.expectedError && err == nil { -// t.Error("Expected an error") -// return -// } -// -// if !test.expectedError && err != nil { -// t.Errorf("Unexpectedly encountered error: %s", err) -// return -// } -// -// if !reflect.DeepEqual(test.expectedResult, actual) { -// t.Errorf("Expected result does not match the observed.\nExpected: %v\nObserved: %v\n", test.expectedResult, actual) -// return -// } -// }) -// } -//} -// -//func TestDeleteItem(t *testing.T) { -// tests := []struct { -// name string -// mockClient *http.Client -// testID string -// testPathID string -// testPathName string -// expectedResult []byte -// expectedError bool -// }{ -// { -// name: "Successful call", -// mockClient: mockClientDeleteByNameSuccess(), -// testID: TestID, -// testPathID: TestPathID, -// testPathName: TestPathName, -// -// expectedResult: []byte(SUCCESSFUL_DELETE), -// expectedError: false, -// }, -// { -// name: "Unsuccessful DeleteItemByID and no pathName", -// mockClient: mockClientDeleteByIDErr(), -// testID: TestID, -// testPathID: string(TestInvalidPathID), -// testPathName: "", -// -// expectedResult: nil, -// expectedError: true, -// }, -// { -// name: "Unsuccessful DeleteItemByID has pathname", -// mockClient: mockClientDeleteByIDErr(), -// testID: TestID, -// testPathID: TestPathID, -// testPathName: TestPathName, -// -// expectedResult: nil, -// expectedError: true, -// }, -// } -// -// for _, test := range tests { -// t.Run(test.name, func(t *testing.T) { -// client = test.mockClient -// url:="http://localhost:2222/api/v1/testurl" -// actual, err := DeleteItemByIdOrName(test.testID, test.testPathID, test.testPathName, url) -// -// if test.expectedError && err == nil { -// t.Error("Expected an error") -// return -// } -// -// if !test.expectedError && err != nil { -// t.Errorf("Unexpectedly encountered error: %s", err) -// return -// } -// -// if !reflect.DeepEqual(test.expectedResult, actual) { -// t.Errorf("Expected result does not match the observed.\nExpected: %v\nObserved: %v\n", test.expectedResult, actual) -// return -// } -// }) -// } -//} diff --git a/pkg/utils/utils.go b/pkg/utils/utils.go deleted file mode 100644 index 2c42ca25..00000000 --- a/pkg/utils/utils.go +++ /dev/null @@ -1,68 +0,0 @@ -package utils - -import ( - "fmt" - "time" -) - -// humanDuration return the duration since date. Taken from https://github.com/docker/go-units/blob/master/duration.go -func humanDuration(d time.Duration) string { - if seconds := int(d.Seconds()); seconds < 1 { - return "Less than a second" - } else if seconds == 1 { - return "1 second" - } else if seconds < 60 { - return fmt.Sprintf("%d seconds", seconds) - } else if minutes := int(d.Minutes()); minutes == 1 { - return "About a minute" - } else if minutes < 60 { - return fmt.Sprintf("%d minutes", minutes) - } else if hours := int(d.Hours() + 0.5); hours == 1 { - return "About an hour" - } else if hours < 48 { - return fmt.Sprintf("%d hours", hours) - } else if hours < 24*7*2 { - return fmt.Sprintf("%d days", hours/24) - } else if hours < 24*30*2 { - return fmt.Sprintf("%d weeks", hours/24/7) - } else if hours < 24*365*2 { - return fmt.Sprintf("%d months", hours/24/30) - } - return fmt.Sprintf("%d years", int(d.Hours())/24/365) -} - -func DisplayDuration(tt int64) string { - zeroDisplay := "" - if tt == 0 { - return zeroDisplay - } else { - ttTime := time.Unix(tt/1000, 0) - return humanDuration(time.Since(ttTime)) - } -} - -const TimeUnitDescriptions = "ms - milliseconds\n" + - "s - seconds\n" + - "m - minutes\n" + - "h - hours\n" + - "d - days\n" - -//empty struct has width of zero. It occupies zero bytes of storage -var TimeUnitsMap = map[string]struct{}{"ms": {}, "s": {}, "h": {}, "d": {}, "m": {}} - -func ConvertAgeToMillisecond(unit string, age int64) int64 { - var ageMilliseconds int64 - switch unit { - case "ms": - ageMilliseconds = age - case "s": - ageMilliseconds = age * 1000 - case "m": - ageMilliseconds = age * 60 * 1000 - case "h": - ageMilliseconds = age * 60 * 60 * 1000 - case "d": - ageMilliseconds = age * 24 * 60 * 60 * 1000 - } - return ageMilliseconds -} diff --git a/run_cmds.sh b/run_cmds.sh deleted file mode 100755 index 7096fa2c..00000000 --- a/run_cmds.sh +++ /dev/null @@ -1,113 +0,0 @@ -#!/usr/bin/env bash -echo ">make build" -make build - -execute(){ - slug=$1 - shift - commands=("$@") - - echo -e "*** $slug ***\n" - for cmd in "${commands[@]}" - do - echo "> ./edgex-cli $cmd" - ./edgex-cli $cmd - echo -e "\n" - done - echo -e "*** End $slug ***\n" -} -test_deviceService() { - declare -a commands=("deviceservice list --no-pager" - ) - execute "Device Service" "${commands[@]}" -} - -test_device() { -#TODO add device fails because there is no deviceservice. - declare -a commands=("device list --no-pager" -# "device add -f samples/createDevice.json" -# "device rm --name Car-001" - ) - execute "Device" "${commands[@]}" -} - -test_dp() { - declare -a commands=("profile list --no-pager" - "profile add -f samples/createDP.json" - "profile rm --name DeviceProfileCLI") - execute "DeviceProfile" "${commands[@]}" -} - - -test_intervals(){ - declare -a commands=("interval add -f samples/createInterval.json" - "interval list --no-pager" \ - "interval rm --name noon"\ - "interval rm --name fourteen-hundrend-hours") - execute "Intervals" "${commands[@]}" -} - - -test_notifications(){ - declare -a commands=("notification add -f samples/createNotification.json" - "notification list --new --no-pager" \ - "notification list --sender SystemManagement" \ - "notification list --slug notice-001" \ - "notification list --labels=temperature" \ - "notification rm --slug notice-001") - execute "Notification" "${commands[@]}" -} - -test_others(){ - declare -a commands=("addressable list --no-pager" - "deviceservice list --no-pager" \ - "event list --no-pager" \ - "reading list --no-pager"\ - "subscription list --no-pager" - "status" - "version") - execute "Others" "${commands[@]}" -} - -test_all(){ - test_deviceService - test_dp - test_device - test_notifications - test_intervals - test_others -} - -if [[ $# -eq 0 ]] ; then - test_all - exit 0 -fi - -for i in "$@" -do - case $i in - -d) - test_device - ;; - -dp) - test_dp - ;; - -ds) - test_deviceService - ;; - -i) - test_intervals - ;; - -n) - test_notifications - ;; - -o) - test_others - ;; - - *) - test_all - ;; - - esac -done \ No newline at end of file diff --git a/samples/createDP.json b/samples/createDP.json deleted file mode 100644 index 63c52fa5..00000000 --- a/samples/createDP.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "name":"DeviceProfileCLI", - "manufacturer":"CLI-Manufacturer", - "model":"cliModel", - "labels":[ - "bacnet", - "thermostat", - "Honeywell" - ], - "description":"This device profile has link in device.", - "deviceCommands":[ - { - "get":[ - { - "parameter":"CurrentHumidity", - "property":"presentValue", - "operation":"get", - "object":"AnalogValue_22" - } - ] - } - ], - "origin":1471806386919, - "coreCommands":[ - { - "name":"testCommand1", - "get":{ - "path":"/testCommand1", - "responses":[{ - "code":"200", - "errorDescription":"not a float", - "expectedValues":[ - "temperature" - ] - }] - }, - "put":{ - "parameterNames":[ - "testCommand1", - "float" - ], - "path":"/testCommand1", - "responses":[ - { - "code":"200", - "errorDescription":"not a float", - "expectedValues":["temperature"] - } - ] - }, - "origin":1471806386919 - } - ] -} \ No newline at end of file diff --git a/samples/createDP.yaml b/samples/createDP.yaml deleted file mode 100644 index 8340a7c4..00000000 --- a/samples/createDP.yaml +++ /dev/null @@ -1,133 +0,0 @@ -name: "Simple-Device-1" -manufacturer: "Simple Corp." -model: "SP-01" -labels: - - "modbus" -description: "Example of Simple Device" - -deviceResources: - - - name: "SwitchButton" - description: "Switch On/Off." - properties: - value: - { type: "Bool", readWrite: "RW", defaultValue: "true" } - units: - { type: "String", readWrite: "R", defaultValue: "On/Off" } - - - name: "Image" - description: "Visual representation of Switch state." - properties: - value: - { type: "Binary", readWrite: "R" } - units: - { type: "Binary", readWrite: "R", defaultValue: "On/Off" } - - - name: "Xrotation" - description: "X axis rotation rate" - properties: - value: - { type: "Int32", readWrite: "RW" } - units: - { type: "string", readWrite: "R", defaultValue: "degrees/sec" } - - - name: "Yrotation" - description: "Y axis rotation rate" - properties: - value: - { type: "Int32", readWrite: "RW" } - units: - { type: "string", readWrite: "R", defaultValue: "degrees/sec" } - - - name: "Zrotation" - description: "Z axis rotation rate" - properties: - value: - { type: "Int32", readWrite: "RW" } - units: - { type: "string", readWrite: "R", defaultValue: "degrees/sec" } - -deviceCommands: - - - name: "Switch" - get: - - { operation: "get", deviceResource: "SwitchButton" } - set: - - { operation: "set", deviceResource: "SwitchButton", parameter: "false" } - - - name: "Image" - get: - - { operation: "get", deviceResource: "Image" } - - - name: "Rotation" - get: - - { operation: "get", deviceResource: "Xrotation" } - - { operation: "get", deviceResource: "Yrotation" } - - { operation: "get", deviceResource: "Zrotation" } - set: - - { operation: "set", deviceResource: "Xrotation", parameter: "0" } - - { operation: "set", deviceResource: "Yrotation", parameter: "0" } - - { operation: "set", deviceResource: "Zrotation", parameter: "0" } - -coreCommands: - - - name: "Switch" - get: - path: "/api/v1/device/{deviceId}/SwitchButton" - responses: - - - code: "200" - description: "" - expectedValues: ["SwitchButton"] - - - code: "503" - description: "service unavailable" - expectedValues: [] - put: - path: "/api/v1/device/{deviceId}/SwitchButton" - parameterNames: ["SwitchButton"] - responses: - - - code: "200" - description: "" - - - code: "503" - description: "service unavailable" - expectedValues: [] - - - name: "Image" - get: - path: "/api/v1/device/{deviceId}/Image" - responses: - - - code: "200" - description: "CBOR encoded image transmitted as event to Core-Data" - expectedValues: ["Image"] - - - code: "500" - description: "Internal Server Error" - expectedValues: [] - - - name: "Rotation" - get: - path: "/api/v1/device/{deviceId}/Rotation" - responses: - - - code: "200" - description: "Successfully read the rotation sensors" - expectedValues: ["Xrotation","Yrotation","Zrotation"] - - - code: "500" - description: "Internal Server Error" - expectedValues: [] - put: - path: "/api/v1/device/{deviceId}/Rotation" - parameterNames: ["Xrotation","Yrotation","Zrotation"] - responses: - - - code: "200" - description: "Successfully write the rotation sensors" - - - code: "500" - description: "Internal Server Error" - expectedValues: [] diff --git a/samples/createDevice.json b/samples/createDevice.json deleted file mode 100644 index 08bf41e6..00000000 --- a/samples/createDevice.json +++ /dev/null @@ -1,27 +0,0 @@ -[ - { - "AddressableName": "TestAddressable", - "AutoEvents": [], - "Description": "my test", - "Labels": [ - "car", - "rev-counter", - "tachometer" - ], - "Name": "Car-001", - "Profile": "car tachometer profile", - "Service": "car tachometer device service", - "Protocols": { - "modbus-http": { - "host": "localhost", - "port": "1029", - "unitID": "1" - }, - "modbus-tcp": { - "host": "localhost", - "port": "1029", - "unitID": "1" - } - } - } -] diff --git a/samples/createInterval.json b/samples/createInterval.json deleted file mode 100644 index a58d542c..00000000 --- a/samples/createInterval.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "frequency": "P1D", - "name": "noon", - "origin": 0, - "start": "20180101T000000" - }, - { - "frequency": "P1D", - "name": "fourteen-hundrend-hours", - "origin": 0, - "start": "20180101T000000" - } -] diff --git a/samples/createNotification.json b/samples/createNotification.json deleted file mode 100644 index 4d8ba4b1..00000000 --- a/samples/createNotification.json +++ /dev/null @@ -1,13 +0,0 @@ -[ - { - "category": "HW_HEALTH", - "content": " [ Notification ] Room's temperature is 82 °F", - "labels": [ - "room", - "temperature" - ], - "sender": "SystemManagement", - "severity": "NORMAL", - "slug": "notice-001" - } -] diff --git a/samples/updateInterval.json b/samples/updateInterval.json deleted file mode 100644 index 58ff7b84..00000000 --- a/samples/updateInterval.json +++ /dev/null @@ -1,14 +0,0 @@ -[ - { - "frequency": "P4D", - "name": "noon", - "origin": 0, - "start": "20180101T000000" - }, - { - "frequency": "P4D", - "name": "fourteen-hundrend-hours", - "origin": 0, - "start": "20180101T000000" - } -] diff --git a/snap/local/runtime-helpers/bin/edgex-cli-wrapper.sh b/snap/local/runtime-helpers/bin/edgex-cli-wrapper.sh index 85833c14..a3fbe4ed 100755 --- a/snap/local/runtime-helpers/bin/edgex-cli-wrapper.sh +++ b/snap/local/runtime-helpers/bin/edgex-cli-wrapper.sh @@ -1,10 +1,3 @@ #!/bin/sh -e -# configuration.toml is required for the client to run -if [ ! -f "$HOME/.edgex-cli/configuration.toml" ]; then - mkdir -p "$HOME/.edgex-cli" - cp "$SNAP/res/sample-configuration.toml" "$HOME/.edgex-cli/configuration.toml" - echo "Created $HOME/.edgex-cli/configuration.toml from sample configuration file." -fi - exec "$@" diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 6dbd9616..8f8637c2 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -20,61 +20,33 @@ apps: parts: version: plugin: nil - # the source dir is unrelated to this part and is used since it - # changes rarely and therefore will not trigger a new pull - source: . + # we need to include git, in case we are building the minimal-snap-build + build-packages: + - git + # as with static-packages part, the source dir is unrelated to this part and is used + # since it changes rarely and therefore will not trigger a new pull + source: snap/local/runtime-helpers override-pull: | cd $SNAPCRAFT_PROJECT_DIR - if [ -f VERSION ]; then - PROJECT_VERSION=$(cat VERSION) - else - PROJECT_VERSION=local-dev + GIT_VERSION=$(git describe --tags --abbrev=0 | sed 's/v//') + if [ -z "$GIT_VERSION" ]; then + GIT_VERSION="0.0.0" fi - - snapcraftctl set-version ${PROJECT_VERSION} + snapcraftctl set-version ${GIT_VERSION} config-common: plugin: dump source: snap/local/runtime-helpers - go: - plugin: nil - source: . - build-packages: [curl] - override-build: | - # use dpkg architecture to figure out our target arch - # note - we specifically don't use arch - case "$(dpkg --print-architecture)" in - amd64) - FILE_NAME=go1.15.2.linux-amd64.tar.gz - FILE_HASH=b49fda1ca29a1946d6bb2a5a6982cf07ccd2aba849289508ee0f9918f6bb4552 - ;; - arm64) - FILE_NAME=go1.15.2.linux-arm64.tar.gz - FILE_HASH=c8ec460cc82d61604b048f9439c06bd591722efce5cd48f49e19b5f6226bd36d - ;; - esac - # download the archive, failing on ssl cert problems - curl https://dl.google.com/go/$FILE_NAME -O - echo "$FILE_HASH $FILE_NAME" > sha256 - sha256sum -c sha256 | grep OK - tar -C $SNAPCRAFT_STAGE -xf go*.tar.gz --strip-components=1 - prime: - - "-*" - edgex-cli: source: . - source-type: git - plugin: make - build-packages: [git] - after: [go] + plugin: make + build-snaps: + - go/1.16/stable override-build: | cd $SNAPCRAFT_PART_SRC make build + install -DT "./bin/edgex-cli" "$SNAPCRAFT_PART_INSTALL/bin/edgex-cli" + install -DT "./Attribution.txt" "$SNAPCRAFT_PART_INSTALL/usr/share/doc/edgex-cli/Attribution.txt" + install -DT "./LICENSE" "$SNAPCRAFT_PART_INSTALL/usr/share/doc/edgex-cli/LICENSE" - install -DT "./edgex-cli" "$SNAPCRAFT_PART_INSTALL/bin/edgex-cli" - install -DT "./res/sample-configuration.toml" "$SNAPCRAFT_PART_INSTALL/res/sample-configuration.toml" - install -DT "./Attribution.txt" \ - "$SNAPCRAFT_PART_INSTALL/usr/share/doc/edgex-cli/Attribution.txt" - install -DT "./LICENSE" \ - "$SNAPCRAFT_PART_INSTALL/usr/share/doc/edgex-cli/LICENSE" diff --git a/version.go b/version.go new file mode 100644 index 00000000..db94adb3 --- /dev/null +++ b/version.go @@ -0,0 +1,7 @@ +package edgex + +// Global version for edgex-cli +var ( + BuildVersion string + BuildTime string +)