diff --git a/rest/src/main/resources/scripts/GoApiTemplate.groovy b/rest/src/main/resources/scripts/GoApiTemplate.groovy index fefe945ba3c..1141156f7f2 100644 --- a/rest/src/main/resources/scripts/GoApiTemplate.groovy +++ b/rest/src/main/resources/scripts/GoApiTemplate.groovy @@ -2,6 +2,8 @@ package scripts import org.zstack.header.query.APIQueryMessage import org.zstack.header.rest.RestRequest +import org.zstack.header.rest.SDK +import org.zstack.header.rest.RestResponse import org.zstack.rest.sdk.SdkFile import org.zstack.rest.sdk.SdkTemplate import org.zstack.utils.Utils @@ -20,6 +22,7 @@ class GoApiTemplate implements SdkTemplate { private RestRequest at private String path private Class responseClass + private String allTo; private String replyName private SdkTemplate inventoryGenerator @@ -54,6 +57,9 @@ class GoApiTemplate implements SdkTemplate { // Track APIs that should be skipped during generation private static Set skippedApis = new HashSet<>() + // Flag to indicate if the template was successfully initialized + private boolean valid = false + GoApiTemplate(Class apiMsgClass, SdkTemplate inventoryGenerator) { try { apiMsgClazz = apiMsgClass @@ -85,6 +91,12 @@ class GoApiTemplate implements SdkTemplate { } } + allTo = "" + if (responseClass != null) { + RestResponse restResponse = responseClass.getAnnotation(RestResponse) + allTo = restResponse != null ? restResponse.allTo() : "" + } + if (responseClass != null) { replyName = responseClass.simpleName.replaceAll('^API', '').replaceAll('Reply$', '').replaceAll('Event$', '') } else { @@ -103,6 +115,7 @@ class GoApiTemplate implements SdkTemplate { queryInventoryClass = findInventoryClass() logger.warn("[GoSDK] Processing API: " + clzName + " -> action=" + actionType + ", resource=" + resourceName + ", response=" + responseClass?.simpleName) + valid = true } catch (Throwable e) { logger.error("[GoSDK] CRITICAL ERROR constructing GoApiTemplate for ${apiMsgClass.name}: ${e.class.name}: ${e.message}", e) throw e @@ -113,6 +126,14 @@ class GoApiTemplate implements SdkTemplate { return at } + /** + * Check if the template was successfully initialized. + * Templates without @RestRequest annotation are invalid. + */ + boolean isValid() { + return valid + } + String getActionType() { return actionType } @@ -184,6 +205,21 @@ class GoApiTemplate implements SdkTemplate { logger.warn("[GoSDK] Registered ${mappings.size()} LongJob mappings") } + /** + * Reset all static state for clean re-generation. + * Should be called at the beginning of generate() or before each SDK generation run. + */ + static void reset() { + generatedParamFiles.clear() + generatedActionFiles.clear() + generatedViewFiles.clear() + knownInventoryClasses = null + groupedApiNames.clear() + longJobMappings.clear() + skippedApis.clear() + logger.warn("[GoSDK] Reset all static state") + } + /** * Check if current API supports async operation */ @@ -373,6 +409,13 @@ class GoApiTemplate implements SdkTemplate { return "v1/" + path } + private String getApiOptPath(String optPath) { + if (optPath.startsWith("/")) { + return "v1" + optPath + } + return "v1/" + optPath + } + List generate() { return [] } @@ -509,18 +552,18 @@ class GoApiTemplate implements SdkTemplate { if (!hasParams) { // No params: don't require user to pass params, use empty map internally if (unwrap) { - return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context) (*view.${viewStructName}, error) { \tvar resp view.${responseStructName} -\tif err := cli.Post("${apiPath}", map[string]interface{}{}, &resp); err != nil { +\tif err := cli.Post(ctx, "${apiPath}", map[string]interface{}{}, &resp); err != nil { \t\treturn nil, err \t} \treturn &resp.${fieldName}, nil } """ } else { - return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context) (*view.${viewStructName}, error) { \tresp := view.${viewStructName}{} -\tif err := cli.Post("${apiPath}", map[string]interface{}{}, &resp); err != nil { +\tif err := cli.Post(ctx, "${apiPath}", map[string]interface{}{}, &resp); err != nil { \t\treturn nil, err \t} \treturn &resp, nil @@ -531,18 +574,18 @@ class GoApiTemplate implements SdkTemplate { // Has params: require user to pass params if (unwrap) { - return """func (cli *ZSClient) ${clzName}(params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, params param.${clzName}Param) (*view.${viewStructName}, error) { \tvar resp view.${responseStructName} -\tif err := cli.Post("${apiPath}", params, &resp); err != nil { +\tif err := cli.Post(ctx, "${apiPath}", params, &resp); err != nil { \t\treturn nil, err \t} \treturn &resp.${fieldName}, nil } """ } else { - return """func (cli *ZSClient) ${clzName}(params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, params param.${clzName}Param) (*view.${viewStructName}, error) { \tresp := view.${viewStructName}{} -\tif err := cli.Post("${apiPath}", params, &resp); err != nil { +\tif err := cli.Post(ctx, "${apiPath}", params, &resp); err != nil { \t\treturn nil, err \t} \treturn &resp, nil @@ -552,9 +595,9 @@ class GoApiTemplate implements SdkTemplate { } private String generateQueryMethod(String apiPath, String viewStructName) { - return """func (cli *ZSClient) ${clzName}(params *param.QueryParam) ([]view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, params *param.QueryParam) ([]view.${viewStructName}, error) { \tvar resp []view.${viewStructName} -\treturn resp, cli.List("${apiPath}", params, &resp) +\treturn resp, cli.List(ctx, "${apiPath}", params, &resp) } """ } @@ -566,7 +609,7 @@ class GoApiTemplate implements SdkTemplate { private String generatePageMethod(String apiPath, String viewStructName) { String pageMethodName = clzName.replaceFirst('^Query', 'Page') String varName = resourceName.substring(0, 1).toLowerCase() + resourceName.substring(1) - if (varName.endsWith("y")) { + if (varName.endsWith("y") && varName.length() > 1 && !"aeiou".contains(varName.charAt(varName.length() - 2).toString())) { varName = varName.substring(0, varName.length() - 1) + "ies" } else if (!varName.endsWith("s")) { varName = varName + "s" @@ -574,9 +617,9 @@ class GoApiTemplate implements SdkTemplate { return """ // ${pageMethodName} Pagination -func (cli *ZSClient) ${pageMethodName}(params *param.QueryParam) ([]view.${viewStructName}, int, error) { +func (cli *ZSClient) ${pageMethodName}(ctx context.Context, params *param.QueryParam) ([]view.${viewStructName}, int, error) { \tvar ${varName} []view.${viewStructName} -\ttotal, err := cli.Page("${apiPath}", params, &${varName}) +\ttotal, err := cli.Page(ctx, "${apiPath}", params, &${varName}) \treturn ${varName}, total, err } """ @@ -602,9 +645,9 @@ func (cli *ZSClient) ${pageMethodName}(params *param.QueryParam) ([]view.${viewS String spec = buildSpecPath(remainingPlaceholders) return """ -func (cli *ZSClient) ${getMethodName}(${params}) (*view.${viewStructName}, error) { +func (cli *ZSClient) ${getMethodName}(ctx context.Context, ${params}) (*view.${viewStructName}, error) { \tvar resp view.${viewStructName} -\terr := cli.GetWithSpec("${cleanPath}", ${firstParam}, ${spec}, "", nil, &resp) +\terr := cli.GetWithSpec(ctx, "${cleanPath}", ${firstParam}, ${spec}, "${allTo}", nil, &resp) \tif err != nil { \t\treturn nil, err \t} @@ -615,9 +658,9 @@ func (cli *ZSClient) ${getMethodName}(${params}) (*view.${viewStructName}, error // Standard case: single uuid parameter return """ -func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, error) { +func (cli *ZSClient) ${getMethodName}(ctx context.Context, uuid string) (*view.${viewStructName}, error) { \tvar resp view.${viewStructName} -\tif err := cli.Get("${cleanPath}", uuid, nil, &resp); err != nil { +\tif err := cli.Get(ctx, "${cleanPath}", uuid, nil, &resp); err != nil { \t\treturn nil, err \t} \treturn &resp, nil @@ -639,9 +682,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err if (placeholders.size() == 0) { // No placeholder: no uuid parameter needed // Use GetWithRespKey to extract the inventory field - return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context) (*view.${viewStructName}, error) { \tvar resp view.${responseStructName} -\tif err := cli.GetWithRespKey("${cleanPath}", "", "inventory", nil, &resp); err != nil { +\tif err := cli.GetWithRespKey(ctx, "${cleanPath}", "", "inventory", nil, &resp); err != nil { \t\treturn nil, err \t} \treturn &resp.${fieldName}, nil @@ -649,9 +692,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err """ } else { // Single placeholder: use GetWithRespKey with uuid to extract inventory - return """func (cli *ZSClient) ${clzName}(uuid string) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, uuid string) (*view.${viewStructName}, error) { \tvar resp view.${responseStructName} -\tif err := cli.GetWithRespKey("${cleanPath}", uuid, "inventory", nil, &resp); err != nil { +\tif err := cli.GetWithRespKey(ctx, "${cleanPath}", uuid, "inventory", nil, &resp); err != nil { \t\treturn nil, err \t} \treturn &resp.${fieldName}, nil @@ -666,9 +709,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err String params = placeholders.collect { "${toSafeGoParamName(it)} string" }.join(", ") String spec = buildSpecPath(remainingPlaceholders) - return """func (cli *ZSClient) ${clzName}(${params}) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}) (*view.${viewStructName}, error) { \tvar resp view.${responseStructName} -\terr := cli.GetWithSpec("${cleanPath}", ${firstParam}, ${spec}, "", nil, &resp) +\terr := cli.GetWithSpec(ctx, "${cleanPath}", ${firstParam}, ${spec}, "", nil, &resp) \tif err != nil { \t\treturn nil, err \t} @@ -682,9 +725,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err if (placeholders.size() == 0) { // No placeholder: no uuid parameter needed // Use GetWithRespKey with empty responseKey to parse whole response - return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context) (*view.${viewStructName}, error) { \tvar resp view.${viewStructName} -\tif err := cli.GetWithRespKey("${cleanPath}", "", "", nil, &resp); err != nil { +\tif err := cli.GetWithRespKey(ctx, "${cleanPath}", "", "", nil, &resp); err != nil { \t\treturn nil, err \t} \treturn &resp, nil @@ -692,9 +735,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err """ } else { // Single placeholder: use GetWithRespKey with uuid - return """func (cli *ZSClient) ${clzName}(uuid string) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, uuid string) (*view.${viewStructName}, error) { \tvar resp view.${viewStructName} -\tif err := cli.GetWithRespKey("${cleanPath}", uuid, "", nil, &resp); err != nil { +\tif err := cli.GetWithRespKey(ctx, "${cleanPath}", uuid, "", nil, &resp); err != nil { \t\treturn nil, err \t} \treturn &resp, nil @@ -709,9 +752,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err String params = placeholders.collect { "${toSafeGoParamName(it)} string" }.join(", ") String spec = buildSpecPath(remainingPlaceholders) - return """func (cli *ZSClient) ${clzName}(${params}) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}) (*view.${viewStructName}, error) { \tvar resp view.${viewStructName} -\terr := cli.GetWithSpec("${cleanPath}", ${firstParam}, ${spec}, "", nil, &resp) +\terr := cli.GetWithSpec(ctx, "${cleanPath}", ${firstParam}, ${spec}, "", nil, &resp) \tif err != nil { \t\treturn nil, err \t} @@ -756,9 +799,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err String paramName = toSafeGoParamName(placeholders[0]) if (isActionApi) { // Action APIs wrap params.Params inside a map - return """func (cli *ZSClient) ${clzName}(${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) { \tvar resp view.${responseStructName} -\tif err := cli.Put("${cleanPath}", ${paramName}, map[string]interface{}{ +\tif err := cli.Put(ctx, "${cleanPath}", ${paramName}, map[string]interface{}{ \t\t"${actionKey}": params.Params, \t}, &resp); err != nil { \t\treturn nil, err @@ -767,9 +810,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err } """ } else { - return """func (cli *ZSClient) ${clzName}(${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) { \tvar resp view.${responseStructName} -\tif err := cli.Put("${cleanPath}", ${paramName}, params, &resp); err != nil { +\tif err := cli.Put(ctx, "${cleanPath}", ${paramName}, params, &resp); err != nil { \t\treturn nil, err \t} \treturn &resp.${fieldName}, nil @@ -781,9 +824,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err if (!hasParams) { // No params: don't require user input if (isActionApi) { - return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context) (*view.${viewStructName}, error) { \tvar resp view.${responseStructName} -\tif err := cli.Put("${cleanPath}", "", map[string]interface{}{ +\tif err := cli.Put(ctx, "${cleanPath}", "", map[string]interface{}{ \t\t"${actionKey}": map[string]interface{}{}, \t}, &resp); err != nil { \t\treturn nil, err @@ -792,9 +835,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err } """ } else { - return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context) (*view.${viewStructName}, error) { \tvar resp view.${responseStructName} -\tif err := cli.Put("${cleanPath}", "", map[string]interface{}{}, &resp); err != nil { +\tif err := cli.Put(ctx, "${cleanPath}", "", map[string]interface{}{}, &resp); err != nil { \t\treturn nil, err \t} \treturn &resp.${fieldName}, nil @@ -802,9 +845,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err """ } } else if (isActionApi) { - return """func (cli *ZSClient) ${clzName}(params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, params param.${clzName}Param) (*view.${viewStructName}, error) { \tvar resp view.${responseStructName} -\tif err := cli.Put("${cleanPath}", "", map[string]interface{}{ +\tif err := cli.Put(ctx, "${cleanPath}", "", map[string]interface{}{ \t\t"${actionKey}": params.Params, \t}, &resp); err != nil { \t\treturn nil, err @@ -813,9 +856,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err } """ } else { - return """func (cli *ZSClient) ${clzName}(uuid string, params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, uuid string, params param.${clzName}Param) (*view.${viewStructName}, error) { \tvar resp view.${responseStructName} -\tif err := cli.Put("${cleanPath}", uuid, params, &resp); err != nil { +\tif err := cli.Put(ctx, "${cleanPath}", uuid, params, &resp); err != nil { \t\treturn nil, err \t} \treturn &resp.${fieldName}, nil @@ -831,9 +874,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err String params = placeholders.collect { "${toSafeGoParamName(it)} string" }.join(", ") String spec = buildSpecPath(remainingPlaceholders) - return """func (cli *ZSClient) ${clzName}(${params}, params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}, params param.${clzName}Param) (*view.${viewStructName}, error) { \tvar resp view.${responseStructName} -\terr := cli.PutWithSpec("${cleanPath}", ${firstParam}, ${spec}, "", params, &resp) +\terr := cli.PutWithSpec(ctx, "${cleanPath}", ${firstParam}, ${spec}, "", params, &resp) \tif err != nil { \t\treturn nil, err \t} @@ -850,9 +893,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err String paramName = toSafeGoParamName(placeholders[0]) if (isActionApi) { // Action APIs wrap params.Params inside a map - return """func (cli *ZSClient) ${clzName}(${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) { \tresp := view.${viewStructName}{} -\tif err := cli.PutWithRespKey("${cleanPath}", ${paramName}, "", map[string]interface{}{ +\tif err := cli.PutWithRespKey(ctx, "${cleanPath}", ${paramName}, "", map[string]interface{}{ \t\t"${actionKey}": params.Params, \t}, &resp); err != nil { \t\treturn nil, err @@ -861,9 +904,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err } """ } else { - return """func (cli *ZSClient) ${clzName}(${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) { \tresp := view.${viewStructName}{} -\tif err := cli.PutWithRespKey("${cleanPath}", ${paramName}, "", params, &resp); err != nil { +\tif err := cli.PutWithRespKey(ctx, "${cleanPath}", ${paramName}, "", params, &resp); err != nil { \t\treturn nil, err \t} \treturn &resp, nil @@ -875,9 +918,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err if (!hasParams) { // No params: don't require user input if (isActionApi) { - return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context) (*view.${viewStructName}, error) { \tresp := view.${viewStructName}{} -\tif err := cli.PutWithRespKey("${cleanPath}", "", "", map[string]interface{}{ +\tif err := cli.PutWithRespKey(ctx, "${cleanPath}", "", "", map[string]interface{}{ \t\t"${actionKey}": map[string]interface{}{}, \t}, &resp); err != nil { \t\treturn nil, err @@ -886,9 +929,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err } """ } else { - return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context) (*view.${viewStructName}, error) { \tresp := view.${viewStructName}{} -\tif err := cli.PutWithRespKey("${cleanPath}", "", "", map[string]interface{}{}, &resp); err != nil { +\tif err := cli.PutWithRespKey(ctx, "${cleanPath}", "", "", map[string]interface{}{}, &resp); err != nil { \t\treturn nil, err \t} \treturn &resp, nil @@ -896,9 +939,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err """ } } else if (isActionApi) { - return """func (cli *ZSClient) ${clzName}(params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, params param.${clzName}Param) (*view.${viewStructName}, error) { \tresp := view.${viewStructName}{} -\tif err := cli.PutWithRespKey("${cleanPath}", "", "", map[string]interface{}{ +\tif err := cli.PutWithRespKey(ctx, "${cleanPath}", "", "", map[string]interface{}{ \t\t"${actionKey}": params.Params, \t}, &resp); err != nil { \t\treturn nil, err @@ -907,9 +950,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err } """ } else { - return """func (cli *ZSClient) ${clzName}(uuid string, params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, uuid string, params param.${clzName}Param) (*view.${viewStructName}, error) { \tresp := view.${viewStructName}{} -\tif err := cli.PutWithRespKey("${cleanPath}", uuid, "", params, &resp); err != nil { +\tif err := cli.PutWithRespKey(ctx, "${cleanPath}", uuid, "", params, &resp); err != nil { \t\treturn nil, err \t} \treturn &resp, nil @@ -925,9 +968,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err String params = placeholders.collect { "${toSafeGoParamName(it)} string" }.join(", ") String spec = buildSpecPath(remainingPlaceholders) - return """func (cli *ZSClient) ${clzName}(${params}, params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}, params param.${clzName}Param) (*view.${viewStructName}, error) { \tresp := view.${viewStructName}{} -\terr := cli.PutWithSpec("${cleanPath}", ${firstParam}, ${spec}, "", params, &resp) +\terr := cli.PutWithSpec(ctx, "${cleanPath}", ${firstParam}, ${spec}, "", params, &resp) \tif err != nil { \t\treturn nil, err \t} @@ -948,8 +991,8 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err if (!useSpec) { // Single or no placeholder: use the standard Delete method - return """func (cli *ZSClient) ${clzName}(uuid string, deleteMode param.DeleteMode) error { -\treturn cli.Delete("${cleanPath}", uuid, string(deleteMode)) + return """func (cli *ZSClient) ${clzName}(ctx context.Context, uuid string, deleteMode param.DeleteMode) error { +\treturn cli.Delete(ctx, "${cleanPath}", uuid, string(deleteMode)) } """ } else { @@ -961,8 +1004,8 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err String spec = buildSpecPath(remainingPlaceholders) String paramsStr = "fmt.Sprintf(\"deleteMode=%s\", deleteMode)" - return """func (cli *ZSClient) ${clzName}(${params}, deleteMode param.DeleteMode) error { -\treturn cli.DeleteWithSpec("${cleanPath}", ${firstParam}, ${spec}, ${paramsStr}, nil) + return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}, deleteMode param.DeleteMode) error { +\treturn cli.DeleteWithSpec(ctx, "${cleanPath}", ${firstParam}, ${spec}, ${paramsStr}, nil) } """ } @@ -985,11 +1028,11 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err // Build parameter key, for example expungeImage String paramKey = clzName.substring(0, 1).toLowerCase() + clzName.substring(1) - return """func (cli *ZSClient) ${clzName}(uuid string) error { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, uuid string) error { \tparams := map[string]interface{}{ \t\t"${paramKey}": map[string]interface{}{}, \t} -\treturn cli.Put("${cleanPath}", uuid, params, nil) +\treturn cli.Put(ctx, "${cleanPath}", uuid, params, nil) } """ } @@ -1025,9 +1068,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err switch (httpMethod) { case "GET": if (!useSpec) { - return """func (cli *ZSClient) ${clzName}(params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, params param.${clzName}Param) (*view.${viewStructName}, error) { \tvar resp ${respType} -\tif err := cli.Get("${cleanPath}", "", params, &resp); err != nil { +\tif err := cli.Get(ctx, "${cleanPath}", "", params, &resp); err != nil { \t\treturn nil, err \t} \t${returnStmt} @@ -1036,9 +1079,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err } else { String params = placeholders.collect { "${toSafeGoParamName(it)} string" }.join(", ") String pathSpec = buildPathSpec(placeholders) - return """func (cli *ZSClient) ${clzName}(${params}, params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}, params param.${clzName}Param) (*view.${viewStructName}, error) { \tvar resp ${respType} -\terr := cli.GetWithSpec("${cleanPath}", ${pathSpec}, "", "", params, &resp) +\terr := cli.GetWithSpec(ctx, "${cleanPath}", ${pathSpec}, "", "${allTo}", params, &resp) \tif err != nil { \t\treturn nil, err \t} @@ -1048,9 +1091,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err } case "POST": if (!useSpec) { - return """func (cli *ZSClient) ${clzName}(params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, params param.${clzName}Param) (*view.${viewStructName}, error) { \t${respDecl} -\tif err := cli.Post("${cleanPath}", params, &resp); err != nil { +\tif err := cli.Post(ctx, "${cleanPath}", params, &resp); err != nil { \t\treturn nil, err \t} \t${returnStmt} @@ -1060,9 +1103,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err // POST lacks *WithSpec helpers; build the full URL manually String params = placeholders.collect { "${toSafeGoParamName(it)} string" }.join(", ") String fullPath = buildFullPath(placeholders) - return """func (cli *ZSClient) ${clzName}(${params}, params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}, params param.${clzName}Param) (*view.${viewStructName}, error) { \t${respDecl} -\terr := cli.Post(${fullPath}, params, &resp) +\terr := cli.Post(ctx, ${fullPath}, params, &resp) \tif err != nil { \t\treturn nil, err \t} @@ -1087,7 +1130,7 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err """("${cleanPath}", ${paramName}, "", map[string]interface{}{ \t\t"${actionKey}": map[string]interface{}{}, \t}, &resp)""" - return """func (cli *ZSClient) ${clzName}(${paramName} string) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${paramName} string) (*view.${viewStructName}, error) { \t${respDecl} \tif err := ${putMethod}${putArgs}; err != nil { \t\treturn nil, err @@ -1100,7 +1143,7 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err String putArgs = unwrap ? """("${cleanPath}", ${paramName}, map[string]interface{}{}, &resp)""" : """("${cleanPath}", ${paramName}, "", map[string]interface{}{}, &resp)""" - return """func (cli *ZSClient) ${clzName}(${paramName} string) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${paramName} string) (*view.${viewStructName}, error) { \t${respDecl} \tif err := ${putMethod}${putArgs}; err != nil { \t\treturn nil, err @@ -1119,7 +1162,7 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err """("${cleanPath}", ${paramName}, "", map[string]interface{}{ \t\t"${actionKey}": params.Params, \t}, &resp)""" - return """func (cli *ZSClient) ${clzName}(${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) { \t${respDecl} \tif err := ${putMethod}${putArgs}; err != nil { \t\treturn nil, err @@ -1132,7 +1175,7 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err String putArgs = unwrap ? """("${cleanPath}", ${paramName}, params, &resp)""" : """("${cleanPath}", ${paramName}, "", params, &resp)""" - return """func (cli *ZSClient) ${clzName}(${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${paramName} string, params param.${clzName}Param) (*view.${viewStructName}, error) { \t${respDecl} \tif err := ${putMethod}${putArgs}; err != nil { \t\treturn nil, err @@ -1154,7 +1197,7 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err """("${cleanPath}", "", "", map[string]interface{}{ \t\t"${actionKey}": map[string]interface{}{}, \t}, &resp)""" - return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context) (*view.${viewStructName}, error) { \t${respDecl} \tif err := ${putMethod}${putArgs}; err != nil { \t\treturn nil, err @@ -1167,7 +1210,7 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err String putArgs = unwrap ? """("${cleanPath}", "", map[string]interface{}{}, &resp)""" : """("${cleanPath}", "", "", map[string]interface{}{}, &resp)""" - return """func (cli *ZSClient) ${clzName}() (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context) (*view.${viewStructName}, error) { \t${respDecl} \tif err := ${putMethod}${putArgs}; err != nil { \t\treturn nil, err @@ -1185,7 +1228,7 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err """("${cleanPath}", "", "", map[string]interface{}{ \t\t"${actionKey}": params.Params, \t}, &resp)""" - return """func (cli *ZSClient) ${clzName}(params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, params param.${clzName}Param) (*view.${viewStructName}, error) { \t${respDecl} \tif err := ${putMethod}${putArgs}; err != nil { \t\treturn nil, err @@ -1198,7 +1241,7 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err String putArgs = unwrap ? """("${cleanPath}", uuid, params, &resp)""" : """("${cleanPath}", uuid, "", params, &resp)""" - return """func (cli *ZSClient) ${clzName}(uuid string, params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, uuid string, params param.${clzName}Param) (*view.${viewStructName}, error) { \t${respDecl} \tif err := ${putMethod}${putArgs}; err != nil { \t\treturn nil, err @@ -1215,9 +1258,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err String params = placeholders.collect { "${toSafeGoParamName(it)} string" }.join(", ") String spec = buildSpecPath(remainingPlaceholders) - return """func (cli *ZSClient) ${clzName}(${params}, params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}, params param.${clzName}Param) (*view.${viewStructName}, error) { \t${respDecl} -\terr := cli.PutWithSpec("${cleanPath}", ${firstParam}, ${spec}, "", params, &resp) +\terr := cli.PutWithSpec(ctx, "${cleanPath}", ${firstParam}, ${spec}, "", params, &resp) \tif err != nil { \t\treturn nil, err \t} @@ -1227,8 +1270,8 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err } case "DELETE": if (!useSpec) { - return """func (cli *ZSClient) ${clzName}(uuid string, deleteMode param.DeleteMode) error { -\treturn cli.Delete("${cleanPath}", uuid, string(deleteMode)) + return """func (cli *ZSClient) ${clzName}(ctx context.Context, uuid string, deleteMode param.DeleteMode) error { +\treturn cli.Delete(ctx, "${cleanPath}", uuid, string(deleteMode)) } """ } else { @@ -1239,16 +1282,16 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err String spec = buildSpecPath(remainingPlaceholders) String paramsStr = "fmt.Sprintf(\"deleteMode=%s\", deleteMode)" - return """func (cli *ZSClient) ${clzName}(${params}, deleteMode param.DeleteMode) error { - return cli.DeleteWithSpec("${cleanPath}", ${firstParam}, ${spec}, ${paramsStr}, nil) + return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}, deleteMode param.DeleteMode) error { + return cli.DeleteWithSpec(ctx, "${cleanPath}", ${firstParam}, ${spec}, ${paramsStr}, nil) } """ } default: if (!useSpec) { - return """func (cli *ZSClient) ${clzName}(params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, params param.${clzName}Param) (*view.${viewStructName}, error) { \t${respDecl} -\tif err := cli.Post("${cleanPath}", params, &resp); err != nil { +\tif err := cli.Post(ctx, "${cleanPath}", params, &resp); err != nil { \t\treturn nil, err \t} \t${returnStmt} @@ -1258,9 +1301,9 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err // POST lacks *WithSpec helpers; build the full URL manually String params = placeholders.collect { "${toSafeGoParamName(it)} string" }.join(", ") String fullPath = buildFullPath(placeholders) - return """func (cli *ZSClient) ${clzName}(${params}, params param.${clzName}Param) (*view.${viewStructName}, error) { + return """func (cli *ZSClient) ${clzName}(ctx context.Context, ${params}, params param.${clzName}Param) (*view.${viewStructName}, error) { \t${respDecl} -\terr := cli.Post(${fullPath}, params, &resp) +\terr := cli.Post(ctx, ${fullPath}, params, &resp) \tif err != nil { \t\treturn nil, err \t} @@ -1291,7 +1334,7 @@ func (cli *ZSClient) ${getMethodName}(uuid string) (*view.${viewStructName}, err def builder = new StringBuilder() builder.append("\n// ${asyncMethodName} Async\n") - builder.append("func (cli *ZSClient) ${asyncMethodName}(params param.${clzName}Param) (string, error) {\n") + builder.append("func (cli *ZSClient) ${asyncMethodName}(ctx context.Context, params param.${clzName}Param) (string, error) {\n") builder.append("\n") builder.append("\tresource := \"${resource}\"\n") builder.append("\tresponseKey := \"\"\n") diff --git a/rest/src/main/resources/scripts/GoInventory.groovy b/rest/src/main/resources/scripts/GoInventory.groovy index c31e44149c6..b38ae30a412 100644 --- a/rest/src/main/resources/scripts/GoInventory.groovy +++ b/rest/src/main/resources/scripts/GoInventory.groovy @@ -106,6 +106,27 @@ class GoInventory implements SdkTemplate { return longJobMappings } + /** + * Reset all static and instance state for clean re-generation. + * Must be called before each generate() to avoid stale caches + * causing skipped or duplicate output across repeated runs. + */ + void reset() { + longJobMappings.clear() + allApiTemplates.clear() + inventories.clear() + markedInventories.clear() + additionalClasses.clear() + generatedViewStructs.clear() + generatedViewFiles.clear() + paramNestedTypes.clear() + generatedParamStructs.clear() + generatedClientMethods.clear() + generatingForParam = false + currentGeneratingClass = null + logger.warn("[GoSDK] Reset GoInventory state (static + instance)") + } + /** * Pre-analyze all API classes once and cache metadata. * This avoids expensive re-instantiation of GoApiTemplate and redundant logging. @@ -124,7 +145,7 @@ class GoInventory implements SdkTemplate { try { GoApiTemplate template = new GoApiTemplate(apiClass, this) // If it's a valid template (has @RestRequest) - if (template.at != null) { + if (template.isValid()) { allApiTemplates.add(template) } } catch (Throwable e) { @@ -160,6 +181,9 @@ class GoInventory implements SdkTemplate { List generate() { def files = [] + GoApiTemplate.reset() + reset() + logger.warn("[GoSDK] ===== GoInventory.generate() START =====") logger.warn("[GoSDK] GoInventory.generate() starting...") @@ -296,7 +320,7 @@ class GoInventory implements SdkTemplate { content.append("// Copyright (c) ZStack.io, Inc.\n\n") content.append("package view\n\n") content.append("import \"time\"\n\n") - content.append("var _ = time.Now // avoid unused import\n\n") + content.append("var _ = time.Now() // avoid unused import\n\n") classes.each { Class clz -> String structName = getViewStructName(clz) @@ -314,401 +338,6 @@ class GoInventory implements SdkTemplate { return files } - /** - * Generate ZSClient base file - * @deprecated client.go is manually maintained, this method should not be used - */ - @Deprecated - private SdkFile generateClientFile() { - def sdkFile = new SdkFile() - sdkFile.subPath = "/pkg/client/" - sdkFile.fileName = "client.go" - - def content = new StringBuilder() - content.append("// Copyright (c) ZStack.io, Inc.\n\n") - content.append("package client\n\n") - content.append("import (\n") - content.append("\t\"bytes\"\n") - content.append("\t\"crypto/sha512\"\n") - content.append("\t\"encoding/hex\"\n") - content.append("\t\"encoding/json\"\n") - content.append("\t\"fmt\"\n") - content.append("\t\"io\"\n") - content.append("\t\"net/http\"\n") - content.append("\t\"net/url\"\n") - content.append("\t\"strconv\"\n") - content.append("\t\"strings\"\n") - content.append("\t\"github.com/zstackio/zstack-sdk-go-v2/pkg/param\"\n") - content.append("\t\"time\"\n") - content.append(")\n\n") - content.append("// AuthType authentication type\n") - content.append("type AuthType string\n\n") - content.append("const (\n") - content.append("\tAuthTypeAccessKey AuthType = \"accesskey\"\n") - content.append("\tAuthTypeLogin AuthType = \"login\"\n") - content.append(")\n\n") - content.append("const (\n") - content.append("\tdefaultZStackPort = 8080\n") - content.append(")\n\n") - content.append("// ZSConfig client configuration\n") - content.append("type ZSConfig struct {\n") - content.append("\thostname string\n") - content.append("\tport int\n") - content.append("\tcontextPath string\n") - content.append("\taccessKeyId string\n") - content.append("\taccessKeySecret string\n") - content.append("\tusername string\n") - content.append("\tpassword string\n") - content.append("\tauthType AuthType\n") - content.append("\tdebug bool\n") - content.append("\ttimeout time.Duration\n") - content.append("}\n\n") - content.append("// NewZSConfig creates a new configuration\n") - content.append("func NewZSConfig(hostname string, port int, contextPath string) *ZSConfig {\n") - content.append("\treturn &ZSConfig{\n") - content.append("\t\thostname: hostname,\n") - content.append("\t\tport: port,\n") - content.append("\t\tcontextPath: contextPath,\n") - content.append("\t\ttimeout: 30 * time.Second,\n") - content.append("\t}\n") - content.append("}\n\n") - content.append("// DefaultZSConfig creates a default configuration\n") - content.append("func DefaultZSConfig(hostname, contextPath string) *ZSConfig {\n") - content.append("\treturn NewZSConfig(hostname, defaultZStackPort, contextPath)\n") - content.append("}\n\n") - content.append("// AccessKey sets access key authentication\n") - content.append("func (config *ZSConfig) AccessKey(id, secret string) *ZSConfig {\n") - content.append("\tconfig.accessKeyId = id\n") - content.append("\tconfig.accessKeySecret = secret\n") - content.append("\tconfig.authType = AuthTypeAccessKey\n") - content.append("\treturn config\n") - content.append("}\n\n") - content.append("// Login sets login authentication\n") - content.append("func (config *ZSConfig) Login(username, password string) *ZSConfig {\n") - content.append("\tconfig.username = username\n") - content.append("\tconfig.password = password\n") - content.append("\tconfig.authType = AuthTypeLogin\n") - content.append("\treturn config\n") - content.append("}\n\n") - content.append("// Debug enables debug mode\n") - content.append("func (config *ZSConfig) Debug(debug bool) *ZSConfig {\n") - content.append("\tconfig.debug = debug\n") - content.append("\treturn config\n") - content.append("}\n\n") - content.append("// ZSClient ZStack API client\n") - content.append("type ZSClient struct {\n") - content.append("\tconfig *ZSConfig\n") - content.append("\thttpClient *http.Client\n") - content.append("\tsessionId string\n") - content.append("}\n\n") - content.append("// JobView job inventory view\n") - content.append("type JobView struct {\n") - content.append("\tUUID string `json:\"uuid\"`\n") - content.append("\tState string `json:\"state\"`\n") - content.append("\tResult interface{} `json:\"result,omitempty\"`\n") - content.append("\tError interface{} `json:\"error,omitempty\"`\n") - content.append("\tCreateDate string `json:\"createDate\"`\n") - content.append("}\n\n") - content.append("const (\n") - content.append("\tJobStateProcessing = \"Processing\"\n") - content.append("\tJobStateSucceeded = \"Succeeded\"\n") - content.append("\tJobStateFailed = \"Failed\"\n") - content.append(")\n\n") - content.append("// NewZSClient creates a new ZStack client\n") - content.append("func NewZSClient(config *ZSConfig) *ZSClient {\n") - content.append("\t// Auto-encrypt password for login authentication\n") - content.append("\tif config.authType == AuthTypeLogin && config.password != \"\" {\n") - content.append("\t\tconfig.password = hashPasswordSHA512(config.password)\n") - content.append("\t\tif config.debug {\n") - content.append("\t\t\tfmt.Printf(\"[DEBUG] Password hashed: %s...\\n\", config.password[:16])\n") - content.append("\t\t}\n") - content.append("\t}\n") - content.append("\treturn &ZSClient{\n") - content.append("\t\tconfig: config,\n") - content.append("\t\thttpClient: &http.Client{\n") - content.append("\t\t\tTimeout: config.timeout,\n") - content.append("\t\t},\n") - content.append("\t}\n") - content.append("}\n\n") - content.append("// hashPasswordSHA512 encrypts password using SHA512\n") - content.append("func hashPasswordSHA512(password string) string {\n") - content.append("\thash := sha512.Sum512([]byte(password))\n") - content.append("\treturn hex.EncodeToString(hash[:])\n") - content.append("}\n\n") - content.append("func (cli *ZSClient) baseURL() string {\n") - content.append("\treturn fmt.Sprintf(\"http://%s:%d%s\", cli.config.hostname, cli.config.port, cli.config.contextPath)\n") - content.append("}\n\n") - content.append("// Get performs a GET request\n") - content.append("func (cli *ZSClient) Get(path string, uuid string, params interface{}, result interface{}) error {\n") - content.append("\turl := fmt.Sprintf(\"%s/%s\", cli.baseURL(), path)\n") - content.append("\tif uuid != \"\" {\n") - content.append("\t\turl = fmt.Sprintf(\"%s/%s\", url, uuid)\n") - content.append("\t}\n") - content.append("\treturn cli.doRequest(\"GET\", url, nil, result)\n") - content.append("}\n\n") - content.append("func (cli *ZSClient) QueryJob(uuid string) (*JobView, error) {\n") - content.append("\tvar resp JobView\n") - content.append("\turl := fmt.Sprintf(\"%s/v1/api-jobs/%s\", cli.baseURL(), uuid)\n") - content.append("\terr := cli.doRequest(\"GET\", url, nil, &resp)\n") - content.append("\treturn &resp, err\n") - content.append("}\n\n") - content.append("// List performs a list query\n") - content.append("func (cli *ZSClient) List(path string, params interface{}, result interface{}) error {\n") - content.append("\tbaseURL := cli.baseURL()\n") - content.append("\trequestURL := fmt.Sprintf(\"%s/%s\", baseURL, path)\n") - content.append("\n") - content.append("\tif params != nil {\n") - content.append("\t\tif queryParam, ok := params.(*param.QueryParam); ok {\n") - content.append("\t\t\tqueryString := cli.buildQueryString(queryParam)\n") - content.append("\t\t\tif queryString != \"\" {\n") - content.append("\t\t\t\trequestURL = fmt.Sprintf(\"%s?%s\", requestURL, queryString)\n") - content.append("\t\t\t}\n") - content.append("\t\t}\n") - content.append("\t}\n") - content.append("\n") - content.append("\t// Unmarshal response into wrapper with inventories field\n") - content.append("\tvar wrapper struct {\n") - content.append("\t\tInventories interface{} `json:\"inventories\"`\n") - content.append("\t\tInventory interface{} `json:\"inventory\"`\n") - content.append("\t}\n") - content.append("\n") - content.append("\tif err := cli.doRequest(\"GET\", requestURL, nil, &wrapper); err != nil {\n") - content.append("\t\treturn err\n") - content.append("\t}\n") - content.append("\n") - content.append("\t// Try inventories first (plural), then inventory (singular)\n") - content.append("\tvar data interface{}\n") - content.append("\tif wrapper.Inventories != nil {\n") - content.append("\t\tdata = wrapper.Inventories\n") - content.append("\t} else if wrapper.Inventory != nil {\n") - content.append("\t\tdata = wrapper.Inventory\n") - content.append("\t}\n") - content.append("\n") - content.append("\t// Re-marshal and unmarshal into the actual result type\n") - content.append("\tif data != nil {\n") - content.append("\t\tdataBytes, err := json.Marshal(data)\n") - content.append("\t\tif err != nil {\n") - content.append("\t\t\treturn fmt.Errorf(\"failed to marshal data: %v\", err)\n") - content.append("\t\t}\n") - content.append("\t\tif cli.config.debug {\n") - content.append("\t\t\tfmt.Printf(\"[DEBUG] Received %d bytes of inventory data\\n\", len(dataBytes))\n") - content.append("\t\t}\n") - content.append("\t\terr = json.Unmarshal(dataBytes, result)\n") - content.append("\t\tif err != nil {\n") - content.append("\t\t\treturn fmt.Errorf(\"failed to unmarshal data into result: %v\", err)\n") - content.append("\t\t}\n") - content.append("\t\treturn nil\n") - content.append("\t}\n") - content.append("\tif cli.config.debug {\n") - content.append("\t\tfmt.Println(\"[DEBUG] Both inventories and inventory are nil, returning empty result\")\n") - content.append("\t}\n") - content.append("\treturn nil\n") - content.append("}\n\n") - content.append("// Post performs a POST request\n") - content.append("func (cli *ZSClient) Post(path string, params interface{}, result interface{}) error {\n") - content.append("\turl := fmt.Sprintf(\"%s/%s\", cli.baseURL(), path)\n") - content.append("\treturn cli.doRequest(\"POST\", url, params, result)\n") - content.append("}\n\n") - content.append("// Put performs a PUT request\n") - content.append("func (cli *ZSClient) Put(path string, uuid string, params interface{}, result interface{}) error {\n") - content.append("\turl := fmt.Sprintf(\"%s/%s/%s\", cli.baseURL(), path, uuid)\n") - content.append("\treturn cli.doRequest(\"PUT\", url, params, result)\n") - content.append("}\n\n") - content.append("// Delete performs a DELETE request\n") - content.append("func (cli *ZSClient) Delete(path string, uuid string, deleteMode string) error {\n") - content.append("\turl := fmt.Sprintf(\"%s/%s/%s?deleteMode=%s\", cli.baseURL(), path, uuid, deleteMode)\n") - content.append("\treturn cli.doRequest(\"DELETE\", url, nil, nil)\n") - content.append("}\n\n") - content.append("func (cli *ZSClient) doRequest(method, url string, body interface{}, result interface{}) error {\n") - content.append("\t// Auto-login if using login auth and no session yet\n") - content.append("\tif cli.config.authType == AuthTypeLogin && cli.sessionId == \"\" && !strings.HasSuffix(url, \"/accounts/login\") {\n") - content.append("\t\terr := cli.Login(cli.config.username, cli.config.password)\n") - content.append("\t\tif err != nil {\n") - content.append("\t\t\treturn fmt.Errorf(\"auto-login failed: %v\", err)\n") - content.append("\t\t}\n") - content.append("\t}\n\n") - content.append("\tvar bodyReader io.Reader\n") - content.append("\tvar bodyBytes []byte\n") - content.append("\tif body != nil {\n") - content.append("\t\tvar err error\n") - content.append("\t\tbodyBytes, err = json.Marshal(body)\n") - content.append("\t\tif err != nil {\n") - content.append("\t\t\treturn err\n") - content.append("\t\t}\n") - content.append("\t\tbodyReader = bytes.NewBuffer(bodyBytes)\n") - content.append("\t}\n\n") - content.append("\treq, err := http.NewRequest(method, url, bodyReader)\n") - content.append("\tif err != nil {\n") - content.append("\t\treturn err\n") - content.append("\t}\n\n") - content.append("\treq.Header.Set(\"Content-Type\", \"application/json\")\n") - content.append("\tcli.addAuthHeaders(req)\n\n") - content.append("\tif cli.config.debug && bodyBytes != nil {\n") - content.append("\t\tfmt.Printf(\"[DEBUG] %s %s\\n\", method, url)\n") - content.append("\t\tfmt.Printf(\"[DEBUG] Body: %s\\n\", string(bodyBytes))\n") - content.append("\t\tfmt.Printf(\"[DEBUG] Headers: Authorization=%s\\n\", req.Header.Get(\"Authorization\"))\n") - content.append("\t}\n\n") - content.append("\tresp, err := cli.httpClient.Do(req)\n") - content.append("\tif err != nil {\n") - content.append("\t\treturn err\n") - content.append("\t}\n") - content.append("\tdefer resp.Body.Close()\n\n") - content.append("\tif resp.StatusCode == 202 {\n") - content.append("\t\tvar location struct {\n") - content.append("\t\t\tLocation string `json:\"location\"`\n") - content.append("\t\t\tUuid string `json:\"org.zstack.header.rest.APIEvent/uuid\"`\n") - content.append("\t\t}\n") - content.append("\t\tif err := json.NewDecoder(resp.Body).Decode(&location); err != nil {\n") - content.append("\t\t\treturn fmt.Errorf(\"failed to decode 202 response: %v\", err)\n") - content.append("\t\t}\n") - content.append("\t\tjobUUID := location.Uuid\n") - content.append("\t\tif jobUUID == \"\" {\n") - content.append("\t\t\tparts := bytes.Split([]byte(location.Location), []byte(\"/\"))\n") - content.append("\t\t\tif len(parts) > 0 {\n") - content.append("\t\t\t\tjobUUID = string(parts[len(parts)-1])\n") - content.append("\t\t\t}\n") - content.append("\t\t}\n") - content.append("\n") - content.append("\t\tif jobUUID == \"\" {\n") - content.append("\t\t\treturn fmt.Errorf(\"failed to extract job uuid from 202 response\")\n") - content.append("\t\t}\n") - content.append("\n") - content.append("\t\treturn cli.waitForJob(jobUUID, result)\n") - content.append("\t}\n\n") - content.append("\tif resp.StatusCode >= 400 {\n") - content.append("\t\trespBody, _ := io.ReadAll(resp.Body)\n") - content.append("\t\terrMsg := fmt.Sprintf(\"API error: %s %s returned status code %d\\n\", method, url, resp.StatusCode)\n") - content.append("\t\terrMsg += fmt.Sprintf(\"Authorization: %s\\n\", req.Header.Get(\"Authorization\"))\n") - content.append("\t\terrMsg += fmt.Sprintf(\"Response: %s\", string(respBody))\n") - content.append("\t\treturn fmt.Errorf(errMsg)\n") - content.append("\t}\n\n") - content.append("\tif result != nil {\n") - content.append("\t\treturn json.NewDecoder(resp.Body).Decode(result)\n") - content.append("\t}\n") - content.append("\treturn nil\n") - content.append("}\n\n") - content.append("func (cli *ZSClient) waitForJob(jobUUID string, result interface{}) error {\n") - content.append("\tticker := time.NewTicker(500 * time.Millisecond)\n") - content.append("\tdefer ticker.Stop()\n") - content.append("\n") - content.append("\ttimeout := time.After(30 * time.Minute)\n") - content.append("\n") - content.append("\tfor {\n") - content.append("\t\tselect {\n") - content.append("\t\tcase <-timeout:\n") - content.append("\t\t\treturn fmt.Errorf(\"job %s timeout\", jobUUID)\n") - content.append("\t\tcase <-ticker.C:\n") - content.append("\t\t\tjob, err := cli.QueryJob(jobUUID)\n") - content.append("\t\t\tif err != nil {\n") - content.append("\t\t\t\tcontinue\n") - content.append("\t\t\t}\n") - content.append("\n") - content.append("\t\t\tif job.State == JobStateSucceeded {\n") - content.append("\t\t\t\tif result != nil && job.Result != nil {\n") - content.append("\t\t\t\t\tdata, err := json.Marshal(job.Result)\n") - content.append("\t\t\t\t\tif err != nil {\n") - content.append("\t\t\t\t\t\treturn fmt.Errorf(\"failed to marshal job result: %v\", err)\n") - content.append("\t\t\t\t\t}\n") - content.append("\t\t\t\t\treturn json.Unmarshal(data, result)\n") - content.append("\t\t\t\t}\n") - content.append("\t\t\t\treturn nil\n") - content.append("\t\t\t}\n") - content.append("\n") - content.append("\t\t\tif job.State == JobStateFailed {\n") - content.append("\t\t\t\treturn fmt.Errorf(\"job failed: %v\", job.Error)\n") - content.append("\t\t\t}\n") - content.append("\t\t}\n") - content.append("\t}\n") - content.append("}\n\n") - content.append("func (cli *ZSClient) buildQueryString(params *param.QueryParam) string {\n") - content.append("\tif params == nil {\n") - content.append("\t\treturn \"\"\n") - content.append("\t}\n") - content.append("\tu := url.Values{}\n") - content.append("\n") - content.append("\tfor _, q := range params.Conditions {\n") - content.append("\t\tif q.Name != \"\" && q.Op != \"\" {\n") - content.append("\t\t\tu.Add(\"q\", fmt.Sprintf(\"%s%s%s\", q.Name, q.Op, q.Value))\n") - content.append("\t\t} else if q.Value != \"\" {\n") - content.append("\t\t\tu.Add(\"q\", q.Value)\n") - content.append("\t\t}\n") - content.append("\t}\n") - content.append("\n") - content.append("\tif params.LimitNum != nil {\n") - content.append("\t\tu.Set(\"limit\", strconv.Itoa(*params.LimitNum))\n") - content.append("\t}\n") - content.append("\tif params.StartNum != nil {\n") - content.append("\t\tu.Set(\"start\", strconv.Itoa(*params.StartNum))\n") - content.append("\t}\n") - content.append("\tif params.Count {\n") - content.append("\t\tu.Set(\"count\", \"true\")\n") - content.append("\t}\n") - content.append("\tif params.ReplyWithCount {\n") - content.append("\t\tu.Set(\"replyWithCount\", \"true\")\n") - content.append("\t}\n") - content.append("\tif params.GroupBy != \"\" {\n") - content.append("\t\tu.Set(\"groupBy\", params.GroupBy)\n") - content.append("\t}\n") - content.append("\tif params.SortBy != \"\" {\n") - content.append("\t\tu.Set(\"sortBy\", params.SortBy)\n") - content.append("\t}\n") - content.append("\tif params.SortDirection != \"\" {\n") - content.append("\t\tu.Set(\"sortDirection\", params.SortDirection)\n") - content.append("\t}\n") - content.append("\tfor _, f := range params.Fields {\n") - content.append("\t\tu.Add(\"fields\", f)\n") - content.append("\t}\n") - content.append("\n") - content.append("\treturn u.Encode()\n") - content.append("}\n\n") - content.append("func (cli *ZSClient) addAuthHeaders(req *http.Request) {\n") - content.append("\tif cli.config.authType == AuthTypeAccessKey {\n") - content.append("\t\treq.Header.Set(\"X-Access-Key-Id\", cli.config.accessKeyId)\n") - content.append("\t\treq.Header.Set(\"X-Access-Key-Secret\", cli.config.accessKeySecret)\n") - content.append("\t} else if cli.sessionId != \"\" {\n") - content.append("\t\treq.Header.Set(\"Authorization\", \"OAuth \"+cli.sessionId)\n") - content.append("\t}\n") - content.append("}\n\n") - content.append("// Login authenticates with username and password\n") - content.append("func (cli *ZSClient) Login(username, password string) error {\n") - content.append("\tif cli.config.authType != AuthTypeLogin {\n") - content.append("\t\treturn fmt.Errorf(\"client is not configured for login authentication\")\n") - content.append("\t}\n\n") - content.append("\tvar loginReq = map[string]map[string]string{\n") - content.append("\t\t\"logInByAccount\": {\n") - content.append("\t\t\t\"accountName\": username,\n") - content.append("\t\t\t\"password\": password, // Already hashed in NewZSClient\n") - content.append("\t\t},\n") - content.append("\t}\n\n") - content.append("\tvar loginResp struct {\n") - content.append("\t\tInventory struct {\n") - content.append("\t\t\tUUID string `json:\"uuid\"`\n") - content.append("\t\t} `json:\"inventory\"`\n") - content.append("\t}\n\n") - content.append("\turl := fmt.Sprintf(\"%s/v1/accounts/login\", cli.baseURL())\n") - content.append("\terr := cli.doRequest(\"PUT\", url, loginReq, &loginResp)\n") - content.append("\tif err != nil {\n") - content.append("\t\treturn fmt.Errorf(\"login failed: %v\", err)\n") - content.append("\t}\n\n") - content.append("\tcli.sessionId = loginResp.Inventory.UUID\n") - content.append("\tif cli.config.debug {\n") - content.append("\t\tfmt.Printf(\"[DEBUG] Login successful, sessionId=%s\\n\", cli.sessionId)\n") - content.append("\t}\n") - content.append("\treturn nil\n") - content.append("}\n\n") - content.append("func (cli *ZSClient) Logout() error {\n") - content.append("\tif cli.sessionId == \"\" {\n") - content.append("\t\treturn nil\n") - content.append("\t}\n\n") - content.append("\turl := fmt.Sprintf(\"%s/v1/accounts/sessions/%s\", cli.baseURL(), cli.sessionId)\n") - content.append("\terr := cli.doRequest(\"DELETE\", url, nil, nil)\n") - content.append("\tcli.sessionId = \"\"\n") - content.append("\treturn err\n") - content.append("}\n") - - sdkFile.content = content.toString() - return sdkFile - } /** * Generate base view file @@ -1760,7 +1389,7 @@ class GoInventory implements SdkTemplate { content.append("// Copyright (c) ZStack.io, Inc.\n\n") content.append("package view\n\n") content.append("import \"time\"\n\n") - content.append("var _ = time.Now // avoid unused import\n\n") + content.append("var _ = time.Now() // avoid unused import\n\n") int addedCount = 0 Set processedViews = new HashSet<>() @@ -1912,7 +1541,7 @@ class GoInventory implements SdkTemplate { content.append("// Copyright (c) ZStack.io, Inc.\n\n") content.append("package view\n\n") content.append("import \"time\"\n\n") - content.append("var _ = time.Now // avoid unused import\n\n") + content.append("var _ = time.Now() // avoid unused import\n\n") logger.warn("[GoSDK] Generating new view file for: ${structName} (${fileName})") } @@ -2086,7 +1715,7 @@ class GoInventory implements SdkTemplate { content.append("// Copyright (c) ZStack.io, Inc.\n\n") content.append("package param\n\n") content.append("import \"time\"\n\n") - content.append("var _ = time.Now // avoid unused import\n\n") + content.append("var _ = time.Now() // avoid unused import\n\n") boolean hasParams = false allApiTemplates.each { GoApiTemplate template -> diff --git a/rest/src/main/resources/scripts/templates/base_param_types.go.template b/rest/src/main/resources/scripts/templates/base_param_types.go.template index 962ccda910b..1c3e8ec9ffa 100644 --- a/rest/src/main/resources/scripts/templates/base_param_types.go.template +++ b/rest/src/main/resources/scripts/templates/base_param_types.go.template @@ -4,7 +4,7 @@ package param import "time" -var _ = time.Now // avoid unused import +var _ = time.Now() // avoid unused import type DeleteMode string