/ cmd / main.go
main.go
  1  package main
  2  
  3  import (
  4  	"context"
  5  	"fmt"
  6  	"log"
  7  	"net/http"
  8  	"os"
  9  	"strings"
 10  	"sync"
 11  
 12  	"github.com/docker/docker/api/types"
 13  	containertypes "github.com/docker/docker/api/types/container"
 14  	"github.com/docker/docker/client"
 15  	"github.com/robfig/cron"
 16  )
 17  
 18  type ImageUpdate struct {
 19  	ImageName       string
 20  	CurrentHash     string
 21  	LatestHash      string
 22  	UpdateAvailable bool
 23  	Architecture    string
 24  	ImageCreated    string
 25  }
 26  
 27  type Image struct {
 28  	Architecture string `json:"architecture"`
 29  	Digest       string `json:"digest"`
 30  }
 31  
 32  type Repository struct {
 33  	Creator int     `json:"creator"`
 34  	ID      int     `json:"id"`
 35  	Digest  string  `json:"digest"`
 36  	Images  []Image `json:"images"`
 37  }
 38  
 39  var offlineImages = make(map[string]bool)
 40  
 41  func checkOffline(container types.Container, namespace, repository, tag string) bool {
 42  	statusCode, _ := pingDockerhub(namespace, repository, tag)
 43  	if statusCode == http.StatusNotFound {
 44  		offlineImages[container.Image] = true
 45  		log.Printf("image %s is not available on dockerhub. likely local image.", container.Image)
 46  		return true
 47  	}
 48  	return false
 49  }
 50  
 51  func updates(containers []types.Container, ctx context.Context, cli *client.Client) []ImageUpdate {
 52  	var updates []ImageUpdate
 53  	var wg sync.WaitGroup
 54  	results := make(chan ImageUpdate, len(containers))
 55  
 56  	for _, container := range containers {
 57  		wg.Add(1)
 58  		go func(container types.Container) {
 59  			defer wg.Done()
 60  
 61  			namespace, repository, tag := parseImageName(container.Image)
 62  
 63  			// check for offline images
 64  			for range offlineImages {
 65  				if offlineImages[container.Image] {
 66  					return
 67  				}
 68  			}
 69  			if checkOffline(container, namespace, repository, tag) {
 70  				return
 71  			}
 72  
 73  			imageName := fmt.Sprintf("%s/%s:%s", namespace, repository, tag)
 74  			currentHash, arch, imageCreated := getCurrentHash(ctx, cli, imageName)
 75  
 76  			log.Printf("checking updates for %s", imageName)
 77  			latestHash, err := getLatestHash(namespace, repository, tag, arch)
 78  			log.Printf("namespace: %s, repository: %s, tag: %s, arch: %s", namespace, repository, tag, arch)
 79  
 80  			log.Printf("current hash for %s is: %s", container.Image, currentHash)
 81  			log.Printf("latest hash for %s is: %s", container.Image, latestHash)
 82  
 83  			if err != nil {
 84  				log.Fatal(err)
 85  			}
 86  
 87  			updateAvailable := strings.SplitN(currentHash, ":", 2)[1] != strings.SplitN(latestHash, ":", 2)[1]
 88  
 89  			results <- ImageUpdate{
 90  				ImageName:       imageName,
 91  				CurrentHash:     currentHash,
 92  				LatestHash:      latestHash,
 93  				UpdateAvailable: updateAvailable,
 94  				Architecture:    arch,
 95  				ImageCreated:    imageCreated,
 96  			}
 97  		}(container)
 98  	}
 99  
100  	go func() {
101  		wg.Wait()
102  		close(results)
103  	}()
104  
105  	for result := range results {
106  		updates = append(updates, result)
107  	}
108  
109  	return updates
110  }
111  
112  func cronJob(containers []types.Container, ctx context.Context, cli *client.Client) {
113  	imageUpdate := updates(containers, ctx, cli)
114  	generateRSSFeed(imageUpdate)
115  }
116  func main() {
117  	ctx := context.Background()
118  	cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation())
119  	if err != nil {
120  		log.Fatalf("Error creating Docker client: %v", err)
121  	}
122  	defer cli.Close()
123  
124  	containers, err := cli.ContainerList(ctx, containertypes.ListOptions{})
125  	if err != nil {
126  		log.Fatal("panic containers: ", err)
127  	}
128  
129  	initFeed()
130  	http.HandleFunc("/feed", feedHandler)
131  
132  	go func() {
133  		log.Fatal(http.ListenAndServe("0.0.0.0:8083", nil))
134  	}()
135  
136  	log.Println("docker-rss server started at 0.0.0.0:8083...")
137  	log.Printf("cronjob expression specified: %s", os.Getenv("UPDATE_SCHEDULE"))
138  
139  	c := cron.New()
140  
141  	c.AddFunc(os.Getenv("UPDATE_SCHEDULE"), func() {
142  		cronJob(containers, ctx, cli)
143  	})
144  
145  	c.Start()
146  	select {}
147  }