Files
virtual-kubelet/providers/hypersh/util.go

418 lines
11 KiB
Go

package hypersh
import (
"context"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"net/url"
"os"
"strings"
"time"
"github.com/docker/go-connections/nat"
"github.com/docker/go-connections/tlsconfig"
"github.com/hyperhq/hyper-api/types"
"github.com/hyperhq/hyper-api/types/container"
registrytypes "github.com/hyperhq/hyper-api/types/registry"
"github.com/hyperhq/hypercli/cliconfig"
"github.com/hyperhq/hypercli/opts"
"github.com/hyperhq/hypercli/pkg/jsonmessage"
"github.com/hyperhq/hypercli/pkg/term"
"github.com/hyperhq/hypercli/reference"
"github.com/hyperhq/hypercli/registry"
"k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
func (p *HyperProvider) getContainers(pod *v1.Pod) ([]container.Config, []container.HostConfig, error) {
containers := make([]container.Config, len(pod.Spec.Containers))
hostConfigs := make([]container.HostConfig, len(pod.Spec.Containers))
for x, ctr := range pod.Spec.Containers {
// Do container.Config
var c container.Config
c.Image = ctr.Image
c.Cmd = ctr.Command
ports := map[nat.Port]struct{}{}
portBindings := nat.PortMap{}
for _, p := range ctr.Ports {
//TODO: p.HostPort is 0 by default, but it's invalid in hyper.sh
if p.HostPort == 0 {
p.HostPort = p.ContainerPort
}
port, err := nat.NewPort(strings.ToLower(string(p.Protocol)), fmt.Sprintf("%d", p.HostPort))
if err != nil {
return nil, nil, fmt.Errorf("creating new port in container conversion failed: %v", err)
}
ports[port] = struct{}{}
portBindings[port] = []nat.PortBinding{
{
HostIP: "0.0.0.0",
HostPort: fmt.Sprintf("%d", p.HostPort),
},
}
}
c.ExposedPorts = ports
// TODO: do volumes
envs := make([]string, len(ctr.Env))
for z, e := range ctr.Env {
envs[z] = fmt.Sprintf("%s=%s", e.Name, e.Value)
}
c.Env = envs
// Do container.HostConfig
var hc container.HostConfig
cpuLimit := ctr.Resources.Limits.Cpu().Value()
memoryLimit := ctr.Resources.Limits.Memory().Value()
hc.Resources = container.Resources{
CPUShares: cpuLimit,
Memory: memoryLimit,
}
hc.PortBindings = portBindings
containers[x] = c
hostConfigs[x] = hc
}
return containers, hostConfigs, nil
}
func (p *HyperProvider) containerJSONToPod(container *types.ContainerJSON) (*v1.Pod, error) {
podName, found := container.Config.Labels[containerLabel]
if !found {
return nil, fmt.Errorf("can not found podName: key %q not found in container label", containerLabel)
}
nodeName, found := container.Config.Labels[nodeLabel]
if !found {
return nil, fmt.Errorf("can not found nodeName: key %q not found in container label", containerLabel)
}
created, err := time.Parse(time.RFC3339, container.Created)
if err != nil {
return nil, fmt.Errorf("parse Created time failed:%v", container.Created)
}
startedAt, err := time.Parse(time.RFC3339, container.State.StartedAt)
if err != nil {
return nil, fmt.Errorf("parse StartedAt time failed:%v", container.State.StartedAt)
}
finishedAt, err := time.Parse(time.RFC3339, container.State.FinishedAt)
if err != nil {
return nil, fmt.Errorf("parse FinishedAt time failed:%v", container.State.FinishedAt)
}
var (
podCondition v1.PodCondition
containerState v1.ContainerState
)
switch p.hyperStateToPodPhase(container.State.Status) {
case v1.PodPending:
podCondition = v1.PodCondition{
Type: v1.PodInitialized,
Status: v1.ConditionFalse,
}
containerState = v1.ContainerState{
Waiting: &v1.ContainerStateWaiting{},
}
case v1.PodRunning: // running
podCondition = v1.PodCondition{
Type: v1.PodReady,
Status: v1.ConditionTrue,
}
containerState = v1.ContainerState{
Running: &v1.ContainerStateRunning{
StartedAt: metav1.NewTime(startedAt),
},
}
case v1.PodSucceeded: // normal exit
podCondition = v1.PodCondition{
Type: v1.PodReasonUnschedulable,
Status: v1.ConditionFalse,
}
containerState = v1.ContainerState{
Terminated: &v1.ContainerStateTerminated{
ExitCode: int32(container.State.ExitCode),
FinishedAt: metav1.NewTime(finishedAt),
},
}
case v1.PodFailed: // exit with error
podCondition = v1.PodCondition{
Type: v1.PodReasonUnschedulable,
Status: v1.ConditionFalse,
}
containerState = v1.ContainerState{
Terminated: &v1.ContainerStateTerminated{
ExitCode: int32(container.State.ExitCode),
FinishedAt: metav1.NewTime(finishedAt),
Reason: container.State.Error,
},
}
default: //unkown
podCondition = v1.PodCondition{
Type: v1.PodReasonUnschedulable,
Status: v1.ConditionUnknown,
}
containerState = v1.ContainerState{}
}
pod := v1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: podName,
Namespace: "default",
CreationTimestamp: metav1.NewTime(created),
},
Spec: v1.PodSpec{
NodeName: nodeName,
Volumes: []v1.Volume{},
Containers: []v1.Container{
{
Name: podName,
Image: container.Config.Image,
Command: container.Config.Cmd,
},
},
},
Status: v1.PodStatus{
Phase: p.hyperStateToPodPhase(container.State.Status),
Conditions: []v1.PodCondition{podCondition},
Message: "",
Reason: "",
HostIP: "",
PodIP: container.NetworkSettings.IPAddress,
ContainerStatuses: []v1.ContainerStatus{
{
Name: podName,
RestartCount: int32(container.RestartCount),
Image: container.Config.Image,
ImageID: container.Image,
ContainerID: container.ID,
Ready: container.State.Running,
State: containerState,
},
},
},
}
return &pod, nil
}
func (p *HyperProvider) containerToPod(container *types.Container) (*v1.Pod, error) {
// TODO: convert containers into pods
podName, found := container.Labels[containerLabel]
if !found {
return nil, fmt.Errorf("can not found podName: key %q not found in container label", containerLabel)
}
nodeName, found := container.Labels[nodeLabel]
if !found {
return nil, fmt.Errorf("can not found nodeName: key %q not found in container label", containerLabel)
}
var (
podCondition v1.PodCondition
isReady bool = true
)
if strings.ToLower(string(container.State)) == strings.ToLower(string(v1.PodRunning)) {
podCondition = v1.PodCondition{
Type: v1.PodReady,
Status: v1.ConditionTrue,
}
} else {
podCondition = v1.PodCondition{
Type: v1.PodReasonUnschedulable,
Status: v1.ConditionFalse,
}
isReady = false
}
pod := v1.Pod{
TypeMeta: metav1.TypeMeta{
Kind: "Pod",
APIVersion: "v1",
},
ObjectMeta: metav1.ObjectMeta{
Name: podName,
Namespace: "default",
ClusterName: "",
UID: "",
CreationTimestamp: metav1.NewTime(time.Unix(container.Created, 0)),
},
Spec: v1.PodSpec{
NodeName: nodeName,
Volumes: []v1.Volume{},
Containers: []v1.Container{
{
Name: podName,
Image: container.Image,
Command: strings.Split(container.Command, " "),
Resources: v1.ResourceRequirements{},
},
},
},
Status: v1.PodStatus{
//Phase: "",
Conditions: []v1.PodCondition{podCondition},
Message: "",
Reason: "",
HostIP: "",
PodIP: "",
ContainerStatuses: []v1.ContainerStatus{
{
Name: container.Names[0],
Image: container.Image,
ImageID: container.ImageID,
ContainerID: container.ID,
Ready: isReady,
State: v1.ContainerState{},
},
},
},
}
return &pod, nil
}
func (p *HyperProvider) hyperStateToPodPhase(state string) v1.PodPhase {
switch strings.ToLower(state) {
case "created":
return v1.PodPending
case "restarting":
return v1.PodPending
case "running":
return v1.PodRunning
case "exited":
return v1.PodSucceeded
case "paused":
return v1.PodSucceeded
case "dead":
return v1.PodFailed
}
return v1.PodUnknown
}
func (p *HyperProvider) getServerHost(region string, tlsOptions *tlsconfig.Options) (host string, dft bool, err error) {
dft = false
host = region
if host == "" {
host = os.Getenv("HYPER_DEFAULT_REGION")
region = p.getDefaultRegion()
}
if _, err := url.ParseRequestURI(host); err != nil {
host = "tcp://" + region + "." + cliconfig.DefaultHyperEndpoint
dft = true
}
host, err = opts.ParseHost(tlsOptions != nil, host)
return
}
func (p *HyperProvider) getDefaultRegion() string {
cc, ok := p.configFile.CloudConfig[cliconfig.DefaultHyperFormat]
if ok && cc.Region != "" {
return cc.Region
}
return cliconfig.DefaultHyperRegion
}
func (p *HyperProvider) ensureImage(image string) error {
distributionRef, err := reference.ParseNamed(image)
if err != nil {
return err
}
if reference.IsNameOnly(distributionRef) {
distributionRef = reference.WithDefaultTag(distributionRef)
log.Printf("Using default tag: %s", reference.DefaultTag)
}
// Resolve the Repository name from fqn to RepositoryInfo
repoInfo, err := registry.ParseRepositoryInfo(distributionRef)
authConfig := p.resolveAuthConfig(p.configFile.AuthConfigs, repoInfo.Index)
encodedAuth, err := p.encodeAuthToBase64(authConfig)
if err != nil {
return err
}
options := types.ImagePullOptions{
RegistryAuth: encodedAuth,
All: false,
}
responseBody, err := p.hyperClient.ImagePull(context.Background(), distributionRef.String(), options)
if err != nil {
return err
}
defer responseBody.Close()
var (
outFd uintptr
isTerminalOut bool
)
_, stdout, _ := term.StdStreams()
if stdout != nil {
outFd, isTerminalOut = term.GetFdInfo(stdout)
}
jsonmessage.DisplayJSONMessagesStream(responseBody, stdout, outFd, isTerminalOut, nil)
return nil
}
func (p *HyperProvider) resolveAuthConfig(authConfigs map[string]types.AuthConfig, index *registrytypes.IndexInfo) types.AuthConfig {
configKey := index.Name
if index.Official {
configKey = p.electAuthServer()
}
// First try the happy case
if c, found := authConfigs[configKey]; found || index.Official {
return c
}
convertToHostname := func(url string) string {
stripped := url
if strings.HasPrefix(url, "http://") {
stripped = strings.Replace(url, "http://", "", 1)
} else if strings.HasPrefix(url, "https://") {
stripped = strings.Replace(url, "https://", "", 1)
}
nameParts := strings.SplitN(stripped, "/", 2)
return nameParts[0]
}
// Maybe they have a legacy config file, we will iterate the keys converting
// them to the new format and testing
for registry, ac := range authConfigs {
if configKey == convertToHostname(registry) {
return ac
}
}
// When all else fails, return an empty auth config
return types.AuthConfig{}
}
func (p *HyperProvider) electAuthServer() string {
serverAddress := registry.IndexServer
if info, err := p.hyperClient.Info(context.Background()); err != nil {
log.Printf("Warning: failed to get default registry endpoint from daemon (%v). Using system default: %s", err, serverAddress)
} else {
serverAddress = info.IndexServerAddress
}
return serverAddress
}
// encodeAuthToBase64 serializes the auth configuration as JSON base64 payload
func (p *HyperProvider) encodeAuthToBase64(authConfig types.AuthConfig) (string, error) {
buf, err := json.Marshal(authConfig)
if err != nil {
return "", err
}
return base64.URLEncoding.EncodeToString(buf), nil
}