commit e9e5c47037c259f65f76dbe1bb798720964ae7b9
Author: muya-zj <45938993+muya-zj@users.noreply.github.com>
Date: Wed Dec 26 22:42:56 2018 +0800
[AlibabaCloud] Change alicloud to alibabacloud (#470)
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..c052d5e
--- /dev/null
+++ b/README.md
@@ -0,0 +1,57 @@
+# Alibaba Cloud ECI
+
+
+
+Alibaba Cloud ECI(Elastic Container Instance) is a service that allow you run containers without having to manage servers or clusters.
+
+You can find more infomation via [alibaba cloud ECI web portal](https://www.aliyun.com/product/eci)
+
+## Alibaba Cloud ECI Virtual-Kubelet Provider
+Alibaba ECI provider is an adapter to connect between k8s and ECI service to implement pod from k8s cluster on alibaba cloud platform
+
+## Prerequisites
+To using ECI service on alibaba cloud, you may need open ECI service on [web portal](https://www.aliyun.com/product/eci), and then the ECI service will be available
+
+## Deployment of the ECI provider in your cluster
+configure and launch virtual kubelet
+```
+export ECI_REGION=cn-hangzhou
+export ECI_SECURITY_GROUP=sg-123
+export ECI_VSWITCH=vsw-123
+export ECI_ACCESS_KEY=123
+export ECI_SECRET_KEY=123
+
+VKUBELET_TAINT_KEY=alibabacloud.com/eci virtual-kubelet --provider alibabacloud
+```
+confirm the virtual kubelet is connected to k8s cluster
+```
+$kubectl get node
+NAME STATUS ROLES AGE VERSION
+cn-shanghai.i-uf69qodr5ntaxleqdhhk Ready 1d v1.9.3
+virtual-kubelet Ready agent 10s v1.8.3
+```
+
+## Schedule K8s Pod to ECI via virtual kubelet
+You can assign pod to virtual kubelet via node-selector and toleration.
+```
+apiVersion: v1
+kind: Pod
+metadata:
+ name: mypod
+spec:
+ nodeName: virtual-kubelet
+ containers:
+ - name: nginx
+ image: nginx
+ tolerations:
+ - key: alibabacloud.com/eci
+ operator: "Exists"
+ effect: NoSchedule
+```
+
+# Alibaba Cloud Serverless Kubernetes
+Alibaba Cloud serverless kubernetes allows you to quickly create kubernetes container applications without
+having to manage and maintain clusters and servers. It is based on ECI and fully compatible with the Kuberentes API.
+
+You can find more infomation via [alibaba cloud serverless kubernetes product doc](https://www.alibabacloud.com/help/doc-detail/94078.htm)
+
diff --git a/config.go b/config.go
new file mode 100644
index 0000000..e2de794
--- /dev/null
+++ b/config.go
@@ -0,0 +1,56 @@
+package alibabacloud
+
+import (
+ "io"
+
+ "github.com/BurntSushi/toml"
+ "github.com/virtual-kubelet/virtual-kubelet/providers"
+)
+
+type providerConfig struct {
+ Region string
+ OperatingSystem string
+ CPU string
+ Memory string
+ Pods string
+ VSwitch string
+ SecureGroup string
+ ClusterName string
+}
+
+func (p *ECIProvider) loadConfig(r io.Reader) error {
+ var config providerConfig
+ if _, err := toml.DecodeReader(r, &config); err != nil {
+ return err
+ }
+
+ p.region = config.Region
+ if p.region == "" {
+ p.region = "cn-hangzhou"
+ }
+
+ p.vSwitch = config.VSwitch
+ p.secureGroup = config.SecureGroup
+
+ p.cpu = config.CPU
+ if p.cpu == "" {
+ p.cpu = "20"
+ }
+ p.memory = config.Memory
+ if p.memory == "" {
+ p.memory = "100Gi"
+ }
+ p.pods = config.Pods
+ if p.pods == "" {
+ p.pods = "20"
+ }
+ p.operatingSystem = config.OperatingSystem
+ if p.operatingSystem == "" {
+ p.operatingSystem = providers.OperatingSystemLinux
+ }
+ p.clusterName = config.ClusterName
+ if p.clusterName == "" {
+ p.clusterName = "default"
+ }
+ return nil
+}
diff --git a/eci.go b/eci.go
new file mode 100644
index 0000000..23d68e1
--- /dev/null
+++ b/eci.go
@@ -0,0 +1,895 @@
+package alibabacloud
+
+import (
+ "bytes"
+ "context"
+ "crypto/sha256"
+ "encoding/base64"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io"
+ "os"
+ "strconv"
+ "strings"
+ "time"
+
+ "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
+ "github.com/cpuguy83/strongerrors"
+ "github.com/virtual-kubelet/virtual-kubelet/log"
+ "github.com/virtual-kubelet/virtual-kubelet/manager"
+ "github.com/virtual-kubelet/virtual-kubelet/providers/alibabacloud/eci"
+ "k8s.io/api/core/v1"
+ k8serr "k8s.io/apimachinery/pkg/api/errors"
+ "k8s.io/apimachinery/pkg/api/resource"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+ "k8s.io/apimachinery/pkg/types"
+ "k8s.io/client-go/tools/remotecommand"
+)
+
+// The service account secret mount path.
+const serviceAccountSecretMountPath = "/var/run/secrets/kubernetes.io/serviceaccount"
+
+const podTagTimeFormat = "2006-01-02T15-04-05Z"
+const timeFormat = "2006-01-02T15:04:05Z"
+
+// ECIProvider implements the virtual-kubelet provider interface and communicates with Alibaba Cloud's ECI APIs.
+type ECIProvider struct {
+ eciClient *eci.Client
+ resourceManager *manager.ResourceManager
+ resourceGroup string
+ region string
+ nodeName string
+ operatingSystem string
+ clusterName string
+ cpu string
+ memory string
+ pods string
+ internalIP string
+ daemonEndpointPort int32
+ secureGroup string
+ vSwitch string
+}
+
+// AuthConfig is the secret returned from an ImageRegistryCredential
+type AuthConfig struct {
+ Username string `json:"username,omitempty"`
+ Password string `json:"password,omitempty"`
+ Auth string `json:"auth,omitempty"`
+ Email string `json:"email,omitempty"`
+ ServerAddress string `json:"serveraddress,omitempty"`
+ IdentityToken string `json:"identitytoken,omitempty"`
+ RegistryToken string `json:"registrytoken,omitempty"`
+}
+
+var validEciRegions = []string{
+ "cn-hangzhou",
+ "cn-shanghai",
+ "cn-beijing",
+ "us-west-1",
+}
+
+// isValidECIRegion checks to make sure we're using a valid ECI region
+func isValidECIRegion(region string) bool {
+ regionLower := strings.ToLower(region)
+ regionTrimmed := strings.Replace(regionLower, " ", "", -1)
+
+ for _, validRegion := range validEciRegions {
+ if regionTrimmed == validRegion {
+ return true
+ }
+ }
+
+ return false
+}
+
+// NewECIProvider creates a new ECIProvider.
+func NewECIProvider(config string, rm *manager.ResourceManager, nodeName, operatingSystem string, internalIP string, daemonEndpointPort int32) (*ECIProvider, error) {
+ var p ECIProvider
+ var err error
+
+ p.resourceManager = rm
+
+ if config != "" {
+ f, err := os.Open(config)
+ if err != nil {
+ return nil, err
+ }
+ defer f.Close()
+
+ if err := p.loadConfig(f); err != nil {
+ return nil, err
+ }
+ }
+ if r := os.Getenv("ECI_CLUSTER_NAME"); r != "" {
+ p.clusterName = r
+ }
+ if p.clusterName == "" {
+ p.clusterName = "default"
+ }
+ if r := os.Getenv("ECI_REGION"); r != "" {
+ p.region = r
+ }
+ if p.region == "" {
+ return nil, errors.New("Region can't be empty please set ECI_REGION\n")
+ }
+ if r := p.region; !isValidECIRegion(r) {
+ unsupportedRegionMessage := fmt.Sprintf("Region %s is invalid. Current supported regions are: %s",
+ r, strings.Join(validEciRegions, ", "))
+
+ return nil, errors.New(unsupportedRegionMessage)
+ }
+
+ var accessKey, secretKey string
+
+ if ak := os.Getenv("ECI_ACCESS_KEY"); ak != "" {
+ accessKey = ak
+ }
+ if sk := os.Getenv("ECI_SECRET_KEY"); sk != "" {
+ secretKey = sk
+ }
+ if sg := os.Getenv("ECI_SECURITY_GROUP"); sg != "" {
+ p.secureGroup = sg
+ }
+ if vsw := os.Getenv("ECI_VSWITCH"); vsw != "" {
+ p.vSwitch = vsw
+ }
+ if p.secureGroup == "" {
+ return nil, errors.New("secureGroup can't be empty\n")
+ }
+
+ if p.vSwitch == "" {
+ return nil, errors.New("vSwitch can't be empty\n")
+ }
+
+ p.eciClient, err = eci.NewClientWithAccessKey(p.region, accessKey, secretKey)
+ if err != nil {
+ return nil, err
+ }
+
+ p.cpu = "1000"
+ p.memory = "4Ti"
+ p.pods = "1000"
+
+ if cpuQuota := os.Getenv("ECI_QUOTA_CPU"); cpuQuota != "" {
+ p.cpu = cpuQuota
+ }
+
+ if memoryQuota := os.Getenv("ECI_QUOTA_MEMORY"); memoryQuota != "" {
+ p.memory = memoryQuota
+ }
+
+ if podsQuota := os.Getenv("ECI_QUOTA_POD"); podsQuota != "" {
+ p.pods = podsQuota
+ }
+
+ p.operatingSystem = operatingSystem
+ p.nodeName = nodeName
+ p.internalIP = internalIP
+ p.daemonEndpointPort = daemonEndpointPort
+ return &p, err
+}
+
+// CreatePod accepts a Pod definition and creates
+// an ECI deployment
+func (p *ECIProvider) CreatePod(ctx context.Context, pod *v1.Pod) error {
+ //Ignore daemonSet Pod
+ if pod != nil && pod.OwnerReferences != nil && len(pod.OwnerReferences) != 0 && pod.OwnerReferences[0].Kind == "DaemonSet" {
+ msg := fmt.Sprintf("Skip to create DaemonSet pod %q", pod.Name)
+ log.G(ctx).WithField("Method", "CreatePod").Info(msg)
+ return nil
+ }
+
+ request := eci.CreateCreateContainerGroupRequest()
+ request.RestartPolicy = string(pod.Spec.RestartPolicy)
+
+ // get containers
+ containers, err := p.getContainers(pod, false)
+ initContainers, err := p.getContainers(pod, true)
+ if err != nil {
+ return err
+ }
+
+ // get registry creds
+ creds, err := p.getImagePullSecrets(pod)
+ if err != nil {
+ return err
+ }
+
+ // get volumes
+ volumes, err := p.getVolumes(pod)
+ if err != nil {
+ return err
+ }
+
+ // assign all the things
+ request.Containers = containers
+ request.InitContainers = initContainers
+ request.Volumes = volumes
+ request.ImageRegistryCredentials = creds
+ CreationTimestamp := pod.CreationTimestamp.UTC().Format(podTagTimeFormat)
+ tags := []eci.Tag{
+ eci.Tag{Key: "ClusterName", Value: p.clusterName},
+ eci.Tag{Key: "NodeName", Value: p.nodeName},
+ eci.Tag{Key: "NameSpace", Value: pod.Namespace},
+ eci.Tag{Key: "PodName", Value: pod.Name},
+ eci.Tag{Key: "UID", Value: string(pod.UID)},
+ eci.Tag{Key: "CreationTimestamp", Value: CreationTimestamp},
+ }
+
+ ContainerGroupName := containerGroupName(pod)
+ request.Tags = tags
+ request.SecurityGroupId = p.secureGroup
+ request.VSwitchId = p.vSwitch
+ request.ContainerGroupName = ContainerGroupName
+ msg := fmt.Sprintf("CreateContainerGroup request %+v", request)
+ log.G(ctx).WithField("Method", "CreatePod").Info(msg)
+ response, err := p.eciClient.CreateContainerGroup(request)
+ if err != nil {
+ return err
+ }
+ msg = fmt.Sprintf("CreateContainerGroup successed. %s, %s, %s", response.RequestId, response.ContainerGroupId, ContainerGroupName)
+ log.G(ctx).WithField("Method", "CreatePod").Info(msg)
+ return nil
+}
+
+func containerGroupName(pod *v1.Pod) string {
+ return fmt.Sprintf("%s-%s", pod.Namespace, pod.Name)
+}
+
+// UpdatePod is a noop, ECI currently does not support live updates of a pod.
+func (p *ECIProvider) UpdatePod(ctx context.Context, pod *v1.Pod) error {
+ return nil
+}
+
+// DeletePod deletes the specified pod out of ECI.
+func (p *ECIProvider) DeletePod(ctx context.Context, pod *v1.Pod) error {
+ eciId := ""
+ for _, cg := range p.GetCgs() {
+ if getECITagValue(&cg, "PodName") == pod.Name && getECITagValue(&cg, "NameSpace") == pod.Namespace {
+ eciId = cg.ContainerGroupId
+ break
+ }
+ }
+ if eciId == "" {
+ return strongerrors.NotFound(fmt.Errorf("DeletePod can't find Pod %s-%s", pod.Namespace, pod.Name))
+ }
+
+ request := eci.CreateDeleteContainerGroupRequest()
+ request.ContainerGroupId = eciId
+ _, err := p.eciClient.DeleteContainerGroup(request)
+ return wrapError(err)
+}
+
+// GetPod returns a pod by name that is running inside ECI
+// returns nil if a pod by that name is not found.
+func (p *ECIProvider) GetPod(ctx context.Context, namespace, name string) (*v1.Pod, error) {
+ pods, err := p.GetPods(ctx)
+ if err != nil {
+ return nil, err
+ }
+ for _, pod := range pods {
+ if pod.Name == name && pod.Namespace == namespace {
+ return pod, nil
+ }
+ }
+ return nil, nil
+}
+
+// GetContainerLogs returns the logs of a pod by name that is running inside ECI.
+func (p *ECIProvider) GetContainerLogs(ctx context.Context, namespace, podName, containerName string, tail int) (string, error) {
+ eciId := ""
+ for _, cg := range p.GetCgs() {
+ if getECITagValue(&cg, "PodName") == podName && getECITagValue(&cg, "NameSpace") == namespace {
+ eciId = cg.ContainerGroupId
+ break
+ }
+ }
+ if eciId == "" {
+ return "", errors.New(fmt.Sprintf("GetContainerLogs can't find Pod %s-%s", namespace, podName))
+ }
+
+ request := eci.CreateDescribeContainerLogRequest()
+ request.ContainerGroupId = eciId
+ request.ContainerName = containerName
+ request.Tail = requests.Integer(tail)
+
+ // get logs from cg
+ logContent := ""
+ retry := 10
+ for i := 0; i < retry; i++ {
+ response, err := p.eciClient.DescribeContainerLog(request)
+ if err != nil {
+ msg := fmt.Sprint("Error getting container logs, retrying")
+ log.G(ctx).WithField("Method", "GetContainerLogs").Info(msg)
+ time.Sleep(5000 * time.Millisecond)
+ } else {
+ logContent = response.Content
+ break
+ }
+ }
+
+ return logContent, nil
+}
+
+// Get full pod name as defined in the provider context
+func (p *ECIProvider) GetPodFullName(namespace string, pod string) string {
+ return fmt.Sprintf("%s-%s", namespace, pod)
+}
+
+// ExecInContainer executes a command in a container in the pod, copying data
+// between in/out/err and the container's stdin/stdout/stderr.
+func (p *ECIProvider) ExecInContainer(name string, uid types.UID, container string, cmd []string, in io.Reader, out, errstream io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize, timeout time.Duration) error {
+ return nil
+}
+
+// GetPodStatus returns the status of a pod by name that is running inside ECI
+// returns nil if a pod by that name is not found.
+func (p *ECIProvider) GetPodStatus(ctx context.Context, namespace, name string) (*v1.PodStatus, error) {
+ pod, err := p.GetPod(ctx, namespace, name)
+ if err != nil {
+ return nil, err
+ }
+
+ if pod == nil {
+ return nil, nil
+ }
+
+ return &pod.Status, nil
+}
+
+func (p *ECIProvider) GetCgs() []eci.ContainerGroup {
+ cgs := make([]eci.ContainerGroup, 0)
+ request := eci.CreateDescribeContainerGroupsRequest()
+ for {
+ cgsResponse, err := p.eciClient.DescribeContainerGroups(request)
+ if err != nil || len(cgsResponse.ContainerGroups) == 0 {
+ break
+ }
+ request.NextToken = cgsResponse.NextToken
+
+ for _, cg := range cgsResponse.ContainerGroups {
+ if getECITagValue(&cg, "NodeName") != p.nodeName {
+ continue
+ }
+ cn := getECITagValue(&cg, "ClusterName")
+ if cn == "" {
+ cn = "default"
+ }
+ if cn != p.clusterName {
+ continue
+ }
+ cgs = append(cgs, cg)
+ }
+ if request.NextToken == "" {
+ break
+ }
+ }
+ return cgs
+}
+
+// GetPods returns a list of all pods known to be running within ECI.
+func (p *ECIProvider) GetPods(ctx context.Context) ([]*v1.Pod, error) {
+ pods := make([]*v1.Pod, 0)
+ for _, cg := range p.GetCgs() {
+ c := cg
+ pod, err := containerGroupToPod(&c)
+ if err != nil {
+ msg := fmt.Sprint("error converting container group to pod", cg.ContainerGroupId, err)
+ log.G(context.TODO()).WithField("Method", "GetPods").Info(msg)
+ continue
+ }
+ pods = append(pods, pod)
+ }
+ return pods, nil
+}
+
+// Capacity returns a resource list containing the capacity limits set for ECI.
+func (p *ECIProvider) Capacity(ctx context.Context) v1.ResourceList {
+ return v1.ResourceList{
+ "cpu": resource.MustParse(p.cpu),
+ "memory": resource.MustParse(p.memory),
+ "pods": resource.MustParse(p.pods),
+ }
+}
+
+// NodeConditions returns a list of conditions (Ready, OutOfDisk, etc), for updates to the node status
+// within Kubernetes.
+func (p *ECIProvider) NodeConditions(ctx context.Context) []v1.NodeCondition {
+ // TODO: Make these dynamic and augment with custom ECI specific conditions of interest
+ return []v1.NodeCondition{
+ {
+ Type: "Ready",
+ Status: v1.ConditionTrue,
+ LastHeartbeatTime: metav1.Now(),
+ LastTransitionTime: metav1.Now(),
+ Reason: "KubeletReady",
+ Message: "kubelet is ready.",
+ },
+ {
+ Type: "OutOfDisk",
+ Status: v1.ConditionFalse,
+ LastHeartbeatTime: metav1.Now(),
+ LastTransitionTime: metav1.Now(),
+ Reason: "KubeletHasSufficientDisk",
+ Message: "kubelet has sufficient disk space available",
+ },
+ {
+ Type: "MemoryPressure",
+ Status: v1.ConditionFalse,
+ LastHeartbeatTime: metav1.Now(),
+ LastTransitionTime: metav1.Now(),
+ Reason: "KubeletHasSufficientMemory",
+ Message: "kubelet has sufficient memory available",
+ },
+ {
+ Type: "DiskPressure",
+ Status: v1.ConditionFalse,
+ LastHeartbeatTime: metav1.Now(),
+ LastTransitionTime: metav1.Now(),
+ Reason: "KubeletHasNoDiskPressure",
+ Message: "kubelet has no disk pressure",
+ },
+ {
+ Type: "NetworkUnavailable",
+ Status: v1.ConditionFalse,
+ LastHeartbeatTime: metav1.Now(),
+ LastTransitionTime: metav1.Now(),
+ Reason: "RouteCreated",
+ Message: "RouteController created a route",
+ },
+ }
+}
+
+// NodeAddresses returns a list of addresses for the node status
+// within Kubernetes.
+func (p *ECIProvider) NodeAddresses(ctx context.Context) []v1.NodeAddress {
+ // TODO: Make these dynamic and augment with custom ECI specific conditions of interest
+ return []v1.NodeAddress{
+ {
+ Type: "InternalIP",
+ Address: p.internalIP,
+ },
+ }
+}
+
+// NodeDaemonEndpoints returns NodeDaemonEndpoints for the node status
+// within Kubernetes.
+func (p *ECIProvider) NodeDaemonEndpoints(ctx context.Context) *v1.NodeDaemonEndpoints {
+ return &v1.NodeDaemonEndpoints{
+ KubeletEndpoint: v1.DaemonEndpoint{
+ Port: p.daemonEndpointPort,
+ },
+ }
+}
+
+// OperatingSystem returns the operating system that was provided by the config.
+func (p *ECIProvider) OperatingSystem() string {
+ return p.operatingSystem
+}
+
+func (p *ECIProvider) getImagePullSecrets(pod *v1.Pod) ([]eci.ImageRegistryCredential, error) {
+ ips := make([]eci.ImageRegistryCredential, 0, len(pod.Spec.ImagePullSecrets))
+ for _, ref := range pod.Spec.ImagePullSecrets {
+ secret, err := p.resourceManager.GetSecret(ref.Name, pod.Namespace)
+ if err != nil {
+ return ips, err
+ }
+ if secret == nil {
+ return nil, fmt.Errorf("error getting image pull secret")
+ }
+ // TODO: Check if secret type is v1.SecretTypeDockercfg and use DockerConfigKey instead of hardcoded value
+ // TODO: Check if secret type is v1.SecretTypeDockerConfigJson and use DockerConfigJsonKey to determine if it's in json format
+ // TODO: Return error if it's not one of these two types
+ switch secret.Type {
+ case v1.SecretTypeDockercfg:
+ ips, err = readDockerCfgSecret(secret, ips)
+ case v1.SecretTypeDockerConfigJson:
+ ips, err = readDockerConfigJSONSecret(secret, ips)
+ default:
+ return nil, fmt.Errorf("image pull secret type is not one of kubernetes.io/dockercfg or kubernetes.io/dockerconfigjson")
+ }
+
+ if err != nil {
+ return ips, err
+ }
+ }
+ return ips, nil
+}
+
+func readDockerCfgSecret(secret *v1.Secret, ips []eci.ImageRegistryCredential) ([]eci.ImageRegistryCredential, error) {
+ var err error
+ var authConfigs map[string]AuthConfig
+ repoData, ok := secret.Data[string(v1.DockerConfigKey)]
+
+ if !ok {
+ return ips, fmt.Errorf("no dockercfg present in secret")
+ }
+
+ err = json.Unmarshal(repoData, &authConfigs)
+ if err != nil {
+ return ips, fmt.Errorf("failed to unmarshal auth config %+v", err)
+ }
+
+ for server, authConfig := range authConfigs {
+ ips = append(ips, eci.ImageRegistryCredential{
+ Password: authConfig.Password,
+ Server: server,
+ UserName: authConfig.Username,
+ })
+ }
+
+ return ips, err
+}
+
+func readDockerConfigJSONSecret(secret *v1.Secret, ips []eci.ImageRegistryCredential) ([]eci.ImageRegistryCredential, error) {
+ var err error
+ repoData, ok := secret.Data[string(v1.DockerConfigJsonKey)]
+
+ if !ok {
+ return ips, fmt.Errorf("no dockerconfigjson present in secret")
+ }
+
+ var authConfigs map[string]map[string]AuthConfig
+
+ err = json.Unmarshal(repoData, &authConfigs)
+ if err != nil {
+ return ips, err
+ }
+
+ auths, ok := authConfigs["auths"]
+
+ if !ok {
+ return ips, fmt.Errorf("malformed dockerconfigjson in secret")
+ }
+
+ for server, authConfig := range auths {
+ ips = append(ips, eci.ImageRegistryCredential{
+ Password: authConfig.Password,
+ Server: server,
+ UserName: authConfig.Username,
+ })
+ }
+
+ return ips, err
+}
+
+func (p *ECIProvider) getContainers(pod *v1.Pod, init bool) ([]eci.CreateContainer, error) {
+ podContainers := pod.Spec.Containers
+ if init {
+ podContainers = pod.Spec.InitContainers
+ }
+ containers := make([]eci.CreateContainer, 0, len(podContainers))
+ for _, container := range podContainers {
+ c := eci.CreateContainer{
+ Name: container.Name,
+ Image: container.Image,
+ Commands: append(container.Command, container.Args...),
+ Ports: make([]eci.ContainerPort, 0, len(container.Ports)),
+ }
+
+ for _, p := range container.Ports {
+ c.Ports = append(c.Ports, eci.ContainerPort{
+ Port: requests.Integer(strconv.FormatInt(int64(p.ContainerPort), 10)),
+ Protocol: string(p.Protocol),
+ })
+ }
+
+ c.VolumeMounts = make([]eci.VolumeMount, 0, len(container.VolumeMounts))
+ for _, v := range container.VolumeMounts {
+ c.VolumeMounts = append(c.VolumeMounts, eci.VolumeMount{
+ Name: v.Name,
+ MountPath: v.MountPath,
+ ReadOnly: requests.Boolean(strconv.FormatBool(v.ReadOnly)),
+ })
+ }
+
+ c.EnvironmentVars = make([]eci.EnvironmentVar, 0, len(container.Env))
+ for _, e := range container.Env {
+ c.EnvironmentVars = append(c.EnvironmentVars, eci.EnvironmentVar{Key: e.Name, Value: e.Value})
+ }
+
+ cpuRequest := 1.00
+ if _, ok := container.Resources.Requests[v1.ResourceCPU]; ok {
+ cpuRequest = float64(container.Resources.Requests.Cpu().MilliValue()) / 1000.00
+ }
+
+ c.Cpu = requests.Float(fmt.Sprintf("%.3f", cpuRequest))
+
+ memoryRequest := 2.0
+ if _, ok := container.Resources.Requests[v1.ResourceMemory]; ok {
+ memoryRequest = float64(container.Resources.Requests.Memory().Value()) / 1024.0 / 1024.0 / 1024.0
+ }
+
+ c.Memory = requests.Float(fmt.Sprintf("%.3f", memoryRequest))
+
+ c.ImagePullPolicy = string(container.ImagePullPolicy)
+ c.WorkingDir = container.WorkingDir
+
+ containers = append(containers, c)
+ }
+ return containers, nil
+}
+
+func (p *ECIProvider) getVolumes(pod *v1.Pod) ([]eci.Volume, error) {
+ volumes := make([]eci.Volume, 0, len(pod.Spec.Volumes))
+ for _, v := range pod.Spec.Volumes {
+ // Handle the case for the EmptyDir.
+ if v.EmptyDir != nil {
+ volumes = append(volumes, eci.Volume{
+ Type: eci.VOL_TYPE_EMPTYDIR,
+ Name: v.Name,
+ EmptyDirVolumeEnable: requests.Boolean(strconv.FormatBool(true)),
+ })
+ continue
+ }
+
+ // Handle the case for the NFS.
+ if v.NFS != nil {
+ volumes = append(volumes, eci.Volume{
+ Type: eci.VOL_TYPE_NFS,
+ Name: v.Name,
+ NfsVolumeServer: v.NFS.Server,
+ NfsVolumePath: v.NFS.Path,
+ NfsVolumeReadOnly: requests.Boolean(strconv.FormatBool(v.NFS.ReadOnly)),
+ })
+ continue
+ }
+
+ // Handle the case for ConfigMap volume.
+ if v.ConfigMap != nil {
+ ConfigFileToPaths := make([]eci.ConfigFileToPath, 0)
+ configMap, err := p.resourceManager.GetConfigMap(v.ConfigMap.Name, pod.Namespace)
+ if v.ConfigMap.Optional != nil && !*v.ConfigMap.Optional && k8serr.IsNotFound(err) {
+ return nil, fmt.Errorf("ConfigMap %s is required by Pod %s and does not exist", v.ConfigMap.Name, pod.Name)
+ }
+ if configMap == nil {
+ continue
+ }
+
+ for k, v := range configMap.Data {
+ var b bytes.Buffer
+ enc := base64.NewEncoder(base64.StdEncoding, &b)
+ enc.Write([]byte(v))
+
+ ConfigFileToPaths = append(ConfigFileToPaths, eci.ConfigFileToPath{Path: k, Content: b.String()})
+ }
+
+ if len(ConfigFileToPaths) != 0 {
+ volumes = append(volumes, eci.Volume{
+ Type: eci.VOL_TYPE_CONFIGFILEVOLUME,
+ Name: v.Name,
+ ConfigFileToPaths: ConfigFileToPaths,
+ })
+ }
+ continue
+ }
+
+ if v.Secret != nil {
+ ConfigFileToPaths := make([]eci.ConfigFileToPath, 0)
+ secret, err := p.resourceManager.GetSecret(v.Secret.SecretName, pod.Namespace)
+ if v.Secret.Optional != nil && !*v.Secret.Optional && k8serr.IsNotFound(err) {
+ return nil, fmt.Errorf("Secret %s is required by Pod %s and does not exist", v.Secret.SecretName, pod.Name)
+ }
+ if secret == nil {
+ continue
+ }
+ for k, v := range secret.Data {
+ var b bytes.Buffer
+ enc := base64.NewEncoder(base64.StdEncoding, &b)
+ enc.Write(v)
+ ConfigFileToPaths = append(ConfigFileToPaths, eci.ConfigFileToPath{Path: k, Content: b.String()})
+ }
+
+ if len(ConfigFileToPaths) != 0 {
+ volumes = append(volumes, eci.Volume{
+ Type: eci.VOL_TYPE_CONFIGFILEVOLUME,
+ Name: v.Name,
+ ConfigFileToPaths: ConfigFileToPaths,
+ })
+ }
+ continue
+ }
+
+ // If we've made it this far we have found a volume type that isn't supported
+ return nil, fmt.Errorf("Pod %s requires volume %s which is of an unsupported type\n", pod.Name, v.Name)
+ }
+
+ return volumes, nil
+}
+
+func containerGroupToPod(cg *eci.ContainerGroup) (*v1.Pod, error) {
+ var podCreationTimestamp, containerStartTime metav1.Time
+
+ CreationTimestamp := getECITagValue(cg, "CreationTimestamp")
+ if CreationTimestamp != "" {
+ if t, err := time.Parse(podTagTimeFormat, CreationTimestamp); err == nil {
+ podCreationTimestamp = metav1.NewTime(t)
+ }
+ }
+
+ if t, err := time.Parse(timeFormat, cg.Containers[0].CurrentState.StartTime); err == nil {
+ containerStartTime = metav1.NewTime(t)
+ }
+
+ // Use the Provisioning State if it's not Succeeded,
+ // otherwise use the state of the instance.
+ eciState := cg.Status
+
+ containers := make([]v1.Container, 0, len(cg.Containers))
+ containerStatuses := make([]v1.ContainerStatus, 0, len(cg.Containers))
+ for _, c := range cg.Containers {
+ container := v1.Container{
+ Name: c.Name,
+ Image: c.Image,
+ Command: c.Commands,
+ Resources: v1.ResourceRequirements{
+ Requests: v1.ResourceList{
+ v1.ResourceCPU: resource.MustParse(fmt.Sprintf("%.2f", c.Cpu)),
+ v1.ResourceMemory: resource.MustParse(fmt.Sprintf("%.1fG", c.Memory)),
+ },
+ },
+ }
+
+ container.Resources.Limits = v1.ResourceList{
+ v1.ResourceCPU: resource.MustParse(fmt.Sprintf("%.2f", c.Cpu)),
+ v1.ResourceMemory: resource.MustParse(fmt.Sprintf("%.1fG", c.Memory)),
+ }
+
+ containers = append(containers, container)
+ containerStatus := v1.ContainerStatus{
+ Name: c.Name,
+ State: eciContainerStateToContainerState(c.CurrentState),
+ LastTerminationState: eciContainerStateToContainerState(c.PreviousState),
+ Ready: eciStateToPodPhase(c.CurrentState.State) == v1.PodRunning,
+ RestartCount: int32(c.RestartCount),
+ Image: c.Image,
+ ImageID: "",
+ ContainerID: getContainerID(cg.ContainerGroupId, c.Name),
+ }
+
+ // Add to containerStatuses
+ containerStatuses = append(containerStatuses, containerStatus)
+ }
+
+ pod := v1.Pod{
+ TypeMeta: metav1.TypeMeta{
+ Kind: "Pod",
+ APIVersion: "v1",
+ },
+ ObjectMeta: metav1.ObjectMeta{
+ Name: getECITagValue(cg, "PodName"),
+ Namespace: getECITagValue(cg, "NameSpace"),
+ ClusterName: getECITagValue(cg, "ClusterName"),
+ UID: types.UID(getECITagValue(cg, "UID")),
+ CreationTimestamp: podCreationTimestamp,
+ },
+ Spec: v1.PodSpec{
+ NodeName: getECITagValue(cg, "NodeName"),
+ Volumes: []v1.Volume{},
+ Containers: containers,
+ },
+ Status: v1.PodStatus{
+ Phase: eciStateToPodPhase(eciState),
+ Conditions: eciStateToPodConditions(eciState, podCreationTimestamp),
+ Message: "",
+ Reason: "",
+ HostIP: "",
+ PodIP: cg.IntranetIp,
+ StartTime: &containerStartTime,
+ ContainerStatuses: containerStatuses,
+ },
+ }
+
+ return &pod, nil
+}
+
+func getContainerID(cgID, containerName string) string {
+ if cgID == "" {
+ return ""
+ }
+
+ containerResourceID := fmt.Sprintf("%s/containers/%s", cgID, containerName)
+
+ h := sha256.New()
+ h.Write([]byte(strings.ToUpper(containerResourceID)))
+ hashBytes := h.Sum(nil)
+ return fmt.Sprintf("eci://%s", hex.EncodeToString(hashBytes))
+}
+
+func eciStateToPodPhase(state string) v1.PodPhase {
+ switch state {
+ case "Scheduling":
+ return v1.PodPending
+ case "ScheduleFailed":
+ return v1.PodFailed
+ case "Pending":
+ return v1.PodPending
+ case "Running":
+ return v1.PodRunning
+ case "Failed":
+ return v1.PodFailed
+ case "Succeeded":
+ return v1.PodSucceeded
+ }
+ return v1.PodUnknown
+}
+
+func eciStateToPodConditions(state string, transitionTime metav1.Time) []v1.PodCondition {
+ switch state {
+ case "Running", "Succeeded":
+ return []v1.PodCondition{
+ v1.PodCondition{
+ Type: v1.PodReady,
+ Status: v1.ConditionTrue,
+ LastTransitionTime: transitionTime,
+ }, v1.PodCondition{
+ Type: v1.PodInitialized,
+ Status: v1.ConditionTrue,
+ LastTransitionTime: transitionTime,
+ }, v1.PodCondition{
+ Type: v1.PodScheduled,
+ Status: v1.ConditionTrue,
+ LastTransitionTime: transitionTime,
+ },
+ }
+ }
+ return []v1.PodCondition{}
+}
+
+func eciContainerStateToContainerState(cs eci.ContainerState) v1.ContainerState {
+ t1, err := time.Parse(timeFormat, cs.StartTime)
+ if err != nil {
+ return v1.ContainerState{}
+ }
+
+ startTime := metav1.NewTime(t1)
+
+ // Handle the case where the container is running.
+ if cs.State == "Running" || cs.State == "Succeeded" {
+ return v1.ContainerState{
+ Running: &v1.ContainerStateRunning{
+ StartedAt: startTime,
+ },
+ }
+ }
+
+ t2, err := time.Parse(timeFormat, cs.FinishTime)
+ if err != nil {
+ return v1.ContainerState{}
+ }
+
+ finishTime := metav1.NewTime(t2)
+
+ // Handle the case where the container failed.
+ if cs.State == "Failed" || cs.State == "Canceled" {
+ return v1.ContainerState{
+ Terminated: &v1.ContainerStateTerminated{
+ ExitCode: int32(cs.ExitCode),
+ Reason: cs.State,
+ Message: cs.DetailStatus,
+ StartedAt: startTime,
+ FinishedAt: finishTime,
+ },
+ }
+ }
+
+ // Handle the case where the container is pending.
+ // Which should be all other eci states.
+ return v1.ContainerState{
+ Waiting: &v1.ContainerStateWaiting{
+ Reason: cs.State,
+ Message: cs.DetailStatus,
+ },
+ }
+}
+
+func getECITagValue(cg *eci.ContainerGroup, key string) string {
+ for _, tag := range cg.Tags {
+ if tag.Key == key {
+ return tag.Value
+ }
+ }
+ return ""
+}
diff --git a/eci.svg b/eci.svg
new file mode 100644
index 0000000..e31def4
--- /dev/null
+++ b/eci.svg
@@ -0,0 +1,16 @@
+
+
+
diff --git a/eci.toml b/eci.toml
new file mode 100644
index 0000000..f37ae37
--- /dev/null
+++ b/eci.toml
@@ -0,0 +1,6 @@
+Region = "cn-hangzhou"
+OperatingSystem = "Linux"
+CPU = "20"
+Memory = "100Gi"
+Pods = "20"
+ClusterName = "default"
diff --git a/eci/client.go b/eci/client.go
new file mode 100644
index 0000000..fed034a
--- /dev/null
+++ b/eci/client.go
@@ -0,0 +1,81 @@
+package eci
+
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+//
+// Code generated by Alibaba Cloud SDK Code Generator.
+// Changes may cause incorrect behavior and will be lost if the code is regenerated.
+
+import (
+ "github.com/aliyun/alibaba-cloud-sdk-go/sdk"
+ "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth"
+)
+
+// Client is the sdk client struct, each func corresponds to an OpenAPI
+type Client struct {
+ sdk.Client
+}
+
+// NewClient creates a sdk client with environment variables
+func NewClient() (client *Client, err error) {
+ client = &Client{}
+ err = client.Init()
+ return
+}
+
+// NewClientWithOptions creates a sdk client with regionId/sdkConfig/credential
+// this is the common api to create a sdk client
+func NewClientWithOptions(regionId string, config *sdk.Config, credential auth.Credential) (client *Client, err error) {
+ client = &Client{}
+ err = client.InitWithOptions(regionId, config, credential)
+ return
+}
+
+// NewClientWithAccessKey is a shortcut to create sdk client with accesskey
+// usage: https://help.aliyun.com/document_detail/66217.html
+func NewClientWithAccessKey(regionId, accessKeyId, accessKeySecret string) (client *Client, err error) {
+ client = &Client{}
+ err = client.InitWithAccessKey(regionId, accessKeyId, accessKeySecret)
+ return
+}
+
+// NewClientWithStsToken is a shortcut to create sdk client with sts token
+// usage: https://help.aliyun.com/document_detail/66222.html
+func NewClientWithStsToken(regionId, stsAccessKeyId, stsAccessKeySecret, stsToken string) (client *Client, err error) {
+ client = &Client{}
+ err = client.InitWithStsToken(regionId, stsAccessKeyId, stsAccessKeySecret, stsToken)
+ return
+}
+
+// NewClientWithRamRoleArn is a shortcut to create sdk client with ram roleArn
+// usage: https://help.aliyun.com/document_detail/66222.html
+func NewClientWithRamRoleArn(regionId string, accessKeyId, accessKeySecret, roleArn, roleSessionName string) (client *Client, err error) {
+ client = &Client{}
+ err = client.InitWithRamRoleArn(regionId, accessKeyId, accessKeySecret, roleArn, roleSessionName)
+ return
+}
+
+// NewClientWithEcsRamRole is a shortcut to create sdk client with ecs ram role
+// usage: https://help.aliyun.com/document_detail/66223.html
+func NewClientWithEcsRamRole(regionId string, roleName string) (client *Client, err error) {
+ client = &Client{}
+ err = client.InitWithEcsRamRole(regionId, roleName)
+ return
+}
+
+// NewClientWithRsaKeyPair is a shortcut to create sdk client with rsa key pair
+// attention: rsa key pair auth is only Japan regions available
+func NewClientWithRsaKeyPair(regionId string, publicKeyId, privateKey string, sessionExpiration int) (client *Client, err error) {
+ client = &Client{}
+ err = client.InitWithRsaKeyPair(regionId, publicKeyId, privateKey, sessionExpiration)
+ return
+}
diff --git a/eci/create_container_group.go b/eci/create_container_group.go
new file mode 100644
index 0000000..e9d8716
--- /dev/null
+++ b/eci/create_container_group.go
@@ -0,0 +1,138 @@
+package eci
+
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+//
+// Code generated by Alibaba Cloud SDK Code Generator.
+// Changes may cause incorrect behavior and will be lost if the code is regenerated.
+
+import (
+ "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
+ "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
+)
+
+// CreateContainerGroup invokes the eci.CreateContainerGroup API synchronously
+// api document: https://help.aliyun.com/api/eci/createcontainergroup.html
+func (client *Client) CreateContainerGroup(request *CreateContainerGroupRequest) (response *CreateContainerGroupResponse, err error) {
+ response = CreateCreateContainerGroupResponse()
+ err = client.DoAction(request, response)
+ return
+}
+
+// CreateContainerGroupWithChan invokes the eci.CreateContainerGroup API asynchronously
+// api document: https://help.aliyun.com/api/eci/createcontainergroup.html
+// asynchronous document: https://help.aliyun.com/document_detail/66220.html
+func (client *Client) CreateContainerGroupWithChan(request *CreateContainerGroupRequest) (<-chan *CreateContainerGroupResponse, <-chan error) {
+ responseChan := make(chan *CreateContainerGroupResponse, 1)
+ errChan := make(chan error, 1)
+ err := client.AddAsyncTask(func() {
+ defer close(responseChan)
+ defer close(errChan)
+ response, err := client.CreateContainerGroup(request)
+ if err != nil {
+ errChan <- err
+ } else {
+ responseChan <- response
+ }
+ })
+ if err != nil {
+ errChan <- err
+ close(responseChan)
+ close(errChan)
+ }
+ return responseChan, errChan
+}
+
+// CreateContainerGroupWithCallback invokes the eci.CreateContainerGroup API asynchronously
+// api document: https://help.aliyun.com/api/eci/createcontainergroup.html
+// asynchronous document: https://help.aliyun.com/document_detail/66220.html
+func (client *Client) CreateContainerGroupWithCallback(request *CreateContainerGroupRequest, callback func(response *CreateContainerGroupResponse, err error)) <-chan int {
+ result := make(chan int, 1)
+ err := client.AddAsyncTask(func() {
+ var response *CreateContainerGroupResponse
+ var err error
+ defer close(result)
+ response, err = client.CreateContainerGroup(request)
+ callback(response, err)
+ result <- 1
+ })
+ if err != nil {
+ defer close(result)
+ callback(nil, err)
+ result <- 0
+ }
+ return result
+}
+
+// CreateContainerGroupRequest is the request struct for api CreateContainerGroup
+type CreateContainerGroupRequest struct {
+ *requests.RpcRequest
+ Containers []CreateContainer `position:"Query" name:"Container" type:"Repeated"`
+ InitContainers []CreateContainer `position:"Query" name:"InitContainer" type:"Repeated"`
+ ResourceOwnerId requests.Integer `position:"Query" name:"ResourceOwnerId"`
+ SecurityGroupId string `position:"Query" name:"SecurityGroupId"`
+ ImageRegistryCredentials []ImageRegistryCredential `position:"Query" name:"ImageRegistryCredential" type:"Repeated"`
+ Tags []Tag `position:"Query" name:"Tag" type:"Repeated"`
+ ResourceOwnerAccount string `position:"Query" name:"ResourceOwnerAccount"`
+ RestartPolicy string `position:"Query" name:"RestartPolicy"`
+ OwnerAccount string `position:"Query" name:"OwnerAccount"`
+ OwnerId requests.Integer `position:"Query" name:"OwnerId"`
+ VSwitchId string `position:"Query" name:"VSwitchId"`
+ Volumes []Volume `position:"Query" name:"Volume" type:"Repeated"`
+ ContainerGroupName string `position:"Query" name:"ContainerGroupName"`
+ ZoneId string `position:"Query" name:"ZoneId"`
+}
+
+type CreateContainer struct {
+ Name string `name:"Name"`
+ Image string `name:"Image"`
+ Memory requests.Float `name:"Memory"`
+ Cpu requests.Float `name:"Cpu"`
+ WorkingDir string `name:"WorkingDir"`
+ ImagePullPolicy string `name:"ImagePullPolicy"`
+ Commands []string `name:"Command" type:"Repeated"`
+ Args []string `name:"Arg" type:"Repeated"`
+ VolumeMounts []VolumeMount `name:"VolumeMount" type:"Repeated"`
+ Ports []ContainerPort `name:"Port" type:"Repeated"`
+ EnvironmentVars []EnvironmentVar `name:"EnvironmentVar" type:"Repeated"`
+}
+
+// CreateContainerGroupImageRegistryCredential is a repeated param struct in CreateContainerGroupRequest
+type ImageRegistryCredential struct {
+ Server string `name:"Server"`
+ UserName string `name:"UserName"`
+ Password string `name:"Password"`
+}
+
+// CreateContainerGroupResponse is the response struct for api CreateContainerGroup
+type CreateContainerGroupResponse struct {
+ *responses.BaseResponse
+ RequestId string
+ ContainerGroupId string
+}
+
+// CreateCreateContainerGroupRequest creates a request to invoke CreateContainerGroup API
+func CreateCreateContainerGroupRequest() (request *CreateContainerGroupRequest) {
+ request = &CreateContainerGroupRequest{
+ RpcRequest: &requests.RpcRequest{},
+ }
+ request.InitWithApiInfo("Eci", "2018-08-08", "CreateContainerGroup", "eci", "openAPI")
+ return
+}
+
+// CreateCreateContainerGroupResponse creates a response to parse from CreateContainerGroup response
+func CreateCreateContainerGroupResponse() (response *CreateContainerGroupResponse) {
+ response = &CreateContainerGroupResponse{
+ BaseResponse: &responses.BaseResponse{},
+ }
+ return
+}
diff --git a/eci/delete_container_group.go b/eci/delete_container_group.go
new file mode 100644
index 0000000..5b28843
--- /dev/null
+++ b/eci/delete_container_group.go
@@ -0,0 +1,107 @@
+package eci
+
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+//
+// Code generated by Alibaba Cloud SDK Code Generator.
+// Changes may cause incorrect behavior and will be lost if the code is regenerated.
+
+import (
+ "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
+ "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
+)
+
+// DeleteContainerGroup invokes the eci.DeleteContainerGroup API synchronously
+// api document: https://help.aliyun.com/api/eci/deletecontainergroup.html
+func (client *Client) DeleteContainerGroup(request *DeleteContainerGroupRequest) (response *DeleteContainerGroupResponse, err error) {
+ response = CreateDeleteContainerGroupResponse()
+ err = client.DoAction(request, response)
+ return
+}
+
+// DeleteContainerGroupWithChan invokes the eci.DeleteContainerGroup API asynchronously
+// api document: https://help.aliyun.com/api/eci/deletecontainergroup.html
+// asynchronous document: https://help.aliyun.com/document_detail/66220.html
+func (client *Client) DeleteContainerGroupWithChan(request *DeleteContainerGroupRequest) (<-chan *DeleteContainerGroupResponse, <-chan error) {
+ responseChan := make(chan *DeleteContainerGroupResponse, 1)
+ errChan := make(chan error, 1)
+ err := client.AddAsyncTask(func() {
+ defer close(responseChan)
+ defer close(errChan)
+ response, err := client.DeleteContainerGroup(request)
+ if err != nil {
+ errChan <- err
+ } else {
+ responseChan <- response
+ }
+ })
+ if err != nil {
+ errChan <- err
+ close(responseChan)
+ close(errChan)
+ }
+ return responseChan, errChan
+}
+
+// DeleteContainerGroupWithCallback invokes the eci.DeleteContainerGroup API asynchronously
+// api document: https://help.aliyun.com/api/eci/deletecontainergroup.html
+// asynchronous document: https://help.aliyun.com/document_detail/66220.html
+func (client *Client) DeleteContainerGroupWithCallback(request *DeleteContainerGroupRequest, callback func(response *DeleteContainerGroupResponse, err error)) <-chan int {
+ result := make(chan int, 1)
+ err := client.AddAsyncTask(func() {
+ var response *DeleteContainerGroupResponse
+ var err error
+ defer close(result)
+ response, err = client.DeleteContainerGroup(request)
+ callback(response, err)
+ result <- 1
+ })
+ if err != nil {
+ defer close(result)
+ callback(nil, err)
+ result <- 0
+ }
+ return result
+}
+
+// DeleteContainerGroupRequest is the request struct for api DeleteContainerGroup
+type DeleteContainerGroupRequest struct {
+ *requests.RpcRequest
+ ResourceOwnerId requests.Integer `position:"Query" name:"ResourceOwnerId"`
+ ContainerGroupId string `position:"Query" name:"ContainerGroupId"`
+ ResourceOwnerAccount string `position:"Query" name:"ResourceOwnerAccount"`
+ OwnerAccount string `position:"Query" name:"OwnerAccount"`
+ OwnerId requests.Integer `position:"Query" name:"OwnerId"`
+}
+
+// DeleteContainerGroupResponse is the response struct for api DeleteContainerGroup
+type DeleteContainerGroupResponse struct {
+ *responses.BaseResponse
+ RequestId string `json:"RequestId" xml:"RequestId"`
+}
+
+// CreateDeleteContainerGroupRequest creates a request to invoke DeleteContainerGroup API
+func CreateDeleteContainerGroupRequest() (request *DeleteContainerGroupRequest) {
+ request = &DeleteContainerGroupRequest{
+ RpcRequest: &requests.RpcRequest{},
+ }
+ request.InitWithApiInfo("Eci", "2018-08-08", "DeleteContainerGroup", "eci", "openAPI")
+ return
+}
+
+// CreateDeleteContainerGroupResponse creates a response to parse from DeleteContainerGroup response
+func CreateDeleteContainerGroupResponse() (response *DeleteContainerGroupResponse) {
+ response = &DeleteContainerGroupResponse{
+ BaseResponse: &responses.BaseResponse{},
+ }
+ return
+}
diff --git a/eci/describe_container_groups.go b/eci/describe_container_groups.go
new file mode 100644
index 0000000..b4941c1
--- /dev/null
+++ b/eci/describe_container_groups.go
@@ -0,0 +1,122 @@
+package eci
+
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+//
+// Code generated by Alibaba Cloud SDK Code Generator.
+// Changes may cause incorrect behavior and will be lost if the code is regenerated.
+
+import (
+ "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
+ "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
+)
+
+// DescribeContainerGroups invokes the eci.DescribeContainerGroups API synchronously
+// api document: https://help.aliyun.com/api/eci/describecontainergroups.html
+func (client *Client) DescribeContainerGroups(request *DescribeContainerGroupsRequest) (response *DescribeContainerGroupsResponse, err error) {
+ response = CreateDescribeContainerGroupsResponse()
+ err = client.DoAction(request, response)
+ return
+}
+
+// DescribeContainerGroupsWithChan invokes the eci.DescribeContainerGroups API asynchronously
+// api document: https://help.aliyun.com/api/eci/describecontainergroups.html
+// asynchronous document: https://help.aliyun.com/document_detail/66220.html
+func (client *Client) DescribeContainerGroupsWithChan(request *DescribeContainerGroupsRequest) (<-chan *DescribeContainerGroupsResponse, <-chan error) {
+ responseChan := make(chan *DescribeContainerGroupsResponse, 1)
+ errChan := make(chan error, 1)
+ err := client.AddAsyncTask(func() {
+ defer close(responseChan)
+ defer close(errChan)
+ response, err := client.DescribeContainerGroups(request)
+ if err != nil {
+ errChan <- err
+ } else {
+ responseChan <- response
+ }
+ })
+ if err != nil {
+ errChan <- err
+ close(responseChan)
+ close(errChan)
+ }
+ return responseChan, errChan
+}
+
+// DescribeContainerGroupsWithCallback invokes the eci.DescribeContainerGroups API asynchronously
+// api document: https://help.aliyun.com/api/eci/describecontainergroups.html
+// asynchronous document: https://help.aliyun.com/document_detail/66220.html
+func (client *Client) DescribeContainerGroupsWithCallback(request *DescribeContainerGroupsRequest, callback func(response *DescribeContainerGroupsResponse, err error)) <-chan int {
+ result := make(chan int, 1)
+ err := client.AddAsyncTask(func() {
+ var response *DescribeContainerGroupsResponse
+ var err error
+ defer close(result)
+ response, err = client.DescribeContainerGroups(request)
+ callback(response, err)
+ result <- 1
+ })
+ if err != nil {
+ defer close(result)
+ callback(nil, err)
+ result <- 0
+ }
+ return result
+}
+
+// DescribeContainerGroupsRequest is the request struct for api DescribeContainerGroups
+type DescribeContainerGroupsRequest struct {
+ *requests.RpcRequest
+ ResourceOwnerId requests.Integer `position:"Query" name:"ResourceOwnerId"`
+ NextToken string `position:"Query" name:"NextToken"`
+ Limit requests.Integer `position:"Query" name:"Limit"`
+ Tags *[]DescribeContainerGroupsTag `position:"Query" name:"Tag" type:"Repeated"`
+ ContainerGroupId string `position:"Query" name:"ContainerGroupId"`
+ ResourceOwnerAccount string `position:"Query" name:"ResourceOwnerAccount"`
+ OwnerAccount string `position:"Query" name:"OwnerAccount"`
+ OwnerId requests.Integer `position:"Query" name:"OwnerId"`
+ VSwitchId string `position:"Query" name:"VSwitchId"`
+ ContainerGroupName string `position:"Query" name:"ContainerGroupName"`
+ ZoneId string `position:"Query" name:"ZoneId"`
+}
+
+// DescribeContainerGroupsTag is a repeated param struct in DescribeContainerGroupsRequest
+type DescribeContainerGroupsTag struct {
+ Key string `name:"Key"`
+ Value string `name:"Value"`
+}
+
+// DescribeContainerGroupsResponse is the response struct for api DescribeContainerGroups
+type DescribeContainerGroupsResponse struct {
+ *responses.BaseResponse
+ RequestId string `json:"RequestId" xml:"RequestId"`
+ NextToken string `json:"NextToken" xml:"NextToken"`
+ TotalCount int `json:"TotalCount" xml:"TotalCount"`
+ ContainerGroups []ContainerGroup `json:"ContainerGroups" xml:"ContainerGroups"`
+}
+
+// CreateDescribeContainerGroupsRequest creates a request to invoke DescribeContainerGroups API
+func CreateDescribeContainerGroupsRequest() (request *DescribeContainerGroupsRequest) {
+ request = &DescribeContainerGroupsRequest{
+ RpcRequest: &requests.RpcRequest{},
+ }
+ request.InitWithApiInfo("Eci", "2018-08-08", "DescribeContainerGroups", "eci", "openAPI")
+ return
+}
+
+// CreateDescribeContainerGroupsResponse creates a response to parse from DescribeContainerGroups response
+func CreateDescribeContainerGroupsResponse() (response *DescribeContainerGroupsResponse) {
+ response = &DescribeContainerGroupsResponse{
+ BaseResponse: &responses.BaseResponse{},
+ }
+ return
+}
diff --git a/eci/describe_container_log.go b/eci/describe_container_log.go
new file mode 100644
index 0000000..0768568
--- /dev/null
+++ b/eci/describe_container_log.go
@@ -0,0 +1,112 @@
+package eci
+
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+//
+// Code generated by Alibaba Cloud SDK Code Generator.
+// Changes may cause incorrect behavior and will be lost if the code is regenerated.
+
+import (
+ "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
+ "github.com/aliyun/alibaba-cloud-sdk-go/sdk/responses"
+)
+
+// DescribeContainerLog invokes the eci.DescribeContainerLog API synchronously
+// api document: https://help.aliyun.com/api/eci/describecontainerlog.html
+func (client *Client) DescribeContainerLog(request *DescribeContainerLogRequest) (response *DescribeContainerLogResponse, err error) {
+ response = CreateDescribeContainerLogResponse()
+ err = client.DoAction(request, response)
+ return
+}
+
+// DescribeContainerLogWithChan invokes the eci.DescribeContainerLog API asynchronously
+// api document: https://help.aliyun.com/api/eci/describecontainerlog.html
+// asynchronous document: https://help.aliyun.com/document_detail/66220.html
+func (client *Client) DescribeContainerLogWithChan(request *DescribeContainerLogRequest) (<-chan *DescribeContainerLogResponse, <-chan error) {
+ responseChan := make(chan *DescribeContainerLogResponse, 1)
+ errChan := make(chan error, 1)
+ err := client.AddAsyncTask(func() {
+ defer close(responseChan)
+ defer close(errChan)
+ response, err := client.DescribeContainerLog(request)
+ if err != nil {
+ errChan <- err
+ } else {
+ responseChan <- response
+ }
+ })
+ if err != nil {
+ errChan <- err
+ close(responseChan)
+ close(errChan)
+ }
+ return responseChan, errChan
+}
+
+// DescribeContainerLogWithCallback invokes the eci.DescribeContainerLog API asynchronously
+// api document: https://help.aliyun.com/api/eci/describecontainerlog.html
+// asynchronous document: https://help.aliyun.com/document_detail/66220.html
+func (client *Client) DescribeContainerLogWithCallback(request *DescribeContainerLogRequest, callback func(response *DescribeContainerLogResponse, err error)) <-chan int {
+ result := make(chan int, 1)
+ err := client.AddAsyncTask(func() {
+ var response *DescribeContainerLogResponse
+ var err error
+ defer close(result)
+ response, err = client.DescribeContainerLog(request)
+ callback(response, err)
+ result <- 1
+ })
+ if err != nil {
+ defer close(result)
+ callback(nil, err)
+ result <- 0
+ }
+ return result
+}
+
+// DescribeContainerLogRequest is the request struct for api DescribeContainerLog
+type DescribeContainerLogRequest struct {
+ *requests.RpcRequest
+ ResourceOwnerId requests.Integer `position:"Query" name:"ResourceOwnerId"`
+ ContainerName string `position:"Query" name:"ContainerName"`
+ StartTime string `position:"Query" name:"StartTime"`
+ ContainerGroupId string `position:"Query" name:"ContainerGroupId"`
+ ResourceOwnerAccount string `position:"Query" name:"ResourceOwnerAccount"`
+ Tail requests.Integer `position:"Query" name:"Tail"`
+ OwnerAccount string `position:"Query" name:"OwnerAccount"`
+ OwnerId requests.Integer `position:"Query" name:"OwnerId"`
+}
+
+// DescribeContainerLogResponse is the response struct for api DescribeContainerLog
+type DescribeContainerLogResponse struct {
+ *responses.BaseResponse
+ RequestId string `json:"RequestId" xml:"RequestId"`
+ ContainerName string `json:"ContainerName" xml:"ContainerName"`
+ Content string `json:"Content" xml:"Content"`
+}
+
+// CreateDescribeContainerLogRequest creates a request to invoke DescribeContainerLog API
+func CreateDescribeContainerLogRequest() (request *DescribeContainerLogRequest) {
+ request = &DescribeContainerLogRequest{
+ RpcRequest: &requests.RpcRequest{},
+ }
+ request.InitWithApiInfo("Eci", "2018-08-08", "DescribeContainerLog", "eci", "openAPI")
+ return
+}
+
+// CreateDescribeContainerLogResponse creates a response to parse from DescribeContainerLog response
+func CreateDescribeContainerLogResponse() (response *DescribeContainerLogResponse) {
+ response = &DescribeContainerLogResponse{
+ BaseResponse: &responses.BaseResponse{},
+ }
+ return
+}
diff --git a/eci/struct_config_file_to_path.go b/eci/struct_config_file_to_path.go
new file mode 100644
index 0000000..95477b7
--- /dev/null
+++ b/eci/struct_config_file_to_path.go
@@ -0,0 +1,22 @@
+package eci
+
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+//
+// Code generated by Alibaba Cloud SDK Code Generator.
+// Changes may cause incorrect behavior and will be lost if the code is regenerated.
+
+// ConfigFileVolumeConfigFileToPath is a nested struct in eci response
+type ConfigFileToPath struct {
+ Content string `name:"Content"`
+ Path string `name:"Path"`
+}
diff --git a/eci/struct_container.go b/eci/struct_container.go
new file mode 100644
index 0000000..1072c1a
--- /dev/null
+++ b/eci/struct_container.go
@@ -0,0 +1,34 @@
+package eci
+
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+//
+// Code generated by Alibaba Cloud SDK Code Generator.
+// Changes may cause incorrect behavior and will be lost if the code is regenerated.
+
+// Container is a nested struct in eci response
+type Container struct {
+ Name string `json:"Name" xml:"Name" `
+ Image string `json:"Image" xml:"Image"`
+ Memory float64 `json:"Memory" xml:"Memory"`
+ Cpu float64 `json:"Cpu" xml:"Cpu"`
+ RestartCount int `json:"RestartCount" xml:"RestartCount"`
+ WorkingDir string `json:"WorkingDir" xml:"WorkingDir"`
+ ImagePullPolicy string `json:"ImagePullPolicy" xml:"ImagePullPolicy"`
+ Commands []string `json:"Commands" xml:"Commands"`
+ Args []string `json:"Args" xml:"Args"`
+ PreviousState ContainerState `json:"PreviousState" xml:"PreviousState"`
+ CurrentState ContainerState `json:"CurrentState" xml:"CurrentState"`
+ VolumeMounts []VolumeMount `json:"VolumeMounts" xml:"VolumeMounts"`
+ Ports []ContainerPort `json:"Ports" xml:"Ports"`
+ EnvironmentVars []EnvironmentVar `json:"EnvironmentVars" xml:"EnvironmentVars"`
+}
diff --git a/eci/struct_container_group.go b/eci/struct_container_group.go
new file mode 100644
index 0000000..e5c0f67
--- /dev/null
+++ b/eci/struct_container_group.go
@@ -0,0 +1,38 @@
+package eci
+
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+//
+// Code generated by Alibaba Cloud SDK Code Generator.
+// Changes may cause incorrect behavior and will be lost if the code is regenerated.
+
+// ContainerGroup is a nested struct in eci response
+type ContainerGroup struct {
+ ContainerGroupId string `json:"ContainerGroupId" xml:"ContainerGroupId"`
+ ContainerGroupName string `json:"ContainerGroupName" xml:"ContainerGroupName"`
+ RegionId string `json:"RegionId" xml:"RegionId"`
+ ZoneId string `json:"ZoneId" xml:"ZoneId"`
+ Memory float64 `json:"Memory" xml:"Memory"`
+ Cpu float64 `json:"Cpu" xml:"Cpu"`
+ VSwitchId string `json:"VSwitchId" xml:"VSwitchId"`
+ SecurityGroupId string `json:"SecurityGroupId" xml:"SecurityGroupId"`
+ RestartPolicy string `json:"RestartPolicy" xml:"RestartPolicy"`
+ IntranetIp string `json:"IntranetIp" xml:"IntranetIp"`
+ Status string `json:"Status" xml:"Status"`
+ InternetIp string `json:"InternetIp" xml:"InternetIp"`
+ CreationTime string `json:"CreationTime" xml:"CreationTime"`
+ SucceededTime string `json:"SucceededTime" xml:"SucceededTime"`
+ Tags []Tag `json:"Tags" xml:"Tags"`
+ Events []Event `json:"Events" xml:"Events"`
+ Containers []Container `json:"Containers" xml:"Containers"`
+ Volumes []Volume `json:"Volumes" xml:"Volumes"`
+}
diff --git a/eci/struct_container_port.go b/eci/struct_container_port.go
new file mode 100644
index 0000000..cc0f1fc
--- /dev/null
+++ b/eci/struct_container_port.go
@@ -0,0 +1,26 @@
+package eci
+
+import (
+ "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
+)
+
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+//
+// Code generated by Alibaba Cloud SDK Code Generator.
+// Changes may cause incorrect behavior and will be lost if the code is regenerated.
+
+// ContainerPort is a nested struct in eci response
+type ContainerPort struct {
+ Port requests.Integer `name:"Port"`
+ Protocol string `name:"Protocol"`
+}
diff --git a/eci/struct_container_state.go b/eci/struct_container_state.go
new file mode 100644
index 0000000..64bf274
--- /dev/null
+++ b/eci/struct_container_state.go
@@ -0,0 +1,25 @@
+package eci
+
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+//
+// Code generated by Alibaba Cloud SDK Code Generator.
+// Changes may cause incorrect behavior and will be lost if the code is regenerated.
+
+// CurrentState is a nested struct in eci response
+type ContainerState struct {
+ State string `json:"State" xml:"State"`
+ DetailStatus string `json:"DetailStatus" xml:"DetailStatus"`
+ ExitCode int `json:"ExitCode" xml:"ExitCode"`
+ StartTime string `json:"StartTime" xml:"StartTime"`
+ FinishTime string `json:"FinishTime" xml:"FinishTime"`
+}
diff --git a/eci/struct_environment_var.go b/eci/struct_environment_var.go
new file mode 100644
index 0000000..404b5d9
--- /dev/null
+++ b/eci/struct_environment_var.go
@@ -0,0 +1,22 @@
+package eci
+
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+//
+// Code generated by Alibaba Cloud SDK Code Generator.
+// Changes may cause incorrect behavior and will be lost if the code is regenerated.
+
+// EnvironmentVar is a nested struct in eci response
+type EnvironmentVar struct {
+ Key string `name:"Key"`
+ Value string `name:"Value"`
+}
diff --git a/eci/struct_event.go b/eci/struct_event.go
new file mode 100644
index 0000000..30afd70
--- /dev/null
+++ b/eci/struct_event.go
@@ -0,0 +1,26 @@
+package eci
+
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+//
+// Code generated by Alibaba Cloud SDK Code Generator.
+// Changes may cause incorrect behavior and will be lost if the code is regenerated.
+
+// Event is a nested struct in eci response
+type Event struct {
+ Count int `json:"Count" xml:"Count"`
+ Type string `json:"Type" xml:"Type"`
+ Name string `json:"Name" xml:"Name"`
+ Message string `json:"Message" xml:"Message"`
+ FirstTimestamp string `json:"FirstTimestamp" xml:"FirstTimestamp"`
+ LastTimestamp string `json:"LastTimestamp" xml:"LastTimestamp"`
+}
diff --git a/eci/struct_tag.go b/eci/struct_tag.go
new file mode 100644
index 0000000..5362ecc
--- /dev/null
+++ b/eci/struct_tag.go
@@ -0,0 +1,22 @@
+package eci
+
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+//
+// Code generated by Alibaba Cloud SDK Code Generator.
+// Changes may cause incorrect behavior and will be lost if the code is regenerated.
+
+// Label is a nested struct in eci response
+type Tag struct {
+ Key string `name:"Key"`
+ Value string `name:"Value"`
+}
diff --git a/eci/struct_volume.go b/eci/struct_volume.go
new file mode 100644
index 0000000..1015d63
--- /dev/null
+++ b/eci/struct_volume.go
@@ -0,0 +1,35 @@
+package eci
+
+import "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
+
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+//
+// Code generated by Alibaba Cloud SDK Code Generator.
+// Changes may cause incorrect behavior and will be lost if the code is regenerated.
+
+// Volume is a nested struct in eci response
+const (
+ VOL_TYPE_NFS = "NFSVolume"
+ VOL_TYPE_EMPTYDIR = "EmptyDirVolume"
+ VOL_TYPE_CONFIGFILEVOLUME = "ConfigFileVolume"
+)
+
+type Volume struct {
+ Type string `name:"Type"`
+ Name string `name:"Name"`
+ NfsVolumePath string `name:"NFSVolume.Path"`
+ NfsVolumeServer string `name:"NFSVolume.Server"`
+ NfsVolumeReadOnly requests.Boolean `name:"NFSVolume.ReadOnly"`
+ EmptyDirVolumeEnable requests.Boolean `name:"EmptyDirVolume.Enable"`
+ ConfigFileToPaths []ConfigFileToPath `name:"ConfigFileVolume.ConfigFileToPath" type:"Repeated"`
+}
diff --git a/eci/struct_volume_mount.go b/eci/struct_volume_mount.go
new file mode 100644
index 0000000..2408159
--- /dev/null
+++ b/eci/struct_volume_mount.go
@@ -0,0 +1,25 @@
+package eci
+
+import "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
+
+//Licensed under the Apache License, Version 2.0 (the "License");
+//you may not use this file except in compliance with the License.
+//You may obtain a copy of the License at
+//
+//http://www.apache.org/licenses/LICENSE-2.0
+//
+//Unless required by applicable law or agreed to in writing, software
+//distributed under the License is distributed on an "AS IS" BASIS,
+//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+//See the License for the specific language governing permissions and
+//limitations under the License.
+//
+// Code generated by Alibaba Cloud SDK Code Generator.
+// Changes may cause incorrect behavior and will be lost if the code is regenerated.
+
+// VolumeMount is a nested struct in eci response
+type VolumeMount struct {
+ MountPath string `name:"MountPath"`
+ ReadOnly requests.Boolean `name:"ReadOnly"`
+ Name string `name:"Name"`
+}
diff --git a/errors.go b/errors.go
new file mode 100644
index 0000000..97807a4
--- /dev/null
+++ b/errors.go
@@ -0,0 +1,26 @@
+package alibabacloud
+
+import (
+ "net/http"
+
+ "github.com/aliyun/alibaba-cloud-sdk-go/sdk/errors"
+ "github.com/cpuguy83/strongerrors"
+)
+
+func wrapError(err error) error {
+ if err == nil {
+ return nil
+ }
+
+ se, ok := err.(*errors.ServerError)
+ if !ok {
+ return err
+ }
+
+ switch se.HttpStatus() {
+ case http.StatusNotFound:
+ return strongerrors.NotFound(err)
+ default:
+ return err
+ }
+}