From: Sebastien Douheret Date: Fri, 3 Nov 2017 17:32:24 +0000 (+0100) Subject: Initial commit X-Git-Tag: v0.0.1^0 X-Git-Url: https://gerrit.automotivelinux.org/gerrit/gitweb?p=src%2Fxds%2Fxds-cli.git;a=commitdiff_plain;h=c35d7a0fc8bbb1f9123bb41a7b66e45ea2564dd2 Initial commit Signed-off-by: Sebastien Douheret --- c35d7a0fc8bbb1f9123bb41a7b66e45ea2564dd2 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d33064e --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +debug +glide.lock +bin/** +tools/** +vendor/** diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..6978301 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,87 @@ +{ + "version": "0.2.0", + "configurations": [{ + "name": "xds-cli (version)", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}", + "env": { + "GOPATH": "${workspaceRoot}/../../../..:${env:GOPATH}", + "XDS_APPNAME": "xds-cli", + "XDS_SERVER_URL": "localhost:8800", + "XDS_LOGLEVEL": "debug" + }, + "args": ["misc", "version"], + "showLog": false + }, + { + "name": "xds-cli (list)", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}", + "env": { + "GOPATH": "${workspaceRoot}/../../../..:${env:GOPATH}", + "XDS_APPNAME": "xds-cli", + "XDS_SERVER_URL": "localhost:8800", + "XDS_LOGLEVEL": "debug" + }, + "args": ["sdks", "list"], + "showLog": false + }, + { + "name": "xds-cli (add Projects)", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}", + "env": { + "GOPATH": "${workspaceRoot}/../../../..:${env:GOPATH}", + "XDS_APPNAME": "xds-cli", + "XDS_SERVER_URL": "localhost:8800", + "XDS_LOGLEVEL": "debug" + }, + "args": ["prj", "add", + "-type", "pm", + "-path", "/home/seb/xds-workspace/test1", + "-server-path", "/home/seb/xds-workspace/test1" + ], + "showLog": false + }, + { + "name": "xds-cli (exec Projects)", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}", + "env": { + "GOPATH": "${workspaceRoot}/../../../..:${env:GOPATH}", + "XDS_APPNAME": "xds-cli", + "XDS_SERVER_URL": "localhost:8800", + "XDS_LOGLEVEL": "debug" + }, + "args": ["exec", + "-id", "IW7B4EE-DBY4Z74_Agent-TCF", + "-rpath", "build", + "pwd && ls .." + ], + "showLog": false + }, + { + "name": "xds-cli (with xds-config.env)", + "type": "go", + "request": "launch", + "mode": "debug", + "program": "${workspaceRoot}", + "env": { + "GOPATH": "${workspaceRoot}/../../../..:${env:GOPATH}", + "XDS_APPNAME": "xds-cli", + "XDS_LOGLEVEL": "debug" + }, + "args": ["-c", "xds-config-sample.env", "sdks", "ls"], + "showLog": false + } + + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..a659df9 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,26 @@ +// Place your settings in this file to overwrite default and user settings. +{ + // Configure glob patterns for excluding files and folders. + "files.exclude": { + "**/.tmp": true, + ".git": true, + "glide.lock": true, + "vendor": true, + "debug": true, + "bin": true, + "tools": true + }, + // Words to add to dictionary for a workspace. + "cSpell.words": [ + "apiv", + "iosk", + "zhouhui", + "ldflags", + "socketio", + "xdsconfig", + "sdkid", + "godotenv", + "crosssdk", + "prjs" + ] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + 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. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ef2644d --- /dev/null +++ b/Makefile @@ -0,0 +1,153 @@ +# Makefile used to build xds-cli commands + +# Application Version +TARGET=xds-cli + + +# Retrieve git tag/commit to set version & sub-version strings +GIT_DESC := $(shell git describe --always --tags) +VERSION := $(firstword $(subst -, ,$(GIT_DESC))) +SUB_VERSION := $(wordlist 2,3,$(subst -, ,$(GIT_DESC))) +ifeq ($(VERSION), ) + VERSION := unknown-dev +endif +ifeq ($(SUB_VERSION), ) + SUB_VERSION := $(shell date +'%Y-%m-%d_%H%M%S') +endif + +# Configurable variables for installation (default /opt/AGL/...) +ifeq ($(origin DESTDIR), undefined) + DESTDIR := /opt/AGL/xds/cli +endif + +HOST_GOOS=$(shell go env GOOS) +HOST_GOARCH=$(shell go env GOARCH) +ARCH=$(HOST_GOOS)-$(HOST_GOARCH) +REPOPATH=github.com/iotbzh/$(TARGET) + +EXT= +ifeq ($(HOST_GOOS), windows) + EXT=.exe +endif + +mkfile_path := $(abspath $(lastword $(MAKEFILE_LIST))) +ROOT_SRCDIR := $(patsubst %/,%,$(dir $(mkfile_path))) +ROOT_GOPRJ := $(abspath $(ROOT_SRCDIR)/../../../..) +LOCAL_BINDIR := $(ROOT_SRCDIR)/bin +LOCAL_TOOLSDIR := $(ROOT_SRCDIR)/tools/${HOST_GOOS} +PACKAGE_DIR := $(ROOT_SRCDIR)/package + +export GOPATH := $(shell go env GOPATH):$(ROOT_GOPRJ) +export PATH := $(PATH):$(LOCAL_TOOLSDIR) + +VERBOSE_1 := -v +VERBOSE_2 := -v -x + +# Release or Debug mode +ifeq ($(filter 1,$(RELEASE) $(REL)),) + GO_LDFLAGS= + # disable compiler optimizations and inlining + GO_GCFLAGS=-N -l + BUILD_MODE="Debug mode" +else + # optimized code without debug info + GO_LDFLAGS=-s -w + GO_GCFLAGS= + BUILD_MODE="Release mode" +endif + + +ifeq ($(SUB_VERSION), ) + PACKAGE_ZIPFILE := $(TARGET)_$(ARCH)-v$(VERSION).zip +else + PACKAGE_ZIPFILE := $(TARGET)_$(ARCH)-v$(VERSION)_$(SUB_VERSION).zip +endif + +.PHONY: all +all: vendor build + +.PHONY: build +build: + @echo "### Build $(TARGET) (version $(VERSION), subversion $(SUB_VERSION) - $(BUILD_MODE))"; + @cd $(ROOT_SRCDIR); $(BUILD_ENV_FLAGS) go build $(VERBOSE_$(V)) -i -o $(LOCAL_BINDIR)/$(TARGET)$(EXT) -ldflags "$(GO_LDFLAGS) -X main.AppVersion=$(VERSION) -X main.AppSubVersion=$(SUB_VERSION)" -gcflags "$(GO_GCFLAGS)" . + +test: tools/glide + go test --race $(shell $(LOCAL_TOOLSDIR)/glide novendor) + +vet: tools/glide + go vet $(shell $(LOCAL_TOOLSDIR)/glide novendor) + +fmt: tools/glide + go fmt $(shell $(LOCAL_TOOLSDIR)/glide novendor) + +.PHONY: clean +clean: + rm -rf $(LOCAL_BINDIR)/* debug $(ROOT_GOPRJ)/pkg/*/$(REPOPATH) $(PACKAGE_DIR) + +.PHONY: distclean +distclean: clean + rm -rf $(LOCAL_BINDIR) $(ROOT_SRCDIR)/tools glide.lock vendor + + +.PHONY: scripts +scripts: + @mkdir -p $(LOCAL_BINDIR) && cp -rf scripts/*.sh scripts/xds-utils $(LOCAL_BINDIR) + +.PHONY: release +release: + RELEASE=1 make -f $(ROOT_SRCDIR)/Makefile clean build + +package: clean vendor build + @mkdir -p $(PACKAGE_DIR)/$(TARGET) + @cp -a $(LOCAL_BINDIR)/*cli$(EXT) $(PACKAGE_DIR)/$(TARGET) + @cp -r $(ROOT_SRCDIR)/conf.d $(ROOT_SRCDIR)/scripts $(PACKAGE_DIR)/$(TARGET) + cd $(PACKAGE_DIR) && zip -r $(ROOT_SRCDIR)/$(PACKAGE_ZIPFILE) ./$(TARGET) + +.PHONY: package-all +package-all: + @echo "# Build linux amd64..." + GOOS=linux GOARCH=amd64 RELEASE=1 make -f $(ROOT_SRCDIR)/Makefile package + @echo "# Build windows amd64..." + GOOS=windows GOARCH=amd64 RELEASE=1 make -f $(ROOT_SRCDIR)/Makefile package + @echo "# Build darwin amd64..." + GOOS=darwin GOARCH=amd64 RELEASE=1 make -f $(ROOT_SRCDIR)/Makefile package + make -f $(ROOT_SRCDIR)/Makefile clean + +.PHONY: install +install: + @test -e $(LOCAL_BINDIR)/$(TARGET)$(EXT) || { echo "Please execute first: make all\n"; exit 1; } + export DESTDIR=$(DESTDIR) && $(ROOT_SRCDIR)/scripts/install.sh + +.PHONY: uninstall +uninstall: + export DESTDIR=$(DESTDIR) && $(ROOT_SRCDIR)/scripts/install.sh uninstall + +vendor: tools/glide glide.yaml + $(LOCAL_TOOLSDIR)/glide install --strip-vendor + +vendor/debug: vendor + (cd vendor/github.com/iotbzh && \ + rm -rf xds-common && ln -s ../../../../xds-common && \ + rm -rf xds-agent && ln -s ../../../../xds-agent ) + +.PHONY: tools/glide +tools/glide: + @test -f $(LOCAL_TOOLSDIR)/glide || { \ + echo "Downloading glide"; \ + mkdir -p $(LOCAL_TOOLSDIR); \ + curl --silent -L https://glide.sh/get | GOBIN=$(LOCAL_TOOLSDIR) sh; \ + } + +help: + @echo "Main supported rules:" + @echo " all (default)" + @echo " build" + @echo " release" + @echo " clean" + @echo " package" + @echo " install / uninstall" + @echo " distclean" + @echo "" + @echo "Influential make variables:" + @echo " V - Build verbosity {0,1,2}." + @echo " BUILD_ENV_FLAGS - Environment added to 'go build'." diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c2b663 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +# xds-cli + +`xds-cli` is a command line tool used to control / interface X(cross) Development System. + +## Documentation + +Please find XDS User's Guide online at : + +[http://docs.automotivelinux.org/docs/devguides/en/dev/#xcross-development-system-user's-guide](http://docs.automotivelinux.org/docs/devguides/en/dev/#xcross-development-system-user's-guide) + +and `xds-cli` advanced documentation at : + +[http://docs.automotivelinux.org/docs/devguides/en/dev/reference/xds/part-2/4_xds-cli.html](http://docs.automotivelinux.org/docs/devguides/en/dev/reference/xds/part-2/4_xds-cli.html) diff --git a/cmd-exec.go b/cmd-exec.go new file mode 100644 index 0000000..612851f --- /dev/null +++ b/cmd-exec.go @@ -0,0 +1,166 @@ +package main + +import ( + "fmt" + "os" + "strings" + + "github.com/iotbzh/xds-agent/lib/apiv1" + common "github.com/iotbzh/xds-common/golib" + "github.com/joho/godotenv" + "github.com/urfave/cli" +) + +func initCmdExec(cmdDef *[]cli.Command) { + *cmdDef = append(*cmdDef, cli.Command{ + Name: "exec", + Usage: "execute a command in XDS", + Action: exec, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + EnvVar: "XDS_PROJECT_ID", + Usage: "project ID you want to build (mandatory variable)", + }, + cli.StringFlag{ + Name: "rpath", + EnvVar: "XDS_RPATH", + Usage: "relative path into project", + }, + cli.StringFlag{ + Name: "sdkid", + EnvVar: "XDS_SDK_ID", + Usage: "Cross Sdk ID to use to build project", + }, + }, + }) +} + +func exec(ctx *cli.Context) error { + prjID := ctx.String("id") + confFile := ctx.String("config") + rPath := ctx.String("rPath") + sdkid := ctx.String("sdkid") + + // Check mandatory args + if prjID == "" { + return cli.NewExitError("project id must be set (see --id option)", 1) + } + + // Load config file if requested + envMap := make(map[string]string) + if confFile != "" { + if !common.Exists(confFile) { + exitError(1, "Error env config file not found") + } + // Load config file variables that will overwrite env variables + err := godotenv.Overload(confFile) + if err != nil { + exitError(1, "Error loading env config file "+confFile) + } + envMap, err = godotenv.Read(confFile) + if err != nil { + exitError(1, "Error reading env config file "+confFile) + } + } + + argsCommand := make([]string, len(ctx.Args())) + copy(argsCommand, ctx.Args()) + Log.Infof("Execute: /exec %v", argsCommand) + + // Log useful info for debugging + ver := apiv1.XDSVersion{} + XdsVersionGet(&ver) + Log.Infof("XDS version: %v", ver) + + // Process Socket IO events + type exitResult struct { + error error + code int + } + exitChan := make(chan exitResult, 1) + + IOsk.On("disconnection", func(err error) { + exitChan <- exitResult{err, 2} + }) + + outFunc := func(timestamp, stdout, stderr string) { + tm := "" + if ctx.Bool("WithTimestamp") { + tm = timestamp + "| " + } + if stdout != "" { + fmt.Printf("%s%s", tm, stdout) + } + if stderr != "" { + fmt.Fprintf(os.Stderr, "%s%s", tm, stderr) + } + } + + IOsk.On(apiv1.ExecOutEvent, func(ev apiv1.ExecOutMsg) { + outFunc(ev.Timestamp, ev.Stdout, ev.Stderr) + }) + + IOsk.On(apiv1.ExecExitEvent, func(ev apiv1.ExecExitMsg) { + exitChan <- exitResult{ev.Error, ev.Code} + }) + + // Retrieve the project definition + prj := apiv1.ProjectConfig{} + if err := HTTPCli.Get("/projects/"+prjID, &prj); err != nil { + return cli.NewExitError(err, 1) + } + + // Auto setup rPath if needed + if rPath == "" { + cwd, err := os.Getwd() + if err == nil { + fldRp := prj.ClientPath + if !strings.HasPrefix(fldRp, "/") { + fldRp = "/" + fldRp + } + Log.Debugf("Try to auto-setup rPath: cwd=%s ; ClientPath=%s", cwd, fldRp) + if sp := strings.SplitAfter(cwd, fldRp); len(sp) == 2 { + rPath = strings.Trim(sp[1], "/") + Log.Debugf("Auto-setup rPath to: '%s'", rPath) + } + } + } + + // Build env + Log.Debugf("Command env: %v", envMap) + env := []string{} + for k, v := range envMap { + env = append(env, k+"="+v) + } + + // Send build command + args := apiv1.ExecArgs{ + ID: prjID, + SdkID: sdkid, + Cmd: strings.Trim(argsCommand[0], " "), + Args: argsCommand[1:], + Env: env, + RPath: rPath, + CmdTimeout: 60, + } + + LogPost("POST /exec %v", args) + if err := HTTPCli.Post("/exec", args, nil); err != nil { + return cli.NewExitError(err.Error(), 1) + } + + // Wait exit + select { + case res := <-exitChan: + errStr := "" + if res.code == 0 { + Log.Debugln("Exit successfully") + } + if res.error != nil { + Log.Debugln("Exit with ERROR: ", res.error.Error()) + errStr = res.error.Error() + } + return cli.NewExitError(errStr, res.code) + } +} diff --git a/cmd-misc.go b/cmd-misc.go new file mode 100644 index 0000000..b4b579d --- /dev/null +++ b/cmd-misc.go @@ -0,0 +1,66 @@ +package main + +import ( + "fmt" + + "github.com/iotbzh/xds-agent/lib/apiv1" + "github.com/urfave/cli" +) + +func initCmdMisc(cmdDef *[]cli.Command) { + *cmdDef = append(*cmdDef, cli.Command{ + Name: "misc", + HideHelp: true, + Usage: "miscellaneous commands group", + Subcommands: []cli.Command{ + { + Name: "version", + Aliases: []string{"v"}, + Usage: "Get version of XDS agent and XDS server", + Action: xdsVersion, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "verbose, v", + Usage: "display verbose output", + }, + }, + }, + }, + }) +} + +func xdsVersion(ctx *cli.Context) error { + verbose := ctx.Bool("verbose") + + // Get version + ver := apiv1.XDSVersion{} + if err := XdsVersionGet(&ver); err != nil { + return cli.NewExitError(err.Error(), 1) + } + + writer := NewTableWriter() + fmt.Fprintln(writer, "Agent ID:\t", ver.Client.ID) + v := ver.Client.Version + if verbose { + v += " (" + ver.Client.VersionGitTag + ")" + } + fmt.Fprintln(writer, " Version:\t", v) + if verbose { + fmt.Fprintln(writer, " API Version:\t", ver.Client.APIVersion) + } + + for _, svr := range ver.Server { + fmt.Fprintln(writer, "Server ID:\t", svr.ID) + v = svr.Version + if verbose { + v += " (" + svr.VersionGitTag + ")" + } + fmt.Fprintln(writer, " Version:\t", v) + if verbose { + fmt.Fprintln(writer, " API Version:\t", svr.APIVersion) + } + } + writer.Flush() + + return nil +} diff --git a/cmd-projects.go b/cmd-projects.go new file mode 100644 index 0000000..9a113db --- /dev/null +++ b/cmd-projects.go @@ -0,0 +1,209 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/iotbzh/xds-agent/lib/apiv1" + "github.com/urfave/cli" +) + +func initCmdProjects(cmdDef *[]cli.Command) { + *cmdDef = append(*cmdDef, cli.Command{ + Name: "projects", + Aliases: []string{"prj"}, + HideHelp: true, + Usage: "project commands group", + Subcommands: []cli.Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "Add a new project", + Action: projectsAdd, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "label", + Usage: "project label (free form string)", + }, + cli.StringFlag{ + Name: "path", + Usage: "project local path", + }, + cli.StringFlag{ + Name: "server-path", + Usage: "project server path (only used with pathmap type)", + }, + cli.StringFlag{ + Name: "type", + Usage: "project type (pathmap|pm, cloudsync|sc)", + }, + }, + }, + { + Name: "get", + Usage: "Get a property of a project", + Action: projectsGet, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "project id", + }, + }, + }, + { + Name: "list", + Aliases: []string{"ls"}, + Usage: "List existing projects", + Action: projectsList, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "verbose, v", + Usage: "display verbose output", + }, + }, + }, + { + Name: "remove", + Aliases: []string{"rm"}, + Usage: "Remove an existing project", + Action: projectsRemove, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "project id", + }, + }, + }, + { + Name: "sync", + Aliases: []string{}, + Usage: "Force synchronization of project sources", + Action: projectsSync, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "project id", + }, + }, + }, + }, + }) +} + +func projectsList(ctx *cli.Context) error { + // Get projects list + prjs := []apiv1.ProjectConfig{} + if err := ProjectsListGet(&prjs); err != nil { + return cli.NewExitError(err.Error(), 1) + } + _displayProjects(prjs, ctx.Bool("verbose")) + return nil +} + +func projectsGet(ctx *cli.Context) error { + id := GetID(ctx) + if id == "" { + return cli.NewExitError("id parameter or option must be set", 1) + } + prjs := make([]apiv1.ProjectConfig, 1) + if err := HTTPCli.Get("/projects/"+id, &prjs[0]); err != nil { + return cli.NewExitError(err, 1) + } + _displayProjects(prjs, true) + return nil +} + +func _displayProjects(prjs []apiv1.ProjectConfig, verbose bool) { + // Display result + first := true + writer := NewTableWriter() + for _, folder := range prjs { + if verbose { + if !first { + fmt.Fprintln(writer) + } + fmt.Fprintln(writer, "ID:\t", folder.ID) + fmt.Fprintln(writer, "Label:\t", folder.Label) + fmt.Fprintln(writer, "Path type:\t", folder.Type) + fmt.Fprintln(writer, "Local Path:\t", folder.ClientPath) + if folder.Type != apiv1.TypeCloudSync { + fmt.Fprintln(writer, "Server Path:\t", folder.ServerPath) + } + fmt.Fprintln(writer, "Status:\t", folder.Status) + fmt.Fprintln(writer, "Is in Sync:\t", folder.IsInSync) + ds := folder.DefaultSdk + if ds == "" { + ds = "-" + } + fmt.Fprintln(writer, "Default Sdk:\t", ds) + + } else { + if first { + fmt.Fprintln(writer, "ID\t Label\t LocalPath") + } + fmt.Fprintln(writer, folder.ID, "\t", folder.Label, "\t", folder.ClientPath) + } + first = false + } + writer.Flush() +} + +func projectsAdd(ctx *cli.Context) error { + + // Decode project type + var ptype apiv1.ProjectType + switch strings.ToLower(ctx.String("type")) { + case "pathmap", "pm": + ptype = apiv1.TypePathMap + case "cloudsync", "cs": + ptype = apiv1.TypeCloudSync + default: + return cli.NewExitError("Unknown project type", 1) + } + + prj := apiv1.ProjectConfig{ + ServerID: XdsServerIDGet(), + Label: ctx.String("label"), + Type: ptype, + ClientPath: ctx.String("path"), + ServerPath: ctx.String("server-path"), + } + + Log.Infof("POST /project %v", prj) + newPrj := apiv1.ProjectConfig{} + err := HTTPCli.Post("/projects", prj, &newPrj) + if err != nil { + return cli.NewExitError(err, 1) + } + + fmt.Printf("New project '%s' (id %v) successfully created.\n", newPrj.Label, newPrj.ID) + + return nil +} + +func projectsRemove(ctx *cli.Context) error { + var res apiv1.ProjectConfig + id := GetID(ctx) + if id == "" { + return cli.NewExitError("id parameter or option must be set", 1) + } + + if err := HTTPCli.Delete("/projects/"+id, &res); err != nil { + return cli.NewExitError(err, 1) + } + + fmt.Println("Project ID " + res.ID + " successfully deleted.") + return nil +} + +func projectsSync(ctx *cli.Context) error { + id := GetID(ctx) + if id == "" { + return cli.NewExitError("id parameter or option must be set", 1) + } + if err := HTTPCli.Post("/projects/sync/"+id, "", nil); err != nil { + return cli.NewExitError(err, 1) + } + fmt.Println("Sync successfully resquested.") + return nil +} diff --git a/cmd-sdks.go b/cmd-sdks.go new file mode 100644 index 0000000..8568b3a --- /dev/null +++ b/cmd-sdks.go @@ -0,0 +1,136 @@ +package main + +import ( + "fmt" + "strconv" + + "github.com/iotbzh/xds-agent/lib/apiv1" + "github.com/urfave/cli" +) + +func initCmdSdks(cmdDef *[]cli.Command) { + *cmdDef = append(*cmdDef, cli.Command{ + Name: "sdks", + Aliases: []string{"sdk"}, + HideHelp: true, + Usage: "SDKs commands group", + Subcommands: []cli.Command{ + { + Name: "add", + Aliases: []string{"a"}, + Usage: "Add a new SDK", + Action: sdksAdd, + }, + { + Name: "get", + Usage: "Get a property of a SDK", + Action: sdksGet, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "sdk id", + }, + }, + }, + { + Name: "list", + Aliases: []string{"ls"}, + Usage: "List installed SDKs", + Action: sdksList, + Flags: []cli.Flag{ + cli.BoolFlag{ + Name: "verbose, v", + Usage: "display verbose output", + }, + }, + }, + { + Name: "remove", + Aliases: []string{"rm"}, + Usage: "Remove an existing SDK", + Action: sdksRemove, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "id", + Usage: "sdk id", + }, + }, + }, + }, + }) +} + +func sdksList(ctx *cli.Context) error { + // Get SDKs list + sdks := []apiv1.SDK{} + if err := sdksListGet(&sdks); err != nil { + return cli.NewExitError(err.Error(), 1) + } + _displaySdks(sdks, ctx.Bool("verbose")) + return nil +} + +func sdksGet(ctx *cli.Context) error { + id := GetID(ctx) + if id == "" { + return cli.NewExitError("id parameter or option must be set", 1) + } + sdks := apiv1.SDK{} + url := "server/" + strconv.Itoa(XdsServerIndexGet()) + "/sdks/" + id + if err := HTTPCli.Get(url, &sdks); err != nil { + return cli.NewExitError(err.Error(), 1) + } + _displaySdks([]apiv1.SDK{sdks}, true) + return nil +} + +func _displaySdks(sdks []apiv1.SDK, verbose bool) { + // Display result + first := true + writer := NewTableWriter() + for _, s := range sdks { + if verbose { + if !first { + fmt.Fprintln(writer) + } + fmt.Fprintln(writer, "ID\t"+s.ID) + fmt.Fprintln(writer, "Name\t"+s.Name) + fmt.Fprintln(writer, "Profile\t"+s.Profile) + fmt.Fprintln(writer, "Arch\t"+s.Arch) + fmt.Fprintln(writer, "Version\t"+s.Version) + fmt.Fprintln(writer, "Path\t"+s.Path) + + } else { + if first { + fmt.Fprintf(writer, "List of installed SDKs: \n") + fmt.Fprintf(writer, " ID\tNAME\n") + } + fmt.Fprintf(writer, " %s\t%s\n", s.ID, s.Name) + } + first = false + } + writer.Flush() +} + +func sdksListGet(sdks *[]apiv1.SDK) error { + url := "server/" + strconv.Itoa(XdsServerIndexGet()) + "/sdks" + if err := HTTPCli.Get(url, &sdks); err != nil { + return err + } + Log.Debugf("Result of %s: %v", url, sdks) + + return nil +} + +func sdksAdd(ctx *cli.Context) error { + return fmt.Errorf("not supported yet") +} + +func sdksRemove(ctx *cli.Context) error { + id := GetID(ctx) + if id == "" { + return cli.NewExitError("id parameter or option must be set", 1) + } + + return fmt.Errorf("not supported yet") +} diff --git a/glide.yaml b/glide.yaml new file mode 100644 index 0000000..5822fdc --- /dev/null +++ b/glide.yaml @@ -0,0 +1,25 @@ +package: github.com/iotbzh/xds-cli +license: Apache-2.0 +owners: +- name: Sebastien Douheret + email: sebastien@iot.bzh +import: +- package: github.com/urfave/cli + version: ^1.19.1 +- package: github.com/Sirupsen/logrus + version: ^0.11.5 +- package: github.com/zhouhui8915/engine.io-go +- package: github.com/sebd71/go-socket.io-client + version: 46defcb47f +- package: github.com/iotbzh/xds-agent + version: ^1.0.0-rc.1 + subpackages: + - agent +- package: github.com/iotbzh/xds-common + version: ^0.1.0 + subpackages: + - golib/common +- package: github.com/joho/godotenv + version: ^1.1.0 + subpackages: + - cmd/godotenv diff --git a/main.go b/main.go new file mode 100644 index 0000000..c8d2095 --- /dev/null +++ b/main.go @@ -0,0 +1,263 @@ +// xds-cli: command line tool used to control / interface X(cross) Development System. +package main + +import ( + "fmt" + "os" + "regexp" + "sort" + "strings" + "text/tabwriter" + + "github.com/Sirupsen/logrus" + common "github.com/iotbzh/xds-common/golib" + socketio_client "github.com/sebd71/go-socket.io-client" + "github.com/urfave/cli" +) + +var appAuthors = []cli.Author{ + cli.Author{Name: "Sebastien Douheret", Email: "sebastien@iot.bzh"}, +} + +// AppName name of this application +var AppName = "xds-cli" + +// AppNativeName native command name that this application can overload +var AppNativeName = "cli" + +// AppVersion Version of this application +// (set by Makefile) +var AppVersion = "?.?.?" + +// AppSubVersion is the git tag id added to version string +// Should be set by compilation -ldflags "-X main.AppSubVersion=xxx" +// (set by Makefile) +var AppSubVersion = "unknown-dev" + +// Application details +const ( + appCopyright = "Apache-2.0" + defaultLogLevel = "error" +) + +// Log Global variable that hold logger +var Log = logrus.New() + +// HTTPCli Global variable that hold HTTP Client +var HTTPCli *common.HTTPClient + +// IOsk Global variable that hold SocketIo client +var IOsk *socketio_client.Client + +// exitError exists this program with the specified error +func exitError(code int, f string, a ...interface{}) { + err := fmt.Sprintf(f, a...) + fmt.Fprintf(os.Stderr, err+"\n") + os.Exit(code) +} + +// main +func main() { + + // Allow to set app name from cli (useful for debugging) + if AppName == "" { + AppName = os.Getenv("XDS_APPNAME") + } + if AppName == "" { + panic("Invalid setup, AppName not define !") + } + if AppNativeName == "" { + AppNativeName = AppName[4:] + } + appUsage := fmt.Sprintf("command line tool for X(cross) Development System.") + appDescription := fmt.Sprintf("%s utility for X(cross) Development System\n", AppName) + /* SEB UPDATE DOC + appDescription += ` + xds-cli configuration is driven either by environment variables or by command line + options or using a config file knowning that the following priority order is used: + 1. use option value (for example use project ID set by --id option), + 2. else use variable 'XDS_xxx' (for example 'XDS_PROJECT_ID' variable) when a + config file is specified with '--config|-c' option, + 3. else use 'XDS_xxx' (for example 'XDS_PROJECT_ID') environment variable. + ` + */ + // Create a new App instance + app := cli.NewApp() + app.Name = AppName + app.Usage = appUsage + app.Version = AppVersion + " (" + AppSubVersion + ")" + app.Authors = appAuthors + app.Copyright = appCopyright + app.Metadata = make(map[string]interface{}) + app.Metadata["version"] = AppVersion + app.Metadata["git-tag"] = AppSubVersion + app.Metadata["logger"] = Log + + // Create env vars help + dynDesc := "\nENVIRONMENT VARIABLES:" + for _, f := range app.Flags { + var env, usage string + switch f.(type) { + case cli.StringFlag: + fs := f.(cli.StringFlag) + env = fs.EnvVar + usage = fs.Usage + case cli.BoolFlag: + fb := f.(cli.BoolFlag) + env = fb.EnvVar + usage = fb.Usage + default: + exitError(1, "Un-implemented option type") + } + if env != "" { + dynDesc += fmt.Sprintf("\n %s \t\t %s", env, usage) + } + } + app.Description = appDescription + dynDesc + + // Declare global flags + app.Flags = []cli.Flag{ + cli.StringFlag{ + Name: "config, c", + EnvVar: "XDS_CONFIG", + Usage: "env config file to source on startup", + }, + cli.StringFlag{ + Name: "log, l", + EnvVar: "XDS_LOGLEVEL", + Usage: "logging level (supported levels: panic, fatal, error, warn, info, debug)", + Value: defaultLogLevel, + }, + cli.StringFlag{ + Name: "url", + EnvVar: "XDS_SERVER_URL", + Value: "localhost:8000", + Usage: "remote XDS server url", + }, + cli.BoolFlag{ + Name: "timestamp, ts", + EnvVar: "XDS_TIMESTAMP", + Usage: "prefix output with timestamp", + }, + } + + // Declare commands + app.Commands = []cli.Command{} + + initCmdProjects(&app.Commands) + initCmdSdks(&app.Commands) + initCmdExec(&app.Commands) + initCmdMisc(&app.Commands) + + sort.Sort(cli.FlagsByName(app.Flags)) + sort.Sort(cli.CommandsByName(app.Commands)) + + app.Before = func(ctx *cli.Context) error { + var err error + loglevel := ctx.String("log") + // Set logger level and formatter + if Log.Level, err = logrus.ParseLevel(loglevel); err != nil { + msg := fmt.Sprintf("Invalid log level : \"%v\"\n", loglevel) + return cli.NewExitError(msg, 1) + } + Log.Formatter = &logrus.TextFormatter{} + + Log.Infof("%s version: %s", AppName, app.Version) + // SEB Add again Log.Debugf("Environment: %v", os.Environ()) + + if err = XdsConnInit(ctx); err != nil { + // Directly call HandleExitCoder to avoid to print help (ShowAppHelp) + // Note that this function wil never return and program will exit + cli.HandleExitCoder(err) + } + + return nil + } + + // Close HTTP client and WS connection on exit + defer func() { + XdsConnClose() + }() + + app.Run(os.Args) +} + +// XdsConnInit Initialized HTTP and WebSocket connection to XDS agent +func XdsConnInit(ctx *cli.Context) error { + var err error + + // Define HTTP and WS url + baseURL := ctx.String("url") + if !strings.HasPrefix(ctx.String("url"), "http://") { + baseURL = "http://" + ctx.String("url") + } + + // Create HTTP client + Log.Debugln("Connect HTTP client on ", baseURL) + conf := common.HTTPClientConfig{ + URLPrefix: "/api/v1", + HeaderClientKeyName: "Xds-Agent-Sid", + CsrfDisable: true, + LogOut: Log.Out, + LogPrefix: "XDSAGENT: ", + LogLevel: common.HTTPLogLevelWarning, + } + + HTTPCli, err = common.HTTPNewClient(baseURL, conf) + if err != nil { + errmsg := err.Error() + if m, err := regexp.MatchString("Get http.?://", errmsg); m && err == nil { + i := strings.LastIndex(errmsg, ":") + errmsg = "Cannot connection to " + baseURL + errmsg[i:] + } + return cli.NewExitError(errmsg, 1) + } + HTTPCli.SetLogLevel(ctx.String("loglevel")) + + // Create io Websocket client + Log.Debugln("Connecting IO.socket client on ", baseURL) + + opts := &socketio_client.Options{ + Transport: "websocket", + Header: make(map[string][]string), + } + opts.Header["XDS-AGENT-SID"] = []string{HTTPCli.GetClientID()} + + IOsk, err = socketio_client.NewClient(baseURL, opts) + if err != nil { + return cli.NewExitError("IO.socket connection error: "+err.Error(), 1) + } + + IOsk.On("error", func(err error) { + fmt.Println("ERROR Websocket: ", err.Error()) + }) + + ctx.App.Metadata["httpCli"] = HTTPCli + ctx.App.Metadata["ioskCli"] = IOsk + + return nil +} + +// XdsConnClose Terminate connection to XDS agent +func XdsConnClose() { + Log.Debugf("Closing HTTP client session...") + /* TODO + if httpCli, ok := app.Metadata["httpCli"]; ok { + c := httpCli.(*common.HTTPClient) + } + */ + + Log.Debugf("Closing WebSocket connection...") + /* + if ioskCli, ok := app.Metadata["ioskCli"]; ok { + c := ioskCli.(*socketio_client.Client) + } + */ +} + +// NewTableWriter Create a writer that inserts padding around tab-delimited +func NewTableWriter() *tabwriter.Writer { + writer := new(tabwriter.Writer) + writer.Init(os.Stdout, 0, 8, 0, '\t', 0) + return writer +} diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..6a05ef3 --- /dev/null +++ b/utils.go @@ -0,0 +1,73 @@ +package main + +import ( + "encoding/json" + + "github.com/iotbzh/xds-agent/lib/apiv1" + "github.com/urfave/cli" +) + +var cacheXdsVersion *apiv1.XDSVersion + +// XdsVersionGet Get version of XDS agent & server +func XdsVersionGet(ver *apiv1.XDSVersion) error { + // Use cached data + if cacheXdsVersion != nil { + ver = cacheXdsVersion + return nil + } + + dataVer := apiv1.XDSVersion{} + if err := HTTPCli.Get("/version", &dataVer); err != nil { + return err + } + + cacheXdsVersion = &dataVer + *ver = dataVer + return nil +} + +// XdsServerIDGet returns the XDS Server ID +func XdsServerIDGet() string { + ver := apiv1.XDSVersion{} + if err := XdsVersionGet(&ver); err != nil { + return "" + } + if len(ver.Server) < 1 { + return "" + } + return ver.Server[XdsServerIndexGet()].ID +} + +// XdsServerIndexGet returns the index number of XDS Server +func XdsServerIndexGet() int { + // FIXME support multiple server + return 0 +} + +// ProjectsListGet Get the list of existing projects +func ProjectsListGet(prjs *[]apiv1.ProjectConfig) error { + var data []byte + if err := HTTPCli.HTTPGet("/projects", &data); err != nil { + return err + } + Log.Debugf("Result of /projects: %v", string(data[:])) + + return json.Unmarshal(data, &prjs) +} + +// LogPost Helper to log a POST request +func LogPost(format string, data interface{}) { + b, _ := json.Marshal(data) + Log.Infof(format, string(b)) +} + +// GetID Return a string ID set with --id option or as simple parameter +func GetID(ctx *cli.Context) string { + id := ctx.String("id") + idArgs := ctx.Args().First() + if id == "" && idArgs != "" { + id = idArgs + } + return id +}