diff --git a/contrib/podex/podex b/contrib/podex/podex deleted file mode 100755 index 5e216f5be4e..00000000000 Binary files a/contrib/podex/podex and /dev/null differ diff --git a/contrib/podex/podex.go b/contrib/podex/podex.go index 8f5760d3aa6..5f0ee3679fa 100644 --- a/contrib/podex/podex.go +++ b/contrib/podex/podex.go @@ -28,15 +28,18 @@ limitations under the License. package main import ( + "bytes" "encoding/json" "flag" + "fmt" + "io/ioutil" "log" + "net/http" "os" "strconv" "strings" "github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1" - dockerclient "github.com/fsouza/go-dockerclient" "github.com/ghodss/yaml" ) @@ -46,6 +49,13 @@ var generateJSON = flag.Bool("json", false, "generate json manifest") var generateYAML = flag.Bool("yaml", false, "generate yaml manifest") var podName = flag.String("id", "", "set pod name") +type image struct { + Host string + Namespace string + Image string + Tag string +} + func main() { flag.Parse() @@ -54,45 +64,38 @@ func main() { } if *podName == "" { if flag.NArg() > 1 { - log.Fatal(usage) + log.Print(usage) + log.Fatal("podex: -id arg is required when passing more than one image") } - _, *podName = parseDockerImage(flag.Arg(0)) + _, _, *podName, _ = splitDockerImageName(flag.Arg(0)) } if (!*generateJSON && !*generateYAML) || (*generateJSON && *generateYAML) { log.Fatal(usage) } - dockerHost := os.Getenv("DOCKER_HOST") - if dockerHost == "" { - log.Fatalf("DOCKER_HOST is not set") - } - docker, err := dockerclient.NewClient(dockerHost) - if err != nil { - log.Fatalf("failed to connect to %q: %v", dockerHost, err) - } - podContainers := []v1beta1.Container{} for _, imageName := range flag.Args() { - parts, baseName := parseDockerImage(imageName) + host, namespace, repo, tag := splitDockerImageName(imageName) + container := v1beta1.Container{ - Name: baseName, + Name: repo, Image: imageName, } - // TODO(proppy): use the regitry API instead of the remote API to get image metadata. - img, err := docker.InspectImage(imageName) + img, err := getImageMetadata(host, namespace, repo, tag) + if err != nil { - log.Fatalf("failed to inspect image %q: %v", imageName, err) + log.Fatalf("failed to get image metadata %q: %v", imageName, err) } - for p := range img.Config.ExposedPorts { + for p := range img.ContainerConfig.ExposedPorts { port, err := strconv.Atoi(p.Port()) if err != nil { - log.Fatalf("failed to parse port %q: %v", parts[0], err) + log.Fatalf("failed to parse port %q: %v", p.Port(), err) } container.Ports = append(container.Ports, v1beta1.Port{ - Name: strings.Join([]string{baseName, p.Proto(), p.Port()}, "-"), + Name: strings.Join([]string{repo, p.Proto(), p.Port()}, "-"), ContainerPort: port, Protocol: v1beta1.Protocol(strings.ToUpper(p.Proto())), }) @@ -126,14 +129,98 @@ func main() { } } -// parseDockerImage split a docker image name of the form [REGISTRYHOST/][USERNAME/]NAME[:TAG] -// TODO: handle the TAG -// Returns array of images name parts and base image name -func parseDockerImage(imageName string) (parts []string, baseName string) { - // Parse docker image name - // IMAGE: [REGISTRYHOST/][USERNAME/]NAME[:TAG] - // NAME: [a-z0-9-_.] - parts = strings.Split(imageName, "/") - baseName = parts[len(parts)-1] +// splitDockerImageName split a docker image name of the form [HOST/][NAMESPACE/]REPOSITORY[:TAG] +func splitDockerImageName(imageName string) (host, namespace, repo, tag string) { + hostNamespaceImage := strings.Split(imageName, "/") + last := len(hostNamespaceImage) - 1 + repoTag := strings.Split(hostNamespaceImage[last], ":") + repo = repoTag[0] + if len(repoTag) > 1 { + tag = repoTag[1] + } + switch len(hostNamespaceImage) { + case 2: + host = "" + namespace = hostNamespaceImage[0] + case 3: + host = hostNamespaceImage[0] + namespace = hostNamespaceImage[1] + } return } + +type Port string + +func (p Port) Port() string { + parts := strings.Split(string(p), "/") + return parts[0] +} + +func (p Port) Proto() string { + parts := strings.Split(string(p), "/") + if len(parts) == 1 { + return "tcp" + } + return parts[1] +} + +type imageMetadata struct { + ID string `json:"id"` + ContainerConfig struct { + ExposedPorts map[Port]struct{} + } `json:"container_config"` +} + +func getImageMetadata(host, namespace, repo, tag string) (*imageMetadata, error) { + if host == "" { + host = "index.docker.io" + } + if namespace == "" { + namespace = "library" + } + if tag == "" { + tag = "latest" + } + req, err := http.NewRequest("GET", fmt.Sprintf("https://%s/v1/repositories/%s/%s/images", host, namespace, repo), nil) + + if err != nil { + return nil, fmt.Errorf("error creating request: %v", err) + } + req.Header.Add("X-Docker-Token", "true") + resp, err := http.DefaultClient.Do(req) + if err != nil { + return nil, fmt.Errorf("error getting X-Docker-Token from index.docker.io: %v", err) + } + endpoints := resp.Header.Get("X-Docker-Endpoints") + token := resp.Header.Get("X-Docker-Token") + req, err = http.NewRequest("GET", fmt.Sprintf("https://%s/v1/repositories/%s/%s/tags/%s", endpoints, namespace, repo, tag), nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %v", err) + } + req.Header.Add("Authorization", "Token "+token) + resp, err = http.DefaultClient.Do(req) + if err != nil { + return nil, fmt.Errorf("error getting image id for %s/%s:%s %v", namespace, repo, tag, err) + } + var imageID string + if err = json.NewDecoder(resp.Body).Decode(&imageID); err != nil { + return nil, fmt.Errorf("error decoding image id: %v", err) + } + req, err = http.NewRequest("GET", fmt.Sprintf("https://%s/v1/images/%s/json", endpoints, imageID), nil) + if err != nil { + return nil, fmt.Errorf("error creating request: %v", err) + } + req.Header.Add("Authorization", "Token "+token) + resp, err = http.DefaultClient.Do(req) + if err != nil { + return nil, fmt.Errorf("error getting json for image %q: %v", imageID, err) + } + data, _ := ioutil.ReadAll(resp.Body) + buf := bytes.NewBuffer(data) + log.Print(string(data)) + var image imageMetadata + if err := json.NewDecoder(buf).Decode(&image); err != nil { + return nil, fmt.Errorf("error decoding image %q metadata: %v", imageID, err) + } + return &image, nil +} diff --git a/contrib/podex/podex_test.go b/contrib/podex/podex_test.go new file mode 100644 index 00000000000..083396f93af --- /dev/null +++ b/contrib/podex/podex_test.go @@ -0,0 +1,91 @@ +/* +Copyright 2014 Google Inc. All rights reserved. + +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. +*/ + +package main + +import "testing" + +func TestSplitDockerImageName(t *testing.T) { + tests := []struct { + name string + expectedHost string + expectedNamespace string + expectedRepo string + expectedTag string + }{ + { + name: "foo", + expectedRepo: "foo", + }, + { + name: "foo:bar", + expectedRepo: "foo", + expectedTag: "bar", + }, + { + name: "me/foo", + expectedNamespace: "me", + expectedRepo: "foo", + }, + { + name: "me/foo:bar", + expectedNamespace: "me", + expectedRepo: "foo", + expectedTag: "bar", + }, + { + name: "example.com/me/foo", + expectedHost: "example.com", + expectedNamespace: "me", + expectedRepo: "foo", + }, + { + name: "example.com/me/foo:bar", + expectedHost: "example.com", + expectedNamespace: "me", + expectedRepo: "foo", + expectedTag: "bar", + }, + { + name: "localhost:8080/me/foo", + expectedHost: "localhost:8080", + expectedNamespace: "me", + expectedRepo: "foo", + }, + { + name: "localhost:8080/me/foo:bar", + expectedHost: "localhost:8080", + expectedNamespace: "me", + expectedRepo: "foo", + expectedTag: "bar", + }, + } + for _, test := range tests { + host, namespace, repo, tag := splitDockerImageName(test.name) + if host != test.expectedHost { + t.Errorf("expected host %q got %q", test.expectedHost, host) + } + if namespace != test.expectedNamespace { + t.Errorf("expected namespace %q got %q", test.expectedNamespace, namespace) + } + if repo != test.expectedRepo { + t.Errorf("expected repo %q got %q", test.expectedRepo, repo) + } + if tag != test.expectedTag { + t.Errorf("expected tag %q got %q", test.expectedTag, tag) + } + } +}