From 919863e50b28aefe1e939018826f64d10b17a0db Mon Sep 17 00:00:00 2001 From: Abhijit Dasgupta Date: Thu, 21 Nov 2019 13:27:42 +0000 Subject: [PATCH] ELIOT Command Line Interface Commit elcli GoLang Code Commit for operations 1. elcli init 2. elcli reset 3. elcli cleanup 4. elcli --help Signed-off-by: Abhijit Dasgupta Change-Id: If3637f312d2371ceac2b2546c4310bf6f9bfd6f4 --- blueprints/common/elcli/elcli/LICENSE | 202 +++++++++++ blueprints/common/elcli/elcli/cmd/clean.go | 65 ++++ .../common/elcli/elcli/cmd/common/constant.go | 46 +++ blueprints/common/elcli/elcli/cmd/common/types.go | 88 +++++ blueprints/common/elcli/elcli/cmd/init.go | 80 +++++ blueprints/common/elcli/elcli/cmd/join.go | 144 ++++++++ blueprints/common/elcli/elcli/cmd/reset.go | 59 +++ blueprints/common/elcli/elcli/cmd/root.go | 107 ++++++ .../common/elcli/elcli/cmd/util/centosinstaller.go | 162 +++++++++ blueprints/common/elcli/elcli/cmd/util/cleanup.go | 59 +++ blueprints/common/elcli/elcli/cmd/util/common.go | 206 +++++++++++ .../common/elcli/elcli/cmd/util/dockerinstaller.go | 67 ++++ .../common/elcli/elcli/cmd/util/k8sinstaller.go | 74 ++++ blueprints/common/elcli/elcli/cmd/util/setup.go | 67 ++++ .../common/elcli/elcli/cmd/util/ubuntuinstaller.go | 396 +++++++++++++++++++++ blueprints/common/elcli/elcli/main.go | 22 ++ .../common/elcli/github.com/fsnotify/fsnotify | 1 + blueprints/common/elcli/github.com/hashicorp/hcl | 1 + .../common/elcli/github.com/magiconair/properties | 1 + .../common/elcli/github.com/mitchellh/go-homedir | 1 + .../common/elcli/github.com/mitchellh/mapstructure | 1 + .../common/elcli/github.com/pelletier/go-toml | 1 + blueprints/common/elcli/github.com/spf13/afero | 1 + blueprints/common/elcli/github.com/spf13/cast | 1 + blueprints/common/elcli/github.com/spf13/cobra | 1 + blueprints/common/elcli/github.com/spf13/elcli.tar | Bin 0 -> 81920 bytes .../elcli/github.com/spf13/jwalterweatherman | 1 + blueprints/common/elcli/github.com/spf13/pflag | 1 + blueprints/common/elcli/github.com/spf13/viper | 1 + blueprints/common/elcli/github.com/subosito/gotenv | 1 + .../common/elcli/github.com/trial/app/trialroot.go | 19 + blueprints/common/elcli/golang.org/x/lint | 1 + blueprints/common/elcli/golang.org/x/sys | 1 + blueprints/common/elcli/golang.org/x/text | 1 + blueprints/common/elcli/golang.org/x/tools | 1 + blueprints/common/elcli/gopkg.in/yaml.v2 | 1 + 36 files changed, 1881 insertions(+) create mode 100644 blueprints/common/elcli/elcli/LICENSE create mode 100644 blueprints/common/elcli/elcli/cmd/clean.go create mode 100644 blueprints/common/elcli/elcli/cmd/common/constant.go create mode 100644 blueprints/common/elcli/elcli/cmd/common/types.go create mode 100644 blueprints/common/elcli/elcli/cmd/init.go create mode 100644 blueprints/common/elcli/elcli/cmd/join.go create mode 100644 blueprints/common/elcli/elcli/cmd/reset.go create mode 100644 blueprints/common/elcli/elcli/cmd/root.go create mode 100644 blueprints/common/elcli/elcli/cmd/util/centosinstaller.go create mode 100644 blueprints/common/elcli/elcli/cmd/util/cleanup.go create mode 100644 blueprints/common/elcli/elcli/cmd/util/common.go create mode 100644 blueprints/common/elcli/elcli/cmd/util/dockerinstaller.go create mode 100644 blueprints/common/elcli/elcli/cmd/util/k8sinstaller.go create mode 100644 blueprints/common/elcli/elcli/cmd/util/setup.go create mode 100644 blueprints/common/elcli/elcli/cmd/util/ubuntuinstaller.go create mode 100644 blueprints/common/elcli/elcli/main.go create mode 160000 blueprints/common/elcli/github.com/fsnotify/fsnotify create mode 160000 blueprints/common/elcli/github.com/hashicorp/hcl create mode 160000 blueprints/common/elcli/github.com/magiconair/properties create mode 160000 blueprints/common/elcli/github.com/mitchellh/go-homedir create mode 160000 blueprints/common/elcli/github.com/mitchellh/mapstructure create mode 160000 blueprints/common/elcli/github.com/pelletier/go-toml create mode 160000 blueprints/common/elcli/github.com/spf13/afero create mode 160000 blueprints/common/elcli/github.com/spf13/cast create mode 160000 blueprints/common/elcli/github.com/spf13/cobra create mode 100644 blueprints/common/elcli/github.com/spf13/elcli.tar create mode 160000 blueprints/common/elcli/github.com/spf13/jwalterweatherman create mode 160000 blueprints/common/elcli/github.com/spf13/pflag create mode 160000 blueprints/common/elcli/github.com/spf13/viper create mode 160000 blueprints/common/elcli/github.com/subosito/gotenv create mode 100644 blueprints/common/elcli/github.com/trial/app/trialroot.go create mode 160000 blueprints/common/elcli/golang.org/x/lint create mode 160000 blueprints/common/elcli/golang.org/x/sys create mode 160000 blueprints/common/elcli/golang.org/x/text create mode 160000 blueprints/common/elcli/golang.org/x/tools create mode 160000 blueprints/common/elcli/gopkg.in/yaml.v2 diff --git a/blueprints/common/elcli/elcli/LICENSE b/blueprints/common/elcli/elcli/LICENSE new file mode 100644 index 0000000..d645695 --- /dev/null +++ b/blueprints/common/elcli/elcli/LICENSE @@ -0,0 +1,202 @@ + + 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/blueprints/common/elcli/elcli/cmd/clean.go b/blueprints/common/elcli/elcli/cmd/clean.go new file mode 100644 index 0000000..57cf158 --- /dev/null +++ b/blueprints/common/elcli/elcli/cmd/clean.go @@ -0,0 +1,65 @@ +/* +Package cmd +Copyright © 2019 NAME HERE + +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 ( + "fmt" + + "github.com/spf13/cobra" + "elcli/cmd/util" +) + +var ( + elcliCleanCmdLongDescription = + ` + +-------------------------------------------------+ + | To complete cleanup execute this command. The | + | command will remove all the configurations, | + | clear up ports used by ELIOT cluster & uninstall| + | all software. | + +-------------------------------------------------| + ` + elcliCleanExample = ` + +-------------------------------------------------+ + | elcli clean | + +-------------------------------------------------+ + ` +) + +// cleanCmd represents the clean command +var cleanCmd = &cobra.Command{ + Use: "clean", + Short: "ELIOT Cluster Uninstall", + Long: elcliCleanCmdLongDescription, + Example: elcliCleanExample, + RunE: func(cmd *cobra.Command, args []string) error{ + fmt.Println("clean called") + err := util.EliotClean() + str:= util.GetOSVersion() + fmt.Println("Print value of GetOSVersion %s", str) + + if(err != nil) { + return err + } + return nil + }, +} + +func init() { + rootCmd.AddCommand(cleanCmd) +} diff --git a/blueprints/common/elcli/elcli/cmd/common/constant.go b/blueprints/common/elcli/elcli/cmd/common/constant.go new file mode 100644 index 0000000..7f0d020 --- /dev/null +++ b/blueprints/common/elcli/elcli/cmd/common/constant.go @@ -0,0 +1,46 @@ +/* +Copyright 2019 The ELIOT Authors. + +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 common + +const ( + //DefaultDockerVersion is the current default version of Docker + DefaultDockerVersion = "18.06.0" + + //DefaultK8SVersion is the current default version of K8S + DefaultK8SVersion = "1.14.1" + + // K8SImageRepository sets the image repository of Kubernetes + K8SImageRepository = "image-repository" + + //K8SPodNetworkCidr sets pod network cidr of Kubernetes + K8SPodNetworkCidr = "pod-network-cidr" + + //DockerVersion sets the version of Docker to be used + DockerVersion = "docker-version" + + //KubernetesVersion sets the version of Kuberneted to be used + KubernetesVersion = "kubernetes-version" + + //K8SAPIServerIPPort sets the IP:Port of Kubernetes api-server + K8SAPIServerIPPort = "k8sserverip" + + //EliotCloudNodeIP sets the IP of KubeEdge cloud component + EliotCloudNodeIP = "eliotcloudnodeip" + + //EliotEdgeNodeID Node unique idenfitcation string + EliotEdgeNodeID = "eliotedgenodeid" +) \ No newline at end of file diff --git a/blueprints/common/elcli/elcli/cmd/common/types.go b/blueprints/common/elcli/elcli/cmd/common/types.go new file mode 100644 index 0000000..b7b77de --- /dev/null +++ b/blueprints/common/elcli/elcli/cmd/common/types.go @@ -0,0 +1,88 @@ +/* Copyright 2019 The ELIOT Authors. + +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 common + +var ( + //Setup having option. + Setup string + //Masters list + Masters []string + //Nodes list + Nodes []string +) + +// InitOptions Strucutre +type InitOptions struct { + KubernetesVersion string + DockerVersion string + K8SImageRepository string + K8SPodNetworkCidr string +} + +//JoinOptions has the kubeedge cloud init information filled by CLI +type JoinOptions struct { + InitOptions + CertPath string + CloudCoreIP string + K8SAPIServerIPPort string + EdgeNodeID string +} + +//InstallState enum set used for verifying a tool version is installed in host +type InstallState uint8 + +//Difference enum values for type InstallState +const ( + NewInstallRequired InstallState = iota + AlreadySameVersionExist + DefVerInstallRequired + VersionNAInRepo +) + +//ModuleRunning is defined to know the running status of KubeEdge components +type ModuleRunning uint8 + +//Different possible values for ModuleRunning type +const ( + NoneRunning ModuleRunning = iota + KubeEdgeCloudRunning + KubeEdgeEdgeRunning +) + +//ToolsInstaller interface for tools with install and teardown methods. +type ToolsInstaller interface { + InstallTools() error + TearDown() error +} + +//OSTypeInstaller interface for methods to be executed over a specified OS distribution type +type OSTypeInstaller interface { + IsToolVerInRepo(string, string) (bool, error) + IsDockerInstalled(string) (InstallState, error) + InstallDocker() error + IsK8SComponentInstalled(string, string) (InstallState, error) + InstallK8S() error + StartK8Scluster() error + SetDockerVersion(string) + SetK8SVersionAndIsNodeFlag(version string, flag bool) + SetK8SImageRepoAndPodNetworkCidr(string, string) + } + +//FlagData stores value and default value of the flags used in this command +type FlagData struct { + Val interface{} + DefVal interface{} +} diff --git a/blueprints/common/elcli/elcli/cmd/init.go b/blueprints/common/elcli/elcli/cmd/init.go new file mode 100644 index 0000000..9364b50 --- /dev/null +++ b/blueprints/common/elcli/elcli/cmd/init.go @@ -0,0 +1,80 @@ +// +// Copyright © 2019 NAME HERE +// +// 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 ( + "fmt" + + + "github.com/spf13/cobra" + + "elcli/cmd/util" +) + +var ( +elcliSetupCmdDescription = +` +ELIOT init command is for setting up the ELIOT Cluster. +The command has options to setup the complete setup which includes +ELIOT Manager and ELIOT Edge Nodes and to setup only ELIOT Manager +or ELIOT Edge Node. The command invokes setup.sh script which handles +the complete setup. + +The Details of ELIOT Edge Nodes must be present in [nodelist] file. +` +) +// initCmd represents the init command +var initCmd = &cobra.Command{ + Use: "init", + Short: "Setup ELIOT Cluster !!", + Long: elcliSetupCmdDescription, + //It will check if the kubernetes process is already running on the node. + //Abort the operation if already running. + PreRunE: func(cmd *cobra.Command,args []string) error { + isELIOTClusterRunning, err := util.IsK8SClusterRunning() + if err != nil { + return err + } else if (isELIOTClusterRunning) { + return fmt.Errorf("Kubernetes Cluster is running in the Node. Clean up the environment and then setup the Cluster") + } + return nil + }, + RunE: func(cmd *cobra.Command, args []string) error{ + fmt.Println("init called") + setupFlag := cmd.Flag("setup") + setupflagoption := setupFlag.Value.String() + + switch setupflagoption { + case "all": + err:= util.EliotSetupAll() + return err + fmt.Println("Inside all option for setup flag") + case "master": + fmt.Println("Its eliot setup Master") + err:= util.EliotSetupMaster() + return err + default: + fmt.Println("Provide option for flag [--setup :- all | master] or [-s :- all | master]") + } + return nil + }, +} + +func init() { + rootCmd.AddCommand(initCmd) + initCmd.Flags().StringP("setup","s","all","Eliot Topology setup options") + +} diff --git a/blueprints/common/elcli/elcli/cmd/join.go b/blueprints/common/elcli/elcli/cmd/join.go new file mode 100644 index 0000000..70733ee --- /dev/null +++ b/blueprints/common/elcli/elcli/cmd/join.go @@ -0,0 +1,144 @@ +/* +Package cmd +Copyright © 2019 NAME HERE + +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 ( + "fmt" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + types "elcli/cmd/common" + "elcli/cmd/util" +) + +var joinOptions = &types.JoinOptions{} + +// joinCmd represents the join command +var joinCmd = &cobra.Command{ + Use: "join", + Short: "A brief description of your command", + Long: `A longer description that spans multiple lines and likely contains examples + and usage of using your command. For example: + + Cobra is a CLI library for Go that empowers applications. + This application is a tool to generate the needed files + to quickly create a Cobra application.`, + + RunE: func(cmd *cobra.Command, args []string) error { + fmt.Println("join called") + + tools := make(map[string]types.ToolsInstaller, 0) + flagVals := make(map[string]types.FlagData, 0) + + checkFlags := func(f *pflag.Flag) { + util.AddToolVals(f, flagVals) + } + cmd.Flags().VisitAll(checkFlags) + + //joinOptions := &types.JoinOptions{} + joinOptions = newJoinOptions() + + Add2ToolsList(tools, flagVals, joinOptions) + return ExecuteTool(tools) + + }, +} + +func init() { + + // Join Command added as sub-command for main command : elcli + rootCmd.AddCommand(joinCmd) + + joinCmd.Flags().StringVar(&joinOptions.DockerVersion, types.DockerVersion, joinOptions.DockerVersion, + "Use this key to download and use the required Docker version") + joinCmd.Flags().Lookup(types.DockerVersion).NoOptDefVal = joinOptions.DockerVersion + + joinCmd.Flags().StringVar(&joinOptions.KubernetesVersion, types.KubernetesVersion, joinOptions.KubernetesVersion, + "Use this key to download and use the required Kubernetes version") + joinCmd.Flags().Lookup(types.KubernetesVersion).NoOptDefVal = joinOptions.KubernetesVersion + + joinCmd.Flags().StringVar(&joinOptions.K8SImageRepository, types.K8SImageRepository, joinOptions.K8SImageRepository, + "Use this key to set the Kubernetes docker image repository") + joinCmd.Flags().Lookup(types.K8SImageRepository).NoOptDefVal = joinOptions.K8SImageRepository + +} + + +func newJoinOptions() *types.JoinOptions { + fmt.Println("Inside newJointOptions Method.....") + opts := &types.JoinOptions{} + opts.InitOptions = types.InitOptions{DockerVersion: types.DefaultDockerVersion, KubernetesVersion: types.DefaultK8SVersion} + //opts.CertPath = types.DefaultCertPath + return opts +} + + +//Add2ToolsList Reads the flagData (containing val and default val) and join options to fill the list of tools. +func Add2ToolsList(toolList map[string]types.ToolsInstaller, flagData map[string]types.FlagData, joinOptions *types.JoinOptions) { + + var k8sVer, dockerVer string + /*var k8sImageRepo string + + flgData, ok := flagData[types.K8SImageRepository] + if ok { + k8sImageRepo = util.CheckIfAvailable(flgData.Val.(string), flgData.DefVal.(string)) + } else { + k8sImageRepo = joinOptions.K8SImageRepository + } + + */ + + //toolList["EliotEdge"] = &util.KubeEdgeInstTool{Common: util.Common{ToolVersion: kubeVer}, K8SApiServerIP: joinOptions.K8SAPIServerIPPort, + // CloudCoreIP: joinOptions.CloudCoreIP, EdgeNodeID: joinOptions.EdgeNodeID} + + flgData, ok := flagData[types.DockerVersion] + if ok { + dockerVer = util.CheckIfAvailable(flgData.Val.(string), flgData.DefVal.(string)) + } else { + dockerVer = joinOptions.DockerVersion + } + toolList["Docker"] = &util.DockerInstTool{Common: util.Common{ToolVersion: dockerVer}, DefaultToolVer: flgData.DefVal.(string)} + + + flgData, ok = flagData[types.KubernetesVersion] + if ok { + k8sVer = util.CheckIfAvailable(flgData.Val.(string), flgData.DefVal.(string)) + } else { + k8sVer = joinOptions.KubernetesVersion + } + toolList["Kubernetes"] = &util.K8SInstTool{Common: util.Common{ToolVersion: k8sVer}, IsEdgeNode: false, DefaultToolVer: flgData.DefVal.(string)} + + +} + +//ExecuteTool the instalation for each tool and start edgecore +func ExecuteTool(toolList map[string]types.ToolsInstaller) error { + + //Install all the required pre-requisite tools + for name, tool := range toolList { + if name != "EliotEdge" { + err := tool.InstallTools() + if err != nil { + return err + } + } + } + + //Install and Start ElioteEdge Node + return toolList["ElioteEdge"].InstallTools() +} \ No newline at end of file diff --git a/blueprints/common/elcli/elcli/cmd/reset.go b/blueprints/common/elcli/elcli/cmd/reset.go new file mode 100644 index 0000000..46f7269 --- /dev/null +++ b/blueprints/common/elcli/elcli/cmd/reset.go @@ -0,0 +1,59 @@ +/* +Copyright © 2019 NAME HERE + +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 ( + "fmt" + + "github.com/spf13/cobra" + "elcli/cmd/util" +) + +var ( + + elcliResetCmdLongDescription = ` ++-----------------------------------------------------------+ +| To Reset the ELIOT Cluster. | +| | ++-----------------------------------------------------------+ +| RESET: It will reset the setting and the kubernetes | +| cluster, underying softwares of ELIOT platform will not be| +| still installed. | ++-----------------------------------------------------------+ +` +) + +// resetCmd represents the reset command +var resetCmd = &cobra.Command{ + Use: "reset", + Short: "Reset ELIOT Cluster!!", + Long: elcliResetCmdLongDescription, + RunE: func(cmd *cobra.Command, args []string) error{ + fmt.Println("reset called") + err := util.EliotReset() + str:= util.GetOSVersion() + if (err != nil){ + return err + } + fmt.Println("Print value of GetOSVersion", str) + return nil + }, +} + +func init() { + rootCmd.AddCommand(resetCmd) +} diff --git a/blueprints/common/elcli/elcli/cmd/root.go b/blueprints/common/elcli/elcli/cmd/root.go new file mode 100644 index 0000000..f868ef4 --- /dev/null +++ b/blueprints/common/elcli/elcli/cmd/root.go @@ -0,0 +1,107 @@ +/* +Copyright © 2019 NAME HERE + +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 ( + "fmt" + "os" + "github.com/spf13/cobra" + + homedir "github.com/mitchellh/go-homedir" + "github.com/spf13/viper" + +) + + +var cfgFile string + +var ( + elcliLongDescription = ` + +----------------------------------------------------------+ + | ELCLI | + | Command Line Interface to Bootsrap ELIOT Cluster | + +----------------------------------------------------------+ + + ELCLI Command is use to setup the ELIOT Cluster. + It installs the ELIOT Manager and the ELIOT Nodes. + ELIOT Manager is the core-controller and the ELIOT Nodes are + the edge nodes which acts as IOT Gateway or uCPE, where the + application PODS will be running. + ` + elcliExample = ` + +-----------------------------------------------------------+ + |To setup up the ELIOT Cluster the elcli init command has to| + |be executed in the ELIOT Manager Node. | + | | + |Example : | + |elcli init | + | | + +-----------------------------------------------------------+ + + ` +) + + +// rootCmd represents the base command when called without any subcommands +var rootCmd = &cobra.Command{ + Use: "elcli", + Short: "elcli : Bootstarp ELIOT Cluster", + Long: elcliLongDescription, + Example: elcliExample, +} + +// 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() { + if err := rootCmd.Execute(); err != nil { + fmt.Println(err) + os.Exit(1) + } +} + +func init() { + cobra.OnInitialize(initConfig) + +} + + +// initConfig reads in config file and ENV variables if set. +func initConfig() { + if cfgFile != "" { + // Use config file from the flag. + viper.SetConfigFile(cfgFile) + } else { + // Find home directory. + home, err := homedir.Dir() + if err != nil { + fmt.Println(err) + os.Exit(1) + } + + // Search config in home directory with name ".elcli" (without extension). + viper.AddConfigPath(home) + viper.SetConfigName(".elcli") + } + + viper.AutomaticEnv() // read in environment variables that match + + // If a config file is found, read it in. + if err := viper.ReadInConfig(); err == nil { + fmt.Println("Using config file:", viper.ConfigFileUsed()) + } +} + diff --git a/blueprints/common/elcli/elcli/cmd/util/centosinstaller.go b/blueprints/common/elcli/elcli/cmd/util/centosinstaller.go new file mode 100644 index 0000000..95b04d5 --- /dev/null +++ b/blueprints/common/elcli/elcli/cmd/util/centosinstaller.go @@ -0,0 +1,162 @@ +/* +Copyright 2019 The Kubeedge Authors. + +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 util + +import ( + "fmt" + "os/exec" + + types "elcli/cmd/common" + +) + +//CentOS struct objects shall have information of the tools version to be installed +//on Hosts having Ubuntu OS. +//It implements OSTypeInstaller interface +type CentOS struct { + DockerVersion string + KubernetesVersion string + IsEdgeNode bool //True - Edgenode False - Cloudnode + K8SImageRepository string + K8SPodNetworkCidr string +} + +//SetDockerVersion sets the Docker version for the objects instance +func (c *CentOS) SetDockerVersion(version string) { + c.DockerVersion = version +} + +//SetK8SVersionAndIsNodeFlag sets the K8S version for the objects instance +//It also sets if this host shall act as edge node or not +func (c *CentOS) SetK8SVersionAndIsNodeFlag(version string, flag bool) { + c.KubernetesVersion = version + c.IsEdgeNode = flag +} + +//SetK8SImageRepoAndPodNetworkCidr sets the K8S image Repository and pod network +// cidr. +func (c *CentOS) SetK8SImageRepoAndPodNetworkCidr(repo, cidr string) { + c.K8SImageRepository = repo + c.K8SPodNetworkCidr = cidr +} + +//IsDockerInstalled checks if docker is installed in the host or not +func (c *CentOS) IsDockerInstalled(string) (types.InstallState, error) { + //yum list installed | grep docker-ce | awk '{print $2}' | cut -d'-' -f 1 + //18.06.1.ce + + return types.VersionNAInRepo, nil + +} + +//InstallDocker will install the specified docker in the host +func (c *CentOS) InstallDocker() error { + fmt.Println("InstallDocker called") + // yum install -y yum-utils + // yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo + // yum makecache + // yum list --showduplicates 'docker-ce' | grep '17.06.0' | head -1 | awk '{print $2}' + // yum install -y docker-ce-17.06.0.ce-1.el7.centos + // [root@localhost ~]# systemctl start docker + // [root@localhost ~]# ---> Always restart systemctl restart docker + // [root@localhost ~]# + // IF downgrade yum downgrade -y docker-ce-17.06.0.ce-1.el7.centos + // Check always for version, if it is a downgrade or upgrade + + return nil +} + +//IsToolVerInRepo checks if the tool mentioned in available in OS repo or not +func (c *CentOS) IsToolVerInRepo(toolName, version string) (bool, error) { + //yum --cacheonly list | grep openssl + //For K8S, dont check in repo, just install + fmt.Println("IsToolVerInRepo called") + return false, nil +} + +//InstallMQTT checks if MQTT is already installed and running, if not then install it from OS repo +//Information is used from https://www.digitalocean.com/community/tutorials/how-to-install-and-secure-the-mosquitto-mqtt-messaging-broker-on-centos-7 +func (c *CentOS) InstallMQTT() error { + + //yum -y install epel-release + cmd := &Command{Cmd: exec.Command("sh", "-c", "yum -y install epel-release")} + err := cmd.ExecuteCmdShowOutput() + stdout := cmd.GetStdOutput() + errout := cmd.GetStdErr() + if err != nil || errout != "" { + return fmt.Errorf("%s", errout) + } + fmt.Println(stdout) + + //yum -y install mosquitto + cmd = &Command{Cmd: exec.Command("sh", "-c", "yum -y install mosquitto")} + err = cmd.ExecuteCmdShowOutput() + stdout = cmd.GetStdOutput() + errout = cmd.GetStdErr() + if err != nil || errout != "" { + return fmt.Errorf("%s", errout) + } + fmt.Println(stdout) + + + //systemctl start mosquitto + cmd = &Command{Cmd: exec.Command("sh", "-c", "systemctl start mosquitto")} + cmd.ExecuteCommand() + stdout = cmd.GetStdOutput() + errout = cmd.GetStdErr() + if errout != "" { + return fmt.Errorf("%s", errout) + } + fmt.Println(stdout) + + //systemctl enable mosquitto + cmd = &Command{Cmd: exec.Command("sh", "-c", "systemctl enable mosquitto")} + cmd.ExecuteCommand() + stdout = cmd.GetStdOutput() + errout = cmd.GetStdErr() + if errout != "" { + return fmt.Errorf("%s", errout) + } + fmt.Println(stdout) + + + return nil +} + +//IsK8SComponentInstalled checks if said K8S version is already installed in the host +func (c *CentOS) IsK8SComponentInstalled(component, defVersion string) (types.InstallState, error) { + // [root@localhost ~]# yum list installed | grep kubeadm | awk '{print $2}' | cut -d'-' -f 1 + // 1.14.1 + // [root@localhost ~]# + // [root@localhost ~]# yum list installed | grep kubeadm + // kubeadm.x86_64 1.14.1-0 @kubernetes + // [root@localhost ~]# + + return types.VersionNAInRepo, nil +} + +//InstallK8S will install kubeadm, kudectl and kubelet for the cloud node +func (c *CentOS) InstallK8S() error { + fmt.Println("InstallK8S called") + //Follow https://kubernetes.io/docs/setup/independent/install-kubeadm/ + return nil +} + +//StartK8Scluster will do "kubeadm init" and cluster will be started +func (c *CentOS) StartK8Scluster() error { + return nil +} \ No newline at end of file diff --git a/blueprints/common/elcli/elcli/cmd/util/cleanup.go b/blueprints/common/elcli/elcli/cmd/util/cleanup.go new file mode 100644 index 0000000..4c4d739 --- /dev/null +++ b/blueprints/common/elcli/elcli/cmd/util/cleanup.go @@ -0,0 +1,59 @@ +package util + + +import ( + "fmt" + "os/exec" +) +// EliotClean function to reset the ELiot Topology +func EliotClean() error { + fmt.Println("Inside EliotClean Function") + + cdEliotScripts := fmt.Sprintf("cd ~/eliot/scripts/ && ls -l") + shCleanEliotTopology := fmt.Sprintf("cd ~/eliot/scripts/ && bash kubernetes_cleanup.sh") + cmd := &Command{Cmd: exec.Command("bash", "-c", cdEliotScripts)} + cmd.ExecuteCommand() + + stdout := cmd.GetStdOutput() + errout := cmd.GetStdErr() + + if errout != "" { + return fmt.Errorf("Error Output .. %s", errout) + } + fmt.Println("Output is .... ", stdout) + + stdout, err := runCommandWithShell(shCleanEliotTopology) + if err != nil { + return err + } + fmt.Println(stdout) + + return nil +} + +// EliotReset function to Reset the ELIOT Cluster. +func EliotReset() error { + fmt.Println("Inside EliotReset Function") + + cdEliotScripts := fmt.Sprintf("cd ~/eliot/scripts/ && ls -l") + shResetEliotTopology := fmt.Sprintf("cd ~/eliot/scripts/ && bash kubernetes_reset.sh") + cmd := &Command{Cmd: exec.Command("sh", "-c", cdEliotScripts)} + cmd.ExecuteCommand() + + stdout := cmd.GetStdOutput() + errout := cmd.GetStdErr() + + if errout != "" { + return fmt.Errorf("Error Output .. %s", errout) + } + fmt.Println("Output is .... \n ", stdout) + return nil + + stdout, err := runCommandWithShell(shResetEliotTopology) + if err != nil { + return err + } + fmt.Println(stdout) + + return nil +} \ No newline at end of file diff --git a/blueprints/common/elcli/elcli/cmd/util/common.go b/blueprints/common/elcli/elcli/cmd/util/common.go new file mode 100644 index 0000000..7cf3dda --- /dev/null +++ b/blueprints/common/elcli/elcli/cmd/util/common.go @@ -0,0 +1,206 @@ +/* +Copyright 2019 The ELIOT Team . + +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 util + +import ( + "bytes" + "fmt" + "io" + "os" + "os/exec" + "strings" + "sync" + + + "github.com/spf13/pflag" + types "elcli/cmd/common" +) + +//Constants used by installers +const ( + UbuntuOSType = "ubuntu" + CentOSType = "centos" + + DefaultDownloadURL = "https://download.docker.com" + DockerPreqReqList = "apt-transport-https ca-certificates curl gnupg-agent software-properties-common" + + KubernetesDownloadURL = "https://apt.kubernetes.io/" + KubernetesGPGURL = "https://packages.cloud.google.com/apt/doc/apt-key.gpg" + + KubeAPIServerName = "kube-apiserver" +) + +//AddToolVals gets the value and default values of each flags and collects them in temporary cache +func AddToolVals(f *pflag.Flag, flagData map[string]types.FlagData) { + flagData[f.Name] = types.FlagData{Val: f.Value.String(), DefVal: f.DefValue} +} + +//CheckIfAvailable checks is val of a flag is empty then return the default value +func CheckIfAvailable(val, defval string) string { + if val == "" { + return defval + } + return val +} + +//Common struct contains OS and Tool version properties and also embeds OS interface +type Common struct { + types.OSTypeInstaller + OSVersion string + ToolVersion string + KubeConfig string +} + +//SetOSInterface defines a method to set the implemtation of the OS interface +func (co *Common) SetOSInterface(intf types.OSTypeInstaller) { + co.OSTypeInstaller = intf +} + +//Command defines commands to be executed and captures std out and std error +type Command struct { + Cmd *exec.Cmd + StdOut []byte + StdErr []byte +} + +//ExecuteCommand executes the command and captures the output in stdOut +func (cm *Command) ExecuteCommand() { + var err error + cm.StdOut, err = cm.Cmd.Output() + if err != nil { + fmt.Println("Output failed: ", err) + cm.StdErr = []byte(err.Error()) + } +} + +//GetStdOutput gets StdOut field +func (cm Command) GetStdOutput() string { + if len(cm.StdOut) != 0 { + return strings.TrimRight(string(cm.StdOut), "\n") + } + return "" +} + +//GetStdErr gets StdErr field +func (cm Command) GetStdErr() string { + if len(cm.StdErr) != 0 { + return strings.TrimRight(string(cm.StdErr), "\n") + } + return "" +} + +//ExecuteCmdShowOutput captures both StdOut and StdErr after exec.cmd(). +//It helps in the commands where it takes some time for execution. +func (cm Command) ExecuteCmdShowOutput() error { + var stdoutBuf, stderrBuf bytes.Buffer + stdoutIn, _ := cm.Cmd.StdoutPipe() + stderrIn, _ := cm.Cmd.StderrPipe() + + var errStdout, errStderr error + stdout := io.MultiWriter(os.Stdout, &stdoutBuf) + stderr := io.MultiWriter(os.Stderr, &stderrBuf) + err := cm.Cmd.Start() + if err != nil { + return fmt.Errorf("failed to start '%s' because of error : %s", strings.Join(cm.Cmd.Args, " "), err.Error()) + } + + var wg sync.WaitGroup + wg.Add(1) + + go func() { + _, errStdout = io.Copy(stdout, stdoutIn) + wg.Done() + }() + + _, errStderr = io.Copy(stderr, stderrIn) + wg.Wait() + + err = cm.Cmd.Wait() + if err != nil { + return fmt.Errorf("failed to run '%s' because of error : %s", strings.Join(cm.Cmd.Args, " "), err.Error()) + } + if errStdout != nil || errStderr != nil { + return fmt.Errorf("failed to capture stdout or stderr") + } + + cm.StdOut, cm.StdErr = stdoutBuf.Bytes(), stderrBuf.Bytes() + return nil +} + +//GetOSVersion gets the OS name +func GetOSVersion() string { + c := &Command{Cmd: exec.Command("sh", "-c", ". /etc/os-release && echo $ID")} + c.ExecuteCommand() + return c.GetStdOutput() +} + +//GetOSInterface helps in returning OS specific object which implements OSTypeInstaller interface. +func GetOSInterface() types.OSTypeInstaller { + + switch GetOSVersion() { + case UbuntuOSType: + return &UbuntuOS{} + case CentOSType: + return &CentOS{} + default: + panic("This OS version is currently un-supported by keadm") + } +} + +// IsCloudCore identifies if the node is having cloudcore and kube-apiserver already running. +// If so, then return true, else it can used as edge node and initialise it. +func IsCloudCore() (types.ModuleRunning, error) { + //osType := GetOSInterface() + + //If any of cloudcore or K8S API server is running, then we believe the node is cloud node + + return types.NoneRunning, nil +} + +//IsK8SClusterRunning check whether Kubernetes Master is running already on the server in which ELIOT Setup command is executed +//Currently there is no check on the ELIOT Edge Nodes. +func IsK8SClusterRunning() (bool, error) { + shK8SClusterRunning := fmt.Sprintf("ps aux | grep kube- | grep -v grep | wc -l") + cmd := &Command {Cmd : exec.Command ("sh" , "-c" ,shK8SClusterRunning)} + cmd.ExecuteCommand() + stdOut:= cmd.GetStdOutput() + errOut:= cmd.GetStdErr() + + if errOut != "" { + return false, fmt.Errorf("%s", errOut) + } + if stdOut != "" { + return true, nil + } + return false,nil + +} + +// runCommandWithShell executes the given command with "sh -c". +// It returns an error if the command outputs anything on the stderr. +func runCommandWithShell(command string) (string, error) { + cmd := &Command{Cmd: exec.Command("sh", "-c", command)} + err := cmd.ExecuteCmdShowOutput() + if err != nil { + return "", err + } + errout := cmd.GetStdErr() + if errout != "" { + return "", fmt.Errorf("%s", errout) + } + return cmd.GetStdOutput(), nil +} diff --git a/blueprints/common/elcli/elcli/cmd/util/dockerinstaller.go b/blueprints/common/elcli/elcli/cmd/util/dockerinstaller.go new file mode 100644 index 0000000..809cb85 --- /dev/null +++ b/blueprints/common/elcli/elcli/cmd/util/dockerinstaller.go @@ -0,0 +1,67 @@ +/* +Copyright 2019 The Kubeedge Authors. + +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 util + +import ( + "fmt" + + types "elcli/cmd/common" +) + +//DockerInstTool embedes Common struct and contains the default docker version +//It implements ToolsInstaller interface +type DockerInstTool struct { + Common + DefaultToolVer string +} + +//InstallTools sets the OS interface, checks if docker installation is required or not. +//If required then install the said version. +func (d *DockerInstTool) InstallTools() error { + d.SetOSInterface(GetOSInterface()) + d.SetDockerVersion(d.ToolVersion) + + action, err := d.IsDockerInstalled(d.DefaultToolVer) + if err != nil { + return err + } + switch action { + case types.VersionNAInRepo: + return fmt.Errorf("Expected Docker version is not available in OS repo") + case types.AlreadySameVersionExist: + fmt.Println("Same version of docker already installed in this host") + return nil + case types.DefVerInstallRequired: + d.SetDockerVersion(d.DefaultToolVer) + fallthrough + case types.NewInstallRequired: + err := d.InstallDocker() + if err != nil { + return err + } + default: + return fmt.Errorf("Error in getting the docker version from host") + } + + return nil +} + +//TearDown shoud uninstall docker, but it is not required either for cloud or edge node. +//It is defined so that DockerInstTool implements ToolsInstaller interface +func (d *DockerInstTool) TearDown() error { + return nil +} diff --git a/blueprints/common/elcli/elcli/cmd/util/k8sinstaller.go b/blueprints/common/elcli/elcli/cmd/util/k8sinstaller.go new file mode 100644 index 0000000..239ed4b --- /dev/null +++ b/blueprints/common/elcli/elcli/cmd/util/k8sinstaller.go @@ -0,0 +1,74 @@ +/* +Copyright 2019 The Eliot Team. + +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 util + +import ( + "fmt" + + types "elcli/cmd/common" +) + + +//K8SInstTool embedes Common struct and contains the default K8S version and +//a flag depicting if host is an edge or cloud node +//It implements ToolsInstaller interface +type K8SInstTool struct { + Common + IsEdgeNode bool //True - Edgenode False - Cloudnode + DefaultToolVer string +} + + +//InstallTools sets the OS interface, checks if K8S installation is required or not. +//If required then install the said version. +func (ks *K8SInstTool) InstallTools() error { + ks.SetOSInterface(GetOSInterface()) + ks.SetK8SVersionAndIsNodeFlag(ks.ToolVersion, ks.IsEdgeNode) + + component := "kubeadm" + if ks.IsEdgeNode == true { + component = "kubectl" + } + action, err := ks.IsK8SComponentInstalled(component, ks.DefaultToolVer) + if err != nil { + return err + } + switch action { + case types.VersionNAInRepo: + return fmt.Errorf("Expected %s version is not available in OS repo", component) + case types.AlreadySameVersionExist: + fmt.Printf("Same version of %s already installed in this host", component) + return nil + case types.DefVerInstallRequired: + ks.SetK8SVersionAndIsNodeFlag(ks.DefaultToolVer, ks.IsEdgeNode) + fallthrough + case types.NewInstallRequired: + err := ks.InstallK8S() + if err != nil { + return err + } + default: + return fmt.Errorf("Error in getting the %s version from host", component) + } + return nil +} + +//TearDown shoud uninstall K8S, but it is not required either for cloud or edge node. +//It is defined so that K8SInstTool implements ToolsInstaller interface +func (ks *K8SInstTool) TearDown() error { + return nil +} \ No newline at end of file diff --git a/blueprints/common/elcli/elcli/cmd/util/setup.go b/blueprints/common/elcli/elcli/cmd/util/setup.go new file mode 100644 index 0000000..cd7e202 --- /dev/null +++ b/blueprints/common/elcli/elcli/cmd/util/setup.go @@ -0,0 +1,67 @@ +package util + +import ( + "fmt" + "os/exec" + //"github.com/spf13/elcli/cmd/common" +) + +// EliotSetupAll function to reset the ELiot Topology +func EliotSetupAll() error { + fmt.Println("Inside EliotSetupAll Function") + + + strCdEliotScripts := fmt.Sprintf("cd ~/eliot/scripts/ && ls -l") + strSetupAll := fmt.Sprintf("cd ~/eliot/scripts/ && bash setup.sh") + cmd := &Command{Cmd: exec.Command("bash", "-c", strCdEliotScripts)} + cmd.ExecuteCommand() + + stdout := cmd.GetStdOutput() + errout := cmd.GetStdErr() + if errout != "" { + return fmt.Errorf("Error Output .. %s", errout) + } + + fmt.Println("Output is .... ", stdout) + + stdout, err := runCommandWithShell(strSetupAll) + if err != nil { + return err + } + fmt.Println(stdout) + return nil +} + +//EliotSetupMaster Setup Method. +func EliotSetupMaster() error { + fmt.Println("Inside EliotSetupMaster Function") + + strCdEliotScripts := fmt.Sprintf("cd ~/eliot/scripts/ && ls -l") + + cmd := &Command{Cmd: exec.Command("bash", "-c", strCdEliotScripts)} + cmd.ExecuteCommand() + + stdout := cmd.GetStdOutput() + errout := cmd.GetStdErr() + if errout != "" { + return fmt.Errorf("Error Output .. %s", errout) + } + fmt.Println("Output is .... ", stdout) + + strSetupCommon := fmt.Sprintf("cd ~/eliot/scripts/ && bash common.sh") + stdout, err := runCommandWithShell(strSetupCommon) + if err != nil { + return err + } + fmt.Println(stdout) + fmt.Println("Output is .... ", stdout) + + strSetupk8sMaster := fmt.Sprintf("cd ~/eliot/scripts/ && bash k8smaster.sh") + stdout, err = runCommandWithShell(strSetupk8sMaster) + if err != nil { + return err + } + fmt.Println(stdout) + + return nil +} \ No newline at end of file diff --git a/blueprints/common/elcli/elcli/cmd/util/ubuntuinstaller.go b/blueprints/common/elcli/elcli/cmd/util/ubuntuinstaller.go new file mode 100644 index 0000000..4d3690e --- /dev/null +++ b/blueprints/common/elcli/elcli/cmd/util/ubuntuinstaller.go @@ -0,0 +1,396 @@ +/* +Copyright 2019 The Kubeedge Authors. + +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 util + +import ( + "fmt" + //"os" + "os/exec" + "strings" + + //types "github.com/kubeedge/kubeedge/keadm/app/cmd/common" + types "elcli/cmd/common" +) + +const downloadRetryTimes int = 3 + +// Ubuntu releases +const ( + UbuntuXenial = "xenial" + UbuntuBionic = "bionic" +) + +//UbuntuOS struct objects shall have information of the tools version to be installed +//on Hosts having Ubuntu OS. +//It implements OSTypeInstaller interface +type UbuntuOS struct { + DockerVersion string + KubernetesVersion string + KubeEdgeVersion string + IsEdgeNode bool //True - Edgenode False - EliotCloudnode + K8SImageRepository string + K8SPodNetworkCidr string +} + +//SetDockerVersion sets the Docker version for the objects instance +func (u *UbuntuOS) SetDockerVersion(version string) { + u.DockerVersion = version +} + +//SetK8SVersionAndIsNodeFlag sets the K8S version for the objects instance +//It also sets if this host shall act as edge node or not +func (u *UbuntuOS) SetK8SVersionAndIsNodeFlag(version string, flag bool) { + u.KubernetesVersion = version + u.IsEdgeNode = flag +} + +//SetK8SImageRepoAndPodNetworkCidr sets the K8S image Repository and pod network +// cidr. +func (u *UbuntuOS) SetK8SImageRepoAndPodNetworkCidr(repo, cidr string) { + u.K8SImageRepository = repo + u.K8SPodNetworkCidr = cidr +} + +//SetKubeEdgeVersion sets the KubeEdge version for the objects instance +func (u *UbuntuOS) SetKubeEdgeVersion(version string) { + u.KubeEdgeVersion = version +} + +//IsToolVerInRepo checks if the tool mentioned in available in OS repo or not +func (u *UbuntuOS) IsToolVerInRepo(toolName, version string) (bool, error) { + //Check if requested Docker or K8S components said version is available in OS repo or not + + chkToolVer := fmt.Sprintf("apt-cache madison '%s' | grep -w %s | head -1 | awk '{$1=$1};1' | cut -d' ' -f 3", toolName, version) + cmd := &Command{Cmd: exec.Command("sh", "-c", chkToolVer)} + cmd.ExecuteCommand() + stdout := cmd.GetStdOutput() + errout := cmd.GetStdErr() + + if errout != "" { + return false, fmt.Errorf("%s", errout) + } + + if stdout != "" { + fmt.Println(toolName, stdout, "is available in OS repo") + return true, nil + } + + fmt.Println(toolName, "version", version, "not found in OS repo") + return false, nil +} + +func (u *UbuntuOS) addDockerRepositoryAndUpdate() error { + //lsb_release -cs + cmd := &Command{Cmd: exec.Command("sh", "-c", "lsb_release -cs")} + cmd.ExecuteCommand() + distVersion := cmd.GetStdOutput() + if distVersion == "" { + return fmt.Errorf("ubuntu dist version not available") + } + fmt.Println("Ubuntu distribution version is", distVersion) + + //'apt-get update' + stdout, err := runCommandWithShell("apt-get update") + if err != nil { + return err + } + fmt.Println(stdout) + + //"curl -fsSL \"$DOWNLOAD_URL/linux/$lsb_dist/gpg\" | apt-key add" + //Get the GPG key + curl := fmt.Sprintf("curl -fsSL \"%s/linux/%s/gpg\" | apt-key add", DefaultDownloadURL, UbuntuOSType) + cmd = &Command{Cmd: exec.Command("sh", "-c", curl)} + cmd.ExecuteCommand() + curlOutput := cmd.GetStdOutput() + if curlOutput == "" { + return fmt.Errorf("not able add the apt key") + } + fmt.Println(curlOutput) + + //Add the repo in OS source.list + aptRepo := fmt.Sprintf("deb [arch=$(dpkg --print-architecture)] %s/linux/%s %s stable", DefaultDownloadURL, UbuntuOSType, distVersion) + updtRepo := fmt.Sprintf("echo \"%s\" > /etc/apt/sources.list.d/docker.list", aptRepo) + cmd = &Command{Cmd: exec.Command("sh", "-c", updtRepo)} + cmd.ExecuteCommand() + updtRepoErr := cmd.GetStdErr() + if updtRepoErr != "" { + return fmt.Errorf("not able add update repo due to error : %s", updtRepoErr) + } + + //Do an apt-get update + stdout, err = runCommandWithShell("apt-get update") + if err != nil { + return err + } + fmt.Println(stdout) + + return nil +} + +//IsDockerInstalled checks if docker is installed in the host or not +func (u *UbuntuOS) IsDockerInstalled(defVersion string) (types.InstallState, error) { + cmd := &Command{Cmd: exec.Command("sh", "-c", "docker -v | cut -d ' ' -f3 | cut -d ',' -f1")} + cmd.ExecuteCommand() + str := cmd.GetStdOutput() + + if strings.Contains(str, u.DockerVersion) { + return types.AlreadySameVersionExist, nil + } + + if err := u.addDockerRepositoryAndUpdate(); err != nil { + return types.VersionNAInRepo, err + } + + if str == "" { + return types.NewInstallRequired, nil + } + + isReqVerAvail, err := u.IsToolVerInRepo("docker-ce", u.DockerVersion) + if err != nil { + return types.VersionNAInRepo, err + } + + var isDefVerAvail bool + if u.DockerVersion != defVersion { + isDefVerAvail, err = u.IsToolVerInRepo("docker-ce", defVersion) + if err != nil { + return types.VersionNAInRepo, err + } + } + + if isReqVerAvail { + return types.NewInstallRequired, nil + } + + if isDefVerAvail { + return types.DefVerInstallRequired, nil + } + + return types.VersionNAInRepo, nil +} + +//InstallDocker will install the specified docker in the host +func (u *UbuntuOS) InstallDocker() error { + fmt.Println("Installing ", u.DockerVersion, "version of docker") + + //Do an apt-get install + instPreReq := fmt.Sprintf("apt-get install -y %s", DockerPreqReqList) + stdout, err := runCommandWithShell(instPreReq) + if err != nil { + return err + } + fmt.Println(stdout) + + //Get the exact version string from OS repo, so that it can search and install. + chkDockerVer := fmt.Sprintf("apt-cache madison 'docker-ce' | grep %s | head -1 | awk '{$1=$1};1' | cut -d' ' -f 3", u.DockerVersion) + cmd := &Command{Cmd: exec.Command("sh", "-c", chkDockerVer)} + cmd.ExecuteCommand() + stdout = cmd.GetStdOutput() + errout := cmd.GetStdErr() + if errout != "" { + return fmt.Errorf("%s", errout) + } + + fmt.Println("Expected docker version to install is", stdout) + + //Install docker-ce + dockerInst := fmt.Sprintf("apt-get install -y --allow-change-held-packages --allow-downgrades docker-ce=%s", stdout) + stdout, err = runCommandWithShell(dockerInst) + if err != nil { + return err + } + fmt.Println(stdout) + + fmt.Println("Docker", u.DockerVersion, "version is installed in this Host") + + return nil +} + +//IsK8SComponentInstalled checks if said K8S version is already installed in the host +func (u *UbuntuOS) IsK8SComponentInstalled(component, defVersion string) (types.InstallState, error) { + + find := fmt.Sprintf("dpkg -l | grep %s | awk '{print $3}'", component) + cmd := &Command{Cmd: exec.Command("sh", "-c", find)} + cmd.ExecuteCommand() + str := cmd.GetStdOutput() + + if strings.Contains(str, u.KubernetesVersion) { + return types.AlreadySameVersionExist, nil + } + + if err := u.addK8SRepositoryAndUpdate(); err != nil { + return types.VersionNAInRepo, err + } + + if str == "" { + return types.NewInstallRequired, nil + } + + isReqVerAvail, err := u.IsToolVerInRepo(component, u.KubernetesVersion) + if err != nil { + return types.VersionNAInRepo, err + } + + var isDefVerAvail bool + if u.KubernetesVersion != defVersion { + isDefVerAvail, _ = u.IsToolVerInRepo(component, defVersion) + if err != nil { + return types.VersionNAInRepo, err + } + } + + if isReqVerAvail { + return types.NewInstallRequired, nil + } + + if isDefVerAvail { + return types.DefVerInstallRequired, nil + } + + return types.VersionNAInRepo, nil +} + +func (u *UbuntuOS) addK8SRepositoryAndUpdate() error { + //Get the distribution version + cmd := &Command{Cmd: exec.Command("sh", "-c", "lsb_release -cs")} + cmd.ExecuteCommand() + distVersion := cmd.GetStdOutput() + if distVersion == "" { + return fmt.Errorf("ubuntu dist version not available") + } + fmt.Println("Ubuntu distribution version is", distVersion) + distVersionForSuite := distVersion + if distVersion == UbuntuBionic { + // No bionic-specific version is available on apt.kubernetes.io. + // Use xenial version instead. + distVersionForSuite = UbuntuXenial + } + suite := fmt.Sprintf("kubernetes-%s", distVersionForSuite) + fmt.Println("Deb suite to use:", suite) + + //Do an apt-get update + stdout, err := runCommandWithShell("apt-get update") + if err != nil { + return err + } + fmt.Println(stdout) + + //curl -s https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add - + //Get the GPG key + curl := fmt.Sprintf("curl -s %s | apt-key add -", KubernetesGPGURL) + cmd = &Command{Cmd: exec.Command("sh", "-c", curl)} + cmd.ExecuteCommand() + curlOutput := cmd.GetStdOutput() + curlErr := cmd.GetStdErr() + if curlOutput == "" || curlErr != "" { + return fmt.Errorf("not able add the apt key due to error : %s", curlErr) + } + fmt.Println(curlOutput) + + //Add K8S repo to local apt-get source.list + aptRepo := fmt.Sprintf("deb %s %s main", KubernetesDownloadURL, suite) + updtRepo := fmt.Sprintf("echo \"%s\" > /etc/apt/sources.list.d/kubernetes.list", aptRepo) + cmd = &Command{Cmd: exec.Command("sh", "-c", updtRepo)} + cmd.ExecuteCommand() + updtRepoErr := cmd.GetStdErr() + if updtRepoErr != "" { + return fmt.Errorf("not able add update repo due to error : %s", updtRepoErr) + } + + //Do an apt-get update + stdout, err = runCommandWithShell("apt-get update") + if err != nil { + return err + } + fmt.Println(stdout) + return nil +} + +//InstallK8S will install kubeadm, kudectl and kubelet for the cloud node +func (u *UbuntuOS) InstallK8S() error { + k8sComponent := "kubeadm" + fmt.Println("Installing", k8sComponent, u.KubernetesVersion, "version") + + //Get the exact version string from OS repo, so that it can search and install. + chkKubeadmVer := fmt.Sprintf("apt-cache madison '%s' | grep %s | head -1 | awk '{$1=$1};1' | cut -d' ' -f 3", k8sComponent, u.KubernetesVersion) + cmd := &Command{Cmd: exec.Command("sh", "-c", chkKubeadmVer)} + cmd.ExecuteCommand() + stdout := cmd.GetStdOutput() + errout := cmd.GetStdErr() + if errout != "" { + return fmt.Errorf("%s", errout) + } + + fmt.Println("Expected K8S('", k8sComponent, "') version to install is", stdout) + + //Install respective K8S components based on where it is being installed + k8sInst := fmt.Sprintf("apt-get install -y --allow-change-held-packages --allow-downgrades kubeadm=%s kubelet=%s kubectl=%s", stdout, stdout, stdout) + stdout, err := runCommandWithShell(k8sInst) + if err != nil { + return err + } + fmt.Println(stdout) + + fmt.Println(k8sComponent, "version", u.KubernetesVersion, "is installed in this Host") + + return nil +} + +//StartK8Scluster will do "kubeadm init" and cluster will be started +func (u *UbuntuOS) StartK8Scluster() error { + var install bool + cmd := &Command{Cmd: exec.Command("sh", "-c", "kubeadm version")} + cmd.ExecuteCommand() + str := cmd.GetStdOutput() + if str != "" { + install = true + } else { + install = false + } + if install == true { + k8sInit := fmt.Sprintf("swapoff -a && kubeadm init --image-repository \"%s\" --pod-network-cidr=%s", u.K8SImageRepository, u.K8SPodNetworkCidr) + stdout, err := runCommandWithShell(k8sInit) + if err != nil { + return err + } + fmt.Println(stdout) + + stdout, err = runCommandWithShell("mkdir -p $HOME/.kube && cp -r /etc/kubernetes/admin.conf $HOME/.kube/config && sudo chown $(id -u):$(id -g) $HOME/.kube/config") + if err != nil { + return err + } + fmt.Println(stdout) + } else { + return fmt.Errorf("kubeadm not installed in this host") + } + fmt.Println("Kubeadm init successfully executed") + return nil +} +// // runCommandWithShell executes the given command with "sh -c". +// // It returns an error if the command outputs anything on the stderr. +// func runCommandWithShell(command string) (string, error) { +// cmd := &Command{Cmd: exec.Command("sh", "-c", command)} +// err := cmd.ExecuteCmdShowOutput() +// if err != nil { +// return "", err +// } +// errout := cmd.GetStdErr() +// if errout != "" { +// return "", fmt.Errorf("%s", errout) +// } +// return cmd.GetStdOutput(), nil +// } diff --git a/blueprints/common/elcli/elcli/main.go b/blueprints/common/elcli/elcli/main.go new file mode 100644 index 0000000..189e9a1 --- /dev/null +++ b/blueprints/common/elcli/elcli/main.go @@ -0,0 +1,22 @@ +/* +Copyright © 2019 NAME HERE + +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 "elcli/cmd" + +func main() { + cmd.Execute() +} diff --git a/blueprints/common/elcli/github.com/fsnotify/fsnotify b/blueprints/common/elcli/github.com/fsnotify/fsnotify new file mode 160000 index 0000000..1485a34 --- /dev/null +++ b/blueprints/common/elcli/github.com/fsnotify/fsnotify @@ -0,0 +1 @@ +Subproject commit 1485a34d5d5723fea214f5710708e19a831720e4 diff --git a/blueprints/common/elcli/github.com/hashicorp/hcl b/blueprints/common/elcli/github.com/hashicorp/hcl new file mode 160000 index 0000000..cf7d376 --- /dev/null +++ b/blueprints/common/elcli/github.com/hashicorp/hcl @@ -0,0 +1 @@ +Subproject commit cf7d376da96d9cecec7c7483cec2735efe54a410 diff --git a/blueprints/common/elcli/github.com/magiconair/properties b/blueprints/common/elcli/github.com/magiconair/properties new file mode 160000 index 0000000..de8848e --- /dev/null +++ b/blueprints/common/elcli/github.com/magiconair/properties @@ -0,0 +1 @@ +Subproject commit de8848e004dd33dc07a2947b3d76f618a7fc7ef1 diff --git a/blueprints/common/elcli/github.com/mitchellh/go-homedir b/blueprints/common/elcli/github.com/mitchellh/go-homedir new file mode 160000 index 0000000..af06845 --- /dev/null +++ b/blueprints/common/elcli/github.com/mitchellh/go-homedir @@ -0,0 +1 @@ +Subproject commit af06845cf3004701891bf4fdb884bfe4920b3727 diff --git a/blueprints/common/elcli/github.com/mitchellh/mapstructure b/blueprints/common/elcli/github.com/mitchellh/mapstructure new file mode 160000 index 0000000..3536a92 --- /dev/null +++ b/blueprints/common/elcli/github.com/mitchellh/mapstructure @@ -0,0 +1 @@ +Subproject commit 3536a929edddb9a5b34bd6861dc4a9647cb459fe diff --git a/blueprints/common/elcli/github.com/pelletier/go-toml b/blueprints/common/elcli/github.com/pelletier/go-toml new file mode 160000 index 0000000..dba45d4 --- /dev/null +++ b/blueprints/common/elcli/github.com/pelletier/go-toml @@ -0,0 +1 @@ +Subproject commit dba45d427ff48cfb9bcf633db3a8e43b7364e261 diff --git a/blueprints/common/elcli/github.com/spf13/afero b/blueprints/common/elcli/github.com/spf13/afero new file mode 160000 index 0000000..588a75e --- /dev/null +++ b/blueprints/common/elcli/github.com/spf13/afero @@ -0,0 +1 @@ +Subproject commit 588a75ec4f32903aa5e39a2619ba6a4631e28424 diff --git a/blueprints/common/elcli/github.com/spf13/cast b/blueprints/common/elcli/github.com/spf13/cast new file mode 160000 index 0000000..c01685b --- /dev/null +++ b/blueprints/common/elcli/github.com/spf13/cast @@ -0,0 +1 @@ +Subproject commit c01685bb8421cecb276fa517e91f757215f980b3 diff --git a/blueprints/common/elcli/github.com/spf13/cobra b/blueprints/common/elcli/github.com/spf13/cobra new file mode 160000 index 0000000..1c9c46d --- /dev/null +++ b/blueprints/common/elcli/github.com/spf13/cobra @@ -0,0 +1 @@ +Subproject commit 1c9c46d5c1cc2aaebdd1898c0680e85e8a44b36d diff --git a/blueprints/common/elcli/github.com/spf13/elcli.tar b/blueprints/common/elcli/github.com/spf13/elcli.tar new file mode 100644 index 0000000000000000000000000000000000000000..1ab3da25c792e203268587dd2fadaa7f6fdd62c2 GIT binary patch literal 81920 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 literal 0 HcmV?d00001 diff --git a/blueprints/common/elcli/github.com/spf13/jwalterweatherman b/blueprints/common/elcli/github.com/spf13/jwalterweatherman new file mode 160000 index 0000000..94f6ae3 --- /dev/null +++ b/blueprints/common/elcli/github.com/spf13/jwalterweatherman @@ -0,0 +1 @@ +Subproject commit 94f6ae3ed3bceceafa716478c5fbf8d29ca601a1 diff --git a/blueprints/common/elcli/github.com/spf13/pflag b/blueprints/common/elcli/github.com/spf13/pflag new file mode 160000 index 0000000..24fa697 --- /dev/null +++ b/blueprints/common/elcli/github.com/spf13/pflag @@ -0,0 +1 @@ +Subproject commit 24fa6976df40757dce6aea913e7b81ade90530e1 diff --git a/blueprints/common/elcli/github.com/spf13/viper b/blueprints/common/elcli/github.com/spf13/viper new file mode 160000 index 0000000..e02bc9e --- /dev/null +++ b/blueprints/common/elcli/github.com/spf13/viper @@ -0,0 +1 @@ +Subproject commit e02bc9eca55d5fc66221bc0aeeaaa77410603914 diff --git a/blueprints/common/elcli/github.com/subosito/gotenv b/blueprints/common/elcli/github.com/subosito/gotenv new file mode 160000 index 0000000..69b5b61 --- /dev/null +++ b/blueprints/common/elcli/github.com/subosito/gotenv @@ -0,0 +1 @@ +Subproject commit 69b5b6104433beb2cb9c3ce00bdadf3c7c2d3f34 diff --git a/blueprints/common/elcli/github.com/trial/app/trialroot.go b/blueprints/common/elcli/github.com/trial/app/trialroot.go new file mode 100644 index 0000000..413faff --- /dev/null +++ b/blueprints/common/elcli/github.com/trial/app/trialroot.go @@ -0,0 +1,19 @@ +/* + +*/ + +package app + +import ( + "flag" + "os" + "fmt" + + "github.com/spf13/pflag" + "github.com/trial/app/cmd" +) + +//Run executes commands +func Run() error { + return "Hello World" +} diff --git a/blueprints/common/elcli/golang.org/x/lint b/blueprints/common/elcli/golang.org/x/lint new file mode 160000 index 0000000..959b441 --- /dev/null +++ b/blueprints/common/elcli/golang.org/x/lint @@ -0,0 +1 @@ +Subproject commit 959b441ac422379a43da2230f62be024250818b0 diff --git a/blueprints/common/elcli/golang.org/x/sys b/blueprints/common/elcli/golang.org/x/sys new file mode 160000 index 0000000..fae7ac5 --- /dev/null +++ b/blueprints/common/elcli/golang.org/x/sys @@ -0,0 +1 @@ +Subproject commit fae7ac547cb717d141c433a2a173315e216b64c4 diff --git a/blueprints/common/elcli/golang.org/x/text b/blueprints/common/elcli/golang.org/x/text new file mode 160000 index 0000000..342b2e1 --- /dev/null +++ b/blueprints/common/elcli/golang.org/x/text @@ -0,0 +1 @@ +Subproject commit 342b2e1fbaa52c93f31447ad2c6abc048c63e475 diff --git a/blueprints/common/elcli/golang.org/x/tools b/blueprints/common/elcli/golang.org/x/tools new file mode 160000 index 0000000..f8d1dee --- /dev/null +++ b/blueprints/common/elcli/golang.org/x/tools @@ -0,0 +1 @@ +Subproject commit f8d1dee965f76837e891ded19dd59ee264db8ddc diff --git a/blueprints/common/elcli/gopkg.in/yaml.v2 b/blueprints/common/elcli/gopkg.in/yaml.v2 new file mode 160000 index 0000000..51d6538 --- /dev/null +++ b/blueprints/common/elcli/gopkg.in/yaml.v2 @@ -0,0 +1 @@ +Subproject commit 51d6538a90f86fe93ac480b35f37b2be17fef232 -- 2.16.6