From 296bbc5357efb0a50f9baf47fda4812a301626f1 Mon Sep 17 00:00:00 2001 From: Kyle Isom Date: Fri, 27 Mar 2026 01:42:28 -0700 Subject: [PATCH] Add mcrctl purge command for tag retention MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Client-side purge that keeps the last N tags per repository (excluding latest) and deletes older manifests. Uses existing MCR APIs — no new server RPCs needed. Server-side: added updated_at to TagInfo struct and GetRepositoryDetail query so tags can be sorted by recency. Usage: mcrctl purge --keep 3 --dry-run --gc Co-Authored-By: Claude Opus 4.6 (1M context) --- cmd/mcrctl/main.go | 1 + cmd/mcrctl/purge.go | 274 ++++++++++++ gen/mcr/v1/registry.pb.go | 708 ++++++++++++++++++++++++++++++-- gen/mcr/v1/registry_grpc.pb.go | 379 +++++++++-------- internal/db/admin.go | 9 +- internal/grpcserver/registry.go | 5 +- proto/mcr/v1/registry.proto | 1 + 7 files changed, 1156 insertions(+), 221 deletions(-) create mode 100644 cmd/mcrctl/purge.go diff --git a/cmd/mcrctl/main.go b/cmd/mcrctl/main.go index 8821e76..619cd62 100644 --- a/cmd/mcrctl/main.go +++ b/cmd/mcrctl/main.go @@ -62,6 +62,7 @@ func main() { root.AddCommand(statusCmd()) root.AddCommand(repoCmd()) root.AddCommand(gcCmd()) + root.AddCommand(purgeCmd()) root.AddCommand(policyCmd()) root.AddCommand(auditCmd()) root.AddCommand(snapshotCmd()) diff --git a/cmd/mcrctl/purge.go b/cmd/mcrctl/purge.go new file mode 100644 index 0000000..bd1dda0 --- /dev/null +++ b/cmd/mcrctl/purge.go @@ -0,0 +1,274 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path" + "sort" + "strings" + + "github.com/spf13/cobra" + + mcrv1 "git.wntrmute.dev/kyle/mcr/gen/mcr/v1" +) + +func purgeCmd() *cobra.Command { + var ( + keep int + repo string + dryRun bool + trigGC bool + ) + + cmd := &cobra.Command{ + Use: "purge", + Short: "Remove old image tags, keeping the last N per repository", + Long: `Purge removes old image tags from repositories based on a retention +policy. The 'latest' tag is always kept. For each repository, tags are +sorted by updated_at (most recent first) and only the --keep most recent +are retained. Remaining tags' manifests are deleted, cascading to their +tag references. Run with --gc to trigger garbage collection afterward.`, + RunE: func(_ *cobra.Command, _ []string) error { + return runPurge(keep, repo, dryRun, trigGC) + }, + } + + cmd.Flags().IntVar(&keep, "keep", 3, "number of tags to keep per repo (excluding latest)") + cmd.Flags().StringVar(&repo, "repo", "", "limit to repositories matching this glob pattern") + cmd.Flags().BoolVar(&dryRun, "dry-run", false, "show what would be deleted without deleting") + cmd.Flags().BoolVar(&trigGC, "gc", false, "trigger garbage collection after purging") + + return cmd +} + +// tagWithTime is a tag with its timestamp for sorting. +type tagWithTime struct { + Name string + Digest string + UpdatedAt string +} + +func runPurge(keep int, repoPattern string, dryRun, trigGC bool) error { + if keep < 0 { + return fmt.Errorf("--keep must be non-negative") + } + + // List repositories. + repos, err := listRepos() + if err != nil { + return err + } + + // Filter by pattern if given. + if repoPattern != "" { + var filtered []string + for _, r := range repos { + matched, _ := path.Match(repoPattern, r) + if matched { + filtered = append(filtered, r) + } + } + repos = filtered + } + + if len(repos) == 0 { + _, _ = fmt.Fprintln(os.Stdout, "No repositories matched.") + return nil + } + + totalDeleted := 0 + + for _, repoName := range repos { + tags, err := listTagsWithTime(repoName) + if err != nil { + _, _ = fmt.Fprintf(os.Stderr, "warning: skipping %s: %v\n", repoName, err) + continue + } + + // Separate latest from versioned tags. + var latest *tagWithTime + var versioned []tagWithTime + for _, t := range tags { + if t.Name == "latest" { + lt := t + latest = < + } else { + versioned = append(versioned, t) + } + } + + // Sort by updated_at descending (most recent first). + sort.Slice(versioned, func(i, j int) bool { + return versioned[i].UpdatedAt > versioned[j].UpdatedAt + }) + + // Determine which tags to delete. + if len(versioned) <= keep { + continue // nothing to purge + } + + toDelete := versioned[keep:] + + // Build set of digests that are still referenced by kept tags. + keptDigests := make(map[string]bool) + if latest != nil { + keptDigests[latest.Digest] = true + } + for _, t := range versioned[:keep] { + keptDigests[t.Digest] = true + } + + for _, t := range toDelete { + if keptDigests[t.Digest] { + // Manifest is shared with a kept tag — skip deletion. + if dryRun { + _, _ = fmt.Fprintf(os.Stdout, "[skip] %s:%s (manifest shared with kept tag)\n", repoName, t.Name) + } + continue + } + + if dryRun { + _, _ = fmt.Fprintf(os.Stdout, "[delete] %s:%s (digest: %s, updated: %s)\n", repoName, t.Name, t.Digest[:12], t.UpdatedAt) + } else { + if err := deleteManifest(repoName, t.Digest); err != nil { + _, _ = fmt.Fprintf(os.Stderr, "warning: failed to delete %s:%s: %v\n", repoName, t.Name, err) + continue + } + _, _ = fmt.Fprintf(os.Stdout, "deleted %s:%s\n", repoName, t.Name) + } + totalDeleted++ + } + } + + if dryRun { + _, _ = fmt.Fprintf(os.Stdout, "\nDry run: %d manifests would be deleted.\n", totalDeleted) + } else { + _, _ = fmt.Fprintf(os.Stdout, "\nPurged %d manifests.\n", totalDeleted) + } + + // Trigger GC if requested and not a dry run. + if trigGC && !dryRun && totalDeleted > 0 { + _, _ = fmt.Fprintln(os.Stdout, "Triggering garbage collection...") + if err := triggerGC(); err != nil { + return fmt.Errorf("gc: %w", err) + } + _, _ = fmt.Fprintln(os.Stdout, "Garbage collection started.") + } + + return nil +} + +// listRepos returns all repository names. +func listRepos() ([]string, error) { + if client.useGRPC() { + resp, err := client.registry.ListRepositories(context.Background(), &mcrv1.ListRepositoriesRequest{}) + if err != nil { + return nil, fmt.Errorf("list repositories: %w", err) + } + var names []string + for _, r := range resp.GetRepositories() { + names = append(names, r.GetName()) + } + return names, nil + } + + data, err := client.restDo("GET", "/v1/repositories", nil) + if err != nil { + return nil, fmt.Errorf("list repositories: %w", err) + } + + var repos []struct { + Name string `json:"name"` + } + if err := json.Unmarshal(data, &repos); err != nil { + return nil, err + } + + var names []string + for _, r := range repos { + names = append(names, r.Name) + } + return names, nil +} + +// listTagsWithTime returns tags with timestamps for a repository. +func listTagsWithTime(repoName string) ([]tagWithTime, error) { + if client.useGRPC() { + resp, err := client.registry.GetRepository(context.Background(), &mcrv1.GetRepositoryRequest{Name: repoName}) + if err != nil { + return nil, fmt.Errorf("get repository: %w", err) + } + var tags []tagWithTime + for _, t := range resp.GetTags() { + tags = append(tags, tagWithTime{ + Name: t.GetName(), + Digest: t.GetDigest(), + UpdatedAt: t.GetUpdatedAt(), + }) + } + return tags, nil + } + + data, err := client.restDo("GET", "/v1/repositories/"+repoName, nil) + if err != nil { + return nil, fmt.Errorf("get repository: %w", err) + } + + var detail struct { + Tags []struct { + Name string `json:"name"` + Digest string `json:"digest"` + UpdatedAt string `json:"updated_at"` + } `json:"tags"` + } + if err := json.Unmarshal(data, &detail); err != nil { + return nil, err + } + + var tags []tagWithTime + for _, t := range detail.Tags { + tags = append(tags, tagWithTime{ + Name: t.Name, + Digest: t.Digest, + UpdatedAt: t.UpdatedAt, + }) + } + return tags, nil +} + +// deleteManifest deletes a manifest by digest via the OCI API. +func deleteManifest(repoName, digest string) error { + if client.useGRPC() { + // No gRPC RPC for OCI manifest delete — fall back to REST. + // The OCI Distribution spec uses DELETE /v2/{name}/manifests/{reference}. + } + + // URL-encode the repo name for path segments with slashes. + _, err := client.restDo("DELETE", "/v2/"+repoName+"/manifests/"+digest, nil) + return err +} + +// triggerGC starts garbage collection. +func triggerGC() error { + if client.useGRPC() { + _, err := client.registry.GarbageCollect(context.Background(), &mcrv1.GarbageCollectRequest{}) + return err + } + + _, err := client.restDo("POST", "/v1/gc", nil) + return err +} + +// formatRepoName truncates long repo names for display. +func formatRepoName(name string, maxLen int) string { + if len(name) <= maxLen { + return name + } + parts := strings.Split(name, "/") + if len(parts) > 1 { + return "..." + parts[len(parts)-1] + } + return name[:maxLen-3] + "..." +} diff --git a/gen/mcr/v1/registry.pb.go b/gen/mcr/v1/registry.pb.go index dc82382..f1479d8 100644 --- a/gen/mcr/v1/registry.pb.go +++ b/gen/mcr/v1/registry.pb.go @@ -1,15 +1,65 @@ // Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.36.11 +// protoc v6.32.1 // source: mcr/v1/registry.proto package mcrv1 -// RepositoryMetadata is a repository summary for listing. +import ( + protoreflect "google.golang.org/protobuf/reflect/protoreflect" + protoimpl "google.golang.org/protobuf/runtime/protoimpl" + reflect "reflect" + sync "sync" + unsafe "unsafe" +) + +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 RepositoryMetadata struct { - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - TagCount int32 `protobuf:"varint,2,opt,name=tag_count,json=tagCount,proto3" json:"tag_count,omitempty"` - ManifestCount int32 `protobuf:"varint,3,opt,name=manifest_count,json=manifestCount,proto3" json:"manifest_count,omitempty"` - TotalSize int64 `protobuf:"varint,4,opt,name=total_size,json=totalSize,proto3" json:"total_size,omitempty"` - CreatedAt string `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + TagCount int32 `protobuf:"varint,2,opt,name=tag_count,json=tagCount,proto3" json:"tag_count,omitempty"` + ManifestCount int32 `protobuf:"varint,3,opt,name=manifest_count,json=manifestCount,proto3" json:"manifest_count,omitempty"` + TotalSize int64 `protobuf:"varint,4,opt,name=total_size,json=totalSize,proto3" json:"total_size,omitempty"` + CreatedAt string `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *RepositoryMetadata) Reset() { + *x = RepositoryMetadata{} + mi := &file_mcr_v1_registry_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *RepositoryMetadata) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*RepositoryMetadata) ProtoMessage() {} + +func (x *RepositoryMetadata) ProtoReflect() protoreflect.Message { + mi := &file_mcr_v1_registry_proto_msgTypes[0] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use RepositoryMetadata.ProtoReflect.Descriptor instead. +func (*RepositoryMetadata) Descriptor() ([]byte, []int) { + return file_mcr_v1_registry_proto_rawDescGZIP(), []int{0} } func (x *RepositoryMetadata) GetName() string { @@ -47,10 +97,43 @@ func (x *RepositoryMetadata) GetCreatedAt() string { return "" } -// TagInfo is a tag with its manifest digest. type TagInfo struct { - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Digest string `protobuf:"bytes,2,opt,name=digest,proto3" json:"digest,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Digest string `protobuf:"bytes,2,opt,name=digest,proto3" json:"digest,omitempty"` + UpdatedAt string `protobuf:"bytes,3,opt,name=updated_at,json=updatedAt,proto3" json:"updated_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *TagInfo) Reset() { + *x = TagInfo{} + mi := &file_mcr_v1_registry_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *TagInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*TagInfo) ProtoMessage() {} + +func (x *TagInfo) ProtoReflect() protoreflect.Message { + mi := &file_mcr_v1_registry_proto_msgTypes[1] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use TagInfo.ProtoReflect.Descriptor instead. +func (*TagInfo) Descriptor() ([]byte, []int) { + return file_mcr_v1_registry_proto_rawDescGZIP(), []int{1} } func (x *TagInfo) GetName() string { @@ -67,12 +150,51 @@ func (x *TagInfo) GetDigest() string { return "" } -// ManifestInfo is a manifest summary. +func (x *TagInfo) GetUpdatedAt() string { + if x != nil { + return x.UpdatedAt + } + return "" +} + type ManifestInfo struct { - Digest string `protobuf:"bytes,1,opt,name=digest,proto3" json:"digest,omitempty"` - MediaType string `protobuf:"bytes,2,opt,name=media_type,json=mediaType,proto3" json:"media_type,omitempty"` - Size int64 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"` - CreatedAt string `protobuf:"bytes,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Digest string `protobuf:"bytes,1,opt,name=digest,proto3" json:"digest,omitempty"` + MediaType string `protobuf:"bytes,2,opt,name=media_type,json=mediaType,proto3" json:"media_type,omitempty"` + Size int64 `protobuf:"varint,3,opt,name=size,proto3" json:"size,omitempty"` + CreatedAt string `protobuf:"bytes,4,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ManifestInfo) Reset() { + *x = ManifestInfo{} + mi := &file_mcr_v1_registry_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ManifestInfo) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ManifestInfo) ProtoMessage() {} + +func (x *ManifestInfo) ProtoReflect() protoreflect.Message { + mi := &file_mcr_v1_registry_proto_msgTypes[2] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ManifestInfo.ProtoReflect.Descriptor instead. +func (*ManifestInfo) Descriptor() ([]byte, []int) { + return file_mcr_v1_registry_proto_rawDescGZIP(), []int{2} } func (x *ManifestInfo) GetDigest() string { @@ -103,9 +225,41 @@ func (x *ManifestInfo) GetCreatedAt() string { return "" } -// ListRepositoriesRequest is the request message for ListRepositories. type ListRepositoriesRequest struct { - Pagination *PaginationRequest `protobuf:"bytes,1,opt,name=pagination,proto3" json:"pagination,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Pagination *PaginationRequest `protobuf:"bytes,1,opt,name=pagination,proto3" json:"pagination,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListRepositoriesRequest) Reset() { + *x = ListRepositoriesRequest{} + mi := &file_mcr_v1_registry_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRepositoriesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRepositoriesRequest) ProtoMessage() {} + +func (x *ListRepositoriesRequest) ProtoReflect() protoreflect.Message { + mi := &file_mcr_v1_registry_proto_msgTypes[3] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRepositoriesRequest.ProtoReflect.Descriptor instead. +func (*ListRepositoriesRequest) Descriptor() ([]byte, []int) { + return file_mcr_v1_registry_proto_rawDescGZIP(), []int{3} } func (x *ListRepositoriesRequest) GetPagination() *PaginationRequest { @@ -115,9 +269,41 @@ func (x *ListRepositoriesRequest) GetPagination() *PaginationRequest { return nil } -// ListRepositoriesResponse is the response message for ListRepositories. type ListRepositoriesResponse struct { - Repositories []*RepositoryMetadata `protobuf:"bytes,1,rep,name=repositories,proto3" json:"repositories,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Repositories []*RepositoryMetadata `protobuf:"bytes,1,rep,name=repositories,proto3" json:"repositories,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *ListRepositoriesResponse) Reset() { + *x = ListRepositoriesResponse{} + mi := &file_mcr_v1_registry_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *ListRepositoriesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListRepositoriesResponse) ProtoMessage() {} + +func (x *ListRepositoriesResponse) ProtoReflect() protoreflect.Message { + mi := &file_mcr_v1_registry_proto_msgTypes[4] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use ListRepositoriesResponse.ProtoReflect.Descriptor instead. +func (*ListRepositoriesResponse) Descriptor() ([]byte, []int) { + return file_mcr_v1_registry_proto_rawDescGZIP(), []int{4} } func (x *ListRepositoriesResponse) GetRepositories() []*RepositoryMetadata { @@ -127,9 +313,41 @@ func (x *ListRepositoriesResponse) GetRepositories() []*RepositoryMetadata { return nil } -// GetRepositoryRequest is the request message for GetRepository. type GetRepositoryRequest struct { - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetRepositoryRequest) Reset() { + *x = GetRepositoryRequest{} + mi := &file_mcr_v1_registry_proto_msgTypes[5] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRepositoryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRepositoryRequest) ProtoMessage() {} + +func (x *GetRepositoryRequest) ProtoReflect() protoreflect.Message { + mi := &file_mcr_v1_registry_proto_msgTypes[5] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRepositoryRequest.ProtoReflect.Descriptor instead. +func (*GetRepositoryRequest) Descriptor() ([]byte, []int) { + return file_mcr_v1_registry_proto_rawDescGZIP(), []int{5} } func (x *GetRepositoryRequest) GetName() string { @@ -139,13 +357,45 @@ func (x *GetRepositoryRequest) GetName() string { return "" } -// GetRepositoryResponse is the response message for GetRepository. type GetRepositoryResponse struct { - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` - Tags []*TagInfo `protobuf:"bytes,2,rep,name=tags,proto3" json:"tags,omitempty"` - Manifests []*ManifestInfo `protobuf:"bytes,3,rep,name=manifests,proto3" json:"manifests,omitempty"` - TotalSize int64 `protobuf:"varint,4,opt,name=total_size,json=totalSize,proto3" json:"total_size,omitempty"` - CreatedAt string `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + Tags []*TagInfo `protobuf:"bytes,2,rep,name=tags,proto3" json:"tags,omitempty"` + Manifests []*ManifestInfo `protobuf:"bytes,3,rep,name=manifests,proto3" json:"manifests,omitempty"` + TotalSize int64 `protobuf:"varint,4,opt,name=total_size,json=totalSize,proto3" json:"total_size,omitempty"` + CreatedAt string `protobuf:"bytes,5,opt,name=created_at,json=createdAt,proto3" json:"created_at,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetRepositoryResponse) Reset() { + *x = GetRepositoryResponse{} + mi := &file_mcr_v1_registry_proto_msgTypes[6] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetRepositoryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetRepositoryResponse) ProtoMessage() {} + +func (x *GetRepositoryResponse) ProtoReflect() protoreflect.Message { + mi := &file_mcr_v1_registry_proto_msgTypes[6] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetRepositoryResponse.ProtoReflect.Descriptor instead. +func (*GetRepositoryResponse) Descriptor() ([]byte, []int) { + return file_mcr_v1_registry_proto_rawDescGZIP(), []int{6} } func (x *GetRepositoryResponse) GetName() string { @@ -183,9 +433,41 @@ func (x *GetRepositoryResponse) GetCreatedAt() string { return "" } -// DeleteRepositoryRequest is the request message for DeleteRepository. type DeleteRepositoryRequest struct { - Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *DeleteRepositoryRequest) Reset() { + *x = DeleteRepositoryRequest{} + mi := &file_mcr_v1_registry_proto_msgTypes[7] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteRepositoryRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteRepositoryRequest) ProtoMessage() {} + +func (x *DeleteRepositoryRequest) ProtoReflect() protoreflect.Message { + mi := &file_mcr_v1_registry_proto_msgTypes[7] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteRepositoryRequest.ProtoReflect.Descriptor instead. +func (*DeleteRepositoryRequest) Descriptor() ([]byte, []int) { + return file_mcr_v1_registry_proto_rawDescGZIP(), []int{7} } func (x *DeleteRepositoryRequest) GetName() string { @@ -195,15 +477,113 @@ func (x *DeleteRepositoryRequest) GetName() string { return "" } -// DeleteRepositoryResponse is the response message for DeleteRepository. -type DeleteRepositoryResponse struct{} +type DeleteRepositoryResponse struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} -// GarbageCollectRequest is the request message for GarbageCollect. -type GarbageCollectRequest struct{} +func (x *DeleteRepositoryResponse) Reset() { + *x = DeleteRepositoryResponse{} + mi := &file_mcr_v1_registry_proto_msgTypes[8] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *DeleteRepositoryResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteRepositoryResponse) ProtoMessage() {} + +func (x *DeleteRepositoryResponse) ProtoReflect() protoreflect.Message { + mi := &file_mcr_v1_registry_proto_msgTypes[8] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use DeleteRepositoryResponse.ProtoReflect.Descriptor instead. +func (*DeleteRepositoryResponse) Descriptor() ([]byte, []int) { + return file_mcr_v1_registry_proto_rawDescGZIP(), []int{8} +} + +type GarbageCollectRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GarbageCollectRequest) Reset() { + *x = GarbageCollectRequest{} + mi := &file_mcr_v1_registry_proto_msgTypes[9] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GarbageCollectRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GarbageCollectRequest) ProtoMessage() {} + +func (x *GarbageCollectRequest) ProtoReflect() protoreflect.Message { + mi := &file_mcr_v1_registry_proto_msgTypes[9] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GarbageCollectRequest.ProtoReflect.Descriptor instead. +func (*GarbageCollectRequest) Descriptor() ([]byte, []int) { + return file_mcr_v1_registry_proto_rawDescGZIP(), []int{9} +} -// GarbageCollectResponse is the response message for GarbageCollect. type GarbageCollectResponse struct { - Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` //nolint:revive,stylecheck // proto field name + state protoimpl.MessageState `protogen:"open.v1"` + Id string `protobuf:"bytes,1,opt,name=id,proto3" json:"id,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GarbageCollectResponse) Reset() { + *x = GarbageCollectResponse{} + mi := &file_mcr_v1_registry_proto_msgTypes[10] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GarbageCollectResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GarbageCollectResponse) ProtoMessage() {} + +func (x *GarbageCollectResponse) ProtoReflect() protoreflect.Message { + mi := &file_mcr_v1_registry_proto_msgTypes[10] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GarbageCollectResponse.ProtoReflect.Descriptor instead. +func (*GarbageCollectResponse) Descriptor() ([]byte, []int) { + return file_mcr_v1_registry_proto_rawDescGZIP(), []int{10} } func (x *GarbageCollectResponse) GetId() string { @@ -213,15 +593,80 @@ func (x *GarbageCollectResponse) GetId() string { return "" } -// GetGCStatusRequest is the request message for GetGCStatus. -type GetGCStatusRequest struct{} +type GetGCStatusRequest struct { + state protoimpl.MessageState `protogen:"open.v1"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetGCStatusRequest) Reset() { + *x = GetGCStatusRequest{} + mi := &file_mcr_v1_registry_proto_msgTypes[11] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetGCStatusRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetGCStatusRequest) ProtoMessage() {} + +func (x *GetGCStatusRequest) ProtoReflect() protoreflect.Message { + mi := &file_mcr_v1_registry_proto_msgTypes[11] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetGCStatusRequest.ProtoReflect.Descriptor instead. +func (*GetGCStatusRequest) Descriptor() ([]byte, []int) { + return file_mcr_v1_registry_proto_rawDescGZIP(), []int{11} +} -// GCLastRun records the result of the last garbage collection run. type GCLastRun struct { - StartedAt string `protobuf:"bytes,1,opt,name=started_at,json=startedAt,proto3" json:"started_at,omitempty"` - CompletedAt string `protobuf:"bytes,2,opt,name=completed_at,json=completedAt,proto3" json:"completed_at,omitempty"` - BlobsRemoved int32 `protobuf:"varint,3,opt,name=blobs_removed,json=blobsRemoved,proto3" json:"blobs_removed,omitempty"` - BytesFreed int64 `protobuf:"varint,4,opt,name=bytes_freed,json=bytesFreed,proto3" json:"bytes_freed,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + StartedAt string `protobuf:"bytes,1,opt,name=started_at,json=startedAt,proto3" json:"started_at,omitempty"` + CompletedAt string `protobuf:"bytes,2,opt,name=completed_at,json=completedAt,proto3" json:"completed_at,omitempty"` + BlobsRemoved int32 `protobuf:"varint,3,opt,name=blobs_removed,json=blobsRemoved,proto3" json:"blobs_removed,omitempty"` + BytesFreed int64 `protobuf:"varint,4,opt,name=bytes_freed,json=bytesFreed,proto3" json:"bytes_freed,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GCLastRun) Reset() { + *x = GCLastRun{} + mi := &file_mcr_v1_registry_proto_msgTypes[12] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GCLastRun) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GCLastRun) ProtoMessage() {} + +func (x *GCLastRun) ProtoReflect() protoreflect.Message { + mi := &file_mcr_v1_registry_proto_msgTypes[12] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GCLastRun.ProtoReflect.Descriptor instead. +func (*GCLastRun) Descriptor() ([]byte, []int) { + return file_mcr_v1_registry_proto_rawDescGZIP(), []int{12} } func (x *GCLastRun) GetStartedAt() string { @@ -252,10 +697,42 @@ func (x *GCLastRun) GetBytesFreed() int64 { return 0 } -// GetGCStatusResponse is the response message for GetGCStatus. type GetGCStatusResponse struct { - Running bool `protobuf:"varint,1,opt,name=running,proto3" json:"running,omitempty"` - LastRun *GCLastRun `protobuf:"bytes,2,opt,name=last_run,json=lastRun,proto3" json:"last_run,omitempty"` + state protoimpl.MessageState `protogen:"open.v1"` + Running bool `protobuf:"varint,1,opt,name=running,proto3" json:"running,omitempty"` + LastRun *GCLastRun `protobuf:"bytes,2,opt,name=last_run,json=lastRun,proto3" json:"last_run,omitempty"` + unknownFields protoimpl.UnknownFields + sizeCache protoimpl.SizeCache +} + +func (x *GetGCStatusResponse) Reset() { + *x = GetGCStatusResponse{} + mi := &file_mcr_v1_registry_proto_msgTypes[13] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) +} + +func (x *GetGCStatusResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*GetGCStatusResponse) ProtoMessage() {} + +func (x *GetGCStatusResponse) ProtoReflect() protoreflect.Message { + mi := &file_mcr_v1_registry_proto_msgTypes[13] + if x != nil { + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + if ms.LoadMessageInfo() == nil { + ms.StoreMessageInfo(mi) + } + return ms + } + return mi.MessageOf(x) +} + +// Deprecated: Use GetGCStatusResponse.ProtoReflect.Descriptor instead. +func (*GetGCStatusResponse) Descriptor() ([]byte, []int) { + return file_mcr_v1_registry_proto_rawDescGZIP(), []int{13} } func (x *GetGCStatusResponse) GetRunning() bool { @@ -271,3 +748,146 @@ func (x *GetGCStatusResponse) GetLastRun() *GCLastRun { } return nil } + +var File_mcr_v1_registry_proto protoreflect.FileDescriptor + +const file_mcr_v1_registry_proto_rawDesc = "" + + "\n" + + "\x15mcr/v1/registry.proto\x12\x06mcr.v1\x1a\x13mcr/v1/common.proto\"\xaa\x01\n" + + "\x12RepositoryMetadata\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x1b\n" + + "\ttag_count\x18\x02 \x01(\x05R\btagCount\x12%\n" + + "\x0emanifest_count\x18\x03 \x01(\x05R\rmanifestCount\x12\x1d\n" + + "\n" + + "total_size\x18\x04 \x01(\x03R\ttotalSize\x12\x1d\n" + + "\n" + + "created_at\x18\x05 \x01(\tR\tcreatedAt\"T\n" + + "\aTagInfo\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12\x16\n" + + "\x06digest\x18\x02 \x01(\tR\x06digest\x12\x1d\n" + + "\n" + + "updated_at\x18\x03 \x01(\tR\tupdatedAt\"x\n" + + "\fManifestInfo\x12\x16\n" + + "\x06digest\x18\x01 \x01(\tR\x06digest\x12\x1d\n" + + "\n" + + "media_type\x18\x02 \x01(\tR\tmediaType\x12\x12\n" + + "\x04size\x18\x03 \x01(\x03R\x04size\x12\x1d\n" + + "\n" + + "created_at\x18\x04 \x01(\tR\tcreatedAt\"T\n" + + "\x17ListRepositoriesRequest\x129\n" + + "\n" + + "pagination\x18\x01 \x01(\v2\x19.mcr.v1.PaginationRequestR\n" + + "pagination\"Z\n" + + "\x18ListRepositoriesResponse\x12>\n" + + "\frepositories\x18\x01 \x03(\v2\x1a.mcr.v1.RepositoryMetadataR\frepositories\"*\n" + + "\x14GetRepositoryRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\"\xc2\x01\n" + + "\x15GetRepositoryResponse\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\x12#\n" + + "\x04tags\x18\x02 \x03(\v2\x0f.mcr.v1.TagInfoR\x04tags\x122\n" + + "\tmanifests\x18\x03 \x03(\v2\x14.mcr.v1.ManifestInfoR\tmanifests\x12\x1d\n" + + "\n" + + "total_size\x18\x04 \x01(\x03R\ttotalSize\x12\x1d\n" + + "\n" + + "created_at\x18\x05 \x01(\tR\tcreatedAt\"-\n" + + "\x17DeleteRepositoryRequest\x12\x12\n" + + "\x04name\x18\x01 \x01(\tR\x04name\"\x1a\n" + + "\x18DeleteRepositoryResponse\"\x17\n" + + "\x15GarbageCollectRequest\"(\n" + + "\x16GarbageCollectResponse\x12\x0e\n" + + "\x02id\x18\x01 \x01(\tR\x02id\"\x14\n" + + "\x12GetGCStatusRequest\"\x93\x01\n" + + "\tGCLastRun\x12\x1d\n" + + "\n" + + "started_at\x18\x01 \x01(\tR\tstartedAt\x12!\n" + + "\fcompleted_at\x18\x02 \x01(\tR\vcompletedAt\x12#\n" + + "\rblobs_removed\x18\x03 \x01(\x05R\fblobsRemoved\x12\x1f\n" + + "\vbytes_freed\x18\x04 \x01(\x03R\n" + + "bytesFreed\"]\n" + + "\x13GetGCStatusResponse\x12\x18\n" + + "\arunning\x18\x01 \x01(\bR\arunning\x12,\n" + + "\blast_run\x18\x02 \x01(\v2\x11.mcr.v1.GCLastRunR\alastRun2\xa6\x03\n" + + "\x0fRegistryService\x12U\n" + + "\x10ListRepositories\x12\x1f.mcr.v1.ListRepositoriesRequest\x1a .mcr.v1.ListRepositoriesResponse\x12L\n" + + "\rGetRepository\x12\x1c.mcr.v1.GetRepositoryRequest\x1a\x1d.mcr.v1.GetRepositoryResponse\x12U\n" + + "\x10DeleteRepository\x12\x1f.mcr.v1.DeleteRepositoryRequest\x1a .mcr.v1.DeleteRepositoryResponse\x12O\n" + + "\x0eGarbageCollect\x12\x1d.mcr.v1.GarbageCollectRequest\x1a\x1e.mcr.v1.GarbageCollectResponse\x12F\n" + + "\vGetGCStatus\x12\x1a.mcr.v1.GetGCStatusRequest\x1a\x1b.mcr.v1.GetGCStatusResponseB,Z*git.wntrmute.dev/kyle/mcr/gen/mcr/v1;mcrv1b\x06proto3" + +var ( + file_mcr_v1_registry_proto_rawDescOnce sync.Once + file_mcr_v1_registry_proto_rawDescData []byte +) + +func file_mcr_v1_registry_proto_rawDescGZIP() []byte { + file_mcr_v1_registry_proto_rawDescOnce.Do(func() { + file_mcr_v1_registry_proto_rawDescData = protoimpl.X.CompressGZIP(unsafe.Slice(unsafe.StringData(file_mcr_v1_registry_proto_rawDesc), len(file_mcr_v1_registry_proto_rawDesc))) + }) + return file_mcr_v1_registry_proto_rawDescData +} + +var file_mcr_v1_registry_proto_msgTypes = make([]protoimpl.MessageInfo, 14) +var file_mcr_v1_registry_proto_goTypes = []any{ + (*RepositoryMetadata)(nil), // 0: mcr.v1.RepositoryMetadata + (*TagInfo)(nil), // 1: mcr.v1.TagInfo + (*ManifestInfo)(nil), // 2: mcr.v1.ManifestInfo + (*ListRepositoriesRequest)(nil), // 3: mcr.v1.ListRepositoriesRequest + (*ListRepositoriesResponse)(nil), // 4: mcr.v1.ListRepositoriesResponse + (*GetRepositoryRequest)(nil), // 5: mcr.v1.GetRepositoryRequest + (*GetRepositoryResponse)(nil), // 6: mcr.v1.GetRepositoryResponse + (*DeleteRepositoryRequest)(nil), // 7: mcr.v1.DeleteRepositoryRequest + (*DeleteRepositoryResponse)(nil), // 8: mcr.v1.DeleteRepositoryResponse + (*GarbageCollectRequest)(nil), // 9: mcr.v1.GarbageCollectRequest + (*GarbageCollectResponse)(nil), // 10: mcr.v1.GarbageCollectResponse + (*GetGCStatusRequest)(nil), // 11: mcr.v1.GetGCStatusRequest + (*GCLastRun)(nil), // 12: mcr.v1.GCLastRun + (*GetGCStatusResponse)(nil), // 13: mcr.v1.GetGCStatusResponse + (*PaginationRequest)(nil), // 14: mcr.v1.PaginationRequest +} +var file_mcr_v1_registry_proto_depIdxs = []int32{ + 14, // 0: mcr.v1.ListRepositoriesRequest.pagination:type_name -> mcr.v1.PaginationRequest + 0, // 1: mcr.v1.ListRepositoriesResponse.repositories:type_name -> mcr.v1.RepositoryMetadata + 1, // 2: mcr.v1.GetRepositoryResponse.tags:type_name -> mcr.v1.TagInfo + 2, // 3: mcr.v1.GetRepositoryResponse.manifests:type_name -> mcr.v1.ManifestInfo + 12, // 4: mcr.v1.GetGCStatusResponse.last_run:type_name -> mcr.v1.GCLastRun + 3, // 5: mcr.v1.RegistryService.ListRepositories:input_type -> mcr.v1.ListRepositoriesRequest + 5, // 6: mcr.v1.RegistryService.GetRepository:input_type -> mcr.v1.GetRepositoryRequest + 7, // 7: mcr.v1.RegistryService.DeleteRepository:input_type -> mcr.v1.DeleteRepositoryRequest + 9, // 8: mcr.v1.RegistryService.GarbageCollect:input_type -> mcr.v1.GarbageCollectRequest + 11, // 9: mcr.v1.RegistryService.GetGCStatus:input_type -> mcr.v1.GetGCStatusRequest + 4, // 10: mcr.v1.RegistryService.ListRepositories:output_type -> mcr.v1.ListRepositoriesResponse + 6, // 11: mcr.v1.RegistryService.GetRepository:output_type -> mcr.v1.GetRepositoryResponse + 8, // 12: mcr.v1.RegistryService.DeleteRepository:output_type -> mcr.v1.DeleteRepositoryResponse + 10, // 13: mcr.v1.RegistryService.GarbageCollect:output_type -> mcr.v1.GarbageCollectResponse + 13, // 14: mcr.v1.RegistryService.GetGCStatus:output_type -> mcr.v1.GetGCStatusResponse + 10, // [10:15] is the sub-list for method output_type + 5, // [5:10] is the sub-list for method input_type + 5, // [5:5] is the sub-list for extension type_name + 5, // [5:5] is the sub-list for extension extendee + 0, // [0:5] is the sub-list for field type_name +} + +func init() { file_mcr_v1_registry_proto_init() } +func file_mcr_v1_registry_proto_init() { + if File_mcr_v1_registry_proto != nil { + return + } + file_mcr_v1_common_proto_init() + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: unsafe.Slice(unsafe.StringData(file_mcr_v1_registry_proto_rawDesc), len(file_mcr_v1_registry_proto_rawDesc)), + NumEnums: 0, + NumMessages: 14, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_mcr_v1_registry_proto_goTypes, + DependencyIndexes: file_mcr_v1_registry_proto_depIdxs, + MessageInfos: file_mcr_v1_registry_proto_msgTypes, + }.Build() + File_mcr_v1_registry_proto = out.File + file_mcr_v1_registry_proto_goTypes = nil + file_mcr_v1_registry_proto_depIdxs = nil +} diff --git a/gen/mcr/v1/registry_grpc.pb.go b/gen/mcr/v1/registry_grpc.pb.go index 67113d7..649ecf7 100644 --- a/gen/mcr/v1/registry_grpc.pb.go +++ b/gen/mcr/v1/registry_grpc.pb.go @@ -1,178 +1,34 @@ // Code generated by protoc-gen-go-grpc. DO NOT EDIT. +// versions: +// - protoc-gen-go-grpc v1.6.1 +// - protoc v6.32.1 // source: mcr/v1/registry.proto package mcrv1 import ( - "context" - - "google.golang.org/grpc" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" + context "context" + grpc "google.golang.org/grpc" + codes "google.golang.org/grpc/codes" + status "google.golang.org/grpc/status" ) -// RegistryServiceServer is the server API for RegistryService. -type RegistryServiceServer interface { - ListRepositories(context.Context, *ListRepositoriesRequest) (*ListRepositoriesResponse, error) - GetRepository(context.Context, *GetRepositoryRequest) (*GetRepositoryResponse, error) - DeleteRepository(context.Context, *DeleteRepositoryRequest) (*DeleteRepositoryResponse, error) - GarbageCollect(context.Context, *GarbageCollectRequest) (*GarbageCollectResponse, error) - GetGCStatus(context.Context, *GetGCStatusRequest) (*GetGCStatusResponse, error) - mustEmbedUnimplementedRegistryServiceServer() -} +// 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.64.0 or later. +const _ = grpc.SupportPackageIsVersion9 -// UnimplementedRegistryServiceServer should be embedded to have forward -// compatible implementations. -type UnimplementedRegistryServiceServer struct{} +const ( + RegistryService_ListRepositories_FullMethodName = "/mcr.v1.RegistryService/ListRepositories" + RegistryService_GetRepository_FullMethodName = "/mcr.v1.RegistryService/GetRepository" + RegistryService_DeleteRepository_FullMethodName = "/mcr.v1.RegistryService/DeleteRepository" + RegistryService_GarbageCollect_FullMethodName = "/mcr.v1.RegistryService/GarbageCollect" + RegistryService_GetGCStatus_FullMethodName = "/mcr.v1.RegistryService/GetGCStatus" +) -func (UnimplementedRegistryServiceServer) ListRepositories(context.Context, *ListRepositoriesRequest) (*ListRepositoriesResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method ListRepositories not implemented") -} - -func (UnimplementedRegistryServiceServer) GetRepository(context.Context, *GetRepositoryRequest) (*GetRepositoryResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetRepository not implemented") -} - -func (UnimplementedRegistryServiceServer) DeleteRepository(context.Context, *DeleteRepositoryRequest) (*DeleteRepositoryResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method DeleteRepository not implemented") -} - -func (UnimplementedRegistryServiceServer) GarbageCollect(context.Context, *GarbageCollectRequest) (*GarbageCollectResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GarbageCollect not implemented") -} - -func (UnimplementedRegistryServiceServer) GetGCStatus(context.Context, *GetGCStatusRequest) (*GetGCStatusResponse, error) { - return nil, status.Errorf(codes.Unimplemented, "method GetGCStatus not implemented") -} - -func (UnimplementedRegistryServiceServer) mustEmbedUnimplementedRegistryServiceServer() {} - -// RegisterRegistryServiceServer registers the RegistryServiceServer with the grpc.Server. -func RegisterRegistryServiceServer(s grpc.ServiceRegistrar, srv RegistryServiceServer) { - s.RegisterService(&RegistryService_ServiceDesc, srv) -} - -func registryServiceListRepositoriesHandler(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { - in := new(ListRepositoriesRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(RegistryServiceServer).ListRepositories(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/mcr.v1.RegistryService/ListRepositories", - } - handler := func(ctx context.Context, req any) (any, error) { - return srv.(RegistryServiceServer).ListRepositories(ctx, req.(*ListRepositoriesRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func registryServiceGetRepositoryHandler(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { - in := new(GetRepositoryRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(RegistryServiceServer).GetRepository(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/mcr.v1.RegistryService/GetRepository", - } - handler := func(ctx context.Context, req any) (any, error) { - return srv.(RegistryServiceServer).GetRepository(ctx, req.(*GetRepositoryRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func registryServiceDeleteRepositoryHandler(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { - in := new(DeleteRepositoryRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(RegistryServiceServer).DeleteRepository(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/mcr.v1.RegistryService/DeleteRepository", - } - handler := func(ctx context.Context, req any) (any, error) { - return srv.(RegistryServiceServer).DeleteRepository(ctx, req.(*DeleteRepositoryRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func registryServiceGarbageCollectHandler(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { - in := new(GarbageCollectRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(RegistryServiceServer).GarbageCollect(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/mcr.v1.RegistryService/GarbageCollect", - } - handler := func(ctx context.Context, req any) (any, error) { - return srv.(RegistryServiceServer).GarbageCollect(ctx, req.(*GarbageCollectRequest)) - } - return interceptor(ctx, in, info, handler) -} - -func registryServiceGetGCStatusHandler(srv any, ctx context.Context, dec func(any) error, interceptor grpc.UnaryServerInterceptor) (any, error) { - in := new(GetGCStatusRequest) - if err := dec(in); err != nil { - return nil, err - } - if interceptor == nil { - return srv.(RegistryServiceServer).GetGCStatus(ctx, in) - } - info := &grpc.UnaryServerInfo{ - Server: srv, - FullMethod: "/mcr.v1.RegistryService/GetGCStatus", - } - handler := func(ctx context.Context, req any) (any, error) { - return srv.(RegistryServiceServer).GetGCStatus(ctx, req.(*GetGCStatusRequest)) - } - return interceptor(ctx, in, info, handler) -} - -// RegistryService_ServiceDesc is the grpc.ServiceDesc for RegistryService. -var RegistryService_ServiceDesc = grpc.ServiceDesc{ - ServiceName: "mcr.v1.RegistryService", - HandlerType: (*RegistryServiceServer)(nil), - Methods: []grpc.MethodDesc{ - { - MethodName: "ListRepositories", - Handler: registryServiceListRepositoriesHandler, - }, - { - MethodName: "GetRepository", - Handler: registryServiceGetRepositoryHandler, - }, - { - MethodName: "DeleteRepository", - Handler: registryServiceDeleteRepositoryHandler, - }, - { - MethodName: "GarbageCollect", - Handler: registryServiceGarbageCollectHandler, - }, - { - MethodName: "GetGCStatus", - Handler: registryServiceGetGCStatusHandler, - }, - }, - Streams: []grpc.StreamDesc{}, - Metadata: "mcr/v1/registry.proto", -} - -// RegistryServiceClient is the client API for RegistryService. +// RegistryServiceClient is the client API for RegistryService 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 RegistryServiceClient interface { ListRepositories(ctx context.Context, in *ListRepositoriesRequest, opts ...grpc.CallOption) (*ListRepositoriesResponse, error) GetRepository(ctx context.Context, in *GetRepositoryRequest, opts ...grpc.CallOption) (*GetRepositoryResponse, error) @@ -185,14 +41,14 @@ type registryServiceClient struct { cc grpc.ClientConnInterface } -// NewRegistryServiceClient creates a new RegistryServiceClient. func NewRegistryServiceClient(cc grpc.ClientConnInterface) RegistryServiceClient { return ®istryServiceClient{cc} } func (c *registryServiceClient) ListRepositories(ctx context.Context, in *ListRepositoriesRequest, opts ...grpc.CallOption) (*ListRepositoriesResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(ListRepositoriesResponse) - err := c.cc.Invoke(ctx, "/mcr.v1.RegistryService/ListRepositories", in, out, opts...) + err := c.cc.Invoke(ctx, RegistryService_ListRepositories_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -200,8 +56,9 @@ func (c *registryServiceClient) ListRepositories(ctx context.Context, in *ListRe } func (c *registryServiceClient) GetRepository(ctx context.Context, in *GetRepositoryRequest, opts ...grpc.CallOption) (*GetRepositoryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetRepositoryResponse) - err := c.cc.Invoke(ctx, "/mcr.v1.RegistryService/GetRepository", in, out, opts...) + err := c.cc.Invoke(ctx, RegistryService_GetRepository_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -209,8 +66,9 @@ func (c *registryServiceClient) GetRepository(ctx context.Context, in *GetReposi } func (c *registryServiceClient) DeleteRepository(ctx context.Context, in *DeleteRepositoryRequest, opts ...grpc.CallOption) (*DeleteRepositoryResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(DeleteRepositoryResponse) - err := c.cc.Invoke(ctx, "/mcr.v1.RegistryService/DeleteRepository", in, out, opts...) + err := c.cc.Invoke(ctx, RegistryService_DeleteRepository_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -218,8 +76,9 @@ func (c *registryServiceClient) DeleteRepository(ctx context.Context, in *Delete } func (c *registryServiceClient) GarbageCollect(ctx context.Context, in *GarbageCollectRequest, opts ...grpc.CallOption) (*GarbageCollectResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GarbageCollectResponse) - err := c.cc.Invoke(ctx, "/mcr.v1.RegistryService/GarbageCollect", in, out, opts...) + err := c.cc.Invoke(ctx, RegistryService_GarbageCollect_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } @@ -227,10 +86,188 @@ func (c *registryServiceClient) GarbageCollect(ctx context.Context, in *GarbageC } func (c *registryServiceClient) GetGCStatus(ctx context.Context, in *GetGCStatusRequest, opts ...grpc.CallOption) (*GetGCStatusResponse, error) { + cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...) out := new(GetGCStatusResponse) - err := c.cc.Invoke(ctx, "/mcr.v1.RegistryService/GetGCStatus", in, out, opts...) + err := c.cc.Invoke(ctx, RegistryService_GetGCStatus_FullMethodName, in, out, cOpts...) if err != nil { return nil, err } return out, nil } + +// RegistryServiceServer is the server API for RegistryService service. +// All implementations must embed UnimplementedRegistryServiceServer +// for forward compatibility. +type RegistryServiceServer interface { + ListRepositories(context.Context, *ListRepositoriesRequest) (*ListRepositoriesResponse, error) + GetRepository(context.Context, *GetRepositoryRequest) (*GetRepositoryResponse, error) + DeleteRepository(context.Context, *DeleteRepositoryRequest) (*DeleteRepositoryResponse, error) + GarbageCollect(context.Context, *GarbageCollectRequest) (*GarbageCollectResponse, error) + GetGCStatus(context.Context, *GetGCStatusRequest) (*GetGCStatusResponse, error) + mustEmbedUnimplementedRegistryServiceServer() +} + +// UnimplementedRegistryServiceServer must be embedded to have +// forward compatible implementations. +// +// NOTE: this should be embedded by value instead of pointer to avoid a nil +// pointer dereference when methods are called. +type UnimplementedRegistryServiceServer struct{} + +func (UnimplementedRegistryServiceServer) ListRepositories(context.Context, *ListRepositoriesRequest) (*ListRepositoriesResponse, error) { + return nil, status.Error(codes.Unimplemented, "method ListRepositories not implemented") +} +func (UnimplementedRegistryServiceServer) GetRepository(context.Context, *GetRepositoryRequest) (*GetRepositoryResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetRepository not implemented") +} +func (UnimplementedRegistryServiceServer) DeleteRepository(context.Context, *DeleteRepositoryRequest) (*DeleteRepositoryResponse, error) { + return nil, status.Error(codes.Unimplemented, "method DeleteRepository not implemented") +} +func (UnimplementedRegistryServiceServer) GarbageCollect(context.Context, *GarbageCollectRequest) (*GarbageCollectResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GarbageCollect not implemented") +} +func (UnimplementedRegistryServiceServer) GetGCStatus(context.Context, *GetGCStatusRequest) (*GetGCStatusResponse, error) { + return nil, status.Error(codes.Unimplemented, "method GetGCStatus not implemented") +} +func (UnimplementedRegistryServiceServer) mustEmbedUnimplementedRegistryServiceServer() {} +func (UnimplementedRegistryServiceServer) testEmbeddedByValue() {} + +// UnsafeRegistryServiceServer may be embedded to opt out of forward compatibility for this service. +// Use of this interface is not recommended, as added methods to RegistryServiceServer will +// result in compilation errors. +type UnsafeRegistryServiceServer interface { + mustEmbedUnimplementedRegistryServiceServer() +} + +func RegisterRegistryServiceServer(s grpc.ServiceRegistrar, srv RegistryServiceServer) { + // If the following call panics, it indicates UnimplementedRegistryServiceServer was + // embedded by pointer and is nil. This will cause panics if an + // unimplemented method is ever invoked, so we test this at initialization + // time to prevent it from happening at runtime later due to I/O. + if t, ok := srv.(interface{ testEmbeddedByValue() }); ok { + t.testEmbeddedByValue() + } + s.RegisterService(&RegistryService_ServiceDesc, srv) +} + +func _RegistryService_ListRepositories_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(ListRepositoriesRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RegistryServiceServer).ListRepositories(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RegistryService_ListRepositories_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RegistryServiceServer).ListRepositories(ctx, req.(*ListRepositoriesRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _RegistryService_GetRepository_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetRepositoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RegistryServiceServer).GetRepository(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RegistryService_GetRepository_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RegistryServiceServer).GetRepository(ctx, req.(*GetRepositoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _RegistryService_DeleteRepository_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(DeleteRepositoryRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RegistryServiceServer).DeleteRepository(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RegistryService_DeleteRepository_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RegistryServiceServer).DeleteRepository(ctx, req.(*DeleteRepositoryRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _RegistryService_GarbageCollect_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GarbageCollectRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RegistryServiceServer).GarbageCollect(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RegistryService_GarbageCollect_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RegistryServiceServer).GarbageCollect(ctx, req.(*GarbageCollectRequest)) + } + return interceptor(ctx, in, info, handler) +} + +func _RegistryService_GetGCStatus_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) { + in := new(GetGCStatusRequest) + if err := dec(in); err != nil { + return nil, err + } + if interceptor == nil { + return srv.(RegistryServiceServer).GetGCStatus(ctx, in) + } + info := &grpc.UnaryServerInfo{ + Server: srv, + FullMethod: RegistryService_GetGCStatus_FullMethodName, + } + handler := func(ctx context.Context, req interface{}) (interface{}, error) { + return srv.(RegistryServiceServer).GetGCStatus(ctx, req.(*GetGCStatusRequest)) + } + return interceptor(ctx, in, info, handler) +} + +// RegistryService_ServiceDesc is the grpc.ServiceDesc for RegistryService service. +// It's only intended for direct use with grpc.RegisterService, +// and not to be introspected or modified (even as a copy) +var RegistryService_ServiceDesc = grpc.ServiceDesc{ + ServiceName: "mcr.v1.RegistryService", + HandlerType: (*RegistryServiceServer)(nil), + Methods: []grpc.MethodDesc{ + { + MethodName: "ListRepositories", + Handler: _RegistryService_ListRepositories_Handler, + }, + { + MethodName: "GetRepository", + Handler: _RegistryService_GetRepository_Handler, + }, + { + MethodName: "DeleteRepository", + Handler: _RegistryService_DeleteRepository_Handler, + }, + { + MethodName: "GarbageCollect", + Handler: _RegistryService_GarbageCollect_Handler, + }, + { + MethodName: "GetGCStatus", + Handler: _RegistryService_GetGCStatus_Handler, + }, + }, + Streams: []grpc.StreamDesc{}, + Metadata: "mcr/v1/registry.proto", +} diff --git a/internal/db/admin.go b/internal/db/admin.go index f24ec9e..29d6d4b 100644 --- a/internal/db/admin.go +++ b/internal/db/admin.go @@ -23,8 +23,9 @@ type RepoMetadata struct { // TagInfo is a tag with its manifest digest for repo detail. type TagInfo struct { - Name string `json:"name"` - Digest string `json:"digest"` + Name string `json:"name"` + Digest string `json:"digest"` + UpdatedAt string `json:"updated_at"` } // ManifestInfo is a manifest summary for repo detail. @@ -112,7 +113,7 @@ func (d *DB) GetRepositoryDetail(name string) (*RepoDetail, error) { // Tags with manifest digests. tagRows, err := d.Query( - `SELECT t.name, m.digest + `SELECT t.name, m.digest, t.updated_at FROM tags t JOIN manifests m ON m.id = t.manifest_id WHERE t.repository_id = ? ORDER BY t.name ASC`, repoID, @@ -124,7 +125,7 @@ func (d *DB) GetRepositoryDetail(name string) (*RepoDetail, error) { for tagRows.Next() { var ti TagInfo - if err := tagRows.Scan(&ti.Name, &ti.Digest); err != nil { + if err := tagRows.Scan(&ti.Name, &ti.Digest, &ti.UpdatedAt); err != nil { return nil, fmt.Errorf("db: scan tag: %w", err) } detail.Tags = append(detail.Tags, ti) diff --git a/internal/grpcserver/registry.go b/internal/grpcserver/registry.go index 408544a..316dc58 100644 --- a/internal/grpcserver/registry.go +++ b/internal/grpcserver/registry.go @@ -78,8 +78,9 @@ func (s *registryService) GetRepository(_ context.Context, req *pb.GetRepository } for _, t := range detail.Tags { resp.Tags = append(resp.Tags, &pb.TagInfo{ - Name: t.Name, - Digest: t.Digest, + Name: t.Name, + Digest: t.Digest, + UpdatedAt: t.UpdatedAt, }) } for _, m := range detail.Manifests { diff --git a/proto/mcr/v1/registry.proto b/proto/mcr/v1/registry.proto index 7a4fcb6..ee8d95f 100644 --- a/proto/mcr/v1/registry.proto +++ b/proto/mcr/v1/registry.proto @@ -25,6 +25,7 @@ message RepositoryMetadata { message TagInfo { string name = 1; string digest = 2; + string updated_at = 3; } message ManifestInfo {