Files
diun/internal/app/diun.go

188 lines
4.8 KiB
Go

package app
import (
"encoding/json"
"fmt"
"sync/atomic"
"time"
"github.com/crazy-max/diun/internal/config"
"github.com/crazy-max/diun/internal/db"
"github.com/crazy-max/diun/internal/model"
"github.com/crazy-max/diun/internal/notif"
"github.com/crazy-max/diun/internal/utl"
"github.com/crazy-max/diun/pkg/docker"
"github.com/crazy-max/diun/pkg/docker/registry"
"github.com/hako/durafmt"
"github.com/rs/zerolog/log"
)
// Diun represents an active diun object
type Diun struct {
cfg *config.Config
db *db.Client
notif *notif.Client
locker uint32
}
// New creates new diun instance
func New(cfg *config.Config) (*Diun, error) {
// DB client
dbcli, err := db.New(cfg.Db)
if err != nil {
return nil, err
}
// Notification client
notifcli, err := notif.New(cfg.Notif, cfg.App)
if err != nil {
return nil, err
}
return &Diun{
cfg: cfg,
db: dbcli,
notif: notifcli,
}, nil
}
// Run starts diun process
func (di *Diun) Run() {
if !atomic.CompareAndSwapUint32(&di.locker, 0, 1) {
log.Warn().Msg("Already running")
return
}
defer atomic.StoreUint32(&di.locker, 0)
defer di.trackTime(time.Now(), "Finished, total time spent: ")
// Iterate items
for _, item := range di.cfg.Items {
reg, err := docker.NewRegistryClient(docker.RegistryOptions{
Os: di.cfg.Watch.Os,
Arch: di.cfg.Watch.Arch,
Username: item.Registry.Username,
Password: item.Registry.Password,
Timeout: time.Duration(item.Registry.Timeout) * time.Second,
InsecureTLS: item.Registry.InsecureTLS,
})
if err != nil {
log.Error().Err(err).Str("image", item.Image).Msg("Cannot create registry client")
continue
}
image, err := di.analyzeImage(item.Image, item, reg)
if err != nil {
log.Error().Err(err).Str("image", item.Image).Msg("Cannot analyze image")
}
if image.Domain != "" && item.WatchRepo {
di.analyzeRepo(image, item, reg)
}
}
}
func (di *Diun) analyzeImage(imageStr string, item model.Item, reg *docker.RegistryClient) (registry.Image, error) {
image, err := registry.ParseImage(imageStr)
if err != nil {
return registry.Image{}, fmt.Errorf("cannot parse image name %s: %v", item.Image, err)
}
if !di.isIncluded(image.Tag, item.IncludeTags) {
log.Warn().Str("image", image.String()).Msgf("Tag %s not included", image.Tag)
return image, nil
} else if di.isExcluded(image.Tag, item.ExcludeTags) {
log.Warn().Str("image", image.String()).Msgf("Tag %s excluded", image.Tag)
return image, nil
}
log.Debug().Str("image", image.String()).Msgf("Fetching manifest")
liveManifest, err := reg.Manifest(image)
if err != nil {
return image, err
}
b, _ := json.MarshalIndent(liveManifest, "", " ")
log.Debug().Msg(string(b))
dbManifest, err := di.db.GetManifest(image)
if err != nil {
return image, err
}
status := model.ImageStatusUnchange
if dbManifest.Name == "" {
status = model.ImageStatusNew
log.Info().Str("image", image.String()).Msgf("New image found")
} else if !liveManifest.Created.Equal(*dbManifest.Created) {
status = model.ImageStatusUpdate
log.Info().Str("image", image.String()).Msgf("Image update found")
} else {
log.Debug().Str("image", image.String()).Msgf("No changes")
return image, nil
}
if err := di.db.PutManifest(image, liveManifest); err != nil {
return image, err
}
log.Debug().Str("image", image.String()).Msg("Manifest saved to database")
di.notif.Send(model.NotifEntry{
Status: status,
Image: image,
Manifest: liveManifest,
})
return image, nil
}
func (di *Diun) analyzeRepo(image registry.Image, item model.Item, reg *docker.RegistryClient) {
tags, tagsCount, err := reg.Tags(image, item.MaxTags)
if err != nil {
log.Error().Err(err).Str("image", image.String()).Msg("Cannot retrieve tags")
return
}
log.Debug().Str("image", image.String()).Msgf("%d tag(s) found in repository. %d will be analyzed.", tagsCount, len(tags))
for _, tag := range tags {
imageStr := fmt.Sprintf("%s/%s:%s", image.Domain, image.Path, tag)
if _, err := di.analyzeImage(imageStr, item, reg); err != nil {
log.Error().Err(err).Str("image", imageStr).Msg("Cannot analyze image")
continue
}
}
}
// Close closes diun
func (di *Diun) Close() {
if err := di.db.Close(); err != nil {
log.Warn().Err(err).Msg("Cannot close database")
}
}
func (di *Diun) isIncluded(tag string, includes []string) bool {
if len(includes) == 0 {
return true
}
for _, include := range includes {
if utl.MatchString(include, tag) {
return true
}
}
return false
}
func (di *Diun) isExcluded(tag string, excludes []string) bool {
if len(excludes) == 0 {
return false
}
for _, exclude := range excludes {
if utl.MatchString(exclude, tag) {
return true
}
}
return false
}
func (di *Diun) trackTime(start time.Time, prefix string) {
log.Info().Msgf("%s%s", prefix, durafmt.ParseShort(time.Since(start)).String())
}