Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,575 changes: 1,507 additions & 68 deletions rest/src/main/resources/scripts/GoApiTemplate.groovy

Large diffs are not rendered by default.

1,742 changes: 1,665 additions & 77 deletions rest/src/main/resources/scripts/GoInventory.groovy

Large diffs are not rendered by default.

551 changes: 551 additions & 0 deletions rest/src/main/resources/scripts/GoTestTemplate.groovy

Large diffs are not rendered by default.

281 changes: 107 additions & 174 deletions rest/src/main/resources/scripts/RestDocumentationGenerator.groovy

Large diffs are not rendered by default.

936 changes: 936 additions & 0 deletions rest/src/main/resources/scripts/SDK生成器开发指南.md

Large diffs are not rendered by default.

606 changes: 305 additions & 301 deletions rest/src/main/resources/scripts/SdkApiTemplate.groovy

Large diffs are not rendered by default.

1,088 changes: 1,088 additions & 0 deletions rest/src/main/resources/scripts/ZStack SDK Go 开发规范与标准.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Copyright (c) ZStack.io, Inc.

package param

import "time"

var _ = time.Now() // avoid unused import

type DeleteMode string

const (
DeleteModePermissive DeleteMode = "Permissive"
DeleteModeEnforcing DeleteMode = "Enforcing"
)

type BaseParam struct {
SystemTags []string `json:"systemTags,omitempty"` // System tags
UserTags []string `json:"userTags,omitempty"` // User tags
RequestIp string `json:"requestIp,omitempty"` // Request IP
}

type HqlParam struct {
OperationName string `json:"operationName"` // Request name
Query string `json:"query"` // Query statement
Variables Variables `json:"variables"` // Parameters for the statement
}

type Variables struct {
Conditions []Condition `json:"conditions"` // Conditions
ExtraConditions []Condition `json:"extraConditions"` // Extra conditions
Input map[string]interface{} `json:"input"` // Input parameters
PageVar `json:",inline,omitempty"`
Type string `json:"type"` // Type
}

type Condition struct {
Key string `json:"key"` // Key
Op string `json:"op"` // Operator
Value string `json:"value"` // Value
}

type PageVar struct {
Start int `json:"start,omitempty"` // Start page
Limit int `json:"limit,omitempty"` // Limit per page
}
102 changes: 102 additions & 0 deletions rest/src/main/resources/scripts/templates/base_params.go.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// Copyright (c) ZStack.io, Inc.

package param

import (
"errors"
"fmt"
"net/url"
"reflect"
"strings"

"github.com/fatih/structs"
)

type QueryParam struct {
url.Values
}

func NewQueryParam() QueryParam {
return QueryParam{
Values: make(url.Values),
}
}

// AddQ adds a query condition, similar to a MySQL database query.
// Omitting this field will return all records, with the number of returned records limited by the 'limit' field.
func (params *QueryParam) AddQ(q string) *QueryParam {
if params.Get("q") == "" {
params.Set("q", q)
} else {
params.Add("q", q)
}
return params
}

// Limit sets the maximum number of records to return, similar to MySQL's 'limit'. Default value is 1000.
func (params *QueryParam) Limit(limit int) *QueryParam {
params.Set("limit", fmt.Sprintf("%d", limit))
return params
}

// Start sets the starting position for the query, similar to MySQL's 'offset'. Used with 'limit' for pagination.
func (params *QueryParam) Start(start int) *QueryParam {
params.Set("start", fmt.Sprintf("%d", start))
return params
}

// Count sets the query to return the count of records that match the query conditions, similar to MySQL's 'count()' function.
func (params *QueryParam) Count(count bool) *QueryParam {
params.Set("count", fmt.Sprintf("%t", count))
return params
}

// GroupBy groups the results by a specified field, similar to MySQL's 'group by' keyword.
func (params *QueryParam) GroupBy(groupBy string) *QueryParam {
params.Set("groupBy", groupBy)
return params
}

// ReplyWithCount, when set to true, includes the total count of records that match the query in the response.
func (params *QueryParam) ReplyWithCount(replyWithCount bool) *QueryParam {
params.Set("replyWithCount", fmt.Sprintf("%t", replyWithCount))
return params
}

