diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f0aa51..062ff03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,13 @@ # vNext +# 2.1.0 +- 新增应用平台user权限的接口 +- 修改日志搜索返回结果字段及其Tag + # Release 2.0.0 - 新增app授权和撤销授权功能 - Service SCALING 状态拆分为 SCALING-UP SCALING-DOWN -- 日志搜索结果添加CollectedAtNano字段 +- 日志搜索结果添加CollectedAtNano字段 - 添加 GetWebProxy 方法 # Release 1.2.0 diff --git a/kirksdk/account_api.go b/kirksdk/account_api.go index 953a9fa..9a53493 100644 --- a/kirksdk/account_api.go +++ b/kirksdk/account_api.go @@ -95,6 +95,24 @@ type AccountClient interface { // GetGrantedAppKey 获取被授权应用的key GetGrantedAppKey(ctx context.Context, appURI string) (ret GrantedAppKey, err error) + + // GetAppspecs 获得应用模板信息 + GetAppspecs(ctx context.Context, specURI string) (ret SpecInfo, err error) + + // ListPublicspecs 列出公开应用的模板 + ListPublicspecs(ctx context.Context) (ret []SpecInfo, err error) + + // ListGrantedspecs 列出被授权应用的模板 + ListGrantedspecs(ctx context.Context) (ret []SpecInfo, err error) + + // GetVendorManagedAppStatus 获得VendorManaged应用运行状态 + GetVendorManagedAppStatus(ctx context.Context, appURI string) (ret VendorManagedAppStatus, err error) + + // GetVendorManagedAppEntry 获得VendorManaged应用入口地址 + GetVendorManagedAppEntry(ctx context.Context, appURI string) (ret VendorManagedAppEntry, err error) + + // VendorManagedAppRepair 尝试修复VendorManaged应用 + VendorManagedAppRepair(ctx context.Context, appURI string) (err error) } // AccountConfig 包含创建 AccountClient 所需的信息 @@ -205,3 +223,30 @@ type GrantedAppKey struct { Ak string `json:"ak"` Sk string `json:"sk"` } + +// SpecInfo 包含 Spec 的相关信息 +type SpecInfo struct { + URI string `json:"uri"` + Owner string `json:"owner"` + Title string `json:"title"` + Ver uint32 `json:"ver"` + Verstr string `json:"verstr"` + Desc string `json:"desc,omitempty"` + Brief string `json:"brief"` + Icon string `json:"icon"` + Seedimg string `json:"seedimg"` + Entryport uint16 `json:"entryport"` + Ctime time.Time `json:"ctime"` + Mtime time.Time `json:"mtime"` +} + +// VendorManagedAppStatus 包含应用运行状态信息 +type VendorManagedAppStatus struct { + Status string `json:"status"` + Details string `json:"details"` +} + +// VendorManagedAppEntry 包含应用入口地址 +type VendorManagedAppEntry struct { + Entry string `json:"entry"` +} diff --git a/kirksdk/account_client.go b/kirksdk/account_client.go index 02d6d72..ccc77fe 100644 --- a/kirksdk/account_client.go +++ b/kirksdk/account_client.go @@ -167,6 +167,42 @@ func (p *accountClientImp) GetGrantedAppKey(ctx context.Context, appURI string) return } +func (p *accountClientImp) GetAppspecs(ctx context.Context, specURI string) (ret SpecInfo, err error) { + url := fmt.Sprintf("%s%s/appspecs/%s", p.host, appVersionPrefix, specURI) + err = p.client.Call(ctx, &ret, "GET", url) + return +} + +func (p *accountClientImp) ListPublicspecs(ctx context.Context) (ret []SpecInfo, err error) { + url := fmt.Sprintf("%s%s/publicspecs", p.host, appVersionPrefix) + err = p.client.Call(ctx, &ret, "GET", url) + return +} + +func (p *accountClientImp) ListGrantedspecs(ctx context.Context) (ret []SpecInfo, err error) { + url := fmt.Sprintf("%s%s/grantedspecs", p.host, appVersionPrefix) + err = p.client.Call(ctx, &ret, "GET", url) + return +} + +func (p *accountClientImp) GetVendorManagedAppStatus(ctx context.Context, appURI string) (ret VendorManagedAppStatus, err error) { + url := fmt.Sprintf("%s%s/apps/%s/status", p.host, appVersionPrefix, appURI) + err = p.client.Call(ctx, &ret, "PUT", url) + return +} + +func (p *accountClientImp) GetVendorManagedAppEntry(ctx context.Context, appURI string) (ret VendorManagedAppEntry, err error) { + url := fmt.Sprintf("%s%s/apps/%s/entry", p.host, appVersionPrefix, appURI) + err = p.client.Call(ctx, &ret, "PUT", url) + return +} + +func (p *accountClientImp) VendorManagedAppRepair(ctx context.Context, appURI string) (err error) { + url := fmt.Sprintf("%s%s/apps/%s/repair", p.host, appVersionPrefix, appURI) + err = p.client.Call(ctx, nil, "PUT", url) + return +} + func (p *accountClientImp) GetIndexClient(ctx context.Context) (client IndexClient, err error) { accountInfo, err := p.GetAccountInfo(ctx) if err != nil { diff --git a/kirksdk/example/search_container_logs.go b/kirksdk/example/search_container_logs.go new file mode 100644 index 0000000..e684740 --- /dev/null +++ b/kirksdk/example/search_container_logs.go @@ -0,0 +1,176 @@ +package example + +import ( + "fmt" + "io" + "os" + "strings" + "time" + + "golang.org/x/net/context" + "qiniupkg.com/kirk/kirksdk" +) + +// A simple example of how to use the sdk to search logs, including container logs and access logs. +// In addition, this example shows how to search more than 10,000 logs in a single search. +// Because the logs search api limits the maximum search size as 10,000, so we will use the field +// collectedAtNano to achieve this goal. + +func SearchContainerLogsExample() { + client := kirksdk.NewQcosClient(kirksdk.QcosConfig{ + AccessKey: "test_fake_ak", + SecretKey: "test_fake_sk", + Host: "test_fake_host", + }) + + err := SearchContainerLogs(client, "access", "", time.Now().Add(time.Hour*time.Duration(-72)), time.Now(), true, 25000, os.Stdout) + if err != nil { + fmt.Println(err) + } +} + +func SearchContainerLogs(client kirksdk.QcosClient, repoType, queryString string, fromTime, toTime time.Time, tail bool, size int, out io.Writer) (err error) { + + args := kirksdk.SearchContainerLogsArgs{ + RepoType: repoType, + } + + var query string + // query is a string template, but not the final query string in SearchContainerLogsArgs + if tail { + query = addClause(query, fmt.Sprintf("collectedAtNano:[%d TO %%d]", fromTime.UnixNano())) + } else { + query = addClause(query, fmt.Sprintf("collectedAtNano:[%%d TO %d]", toTime.UnixNano())) + } + + if len(queryString) > 0 { + // we will use fmt.Sprintf to format the query string later, + // so the raw % should be replaced into %% to prevent BADPREC MISSING error from fmt.Sprintf + query = addClause(query, strings.NewReplacer("%", "%%").Replace(queryString)) + } + + if tail { + args.Sort = "collectedAtNano:desc" + args.Query = fmt.Sprintf(query, toTime.UnixNano()) + } else { + args.Sort = "collectedAtNano:asc" + args.Query = fmt.Sprintf(query, fromTime.UnixNano()) + } + + expectedSize := size + maxSizePerRequest := 10000 + if expectedSize > maxSizePerRequest { + args.Size = maxSizePerRequest + } else { + args.Size = expectedSize + } + + result, err := client.SearchContainerLogs(context.TODO(), args) + if err != nil { + return err + } + + total := result.Total + logs := make([]string, 0) + logs = append(logs, formatLogs(result.Data, repoType)...) + + // We cannot get logs more than total. + if total < expectedSize { + expectedSize = total + } + + for { + expectedSize -= len(result.Data) + if expectedSize <= 0 { + break + } + + if expectedSize > maxSizePerRequest { + args.Size = maxSizePerRequest + } else { + args.Size = expectedSize + } + if tail { + args.Query = fmt.Sprintf(query, result.Data[len(result.Data)-1].CollectedAtNano-1) + } else { + args.Query = fmt.Sprintf(query, result.Data[len(result.Data)-1].CollectedAtNano+1) + } + result, err = client.SearchContainerLogs(context.TODO(), args) + if err != nil { + return err + } + if len(result.Data) <= 0 { + break + } + logs = append(logs, formatLogs(result.Data, repoType)...) + } + + count := len(logs) + + if err != nil { + fmt.Printf("search container logs err: %s\n", err) + } else { + if tail { + for i := len(logs) - 1; i >= 0; i-- { + fmt.Fprintln(out, logs[i]) + } + } else { + for _, log := range logs { + fmt.Fprintln(out, log) + } + } + fmt.Fprintf(out, "\nSummary: %d/%d\n", count, total) + } + + return +} + +func formatLogs(hits []kirksdk.Hit, repoType string) (logs []string) { + var log string + logs = make([]string, 0) + for _, hit := range hits { + if repoType == "access" { + // access log does not have log field, so we need to format access log by using other fields + log = formatAccessLogField(hit) + } else { + log = hit.Log + } + logs = append(logs, log) + } + return +} + +func formatAccessLogField(hit kirksdk.Hit) string { + if hit.Method == "" { + hit.Method = "-" + } + if hit.Url == "" { + hit.Url = "-" + } + if hit.RequestHeader == "" { + hit.RequestHeader = "-" + } + if hit.RequestParams == "" { + hit.RequestParams = "-" + } + if hit.RequestBody == "" { + hit.RequestBody = "-" + } + if hit.ResponseHeader == "" { + hit.ResponseHeader = "-" + } + if hit.ResponseBody == "" { + hit.ResponseBody = "-" + } + return fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s\t%d\t%s\t%s\t%gms", hit.StartAt.Format("2006-01-02 15:04:05.000000Z07:00"), hit.Method, hit.Url, hit.RequestHeader, hit.RequestParams, hit.RequestBody, hit.StatusCode, hit.ResponseHeader, hit.ResponseBody, float64(hit.ElapsedNano)/1e6) +} + +func addClause(q string, clause string) string { + if q == "" { + return clause + } + if clause == "" { + return q + } + return fmt.Sprintf("%s AND %s", q, clause) +} diff --git a/kirksdk/kirksdk.go b/kirksdk/kirksdk.go index a4cf6fd..314fa8a 100644 --- a/kirksdk/kirksdk.go +++ b/kirksdk/kirksdk.go @@ -1,3 +1,3 @@ package kirksdk -const Version = "2.0.0" +const Version = "2.1.0" diff --git a/kirksdk/qcos_api.go b/kirksdk/qcos_api.go index 1abcbf3..cc75a7a 100644 --- a/kirksdk/qcos_api.go +++ b/kirksdk/qcos_api.go @@ -752,13 +752,38 @@ type LogsSearchResult struct { } type Hit struct { - Log string `json:"log"` - CollectedAt time.Time `json:"collectedAt"` + CollectedAt time.Time `json:"collectedAt" repo:"pod,access"` CollectedAtNano int64 `json:"collectedAtNano"` - PodIP string `json:"podIp"` - ProcessName string `json:"processName"` - GateID string `json:"gateId"` - Domain string `json:"domain"` + Host string `json:"host"` + + Log string `json:"log" repo:"pod"` + Path string `json:"path" repo:"pod"` + Pattern string `json:"pattern" repo:"pod"` + ContainerId string `json:"containerId"` + ContainerName string `json:"containerName"` + JobInstance string `json:"jobInstance" repo:"pod"` + JobTask string `json:"jobTask" repo:"pod"` + PodIp string `json:"podIp" repo:"pod"` + PodName string `json:"podName" repo:"pod"` + PodVer string `json:"podVer"` + ProcessName string `json:"processName"` + Sip string `json:"sip"` + Source string `json:"source" repo:"pod"` + + Type string `json:"type" repo:"access"` + RequestApp string `json:"requestApp" repo:"access"` + GateId string `json:"gateId" repo:"access"` + StartAt time.Time `json:"startAt" repo:"access"` + Method string `json:"method" repo:"access"` + Url string `json:"url" repo:"access"` + ReqId string `json:"reqId" repo:"access"` + StatusCode int64 `json:"statusCode" repo:"access"` + ElapsedNano int64 `json:"elapsedNano" repo:"access"` + RequestHeader string `json:"requestHeader" repo:"access"` + RequestParams string `json:"requestParams" repo:"access"` + RequestBody string `json:"requestBody" repo:"access"` + ResponseHeader string `json:"responseHeader" repo:"access"` + ResponseBody string `json:"responseBody" repo:"access"` } var (