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 + } +}