diff --git a/contrib/podex/podex.go b/contrib/podex/podex.go index e5ccd4c2c74..ce270c24942 100644 --- a/contrib/podex/podex.go +++ b/contrib/podex/podex.go @@ -45,9 +45,10 @@ import ( const usage = "podex [-format=yaml|json] [-type=pod|container] [-id NAME] IMAGES..." -var manifestFormat = flag.String("format", "yaml", "manifest format to output, `yaml` or `json`") -var manifestType = flag.String("type", "pod", "manifest type to output, `pod` or `container`") -var manifestName = flag.String("name", "", "manifest name, default to image base name") +var flManifestFormat = flag.String("format", "yaml", "manifest format to output, `yaml` or `json`") +var flManifestType = flag.String("type", "pod", "manifest type to output, `pod` or `container`") +var flManifestName = flag.String("name", "", "manifest name, default to image base name") +var flDaemon = flag.Bool("daemon", false, "daemon mode") func init() { flag.Usage = func() { @@ -66,21 +67,54 @@ type image struct { func main() { flag.Parse() + if *flDaemon { + http.HandleFunc("/pods/", func(w http.ResponseWriter, r *http.Request) { + image := strings.TrimPrefix(r.URL.Path, "/pods/") + _, _, manifestName, _ := splitDockerImageName(image) + manifest, err := getManifest(manifestName, "pod", "json", image) + if err != nil { + errMessage := fmt.Sprintf("failed to generate pod manifest for image %q: %v", image, err) + log.Print(errMessage) + http.Error(w, errMessage, http.StatusInternalServerError) + return + } + io.Copy(w, manifest) + }) + log.Fatal(http.ListenAndServe(":8080", nil)) + } + if flag.NArg() < 1 { flag.Usage() log.Fatal("pod: missing image argument") } - if *manifestName == "" { + if *flManifestName == "" { if flag.NArg() > 1 { flag.Usage() log.Fatal("podex: -id arg is required when passing more than one image") } - _, _, *manifestName, _ = splitDockerImageName(flag.Arg(0)) + _, _, *flManifestName, _ = splitDockerImageName(flag.Arg(0)) + } + if *flManifestType != "pod" && *flManifestType != "container" { + flag.Usage() + log.Fatalf("unsupported manifest type %q", *flManifestType) + } + if *flManifestFormat != "yaml" && *flManifestFormat != "json" { + flag.Usage() + log.Fatalf("unsupported manifest format %q", *flManifestFormat) } + manifest, err := getManifest(*flManifestName, *flManifestType, *flManifestFormat, flag.Args()...) + if err != nil { + log.Fatalf("failed to generate %q manifest for %v: %v", *flManifestType, flag.Args(), err) + } + io.Copy(os.Stdout, manifest) +} + +// getManifest infers a pod (or container) manifest for a list of docker images. +func getManifest(manifestName, manifestType, manifestFormat string, images ...string) (io.Reader, error) { podContainers := []goyaml.MapSlice{} - for _, imageName := range flag.Args() { + for _, imageName := range images { host, namespace, repo, tag := splitDockerImageName(imageName) container := goyaml.MapSlice{ @@ -91,13 +125,13 @@ func main() { img, err := getImageMetadata(host, namespace, repo, tag) if err != nil { - log.Fatalf("failed to get image metadata %q: %v", imageName, err) + return nil, fmt.Errorf("failed to get image metadata %q: %v", imageName, err) } portSlice := []goyaml.MapSlice{} for p := range img.ContainerConfig.ExposedPorts { port, err := strconv.Atoi(p.Port()) if err != nil { - log.Fatalf("failed to parse port %q: %v", p.Port(), err) + return nil, fmt.Errorf("failed to parse port %q: %v", p.Port(), err) } portEntry := goyaml.MapSlice{{ Key: "name", @@ -125,15 +159,15 @@ func main() { var data interface{} - switch *manifestType { + switch manifestType { case "container": containerManifest = append(goyaml.MapSlice{ - {Key: "id", Value: *manifestName}, + {Key: "id", Value: manifestName}, }, containerManifest...) data = containerManifest case "pod": data = goyaml.MapSlice{ - {Key: "id", Value: *manifestName}, + {Key: "id", Value: manifestName}, {Key: "kind", Value: "Pod"}, {Key: "apiVersion", Value: "v1beta1"}, {Key: "desiredState", Value: goyaml.MapSlice{ @@ -141,32 +175,31 @@ func main() { }}, } default: - flag.Usage() - log.Fatalf("unsupported manifest type %q", *manifestType) + return nil, fmt.Errorf("unsupported manifest type %q", manifestFormat) } yamlBytes, err := goyaml.Marshal(data) if err != nil { - log.Fatalf("failed to marshal container manifest: %v", err) + return nil, fmt.Errorf("failed to marshal container manifest: %v", err) } - switch *manifestFormat { + switch manifestFormat { case "yaml": - os.Stdout.Write(yamlBytes) + return bytes.NewBuffer(yamlBytes), nil case "json": jsonBytes, err := yaml.YAMLToJSON(yamlBytes) if err != nil { - log.Fatalf("failed to marshal container manifest into JSON: %v", err) + return nil, fmt.Errorf("failed to marshal container manifest into JSON: %v", err) } var jsonPretty bytes.Buffer if err := json.Indent(&jsonPretty, jsonBytes, "", " "); err != nil { - log.Fatalf("failed to indent json %q: %v", string(jsonBytes), err) + return nil, fmt.Errorf("failed to indent json %q: %v", string(jsonBytes), err) } - io.Copy(os.Stdout, &jsonPretty) + return &jsonPretty, nil default: - flag.Usage() - log.Fatalf("unsupported manifest format %q", *manifestFormat) + return nil, fmt.Errorf("unsupported manifest format %q", manifestFormat) } + } // splitDockerImageName split a docker image name of the form [HOST/][NAMESPACE/]REPOSITORY[:TAG] @@ -229,8 +262,12 @@ func getImageMetadata(host, namespace, repo, tag string) (*imageMetadata, error) 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) + return nil, fmt.Errorf("error making request to %q: %v", host, err) } + if resp.StatusCode != 200 { + return nil, fmt.Errorf("error getting X-Docker-Token from %s: %q", host, resp.Status) + } + 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)