Skip to content

Instantly share code, notes, and snippets.

@hex108
Last active August 29, 2015 14:19
Show Gist options
  • Save hex108/721d47ba9a3a2fc901fb to your computer and use it in GitHub Desktop.
Save hex108/721d47ba9a3a2fc901fb to your computer and use it in GitHub Desktop.
A tool for two purposes: 1. Check repositories to find those broken ones. 2. Draw repository's image dependence using dot
package main
import (
"flag"
"fmt"
"io/ioutil"
"net/http"
"encoding/json"
"strings"
"container/list"
"os"
)
type Repository struct {
Name string
Description string
}
// the result of /v1/search
type Repositories struct{
Num_results int
Results []Repository
}
// Get a list of repositories
func getRepositories(hostUrl string) (repos Repositories, err error) {
body, err := getConent(hostUrl + "/v1/search")
if err != nil {
fmt.Errorf("Failed to get repositories from " + hostUrl)
return
}
err = json.Unmarshal(body, &repos)
if err != nil {
fmt.Errorf("Failed to unmarshal " + string(body))
return
}
return
}
// Check all repositories, and find broken repositories
func checkRepositories(hostUrl string, repos Repositories) {
// record whether image exists
imageRecords := make(map[string]bool)
for i := 0; i < repos.Num_results; i ++ {
repoName := repos.Results[i].Name
fmt.Println("Checking repository : " + repoName)
tagsInfo, err := getRepositoryTags(hostUrl, repoName)
if err != nil {
fmt.Errorf("Failed to get tags for " + repoName)
continue
}
for tagName, imageId := range tagsInfo {
fmt.Print("--> " + repoName + ":" + tagName + " : ")
if checkImage(hostUrl, imageId, imageRecords) {
fmt.Println("OK")
} else {
fmt.Println("Broken")
}
}
}
fmt.Println("Done.")
}
// check whether a image and its ancestries exist
func checkImage(hostUrl string, imageId string, imageRecords map[string]bool) bool{
value, present := imageRecords[imageId]
if present {
return value
}
ancestry, err := getImageAncestry(hostUrl, imageId)
if err != nil {
fmt.Errorf("Failed to get ancestry: " + imageId)
return false
}
for _, ancestryImageId := range ancestry {
if ancestryImageId == imageId {
continue
}
//fmt.Println("Checking ancestry : " + ancestryImageId)
ok := checkImage(hostUrl, ancestryImageId, imageRecords)
// record it to avoid repeated check for a same image
imageRecords[ancestryImageId] = ok
if !ok {
return false
}
}
return true
}
func printRepositories(hostUrl string, repos Repositories) {
for i := 0; i < repos.Num_results; i ++ {
repoName := repos.Results[i].Name
fmt.Println("Repository : " + repoName)
tagsInfo, err := getRepositoryTags(hostUrl, repoName)
if err != nil {
fmt.Errorf("Failed to get tags for " + repoName)
continue
}
for tagName, imageId := range tagsInfo {
fmt.Println("tag : " + tagName + ", images ID: " + imageId)
//getImageList(imageId)
ancestry, err := getImageAncestry(hostUrl, imageId)
if err != nil {
fmt.Errorf("Failed to get ancestry: " + imageId)
continue
}
for _, image := range ancestry {
fmt.Println("--> " + image)
}
}
}
}
// GET /v1/repositories/(namespace)/(repository)/tags
func getRepositoryTags(hostUrl string, repo string) (tags map[string]string, err error) {
body, err := getConent(hostUrl + "/v1/repositories/" + repo + "/tags")
if err != nil {
fmt.Errorf("Failed to get tags for " + repo)
return
}
tags = make(map[string]string)
json.Unmarshal(body, &tags)
if err != nil {
fmt.Errorf("Failed to unmarshal " + string(body))
return
}
return
}
// GET /v1/images/(image_id)/ancestry
func getImageAncestry(hostUrl string, imageId string) (ancestry []string, err error) {
ancestryInfo, err := getConent(hostUrl + "/v1/images/" + imageId + "/ancestry")
if err != nil {
fmt.Println("Failed to get ancestry for " + imageId)
return nil, err
}
err = json.Unmarshal(ancestryInfo, &ancestry)
if err != nil {
//fmt.Println("Failed to unmarshal " + string(ancestryInfo))
return nil, err
}
return
}
// Draw repository's image dependence using dot
func drawRepository(hostUrl string, repositoryWithTag string, outFile string) {
imageId, err := getImageIdForTag(hostUrl, repositoryWithTag)
if err != nil {
fmt.Errorf("Failed to get image id for repository : " + repositoryWithTag)
return
}
out, err := os.Create(outFile)
if err != nil {
fmt.Errorf("Failed to create " + outFile)
return
}
defer out.Close()
writeHead(out, repositoryWithTag)
writeBody(out, repositoryWithTag, imageId[0:9])
imageVisited := make(map[string]bool)
imagesToDraw := list.New()
imagesToDraw.PushBack(imageId)
for imagesToDraw.Len() != 0 {
curElement := imagesToDraw.Remove(imagesToDraw.Front())
curImageId := curElement.(string)
_, visited := imageVisited[curImageId]
if visited {
continue
} else {
imageVisited[curImageId] = true
}
ancestry, err := getImageAncestry(hostUrl, curImageId)
if err != nil {
fmt.Errorf("Failed to get ancestry for " + curImageId)
return
}
for _, ancestryImage := range ancestry {
_, visited := imageVisited[ancestryImage]
if ancestryImage != curImageId && !visited {
imagesToDraw.PushBack(ancestryImage)
fmt.Println(curImageId + " -> " + ancestryImage)
writeBody(out, curImageId[0:9], ancestryImage[0:9])
}
}
}
writeTail(out)
}
func writeHead(file *os.File, grahName string) {
file.WriteString("digraph \"" + grahName + "\" {\n")
file.WriteString(" node [shape = rect]\n")
}
func writeBody(file *os.File, node1 string, node2 string) {
file.WriteString(" \"" + node1 + "\" -> \"" + node2 + "\";\n")
}
func writeTail(file *os.File) {
file.WriteString("}\n")
}
// Get image id for a particular tag
// GET /v1/repositories/(namespace)/(repository)/tags/(tag*)
func getImageIdForTag(hostUrl string, repositoryWithTag string) (imageId string, err error) {
if !strings.Contains(repositoryWithTag, ":") {
repositoryWithTag += ":latest"
}
tmp := strings.Split(repositoryWithTag, ":")
repository, tag := tmp[0], tmp[1]
// TODO: need deal with library?
content, err := getConent(hostUrl + "/v1/repositories/" + repository + "/tags/" + tag)
if err != nil{
fmt.Errorf("Failed to get image id for tag : " + repositoryWithTag)
return
}
err = json.Unmarshal(content, &imageId)
if err != nil {
fmt.Errorf("Failed to unmarshal " + string(content))
return
}
return
}
// Get the body content by 'GET'
func getConent(url string) (data []byte, err error) {
resp, err := http.Get(url)
if err != nil {
fmt.Errorf("Failed to get content from" + url)
return nil, err
}
defer resp.Body.Close()
data, err = ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Errorf("Failed to read body of " + url)
return nil, err
}
return data, nil
}
func printUsage() {
flag.PrintDefaults()
}
func main() {
var hostUrl,repositoryWithTag,outfile string
var checkAll bool
flag.BoolVar(&checkAll, "check", false, "Check all repositories")
flag.StringVar(&repositoryWithTag, "draw", "", "Draw repository's image dependence using dot")
flag.StringVar(&hostUrl, "host", "registry.oa.com:80", "Registry host url, e.g: docker.oa.com:8080")
flag.StringVar(&outfile, "o", "test.dot", "Generate dot file for drawing repository's image dependence")
flag.Bool("help", false, "Print help message")
flag.Parse()
if !strings.HasPrefix(hostUrl, "http://"){
hostUrl = "http://" + hostUrl
}
if checkAll {
repos, err := getRepositories(hostUrl)
if err != nil {
fmt.Errorf("Failed to get repositories from " + hostUrl)
} else {
// printRepositories(hostUrl, repos)
checkRepositories(hostUrl, repos)
}
} else if repositoryWithTag != "" {
fmt.Println("Draw images ...")
drawRepository(hostUrl, repositoryWithTag, outfile)
} else {
printUsage()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment