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 {