[
https://issues.apache.org/jira/browse/SCB-869?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16602667#comment-16602667
]
ASF GitHub Bot commented on SCB-869:
------------------------------------
asifdxtreme closed pull request #432: SCB-869 SC cli tool
URL: https://github.com/apache/incubator-servicecomb-service-center/pull/432
This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:
As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):
diff --git a/README.md b/README.md
index b20813e3..8bd99ca6 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 ba6aef1a..9f113cf8 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 8051f717..55bbdb13 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 00000000..d7a5f0b9
--- /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 00000000..f6dd2fec
--- /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 00000000..b035b0ce
--- /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 e443a890..987e3c07 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 182f4137..71858388 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 9bd81ef7..2d4ffe60 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 6bee8fc2..a168ddf1 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 00000000..7d3c8c6f
--- /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 00000000..5671663d
--- /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 00000000..fae8bbea
--- /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 00000000..e015d3df
--- /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 00000000..d78f84cf
--- /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 00000000..202545aa
--- /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 00000000..464283b8
--- /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 00000000..81ae1d46
--- /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 00000000..7481fa8b
--- /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 00000000..56ee9e88
--- /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 00000000..4efdae18
--- /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 00000000..e84a7835
--- /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 00000000..05fa3ad7
--- /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 00000000..19db3366
--- /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 00000000..a55a5bee
--- /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 00000000..280968be
--- /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 00000000..844a4e74
--- /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 00000000..27eab80d
--- /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/scctl/pkg/version/version.go b/scctl/pkg/version/version.go
new file mode 100644
index 00000000..e98e5852
--- /dev/null
+++ b/scctl/pkg/version/version.go
@@ -0,0 +1,43 @@
+/*
+ * 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 (
+ "github.com/apache/incubator-servicecomb-service-center/version"
+)
+
+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 versionSet version.VersionSet
+
+func init() {
+ versionSet.Version = VERSION
+ versionSet.BuildTag = BUILD_TAG
+ versionSet.LoadRuntimeInfo()
+}
+
+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 00000000..744637e9
--- /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 325b35de..f80a616f 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 a2fa902f..ee0da30b 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 00000000..1fd84842
--- /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 1c14b2f1..87678463 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 3afc8a32..ccad51bb 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 {
----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
For queries about this service, please contact Infrastructure at:
[email protected]
> SC cli tool
> -----------
>
> Key: SCB-869
> URL: https://issues.apache.org/jira/browse/SCB-869
> Project: Apache ServiceComb
> Issue Type: New Feature
> Components: Service-Center
> Reporter: little-cui
> Assignee: little-cui
> Priority: Major
> Fix For: service-center-1.1.0
>
>
--
This message was sent by Atlassian JIRA
(v7.6.3#76005)