From 1115234010ab1758abb6e352a95c9a7edc28c992 Mon Sep 17 00:00:00 2001 From: CrazyMax <1951866+crazy-max@users.noreply.github.com> Date: Wed, 26 May 2021 18:18:10 +0200 Subject: [PATCH] Add CLI to interact with Diun through gRPC (#382) Add simple CLI to interact with Diun through gRPC Create image and notif proto services Compile and validate protos through a dedicated Dockerfile and bake target Implement proto definitions Move server as `serve` command New commands `image` and `notif` Refactor command line usage doc Better CLI error handling Tools build constraint to manage tools deps through go modules Add upgrade notes Co-authored-by: CrazyMax --- .github/workflows/build.yml | 19 +- .github/workflows/validate.yml | 28 - Dockerfile | 3 +- cmd/cli.go | 34 + cmd/image.go | 131 +++ cmd/main.go | 104 +-- cmd/notif.go | 30 + cmd/serve.go | 99 +++ docker-bake.hcl | 26 +- docs/config/index.md | 2 +- docs/config/regopts.md | 12 +- docs/config/watch.md | 4 +- docs/faq.md | 9 +- docs/install/binary.md | 4 +- docs/migration/v3-to-v4.md | 3 +- docs/migration/v4.0-to-v4.17.md | 15 + docs/usage/cli.md | 44 - docs/usage/command-line.md | 124 +++ go.mod | 8 +- go.sum | 30 +- hack/gen.Dockerfile | 40 + internal/app/diun.go | 84 +- internal/config/config.go | 14 +- internal/config/config_test.go | 76 +- internal/config/fixtures/config.err.notif.yml | 2 - internal/db/manifest.go | 54 +- internal/db/metadata.go | 5 +- internal/db/migrate.go | 6 +- internal/grpc/client.go | 58 ++ internal/grpc/image.go | 123 +++ internal/grpc/logger/logger.go | 94 +++ internal/grpc/notif.go | 72 ++ internal/logging/logger.go | 20 +- internal/model/cli.go | 16 - internal/notif/client.go | 5 + mkdocs.yml | 3 +- pb/gen.go | 3 + pb/image.pb.go | 764 ++++++++++++++++++ pb/image.proto | 53 ++ pb/image_grpc.pb.go | 173 ++++ pb/notif.pb.go | 202 +++++ pb/notif.proto | 14 + pb/notif_grpc.pb.go | 101 +++ tools.go | 8 + 44 files changed, 2376 insertions(+), 343 deletions(-) delete mode 100644 .github/workflows/validate.yml create mode 100644 cmd/cli.go create mode 100644 cmd/image.go create mode 100644 cmd/notif.go create mode 100644 cmd/serve.go create mode 100644 docs/migration/v4.0-to-v4.17.md delete mode 100644 docs/usage/cli.md create mode 100644 docs/usage/command-line.md create mode 100644 hack/gen.Dockerfile delete mode 100644 internal/config/fixtures/config.err.notif.yml create mode 100644 internal/grpc/client.go create mode 100644 internal/grpc/image.go create mode 100644 internal/grpc/logger/logger.go create mode 100644 internal/grpc/notif.go delete mode 100644 internal/model/cli.go create mode 100644 pb/gen.go create mode 100644 pb/image.pb.go create mode 100644 pb/image.proto create mode 100644 pb/image_grpc.pb.go create mode 100644 pb/notif.pb.go create mode 100644 pb/notif.proto create mode 100644 pb/notif_grpc.pb.go create mode 100644 tools.go diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index b4a83952..4acf74e6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,6 +25,21 @@ env: GHCR_SLUG: ghcr.io/crazy-max/diun jobs: + validate: + runs-on: ubuntu-latest + steps: + - + name: Checkout + uses: actions/checkout@v2 + - + name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - + name: Validate + uses: docker/bake-action@v1 + with: + targets: validate + test: runs-on: ubuntu-latest steps: @@ -49,7 +64,9 @@ jobs: build: runs-on: ubuntu-latest - needs: [ test ] + needs: + - validate + - test steps: - name: Checkout diff --git a/.github/workflows/validate.yml b/.github/workflows/validate.yml deleted file mode 100644 index 0ffa1500..00000000 --- a/.github/workflows/validate.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: validate - -on: - push: - branches: - - 'master' - tags: - - 'v*' - - 'dockerfile/*' - pull_request: - branches: - - 'master' - -jobs: - validate: - runs-on: ubuntu-latest - steps: - - - name: Checkout - uses: actions/checkout@v2 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - - name: Validate - uses: docker/bake-action@v1 - with: - targets: validate diff --git a/Dockerfile b/Dockerfile index e3c0a036..305893b3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,7 @@ RUN --mount=type=bind,target=/src,rw \ --dist "/out" \ --hooks="go mod tidy" \ --hooks="go mod download" \ - --main="./cmd/main.go" \ + --main="./cmd" \ --ldflags="-s -w -X 'main.version={{.Version}}'" \ --files="CHANGELOG.md" \ --files="LICENSE" \ @@ -44,3 +44,4 @@ ENV PROFILER_PATH="/profiler" \ VOLUME [ "/data" ] ENTRYPOINT [ "diun" ] +CMD [ "serve" ] diff --git a/cmd/cli.go b/cmd/cli.go new file mode 100644 index 00000000..441abd80 --- /dev/null +++ b/cmd/cli.go @@ -0,0 +1,34 @@ +package main + +import ( + "github.com/crazy-max/diun/v4/pb" + "google.golang.org/grpc" +) + +// CliHandler is a cli interface +type CliHandler interface { + BeforeApply() error +} + +// CliGlobals holds globals cli attributes +type CliGlobals struct { + CliHandler `kong:"-"` + + conn *grpc.ClientConn `kong:"-"` + imageSvc pb.ImageServiceClient `kong:"-"` + notifSvc pb.NotifServiceClient `kong:"-"` + + GRPCAuthority string `kong:"name='grpc-authority',default='127.0.0.1:42286',help='Link to Diun gRPC API.'"` +} + +// BeforeApply is a hook that run cli cmd are executed. +func (s *CliGlobals) BeforeApply() (err error) { + s.conn, err = grpc.Dial(s.GRPCAuthority, grpc.WithInsecure()) + if err != nil { + return err + } + + s.imageSvc = pb.NewImageServiceClient(s.conn) + s.notifSvc = pb.NewNotifServiceClient(s.conn) + return +} diff --git a/cmd/image.go b/cmd/image.go new file mode 100644 index 00000000..69caaded --- /dev/null +++ b/cmd/image.go @@ -0,0 +1,131 @@ +package main + +import ( + "context" + "fmt" + "os" + "sort" + "strings" + "time" + "unicode" + + "github.com/crazy-max/diun/v4/pb" + "github.com/docker/go-units" + "github.com/jedib0t/go-pretty/v6/table" + "github.com/tidwall/pretty" + "google.golang.org/protobuf/encoding/protojson" +) + +// ImageCmd holds image command +type ImageCmd struct { + List ImageListCmd `kong:"cmd,default='1',help='List images in database.'"` + Inspect ImageInspectCmd `kong:"cmd,help='Display information of an image in database.'"` + Remove ImageRemoveCmd `kong:"cmd,help='Remove an image manifest from database.'"` + //Prune ImagePruneCmd `kong:"cmd,help='Remove unused manifests from the database.'"` +} + +// ImageListCmd holds image list command +type ImageListCmd struct { + CliGlobals + Raw bool `kong:"name='raw',default='false',help='JSON output.'"` +} + +func (s *ImageListCmd) Run(ctx *Context) error { + defer s.conn.Close() + + il, err := s.imageSvc.ImageList(context.Background(), &pb.ImageListRequest{}) + if err != nil { + return err + } + + sort.Slice(il.Images, func(i, j int) bool { + return strings.Map(unicode.ToUpper, il.Images[i].Name) < strings.Map(unicode.ToUpper, il.Images[j].Name) + }) + + if s.Raw { + b, _ := protojson.Marshal(il) + fmt.Println(string(pretty.Pretty(b))) + return nil + } + + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"Name", "Manifests Count", "Latest Tag", "Latest Created", "Latest Digest"}) + for _, image := range il.Images { + t.AppendRow(table.Row{image.Name, image.ManifestsCount, image.Latest.Tag, image.Latest.Created.AsTime().Format(time.RFC3339), image.Latest.Digest}) + } + t.AppendFooter(table.Row{"Total", len(il.Images)}) + t.Render() + + return nil +} + +// ImageInspectCmd holds image inspect command +type ImageInspectCmd struct { + CliGlobals + Image string `kong:"name='image',required,help='Image to inspect.'"` + Raw bool `kong:"name='raw',default='false',help='JSON output.'"` +} + +func (s *ImageInspectCmd) Run(ctx *Context) error { + defer s.conn.Close() + + ii, err := s.imageSvc.ImageInspect(context.Background(), &pb.ImageInspectRequest{ + Name: s.Image, + }) + if err != nil { + return err + } + + sort.Slice(ii.Image.Manifests, func(i, j int) bool { + return ii.Image.Manifests[i].Created.AsTime().After(ii.Image.Manifests[j].Created.AsTime()) + }) + + if s.Raw { + b, _ := protojson.Marshal(ii) + fmt.Println(string(pretty.Pretty(b))) + return nil + } + + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"Tag", "Created", "Digest"}) + for _, image := range ii.Image.Manifests { + t.AppendRow(table.Row{image.Tag, image.Created.AsTime().Format(time.RFC3339), image.Digest}) + } + t.AppendFooter(table.Row{"Total", len(ii.Image.Manifests)}) + t.Render() + + return nil +} + +// ImageRemoveCmd holds image remove command +type ImageRemoveCmd struct { + CliGlobals + Image string `kong:"name='image',required,help='Image to remove.'"` + All bool `kong:"name='all',default='false',help='Remove all manifests from the database.'"` +} + +func (s *ImageRemoveCmd) Run(ctx *Context) error { + defer s.conn.Close() + + removed, err := s.imageSvc.ImageRemove(context.Background(), &pb.ImageRemoveRequest{ + Name: s.Image, + }) + if err != nil { + return err + } + + t := table.NewWriter() + t.SetOutputMirror(os.Stdout) + t.AppendHeader(table.Row{"Tag", "Created", "Digest", "Size"}) + var totalSize int64 + for _, image := range removed.Manifests { + t.AppendRow(table.Row{image.Tag, image.Created.AsTime().Format(time.RFC3339), image.Digest, units.HumanSize(float64(image.Size))}) + totalSize += image.Size + } + t.AppendFooter(table.Row{"Total", fmt.Sprintf("%d (%s)", len(removed.Manifests), units.HumanSize(float64(totalSize)))}) + t.Render() + + return nil +} diff --git a/cmd/main.go b/cmd/main.go index cefd7ff7..6e59a76d 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -3,48 +3,48 @@ package main import ( "fmt" "os" - "os/signal" - "path" "runtime" "strings" - "syscall" _ "time/tzdata" "github.com/alecthomas/kong" - "github.com/crazy-max/diun/v4/internal/app" - "github.com/crazy-max/diun/v4/internal/config" - "github.com/crazy-max/diun/v4/internal/logging" "github.com/crazy-max/diun/v4/internal/model" - "github.com/pkg/profile" "github.com/rs/zerolog/log" ) var ( - diun *app.Diun - cli model.Cli version = "dev" - meta = model.Meta{ - ID: "diun", - Name: "Diun", - Desc: "Docker image update notifier", - URL: "https://github.com/crazy-max/diun", - Logo: "https://raw.githubusercontent.com/crazy-max/diun/master/.res/diun.png", - Author: "CrazyMax", + cli struct { + Version kong.VersionFlag + Serve ServeCmd `kong:"cmd,help='Starts Diun server.'"` + Image ImageCmd `kong:"cmd,help='Manage image manifests.'"` + Notif NotifCmd `kong:"cmd,help='Manage notifications.'"` } ) +type Context struct { + Meta model.Meta +} + func main() { var err error runtime.GOMAXPROCS(runtime.NumCPU()) - meta.Version = version + meta := model.Meta{ + ID: "diun", + Name: "Diun", + Desc: "Docker image update notifier", + URL: "https://github.com/crazy-max/diun", + Logo: "https://raw.githubusercontent.com/crazy-max/diun/master/.res/diun.png", + Author: "CrazyMax", + Version: version, + } meta.UserAgent = fmt.Sprintf("%s/%s go/%s %s", meta.ID, meta.Version, runtime.Version()[2:], strings.Title(runtime.GOOS)) if meta.Hostname, err = os.Hostname(); err != nil { log.Fatal().Err(err).Msg("Cannot resolve hostname") } - // Parse command line - _ = kong.Parse(&cli, + ctx := kong.Parse(&cli, kong.Name(meta.ID), kong.Description(fmt.Sprintf("%s. More info: %s", meta.Desc, meta.URL)), kong.UsageOnError(), @@ -56,69 +56,5 @@ func main() { Summary: true, })) - // Init - logging.Configure(&cli) - log.Info().Str("version", version).Msgf("Starting %s", meta.Name) - - // Handle os signals - channel := make(chan os.Signal) - signal.Notify(channel, os.Interrupt, syscall.SIGTERM) - go func() { - sig := <-channel - diun.Close() - log.Warn().Msgf("Caught signal %v", sig) - os.Exit(0) - }() - - // Load configuration - cfg, err := config.Load(cli) - if err != nil { - log.Fatal().Err(err).Msg("Cannot load configuration") - } - log.Debug().Msg(cfg.String()) - - // Profiler - if len(cli.Profiler) > 0 && len(cli.ProfilerPath) > 0 { - profilerPath := path.Clean(cli.ProfilerPath) - if err = os.MkdirAll(profilerPath, os.ModePerm); err != nil { - log.Fatal().Err(err).Msg("Cannot create profiler folder") - } - profilePath := profile.ProfilePath(profilerPath) - switch cli.Profiler { - case "cpu": - defer profile.Start(profile.CPUProfile, profilePath).Stop() - case "mem": - defer profile.Start(profile.MemProfile, profilePath).Stop() - case "alloc": - defer profile.Start(profile.MemProfileAllocs, profilePath).Stop() - case "heap": - defer profile.Start(profile.MemProfileHeap, profilePath).Stop() - case "routines": - defer profile.Start(profile.GoroutineProfile, profilePath).Stop() - case "mutex": - defer profile.Start(profile.MutexProfile, profilePath).Stop() - case "threads": - defer profile.Start(profile.ThreadcreationProfile, profilePath).Stop() - case "block": - defer profile.Start(profile.BlockProfile, profilePath).Stop() - default: - log.Fatal().Msgf("Unknown profiler: %s", cli.Profiler) - } - } - - // Init - if diun, err = app.New(meta, cli, cfg); err != nil { - log.Fatal().Err(err).Msgf("Cannot initialize %s", meta.Name) - } - - // Test notif - if cli.TestNotif { - diun.TestNotif() - return - } - - // Start - if err = diun.Start(); err != nil { - log.Fatal().Err(err).Msgf("Cannot start %s", meta.Name) - } + ctx.FatalIfErrorf(ctx.Run(&Context{Meta: meta})) } diff --git a/cmd/notif.go b/cmd/notif.go new file mode 100644 index 00000000..af38da8b --- /dev/null +++ b/cmd/notif.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + "fmt" + + "github.com/crazy-max/diun/v4/pb" +) + +// NotifCmd holds notif command +type NotifCmd struct { + Test NotifTestCmd `kong:"cmd,help='Test notification settings.'"` +} + +// NotifTestCmd holds notif test command +type NotifTestCmd struct { + CliGlobals +} + +func (s *NotifTestCmd) Run(ctx *Context) error { + defer s.conn.Close() + + nt, err := s.notifSvc.NotifTest(context.Background(), &pb.NotifTestRequest{}) + if err != nil { + return err + } + + fmt.Println(nt.Message) + return nil +} diff --git a/cmd/serve.go b/cmd/serve.go new file mode 100644 index 00000000..f9ad208b --- /dev/null +++ b/cmd/serve.go @@ -0,0 +1,99 @@ +package main + +import ( + "os" + "os/signal" + "path" + "syscall" + + "github.com/crazy-max/diun/v4/internal/app" + "github.com/crazy-max/diun/v4/internal/config" + "github.com/crazy-max/diun/v4/internal/logging" + "github.com/pkg/profile" + "github.com/rs/zerolog/log" +) + +// ServeCmd holds serve command args and flags +type ServeCmd struct { + Cfgfile string `kong:"name='config',env='CONFIG',help='Diun configuration file.'"` + ProfilerPath string `kong:"name='profiler-path',env='PROFILER_PATH',help='Base path where profiling files are written.'"` + Profiler string `kong:"name='profiler',env='PROFILER',help='Profiler to use.'"` + LogLevel string `kong:"name='log-level',env='LOG_LEVEL',default='info',help='Set log level.'"` + LogJSON bool `kong:"name='log-json',env='LOG_JSON',default='false',help='Enable JSON logging output.'"` + LogCaller bool `kong:"name='log-caller',env='LOG_CALLER',default='false',help='Add file:line of the caller to log output.'"` + LogNoColor bool `kong:"name='log-nocolor',env='LOG_NOCOLOR',default='false',help='Disables the colorized output.'"` + GRPCAuthority string `kong:"name='grpc-authority',env='GRPC_AUTHORITY',default=':42286',help='Address used to expose the gRPC server.'"` +} + +func (s *ServeCmd) Run(ctx *Context) error { + var diun *app.Diun + + // Logging + logging.Configure(logging.Options{ + LogLevel: s.LogLevel, + LogJSON: s.LogJSON, + LogCaller: s.LogCaller, + LogNoColor: s.LogNoColor, + }) + log.Info().Str("version", version).Msgf("Starting %s", ctx.Meta.Name) + + // Handle os signals + channel := make(chan os.Signal) + signal.Notify(channel, os.Interrupt, syscall.SIGTERM) + go func() { + sig := <-channel + diun.Close() + log.Warn().Msgf("Caught signal %v", sig) + os.Exit(0) + }() + + // Load configuration + cfg, err := config.Load(s.Cfgfile) + if err != nil { + log.Fatal().Err(err).Msg("Cannot load configuration") + } + log.Debug().Msg(cfg.String()) + + // Profiler + if len(s.Profiler) > 0 && len(s.ProfilerPath) > 0 { + profilerPath := path.Clean(s.ProfilerPath) + if err = os.MkdirAll(profilerPath, os.ModePerm); err != nil { + log.Fatal().Err(err).Msg("Cannot create profiler folder") + } + profilePath := profile.ProfilePath(profilerPath) + switch s.Profiler { + case "cpu": + defer profile.Start(profile.CPUProfile, profilePath).Stop() + case "mem": + defer profile.Start(profile.MemProfile, profilePath).Stop() + case "alloc": + defer profile.Start(profile.MemProfileAllocs, profilePath).Stop() + case "heap": + defer profile.Start(profile.MemProfileHeap, profilePath).Stop() + case "routines": + defer profile.Start(profile.GoroutineProfile, profilePath).Stop() + case "mutex": + defer profile.Start(profile.MutexProfile, profilePath).Stop() + case "threads": + defer profile.Start(profile.ThreadcreationProfile, profilePath).Stop() + case "block": + defer profile.Start(profile.BlockProfile, profilePath).Stop() + default: + log.Fatal().Msgf("Unknown profiler: %s", s.Profiler) + } + } + + // Init + diun, err = app.New(ctx.Meta, cfg, s.GRPCAuthority) + if err != nil { + log.Fatal().Err(err).Msgf("Cannot initialize %s", ctx.Meta.Name) + } + + // Start + err = diun.Start() + if err != nil { + log.Fatal().Err(err).Msgf("Cannot start %s", ctx.Meta.Name) + } + + return nil +} diff --git a/docker-bake.hcl b/docker-bake.hcl index 81180511..89f9cbe3 100644 --- a/docker-bake.hcl +++ b/docker-bake.hcl @@ -9,6 +9,17 @@ target "go-version" { } } +// protoc version +variable "PROTOC_VERSION" { + default = "3.17.0" +} + +target "protoc-version" { + args = { + PROTOC_VERSION = PROTOC_VERSION + } +} + // GitHub reference as defined in GitHub Actions (eg. refs/head/master)) variable "GITHUB_REF" { default = "" @@ -30,7 +41,7 @@ group "default" { } group "validate" { - targets = ["lint", "vendor-validate"] + targets = ["lint", "vendor-validate", "gen-validate"] } target "lint" { @@ -52,6 +63,19 @@ target "vendor-update" { output = ["."] } +target "gen-validate" { + inherits = ["go-version", "protoc-version"] + dockerfile = "./hack/gen.Dockerfile" + target = "validate" +} + +target "gen-update" { + inherits = ["go-version", "protoc-version"] + dockerfile = "./hack/gen.Dockerfile" + target = "update" + output = ["."] +} + target "test" { inherits = ["go-version"] dockerfile = "./hack/test.Dockerfile" diff --git a/docs/config/index.md b/docs/config/index.md index b79f96b9..d63b8b57 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -22,7 +22,7 @@ At startup, Diun searches for a file named `diun.yml` (or `diun.yaml`) in: * `$HOME/.config/` * `.` _(the working directory)_ -You can override this using the [`--config` flag or `CONFIG` env var](../usage/cli.md). +You can override this using the [`--config` flag or `CONFIG` env var with `serve` command](../usage/command-line.md#serve). ??? example "diun.yml" ```yaml diff --git a/docs/config/regopts.md b/docs/config/regopts.md index 825925a1..09ad8ab7 100644 --- a/docs/config/regopts.md +++ b/docs/config/regopts.md @@ -20,13 +20,11 @@ regopts: passwordFile: /run/secrets/password ``` -`myregistry` will be used as a `name` selector (default) if referenced by its [name](#name). - -`docker.io` will be used as an `image` selector. If an image is on DockerHub (`docker.io` domain), this registry options will -be selected if not referenced as a `regopt` name. - -`docker.io/crazymax` will be used as an `image` selector. If an image is on DockerHub and in `crazymax` namespace, this registry options will -be selected if not referenced as a `regopt` name. +* `myregistry` will be used as a `name` selector (default) if referenced by its [name](#name). +* `docker.io` will be used as an `image` selector. If an image is on DockerHub (`docker.io` domain), this registry +options will be selected if not referenced as a `regopt` name. +* `docker.io/crazymax` will be used as an `image` selector. If an image is on DockerHub and in `crazymax` namespace, +this registry options will be selected if not referenced as a `regopt` name. ## Configuration diff --git a/docs/config/watch.md b/docs/config/watch.md index 960d2e3a..b6809d07 100644 --- a/docs/config/watch.md +++ b/docs/config/watch.md @@ -60,7 +60,7 @@ Send notification at the very first analysis of an image. (default `false`) ### `compareDigest` Compare the digest of an image with the registry before downloading the image manifest. It is strongly -recommended to leave this value at `true`, especially with [Docker Hub which imposes a rate-limit](../faq.md#docker-hub-rate-limits) +recommended leaving this value at `true`, especially with [Docker Hub which imposes a rate-limit](../faq.md#docker-hub-rate-limits) on image pull. (default `true`) !!! example "Config file" @@ -74,7 +74,7 @@ on image pull. (default `true`) ### `healthchecks` -Healthchecks allows to monitor Diun watcher by sending start and success notification +Healthchecks allows monitoring Diun watcher by sending start and success notification events to [healthchecks.io](https://healthchecks.io/). !!! tip diff --git a/docs/faq.md b/docs/faq.md index cff80357..56f646c5 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -14,16 +14,16 @@ watch: ## Test notifications -Through the [command line](usage/cli.md) with: +Through the [command line](usage/command-line.md#notif-test) with: ```shell -diun --config ./diun.yml --test-notif +diun notif test ``` Or within a container: ```shell -docker-compose exec diun diun --test-notif +docker-compose exec diun diun notif test ``` ## field docker|swarm uses unsupported type: invalid @@ -127,7 +127,8 @@ Or you can tweak the [`schedule` setting](config/watch.md#schedule) with somethi ## Profiling -Diun provides a simple way to manage runtime/pprof profiling through [`--profiler-path` and `--profiler` flags](usage/cli.md#options): +Diun provides a simple way to manage runtime/pprof profiling through the +[`--profiler-path` and `--profiler` flags with `serve` command](usage/command-line.md#serve): ```yaml version: "3.5" diff --git a/docs/install/binary.md b/docs/install/binary.md index b4104eea..31669caf 100644 --- a/docs/install/binary.md +++ b/docs/install/binary.md @@ -25,8 +25,8 @@ And extract diun: wget -qO- {{ config.repo_url }}releases/download/v{{ git.tag | trim('v') }}/diun_{{ git.tag | trim('v') }}_linux_x86_64.tar.gz | tar -zxvf - diun ``` -After getting the binary, it can be tested with [`./diun --help`](../usage/cli.md) command and moved to a permanent -location. +After getting the binary, it can be tested with [`./diun --help`](../usage/command-line.md#global-options) command +and moved to a permanent location. ## Server configuration diff --git a/docs/migration/v3-to-v4.md b/docs/migration/v3-to-v4.md index 44cd773f..c662044e 100644 --- a/docs/migration/v3-to-v4.md +++ b/docs/migration/v3-to-v4.md @@ -233,7 +233,8 @@ Following the transposition of the configuration into environment variables, the is no longer loaded by default in the official Docker image. If you want to load a configuration file through the Docker image you will have to declare the -[`CONFIG` environment variable](../usage/cli.md#environment-variables) pointing to the assigned configuration file: +[`CONFIG` environment variable with `serve` command](../usage/command-line.md#serve) pointing to the assigned +configuration file: !!! tip This is no longer required since version 4.2.0. Now configuration file can be loaded from diff --git a/docs/migration/v4.0-to-v4.17.md b/docs/migration/v4.0-to-v4.17.md new file mode 100644 index 00000000..d027e7ed --- /dev/null +++ b/docs/migration/v4.0-to-v4.17.md @@ -0,0 +1,15 @@ +# Diun v4.0 to v4.17 + +## New CLI + +CLI has changed since 4.17 and includes [`serve` command](../usage/command-line.md#serve) to start Diun: + +!!! example "v4.0" + ```shell + diun --config diun.yml + ``` + +!!! example "v4.17" + ```shell + diun serve --config diun.yml + ``` diff --git a/docs/usage/cli.md b/docs/usage/cli.md deleted file mode 100644 index 71878788..00000000 --- a/docs/usage/cli.md +++ /dev/null @@ -1,44 +0,0 @@ -# Command Line - -## Usage - -```shell -diun [options] -``` - -## Options - -``` -$ diun --help -Usage: diun - -Docker image update notifier. More info: https://github.com/crazy-max/diun - -Flags: - -h, --help Show context-sensitive help. - --version - --config=STRING Diun configuration file ($CONFIG). - --profiler-path=STRING Base path where profiling files are written - ($PROFILER_PATH). - --profiler=STRING Profiler to use ($PROFILER). - --log-level="info" Set log level ($LOG_LEVEL). - --log-json Enable JSON logging output ($LOG_JSON). - --log-caller Add file:line of the caller to log output - ($LOG_CALLER). - --log-nocolor Disables the colorized output ($LOG_NOCOLOR). - --test-notif Test notification settings. -``` - -## Environment variables - -Following environment variables can be used in place: - -| Name | Default | Description | -|--------------------|---------------|---------------| -| `CONFIG` | | Diun configuration file | -| `PROFILER_PATH` | | Base path where profiling files are written | -| `PROFILER` | | [Profiler](../faq.md#profiling) to use | -| `LOG_LEVEL` | `info` | Log level output | -| `LOG_JSON` | `false` | Enable JSON logging output | -| `LOG_CALLER` | `false` | Enable to add `file:line` of the caller | -| `LOG_NOCOLOR` | `false` | Disables the colorized output | diff --git a/docs/usage/command-line.md b/docs/usage/command-line.md new file mode 100644 index 00000000..22c22417 --- /dev/null +++ b/docs/usage/command-line.md @@ -0,0 +1,124 @@ +# Command Line + +## Usage + +```shell +diun [global options] command [command or global options] [arguments...] +``` + +## Global options + +All global options can be placed at the command level. + +* `--help`, `-h`: Show context-sensitive help. +* `--version`: Show version and exit. + +## Commands + +### `serve` + +Starts Diun server. + +* `--config `: Diun configuration file +* `--profiler-path `: Base path where profiling files are written +* `--profiler `: Profiler to use +* `--log-level `: Set log level (default `info`) +* `--log-json`: Enable JSON logging output +* `--log-caller`: Add `file:line` of the caller to log output +* `--log-nocolor`: Disables the colorized output +* `--grpc-authority `: Address used to expose the gRPC server (default `:42286`) + +Examples: + +```shell +diun serve --config diun.yml --log-level debug +``` + +Following environment variables can also be used in place: + +| Name | Default | Description | +|--------------------|---------------|---------------| +| `CONFIG` | | Diun configuration file | +| `PROFILER_PATH` | | Base path where profiling files are written | +| `PROFILER` | | [Profiler](../faq.md#profiling) to use | +| `LOG_LEVEL` | `info` | Log level output | +| `LOG_JSON` | `false` | Enable JSON logging output | +| `LOG_CALLER` | `false` | Enable to add `file:line` of the caller | +| `LOG_NOCOLOR` | `false` | Disables the colorized output | +| `GRPC_AUTHORITY` | `:42286` | Address used to expose the gRPC server | + +### `image list` + +!!! note + Diun needs to be started through [`serve`](#serve) command to be able to use this command. + +List images in database. + +* `--raw`: JSON output +* `--grpc-authority `: Link to Diun gRPC API (default `127.0.0.1:42286`) + +Examples: + +```shell +diun image list +``` +```shell +diun image list --raw +``` + +### `image inspect` + +!!! note + Diun needs to be started through [`serve`](#serve) command to be able to use this command. + +Display information of an image in database. + +* `--image`: Image to inspect (**required**) +* `--raw`: JSON output +* `--grpc-authority `: Link to Diun gRPC API (default `127.0.0.1:42286`) + +Examples: + +```shell +diun image inspect alpine +``` +```shell +diun image inspect drone/drone --raw +``` + +### `image remove` + +!!! note + Diun needs to be started through [`serve`](#serve) command to be able to use this command. + +Remove an image manifest from database. + +* `--image`: Image to remove (**required**) +* `--grpc-authority `: Link to Diun gRPC API (default `127.0.0.1:42286`) + +Examples: + +```shell +diun image remove alpine:latest +``` +```shell +diun image inspect drone/drone +``` + +!!! warning + All manifest for an image will be removed if no tag is specified + +### `notif test` + +!!! note + Diun needs to be started through [`serve`](#serve) command to be able to use this command. + +Test notification settings. + +* `--grpc-authority `: Link to Diun gRPC API (default `127.0.0.1:42286`) + +Examples: + +```shell +diun notif test +``` diff --git a/go.mod b/go.mod index eb895aec..672730f9 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/crazy-max/diun/v4 -go 1.15 +go 1.16 require ( github.com/alecthomas/kong v0.2.16 @@ -11,6 +11,7 @@ require ( github.com/crazy-max/gonfig v0.4.0 github.com/docker/docker v20.10.6+incompatible github.com/docker/go-connections v0.4.0 + github.com/docker/go-units v0.4.0 github.com/eclipse/paho.mqtt.golang v1.3.3 github.com/go-gomail/gomail v0.0.0-20160411212932-81ebce5c23df github.com/go-playground/validator/v10 v10.5.0 @@ -18,6 +19,7 @@ require ( github.com/gregdel/pushover v0.0.0-20201104094836-ddbe0c1d3a38 github.com/hako/durafmt v0.0.0-20190612201238-650ed9f29a84 github.com/imdario/mergo v0.3.12 + github.com/jedib0t/go-pretty/v6 v6.2.1 github.com/matcornic/hermes/v2 v2.1.0 github.com/matrix-org/gomatrix v0.0.0-20200501121722-e5578b12c752 github.com/microcosm-cc/bluemonday v1.0.9 @@ -35,7 +37,11 @@ require ( github.com/streadway/amqp v0.0.0-20200108173154-1c71cc93ed71 github.com/stretchr/testify v1.7.0 github.com/technoweenie/multipartstreamer v1.0.1 // indirect + github.com/tidwall/pretty v1.1.0 go.etcd.io/bbolt v1.3.5 + google.golang.org/grpc v1.37.0 + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 + google.golang.org/protobuf v1.26.0 gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/gomail.v2 v2.0.0-20160411212932-81ebce5c23df // indirect gopkg.in/yaml.v2 v2.4.0 diff --git a/go.sum b/go.sum index 55a501d1..7f2e9dee 100644 --- a/go.sum +++ b/go.sum @@ -298,8 +298,9 @@ github.com/cilium/ebpf v0.2.0 h1:Fv93L3KKckEcEHR3oApXVzyBTDA8WAm6VXhPE00N3f8= github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= github.com/client9/misspell v0.3.4 h1:ta993UF76GwbvJcIo3Y68y/M3WxlpEHPWIGDkJYwzJI= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f h1:WBZRG4aNOuI15bLRrCgN8fCq8E5Xuty6jGbmSNEvSsU= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403 h1:cqQfy1jclcSy/FwLjemeg3SR1yaINm74aQyupQ0Bl8M= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa h1:OaNxuTZr7kxeODyLWsRMC+OD03aFUH+mW6r2d+MWa5Y= github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= github.com/codahale/hdrhistogram v0.0.0-20160425231609-f8ad88b59a58 h1:hHWif/4GirK3P5uvCyyj941XSVIQDzuJhbEguCICdPE= @@ -458,8 +459,9 @@ github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4 h1:rEvIZUSZ3fx39WIi3JkQqQBitGwpELBIYWeBVh6wn+E= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d h1:QyzYnTnPE15SQyUeqU6qLbWxMkwyAyu+vGksa0b7j00= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0 h1:EQciDnbrYxy13PgWoY8AqoxGiPrpgBZ1R8UNe3ddc+A= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/evanphx/json-patch v4.2.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= @@ -478,6 +480,8 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWoS4= github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fzipp/gocyclo v0.3.1 h1:A9UeX3HJSXTBzvHzhqoYVuE0eAhe+aM8XBCCwsPMZOc= +github.com/fzipp/gocyclo v0.3.1/go.mod h1:DJHO6AUmbdqj2ET4Z9iArSuwWgYDRryYt2wASxc7x3E= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7 h1:LofdAjjjqCSXMwLGgOgnE+rdPuvX9DxCqaHwKy7i/ko= github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= @@ -616,8 +620,9 @@ github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:W github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3 h1:JjCZWpVbqXDqFVmTfYWEVTMIYrL/NPdPSCHPJ0T/raM= github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.3 h1:fHPg5GQYlCeLIPB9BZqMVR5nR9A+IM5zcgeTdjMYmLA= github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= @@ -667,8 +672,9 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2 h1:X2ev0eStA3AbceY54o37/0PQ/UWqKEiiO2dKL5OPaFM= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-containerregistry v0.0.0-20191010200024-a3d713f9b7f8/go.mod h1:KyKXa9ciM8+lgMXwOVsXi7UxGrsf9mM61Mzs+xKUrKE= github.com/google/go-containerregistry v0.1.2 h1:YjFNKqxzWUVZND8d4ItF9wuYlE75WQfECE7yKX/Nu3o= github.com/google/go-containerregistry v0.1.2/go.mod h1:GPivBPgdAyd2SU+vf6EpsgOtWDuPqjW0hJZt4rNdTZ4= @@ -856,6 +862,8 @@ github.com/jarcoal/httpmock v1.0.5 h1:cHtVEcTxRSX4J0je7mWPfc9BpDpqzXSJ5HbymZmyHc github.com/jarcoal/httpmock v1.0.5/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0 h1:xqgexXAGQgY3HAjNPSaCqn5Aahbo5TKsmhp8VRfr1iQ= github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0/go.mod h1:CVKlgaMiht+LXvHG173ujK6JUhZXKb2u/BQtjPDIvyk= +github.com/jedib0t/go-pretty/v6 v6.2.1 h1:O/3XdNfyWSyVLLIt1EeDhfP8AhNMjtBSh0MuZ4frg6U= +github.com/jedib0t/go-pretty/v6 v6.2.1/go.mod h1:+nE9fyyHGil+PuISTCrp7avEdo6bqoMwqZnuiK2r2a0= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1 h1:ujPKutqRlJtcfWk6toYVYagwra7HQHbXOaS171b4Tg8= github.com/jellevandenhooff/dkim v0.0.0-20150330215556-f50fe3d243e1/go.mod h1:E0B/fFc00Y+Rasa88328GlI/XbtyysCtTHZS8h7IrBU= github.com/jingyugao/rowserrcheck v0.0.0-20191204022205-72ab7603b68a h1:GmsqmapfzSJkm28dhRoHz2tLRbJmqhU86IPgBtN3mmk= @@ -977,6 +985,7 @@ github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHX github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.3/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= +github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mattn/go-runewidth v0.0.10 h1:CoZ3S2P7pvtP45xOtBw+/mDL2z0RKI576gSkzRRpdGg= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= github.com/mattn/go-shellwords v1.0.10/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= @@ -1152,6 +1161,7 @@ github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= github.com/pkg/profile v1.6.0 h1:hUDfIISABYI59DyeB3OTay/HxSRwTQ8rB/H83k6r5dM= github.com/pkg/profile v1.6.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= @@ -1348,6 +1358,8 @@ github.com/technoweenie/multipartstreamer v1.0.1/go.mod h1:jNVxdtShOxzAsukZwTSw6 github.com/tetafro/godot v0.3.7/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0= github.com/tetafro/godot v0.4.2 h1:Dib7un+rYJFUi8vN0Bk6EHheKy6fv6ZzFURHw75g6m8= github.com/tetafro/godot v0.4.2/go.mod h1:/7NLHhv08H1+8DNj0MElpAACw1ajsCuf3TKNQxA5S+0= +github.com/tidwall/pretty v1.1.0 h1:K3hMW5epkdAVwibsQEfR/7Zj0Qgt4DxtNumTq/VloO8= +github.com/tidwall/pretty v1.1.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/timakin/bodyclose v0.0.0-20190930140734-f7f2e9bca95e/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94 h1:ig99OeTyDwQWhPe2iw9lwfQVF1KB3Q4fpP3X7/2VBG8= github.com/timakin/bodyclose v0.0.0-20200424151742-cb6215831a94/go.mod h1:Qimiffbc6q9tBWlVV6x0P9sat/ao1xEkREYPPj9hphk= @@ -1611,6 +1623,7 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20201207232520-09787c993a3a h1:DcqTD9SDLc+1P/r1EmRBwnVsrOwW+kk2vWf9n+1sGhs= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180816055513-1c9583448a9c/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -1868,8 +1881,11 @@ google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8 google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= -google.golang.org/grpc v1.33.2 h1:EQyQC3sa8M+p6Ulc8yy9SWSS2GVwyRc83gAbG8lrl4o= google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.37.0 h1:uSZWeQJX5j11bIQ4AJoj+McDBo29cY1MCoC1wO3ts+c= +google.golang.org/grpc v1.37.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0 h1:M1YKkFIboKNieVO5DLUEVzQfGwJD30Nv2jfUgzb5UcE= +google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -1879,8 +1895,10 @@ google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2 google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= gopkg.in/airbrake/gobrake.v2 v2.0.9 h1:7z2uVWwn7oVeeugY1DtlPAy5H+KYgB1KeKTnqjNatLo= gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= gopkg.in/alecthomas/kingpin.v2 v2.2.6 h1:jMFz6MfLP0/4fUyZle81rXUoxOBFi19VUFKVDOQfozc= diff --git a/hack/gen.Dockerfile b/hack/gen.Dockerfile new file mode 100644 index 00000000..44b4a9f1 --- /dev/null +++ b/hack/gen.Dockerfile @@ -0,0 +1,40 @@ +# syntax=docker/dockerfile:1.2 +ARG GO_VERSION +ARG PROTOC_VERSION +ARG GLIBC_VERSION=2.33-r0 + +FROM golang:${GO_VERSION}-alpine AS base +ARG GLIBC_VERSION +RUN apk add --no-cache curl file git unzip \ + && curl -sSL "https://alpine-pkgs.sgerrand.com/sgerrand.rsa.pub" -o "/etc/apk/keys/sgerrand.rsa.pub" \ + && curl -sSL "https://github.com/sgerrand/alpine-pkg-glibc/releases/download/${GLIBC_VERSION}/glibc-${GLIBC_VERSION}.apk" -o "glibc.apk" \ + && apk add glibc.apk \ + && rm /etc/apk/keys/sgerrand.rsa.pub glibc.apk +ARG PROTOC_VERSION +RUN curl -sSL "https://github.com/protocolbuffers/protobuf/releases/download/v${PROTOC_VERSION}/protoc-${PROTOC_VERSION}-linux-x86_64.zip" -o "protoc.zip" \ + && unzip "protoc.zip" -d "/usr/local" \ + && protoc --version \ + && rm "protoc.zip" +WORKDIR /src + +FROM base AS gomod +RUN --mount=type=bind,target=.,rw \ + --mount=type=cache,target=/go/pkg/mod \ + go mod tidy && go mod download && go install -v $(sed -n -e 's|^\s*_\s*"\(.*\)".*$|\1| p' tools.go) + +FROM gomod AS generate +RUN --mount=type=bind,target=.,rw \ + --mount=type=cache,target=/go/pkg/mod \ + go generate ./... && mkdir /out && cp -Rf pb /out + +FROM scratch AS update +COPY --from=generate /out / + +FROM generate AS validate +RUN --mount=type=bind,target=.,rw \ + git add -A && cp -rf /out/* .; \ + if [ -n "$(git status --porcelain)" ]; then \ + echo >&2 'ERROR: Generate result differs. Please update with "docker buildx bake gen-update"'; \ + git status --porcelain; \ + exit 1; \ + fi diff --git a/internal/app/diun.go b/internal/app/diun.go index 2b4cdd73..aa37b660 100644 --- a/internal/app/diun.go +++ b/internal/app/diun.go @@ -8,6 +8,7 @@ import ( "github.com/crazy-max/diun/v4/internal/config" "github.com/crazy-max/diun/v4/internal/db" + "github.com/crazy-max/diun/v4/internal/grpc" "github.com/crazy-max/diun/v4/internal/logging" "github.com/crazy-max/diun/v4/internal/model" "github.com/crazy-max/diun/v4/internal/notif" @@ -16,7 +17,6 @@ import ( filePrd "github.com/crazy-max/diun/v4/internal/provider/file" kubernetesPrd "github.com/crazy-max/diun/v4/internal/provider/kubernetes" swarmPrd "github.com/crazy-max/diun/v4/internal/provider/swarm" - "github.com/crazy-max/diun/v4/pkg/registry" "github.com/crazy-max/gohealthchecks" "github.com/hako/durafmt" "github.com/panjf2000/ants/v2" @@ -27,12 +27,15 @@ import ( // Diun represents an active diun object type Diun struct { - meta model.Meta - cfg *config.Config + meta model.Meta + cfg *config.Config + + db *db.Client + grpc *grpc.Client + hc *gohealthchecks.Client + notif *notif.Client + cron *cron.Cron - db *db.Client - hc *gohealthchecks.Client - notif *notif.Client jobID cron.EntryID locker uint32 pool *ants.PoolWithFunc @@ -40,7 +43,7 @@ type Diun struct { } // New creates new diun instance -func New(meta model.Meta, cli model.Cli, cfg *config.Config) (*Diun, error) { +func New(meta model.Meta, cfg *config.Config, grpcAuthority string) (*Diun, error) { var err error diun := &Diun{ @@ -56,11 +59,14 @@ func New(meta model.Meta, cli model.Cli, cfg *config.Config) (*Diun, error) { return nil, err } - if !cli.TestNotif { - diun.db, err = db.New(*cfg.Db) - if err != nil { - return nil, err - } + diun.db, err = db.New(*cfg.Db) + if err != nil { + return nil, err + } + + diun.grpc, err = grpc.New(grpcAuthority, diun.db, diun.notif) + if err != nil { + return nil, err } if cfg.Watch.Healthchecks != nil { @@ -89,6 +95,13 @@ func (di *Diun) Start() error { return err } + // Start GRPC server + go func() { + if err := di.grpc.Start(); err != nil { + log.Fatal().Err(err).Msg("Failed to start GRPC server") + } + }() + // Run on startup di.Run() @@ -157,7 +170,7 @@ func (di *Diun) Run() { di.createJob(job) } - // Dokcerfile provider + // Dockerfile provider for _, job := range dockerfilePrd.New(di.cfg.Providers.Dockerfile).ListJob() { di.createJob(job) } @@ -178,51 +191,8 @@ func (di *Diun) Close() { if di.cron != nil { di.cron.Stop() } + di.grpc.Stop() if err := di.db.Close(); err != nil { log.Warn().Err(err).Msg("Cannot close database") } } - -// TestNotif test the notification settings -func (di *Diun) TestNotif() { - createdAt, _ := time.Parse("2006-01-02T15:04:05Z", "2020-03-26T12:23:56Z") - image, _ := registry.ParseImage(registry.ParseImageOptions{ - Name: "diun/testnotif:latest", - }) - image.HubLink = "" - - log.Info().Msg("Testing notification settings...") - di.notif.Send(model.NotifEntry{ - Status: "new", - Provider: "file", - Image: image, - Manifest: registry.Manifest{ - Name: "diun/testnotif", - Tag: "latest", - MIMEType: "application/vnd.docker.distribution.manifest.list.v2+json", - Digest: "sha256:216e3ae7de4ca8b553eb11ef7abda00651e79e537e85c46108284e5e91673e01", - Created: &createdAt, - DockerVersion: "", - Labels: map[string]string{ - "maintainer": "CrazyMax", - "org.label-schema.build-date": "2020-03-26T12:23:56Z", - "org.label-schema.description": "Docker image update notifier", - "org.label-schema.name": "Diun", - "org.label-schema.schema-version": "1.0", - "org.label-schema.url": "https://github.com/crazy-max/diun", - "org.label-schema.vcs-ref": "e13f097c", - "org.label-schema.vcs-url": "https://github.com/crazy-max/diun", - "org.label-schema.vendor": "CrazyMax", - "org.label-schema.version": "x.x.x", - }, - Layers: []string{ - "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", - "sha256:166c6f165b73185ede72415d780538a55c0c8e854bd177925bc007193e5b0d1b", - "sha256:e05682efa9cc9d6239b2b9252fe0dc1e58d6e1585679733bb94a6549d49e9b10", - "sha256:c6a5bfed445b3ed7e85523cd73c6532ac9f9b72bb588ca728fd5b33987ca6538", - "sha256:df2140efb8abeb727ef0b27ff158b7010a7941eb1cfdade505f510a6e1eaf016", - }, - Platform: "linux/amd64", - }, - }) -} diff --git a/internal/config/config.go b/internal/config/config.go index 8a1e775e..d7f61258 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -22,14 +22,14 @@ type Config struct { } // Load returns Config struct -func Load(cli model.Cli) (*Config, error) { +func Load(config string) (*Config, error) { cfg := Config{ Db: (&model.Db{}).GetDefaults(), Watch: (&model.Watch{}).GetDefaults(), } fileLoader := gonfig.NewFileLoader(gonfig.FileLoaderConfig{ - Filename: cli.Cfgfile, + Filename: config, Finder: gonfig.Finder{ BasePaths: []string{"/etc/diun/diun", "$XDG_CONFIG_HOME/diun", "$HOME/.config/diun", "./diun"}, Extensions: []string{"yaml", "yml"}, @@ -54,14 +54,14 @@ func Load(cli model.Cli) (*Config, error) { log.Info().Msgf("Configuration loaded from %d environment variable(s)", len(envLoader.GetVars())) } - if err := cfg.validate(cli); err != nil { + if err := cfg.validate(); err != nil { return nil, err } return &cfg, nil } -func (cfg *Config) validate(cli model.Cli) error { +func (cfg *Config) validate() error { if len(cfg.Db.Path) > 0 { if err := os.MkdirAll(path.Dir(cfg.Db.Path), os.ModePerm); err != nil { return errors.Wrap(err, "Cannot create database destination folder") @@ -72,11 +72,7 @@ func (cfg *Config) validate(cli model.Cli) error { return errors.New("Healthchecks UUID is required") } - if cfg.Notif == nil && cli.TestNotif { - return errors.New("At least one notifier is required") - } - - if cfg.Providers == nil && !cli.TestNotif { + if cfg.Providers == nil { return errors.New("At least one provider is required") } diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 365b37ae..3314ea29 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -18,55 +18,33 @@ import ( func TestLoadFile(t *testing.T) { cases := []struct { name string - cli model.Cli + cfg string wantData *config.Config wantErr bool }{ { - name: "Failed on non-existing file", - cli: model.Cli{ - TestNotif: false, - }, + name: "Failed on non-existing file", + cfg: "", wantErr: true, }, { - name: "Fail on wrong file format", - cli: model.Cli{ - Cfgfile: "./fixtures/config.invalid.yml", - TestNotif: false, - }, + name: "Fail on wrong file format", + cfg: "./fixtures/config.invalid.yml", wantErr: true, }, { - name: "Fail on no UUID for Healthchecks", - cli: model.Cli{ - Cfgfile: "./fixtures/config.err.hc.yml", - TestNotif: false, - }, + name: "Fail on no UUID for Healthchecks", + cfg: "./fixtures/config.err.hc.yml", wantErr: true, }, { - name: "Fail on no notifier if test notif", - cli: model.Cli{ - Cfgfile: "./fixtures/config.err.notif.yml", - TestNotif: true, - }, - wantErr: true, - }, - { - name: "Fail on no provider", - cli: model.Cli{ - Cfgfile: "./fixtures/config.err.provider.yml", - TestNotif: false, - }, + name: "Fail on no provider", + cfg: "./fixtures/config.err.provider.yml", wantErr: true, }, { name: "Success", - cli: model.Cli{ - Cfgfile: "./fixtures/config.test.yml", - TestNotif: false, - }, + cfg: "./fixtures/config.test.yml", wantData: &config.Config{ Db: &model.Db{ Path: "diun.db", @@ -218,7 +196,7 @@ func TestLoadFile(t *testing.T) { } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { - cfg, err := config.Load(tt.cli) + cfg, err := config.Load(tt.cfg) if tt.wantErr { require.Error(t, err) return @@ -237,7 +215,7 @@ func TestLoadEnv(t *testing.T) { testCases := []struct { desc string - cli model.Cli + cfg string environ []string expected interface{} wantErr bool @@ -365,7 +343,7 @@ func TestLoadEnv(t *testing.T) { } } - cfg, err := config.Load(tt.cli) + cfg, err := config.Load(tt.cfg) if tt.wantErr { require.Error(t, err) return @@ -382,17 +360,14 @@ func TestLoadMixed(t *testing.T) { testCases := []struct { desc string - cli model.Cli + cfg string environ []string expected interface{} wantErr bool }{ { desc: "env vars and invalid file", - cli: model.Cli{ - Cfgfile: "./fixtures/config.invalid.yml", - TestNotif: false, - }, + cfg: "./fixtures/config.invalid.yml", environ: []string{ "DIUN_PROVIDERS_DOCKER=true", }, @@ -401,10 +376,7 @@ func TestLoadMixed(t *testing.T) { }, { desc: "docker provider (file) and notif mails (envs)", - cli: model.Cli{ - Cfgfile: "./fixtures/config.docker.yml", - TestNotif: false, - }, + cfg: "./fixtures/config.docker.yml", environ: []string{ "DIUN_NOTIF_MAIL_HOST=127.0.0.1", "DIUN_NOTIF_MAIL_PORT=25", @@ -441,10 +413,7 @@ func TestLoadMixed(t *testing.T) { }, { desc: "file provider and notif webhook env override", - cli: model.Cli{ - Cfgfile: "./fixtures/config.file.yml", - TestNotif: false, - }, + cfg: "./fixtures/config.file.yml", environ: []string{ "DIUN_NOTIF_WEBHOOK_ENDPOINT=http://webhook.foo.com/sd54qad89azd5a", "DIUN_NOTIF_WEBHOOK_HEADERS_AUTHORIZATION=Token78910", @@ -488,7 +457,7 @@ func TestLoadMixed(t *testing.T) { } } - cfg, err := config.Load(tt.cli) + cfg, err := config.Load(tt.cfg) if tt.wantErr { require.Error(t, err) return @@ -503,19 +472,16 @@ func TestLoadMixed(t *testing.T) { func TestValidation(t *testing.T) { cases := []struct { name string - cli model.Cli + cfg string }{ { name: "Success", - cli: model.Cli{ - Cfgfile: "./fixtures/config.validate.yml", - TestNotif: false, - }, + cfg: "./fixtures/config.validate.yml", }, } for _, tt := range cases { t.Run(tt.name, func(t *testing.T) { - cfg, err := config.Load(tt.cli) + cfg, err := config.Load(tt.cfg) require.NoError(t, err) dec, err := env.Encode("DIUN_", cfg) diff --git a/internal/config/fixtures/config.err.notif.yml b/internal/config/fixtures/config.err.notif.yml deleted file mode 100644 index f5b0b209..00000000 --- a/internal/config/fixtures/config.err.notif.yml +++ /dev/null @@ -1,2 +0,0 @@ -providers: - docker: {} diff --git a/internal/db/manifest.go b/internal/db/manifest.go index e11fa951..3010aba5 100644 --- a/internal/db/manifest.go +++ b/internal/db/manifest.go @@ -3,6 +3,7 @@ package db import ( "bytes" "encoding/json" + "fmt" "github.com/crazy-max/diun/v4/pkg/registry" bolt "go.etcd.io/bbolt" @@ -25,6 +26,25 @@ func (c *Client) First(image registry.Image) (bool, error) { return !found, err } +// ListManifest return a list of Docker images manifests +func (c *Client) ListManifest() ([]registry.Manifest, error) { + var manifests []registry.Manifest + + err := c.View(func(tx *bolt.Tx) error { + c := tx.Bucket([]byte(bucketManifest)).Cursor() + for k, v := c.First(); k != nil; k, v = c.Next() { + var manifest registry.Manifest + if err := json.Unmarshal(v, &manifest); err != nil { + return err + } + manifests = append(manifests, manifest) + } + return nil + }) + + return manifests, err +} + // GetManifest returns Docker image manifest func (c *Client) GetManifest(image registry.Image) (registry.Manifest, error) { var manifest registry.Manifest @@ -43,11 +63,37 @@ func (c *Client) GetManifest(image registry.Image) (registry.Manifest, error) { // PutManifest add Docker image manifest in db func (c *Client) PutManifest(image registry.Image, manifest registry.Manifest) error { entryBytes, _ := json.Marshal(manifest) - - err := c.Update(func(tx *bolt.Tx) error { + return c.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(bucketManifest)) return b.Put([]byte(image.String()), entryBytes) }) - - return err +} + +// DeleteManifest deletes a Docker image manifest +func (c *Client) DeleteManifest(manifest registry.Manifest) error { + return c.Update(func(tx *bolt.Tx) error { + return tx.Bucket([]byte(bucketManifest)).Delete([]byte(fmt.Sprintf("%s:%s", manifest.Name, manifest.Tag))) + }) +} + +// ListImage return a list of Docker images with their linked manifests +func (c *Client) ListImage() (map[string][]registry.Manifest, error) { + images := make(map[string][]registry.Manifest) + + err := c.View(func(tx *bolt.Tx) error { + c := tx.Bucket([]byte(bucketManifest)).Cursor() + for k, v := c.First(); k != nil; k, v = c.Next() { + var manifest registry.Manifest + if err := json.Unmarshal(v, &manifest); err != nil { + return err + } + if _, ok := images[manifest.Name]; !ok { + images[manifest.Name] = []registry.Manifest{} + } + images[manifest.Name] = append(images[manifest.Name], manifest) + } + return nil + }) + + return images, err } diff --git a/internal/db/metadata.go b/internal/db/metadata.go index b4ecf5bc..2d26dbf1 100644 --- a/internal/db/metadata.go +++ b/internal/db/metadata.go @@ -29,11 +29,8 @@ func (c *Client) ReadMetadata() error { // WriteMetadata writes db metadata func (c *Client) WriteMetadata(metadata Metadata) error { entryBytes, _ := json.Marshal(metadata) - - err := c.Update(func(tx *bolt.Tx) error { + return c.Update(func(tx *bolt.Tx) error { b := tx.Bucket([]byte(bucketMetadata)) return b.Put([]byte(metadataKey), entryBytes) }) - - return err } diff --git a/internal/db/migrate.go b/internal/db/migrate.go index 4d2b402a..7a386d43 100644 --- a/internal/db/migrate.go +++ b/internal/db/migrate.go @@ -75,9 +75,5 @@ func (c *Client) migration2() error { } } - if err := tx.Commit(); err != nil { - return err - } - - return nil + return tx.Commit() } diff --git a/internal/grpc/client.go b/internal/grpc/client.go new file mode 100644 index 00000000..f58699e5 --- /dev/null +++ b/internal/grpc/client.go @@ -0,0 +1,58 @@ +package grpc + +import ( + "net" + + "github.com/crazy-max/diun/v4/internal/db" + grpclogger "github.com/crazy-max/diun/v4/internal/grpc/logger" + "github.com/crazy-max/diun/v4/internal/notif" + "github.com/crazy-max/diun/v4/pb" + "github.com/pkg/errors" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + "google.golang.org/grpc" +) + +// Client represents an active grpc object +type Client struct { + server *grpc.Server + authority string + db *db.Client + notif *notif.Client + pb.UnimplementedImageServiceServer + pb.UnimplementedNotifServiceServer +} + +// New creates a new grpc instance +func New(authority string, db *db.Client, notif *notif.Client) (*Client, error) { + grpclogger.SetGrpcLogger(log.Level(zerolog.ErrorLevel)) + + c := &Client{ + authority: authority, + db: db, + notif: notif, + } + + c.server = grpc.NewServer() + pb.RegisterImageServiceServer(c.server, c) + pb.RegisterNotifServiceServer(c.server, c) + + return c, nil +} + +// Start runs the grpc server +func (c *Client) Start() error { + var err error + + lis, err := net.Listen("tcp", c.authority) + if err != nil { + return errors.Wrap(err, "Cannot create gRPC listener") + } + + return c.server.Serve(lis) +} + +// Stop stops the grpc server +func (c *Client) Stop() { + c.server.GracefulStop() +} diff --git a/internal/grpc/image.go b/internal/grpc/image.go new file mode 100644 index 00000000..e02158fa --- /dev/null +++ b/internal/grpc/image.go @@ -0,0 +1,123 @@ +package grpc + +import ( + "context" + "encoding/json" + "fmt" + + "github.com/containers/image/v5/docker/reference" + "github.com/crazy-max/diun/v4/pb" + "google.golang.org/protobuf/types/known/timestamppb" +) + +func (c *Client) ImageList(ctx context.Context, request *pb.ImageListRequest) (*pb.ImageListResponse, error) { + images, err := c.db.ListImage() + if err != nil { + return nil, err + } + + var ilr []*pb.ImageListResponse_Image + for name, manifests := range images { + latest := &manifests[0] + for _, manifest := range manifests { + if manifest.Created.After(*latest.Created) { + latest = &manifest + } + } + ilr = append(ilr, &pb.ImageListResponse_Image{ + Name: name, + ManifestsCount: int64(len(manifests)), + Latest: &pb.Manifest{ + Tag: latest.Tag, + MimeType: latest.MIMEType, + Digest: latest.Digest.String(), + Created: timestamppb.New(*latest.Created), + Labels: latest.Labels, + Platform: latest.Platform, + }, + }) + } + + return &pb.ImageListResponse{ + Images: ilr, + }, nil +} + +func (c *Client) ImageInspect(ctx context.Context, request *pb.ImageInspectRequest) (*pb.ImageInspectResponse, error) { + ref, err := reference.ParseNormalizedNamed(request.Name) + if err != nil { + return nil, err + } + + images, err := c.db.ListImage() + if err != nil { + return nil, err + } + + if _, ok := images[ref.Name()]; !ok { + return nil, fmt.Errorf("%s not found in database", ref.Name()) + } + + iir := &pb.ImageInspectResponse_Image{ + Name: ref.Name(), + Manifests: []*pb.Manifest{}, + } + for _, manifest := range images[ref.Name()] { + iir.Manifests = append(iir.Manifests, &pb.Manifest{ + Tag: manifest.Tag, + MimeType: manifest.MIMEType, + Digest: manifest.Digest.String(), + Created: timestamppb.New(*manifest.Created), + Labels: manifest.Labels, + Platform: manifest.Platform, + }) + } + + return &pb.ImageInspectResponse{ + Image: iir, + }, nil +} + +func (c *Client) ImageRemove(ctx context.Context, request *pb.ImageRemoveRequest) (*pb.ImageRemoveResponse, error) { + ref, err := reference.ParseNormalizedNamed(request.Name) + if err != nil { + return nil, err + } + + images, err := c.db.ListImage() + if err != nil { + return nil, err + } + + if _, ok := images[ref.Name()]; !ok { + return nil, fmt.Errorf("%s not found in database", ref.Name()) + } + + var tag string + if tagged, ok := ref.(reference.Tagged); ok { + tag = tagged.Tag() + } + + var removed []*pb.Manifest + for _, manifest := range images[ref.Name()] { + if len(tag) == 0 || manifest.Tag == tag { + if err = c.db.DeleteManifest(manifest); err != nil { + return nil, err + } + b, _ := json.Marshal(manifest) + removed = append(removed, &pb.Manifest{ + Tag: manifest.Tag, + MimeType: manifest.MIMEType, + Digest: manifest.Digest.String(), + Created: timestamppb.New(*manifest.Created), + Labels: manifest.Labels, + Platform: manifest.Platform, + Size: int64(len(b)), + }) + } + } + + return &pb.ImageRemoveResponse{ + Manifests: removed, + }, nil +} diff --git a/internal/grpc/logger/logger.go b/internal/grpc/logger/logger.go new file mode 100644 index 00000000..93a15649 --- /dev/null +++ b/internal/grpc/logger/logger.go @@ -0,0 +1,94 @@ +package logger + +import ( + "fmt" + + "github.com/rs/zerolog" + "google.golang.org/grpc/grpclog" +) + +func SetGrpcLogger(logger zerolog.Logger) { + grpclog.SetLoggerV2(wrap(logger)) +} + +func wrap(l zerolog.Logger) *bridge { + return &bridge{l} +} + +type bridge struct { + zerolog.Logger +} + +func (b *bridge) Info(args ...interface{}) { + b.Logger.Info().Msg(fmt.Sprint(args...)) +} + +func (b *bridge) Infoln(args ...interface{}) { + b.Logger.Info().Msg(fmt.Sprint(args...)) +} + +func (b *bridge) Infof(format string, args ...interface{}) { + b.Logger.Info().Msgf(format, args...) +} + +func (b *bridge) Warning(args ...interface{}) { + b.Logger.Warn().Msg(fmt.Sprint(args...)) +} + +func (b *bridge) Warningln(args ...interface{}) { + b.Logger.Warn().Msg(fmt.Sprint(args...)) +} + +func (b *bridge) Warningf(format string, args ...interface{}) { + b.Logger.Warn().Msgf(format, args...) +} + +func (b *bridge) Error(args ...interface{}) { + b.Logger.Error().Msg(fmt.Sprint(args...)) +} + +func (b *bridge) Errorln(args ...interface{}) { + b.Logger.Error().Msg(fmt.Sprint(args...)) +} + +func (b *bridge) Errorf(format string, args ...interface{}) { + b.Logger.Error().Msgf(format, args...) +} + +func (b *bridge) Fatal(args ...interface{}) { + b.Logger.Fatal().Msg(fmt.Sprint(args...)) +} + +func (b *bridge) Fatalln(args ...interface{}) { + b.Logger.Fatal().Msg(fmt.Sprint(args...)) +} + +func (b *bridge) Fatalf(format string, args ...interface{}) { + b.Logger.Fatal().Msgf(format, args...) +} + +func (b *bridge) V(verbosity int) bool { + // verbosity values: + // 0 = info + // 1 = warning + // 2 = error + // 3 = fatal + switch b.GetLevel() { + case zerolog.PanicLevel: + return verbosity > 3 + case zerolog.FatalLevel: + return verbosity == 3 + case zerolog.ErrorLevel: + return verbosity == 2 + case zerolog.WarnLevel: + return verbosity == 1 + case zerolog.InfoLevel: + return verbosity == 0 + case zerolog.DebugLevel: + return true + case zerolog.TraceLevel: + return true + default: + return false + } +} diff --git a/internal/grpc/notif.go b/internal/grpc/notif.go new file mode 100644 index 00000000..cf0a3eb6 --- /dev/null +++ b/internal/grpc/notif.go @@ -0,0 +1,72 @@ +package grpc + +import ( + "context" + "fmt" + "strings" + "time" + + "github.com/crazy-max/diun/v4/internal/model" + "github.com/crazy-max/diun/v4/pb" + "github.com/crazy-max/diun/v4/pkg/registry" +) + +func (c *Client) NotifTest(ctx context.Context, request *pb.NotifTestRequest) (*pb.NotifTestResponse, error) { + createdAt, _ := time.Parse("2006-01-02T15:04:05Z", "2020-03-26T12:23:56Z") + image, _ := registry.ParseImage(registry.ParseImageOptions{ + Name: "diun/testnotif:latest", + }) + image.HubLink = "" + + entry := model.NotifEntry{ + Status: "new", + Provider: "file", + Image: image, + Manifest: registry.Manifest{ + Name: "diun/testnotif", + Tag: "latest", + MIMEType: "application/vnd.docker.distribution.manifest.list.v2+json", + Digest: "sha256:216e3ae7de4ca8b553eb11ef7abda00651e79e537e85c46108284e5e91673e01", + Created: &createdAt, + DockerVersion: "", + Labels: map[string]string{ + "maintainer": "CrazyMax", + "org.label-schema.build-date": "2020-03-26T12:23:56Z", + "org.label-schema.description": "Docker image update notifier", + "org.label-schema.name": "Diun", + "org.label-schema.schema-version": "1.0", + "org.label-schema.url": "https://github.com/crazy-max/diun", + "org.label-schema.vcs-ref": "e13f097c", + "org.label-schema.vcs-url": "https://github.com/crazy-max/diun", + "org.label-schema.vendor": "CrazyMax", + "org.label-schema.version": "x.x.x", + }, + Layers: []string{ + "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819", + "sha256:166c6f165b73185ede72415d780538a55c0c8e854bd177925bc007193e5b0d1b", + "sha256:e05682efa9cc9d6239b2b9252fe0dc1e58d6e1585679733bb94a6549d49e9b10", + "sha256:c6a5bfed445b3ed7e85523cd73c6532ac9f9b72bb588ca728fd5b33987ca6538", + "sha256:df2140efb8abeb727ef0b27ff158b7010a7941eb1cfdade505f510a6e1eaf016", + }, + Platform: "linux/amd64", + }, + } + + if len(c.notif.List()) == 0 { + return &pb.NotifTestResponse{ + Message: "No notifier available", + }, nil + } + + var sent []string + for _, n := range c.notif.List() { + if err := n.Send(entry); err != nil { + return nil, err + } + sent = append(sent, n.Name()) + } + + return &pb.NotifTestResponse{ + Message: fmt.Sprintf("Notifcation sent for %s notifier(s)", strings.Join(sent, ", ")), + }, nil +} diff --git a/internal/logging/logger.go b/internal/logging/logger.go index b7483c0d..686e43e0 100644 --- a/internal/logging/logger.go +++ b/internal/logging/logger.go @@ -6,24 +6,30 @@ import ( "os" "time" - "github.com/crazy-max/diun/v4/internal/model" "github.com/rs/zerolog" "github.com/rs/zerolog/log" "github.com/sirupsen/logrus" ) +type Options struct { + LogLevel string + LogJSON bool + LogCaller bool + LogNoColor bool +} + // Configure configures logger -func Configure(cli *model.Cli) { +func Configure(opts Options) { var err error var w io.Writer // Adds support for NO_COLOR. More info https://no-color.org/ _, noColor := os.LookupEnv("NO_COLOR") - if !cli.LogJSON { + if !opts.LogJSON { w = zerolog.ConsoleWriter{ Out: os.Stdout, - NoColor: noColor || cli.LogNoColor, + NoColor: noColor || opts.LogNoColor, TimeFormat: time.RFC1123, } } else { @@ -31,20 +37,20 @@ func Configure(cli *model.Cli) { } ctx := zerolog.New(w).With().Timestamp() - if cli.LogCaller { + if opts.LogCaller { ctx = ctx.Caller() } log.Logger = ctx.Logger() - logLevel, err := zerolog.ParseLevel(cli.LogLevel) + logLevel, err := zerolog.ParseLevel(opts.LogLevel) if err != nil { log.Fatal().Err(err).Msgf("Unknown log level") } else { zerolog.SetGlobalLevel(logLevel) } - logrusLevel, err := logrus.ParseLevel(cli.LogLevel) + logrusLevel, err := logrus.ParseLevel(opts.LogLevel) if err != nil { log.Fatal().Err(err).Msgf("Unknown log level") } else { diff --git a/internal/model/cli.go b/internal/model/cli.go deleted file mode 100644 index c0d32b34..00000000 --- a/internal/model/cli.go +++ /dev/null @@ -1,16 +0,0 @@ -package model - -import "github.com/alecthomas/kong" - -// Cli holds command line args, flags and cmds -type Cli struct { - Version kong.VersionFlag - Cfgfile string `kong:"name='config',env='CONFIG',help='Diun configuration file.'"` - ProfilerPath string `kong:"name='profiler-path',env='PROFILER_PATH',help='Base path where profiling files are written.'"` - Profiler string `kong:"name='profiler',env='PROFILER',help='Profiler to use.'"` - LogLevel string `kong:"name='log-level',env='LOG_LEVEL',default='info',help='Set log level.'"` - LogJSON bool `kong:"name='log-json',env='LOG_JSON',default='false',help='Enable JSON logging output.'"` - LogCaller bool `kong:"name='log-caller',env='LOG_CALLER',default='false',help='Add file:line of the caller to log output.'"` - LogNoColor bool `kong:"name='log-nocolor',env='LOG_NOCOLOR',default='false',help='Disables the colorized output.'"` - TestNotif bool `kong:"name='test-notif',default='false',help='Test notification settings.'"` -} diff --git a/internal/notif/client.go b/internal/notif/client.go index 8a3fb271..534379e9 100644 --- a/internal/notif/client.go +++ b/internal/notif/client.go @@ -95,3 +95,8 @@ func (c *Client) Send(entry model.NotifEntry) { } } } + +// List returns created notifiers +func (c *Client) List() []notifier.Notifier { + return c.notifiers +} diff --git a/mkdocs.yml b/mkdocs.yml index de68fd8f..0347f580 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -90,7 +90,7 @@ nav: - From binary: install/binary.md - Linux service: install/linux-service.md - Usage: - - Command line: usage/cli.md + - Command line: usage/command-line.md - Basic example: usage/basic-example.md - Configuration: - Overview: config/index.md @@ -125,6 +125,7 @@ nav: - FAQ: faq.md - Changelog: changelog.md - Migration: + - Diun v4.0 to v4.17: migration/v4.0-to-v4.17.md - Diun v3 to v4: migration/v3-to-v4.md - Diun v2 to v3: migration/v2-to-v3.md - Diun v1 to v2: migration/v1-to-v2.md diff --git a/pb/gen.go b/pb/gen.go new file mode 100644 index 00000000..9fbf0ce7 --- /dev/null +++ b/pb/gen.go @@ -0,0 +1,3 @@ +package pb + +//go:generate protoc --go_out=paths=source_relative:. --go-grpc_out=paths=source_relative:. image.proto notif.proto diff --git a/pb/image.pb.go b/pb/image.pb.go new file mode 100644 index 00000000..bfd0adb5 --- /dev/null +++ b/pb/image.pb.go @@ -0,0 +1,764 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.17.0 +// source: image.proto + +package pb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + timestamppb "google.golang.org/protobuf/types/known/timestamppb" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type Manifest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + MimeType string `protobuf:"bytes,2,opt,name=mime_type,json=mimeType,proto3" json:"mime_type,omitempty"` + Digest string `protobuf:"bytes,3,opt,name=digest,proto3" json:"digest,omitempty"` + Created *timestamppb.Timestamp `protobuf:"bytes,4,opt,name=created,proto3" json:"created,omitempty"` + Labels map[string]string `protobuf:"bytes,5,rep,name=labels,proto3" json:"labels,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"` + Platform string `protobuf:"bytes,6,opt,name=platform,proto3" json:"platform,omitempty"` + Size int64 `protobuf:"varint,7,opt,name=size,proto3" json:"size,omitempty"` +} + +func (x *Manifest) Reset() { + *x = Manifest{} + if protoimpl.UnsafeEnabled { + mi := &file_image_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Manifest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Manifest) ProtoMessage() {} + +func (x *Manifest) ProtoReflect() protoreflect.Message { + mi := &file_image_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use Manifest.ProtoReflect.Descriptor instead. +func (*Manifest) Descriptor() ([]byte, []int) { + return file_image_proto_rawDescGZIP(), []int{0} +} + +func (x *Manifest) GetTag() string { + if x != nil { + return x.Tag + } + return "" +} + +func (x *Manifest) GetMimeType() string { + if x != nil { + return x.MimeType + } + return "" +} + +func (x *Manifest) GetDigest() string { + if x != nil { + return x.Digest + } + return "" +} + +func (x *Manifest) GetCreated() *timestamppb.Timestamp { + if x != nil { + return x.Created + } + return nil +} + +func (x *Manifest) GetLabels() map[string]string { + if x != nil { + return x.Labels + } + return nil +} + +func (x *Manifest) GetPlatform() string { + if x != nil { + return x.Platform + } + return "" +} + +func (x *Manifest) GetSize() int64 { + if x != nil { + return x.Size + } + return 0 +} + +type ImageListRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *ImageListRequest) Reset() { + *x = ImageListRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_image_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ImageListRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImageListRequest) ProtoMessage() {} + +func (x *ImageListRequest) ProtoReflect() protoreflect.Message { + mi := &file_image_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImageListRequest.ProtoReflect.Descriptor instead. +func (*ImageListRequest) Descriptor() ([]byte, []int) { + return file_image_proto_rawDescGZIP(), []int{1} +} + +type ImageListResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Images []*ImageListResponse_Image `protobuf:"bytes,1,rep,name=images,proto3" json:"images,omitempty"` +} + +func (x *ImageListResponse) Reset() { + *x = ImageListResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_image_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ImageListResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImageListResponse) ProtoMessage() {} + +func (x *ImageListResponse) ProtoReflect() protoreflect.Message { + mi := &file_image_proto_msgTypes[2] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImageListResponse.ProtoReflect.Descriptor instead. +func (*ImageListResponse) Descriptor() ([]byte, []int) { + return file_image_proto_rawDescGZIP(), []int{2} +} + +func (x *ImageListResponse) GetImages() []*ImageListResponse_Image { + if x != nil { + return x.Images + } + return nil +} + +type ImageInspectRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *ImageInspectRequest) Reset() { + *x = ImageInspectRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_image_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ImageInspectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImageInspectRequest) ProtoMessage() {} + +func (x *ImageInspectRequest) ProtoReflect() protoreflect.Message { + mi := &file_image_proto_msgTypes[3] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImageInspectRequest.ProtoReflect.Descriptor instead. +func (*ImageInspectRequest) Descriptor() ([]byte, []int) { + return file_image_proto_rawDescGZIP(), []int{3} +} + +func (x *ImageInspectRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type ImageInspectResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Image *ImageInspectResponse_Image `protobuf:"bytes,1,opt,name=image,proto3" json:"image,omitempty"` +} + +func (x *ImageInspectResponse) Reset() { + *x = ImageInspectResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_image_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ImageInspectResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImageInspectResponse) ProtoMessage() {} + +func (x *ImageInspectResponse) ProtoReflect() protoreflect.Message { + mi := &file_image_proto_msgTypes[4] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImageInspectResponse.ProtoReflect.Descriptor instead. +func (*ImageInspectResponse) Descriptor() ([]byte, []int) { + return file_image_proto_rawDescGZIP(), []int{4} +} + +func (x *ImageInspectResponse) GetImage() *ImageInspectResponse_Image { + if x != nil { + return x.Image + } + return nil +} + +type ImageRemoveRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` +} + +func (x *ImageRemoveRequest) Reset() { + *x = ImageRemoveRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_image_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ImageRemoveRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImageRemoveRequest) ProtoMessage() {} + +func (x *ImageRemoveRequest) ProtoReflect() protoreflect.Message { + mi := &file_image_proto_msgTypes[5] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImageRemoveRequest.ProtoReflect.Descriptor instead. +func (*ImageRemoveRequest) Descriptor() ([]byte, []int) { + return file_image_proto_rawDescGZIP(), []int{5} +} + +func (x *ImageRemoveRequest) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +type ImageRemoveResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Manifests []*Manifest `protobuf:"bytes,1,rep,name=manifests,proto3" json:"manifests,omitempty"` +} + +func (x *ImageRemoveResponse) Reset() { + *x = ImageRemoveResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_image_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ImageRemoveResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImageRemoveResponse) ProtoMessage() {} + +func (x *ImageRemoveResponse) ProtoReflect() protoreflect.Message { + mi := &file_image_proto_msgTypes[6] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImageRemoveResponse.ProtoReflect.Descriptor instead. +func (*ImageRemoveResponse) Descriptor() ([]byte, []int) { + return file_image_proto_rawDescGZIP(), []int{6} +} + +func (x *ImageRemoveResponse) GetManifests() []*Manifest { + if x != nil { + return x.Manifests + } + return nil +} + +type ImageListResponse_Image struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + ManifestsCount int64 `protobuf:"varint,2,opt,name=manifestsCount,proto3" json:"manifestsCount,omitempty"` + Latest *Manifest `protobuf:"bytes,3,opt,name=latest,proto3" json:"latest,omitempty"` +} + +func (x *ImageListResponse_Image) Reset() { + *x = ImageListResponse_Image{} + if protoimpl.UnsafeEnabled { + mi := &file_image_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ImageListResponse_Image) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImageListResponse_Image) ProtoMessage() {} + +func (x *ImageListResponse_Image) ProtoReflect() protoreflect.Message { + mi := &file_image_proto_msgTypes[8] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImageListResponse_Image.ProtoReflect.Descriptor instead. +func (*ImageListResponse_Image) Descriptor() ([]byte, []int) { + return file_image_proto_rawDescGZIP(), []int{2, 0} +} + +func (x *ImageListResponse_Image) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ImageListResponse_Image) GetManifestsCount() int64 { + if x != nil { + return x.ManifestsCount + } + return 0 +} + +func (x *ImageListResponse_Image) GetLatest() *Manifest { + if x != nil { + return x.Latest + } + return nil +} + +type ImageInspectResponse_Image struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Manifests []*Manifest `protobuf:"bytes,2,rep,name=manifests,proto3" json:"manifests,omitempty"` +} + +func (x *ImageInspectResponse_Image) Reset() { + *x = ImageInspectResponse_Image{} + if protoimpl.UnsafeEnabled { + mi := &file_image_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ImageInspectResponse_Image) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ImageInspectResponse_Image) ProtoMessage() {} + +func (x *ImageInspectResponse_Image) ProtoReflect() protoreflect.Message { + mi := &file_image_proto_msgTypes[9] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ImageInspectResponse_Image.ProtoReflect.Descriptor instead. +func (*ImageInspectResponse_Image) Descriptor() ([]byte, []int) { + return file_image_proto_rawDescGZIP(), []int{4, 0} +} + +func (x *ImageInspectResponse_Image) GetName() string { + if x != nil { + return x.Name + } + return "" +} + +func (x *ImageInspectResponse_Image) GetManifests() []*Manifest { + if x != nil { + return x.Manifests + } + return nil +} + +var File_image_proto protoreflect.FileDescriptor + +var file_image_proto_rawDesc = []byte{ + 0x0a, 0x0b, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x70, + 0x62, 0x1a, 0x1f, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2f, 0x74, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x2e, 0x70, 0x72, 0x6f, + 0x74, 0x6f, 0x22, 0xa4, 0x02, 0x0a, 0x08, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x12, + 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x74, 0x61, + 0x67, 0x12, 0x1b, 0x0a, 0x09, 0x6d, 0x69, 0x6d, 0x65, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x08, 0x6d, 0x69, 0x6d, 0x65, 0x54, 0x79, 0x70, 0x65, 0x12, 0x16, + 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, + 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x34, 0x0a, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, + 0x64, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, + 0x61, 0x6d, 0x70, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x30, 0x0a, 0x06, + 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x70, + 0x62, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x2e, 0x4c, 0x61, 0x62, 0x65, 0x6c, + 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x52, 0x06, 0x6c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x12, 0x1a, + 0x0a, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x18, 0x06, 0x20, 0x01, 0x28, 0x09, + 0x52, 0x08, 0x70, 0x6c, 0x61, 0x74, 0x66, 0x6f, 0x72, 0x6d, 0x12, 0x12, 0x0a, 0x04, 0x73, 0x69, + 0x7a, 0x65, 0x18, 0x07, 0x20, 0x01, 0x28, 0x03, 0x52, 0x04, 0x73, 0x69, 0x7a, 0x65, 0x1a, 0x39, + 0x0a, 0x0b, 0x4c, 0x61, 0x62, 0x65, 0x6c, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x10, 0x0a, + 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, + 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, + 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x22, 0x12, 0x0a, 0x10, 0x49, 0x6d, 0x61, + 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0xb3, 0x01, + 0x0a, 0x11, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x12, 0x33, 0x0a, 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x4c, 0x69, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, + 0x52, 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x1a, 0x69, 0x0a, 0x05, 0x49, 0x6d, 0x61, 0x67, + 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x26, 0x0a, 0x0e, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, + 0x74, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x6d, + 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x43, 0x6f, 0x75, 0x6e, 0x74, 0x12, 0x24, 0x0a, + 0x06, 0x6c, 0x61, 0x74, 0x65, 0x73, 0x74, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0c, 0x2e, + 0x70, 0x62, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x06, 0x6c, 0x61, 0x74, + 0x65, 0x73, 0x74, 0x22, 0x29, 0x0a, 0x13, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x73, 0x70, + 0x65, 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x95, + 0x01, 0x0a, 0x14, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x34, 0x0a, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, + 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1e, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6d, 0x61, 0x67, + 0x65, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x05, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x1a, 0x47, 0x0a, + 0x05, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x12, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x2a, 0x0a, 0x09, 0x6d, 0x61, + 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, + 0x70, 0x62, 0x2e, 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x09, 0x6d, 0x61, 0x6e, + 0x69, 0x66, 0x65, 0x73, 0x74, 0x73, 0x22, 0x28, 0x0a, 0x12, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, + 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, + 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, + 0x22, 0x41, 0x0a, 0x13, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2a, 0x0a, 0x09, 0x6d, 0x61, 0x6e, 0x69, 0x66, + 0x65, 0x73, 0x74, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x0c, 0x2e, 0x70, 0x62, 0x2e, + 0x4d, 0x61, 0x6e, 0x69, 0x66, 0x65, 0x73, 0x74, 0x52, 0x09, 0x6d, 0x61, 0x6e, 0x69, 0x66, 0x65, + 0x73, 0x74, 0x73, 0x32, 0xd1, 0x01, 0x0a, 0x0c, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x09, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, + 0x74, 0x12, 0x14, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6d, 0x61, + 0x67, 0x65, 0x4c, 0x69, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x12, 0x43, 0x0a, 0x0c, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, + 0x12, 0x17, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x73, 0x70, 0x65, + 0x63, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x18, 0x2e, 0x70, 0x62, 0x2e, 0x49, + 0x6d, 0x61, 0x67, 0x65, 0x49, 0x6e, 0x73, 0x70, 0x65, 0x63, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, + 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x40, 0x0a, 0x0b, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x6d, 0x6f, 0x76, 0x65, 0x12, 0x16, 0x2e, 0x70, 0x62, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, + 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x70, + 0x62, 0x2e, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x52, 0x65, 0x73, + 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x1e, 0x5a, 0x1c, 0x67, 0x69, 0x74, 0x68, 0x75, + 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x72, 0x61, 0x7a, 0x79, 0x2d, 0x6d, 0x61, 0x78, 0x2f, + 0x64, 0x69, 0x75, 0x6e, 0x2f, 0x70, 0x62, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_image_proto_rawDescOnce sync.Once + file_image_proto_rawDescData = file_image_proto_rawDesc +) + +func file_image_proto_rawDescGZIP() []byte { + file_image_proto_rawDescOnce.Do(func() { + file_image_proto_rawDescData = protoimpl.X.CompressGZIP(file_image_proto_rawDescData) + }) + return file_image_proto_rawDescData +} + +var file_image_proto_msgTypes = make([]protoimpl.MessageInfo, 10) +var file_image_proto_goTypes = []interface{}{ + (*Manifest)(nil), // 0: pb.Manifest + (*ImageListRequest)(nil), // 1: pb.ImageListRequest + (*ImageListResponse)(nil), // 2: pb.ImageListResponse + (*ImageInspectRequest)(nil), // 3: pb.ImageInspectRequest + (*ImageInspectResponse)(nil), // 4: pb.ImageInspectResponse + (*ImageRemoveRequest)(nil), // 5: pb.ImageRemoveRequest + (*ImageRemoveResponse)(nil), // 6: pb.ImageRemoveResponse + nil, // 7: pb.Manifest.LabelsEntry + (*ImageListResponse_Image)(nil), // 8: pb.ImageListResponse.Image + (*ImageInspectResponse_Image)(nil), // 9: pb.ImageInspectResponse.Image + (*timestamppb.Timestamp)(nil), // 10: google.protobuf.Timestamp +} +var file_image_proto_depIdxs = []int32{ + 10, // 0: pb.Manifest.created:type_name -> google.protobuf.Timestamp + 7, // 1: pb.Manifest.labels:type_name -> pb.Manifest.LabelsEntry + 8, // 2: pb.ImageListResponse.images:type_name -> pb.ImageListResponse.Image + 9, // 3: pb.ImageInspectResponse.image:type_name -> pb.ImageInspectResponse.Image + 0, // 4: pb.ImageRemoveResponse.manifests:type_name -> pb.Manifest + 0, // 5: pb.ImageListResponse.Image.latest:type_name -> pb.Manifest + 0, // 6: pb.ImageInspectResponse.Image.manifests:type_name -> pb.Manifest + 1, // 7: pb.ImageService.ImageList:input_type -> pb.ImageListRequest + 3, // 8: pb.ImageService.ImageInspect:input_type -> pb.ImageInspectRequest + 5, // 9: pb.ImageService.ImageRemove:input_type -> pb.ImageRemoveRequest + 2, // 10: pb.ImageService.ImageList:output_type -> pb.ImageListResponse + 4, // 11: pb.ImageService.ImageInspect:output_type -> pb.ImageInspectResponse + 6, // 12: pb.ImageService.ImageRemove:output_type -> pb.ImageRemoveResponse + 10, // [10:13] is the sub-list for method output_type + 7, // [7:10] is the sub-list for method input_type + 7, // [7:7] is the sub-list for extension type_name + 7, // [7:7] is the sub-list for extension extendee + 0, // [0:7] is the sub-list for field type_name +} + +func init() { file_image_proto_init() } +func file_image_proto_init() { + if File_image_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_image_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Manifest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_image_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImageListRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_image_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImageListResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_image_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImageInspectRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_image_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImageInspectResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_image_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImageRemoveRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_image_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImageRemoveResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_image_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImageListResponse_Image); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_image_proto_msgTypes[9].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ImageInspectResponse_Image); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_image_proto_rawDesc, + NumEnums: 0, + NumMessages: 10, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_image_proto_goTypes, + DependencyIndexes: file_image_proto_depIdxs, + MessageInfos: file_image_proto_msgTypes, + }.Build() + File_image_proto = out.File + file_image_proto_rawDesc = nil + file_image_proto_goTypes = nil + file_image_proto_depIdxs = nil +} diff --git a/pb/image.proto b/pb/image.proto new file mode 100644 index 00000000..9871297a --- /dev/null +++ b/pb/image.proto @@ -0,0 +1,53 @@ +syntax = "proto3"; +option go_package = "github.com/crazy-max/diun/pb"; + +package pb; + +import "google/protobuf/timestamp.proto"; + +message Manifest { + string tag = 1; + string mime_type = 2; + string digest = 3; + google.protobuf.Timestamp created = 4; + map labels = 5; + string platform = 6; + int64 size = 7; +} + +message ImageListRequest {} + +message ImageListResponse { + message Image { + string name = 1; + int64 manifestsCount = 2; + Manifest latest = 3; + } + repeated Image images = 1; +} + +message ImageInspectRequest { + string name = 1; +} + +message ImageInspectResponse { + message Image { + string name = 1; + repeated Manifest manifests = 2; + } + Image image = 1; +} + +message ImageRemoveRequest { + string name = 1; +} + +message ImageRemoveResponse { + repeated Manifest manifests = 1; +} + +service ImageService { + rpc ImageList(ImageListRequest) returns (ImageListResponse) {} + rpc ImageInspect(ImageInspectRequest) returns (ImageInspectResponse) {} + rpc ImageRemove(ImageRemoveRequest) returns (ImageRemoveResponse) {} +} diff --git a/pb/image_grpc.pb.go b/pb/image_grpc.pb.go new file mode 100644 index 00000000..d2028311 --- /dev/null +++ b/pb/image_grpc.pb.go @@ -0,0 +1,173 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package pb + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// ImageServiceClient is the client API for ImageService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type ImageServiceClient interface { + ImageList(ctx context.Context, in *ImageListRequest, opts ...grpc.CallOption) (*ImageListResponse, error) + ImageInspect(ctx context.Context, in *ImageInspectRequest, opts ...grpc.CallOption) (*ImageInspectResponse, error) + ImageRemove(ctx context.Context, in *ImageRemoveRequest, opts ...grpc.CallOption) (*ImageRemoveResponse, error) +} + +type imageServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewImageServiceClient(cc grpc.ClientConnInterface) ImageServiceClient { + return &imageServiceClient{cc} +} + +func (c *imageServiceClient) ImageList(ctx context.Context, in *ImageListRequest, opts ...grpc.CallOption) (*ImageListResponse, error) { + out := new(ImageListResponse) + err := c.cc.Invoke(ctx, "/pb.ImageService/ImageList", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *imageServiceClient) ImageInspect(ctx context.Context, in *ImageInspectRequest, opts ...grpc.CallOption) (*ImageInspectResponse, error) { + out := new(ImageInspectResponse) + err := c.cc.Invoke(ctx, "/pb.ImageService/ImageInspect", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +func (c *imageServiceClient) ImageRemove(ctx context.Context, in *ImageRemoveRequest, opts ...grpc.CallOption) (*ImageRemoveResponse, error) { + out := new(ImageRemoveResponse) + err := c.cc.Invoke(ctx, "/pb.ImageService/ImageRemove", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// ImageServiceServer is the server API for ImageService service. +// All implementations must embed UnimplementedImageServiceServer +// for forward compatibility +type ImageServiceServer interface { + ImageList(context.Context, *ImageListRequest) (*ImageListResponse, error) + ImageInspect(context.Context, *ImageInspectRequest) (*ImageInspectResponse, error) + ImageRemove(context.Context, *ImageRemoveRequest) (*ImageRemoveResponse, error) + mustEmbedUnimplementedImageServiceServer() +} + +// UnimplementedImageServiceServer must be embedded to have forward compatible implementations. +type UnimplementedImageServiceServer struct { +} + +func (UnimplementedImageServiceServer) ImageList(context.Context, *ImageListRequest) (*ImageListResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ImageList not implemented") +} +func (UnimplementedImageServiceServer) ImageInspect(context.Context, *ImageInspectRequest) (*ImageInspectResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ImageInspect not implemented") +} +func (UnimplementedImageServiceServer) ImageRemove(context.Context, *ImageRemoveRequest) (*ImageRemoveResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method ImageRemove not implemented") +} +func (UnimplementedImageServiceServer) mustEmbedUnimplementedImageServiceServer() {} + +// UnsafeImageServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to ImageServiceServer will +// result in compilation errors. +type UnsafeImageServiceServer interface { + mustEmbedUnimplementedImageServiceServer() +} + +func RegisterImageServiceServer(s grpc.ServiceRegistrar, srv ImageServiceServer) { + s.RegisterService(&ImageService_ServiceDesc, srv) +} + +func _ImageService_ImageList_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ImageListRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ImageServiceServer).ImageList(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/pb.ImageService/ImageList", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ImageServiceServer).ImageList(ctx, req.(*ImageListRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ImageService_ImageInspect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ImageInspectRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ImageServiceServer).ImageInspect(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/pb.ImageService/ImageInspect", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ImageServiceServer).ImageInspect(ctx, req.(*ImageInspectRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _ImageService_ImageRemove_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ImageRemoveRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(ImageServiceServer).ImageRemove(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/pb.ImageService/ImageRemove", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(ImageServiceServer).ImageRemove(ctx, req.(*ImageRemoveRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// ImageService_ServiceDesc is the grpc.ServiceDesc for ImageService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var ImageService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "pb.ImageService", + HandlerType: (*ImageServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ImageList", + Handler: _ImageService_ImageList_Handler, + }, + { + MethodName: "ImageInspect", + Handler: _ImageService_ImageInspect_Handler, + }, + { + MethodName: "ImageRemove", + Handler: _ImageService_ImageRemove_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "image.proto", +} diff --git a/pb/notif.pb.go b/pb/notif.pb.go new file mode 100644 index 00000000..636f6190 --- /dev/null +++ b/pb/notif.pb.go @@ -0,0 +1,202 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.26.0 +// protoc v3.17.0 +// source: notif.proto + +package pb + +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" +) + +const ( + // Verify that this generated code is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion) + // Verify that runtime/protoimpl is sufficiently up-to-date. + _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) +) + +type NotifTestRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *NotifTestRequest) Reset() { + *x = NotifTestRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_notif_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NotifTestRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NotifTestRequest) ProtoMessage() {} + +func (x *NotifTestRequest) ProtoReflect() protoreflect.Message { + mi := &file_notif_proto_msgTypes[0] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NotifTestRequest.ProtoReflect.Descriptor instead. +func (*NotifTestRequest) Descriptor() ([]byte, []int) { + return file_notif_proto_rawDescGZIP(), []int{0} +} + +type NotifTestResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"` +} + +func (x *NotifTestResponse) Reset() { + *x = NotifTestResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_notif_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *NotifTestResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*NotifTestResponse) ProtoMessage() {} + +func (x *NotifTestResponse) ProtoReflect() protoreflect.Message { + mi := &file_notif_proto_msgTypes[1] + if protoimpl.UnsafeEnabled && x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use NotifTestResponse.ProtoReflect.Descriptor instead. +func (*NotifTestResponse) Descriptor() ([]byte, []int) { + return file_notif_proto_rawDescGZIP(), []int{1} +} + +func (x *NotifTestResponse) GetMessage() string { + if x != nil { + return x.Message + } + return "" +} + +var File_notif_proto protoreflect.FileDescriptor + +var file_notif_proto_rawDesc = []byte{ + 0x0a, 0x0b, 0x6e, 0x6f, 0x74, 0x69, 0x66, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x02, 0x70, + 0x62, 0x22, 0x12, 0x0a, 0x10, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, + 0x71, 0x75, 0x65, 0x73, 0x74, 0x22, 0x2d, 0x0a, 0x11, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x54, 0x65, + 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, + 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, + 0x73, 0x61, 0x67, 0x65, 0x32, 0x4a, 0x0a, 0x0c, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x53, 0x65, 0x72, + 0x76, 0x69, 0x63, 0x65, 0x12, 0x3a, 0x0a, 0x09, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x54, 0x65, 0x73, + 0x74, 0x12, 0x14, 0x2e, 0x70, 0x62, 0x2e, 0x4e, 0x6f, 0x74, 0x69, 0x66, 0x54, 0x65, 0x73, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x15, 0x2e, 0x70, 0x62, 0x2e, 0x4e, 0x6f, 0x74, + 0x69, 0x66, 0x54, 0x65, 0x73, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, + 0x42, 0x1e, 0x5a, 0x1c, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, + 0x72, 0x61, 0x7a, 0x79, 0x2d, 0x6d, 0x61, 0x78, 0x2f, 0x64, 0x69, 0x75, 0x6e, 0x2f, 0x70, 0x62, + 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_notif_proto_rawDescOnce sync.Once + file_notif_proto_rawDescData = file_notif_proto_rawDesc +) + +func file_notif_proto_rawDescGZIP() []byte { + file_notif_proto_rawDescOnce.Do(func() { + file_notif_proto_rawDescData = protoimpl.X.CompressGZIP(file_notif_proto_rawDescData) + }) + return file_notif_proto_rawDescData +} + +var file_notif_proto_msgTypes = make([]protoimpl.MessageInfo, 2) +var file_notif_proto_goTypes = []interface{}{ + (*NotifTestRequest)(nil), // 0: pb.NotifTestRequest + (*NotifTestResponse)(nil), // 1: pb.NotifTestResponse +} +var file_notif_proto_depIdxs = []int32{ + 0, // 0: pb.NotifService.NotifTest:input_type -> pb.NotifTestRequest + 1, // 1: pb.NotifService.NotifTest:output_type -> pb.NotifTestResponse + 1, // [1:2] is the sub-list for method output_type + 0, // [0:1] is the sub-list for method input_type + 0, // [0:0] is the sub-list for extension type_name + 0, // [0:0] is the sub-list for extension extendee + 0, // [0:0] is the sub-list for field type_name +} + +func init() { file_notif_proto_init() } +func file_notif_proto_init() { + if File_notif_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_notif_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NotifTestRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_notif_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*NotifTestResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_notif_proto_rawDesc, + NumEnums: 0, + NumMessages: 2, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_notif_proto_goTypes, + DependencyIndexes: file_notif_proto_depIdxs, + MessageInfos: file_notif_proto_msgTypes, + }.Build() + File_notif_proto = out.File + file_notif_proto_rawDesc = nil + file_notif_proto_goTypes = nil + file_notif_proto_depIdxs = nil +} diff --git a/pb/notif.proto b/pb/notif.proto new file mode 100644 index 00000000..45cf46f4 --- /dev/null +++ b/pb/notif.proto @@ -0,0 +1,14 @@ +syntax = "proto3"; +option go_package = "github.com/crazy-max/diun/pb"; + +package pb; + +message NotifTestRequest {} + +message NotifTestResponse { + string message = 1; +} + +service NotifService { + rpc NotifTest(NotifTestRequest) returns (NotifTestResponse) {} +} diff --git a/pb/notif_grpc.pb.go b/pb/notif_grpc.pb.go new file mode 100644 index 00000000..3a6f1cbc --- /dev/null +++ b/pb/notif_grpc.pb.go @@ -0,0 +1,101 @@ +// Code generated by protoc-gen-go-grpc. DO NOT EDIT. + +package pb + +import ( + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" +) + +// This is a compile-time assertion to ensure that this generated file +// is compatible with the grpc package it is being compiled against. +// Requires gRPC-Go v1.32.0 or later. +const _ = grpc.SupportPackageIsVersion7 + +// NotifServiceClient is the client API for NotifService service. +// +// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream. +type NotifServiceClient interface { + NotifTest(ctx context.Context, in *NotifTestRequest, opts ...grpc.CallOption) (*NotifTestResponse, error) +} + +type notifServiceClient struct { + cc grpc.ClientConnInterface +} + +func NewNotifServiceClient(cc grpc.ClientConnInterface) NotifServiceClient { + return ¬ifServiceClient{cc} +} + +func (c *notifServiceClient) NotifTest(ctx context.Context, in *NotifTestRequest, opts ...grpc.CallOption) (*NotifTestResponse, error) { + out := new(NotifTestResponse) + err := c.cc.Invoke(ctx, "/pb.NotifService/NotifTest", in, out, opts...) + if err != nil { + return nil, err + } + return out, nil +} + +// NotifServiceServer is the server API for NotifService service. +// All implementations must embed UnimplementedNotifServiceServer +// for forward compatibility +type NotifServiceServer interface { + NotifTest(context.Context, *NotifTestRequest) (*NotifTestResponse, error) + mustEmbedUnimplementedNotifServiceServer() +} + +// UnimplementedNotifServiceServer must be embedded to have forward compatible implementations. +type UnimplementedNotifServiceServer struct { +} + +func (UnimplementedNotifServiceServer) NotifTest(context.Context, *NotifTestRequest) (*NotifTestResponse, error) { + return nil, status.Errorf(codes.Unimplemented, "method NotifTest not implemented") +} +func (UnimplementedNotifServiceServer) mustEmbedUnimplementedNotifServiceServer() {} + +// UnsafeNotifServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to NotifServiceServer will +// result in compilation errors. +type UnsafeNotifServiceServer interface { + mustEmbedUnimplementedNotifServiceServer() +} + +func RegisterNotifServiceServer(s grpc.ServiceRegistrar, srv NotifServiceServer) { + s.RegisterService(&NotifService_ServiceDesc, srv) +} + +func _NotifService_NotifTest_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(NotifTestRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(NotifServiceServer).NotifTest(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: "/pb.NotifService/NotifTest", + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(NotifServiceServer).NotifTest(ctx, req.(*NotifTestRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// NotifService_ServiceDesc is the grpc.ServiceDesc for NotifService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var NotifService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "pb.NotifService", + HandlerType: (*NotifServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "NotifTest", + Handler: _NotifService_NotifTest_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "notif.proto", +} diff --git a/tools.go b/tools.go new file mode 100644 index 00000000..07d59d1d --- /dev/null +++ b/tools.go @@ -0,0 +1,8 @@ +// +build tools + +package tools + +import ( + _ "google.golang.org/grpc/cmd/protoc-gen-go-grpc" + _ "google.golang.org/protobuf/cmd/protoc-gen-go" +)