This document defines the development standards and coding conventions for the ZStack SDK Go project
zstack-sdk-go/
├── pkg/
│ ├── client/ # API client and action methods
│ ├── param/ # Request parameter structs
│ ├── view/ # Response view structs
│ ├── errors/ # Error definitions and handling
│ ├── util/ # Common utility packages
│ │ ├── jsonutils/ # JSON utilities
│ │ ├── httputils/ # HTTP utilities
│ │ └── ... # Other utilities
│ └── test/ # Integration tests
├── go.mod
├── go.sum
└── README.md
| Package | Responsibility | Naming Convention |
|---|---|---|
client |
API action method implementations | {resource}_actions.go |
param |
Request parameter definitions | {resource}_params.go |
view |
Response data structures | {resource}_views.go |
errors |
Error type definitions | errors.go, consts.go |
util |
Common utility functions | Organized by sub-packages |
All Go files must include a copyright notice:
// Copyright (c) ZStack.io, Inc.
package packagenameOrganize imports in the following order, separated by blank lines:
import (
// 1. Standard library
"context"
"fmt"
"net/http"
// 2. Third-party packages
"github.com/kataras/golog"
// 3. Internal project packages
"github.com/zstackio/zstack-sdk-go-v2/pkg/errors"
"github.com/zstackio/zstack-sdk-go-v2/pkg/param"
"github.com/zstackio/zstack-sdk-go-v2/pkg/view"
)| Type | Format | Example |
|---|---|---|
| Actions | {resource}_actions.go |
vm_instance_actions.go |
| Params | {resource}_params.go |
vm_instance_params.go |
| Views | {resource}_views.go |
vm_instance_views.go |
| Tests | {resource}_test.go |
vm_instance_test.go |
// Parameter structs: {Action}{Resource}Param
type CreateVmInstanceParam struct { ... }
type UpdateVmInstanceParam struct { ... }
// Detail parameters: {Action}{Resource}DetailParam
type CreateVmInstanceDetailParam struct { ... }
// View structs: {Resource}InventoryView or {Resource}View
type VmInstanceInventoryView struct { ... }
type VMConsoleAddressView struct { ... }
// Type aliases for enums
type DeleteMode string
type InstanceType string// CRUD operations
func (cli *ZSClient) Create{Resource}(params) (*View, error)
func (cli *ZSClient) Query{Resource}(params) ([]View, error)
func (cli *ZSClient) Get{Resource}(uuid) (*View, error)
func (cli *ZSClient) Update{Resource}(uuid, params) (*View, error)
func (cli *ZSClient) Destroy{Resource}(uuid, deleteMode) error
func (cli *ZSClient) Delete{Resource}(uuid, deleteMode) error
// Specific operations
func (cli *ZSClient) Start{Resource}(uuid, params) (*View, error)
func (cli *ZSClient) Stop{Resource}(uuid, params) (*View, error)
func (cli *ZSClient) Attach{A}To{B}(aUUID, bUUID) (*View, error)
func (cli *ZSClient) Detach{A}From{B}(aUUID, bUUID) (*View, error)// Use type aliases for enums
type InstanceType string
const (
UserVm InstanceType = "UserVm"
ApplianceVm InstanceType = "ApplianceVm"
)
// Error constants
const (
ErrNotFound = Error("NotFoundError")
ErrDuplicateId = Error("DuplicateIdError")
)View structs use embedding to share common fields:
// Base info view
type BaseInfoView struct {
UUID string `json:"uuid"`
Name string `json:"name"`
Description string `json:"description"`
}
// Time info view
type BaseTimeView struct {
CreateDate time.Time `json:"createDate"`
LastOpDate time.Time `json:"lastOpDate"`
}
// Resource view embeds base structs
type VmInstanceInventoryView struct {
BaseInfoView
BaseTimeView
ZoneUUID string `json:"zoneUuid"`
ClusterUUID string `json:"clusterUuid"`
// ... other fields
}// Base parameters
type BaseParam struct {
SystemTags []string `json:"systemTags,omitempty"`
UserTags []string `json:"userTags,omitempty"`
RequestIp string `json:"requestIp,omitempty"`
}
// Request param embeds base param
type CreateVmInstanceParam struct {
BaseParam
Params CreateVmInstanceDetailParam `json:"params"`
}// Config builder
func DefaultZSConfig(hostname string) *ZSConfig {
return NewZSConfig(hostname, defaultZStackPort, defaultZStackContextPath)
}
func (config *ZSConfig) AccessKey(id, secret string) *ZSConfig {
config.accessKeyId = id
config.accessKeySecret = secret
config.authType = AuthTypeAccessKey
return config
}
func (config *ZSConfig) Debug(debug bool) *ZSConfig {
config.debug = debug
return config
}
// Usage
client := client.NewZSClient(
client.DefaultZSConfig("10.0.0.1").
AccessKey("key-id", "key-secret").
Debug(true),
)
// Query parameter builder
params := param.NewQueryParam().
AddQ("name=test").
Limit(10).
Start(0)type ExampleStruct struct {
// Required fields: no omitempty
UUID string `json:"uuid"`
// Optional fields: use omitempty
Description string `json:"description,omitempty"`
// Pointer types to distinguish zero values from unset
RootDiskSize *int64 `json:"rootDiskSize"`
CpuNum *int `json:"cpuNum"`
}All exported fields must have descriptive comments:
type VmInstanceInventoryView struct {
UUID string `json:"uuid"` // Resource UUID, unique identifier
ZoneUUID string `json:"zoneUuid"` // Zone UUID
ClusterUUID string `json:"clusterUuid"` // Cluster UUID
MemorySize int64 `json:"memorySize"` // Memory size in bytes
CPUNum int `json:"cpuNum"` // Number of CPUs
}Many parameter structs use pointer types (e.g., *int, *string) to distinguish between zero values and unset values. To simplify usage for callers, the SDK provides generic helper functions in the ptr package.
ptr.Of is a generic function that converts any value to a pointer:
import "github.com/zstackio/zstack-sdk-go-v2/pkg/util/ptr"
// Before (verbose approach)
cpuNum := 4
memorySize := int64(8589934592)
name := "my-vm"
params := param.CreateVmInstanceDetailParam{
CpuNum: &cpuNum,
MemorySize: &memorySize,
Name: &name,
}
// Now (using ptr.Of, clean and concise)
params := param.CreateVmInstanceDetailParam{
CpuNum: ptr.Of(4),
MemorySize: ptr.Of(int64(8589934592)),
Name: ptr.Of("my-vm"),
Platform: ptr.Of("Linux"),
Tags: ptr.Of([]string{"tag1", "tag2"}),
}| Function | Purpose | Example |
|---|---|---|
ptr.Of(v) |
Convert value to pointer | ptr.Of(4) → *int |
ptr.ValueOr(p, default) |
Get pointer value, returns default if nil | ptr.ValueOr(p, 0) |
ptr.Value(p) |
Get pointer value, returns zero value if nil | ptr.Value(p) |
ptr.Of supports all Go types, including:
- Primitive types:
int,int64,string,bool,float64, etc. - Composite types:
[]string,map[string]int, etc. - Custom types: any struct
// Use custom error type
type Error string
func (e Error) Error() string {
return string(e)
}
// Predefined error constants
const (
ErrNotFound = Error("NotFoundError")
ErrDuplicateId = Error("DuplicateIdError")
ErrParameter = Error("ParameterError")
)import "github.com/zstackio/zstack-sdk-go-v2/pkg/errors"
// Use Wrap to add context
if err != nil {
return errors.Wrap(err, "failed to create vm instance")
}
// Use Wrapf for formatted context
if err != nil {
return errors.Wrapf(err, "failed to query %s", resource)
}func (cli *ZSClient) GetVmInstance(uuid string) (*view.VmInstanceInventoryView, error) {
var resp view.VmInstanceInventoryView
if err := cli.Get("v1/vm-instances", uuid, nil, &resp); err != nil {
return nil, err // Return error directly, let caller handle
}
return &resp, nil
}// {Description} Method description
func (cli *ZSClient) {MethodName}(params...) (*view.{ReturnType}, error) {
var resp view.{ReturnType}
if err := cli.{HttpMethod}("v1/{resource}", params, &resp); err != nil {
return nil, err
}
return &resp, nil
}// CreateVmInstance creates a VM instance
func (cli *ZSClient) CreateVmInstance(params param.CreateVmInstanceParam) (*view.VmInstanceInventoryView, error) {
resp := view.VmInstanceInventoryView{}
if err := cli.Post("v1/vm-instances", params, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// QueryVmInstance queries VM instance list
func (cli *ZSClient) QueryVmInstance(params param.QueryParam) ([]view.VmInstanceInventoryView, error) {
var resp []view.VmInstanceInventoryView
return resp, cli.List("v1/vm-instances", ¶ms, &resp)
}
// DestroyVmInstance deletes a VM instance
func (cli *ZSClient) DestroyVmInstance(uuid string, deleteMode param.DeleteMode) error {
return cli.Delete("v1/vm-instances", uuid, string(deleteMode))
}Test files are placed in the pkg/test/ directory with naming format: {resource}_test.go
func Test{MethodName}(t *testing.T) {
// Test implementation
}// Copyright (c) ZStack.io, Inc.
package test
import (
"testing"
"github.com/kataras/golog"
"github.com/zstackio/zstack-sdk-go-v2/pkg/param"
"github.com/zstackio/zstack-sdk-go-v2/pkg/util/jsonutils"
)
func TestQueryVmInstance(t *testing.T) {
data, err := accessKeyAuthCli.QueryVmInstance(param.NewQueryParam())
if err != nil {
t.Errorf("TestQueryVmInstance: %v", err)
}
golog.Info(jsonutils.Marshal(data))
}
func TestGetVmInstance(t *testing.T) {
data, err := accountLoginCli.GetVmInstance("uuid-here")
if err != nil {
t.Errorf("TestGetVmInstance: %v", err)
}
golog.Info(jsonutils.Marshal(data))
}We provide scripts to run tests and generate HTML reports using go-test-report.
Windows (PowerShell):
./scripts/run_tests_with_report.ps1Linux (Bash):
chmod +x scripts/run_tests_with_report.sh
./scripts/run_tests_with_report.shThe report will be generated as test_report.html in the root directory.
When adding support for a new ZStack resource, follow these steps:
Create pkg/view/{resource}_views.go:
// Copyright (c) ZStack.io, Inc.
package view
type {Resource}InventoryView struct {
BaseInfoView
BaseTimeView
// Resource-specific fields
Field1 string `json:"field1"` // Field description
Field2 int `json:"field2"` // Field description
}Create pkg/param/{resource}_params.go:
// Copyright (c) ZStack.io, Inc.
package param
type Create{Resource}Param struct {
BaseParam
Params Create{Resource}DetailParam `json:"params"`
}
type Create{Resource}DetailParam struct {
Name string `json:"name"` // Name
Description string `json:"description"` // Description
// Other parameters
}Create pkg/client/{resource}_actions.go:
// Copyright (c) ZStack.io, Inc.
package client
import (
"github.com/zstackio/zstack-sdk-go-v2/pkg/param"
"github.com/zstackio/zstack-sdk-go-v2/pkg/view"
)
// Create{Resource} creates a resource
func (cli *ZSClient) Create{Resource}(params param.Create{Resource}Param) (*view.{Resource}InventoryView, error) {
resp := view.{Resource}InventoryView{}
if err := cli.Post("v1/{resources}", params, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// Query{Resource} queries resource list
func (cli *ZSClient) Query{Resource}(params param.QueryParam) ([]view.{Resource}InventoryView, error) {
var resp []view.{Resource}InventoryView
return resp, cli.List("v1/{resources}", ¶ms, &resp)
}
// Get{Resource} gets a single resource
func (cli *ZSClient) Get{Resource}(uuid string) (*view.{Resource}InventoryView, error) {
var resp view.{Resource}InventoryView
if err := cli.Get("v1/{resources}", uuid, nil, &resp); err != nil {
return nil, err
}
return &resp, nil
}
// Destroy{Resource} deletes a resource
func (cli *ZSClient) Destroy{Resource}(uuid string, deleteMode param.DeleteMode) error {
return cli.Delete("v1/{resources}", uuid, string(deleteMode))
}Create integration tests in pkg/test/{resource}_test.go.
Before submitting code, ensure:
- All files include copyright notice
- Imports are organized in standard order
- All exported types and functions have comments
- Struct fields have JSON tags and comments
- Error handling correctly uses the errors package
- Naming follows project conventions
- Tests cover main functionality
- Code is formatted with
gofmt
- Go Version: 1.22.0+
- Main Dependencies:
github.com/kataras/golog- Logginggithub.com/pkg/errors- Error handlinggithub.com/fatih/color- Terminal colorsgithub.com/fatih/structs- Struct reflection
Document Version: 1.0 Last Updated: 2024-12