mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	Merge pull request #3313 from proppy/podex-registry-api
contrib/podex: use registry API to resolve image metadata
This commit is contained in:
		
										
											Binary file not shown.
										
									
								
							@@ -28,15 +28,18 @@ limitations under the License.
 | 
				
			|||||||
package main
 | 
					package main
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"flag"
 | 
						"flag"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"io/ioutil"
 | 
				
			||||||
	"log"
 | 
						"log"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
	"os"
 | 
						"os"
 | 
				
			||||||
	"strconv"
 | 
						"strconv"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
 | 
						"github.com/GoogleCloudPlatform/kubernetes/pkg/api/v1beta1"
 | 
				
			||||||
	dockerclient "github.com/fsouza/go-dockerclient"
 | 
					 | 
				
			||||||
	"github.com/ghodss/yaml"
 | 
						"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 generateYAML = flag.Bool("yaml", false, "generate yaml manifest")
 | 
				
			||||||
var podName = flag.String("id", "", "set pod name")
 | 
					var podName = flag.String("id", "", "set pod name")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type image struct {
 | 
				
			||||||
 | 
						Host      string
 | 
				
			||||||
 | 
						Namespace string
 | 
				
			||||||
 | 
						Image     string
 | 
				
			||||||
 | 
						Tag       string
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func main() {
 | 
				
			||||||
	flag.Parse()
 | 
						flag.Parse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -54,45 +64,38 @@ func main() {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	if *podName == "" {
 | 
						if *podName == "" {
 | 
				
			||||||
		if flag.NArg() > 1 {
 | 
							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) {
 | 
						if (!*generateJSON && !*generateYAML) || (*generateJSON && *generateYAML) {
 | 
				
			||||||
		log.Fatal(usage)
 | 
							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{}
 | 
						podContainers := []v1beta1.Container{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	for _, imageName := range flag.Args() {
 | 
						for _, imageName := range flag.Args() {
 | 
				
			||||||
		parts, baseName := parseDockerImage(imageName)
 | 
							host, namespace, repo, tag := splitDockerImageName(imageName)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		container := v1beta1.Container{
 | 
							container := v1beta1.Container{
 | 
				
			||||||
			Name:  baseName,
 | 
								Name:  repo,
 | 
				
			||||||
			Image: imageName,
 | 
								Image: imageName,
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// TODO(proppy): use the regitry API instead of the remote API to get image metadata.
 | 
							img, err := getImageMetadata(host, namespace, repo, tag)
 | 
				
			||||||
		img, err := docker.InspectImage(imageName)
 | 
					
 | 
				
			||||||
		if err != nil {
 | 
							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())
 | 
								port, err := strconv.Atoi(p.Port())
 | 
				
			||||||
			if err != nil {
 | 
								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{
 | 
								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,
 | 
									ContainerPort: port,
 | 
				
			||||||
				Protocol:      v1beta1.Protocol(strings.ToUpper(p.Proto())),
 | 
									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]
 | 
					// splitDockerImageName split a docker image name of the form [HOST/][NAMESPACE/]REPOSITORY[:TAG]
 | 
				
			||||||
// TODO: handle the TAG
 | 
					func splitDockerImageName(imageName string) (host, namespace, repo, tag string) {
 | 
				
			||||||
// Returns array of images name parts and base image name
 | 
						hostNamespaceImage := strings.Split(imageName, "/")
 | 
				
			||||||
func parseDockerImage(imageName string) (parts []string, baseName string) {
 | 
						last := len(hostNamespaceImage) - 1
 | 
				
			||||||
	// Parse docker image name
 | 
						repoTag := strings.Split(hostNamespaceImage[last], ":")
 | 
				
			||||||
	// IMAGE: [REGISTRYHOST/][USERNAME/]NAME[:TAG]
 | 
						repo = repoTag[0]
 | 
				
			||||||
	// NAME: [a-z0-9-_.]
 | 
						if len(repoTag) > 1 {
 | 
				
			||||||
	parts = strings.Split(imageName, "/")
 | 
							tag = repoTag[1]
 | 
				
			||||||
	baseName = parts[len(parts)-1]
 | 
						}
 | 
				
			||||||
 | 
						switch len(hostNamespaceImage) {
 | 
				
			||||||
 | 
						case 2:
 | 
				
			||||||
 | 
							host = ""
 | 
				
			||||||
 | 
							namespace = hostNamespaceImage[0]
 | 
				
			||||||
 | 
						case 3:
 | 
				
			||||||
 | 
							host = hostNamespaceImage[0]
 | 
				
			||||||
 | 
							namespace = hostNamespaceImage[1]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return
 | 
						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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										91
									
								
								contrib/podex/podex_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										91
									
								
								contrib/podex/podex_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -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)
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user