// FilterName sets a filter name, functionality is unknown from ZStack Java SDK (sdk-4.4.0.jar).
func (params *QueryParam) FilterName(filterName string) *QueryParam {
params.Set("filterName", filterName)
return params
}

// Sort sorts the results by a specified field, similar to MySQL's 'sort by' keyword.
// Use '+' for ascending order and '-' for descending order, followed by the field name.
func (params *QueryParam) Sort(sort string) *QueryParam {
params.Set("sort", sort)
return params
}

// Fields specifies the fields to return, similar to MySQL's 'select' fields functionality.
func (params *QueryParam) Fields(fields []string) *QueryParam {
params.Set("fields", strings.Join(fields, ","))
return params
}

// ConvertStruct2UrlValues converts a struct to url.Values.
func ConvertStruct2UrlValues(param interface{}) (url.Values, error) {
if reflect.Ptr != reflect.TypeOf(param).Kind() {
return nil, errors.New("model should be pointer kind")
}
result := url.Values{}
if param == nil || reflect.ValueOf(param).IsNil() {
return nil, errors.New("param is nil")
}

s := structs.New(param)
s.TagName = "json"
mappedOpts := s.Map()
for k, v := range mappedOpts {
result.Set(k, fmt.Sprintf("%v", v))
}
return result, nil
}
228 changes: 228 additions & 0 deletions rest/src/main/resources/scripts/templates/client.go.template
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
// Copyright (c) ZStack.io, Inc.

package client

import (
"context"
"crypto/sha512"
"fmt"
"net/http"
"net/url"

"github.com/kataras/golog"
"github.com/zstackio/zsphere-sdk-go-v2/pkg/errors"
"github.com/zstackio/zsphere-sdk-go-v2/pkg/param"
"github.com/zstackio/zsphere-sdk-go-v2/pkg/view"
)

type ZSClient struct {
*ZSHttpClient
}

func NewZSClient(config *ZSConfig) *ZSClient {
return &ZSClient{
ZSHttpClient: NewZSHttpClient(config),
}
}

func (cli *ZSClient) Login(ctx context.Context) (*view.SessionInventoryView, error) {
if cli.authType != AuthTypeAccountUser && cli.authType != AuthTypeAccount {
return nil, errors.ErrNotSupported
}

var sessionView *view.SessionInventoryView
var err error
if cli.authType == AuthTypeAccountUser {
sessionView, err = cli.logInByAccountUser(ctx)
} else {
sessionView, err = cli.logInByAccount(ctx)
}

if err != nil {
golog.Errorf("ZSClient.Login error:%v", err)
return nil, err
}

cli.LoadSession(sessionView.Uuid)
return sessionView, nil
}

func (cli *ZSClient) logInByAccountUser(ctx context.Context) (*view.SessionInventoryView, error) {
if cli.authType != AuthTypeAccountUser {
return nil, errors.ErrNotSupported
}

if len(cli.accountName) == 0 || len(cli.accountUserName) == 0 || len(cli.password) == 0 {
return nil, errors.ErrParameter
}

params := param.LogInByUserParam{
LogInByUser: param.LogInByUserDetailParam{
AccountName: cli.accountName,
UserName: cli.accountUserName,
Password: fmt.Sprintf("%x", sha512.Sum512([]byte(cli.password))),
},
}
sessionView := view.SessionInventoryView{}
err := cli.Put(ctx, "v1/accounts/users/login", "", params, &sessionView)
if err != nil {
golog.Errorf("ZSClient.logInByAccountUser Account[%s] User[%s] error:%v",
cli.accountName, cli.accountUserName, err)
return nil, err
}

return &sessionView, nil
}

