This is an automated email from the ASF dual-hosted git repository.

asifdxtreme pushed a commit to branch master
in repository 
https://gitbox.apache.org/repos/asf/incubator-servicecomb-service-center.git


The following commit(s) were added to refs/heads/master by this push:
     new 629b44a  SCB-869 SC cli tool (#432)
629b44a is described below

commit 629b44af670e1a21b7e59ad61dcdb453ed86c623
Author: little-cui <[email protected]>
AuthorDate: Tue Sep 4 14:56:44 2018 +0800

    SCB-869 SC cli tool (#432)
    
    * SCB-869 SC cli tool
    
    * Add version cmd
    
    * Add README
    
    * Add build scripts
---
 README.md                                          |   2 +-
 docs/README.md                                     |   5 +-
 glide.yaml                                         |   5 +
 pkg/client/etcd/client.go                          |  66 ++++
 pkg/client/sc/apis.go                              |  93 ++++++
 pkg/client/sc/client.go                            |  51 +++
 pkg/rest/client.go                                 | 346 ++++++++-------------
 pkg/rest/common.go                                 |   2 +
 pkg/util/sys.go                                    |   8 +
 pkg/util/util_test.go                              |  16 +
 scctl/README.md                                    |  47 +++
 scctl/bootstrap/bootstrap.go                       |  21 ++
 scctl/main.go                                      |  25 ++
 scctl/pkg/cmd/cmd.go                               |  98 ++++++
 scctl/pkg/cmd/help.go                              | 169 ++++++++++
 scctl/pkg/model/types.go                           | 122 ++++++++
 scctl/pkg/plugin/README.md                         | 107 +++++++
 scctl/pkg/plugin/diagnose/cmd.go                   |  57 ++++
 scctl/pkg/plugin/diagnose/compare_holder.go        | 168 ++++++++++
 scctl/pkg/plugin/diagnose/compare_holder_test.go   |  51 +++
 scctl/pkg/plugin/diagnose/diagnose.go              | 176 +++++++++++
 scctl/pkg/plugin/diagnose/diagnose_test.go         |  66 ++++
 scctl/pkg/plugin/get/cmd.go                        |  45 +++
 scctl/pkg/plugin/get/instance/instance_cmd.go      |  95 ++++++
 scctl/pkg/plugin/get/instance/types.go             | 118 +++++++
 scctl/pkg/plugin/get/service/service_cmd.go        |  95 ++++++
 scctl/pkg/plugin/get/service/types.go              | 112 +++++++
 scctl/pkg/plugin/version/cmd.go                    |  62 ++++
 pkg/util/sys.go => scctl/pkg/version/version.go    |  57 +---
 scctl/pkg/writer/writer.go                         |  67 ++++
 scripts/build/tools.sh                             |  27 +-
 scripts/release/LICENSE                            |   7 +
 .../licenses/LICENSE-olekukonko-tablewriter        |  19 ++
 scripts/release/make_release.sh                    |   2 +
 version/version.go                                 |  26 +-
 35 files changed, 2156 insertions(+), 277 deletions(-)

diff --git a/README.md b/README.md
index b20813e..8bd99ca 100644
--- a/README.md
+++ b/README.md
@@ -20,7 +20,7 @@ Apache ServiceComb (incubating) Service-Center is a Restful 
based service-regist
  
 ## Documentation
 
-Project documentation is available on the [ServiceComb 
website][servicecomb-website]. You can also find some development guide 
[here](/docs).
+Project documentation is available on the [ServiceComb 
website][servicecomb-website]. You can also find full document [`here`](/docs).
 
 [servicecomb-website]: http://servicecomb.incubator.apache.org/
 
diff --git a/docs/README.md b/docs/README.md
index ba6aef1..9f113cf 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -5,13 +5,12 @@
 #### [Development Guide](/docs/dev-guide.md) 
 
 - [Api 
Documentation](https://rawcdn.githack.com/ServiceComb/service-center/master/docs/api-docs.html)
-
 - [Plug-in Extension](/server/plugin/README.md)
+- [Command Line Client](/scctl/README.md)
 
 #### [Docker Image Guide](/scripts/docker) 
 
 - [Making Service-Center Image](/scripts/docker/build-image)
-
 - [Making Front-end Image](/scripts/docker/build-frontend-image)
 
 #### Deploy Service-Center Locally
@@ -21,9 +20,7 @@
 #### Deploy Service-Center Cluster
 
 - [In Kubernetes Cluster](/integration/k8s)
-
 - [In VMs](/docs/sc-cluster.md)
-
 - [Deploy with TLS](/docs/security-tls.md)
 
 #### Monitoring Service-Center
diff --git a/glide.yaml b/glide.yaml
index 8051f71..55bbdb1 100644
--- a/glide.yaml
+++ b/glide.yaml
@@ -289,3 +289,8 @@ import:
 - package: github.com/natefinch/lumberjack
   version: a96e63847dc3c67d17befa69c303767e2f84e54f
   repo: https://github.com/natefinch/lumberjack
+
+# scctl depends
+- package: github.com/olekukonko/tablewriter
+  version: d4647c9c7a84d847478d890b816b7d8b62b0b279
+  repo: https://github.com/olekukonko/tablewriter
diff --git a/pkg/client/etcd/client.go b/pkg/client/etcd/client.go
new file mode 100644
index 0000000..d7a5f0b
--- /dev/null
+++ b/pkg/client/etcd/client.go
@@ -0,0 +1,66 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 etcd
+
+import (
+       "crypto/tls"
+       "github.com/apache/incubator-servicecomb-service-center/pkg/tlsutil"
+       "github.com/coreos/etcd/clientv3"
+       "io/ioutil"
+       "strings"
+       "time"
+)
+
+var (
+       Addrs       string
+       CertPath    string
+       KeyPath     string
+       KeyPassPath string
+       KeyPass     string
+       CAPath      string
+)
+
+func NewEtcdClient() (*clientv3.Client, error) {
+       var (
+               endpoints = strings.Split(Addrs, ",")
+               cliTls    *tls.Config
+       )
+       for _, ip := range endpoints {
+               if strings.Index(ip, "https://";) >= 0 {
+                       if len(KeyPass) == 0 && len(KeyPassPath) > 0 {
+                               content, _ := ioutil.ReadFile(KeyPassPath)
+                               KeyPass = string(content)
+                       }
+                       opts := append(tlsutil.DefaultClientTLSOptions(),
+                               tlsutil.WithCA(CAPath),
+                               tlsutil.WithCert(CertPath),
+                               tlsutil.WithKey(KeyPath),
+                               tlsutil.WithKeyPass(KeyPass))
+                       cliTls, _ = tlsutil.GetClientTLSConfig(opts...)
+                       break
+               }
+       }
+
+       client, err := clientv3.New(clientv3.Config{
+               Endpoints:   endpoints,
+               DialTimeout: 10 * time.Second,
+               TLS:         cliTls,
+       })
+       if err != nil {
+               return nil, err
+       }
+       return client, nil
+}
diff --git a/pkg/client/sc/apis.go b/pkg/client/sc/apis.go
new file mode 100644
index 0000000..f6dd2fe
--- /dev/null
+++ b/pkg/client/sc/apis.go
@@ -0,0 +1,93 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 sc
+
+import (
+       "encoding/json"
+       "fmt"
+       "github.com/apache/incubator-servicecomb-service-center/pkg/rest"
+       
"github.com/apache/incubator-servicecomb-service-center/server/admin/model"
+       "github.com/apache/incubator-servicecomb-service-center/version"
+       "io/ioutil"
+       "net/http"
+)
+
+const (
+       apiVersionURL = "/version"
+       apiDumpURL    = "/v4/default/admin/dump"
+)
+
+func GetScVersion(scClient *rest.URLClient) (*version.VersionSet, error) {
+       var headers = make(http.Header)
+       if len(Token) > 0 {
+               headers.Set("X-Auth-Token", Token)
+       }
+       resp, err := scClient.HttpDo(http.MethodGet, Addr+apiVersionURL, 
headers, nil)
+       if err != nil {
+               return nil, err
+       }
+       defer resp.Body.Close()
+
+       body, err := ioutil.ReadAll(resp.Body)
+       if err != nil {
+               return nil, err
+       }
+
+       if resp.StatusCode != http.StatusOK {
+               return nil, fmt.Errorf("%d %s", resp.StatusCode, string(body))
+       }
+
+       v := &version.VersionSet{}
+       err = json.Unmarshal(body, v)
+       if err != nil {
+               fmt.Println(string(body))
+               return nil, err
+       }
+
+       return v, nil
+}
+
+func GetScCache(scClient *rest.URLClient) (*model.Cache, error) {
+       headers := http.Header{
+               "X-Domain-Name": []string{"default"},
+       }
+       if len(Token) > 0 {
+               headers.Set("X-Auth-Token", Token)
+       }
+       resp, err := scClient.HttpDo(http.MethodGet, Addr+apiDumpURL, headers, 
nil)
+       if err != nil {
+               return nil, err
+       }
+       defer resp.Body.Close()
+
+       body, err := ioutil.ReadAll(resp.Body)
+       if err != nil {
+               return nil, err
+       }
+
+       if resp.StatusCode != http.StatusOK {
+               return nil, fmt.Errorf("%d %s", resp.StatusCode, string(body))
+       }
+
+       dump := &model.DumpResponse{}
+       err = json.Unmarshal(body, dump)
+       if err != nil {
+               fmt.Println(string(body))
+               return nil, err
+       }
+
+       return dump.Cache, nil
+}
diff --git a/pkg/client/sc/client.go b/pkg/client/sc/client.go
new file mode 100644
index 0000000..b035b0c
--- /dev/null
+++ b/pkg/client/sc/client.go
@@ -0,0 +1,51 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 sc
+
+import (
+       "github.com/apache/incubator-servicecomb-service-center/pkg/rest"
+       "io/ioutil"
+       "strings"
+       "time"
+)
+
+var (
+       Addr        string
+       Token       string
+       VerifyPeer  bool
+       CertPath    string
+       KeyPath     string
+       KeyPassPath string
+       KeyPass     string
+       CAPath      string
+)
+
+func NewSCClient() (*rest.URLClient, error) {
+       ssl := strings.Index(Addr, "https://";) >= 0
+       if ssl && len(KeyPass) == 0 && len(KeyPassPath) > 0 {
+               content, _ := ioutil.ReadFile(KeyPassPath)
+               KeyPass = string(content)
+       }
+       return rest.GetURLClient(&rest.URLClientOption{
+               SSLEnabled:     ssl,
+               VerifyPeer:     VerifyPeer,
+               CAFile:         CAPath,
+               CertFile:       CertPath,
+               CertKeyFile:    KeyPath,
+               CertKeyPWD:     KeyPass,
+               RequestTimeout: 10 * time.Second,
+       })
+}
diff --git a/pkg/rest/client.go b/pkg/rest/client.go
index e443a89..987e3c0 100644
--- a/pkg/rest/client.go
+++ b/pkg/rest/client.go
@@ -1,265 +1,179 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You 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.
- */
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 rest
 
 import (
        "bytes"
-       "compress/gzip"
-       "encoding/json"
+       "crypto/tls"
+       "errors"
        "fmt"
-       "github.com/apache/incubator-servicecomb-service-center/pkg/log"
+       "github.com/apache/incubator-servicecomb-service-center/pkg/tlsutil"
        "github.com/apache/incubator-servicecomb-service-center/pkg/util"
-       "io"
-       "io/ioutil"
-       "net"
        "net/http"
-       "reflect"
+       "net/url"
+       "os"
+       "strings"
        "time"
 )
 
-const (
-       DEFAULT_TLS_HANDSHAKE_TIMEOUT = 30 * time.Second
-       DEFAULT_HTTP_RESPONSE_TIMEOUT = 10 * time.Second
-       DEFAULT_REQUEST_TIMEOUT       = 300 * time.Second
-
-       HTTP_ERROR_STATUS_CODE = 600
-)
-
-type HttpClient struct {
-       gzip   bool
-       Client *http.Client
+var defaultURLClientOption = &URLClientOption{
+       Compressed:            true,
+       VerifyPeer:            true,
+       SSLVersion:            tls.VersionTLS12,
+       HandshakeTimeout:      30 * time.Second,
+       ResponseHeaderTimeout: 180 * time.Second,
+       RequestTimeout:        300 * time.Second,
+       ConnsPerHost:          DEFAULT_CONN_POOL_PER_HOST_SIZE,
 }
 
-func NewDialer() *net.Dialer {
-       return &net.Dialer{
-               Timeout:   5 * time.Second,
-               KeepAlive: 30 * time.Second,
-       }
+var defaultClientTLSOptions = tlsutil.DefaultClientTLSOptions()
+
+type URLClientOption struct {
+       SSLEnabled            bool
+       Compressed            bool
+       VerifyPeer            bool
+       CAFile                string
+       CertFile              string
+       CertKeyFile           string
+       CertKeyPWD            string
+       SSLVersion            uint16
+       HandshakeTimeout      time.Duration
+       ResponseHeaderTimeout time.Duration
+       RequestTimeout        time.Duration
+       ConnsPerHost          int
 }
 
-func NewTransport() *http.Transport {
-       return &http.Transport{
-               Dial:                  NewDialer().Dial,
-               MaxIdleConnsPerHost:   5,
-               ResponseHeaderTimeout: DEFAULT_HTTP_RESPONSE_TIMEOUT,
-               TLSHandshakeTimeout:   DEFAULT_TLS_HANDSHAKE_TIMEOUT,
-       }
-}
+type URLClient struct {
+       *http.Client
 
-/**
-  获取普通HTTP客户端
-*/
+       TLS *tls.Config
 
-func GetHttpClient(gzip bool) (client *HttpClient, err error) {
-       return &HttpClient{
-               gzip: gzip,
-               Client: &http.Client{
-                       Transport: NewTransport(),
-                       Timeout:   DEFAULT_REQUEST_TIMEOUT,
-               },
-       }, nil
-}
+       Request *http.Request
 
-func GetClient() (*HttpClient, error) {
-       return GetHttpClient(false)
+       Cfg URLClientOption
 }
 
-func (client *HttpClient) getHeaders(method string, headers map[string]string, 
body interface{}) map[string]string {
-       newHeaders := make(map[string]string)
-       if body != nil {
-               newHeaders[HEADER_CONTENT_TYPE] = CONTENT_TYPE_JSON
-               newHeaders[HEADER_ACCEPT] = ACCEPT_JSON
+func (client *URLClient) HttpDo(method string, rawURL string, headers 
http.Header, body []byte) (resp *http.Response, err error) {
+       if strings.HasPrefix(rawURL, "https") {
+               if transport, ok := client.Client.Transport.(*http.Transport); 
ok {
+                       transport.TLSClientConfig = client.TLS
+               }
        }
 
-       if client.gzip {
-               newHeaders[HEADER_ACCEPT_ENCODING] = ENCODING_GZIP
-               newHeaders[HEADER_CONTENT_ENCODING] = ENCODING_GZIP
+       if headers == nil {
+               headers = make(http.Header)
        }
 
-       if headers != nil {
-               for key, value := range headers {
-                       newHeaders[key] = value
+       if _, ok := headers["Host"]; !ok {
+               parsedURL, err := url.Parse(rawURL)
+               if err != nil {
+                       return nil, err
                }
+               headers.Set("Host", parsedURL.Host)
        }
-
-       return newHeaders
-}
-
-func gzipCompress(src []byte) (dst []byte) {
-       var byteBuffer bytes.Buffer
-
-       func() {
-               gzipWriter := gzip.NewWriter(&byteBuffer)
-               defer gzipWriter.Close()
-               gzipWriter.Write(src)
-       }()
-
-       return byteBuffer.Bytes()
-}
-
-func readAndGunzip(reader io.Reader) (dst []byte, err error) {
-       gzipReader, err := gzip.NewReader(reader)
-       if err != nil {
-               log.Errorf(err, "duplicate gzip reader failed.")
-               return nil, err
-       }
-
-       defer gzipReader.Close()
-       dst, err = ioutil.ReadAll(gzipReader)
-       if err != nil {
-               log.Errorf(err, "read from gzip reader failed.")
-               return nil, err
+       if _, ok := headers["Accept"]; !ok {
+               headers.Set("Accept", "*/*")
        }
-
-       return dst, nil
-}
-
-func (client *HttpClient) httpDo(method string, url string, headers 
map[string]string, body interface{}) (int, string) {
-       status, result := HTTP_ERROR_STATUS_CODE, ""
-
-       var bodyBytes []byte = nil
-       var err error = nil
-       var bodyReader io.Reader = nil
-       if body != nil {
-               if headers == nil || len(headers[HEADER_CONTENT_TYPE]) == 0 {
-                       // 
如果请求头未传入Content-Type,则按照json格式进行编码(如果是非json类型,需要自行在headers里指定类型)
-                       bodyBytes, err = json.Marshal(body)
-                       if err != nil {
-                               log.Errorf(err, "marshal object failed.")
-                               return status, result
-                       }
-               } else {
-                       // 如果指定了Content-Type类型,则传入的body必须为byte流
-                       var ok bool = false
-                       bodyBytes, ok = body.([]byte)
-                       if !ok {
-                               log.Errorf(nil,
-                                       "invalid body type '%s'(%s), body must 
type of byte array if Content-Type specified.",
-                                       reflect.TypeOf(body), 
headers[HEADER_CONTENT_TYPE])
-                               return status, result
-                       }
-               }
-
-               //如果配置了gzip压缩,则对body压缩一次(如果请求头里传入已经gzip压缩了,则不重复压缩)
-               if client.gzip && (headers == nil || 
headers[HEADER_CONTENT_ENCODING] != ENCODING_GZIP) {
-                       bodyBytes = gzipCompress(bodyBytes)
-               }
-
-               bodyReader = bytes.NewBuffer(bodyBytes)
+       if _, ok := headers["Accept-Encoding"]; !ok && client.Cfg.Compressed {
+               headers.Set("Accept-Encoding", "deflate, gzip")
        }
 
-       req, err := http.NewRequest(method, url, bodyReader)
+       req, err := http.NewRequest(method, rawURL, bytes.NewBuffer(body))
        if err != nil {
-               log.Errorf(err, "create request failed.")
-               return status, result
+               return nil, errors.New(fmt.Sprintf("create request failed: %s", 
err.Error()))
        }
+       client.Request = req
 
-       newHeaders := client.getHeaders(method, headers, body)
-       for key, value := range newHeaders {
-               req.Header.Set(key, value)
-       }
+       req.Header = headers
 
-       resp, err := client.Client.Do(req)
+       resp, err = client.Client.Do(req)
        if err != nil {
-               log.Errorf(err, "invoke request failed.")
-               return status, result
+               return nil, errors.New(fmt.Sprintf("invoke request failed: %s", 
err.Error()))
        }
 
-       defer resp.Body.Close()
-       status = resp.StatusCode
-       var respBody []byte
-       if resp.Header.Get(HEADER_CONTENT_ENCODING) == ENCODING_GZIP {
-               // 如果响应头里包含了响应消息的压缩格式为gzip,则在返回前先解压缩
-               respBody, _ = readAndGunzip(resp.Body)
-       } else {
-               respBody, _ = ioutil.ReadAll(resp.Body)
-       }
-       result = util.BytesToStringWithNoCopy(respBody)
-
-       return status, result
-}
-
-func (client *HttpClient) HttpDo(method string, url string, headers 
map[string]string, body interface{}) (*http.Response, error) {
-       var bodyBytes []byte = nil
-       var err error = nil
-       var bodyReader io.Reader = nil
-       if body != nil {
-               if headers == nil || len(headers[HEADER_CONTENT_TYPE]) == 0 {
-                       // 
如果请求头未传入Conent-Type,则按照json格式进行编码(如果是非json类型,需要自行在headers里指定类型)
-                       bodyBytes, err = json.Marshal(body)
-                       if err != nil {
-                               log.Errorf(err, "marshal object failed.")
-                               return nil, err
-                       }
-               } else {
-                       // 如果指定了Content-Type类型,则传入的body必须为byte流
-                       var ok bool = false
-                       bodyBytes, ok = body.([]byte)
-                       if !ok {
-                               err := fmt.Errorf("invalid body type '%s'(%s), 
body must type of byte array if Content-Type specified.",
-                                       reflect.TypeOf(body), 
headers[HEADER_CONTENT_TYPE])
-                               log.Errorf(err, "")
-                               return nil, err
+       if os.Getenv("DEBUG_MODE") == "1" {
+               fmt.Println("--- BEGIN ---")
+               fmt.Printf("> %s %s %s\n", client.Request.Method, 
client.Request.URL.RequestURI(), client.Request.Proto)
+               for key, header := range client.Request.Header {
+                       for _, value := range header {
+                               fmt.Printf("> %s: %s\n", key, value)
                        }
                }
-
-               //如果配置了gzip压缩,则对body压缩一次(如果请求头里传入已经gzip压缩了,则不重复压缩)
-               if client.gzip && (headers == nil || 
headers[HEADER_CONTENT_ENCODING] != ENCODING_GZIP) {
-                       bodyBytes = gzipCompress(bodyBytes)
+               fmt.Println(">")
+               fmt.Println(util.BytesToStringWithNoCopy(body))
+               fmt.Printf("< %s %s\n", resp.Proto, resp.Status)
+               for key, header := range resp.Header {
+                       for _, value := range header {
+                               fmt.Printf("< %s: %s\n", key, value)
+                       }
                }
-
-               bodyReader = bytes.NewBuffer(bodyBytes)
+               fmt.Println("<")
+               fmt.Println("--- END ---")
        }
+       return resp, nil
+}
 
-       req, err := http.NewRequest(method, url, bodyReader)
-       if err != nil {
-               log.Errorf(err, "create request failed.")
-               return nil, err
+func setOptionDefaultValue(o *URLClientOption) URLClientOption {
+       if o == nil {
+               return *defaultURLClientOption
        }
 
-       newHeaders := client.getHeaders(method, headers, body)
-       for key, value := range newHeaders {
-               req.Header.Set(key, value)
+       option := *o
+       if option.RequestTimeout <= 0 {
+               option.RequestTimeout = defaultURLClientOption.RequestTimeout
        }
-
-       resp, err := client.Client.Do(req)
-       if err != nil {
-               log.Errorf(err, "Request -----> %s failed.", url)
-               return resp, err
+       if option.HandshakeTimeout <= 0 {
+               option.HandshakeTimeout = 
defaultURLClientOption.HandshakeTimeout
        }
-       return resp, err
-}
-
-func (client *HttpClient) Get(url string, headers map[string]string) (int, 
string) {
-       return client.httpDo(http.MethodGet, url, headers, nil)
-}
-
-func (client *HttpClient) Put(url string, headers map[string]string, body 
interface{}) (int, string) {
-       return client.httpDo(http.MethodPut, url, headers, body)
+       if option.ResponseHeaderTimeout <= 0 {
+               option.ResponseHeaderTimeout = 
defaultURLClientOption.ResponseHeaderTimeout
+       }
+       if option.SSLVersion == 0 {
+               option.SSLVersion = defaultURLClientOption.SSLVersion
+       }
+       return option
 }
 
-func (client *HttpClient) Post(url string, headers map[string]string, body 
interface{}) (int, string) {
-       return client.httpDo(http.MethodPost, url, headers, body)
-}
+func GetURLClient(o *URLClientOption) (client *URLClient, err error) {
+       option := setOptionDefaultValue(o)
+       client = &URLClient{
+               Client: &http.Client{
+                       Transport: &http.Transport{
+                               MaxIdleConnsPerHost:   option.ConnsPerHost,
+                               TLSHandshakeTimeout:   option.HandshakeTimeout,
+                               ResponseHeaderTimeout: 
option.ResponseHeaderTimeout,
+                               DisableCompression:    !option.Compressed,
+                       },
+                       Timeout: option.RequestTimeout,
+               },
+               Cfg: option,
+       }
 
-func (client *HttpClient) Delete(url string, headers map[string]string) (int, 
string) {
-       return client.httpDo(http.MethodDelete, url, headers, nil)
-}
+       if option.SSLEnabled {
+               opts := append(defaultClientTLSOptions,
+                       tlsutil.WithVerifyPeer(option.VerifyPeer),
+                       tlsutil.WithCA(option.CAFile),
+                       tlsutil.WithCert(option.CertFile),
+                       tlsutil.WithKey(option.CertKeyFile),
+                       tlsutil.WithKeyPass(option.CertKeyPWD))
 
-func (client *HttpClient) Do(req *http.Request) (*http.Response, error) {
-       return client.Client.Do(req)
+               client.TLS, err = tlsutil.GetClientTLSConfig(opts...)
+               if err != nil {
+                       return nil, err
+               }
+       }
+       return
 }
diff --git a/pkg/rest/common.go b/pkg/rest/common.go
index 182f413..7185838 100644
--- a/pkg/rest/common.go
+++ b/pkg/rest/common.go
@@ -48,6 +48,8 @@ const (
        CONTENT_TYPE_TEXT = "text/plain; charset=UTF-8"
 
        ENCODING_GZIP = "gzip"
+
+       DEFAULT_CONN_POOL_PER_HOST_SIZE = 5
 )
 
 func isValidMethod(method string) bool {
diff --git a/pkg/util/sys.go b/pkg/util/sys.go
index 9bd81ef..2d4ffe6 100644
--- a/pkg/util/sys.go
+++ b/pkg/util/sys.go
@@ -64,3 +64,11 @@ func GetEnvInt(name string, def int) int {
        }
        return def
 }
+
+func GetEnvString(name string, def string) string {
+       env, ok := os.LookupEnv(name)
+       if ok {
+               return env
+       }
+       return def
+}
diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go
index 6bee8fc..a168ddf 100644
--- a/pkg/util/util_test.go
+++ b/pkg/util/util_test.go
@@ -140,6 +140,7 @@ func TestSystemPackage(t *testing.T) {
 }
 
 func TestGetEnvInt(t *testing.T) {
+       os.Unsetenv("a")
        if GetEnvInt("a", 1) != 1 {
                t.Fatalf("TestGetEnvInt failed")
        }
@@ -156,3 +157,18 @@ func TestGetEnvInt(t *testing.T) {
                t.Fatalf("TestGetEnvInt failed")
        }
 }
+
+func TestGetEnvString(t *testing.T) {
+       os.Unsetenv("a")
+       if GetEnvString("a", "1") != "1" {
+               t.Fatalf("TestGetEnvInt failed")
+       }
+       os.Setenv("a", "")
+       if GetEnvString("a", "1") != "" {
+               t.Fatalf("TestGetEnvInt failed")
+       }
+       os.Setenv("a", "2")
+       if GetEnvString("a", "1") != "2" {
+               t.Fatalf("TestGetEnvInt failed")
+       }
+}
diff --git a/scctl/README.md b/scctl/README.md
new file mode 100644
index 0000000..7d3c8c6
--- /dev/null
+++ b/scctl/README.md
@@ -0,0 +1,47 @@
+## scctl
+
+`scctl` enables user to view the list of MicroServices registered in SC.
+You can view all the commands from [here](/scctl/pkg/plugin/README.md)
+
+### QuickStart Guide
+
+##### Install
+Easiest way to get started with `scctl` is to download the release 
+from 
[here](https://dist.apache.org/repos/dist/dev/incubator/servicecomb/incubator-servicecomb-service-center/)
+and then untar/unzip it based on your OS.
+
+##### Check the version
+Windows(apache-incubator-servicecomb-service-center-XXX-windows-amd64.zip):
+```
+scctl.exe version
+```
+
+Linux(apache-incubator-servicecomb-service-center-XXXX-linux-amd64.tar.gz):
+```sh
+./scctl version
+```
+
+Note: If you already bootstrap SC and listen on `127.0.0.1:30100`, this
+command will also print the SC version.
+
+### Running scctl from source code
+However if you want to try our latest code then you can follow the below steps
+```
+#Make sure your GOPATH is set correctly and download all the vendors of SC
+git clone https://github.com/apache/incubator-servicecomb-service-center.git 
$GOPATH/src/github.com/apache/incubator-servicecomb-service-center
+cd $GOPATH/src/github.com/apache/incubator-servicecomb-service-center
+
+cd scctl
+
+go build
+
+```
+Windows:
+```
+scctl.exe version
+```
+
+Linux:
+```sh
+./scctl version
+```
diff --git a/scctl/bootstrap/bootstrap.go b/scctl/bootstrap/bootstrap.go
new file mode 100644
index 0000000..5671663
--- /dev/null
+++ b/scctl/bootstrap/bootstrap.go
@@ -0,0 +1,21 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 bootstrap
+
+import _ 
"github.com/apache/incubator-servicecomb-service-center/scctl/pkg/plugin/version"
+import _ 
"github.com/apache/incubator-servicecomb-service-center/scctl/pkg/plugin/diagnose"
+import _ 
"github.com/apache/incubator-servicecomb-service-center/scctl/pkg/plugin/get/service"
+import _ 
"github.com/apache/incubator-servicecomb-service-center/scctl/pkg/plugin/get/instance"
diff --git a/scctl/main.go b/scctl/main.go
new file mode 100644
index 0000000..fae8bbe
--- /dev/null
+++ b/scctl/main.go
@@ -0,0 +1,25 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You under the Apache License, Version 2.0
+// (the "License"); you may not use this file except in compliance with
+// the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package main
+
+import (
+       _ 
"github.com/apache/incubator-servicecomb-service-center/scctl/bootstrap"
+       "github.com/apache/incubator-servicecomb-service-center/scctl/pkg/cmd"
+)
+
+func main() {
+       cmd.Run()
+}
diff --git a/scctl/pkg/cmd/cmd.go b/scctl/pkg/cmd/cmd.go
new file mode 100644
index 0000000..e015d3d
--- /dev/null
+++ b/scctl/pkg/cmd/cmd.go
@@ -0,0 +1,98 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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/apache/incubator-servicecomb-service-center/pkg/client/sc"
+       "github.com/apache/incubator-servicecomb-service-center/pkg/util"
+       
"github.com/apache/incubator-servicecomb-service-center/scctl/pkg/version"
+       "github.com/spf13/cobra"
+       "os"
+       "path/filepath"
+)
+
+const (
+       ExitSuccess = iota
+       ExitError
+)
+
+var rootCmd = &cobra.Command{
+       Use:   version.TOOL_NAME + " <command>",
+       Short: "The admin control command of service center",
+}
+
+func init() {
+       rootCmd.PersistentFlags().BoolP("verbose", "v", false, "make the 
operation more talkative")
+       rootCmd.PersistentPreRun = func(cmd *cobra.Command, args []string) {
+               if v, _ := cmd.Flags().GetBool("verbose"); v {
+                       os.Setenv("DEBUG_MODE", "1")
+               }
+       }
+
+       rootCmd.PersistentFlags().StringVar(&sc.Addr, "addr",
+               "http://"+util.GetEnvString("HOSTING_SERVER_IP", 
"127.0.0.1")+":30100",
+               "the http host and port of service center, can be overrode by 
env HOSTING_SERVER_IP.")
+
+       rootCmd.PersistentFlags().StringVarP(&sc.Token, "token", "t", "",
+               "the auth token string to access service center.")
+
+       rootCmd.PersistentFlags().BoolVarP(&sc.VerifyPeer, "peer", "p", false,
+               "verify service center certificates.")
+       rootCmd.PersistentFlags().StringVar(&sc.CertPath, "cert",
+               filepath.Join(util.GetEnvString("SSL_ROOT", "."), "server.cer"),
+               "the certificate file path to access service center, can be 
overrode by $SSL_ROOT/server.cer.")
+       rootCmd.PersistentFlags().StringVar(&sc.KeyPath, "key",
+               filepath.Join(util.GetEnvString("SSL_ROOT", "."), 
"server_key.pem"),
+               "the key file path to access service center, can be overrode by 
$SSL_ROOT/server_key.pem.")
+       rootCmd.PersistentFlags().StringVar(&sc.CAPath, "ca",
+               filepath.Join(util.GetEnvString("SSL_ROOT", "."), "trust.cer"),
+               "the CA file path  to access service center, can be overrode by 
$SSL_ROOT/trust.cer.")
+       rootCmd.PersistentFlags().StringVar(&sc.KeyPassPath, "pass-file",
+               filepath.Join(util.GetEnvString("SSL_ROOT", "."), "cert_pwd"),
+               "the passphase file path to decrypt key file, can be overrode 
by $SSL_ROOT/cert_pwd.")
+       rootCmd.PersistentFlags().StringVar(&sc.KeyPass, "pass", "",
+               "the passphase string to decrypt key file.")
+}
+
+func RootCmd() *cobra.Command {
+       return rootCmd
+}
+
+func StopAndExit(code int, args ...interface{}) {
+       if len(args) == 0 {
+               os.Exit(code)
+       }
+
+       if code == ExitSuccess {
+               fmt.Fprintln(os.Stdout, args...)
+       } else {
+               fmt.Fprintln(os.Stderr, args...)
+       }
+       os.Exit(code)
+}
+
+func Run() {
+       RootCmd().SetUsageFunc(UsageFunc)
+       // Show usage in help command
+       RootCmd().SetHelpTemplate(`{{.UsageString}}`)
+
+       err := RootCmd().Execute()
+       if err != nil {
+               StopAndExit(ExitError, err)
+       }
+       StopAndExit(ExitSuccess)
+}
diff --git a/scctl/pkg/cmd/help.go b/scctl/pkg/cmd/help.go
new file mode 100644
index 0000000..d78f84c
--- /dev/null
+++ b/scctl/pkg/cmd/help.go
@@ -0,0 +1,169 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 (
+       "bytes"
+       "fmt"
+       
"github.com/apache/incubator-servicecomb-service-center/scctl/pkg/version"
+       "github.com/spf13/cobra"
+       "github.com/spf13/pflag"
+       "io"
+       "os"
+       "strings"
+       "text/tabwriter"
+       "text/template"
+)
+
+var (
+       commandUsageTemplate *template.Template
+       templFuncs           = template.FuncMap{
+               "descToLines": func(s string) []string {
+                       // trim leading/trailing whitespace and split into 
slice of lines
+                       return strings.Split(strings.Trim(s, "\n\t "), "\n")
+               },
+               "cmdName": func(cmd *cobra.Command, startCmd *cobra.Command) 
string {
+                       parts := []string{cmd.Name()}
+                       for cmd.HasParent() && cmd.Parent().Name() != 
startCmd.Name() {
+                               cmd = cmd.Parent()
+                               parts = append([]string{cmd.Name()}, parts...)
+                       }
+                       return strings.Join(parts, " ")
+               },
+       }
+)
+
+func init() {
+       commandUsage := `
+{{ $cmd := .Cmd }}\
+{{ $cmdname := cmdName .Cmd .Cmd.Root }}\
+NAME:
+{{ if not .Cmd.HasParent }}\
+{{printf "\t%s - %s" .Cmd.Name .Cmd.Short}}
+{{else}}\
+{{printf "\t%s - %s" $cmdname .Cmd.Short}}
+{{end}}\
+
+USAGE:
+{{printf "\t%s" .Cmd.UseLine}}
+{{ if .Cmd.HasExample }}\
+
+EXAMPLE:
+{{printf "\t%s" .Cmd.Example}}
+{{end}}\
+{{ if not .Cmd.HasParent }}\
+
+VERSION:
+{{printf "\t%s" .Version}}
+{{end}}\
+{{if .Cmd.HasSubCommands}}\
+
+COMMANDS:
+{{range .SubCommands}}\
+{{ $cmdname := cmdName . $cmd }}\
+{{ if .Runnable }}\
+{{printf "\t%s\t%s" $cmdname .Short}}
+{{end}}\
+{{end}}\
+{{end}}\
+{{ if .Cmd.Long }}\
+
+DESCRIPTION:
+{{range $line := descToLines .Cmd.Long}}{{printf "\t%s" $line}}
+{{end}}\
+{{end}}\
+{{if .Cmd.HasLocalFlags}}\
+
+OPTIONS:
+{{.LocalFlags}}\
+{{end}}\
+{{if .Cmd.HasInheritedFlags}}\
+
+GLOBAL OPTIONS:
+{{.GlobalFlags}}\
+{{end}}
+`[1:]
+
+       commandUsageTemplate = 
template.Must(template.New("command_usage").Funcs(templFuncs).Parse(strings.Replace(commandUsage,
 "\\\n", "", -1)))
+}
+
+func toolFlagUsages(flagSet *pflag.FlagSet) string {
+       x := new(bytes.Buffer)
+
+       flagSet.VisitAll(func(flag *pflag.Flag) {
+               if len(flag.Deprecated) > 0 {
+                       return
+               }
+               format := ""
+               if len(flag.Shorthand) > 0 {
+                       format = "  -%s, --%s"
+               } else {
+                       format = "   %s   --%s"
+               }
+               if len(flag.NoOptDefVal) > 0 {
+                       format = format + "["
+               }
+               if flag.Value.Type() == "string" {
+                       // put quotes on the value
+                       format = format + "=%q"
+               } else {
+                       format = format + "=%s"
+               }
+               if len(flag.NoOptDefVal) > 0 {
+                       format = format + "]"
+               }
+               format = format + "\t%s\n"
+               shorthand := flag.Shorthand
+               fmt.Fprintf(x, format, shorthand, flag.Name, flag.DefValue, 
flag.Usage)
+       })
+
+       return x.String()
+}
+
+func getSubCommands(cmd *cobra.Command) []*cobra.Command {
+       var subCommands []*cobra.Command
+       for _, subCmd := range cmd.Commands() {
+               subCommands = append(subCommands, subCmd)
+               subCommands = append(subCommands, getSubCommands(subCmd)...)
+       }
+       return subCommands
+}
+
+func UsageFunc(cmd *cobra.Command) error {
+       subCommands := getSubCommands(cmd)
+       tabOut := getTabOutWithWriter(os.Stdout)
+       commandUsageTemplate.Execute(tabOut, struct {
+               Cmd         *cobra.Command
+               LocalFlags  string
+               GlobalFlags string
+               SubCommands []*cobra.Command
+               Version     string
+       }{
+               cmd,
+               toolFlagUsages(cmd.LocalFlags()),
+               toolFlagUsages(cmd.InheritedFlags()),
+               subCommands,
+               version.Ver().Version,
+       })
+       tabOut.Flush()
+       return nil
+}
+
+func getTabOutWithWriter(writer io.Writer) *tabwriter.Writer {
+       aTabOut := new(tabwriter.Writer)
+       aTabOut.Init(writer, 0, 8, 1, '\t', 0)
+       return aTabOut
+}
diff --git a/scctl/pkg/model/types.go b/scctl/pkg/model/types.go
new file mode 100644
index 0000000..202545a
--- /dev/null
+++ b/scctl/pkg/model/types.go
@@ -0,0 +1,122 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 model
+
+import (
+       "github.com/apache/incubator-servicecomb-service-center/pkg/util"
+       
"github.com/apache/incubator-servicecomb-service-center/server/admin/model"
+       
"github.com/apache/incubator-servicecomb-service-center/server/core/backend"
+       
"github.com/apache/incubator-servicecomb-service-center/server/core/proto"
+       "strconv"
+       "time"
+)
+
+func GetDomainProject(resouce interface{}) (domainProject string) {
+       switch resouce.(type) {
+       case *model.Microservice:
+               _, domainProject = backend.GetInfoFromSvcKV(&backend.KeyValue{
+                       Key: 
util.StringToBytesWithNoCopy(resouce.(*model.Microservice).Key)})
+       case *model.Instance:
+               _, _, domainProject = 
backend.GetInfoFromInstKV(&backend.KeyValue{
+                       Key: 
util.StringToBytesWithNoCopy(resouce.(*model.Instance).Key)})
+       }
+       return
+}
+
+type Service struct {
+       DomainProject string
+       Environment   string
+       AppId         string
+       ServiceName   string
+       Versions      []string
+       Frameworks    []*proto.FrameWorkProperty
+       Endpoints     []string
+       Timestamp     int64 // the seconds from 0 to now
+}
+
+func (s *Service) AppendVersion(v string) {
+       s.Versions = append(s.Versions, v)
+}
+
+func (s *Service) AppendFramework(property *proto.FrameWorkProperty) {
+       if property == nil || property.Name == "" {
+               return
+       }
+       for _, fw := range s.Frameworks {
+               if fw.Name == property.Name && fw.Version == property.Version {
+                       return
+               }
+       }
+       s.Frameworks = append(s.Frameworks, property)
+}
+
+func (s *Service) AppendEndpoints(endpoints []string) {
+       s.Endpoints = append(s.Endpoints, endpoints...)
+}
+
+func (s *Service) UpdateTimestamp(t string) {
+       d, err := strconv.ParseInt(t, 10, 64)
+       if err != nil {
+               return
+       }
+       if s.Timestamp == 0 || s.Timestamp > d {
+               s.Timestamp = d
+       }
+}
+
+func (s *Service) Age() time.Duration {
+       return time.Now().Sub(time.Unix(s.Timestamp, 0).Local())
+}
+
+type Instance struct {
+       DomainProject string
+       Host          string
+       Endpoints     []string
+       Environment   string
+       AppId         string
+       ServiceName   string
+       Version       string
+       Framework     *proto.FrameWorkProperty
+       Lease         int64 // seconds
+       Timestamp     int64 // the seconds from 0 to now
+}
+
+func (s *Instance) SetLease(hc *proto.HealthCheck) {
+       if hc == nil {
+               s.Lease = -1
+               return
+       }
+       if hc.Mode == proto.CHECK_BY_PLATFORM {
+               s.Lease = 0
+               return
+       }
+       s.Lease = int64(hc.Interval * (hc.Times + 1))
+       return
+}
+
+func (s *Instance) UpdateTimestamp(t string) {
+       d, err := strconv.ParseInt(t, 10, 64)
+       if err != nil {
+               return
+       }
+       if s.Timestamp == 0 || s.Timestamp > d {
+               s.Timestamp = d
+       }
+}
+
+func (s *Instance) Age() time.Duration {
+       return time.Now().Sub(time.Unix(s.Timestamp, 0).Local())
+}
diff --git a/scctl/pkg/plugin/README.md b/scctl/pkg/plugin/README.md
new file mode 100644
index 0000000..464283b
--- /dev/null
+++ b/scctl/pkg/plugin/README.md
@@ -0,0 +1,107 @@
+scctl
+========
+
+`scctl` is a command line client for ServiceCenter.
+
+## Global options
+
+- `addr` the http host and port of service center, can be overrode by env 
`HOSTING_SERVER_IP`.
+- `ca` the CA file path  to access service center, can be overrode by 
`$SSL_ROOT`/trust.cer.
+- `cert` the certificate file path to access service center, can be overrode 
by `$SSL_ROOT`/server.cer.
+- `key` the key file path to access service center, can be overrode by 
`$SSL_ROOT`/server_key.pem.
+- `pass` the passphase string to decrypt key file.
+- `pass-file` the passphase file path to decrypt key file, can be overrode by 
`$SSL_ROOT`/cert_pwd.
+
+## Get commands
+
+### service [options]
+
+Get the microservices list from ServiceCenter. `service` command can be 
instead of `svc`.
+
+#### Options
+
+- `domain`(d) domain name, return `default` domain microservices list by 
default.
+- `output`(o) return the complete microservices information(e.g., framework, 
endpoints).
+- `all-domains` return all domains microservices information.
+
+#### Examples
+
+```bash
+./scctl get svc
+#       NAME      |   APPID   | VERSIONS |     ENV     |               
FRAMEWORK                | AGE  
+# 
+---------------+-----------+----------+-------------+----------------------------------------+-----+
+#   SERVICECENTER | default   | 0.0.1    | development |                       
                 | 9d   
+#   Client        | springmvc | 1.0.0    |             |                       
                 | 9d   
+#   consumer      | springmvc | 0.0.1    |             | 
servicecomb-java-chassis-CSE:2.3.35... | 9d   
+#   provider      | springmvc | 0.0.1    |             | 
servicecomb-java-chassis-CSE:2.3.35... | 9d
+
+./scctl get svc -owide
+#   DOMAIN  |     NAME      |   APPID   | VERSIONS |     ENV     |             
            FRAMEWORK                          |        ENDPOINTS        | AGE  
+# 
+---------+---------------+-----------+----------+-------------+------------------------------------------------------------+-------------------------+-----+
+#   default | Client        | springmvc | 1.0.0    |             |             
                                               |                         | 9d   
+#   default | consumer      | springmvc | 0.0.1    |             | 
servicecomb-java-chassis-CSE:2.3.35;ServiceComb:1.1.0.B006 |                    
     | 9d   
+#   default | provider      | springmvc | 0.0.1    |             | 
servicecomb-java-chassis-CSE:2.3.35;ServiceComb:1.1.0.B006 |                    
     | 9d   
+#   default | SERVICECENTER | default   | 0.0.1    | development |             
                                               | rest://127.0.0.1:30100/ | 9d
+
+./scctl get svc -d test
+#    NAME  |  APPID  |  VERSIONS   | ENV | FRAMEWORK | AGE  
+# +--------+---------+-------------+-----+-----------+-----+
+#   Server | default | 1.0.0,1.0.1 |     |           | 1d
+```
+
+### instance [options]
+
+Get the instances list from ServiceCenter. `instance` command can be instead 
of `inst`.
+
+#### Options
+
+- `domain`(d) domain name, return `default` domain microservices list by 
default.
+- `output`(o) return the complete microservices information(e.g., framework, 
endpoints).
+- `all-domains` return all domains microservices information.
+
+#### Examples
+```bash
+./scctl get inst
+#       HOST     |        ENDPOINTS        | VERSION |    SERVICE    |  APPID  
| LEASE | AGE  
+# 
+--------------+-------------------------+---------+---------------+---------+-------+-----+
+#   desktop-0001 | rest://127.0.0.1:30100/ | 0.0.1   | SERVICECENTER | default 
| 2m    | 11m 
+
+./scctl get inst -owide
+#   DOMAIN  |     HOST     |        ENDPOINTS        | VERSION |    SERVICE    
|  APPID  |     ENV     | FRAMEWORK | LEASE | AGE  
+# 
+---------+--------------+-------------------------+---------+---------------+---------+-------------+-----------+-------+-----+
+#   default | desktop-0001 | rest://127.0.0.1:30100/ | 0.0.1   | SERVICECENTER 
| default | development |           | 2m    | 17m
+
+./scctl get inst -d default
+#       HOST     |        ENDPOINTS        | VERSION |    SERVICE    |  APPID  
| LEASE | AGE  
+# 
+--------------+-------------------------+---------+---------------+---------+-------+-----+
+#   desktop-0001 | rest://127.0.0.1:30100/ | 0.0.1   | SERVICECENTER | default 
| 2m    | 18m
+```
+
+## Diagnose commands
+
+The diagnostic command can output the ServiceCenter health report. 
+If the service center is isolated from etcd, the diagnosis will print wrong 
information.
+
+#### Options
+
+- `etcd-addr` the http addr and port of etcd endpoints
+- `etcd-ca` the CA file path  to access etcd, can be overrode by env 
`$SSL_ROOT`/trust.cer.
+- `etcd-cert` the certificate file path to access etcd, can be overrode by env 
`$SSL_ROOT`/server.cer.
+- `etcd-key` the key file path to access etcd, can be overrode by env 
`$SSL_ROOT`/server_key.pem.
+- `etcd-pass` the passphase string to decrypt key file.
+- `etcd-pass-file` the passphase file path to decrypt key file, can be 
overrode by env `$SSL_ROOT`/cert_pwd.
+
+#### Examples
+```bash
+./scctl diagnose
+echo exit $?
+# exit 0
+
+./scctl diagnose
+echo exit $?
+# 1. found in etcd but not in cache, details:
+#   service: [springmvc/Client/1.0.0 springmvc/consumer/0.0.1 
springmvc/provider/0.0.1 default/SERVICECENTER/0.0.1 default/Server/1.0.0 
default/Server/1.0.1]
+#   instance: [[rest://127.0.0.1:30100/]]
+# error: 1. found in etcd but not in cache
+# exit 1
+```
\ No newline at end of file
diff --git a/scctl/pkg/plugin/diagnose/cmd.go b/scctl/pkg/plugin/diagnose/cmd.go
new file mode 100644
index 0000000..81ae1d4
--- /dev/null
+++ b/scctl/pkg/plugin/diagnose/cmd.go
@@ -0,0 +1,57 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 diagnose
+
+import (
+       "github.com/apache/incubator-servicecomb-service-center/pkg/client/etcd"
+       "github.com/apache/incubator-servicecomb-service-center/pkg/util"
+       root 
"github.com/apache/incubator-servicecomb-service-center/scctl/pkg/cmd"
+       "github.com/spf13/cobra"
+       "path/filepath"
+)
+
+func init() {
+       root.RootCmd().AddCommand(NewDiagnoseCommand(root.RootCmd()))
+}
+
+func NewDiagnoseCommand(parent *cobra.Command) *cobra.Command {
+       cmd := &cobra.Command{
+               Use:     "diagnose [options]",
+               Short:   "Output the service center diagnostic report",
+               Run:     DiagnoseCommandFunc,
+               Example: parent.CommandPath() + ` diagnose --addr 
"http://127.0.0.1:30100"; --etcd-addr "http://127.0.0.1:2379";`,
+       }
+
+       cmd.Flags().StringVar(&etcd.Addrs, "etcd-addr",
+               util.GetEnvString("CSE_REGISTRY_ADDRESS", 
"http://127.0.0.1:2379";),
+               "the http addr and port of etcd endpoints")
+       cmd.Flags().StringVar(&etcd.CertPath, "etcd-cert",
+               filepath.Join(util.GetEnvString("SSL_ROOT", "."), "server.cer"),
+               "the certificate file path to access etcd, can be overrode by 
env $SSL_ROOT/server.cer.")
+       cmd.Flags().StringVar(&etcd.KeyPath, "etcd-key",
+               filepath.Join(util.GetEnvString("SSL_ROOT", "."), 
"server_key.pem"),
+               "the key file path to access etcd, can be overrode by env 
$SSL_ROOT/server_key.pem.")
+       cmd.Flags().StringVar(&etcd.CAPath, "etcd-ca",
+               filepath.Join(util.GetEnvString("SSL_ROOT", "."), "trust.cer"),
+               "the CA file path  to access etcd, can be overrode by env 
$SSL_ROOT/trust.cer.")
+       cmd.Flags().StringVar(&etcd.KeyPassPath, "etcd-pass-file",
+               filepath.Join(util.GetEnvString("SSL_ROOT", "."), "cert_pwd"),
+               "the passphase file path to decrypt key file, can be overrode 
by env $SSL_ROOT/cert_pwd.")
+       cmd.Flags().StringVar(&etcd.KeyPass, "etcd-pass", "",
+               "the passphase string to decrypt key file.")
+
+       return cmd
+}
diff --git a/scctl/pkg/plugin/diagnose/compare_holder.go 
b/scctl/pkg/plugin/diagnose/compare_holder.go
new file mode 100644
index 0000000..7481fa8
--- /dev/null
+++ b/scctl/pkg/plugin/diagnose/compare_holder.go
@@ -0,0 +1,168 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 diagnose
+
+import (
+       "fmt"
+       "github.com/apache/incubator-servicecomb-service-center/pkg/gopool"
+       
"github.com/apache/incubator-servicecomb-service-center/server/admin/model"
+       
"github.com/apache/incubator-servicecomb-service-center/server/core/backend"
+       pb 
"github.com/apache/incubator-servicecomb-service-center/server/core/proto"
+       "github.com/coreos/etcd/mvcc/mvccpb"
+       "golang.org/x/net/context"
+)
+
+type CompareHolder interface {
+       Compare() *CompareResult
+}
+
+type DataStore struct {
+       Data       []*mvccpb.KeyValue
+       DataParser backend.Parser
+}
+
+func (d *DataStore) ForEach(f func(i int, v *model.KV)) {
+       for i, kv := range d.Data {
+               obj, _ := d.DataParser.Unmarshal(kv.Value)
+               f(i, &model.KV{Key: string(kv.Key), Rev: kv.ModRevision, Value: 
obj})
+       }
+}
+
+type CompareResult struct {
+       Name    string
+       Results map[int][]string
+}
+
+type abstractCompareHolder struct {
+       Cache        model.Getter
+       DataStore    *DataStore
+       MismatchFunc func(v *model.KV) string
+}
+
+func (h *abstractCompareHolder) toMap(getter model.Getter) 
map[string]*model.KV {
+       m := make(map[string]*model.KV)
+       getter.ForEach(func(i int, v *model.KV) {
+               m[v.Key] = v
+       })
+       return m
+}
+
+func (h *abstractCompareHolder) Compare() *CompareResult {
+       result := &CompareResult{
+               Results: make(map[int][]string),
+       }
+       leftCh := make(chan map[string]*model.KV, 2)
+       rightCh := make(chan map[string]*model.KV, 2)
+
+       var (
+               add    []string
+               update []string
+               del    []string
+       )
+
+       gopool.New(context.Background(), gopool.Configure().Workers(3)).
+               Do(func(_ context.Context) {
+                       left := h.toMap(h.Cache)
+                       leftCh <- left
+                       leftCh <- left
+               }).
+               Do(func(_ context.Context) {
+                       right := h.toMap(h.DataStore)
+                       rightCh <- right
+                       rightCh <- right
+               }).
+               Do(func(_ context.Context) {
+                       left := <-leftCh
+                       right := <-rightCh
+                       // add or update
+                       for lk, lkv := range left {
+                               rkv, ok := right[lk]
+                               if !ok {
+                                       add = append(add, h.MismatchFunc(lkv))
+                                       continue
+                               }
+                               if rkv.Rev != lkv.Rev {
+                                       update = append(update, 
h.MismatchFunc(lkv))
+                               }
+                       }
+               }).
+               Do(func(_ context.Context) {
+                       left := <-leftCh
+                       right := <-rightCh
+                       // delete
+                       for rk, rkv := range right {
+                               if _, ok := left[rk]; !ok {
+                                       del = append(del, h.MismatchFunc(rkv))
+                               }
+                       }
+               }).
+               Done()
+
+       if len(add) > 0 {
+               result.Results[greater] = add
+       }
+       if len(update) > 0 {
+               result.Results[mismatch] = update
+       }
+       if len(del) > 0 {
+               result.Results[less] = del
+       }
+       return result
+}
+
+type ServiceCompareHolder struct {
+       *abstractCompareHolder
+       Cache model.MicroserviceSlice
+       Kvs   []*mvccpb.KeyValue
+}
+
+func (h *ServiceCompareHolder) Compare() *CompareResult {
+       h.abstractCompareHolder = &abstractCompareHolder{
+               Cache: &h.Cache, DataStore: &DataStore{Data: h.Kvs, DataParser: 
backend.ServiceParser}, MismatchFunc: h.toName,
+       }
+       r := h.abstractCompareHolder.Compare()
+       r.Name = service
+       return r
+}
+func (h *ServiceCompareHolder) toName(kv *model.KV) string {
+       s, ok := kv.Value.(*pb.MicroService)
+       if !ok {
+               return "unknown"
+       }
+       return fmt.Sprintf("%s/%s/%s(%s)", s.AppId, s.ServiceName, s.Version, 
s.ServiceId)
+}
+
+type InstanceCompareHolder struct {
+       *abstractCompareHolder
+       Cache model.InstanceSlice
+       Kvs   []*mvccpb.KeyValue
+}
+
+func (h *InstanceCompareHolder) Compare() *CompareResult {
+       h.abstractCompareHolder = &abstractCompareHolder{
+               Cache: &h.Cache, DataStore: &DataStore{Data: h.Kvs, DataParser: 
backend.InstanceParser}, MismatchFunc: h.toName,
+       }
+       r := h.abstractCompareHolder.Compare()
+       r.Name = instance
+       return r
+}
+func (h *InstanceCompareHolder) toName(kv *model.KV) string {
+       s, ok := kv.Value.(*pb.MicroServiceInstance)
+       if !ok {
+               return "unknown"
+       }
+       return fmt.Sprintf("%v(%s/%s)", s.Endpoints, s.ServiceId, s.InstanceId)
+}
diff --git a/scctl/pkg/plugin/diagnose/compare_holder_test.go 
b/scctl/pkg/plugin/diagnose/compare_holder_test.go
new file mode 100644
index 0000000..56ee9e8
--- /dev/null
+++ b/scctl/pkg/plugin/diagnose/compare_holder_test.go
@@ -0,0 +1,51 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 diagnose
+
+import (
+       
"github.com/apache/incubator-servicecomb-service-center/server/admin/model"
+       
"github.com/apache/incubator-servicecomb-service-center/server/core/proto"
+       "github.com/coreos/etcd/mvcc/mvccpb"
+       "testing"
+)
+
+func TestAbstractCompareHolder_Compare(t *testing.T) {
+       services := model.MicroserviceSlice{
+               model.NewMicroservice(&model.KV{Key: "1", Rev: 1, Value: 
&proto.MicroService{ServiceId: "1"}}), // greater
+               model.NewMicroservice(&model.KV{Key: "2", Rev: 1, Value: 
&proto.MicroService{ServiceId: "2"}}), // mismatch
+               model.NewMicroservice(&model.KV{Key: "4", Rev: 2, Value: 
&proto.MicroService{ServiceId: "4"}}), // pass
+       }
+       kvs := []*mvccpb.KeyValue{
+               {Key: []byte("2"), ModRevision: 2, Value: 
[]byte(`{"ServiceId":"22"}`)},
+               {Key: []byte("3"), ModRevision: 3, Value: 
[]byte(`{"ServiceId":"3"}`)}, // less
+               {Key: []byte("4"), ModRevision: 2, Value: 
[]byte(`{"ServiceId":"4"}`)},
+               {Key: []byte("5"), ModRevision: 4, Value: []byte(`xxxx`)},
+       }
+       service := ServiceCompareHolder{Cache: services, Kvs: kvs}
+       rs := service.Compare()
+       if rs == nil {
+               t.Fatalf("TestAbstractCompareHolder_Compare failed")
+       }
+       if len(rs.Results) != 3 {
+               t.Fatalf("TestAbstractCompareHolder_Compare failed")
+       }
+       if rs.Name != "service" ||
+               rs.Results[greater][0] != "//(1)" ||
+               rs.Results[mismatch][0] != "//(2)" ||
+               !(rs.Results[less][0] == "unknown" || rs.Results[less][1] == 
"unknown") {
+               t.Fatalf("TestAbstractCompareHolder_Compare failed, %v", rs)
+       }
+}
diff --git a/scctl/pkg/plugin/diagnose/diagnose.go 
b/scctl/pkg/plugin/diagnose/diagnose.go
new file mode 100644
index 0000000..4efdae1
--- /dev/null
+++ b/scctl/pkg/plugin/diagnose/diagnose.go
@@ -0,0 +1,176 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 diagnose
+
+import (
+       "bytes"
+       "fmt"
+       "github.com/apache/incubator-servicecomb-service-center/pkg/client/etcd"
+       "github.com/apache/incubator-servicecomb-service-center/pkg/client/sc"
+       "github.com/apache/incubator-servicecomb-service-center/scctl/pkg/cmd"
+       
"github.com/apache/incubator-servicecomb-service-center/server/admin/model"
+       "github.com/coreos/etcd/clientv3"
+       "github.com/coreos/etcd/mvcc/mvccpb"
+       "github.com/spf13/cobra"
+       "golang.org/x/net/context"
+)
+
+const (
+       service  = "service"
+       instance = "instance"
+)
+
+const (
+       greater = iota
+       mismatch
+       less
+)
+
+var typeMap = map[string]string{
+       service:  "/cse-sr/ms/files/",
+       instance: "/cse-sr/inst/files/",
+}
+
+type etcdResponse map[string][]*mvccpb.KeyValue
+
+func DiagnoseCommandFunc(_ *cobra.Command, args []string) {
+       // initialize sc/etcd clients
+       scClient, err := sc.NewSCClient()
+       if err != nil {
+               cmd.StopAndExit(cmd.ExitError, err)
+       }
+       etcdClient, err := etcd.NewEtcdClient()
+       if err != nil {
+               cmd.StopAndExit(cmd.ExitError, err)
+       }
+       defer etcdClient.Close()
+
+       // query etcd
+       etcdResp, err := getEtcdResponse(context.Background(), etcdClient)
+       if err != nil {
+               cmd.StopAndExit(cmd.ExitError, err)
+       }
+
+       // query sc
+       cache, err := sc.GetScCache(scClient)
+       if err != nil {
+               cmd.StopAndExit(cmd.ExitError, err)
+       }
+
+       // diagnose go...
+       err, details := diagnose(cache, etcdResp)
+       if err != nil {
+               fmt.Println(details)                // stdout
+               cmd.StopAndExit(cmd.ExitError, err) // stderr
+       }
+}
+
+func getEtcdResponse(ctx context.Context, etcdClient *clientv3.Client) 
(etcdResponse, error) {
+       etcdResp := make(etcdResponse)
+       for t, prefix := range typeMap {
+               if err := setResponse(ctx, etcdClient, t, prefix, etcdResp); 
err != nil {
+                       return nil, err
+               }
+       }
+       return etcdResp, nil
+}
+
+func setResponse(ctx context.Context, etcdClient *clientv3.Client, key, prefix 
string, etcdResp etcdResponse) error {
+       resp, err := etcdClient.Get(ctx, prefix, clientv3.WithPrefix())
+       if err != nil {
+               return err
+       }
+       etcdResp[key] = resp.Kvs
+       return nil
+}
+
+func diagnose(cache *model.Cache, etcdResp etcdResponse) (err error, details 
string) {
+       var (
+               service  = ServiceCompareHolder{Cache: cache.Microservices, 
Kvs: etcdResp[service]}
+               instance = InstanceCompareHolder{Cache: cache.Instances, Kvs: 
etcdResp[instance]}
+       )
+
+       sr := service.Compare()
+       ir := instance.Compare()
+
+       var (
+               b    bytes.Buffer
+               full bytes.Buffer
+       )
+       writeResult(&b, &full, sr, ir)
+       if b.Len() > 0 {
+               return fmt.Errorf("error: %s", b.String()), full.String()
+       }
+       return nil, ""
+}
+
+func writeResult(b *bytes.Buffer, full *bytes.Buffer, rss ...*CompareResult) {
+       g, m, l := make(map[string][]string), make(map[string][]string), 
make(map[string][]string)
+       for _, rs := range rss {
+               for t, arr := range rs.Results {
+                       switch t {
+                       case greater:
+                               g[rs.Name] = arr
+                       case mismatch:
+                               m[rs.Name] = arr
+                       case less:
+                               l[rs.Name] = arr
+                       }
+               }
+       }
+
+       i := 0
+       if s := len(g); s > 0 {
+               i++
+               header := fmt.Sprintf("%d. found in cache but not in etcd ", i)
+               b.WriteString(header)
+               full.WriteString(header)
+               writeBody(full, g)
+       }
+       if s := len(m); s > 0 {
+               i++
+               header := fmt.Sprintf("%d. found different between cache and 
etcd ", i)
+               b.WriteString(header)
+               full.WriteString(header)
+               writeBody(full, m)
+       }
+       if s := len(l); s > 0 {
+               i++
+               header := fmt.Sprintf("%d. found in etcd but not in cache ", i)
+               b.WriteString(header)
+               full.WriteString(header)
+               writeBody(full, l)
+       }
+       if l := b.Len(); l > 0 {
+               b.Truncate(l - 1)
+               full.Truncate(full.Len() - 1)
+       }
+}
+
+func writeBody(b *bytes.Buffer, r map[string][]string) {
+       b.WriteString("\b, details:\n")
+       for t, v := range r {
+               writeSection(b, t)
+               b.WriteString(fmt.Sprint(v))
+               b.WriteRune('\n')
+       }
+}
+
+func writeSection(b *bytes.Buffer, t string) {
+       b.WriteString("  ")
+       b.WriteString(t)
+       b.WriteString(": ")
+}
diff --git a/scctl/pkg/plugin/diagnose/diagnose_test.go 
b/scctl/pkg/plugin/diagnose/diagnose_test.go
new file mode 100644
index 0000000..e84a783
--- /dev/null
+++ b/scctl/pkg/plugin/diagnose/diagnose_test.go
@@ -0,0 +1,66 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 diagnose
+
+import (
+       "fmt"
+       
"github.com/apache/incubator-servicecomb-service-center/server/admin/model"
+       
"github.com/apache/incubator-servicecomb-service-center/server/core/proto"
+       "github.com/coreos/etcd/mvcc/mvccpb"
+       "testing"
+)
+
+func TestNewDiagnoseCommand(t *testing.T) {
+       services := model.MicroserviceSlice{
+               model.NewMicroservice(&model.KV{Key: "1", Rev: 1,
+                       Value: &proto.MicroService{
+                               ServiceId: "667570b6842411e89c66286ed488de36", 
AppId: "app", ServiceName: "name1", Version: "0.0.1",
+                       }}), // greater
+               model.NewMicroservice(&model.KV{Key: "6", Rev: 1,
+                       Value: &proto.MicroService{
+                               ServiceId: "667570b6842411e89c66286ed488de36", 
AppId: "app", ServiceName: "name2", Version: "0.0.1",
+                       }}), // greater
+               model.NewMicroservice(&model.KV{Key: "2", Rev: 1, Value: 
&proto.MicroService{ServiceId: "2"}}), // mismatch
+               model.NewMicroservice(&model.KV{Key: "4", Rev: 2, Value: 
&proto.MicroService{ServiceId: "4"}}), // pass
+       }
+       instances := model.InstanceSlice{
+               model.NewInstance(&model.KV{Key: "1", Rev: 1,
+                       Value: &proto.MicroServiceInstance{
+                               ServiceId: "667570b6842411e89c66286ed488de36", 
InstanceId: "667570b6842411e89c66286ed488de36", Version: "0.0.1",
+                               Endpoints: []string{"rest://127.0.0.1:8080"},
+                       }}), // greater
+               model.NewInstance(&model.KV{Key: "2", Rev: 1,
+                       Value: &proto.MicroServiceInstance{
+                               ServiceId: "667570b6842411e89c66286ed488de36", 
InstanceId: "667570b6842411e89c66286ed488de36", Version: "0.0.1",
+                               Endpoints: []string{"rest://127.0.0.2:8080"},
+                       }}), // greater
+       }
+       kvs := []*mvccpb.KeyValue{
+               {Key: []byte("2"), ModRevision: 2, Value: 
[]byte(`{"ServiceId":"22"}`)},
+               {Key: []byte("3"), ModRevision: 3, Value: 
[]byte(`{"ServiceId":"3"}`)}, // less
+               {Key: []byte("4"), ModRevision: 2, Value: 
[]byte(`{"ServiceId":"4"}`)},
+               {Key: []byte("5"), ModRevision: 4, Value: []byte(`xxxx`)},
+       }
+
+       //for {
+       err, details := diagnose(&model.Cache{Microservices: services, 
Instances: instances}, etcdResponse{service: kvs})
+       if err == nil || len(details) == 0 {
+               t.Fatalf("TestNewDiagnoseCommand failed")
+       }
+       fmt.Println(err)
+       fmt.Println(details)
+       //}
+}
diff --git a/scctl/pkg/plugin/get/cmd.go b/scctl/pkg/plugin/get/cmd.go
new file mode 100644
index 0000000..05fa3ad
--- /dev/null
+++ b/scctl/pkg/plugin/get/cmd.go
@@ -0,0 +1,45 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 get
+
+import (
+       "github.com/apache/incubator-servicecomb-service-center/scctl/pkg/cmd"
+       "github.com/spf13/cobra"
+)
+
+var (
+       Domain     string
+       Output     string
+       AllDomains bool
+       RootCmd    *cobra.Command
+)
+
+func init() {
+       RootCmd = NewGetCommand(cmd.RootCmd())
+}
+
+func NewGetCommand(parent *cobra.Command) *cobra.Command {
+       cmd := &cobra.Command{
+               Use:   "get <command> [options]",
+               Short: "Output the resources information of service center",
+       }
+       parent.AddCommand(cmd)
+       cmd.PersistentFlags().StringVarP(&Domain, "domain", "d", "default", 
"the info of the service center")
+       cmd.PersistentFlags().StringVarP(&Output, "output", "o", "", "print the 
info of the service center")
+       cmd.PersistentFlags().BoolVar(&AllDomains, "all-domains", false, "print 
the info of the service center")
+
+       return cmd
+}
diff --git a/scctl/pkg/plugin/get/instance/instance_cmd.go 
b/scctl/pkg/plugin/get/instance/instance_cmd.go
new file mode 100644
index 0000000..19db336
--- /dev/null
+++ b/scctl/pkg/plugin/get/instance/instance_cmd.go
@@ -0,0 +1,95 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 instance
+
+import (
+       "github.com/apache/incubator-servicecomb-service-center/pkg/client/sc"
+       "github.com/apache/incubator-servicecomb-service-center/scctl/pkg/cmd"
+       "github.com/apache/incubator-servicecomb-service-center/scctl/pkg/model"
+       
"github.com/apache/incubator-servicecomb-service-center/scctl/pkg/plugin/get"
+       
"github.com/apache/incubator-servicecomb-service-center/scctl/pkg/writer"
+       admin 
"github.com/apache/incubator-servicecomb-service-center/server/admin/model"
+       "github.com/apache/incubator-servicecomb-service-center/server/core"
+       "github.com/spf13/cobra"
+       "strings"
+)
+
+func init() {
+       NewInstanceCommand(get.RootCmd)
+}
+
+func NewInstanceCommand(parent *cobra.Command) *cobra.Command {
+       cmd := &cobra.Command{
+               Use:     "instance [options]",
+               Aliases: []string{"inst"},
+               Short:   "Output the instance information of the service center 
",
+               Run:     InstanceCommandFunc,
+       }
+       parent.AddCommand(cmd)
+       return cmd
+}
+
+func InstanceCommandFunc(_ *cobra.Command, args []string) {
+       scClient, err := sc.NewSCClient()
+       if err != nil {
+               cmd.StopAndExit(cmd.ExitError, err)
+       }
+       cache, err := sc.GetScCache(scClient)
+       if err != nil {
+               cmd.StopAndExit(cmd.ExitError, err)
+       }
+
+       svcMap := make(map[string]*admin.Microservice)
+       for _, ms := range cache.Microservices {
+               svcMap[ms.Value.ServiceId] = ms
+       }
+
+       records := make(map[string]*InstanceRecord)
+       for _, inst := range cache.Instances {
+               domainProject := model.GetDomainProject(inst)
+               if !get.AllDomains && strings.Index(domainProject+core.SPLIT, 
get.Domain+core.SPLIT) != 0 {
+                       continue
+               }
+
+               svc, ok := svcMap[inst.Value.ServiceId]
+               if !ok {
+                       continue
+               }
+
+               instance, ok := records[inst.Value.InstanceId]
+               if !ok {
+                       instance = &InstanceRecord{
+                               Instance: model.Instance{
+                                       DomainProject: domainProject,
+                                       Host:          inst.Value.HostName,
+                                       Endpoints:     inst.Value.Endpoints,
+                                       Environment:   svc.Value.Environment,
+                                       AppId:         svc.Value.AppId,
+                                       ServiceName:   svc.Value.ServiceName,
+                                       Version:       svc.Value.Version,
+                                       Framework:     svc.Value.Framework,
+                               },
+                       }
+                       records[inst.Value.InstanceId] = instance
+               }
+               instance.SetLease(inst.Value.HealthCheck)
+               instance.UpdateTimestamp(inst.Value.Timestamp)
+       }
+
+       sp := &InstancePrinter{Records: records}
+       sp.SetOutputFormat(get.Output, get.AllDomains)
+       writer.PrintTable(sp)
+}
diff --git a/scctl/pkg/plugin/get/instance/types.go 
b/scctl/pkg/plugin/get/instance/types.go
new file mode 100644
index 0000000..a55a5be
--- /dev/null
+++ b/scctl/pkg/plugin/get/instance/types.go
@@ -0,0 +1,118 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 instance
+
+import (
+       "fmt"
+       "github.com/apache/incubator-servicecomb-service-center/pkg/util"
+       "github.com/apache/incubator-servicecomb-service-center/scctl/pkg/model"
+       
"github.com/apache/incubator-servicecomb-service-center/scctl/pkg/writer"
+       "github.com/apache/incubator-servicecomb-service-center/server/core"
+       "strings"
+       "time"
+)
+
+const neverExpire = "Never"
+
+var (
+       longInstanceTableHeader   = []string{"DOMAIN", "HOST", "ENDPOINTS", 
"VERSION", "SERVICE", "APPID", "ENV", "FRAMEWORK", "LEASE", "AGE"}
+       domainInstanceTableHeader = []string{"DOMAIN", "HOST", "ENDPOINTS", 
"VERSION", "SERVICE", "APPID", "LEASE", "AGE"}
+       shortInstanceTableHeader  = []string{"HOST", "ENDPOINTS", "VERSION", 
"SERVICE", "APPID", "LEASE", "AGE"}
+)
+
+type InstanceRecord struct {
+       model.Instance
+}
+
+func (s *InstanceRecord) FrameworksString() string {
+       if s.Framework == nil || len(s.Framework.Name) == 0 {
+               return ""
+       }
+       return fmt.Sprintf("%s-%s", s.Framework.Name, s.Framework.Version)
+}
+
+func (s *InstanceRecord) EndpointsString() string {
+       return util.StringJoin(s.Endpoints, ",")
+}
+
+func (s *InstanceRecord) LeaseString() string {
+       if s.Lease < 0 {
+               return ""
+       }
+       if s.Lease == 0 {
+               return neverExpire
+       }
+       return writer.TimeFormat(time.Duration(s.Lease) * time.Second)
+}
+
+func (s *InstanceRecord) AgeString() string {
+       return writer.TimeFormat(s.Age())
+}
+
+func (s *InstanceRecord) Domain() string {
+       if i := strings.Index(s.DomainProject, core.SPLIT); i >= 0 {
+               return s.DomainProject[:i]
+       }
+       return s.DomainProject
+}
+
+func (s *InstanceRecord) PrintBody(fmt string, all bool) []string {
+       switch {
+       case fmt == "wide":
+               return []string{s.Domain(), s.Host, s.EndpointsString(), 
s.Version, s.ServiceName, s.AppId, s.Environment,
+                       s.FrameworksString(), s.LeaseString(), s.AgeString()}
+       case all:
+               return []string{s.Domain(), s.Host, s.EndpointsString(), 
s.Version, s.ServiceName,
+                       s.AppId, s.LeaseString(), s.AgeString()}
+       default:
+               return []string{s.Host, s.EndpointsString(), s.Version, 
s.ServiceName,
+                       s.AppId, s.LeaseString(), s.AgeString()}
+       }
+}
+
+type InstancePrinter struct {
+       Records map[string]*InstanceRecord
+       flags   []interface{}
+}
+
+func (sp *InstancePrinter) SetOutputFormat(f string, all bool) {
+       sp.Flags(f, all)
+}
+
+func (sp *InstancePrinter) Flags(flags ...interface{}) []interface{} {
+       if len(flags) > 0 {
+               sp.flags = flags
+       }
+       return sp.flags
+}
+
+func (sp *InstancePrinter) PrintBody() (slice [][]string) {
+       for _, s := range sp.Records {
+               slice = append(slice, s.PrintBody(sp.flags[0].(string), 
sp.flags[1].(bool)))
+       }
+       return
+}
+
+func (sp *InstancePrinter) PrintTitle() []string {
+       switch {
+       case sp.flags[0] == "wide":
+               return longInstanceTableHeader
+       case sp.flags[1].(bool):
+               return domainInstanceTableHeader
+       default:
+               return shortInstanceTableHeader
+       }
+}
diff --git a/scctl/pkg/plugin/get/service/service_cmd.go 
b/scctl/pkg/plugin/get/service/service_cmd.go
new file mode 100644
index 0000000..280968b
--- /dev/null
+++ b/scctl/pkg/plugin/get/service/service_cmd.go
@@ -0,0 +1,95 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 service
+
+import (
+       "github.com/apache/incubator-servicecomb-service-center/pkg/client/sc"
+       "github.com/apache/incubator-servicecomb-service-center/pkg/util"
+       "github.com/apache/incubator-servicecomb-service-center/scctl/pkg/cmd"
+       "github.com/apache/incubator-servicecomb-service-center/scctl/pkg/model"
+       
"github.com/apache/incubator-servicecomb-service-center/scctl/pkg/plugin/get"
+       
"github.com/apache/incubator-servicecomb-service-center/scctl/pkg/writer"
+       "github.com/apache/incubator-servicecomb-service-center/server/core"
+       "github.com/spf13/cobra"
+       "strings"
+)
+
+func init() {
+       NewServiceCommand(get.RootCmd)
+}
+
+func NewServiceCommand(parent *cobra.Command) *cobra.Command {
+       cmd := &cobra.Command{
+               Use:     "service [options]",
+               Aliases: []string{"svc"},
+               Short:   "Output the service information of the service center 
",
+               Run:     ServiceCommandFunc,
+       }
+
+       parent.AddCommand(cmd)
+       return cmd
+}
+
+func ServiceCommandFunc(_ *cobra.Command, args []string) {
+       scClient, err := sc.NewSCClient()
+       if err != nil {
+               cmd.StopAndExit(cmd.ExitError, err)
+       }
+       cache, err := sc.GetScCache(scClient)
+       if err != nil {
+               cmd.StopAndExit(cmd.ExitError, err)
+       }
+
+       endpointMap := make(map[string][]string)
+       for i := 0; i < len(cache.Instances); i++ {
+               serviceId := cache.Instances[i].Value.ServiceId
+               endpointMap[serviceId] = append(endpointMap[serviceId], 
cache.Instances[i].Value.Endpoints...)
+       }
+
+       records := make(map[string]*ServiceRecord)
+       for _, ms := range cache.Microservices {
+               domainProject := model.GetDomainProject(ms)
+               if !get.AllDomains && strings.Index(domainProject+core.SPLIT, 
get.Domain+core.SPLIT) != 0 {
+                       continue
+               }
+
+               appID := ms.Value.AppId
+               env := ms.Value.Environment
+               name := ms.Value.ServiceName
+
+               key := util.StringJoin([]string{domainProject, env, appID, 
name}, "/")
+               svc, ok := records[key]
+               if !ok {
+                       svc = &ServiceRecord{
+                               Service: model.Service{
+                                       DomainProject: domainProject,
+                                       Environment:   env,
+                                       AppId:         appID,
+                                       ServiceName:   name,
+                               },
+                       }
+                       records[key] = svc
+               }
+               svc.AppendVersion(ms.Value.Version)
+               svc.AppendFramework(ms.Value.Framework)
+               svc.AppendEndpoints(endpointMap[ms.Value.ServiceId])
+               svc.UpdateTimestamp(ms.Value.Timestamp)
+       }
+
+       sp := &ServicePrinter{Records: records}
+       sp.SetOutputFormat(get.Output, get.AllDomains)
+       writer.PrintTable(sp)
+}
diff --git a/scctl/pkg/plugin/get/service/types.go 
b/scctl/pkg/plugin/get/service/types.go
new file mode 100644
index 0000000..844a4e7
--- /dev/null
+++ b/scctl/pkg/plugin/get/service/types.go
@@ -0,0 +1,112 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 service
+
+import (
+       "fmt"
+       "github.com/apache/incubator-servicecomb-service-center/pkg/util"
+       "github.com/apache/incubator-servicecomb-service-center/scctl/pkg/model"
+       
"github.com/apache/incubator-servicecomb-service-center/scctl/pkg/writer"
+       "github.com/apache/incubator-servicecomb-service-center/server/core"
+       "strings"
+)
+
+const maxWidth = 35
+
+var (
+       longServiceTableHeader   = []string{"DOMAIN", "NAME", "APPID", 
"VERSIONS", "ENV", "FRAMEWORK", "ENDPOINTS", "AGE"}
+       domainServiceTableHeader = []string{"DOMAIN", "NAME", "APPID", 
"VERSIONS", "ENV", "FRAMEWORK", "AGE"}
+       shortServiceTableHeader  = []string{"NAME", "APPID", "VERSIONS", "ENV", 
"FRAMEWORK", "AGE"}
+)
+
+type ServiceRecord struct {
+       model.Service
+}
+
+func (s *ServiceRecord) VersionsString() string {
+       return util.StringJoin(s.Versions, ",")
+}
+
+func (s *ServiceRecord) FrameworksString() string {
+       var arr []string
+       for _, fw := range s.Frameworks {
+               arr = append(arr, fmt.Sprintf("%s-%s", fw.Name, fw.Version))
+       }
+       return util.StringJoin(arr, ",")
+}
+
+func (s *ServiceRecord) EndpointsString() string {
+       return util.StringJoin(s.Endpoints, ",")
+}
+
+func (s *ServiceRecord) AgeString() string {
+       return writer.TimeFormat(s.Age())
+}
+
+func (s *ServiceRecord) Domain() string {
+       if i := strings.Index(s.DomainProject, core.SPLIT); i >= 0 {
+               return s.DomainProject[:i]
+       }
+       return s.DomainProject
+}
+
+func (s *ServiceRecord) PrintBody(fmt string, all bool) []string {
+       switch {
+       case fmt == "wide":
+               return []string{s.Domain(), s.ServiceName, s.AppId, 
s.VersionsString(), s.Environment,
+                       s.FrameworksString(), s.EndpointsString(), 
s.AgeString()}
+       case all:
+               return writer.Reshape(maxWidth, []string{s.Domain(), 
s.ServiceName, s.AppId,
+                       s.VersionsString(), s.Environment, 
s.FrameworksString(), s.AgeString()})
+       default:
+               return writer.Reshape(maxWidth, []string{s.ServiceName, s.AppId,
+                       s.VersionsString(), s.Environment, 
s.FrameworksString(), s.AgeString()})
+       }
+}
+
+type ServicePrinter struct {
+       Records map[string]*ServiceRecord
+       flags   []interface{}
+}
+
+func (sp *ServicePrinter) SetOutputFormat(f string, all bool) {
+       sp.Flags(f, all)
+}
+
+func (sp *ServicePrinter) Flags(flags ...interface{}) []interface{} {
+       if len(flags) > 0 {
+               sp.flags = flags
+       }
+       return sp.flags
+}
+
+func (sp *ServicePrinter) PrintBody() (slice [][]string) {
+       for _, s := range sp.Records {
+               slice = append(slice, s.PrintBody(sp.flags[0].(string), 
sp.flags[1].(bool)))
+       }
+       return
+}
+
+func (sp *ServicePrinter) PrintTitle() []string {
+       switch {
+       case sp.flags[0] == "wide":
+               return longServiceTableHeader
+       case sp.flags[1].(bool):
+               return domainServiceTableHeader
+       default:
+               return shortServiceTableHeader
+       }
+}
diff --git a/scctl/pkg/plugin/version/cmd.go b/scctl/pkg/plugin/version/cmd.go
new file mode 100644
index 0000000..27eab80
--- /dev/null
+++ b/scctl/pkg/plugin/version/cmd.go
@@ -0,0 +1,62 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 version
+
+import (
+       "fmt"
+       "github.com/apache/incubator-servicecomb-service-center/pkg/client/sc"
+       "github.com/apache/incubator-servicecomb-service-center/scctl/pkg/cmd"
+       
"github.com/apache/incubator-servicecomb-service-center/scctl/pkg/version"
+       "github.com/spf13/cobra"
+)
+
+var (
+       RootCmd *cobra.Command
+)
+
+func init() {
+       RootCmd = NewGetCommand(cmd.RootCmd())
+}
+
+func NewGetCommand(parent *cobra.Command) *cobra.Command {
+       cmd := &cobra.Command{
+               Use:     "version",
+               Aliases: []string{"ver"},
+               Short:   "Output the version of tool and service center",
+               Run:     VersionCommandFunc,
+       }
+       parent.AddCommand(cmd)
+       return cmd
+}
+
+func VersionCommandFunc(_ *cobra.Command, _ []string) {
+       defer cmd.StopAndExit(cmd.ExitSuccess)
+       fmt.Print(version.TOOL_NAME, " ")
+       version.Ver().Print()
+
+       scClient, err := sc.NewSCClient()
+       if err != nil {
+               return
+       }
+       v, err := sc.GetScVersion(scClient)
+       if err != nil {
+               return
+       }
+
+       fmt.Println()
+       fmt.Print("service center ")
+       v.Print()
+}
diff --git a/pkg/util/sys.go b/scctl/pkg/version/version.go
similarity index 54%
copy from pkg/util/sys.go
copy to scctl/pkg/version/version.go
index 9bd81ef..e98e585 100644
--- a/pkg/util/sys.go
+++ b/scctl/pkg/version/version.go
@@ -14,53 +14,30 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package util
+package version
 
 import (
-       "os"
-       "strconv"
-       "unsafe"
+       "github.com/apache/incubator-servicecomb-service-center/version"
 )
 
-const intSize = int(unsafe.Sizeof(0))
+var (
+       // no need to modify
+       // please use:
+       //      go build -ldflags "-X 
github.com/apache/incubator-servicecomb-service-center/scctl/pkg/version.VERSION=x.x.x"
+       // to set these values.
+       VERSION   = "0.0.1"
+       BUILD_TAG = "Not provided"
+       TOOL_NAME = "scctl"
+)
 
-var bs *[intSize]byte
+var versionSet version.VersionSet
 
 func init() {
-       i := 0x1
-       bs = (*[intSize]byte)(unsafe.Pointer(&i))
-}
-
-func IsBigEndian() bool {
-       return !IsLittleEndian()
-}
-
-func IsLittleEndian() bool {
-       return bs[0] == 0
-}
-
-func PathExist(path string) bool {
-       _, err := os.Stat(path)
-       return err == nil || os.IsExist(err)
-}
-
-func HostName() (hostname string) {
-       var err error
-       hostname, err = os.Hostname()
-       if err != nil {
-               hostname = "UNKNOWN"
-       }
-       return
+       versionSet.Version = VERSION
+       versionSet.BuildTag = BUILD_TAG
+       versionSet.LoadRuntimeInfo()
 }
 
-func GetEnvInt(name string, def int) int {
-       env, ok := os.LookupEnv(name)
-       if ok {
-               i64, err := strconv.ParseInt(env, 10, 0)
-               if err != nil {
-                       return def
-               }
-               return int(i64)
-       }
-       return def
+func Ver() *version.VersionSet {
+       return &versionSet
 }
diff --git a/scctl/pkg/writer/writer.go b/scctl/pkg/writer/writer.go
new file mode 100644
index 0000000..744637e
--- /dev/null
+++ b/scctl/pkg/writer/writer.go
@@ -0,0 +1,67 @@
+// Licensed to the Apache Software Foundation (ASF) under one or more
+// contributor license agreements.  See the NOTICE file distributed with
+// this work for additional information regarding copyright ownership.
+// The ASF licenses this file to You 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 writer
+
+import (
+       "github.com/olekukonko/tablewriter"
+       "os"
+       "strconv"
+       "time"
+)
+
+const Day = time.Hour * 24
+
+type Printer interface {
+       Flags(flags ...interface{}) []interface{}
+       PrintBody() [][]string
+       PrintTitle() []string
+}
+
+func TimeFormat(delta time.Duration) string {
+       switch {
+       case delta < time.Minute:
+               return strconv.FormatFloat(delta.Seconds(), 'f', 0, 64) + "s"
+       case delta < time.Hour:
+               return strconv.FormatFloat(delta.Minutes(), 'f', 0, 64) + "m"
+       case delta < Day:
+               return strconv.FormatFloat(delta.Hours(), 'f', 0, 64) + "h"
+       default:
+               return strconv.FormatFloat(float64(delta/Day), 'f', 0, 64) + "d"
+       }
+}
+
+func Reshape(maxWidth int, line []string) []string {
+       for i, col := range line {
+               if len(col)-maxWidth > 3 {
+                       line[i] = col[:maxWidth] + "..."
+               }
+       }
+       return line
+}
+
+func MakeTable(tableName []string, tableContent [][]string) {
+       table := tablewriter.NewWriter(os.Stdout)
+       table.SetHeader(tableName)
+       table.SetBorder(false)
+       for _, v := range tableContent {
+               table.Append(v)
+       }
+       table.Render()
+}
+
+func PrintTable(p Printer) {
+       MakeTable(p.PrintTitle(), p.PrintBody())
+}
diff --git a/scripts/build/tools.sh b/scripts/build/tools.sh
index 325b35d..f80a616 100644
--- a/scripts/build/tools.sh
+++ b/scripts/build/tools.sh
@@ -61,14 +61,14 @@ build_service_center() {
     ## Build the Service-Center releases
     export GIT_COMMIT=$(git log  --pretty=format:'%h' -n 1)
     export BUILD_NUMBER=$RELEASE
-    GO_LDFLAGS="${GO_LDFLAGS} -X 
'github.com/apache/incubator-servicecomb-service-center/version.BUILD_TAG=$(date
 +%Y%m%d%H%M%S).$BUILD_NUMBER.$GIT_COMMIT'"
-    GO_LDFLAGS="${GO_LDFLAGS} -X 
'github.com/apache/incubator-servicecomb-service-center/version.VERSION=$BUILD_NUMBER'"
+    local ldflags="${GO_LDFLAGS} -s -w -X 
'github.com/apache/incubator-servicecomb-service-center/version.BUILD_TAG=$(date
 +%Y%m%d%H%M%S).$BUILD_NUMBER.$GIT_COMMIT'"
+    ldflags="${ldflags} -X 
'github.com/apache/incubator-servicecomb-service-center/version.VERSION=$BUILD_NUMBER'"
 
     local BINARY_NAME=$app/service-center
     if [ "$GOOS" == "windows" ]; then
         BINARY_NAME=${BINARY_NAME}.exe
     fi
-    go build --ldflags "${GO_LDFLAGS}" -o $BINARY_NAME
+    go build --ldflags "${ldflags}" -o $BINARY_NAME
 }
 
 build_frontend() {
@@ -78,8 +78,25 @@ build_frontend() {
     if [ "$GOOS" == "windows" ]; then
         BINARY_NAME=${BINARY_NAME}.exe
     fi
-    go build -o ../$BINARY_NAME
-    cd ..
+    go build --ldflags "-s -w" -o ../$BINARY_NAME
+    cd -
+}
+
+build_scctl() {
+    local app=../$PACKAGE_PREFIX-$PACKAGE-$GOOS-$GOARCH
+    ## Build the scctl releases
+    cd scctl
+    export GIT_COMMIT=$(git log  --pretty=format:'%h' -n 1)
+    export BUILD_NUMBER=$RELEASE
+    local ldflags="${GO_LDFLAGS} -s -w -X 
'github.com/apache/incubator-servicecomb-service-center/scctl/pkg/version.BUILD_TAG=$(date
 +%Y%m%d%H%M%S).$BUILD_NUMBER.$GIT_COMMIT'"
+    ldflags="${ldflags} -X 
'github.com/apache/incubator-servicecomb-service-center/scctl/pkg/version.VERSION=$BUILD_NUMBER'"
+
+    local BINARY_NAME=$app/scctl
+    if [ "$GOOS" == "windows" ]; then
+        BINARY_NAME=${BINARY_NAME}.exe
+    fi
+    go build --ldflags "${ldflags}" -o $BINARY_NAME
+    cd -
 }
 
 sc_deps() {
diff --git a/scripts/release/LICENSE b/scripts/release/LICENSE
index a2fa902..ee0da30 100644
--- a/scripts/release/LICENSE
+++ b/scripts/release/LICENSE
@@ -880,3 +880,10 @@ For lumberjack (a96e63847dc3c67d17befa69c303767e2f84e54f)
 This product bundles the lumberjack library which is licensed under the MIT 
License.
 For details, see https://github.com/natefinch/lumberjack
 You can find a copy of the License at licenses/LICENSE-natefinch-lumberjack
+
+================================================================
+For tablewriter (d4647c9c7a84d847478d890b816b7d8b62b0b279)
+================================================================
+This product bundles the tablewriter library which is licensed under the MIT 
License.
+For details, see https://github.com/olekukonko/tablewriter
+You can find a copy of the License at licenses/LICENSE-olekukonko-tablewriter
diff --git a/scripts/release/licenses/LICENSE-olekukonko-tablewriter 
b/scripts/release/licenses/LICENSE-olekukonko-tablewriter
new file mode 100644
index 0000000..1fd8484
--- /dev/null
+++ b/scripts/release/licenses/LICENSE-olekukonko-tablewriter
@@ -0,0 +1,19 @@
+Copyright (C) 2014 by Oleku Konko
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
\ No newline at end of file
diff --git a/scripts/release/make_release.sh b/scripts/release/make_release.sh
index 1c14b2f..8767846 100755
--- a/scripts/release/make_release.sh
+++ b/scripts/release/make_release.sh
@@ -69,6 +69,8 @@ build() {
 
     build_frontend
 
+    build_scctl
+
     package
 }
 
diff --git a/version/version.go b/version/version.go
index 3afc8a3..ccad51b 100644
--- a/version/version.go
+++ b/version/version.go
@@ -40,17 +40,23 @@ type VersionSet struct {
 }
 
 func (vs *VersionSet) Print() {
-       fmt.Printf("ServiceCenter version: %s\n", versionSet.Version)
-       fmt.Printf("Build tag: %s\n", versionSet.BuildTag)
-       fmt.Printf("Go version: %s\n", versionSet.GoVersion)
-       fmt.Printf("OS/Arch: %s/%s\n", versionSet.OS, versionSet.Arch)
+       fmt.Printf("Version: %s\n", vs.Version)
+       fmt.Printf("Build tag: %s\n", vs.BuildTag)
+       fmt.Printf("Go version: %s\n", vs.GoVersion)
+       fmt.Printf("OS/Arch: %s/%s\n", vs.OS, vs.Arch)
 }
 
 func (vs *VersionSet) Log() {
-       log.Infof("service center version: %s", versionSet.Version)
-       log.Infof("Build tag: %s", versionSet.BuildTag)
-       log.Infof("Go version: %s", versionSet.GoVersion)
-       log.Infof("OS/Arch: %s/%s", versionSet.OS, versionSet.Arch)
+       log.Infof("Version: %s", vs.Version)
+       log.Infof("Build tag: %s", vs.BuildTag)
+       log.Infof("Go version: %s", vs.GoVersion)
+       log.Infof("OS/Arch: %s/%s", vs.OS, vs.Arch)
+}
+
+func (vs *VersionSet) LoadRuntimeInfo() {
+       vs.GoVersion = runtime.Version()
+       vs.OS = runtime.GOOS
+       vs.Arch = runtime.GOARCH
 }
 
 var versionSet VersionSet
@@ -58,9 +64,7 @@ var versionSet VersionSet
 func init() {
        versionSet.Version = VERSION
        versionSet.BuildTag = BUILD_TAG
-       versionSet.GoVersion = runtime.Version()
-       versionSet.OS = runtime.GOOS
-       versionSet.Arch = runtime.GOARCH
+       versionSet.LoadRuntimeInfo()
 }
 
 func Ver() *VersionSet {

Reply via email to