func (cli *ZSClient) logInByAccount(ctx context.Context) (*view.SessionInventoryView, error) {
if cli.authType != AuthTypeAccount {
return nil, errors.ErrNotSupported
}

if len(cli.accountName) == 0 || len(cli.password) == 0 {
return nil, errors.ErrParameter
}

params := param.LoginByAccountParam{
LoginByAccount: param.LoginByAccountDetailParam{
AccountName: cli.accountName,
Password: fmt.Sprintf("%x", sha512.Sum512([]byte(cli.password))),
},
}
sessionView := view.SessionInventoryView{}
err := cli.Put(ctx, "v1/accounts/login", "", params, &sessionView)
if err != nil {
golog.Errorf("ZSClient.logInByAccount Account[%s] error:%v", cli.accountName, err)
return nil, err
}

return &sessionView, nil
}

func (cli *ZSClient) ValidateSession(ctx context.Context) (map[string]bool, error) {
if cli.authType != AuthTypeAccountUser && cli.authType != AuthTypeAccount {
return nil, errors.ErrNotSupported
}

if len(cli.sessionId) == 0 {
return nil, errors.ErrNotSupported
}

return cli.ValidateSessionId(ctx, cli.sessionId)
}

func (cli *ZSClient) ValidateSessionId(ctx context.Context, sessionId string) (map[string]bool, error) {
validSession := make(map[string]bool)
err := cli.GetWithSpec(ctx, "v1/accounts/sessions", sessionId, "valid", "", nil, &validSession)
if err != nil {
golog.Errorf("ZSClient.ValidateSession sessionId[%s] error:%v", sessionId, err)
return nil, err
}

golog.Debugf("ZSClient.ValidateSession sessionId[%s]:%v", sessionId, validSession)
return validSession, nil
}

func (cli *ZSClient) Logout(ctx context.Context) error {
if cli.authType != AuthTypeAccountUser && cli.authType != AuthTypeAccount {
return errors.ErrNotSupported
}

if len(cli.sessionId) == 0 {
return errors.ErrNotSupported
}

err := cli.Delete(ctx, "v1/accounts/sessions", cli.sessionId, "")
if err != nil {
golog.Errorf("ZSClient.Logout sessionId[%s] error:%v", cli.sessionId, err)
return err
}

cli.unloadSession()
return nil
}

func (cli *ZSClient) WebLogin(ctx context.Context) (*view.WebUISessionView, error) {
if cli.authType != AuthTypeAccountUser && cli.authType != AuthTypeAccount {
return nil, errors.ErrNotSupported
}

var operationName, username, loginType, query string
var input map[string]interface{}
if cli.authType == AuthTypeAccount {
operationName, username, loginType = "loginByAccount", cli.accountName, "iam1"
input = map[string]interface{}{
"accountName": cli.accountName,
"password": fmt.Sprintf("%x", sha512.Sum512([]byte(cli.password))),
}
query = `mutation loginByAccount($input:LoginByAccountInput!) {
loginByAccount(input: $input) {
sessionId,
accountUuid,
userUuid,
currentIdentity
}
}`
} else {
operationName, username, loginType = "loginIAM2VirtualID", cli.accountUserName, "iam2"
input = map[string]interface{}{
"name": cli.accountUserName,
"password": fmt.Sprintf("%x", sha512.Sum512([]byte(cli.password))),
}
query = `mutation loginIAM2VirtualID($input:LoginIAM2VirtualIDInput!) {
loginIAM2VirtualID(input: $input) {
sessionId,
accountUuid,
userUuid,
currentIdentity
}
}`
}

result := new(view.WebUISessionView)
params := param.HqlParam{
OperationName: operationName,
Query: query,
Variables: param.Variables{
Input: input,
},
}
respHeader, err := cli.hql(ctx, params, result, responseKeyData, operationName)
if err != nil {
return nil, err
}
result.UserName = username
result.LoginType = loginType
result.ZSVersion = respHeader.Get("Zs-Version")
return result, nil
}

func (cli *ZSClient) hql(ctx context.Context, params param.HqlParam, retVal interface{}, unMarshalKeys ...string) (http.Header, error) {
urlStr := fmt.Sprintf("http://%s:%d/graphql", cli.hostname, WebZStackPort)
_, respHeader, resp, err := cli.httpPost(ctx, urlStr, jsonMarshal(params), false)
if err != nil {
Comment on lines +200 to +203
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -path "*/rest/src/main/resources/scripts/templates/client.go.template" -type f

Repository: MatheMatrix/zstack

Length of output: 124


🏁 Script executed:

cat -n ./rest/src/main/resources/scripts/templates/client.go.template | sed -n '195,230p'

Repository: MatheMatrix/zstack

Length of output: 1318


🏁 Script executed:

cat -n ./rest/src/main/resources/scripts/templates/client.go.template | grep -A 5 "func.*getRequestURL"

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

rg "getRequestURL|WebZStackPort" ./rest/src/main/resources/scripts/templates/client.go.template | head -20

Repository: MatheMatrix/zstack

Length of output: 177


🏁 Script executed:

wc -l ./rest/src/main/resources/scripts/templates/client.go.template

Repository: MatheMatrix/zstack

Length of output: 128


🏁 Script executed:

cat -n ./rest/src/main/resources/scripts/templates/client.go.template

Repository: MatheMatrix/zstack

Length of output: 8090


🏁 Script executed:

find . -type f -name "*http*client*.go" | grep -i zs

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

rg "type ZSHttpClient" --type go

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

rg "getRequestURL|ZSHttpClient" --type go -A 3 | head -50

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

find ./rest/src/main/resources/scripts/templates -type f | head -20

Repository: MatheMatrix/zstack

Length of output: 475


🏁 Script executed:

rg "WebZStackPort" --type go -B 2 -A 2

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

rg "WebZStackPort|getRequestURL" --type go --type java -B 2 -A 2 | head -100

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

rg "hql|graphql" --type go --type java -i | head -30

Repository: MatheMatrix/zstack

Length of output: 2954


🏁 Script executed:

find ./rest/src/main/resources/scripts/templates -name "*.template" -type f -exec grep -l "WebZStackPort\|getRequestURL" {} \;

Repository: MatheMatrix/zstack

Length of output: 124


🏁 Script executed:

find . -type f -name "*.go" | grep -i client | head -20

Repository: MatheMatrix/zstack

Length of output: 44


🏁 Script executed:

rg "WebZStackPort|getRequestURL" --type go --type java | head -50

Repository: MatheMatrix/zstack

Length of output: 44


GraphQL 登录请求被硬编码为明文 HTTP,无法支持 HTTPS

第 201 行将 GraphQL 端点 URL 硬编码为 http://%s:%d/graphql,而同文件中的 Zql 方法(第 216 行)使用 cli.getRequestURL() 动态构造 URL。由于 WebLogin 调用 hql 执行登录认证,登录链路应该遵循服务配置支持 HTTPS,即便密码已被 SHA512 哈希处理。建议将 hql 改为使用类似 getRequestURL 的灵活方式构造 URL。

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@rest/src/main/resources/scripts/templates/client.go.template` around lines
200 - 203, The hql method currently hardcodes the GraphQL endpoint as
"http://%s:%d/graphql"; change hql to construct its request URL the same way Zql
does by calling cli.getRequestURL("/graphql") (or the existing getRequestURL
helper) instead of fmt.Sprintf so the login flow honors TLS/HTTPS configuration;
update the urlStr assignment in hql to use cli.getRequestURL and ensure the call
to cli.httpPost keeps the same params (jsonMarshal(params)) and semantics.

return nil, err
}

if retVal == nil {
return nil, nil
}

return respHeader, resp.Unmarshal(retVal, unMarshalKeys...)
}

func (cli *ZSClient) Zql(ctx context.Context, querySt string, retVal interface{}, unMarshalKeys ...string) (http.Header, error) {
encodedQuery := url.QueryEscape(querySt)
baseUrl := cli.getRequestURL("v1/zql")
urlStr := fmt.Sprintf("%s?zql=%s", baseUrl, encodedQuery)
_, respHeader, resp, err := cli.httpGet(ctx, urlStr, false)
if err != nil {
return nil, err
}

if retVal == nil {
return nil, nil
}

return respHeader, resp.Unmarshal(retVal, unMarshalKeys...)
}
Loading