diff --git a/pkg/api/rpc.go b/pkg/api/rpc.go index 4edd8a69..07d911c9 100644 --- a/pkg/api/rpc.go +++ b/pkg/api/rpc.go @@ -10,6 +10,7 @@ import ( "buf.build/gen/go/depot/api/connectrpc/go/depot/core/v1/corev1connect" "connectrpc.com/connect" "github.com/depot/cli/pkg/proto/depot/agent/v1/agentv1connect" + "github.com/depot/cli/pkg/proto/depot/build/v1/buildv1connect" "github.com/depot/cli/pkg/proto/depot/cli/v1/cliv1connect" "github.com/depot/cli/pkg/proto/depot/cli/v1beta1/cliv1beta1connect" cliCorev1connect "github.com/depot/cli/pkg/proto/depot/core/v1/corev1connect" @@ -52,6 +53,10 @@ func NewSandboxClient() agentv1connect.SandboxServiceClient { return agentv1connect.NewSandboxServiceClient(getHTTPClient(getBaseURL()), getBaseURL(), WithUserAgent()) } +func NewRegistryClient() buildv1connect.RegistryServiceClient { + return buildv1connect.NewRegistryServiceClient(getHTTPClient(getBaseURL()), getBaseURL(), WithUserAgent()) +} + func WithAuthentication[T any](req *connect.Request[T], token string) *connect.Request[T] { req.Header().Add("Authorization", "Bearer "+token) return req diff --git a/pkg/cmd/image/image.go b/pkg/cmd/image/image.go new file mode 100644 index 00000000..42cca0fb --- /dev/null +++ b/pkg/cmd/image/image.go @@ -0,0 +1,22 @@ +package image + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +func NewCmdImage() *cobra.Command { + cmd := &cobra.Command{ + Use: "image", + Short: "Manage container images in the registry", + RunE: func(cmd *cobra.Command, args []string) error { + return fmt.Errorf("missing subcommand, please run `depot image --help`") + }, + } + + cmd.AddCommand(NewCmdList()) + cmd.AddCommand(NewCmdRM()) + + return cmd +} diff --git a/pkg/cmd/image/list.go b/pkg/cmd/image/list.go new file mode 100644 index 00000000..b1c4a59f --- /dev/null +++ b/pkg/cmd/image/list.go @@ -0,0 +1,354 @@ +package image + +import ( + "context" + "encoding/csv" + "encoding/json" + "fmt" + "os" + "sort" + "time" + + "connectrpc.com/connect" + "github.com/charmbracelet/bubbles/table" + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/lipgloss" + "github.com/depot/cli/pkg/api" + "github.com/depot/cli/pkg/helpers" + v1 "github.com/depot/cli/pkg/proto/depot/build/v1" + "github.com/depot/cli/pkg/proto/depot/build/v1/buildv1connect" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +func NewCmdList() *cobra.Command { + var projectID string + var token string + var outputFormat string + + cmd := &cobra.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List images in the registry", + RunE: func(cmd *cobra.Command, args []string) error { + cwd, _ := os.Getwd() + resolvedProjectID := helpers.ResolveProjectID(projectID, cwd) + if resolvedProjectID == "" { + return errors.Errorf("unknown project ID (run `depot init` or use --project or $DEPOT_PROJECT_ID)") + } + + token, err := helpers.ResolveProjectAuth(context.Background(), token) + if err != nil { + return err + } + + if token == "" { + return fmt.Errorf("missing API token, please run `depot login`") + } + + client := api.NewRegistryClient() + + // Auto-detect CSV output for non-terminal + if !helpers.IsTerminal() && outputFormat == "" { + outputFormat = "csv" + } + + if outputFormat != "" { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + images, err := fetchAllImages(ctx, resolvedProjectID, token, client) + if err != nil { + return err + } + + if len(images) == 0 { + fmt.Println("No images found") + return nil + } + + switch outputFormat { + case "csv": + return images.WriteCSV() + case "json": + return images.WriteJSON() + default: + return errors.Errorf("unknown format: %s. Requires csv or json", outputFormat) + } + } + + // Interactive table view + columns := []table.Column{ + {Title: "Tag", Width: 50}, + {Title: "Size", Width: 15}, + {Title: "Pushed", Width: 20}, + {Title: "Digest", Width: 30}, + } + + styles := table.DefaultStyles() + styles.Header = styles.Header. + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("240")). + BorderBottom(true). + Bold(false) + + styles.Selected = styles.Selected. + Foreground(lipgloss.Color("229")). + Background(lipgloss.Color("57")). + Bold(false) + + tbl := table.New( + table.WithColumns(columns), + table.WithFocused(true), + table.WithStyles(styles), + ) + + m := imagesModel{ + client: client, + imagesTable: tbl, + columns: columns, + projectID: resolvedProjectID, + token: token, + } + + _, err = tea.NewProgram(m, tea.WithAltScreen()).Run() + return err + }, + } + + flags := cmd.Flags() + flags.StringVar(&projectID, "project", "", "Depot project ID") + flags.StringVar(&token, "token", "", "Depot token") + flags.StringVar(&outputFormat, "output", "", "Non-interactive output format (json, csv)") + + return cmd +} + +type DepotImage struct { + Tag string `json:"tag"` + Digest string `json:"digest"` + SizeBytes uint64 `json:"size_bytes"` + PushedAt *time.Time `json:"pushed_at,omitempty"` +} + +type DepotImages []DepotImage + +func fetchAllImages(ctx context.Context, projectID, token string, client buildv1connect.RegistryServiceClient) (DepotImages, error) { + var allImages DepotImages + var pageToken string + + for { + pageSize := int32(100) + req := connect.NewRequest(&v1.ListImagesRequest{ + ProjectId: projectID, + PageSize: &pageSize, + }) + if pageToken != "" { + req.Msg.PageToken = &pageToken + } + + req = api.WithAuthentication(req, token) + resp, err := client.ListImages(ctx, req) + if err != nil { + return nil, fmt.Errorf("failed to list images: %w", err) + } + + for _, img := range resp.Msg.Images { + var pushedAt *time.Time + if img.PushedAt != nil { + t := img.PushedAt.AsTime() + pushedAt = &t + } + allImages = append(allImages, DepotImage{ + Tag: img.Tag, + Digest: img.Digest, + SizeBytes: img.SizeBytes, + PushedAt: pushedAt, + }) + } + + if resp.Msg.NextPageToken == nil || *resp.Msg.NextPageToken == "" { + break + } + pageToken = *resp.Msg.NextPageToken + } + + // Sort images by pushedAt timestamp, newest first + sort.Slice(allImages, func(i, j int) bool { + // Handle nil timestamps - put images without timestamps at the end + if allImages[i].PushedAt == nil && allImages[j].PushedAt == nil { + return false + } + if allImages[i].PushedAt == nil { + return false + } + if allImages[j].PushedAt == nil { + return true + } + // Sort by newest first + return allImages[i].PushedAt.After(*allImages[j].PushedAt) + }) + + return allImages, nil +} + +func (images DepotImages) WriteCSV() error { + w := csv.NewWriter(os.Stdout) + if len(images) > 0 { + if err := w.Write([]string{"Tag", "Digest", "Size (bytes)", "Pushed At"}); err != nil { + return err + } + } + + for _, img := range images { + var pushedAt string + if img.PushedAt != nil { + pushedAt = img.PushedAt.Format(time.RFC3339) + } else { + pushedAt = "" + } + + row := []string{img.Tag, img.Digest, fmt.Sprintf("%d", img.SizeBytes), pushedAt} + if err := w.Write(row); err != nil { + return err + } + } + + w.Flush() + return w.Error() +} + +// WriteJSON outputs images in JSON format +func (images DepotImages) WriteJSON() error { + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + return enc.Encode(images) +} + +// Bubbletea model for interactive image list +type imagesModel struct { + client buildv1connect.RegistryServiceClient + imagesTable table.Model + columns []table.Column + projectID string + token string + err error +} + +func (m imagesModel) Init() tea.Cmd { + return m.loadImages() +} + +func (m imagesModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { + var cmd tea.Cmd + switch msg := msg.(type) { + case tea.KeyMsg: + if msg.Type == tea.KeyCtrlC || msg.Type == tea.KeyEsc { + return m, tea.Quit + } + + if msg.String() == "q" { + return m, tea.Quit + } + + if msg.String() == "r" { + return m, m.loadImages() + } + + case tea.WindowSizeMsg: + m.resizeTable(msg) + + case imageRows: + m.err = nil + m.imagesTable.SetRows(msg) + + case errMsg: + m.err = msg.error + } + + m.imagesTable, cmd = m.imagesTable.Update(msg) + return m, cmd +} + +func (m *imagesModel) resizeTable(msg tea.WindowSizeMsg) { + h, v := baseStyle.GetFrameSize() + m.imagesTable.SetHeight(msg.Height - v - 3) + m.imagesTable.SetWidth(msg.Width - h) + + colWidth := 0 + for _, col := range m.columns { + colWidth += col.Width + } + + remainingWidth := msg.Width - colWidth + if remainingWidth > 0 { + m.columns[len(m.columns)-1].Width += remainingWidth - h - 4 + m.imagesTable.SetColumns(m.columns) + } +} + +func (m imagesModel) View() string { + s := baseStyle.Render(m.imagesTable.View()) + "\n" + if m.err != nil { + s = "Error: " + m.err.Error() + "\n" + } + return s +} + +type imageRows []table.Row +type errMsg struct{ error } + +func (m imagesModel) loadImages() tea.Cmd { + return func() tea.Msg { + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + images, err := fetchAllImages(ctx, m.projectID, m.token, m.client) + if err != nil { + return errMsg{err} + } + + rows := []table.Row{} + for _, img := range images { + tag := img.Tag + if len(tag) > 50 { + tag = tag[:47] + "..." + } + + size := formatSize(img.SizeBytes) + + var pushedStr string + if img.PushedAt != nil { + pushedStr = img.PushedAt.Format(time.RFC3339) + } else { + pushedStr = "-" + } + + digest := img.Digest + if len(digest) > 30 { + digest = digest[:27] + "..." + } + + rows = append(rows, table.Row{tag, size, pushedStr, digest}) + } + + return imageRows(rows) + } +} + +var baseStyle = lipgloss.NewStyle(). + BorderStyle(lipgloss.NormalBorder()). + BorderForeground(lipgloss.Color("240")) + +func formatSize(bytes uint64) string { + const unit = 1024 + if bytes < unit { + return fmt.Sprintf("%d B", bytes) + } + div, exp := uint64(unit), 0 + for n := bytes / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp]) +} diff --git a/pkg/cmd/image/rm.go b/pkg/cmd/image/rm.go new file mode 100644 index 00000000..45177333 --- /dev/null +++ b/pkg/cmd/image/rm.go @@ -0,0 +1,65 @@ +package image + +import ( + "fmt" + + "connectrpc.com/connect" + "github.com/depot/cli/pkg/api" + "github.com/depot/cli/pkg/helpers" + v1 "github.com/depot/cli/pkg/proto/depot/build/v1" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +func NewCmdRM() *cobra.Command { + var token string + var projectID string + + cmd := &cobra.Command{ + Use: "rm [...]", + Aliases: []string{"remove", "delete"}, + Short: "Remove images from the registry by tag", + Args: cobra.MinimumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + ctx := cmd.Context() + + projectID = helpers.ResolveProjectID(projectID) + if projectID == "" { + return errors.New("please specify a project ID") + } + + imageTags := args + + token, err := helpers.ResolveProjectAuth(ctx, token) + if err != nil { + return err + } + + if token == "" { + return fmt.Errorf("missing API token, please run `depot login`") + } + + client := api.NewRegistryClient() + req := connect.NewRequest(&v1.DeleteImageRequest{ProjectId: projectID, ImageTags: imageTags}) + _, err = client.DeleteImage(ctx, api.WithAuthentication(req, token)) + if err != nil { + return fmt.Errorf("failed to delete images: %v", err) + } + + totalImages := len(imageTags) + if totalImages == 1 { + fmt.Printf("Successfully deleted image with tag: %s\n", imageTags[0]) + } else { + fmt.Printf("Successfully deleted %d images\n", totalImages) + } + + return nil + }, + } + + flags := cmd.Flags() + flags.StringVar(&projectID, "project", "", "Depot project ID") + flags.StringVar(&token, "token", "", "Depot token") + + return cmd +} diff --git a/pkg/cmd/root/root.go b/pkg/cmd/root/root.go index 0a705950..e245cd01 100644 --- a/pkg/cmd/root/root.go +++ b/pkg/cmd/root/root.go @@ -14,6 +14,7 @@ import ( dockerCmd "github.com/depot/cli/pkg/cmd/docker" "github.com/depot/cli/pkg/cmd/exec" "github.com/depot/cli/pkg/cmd/gocache" + "github.com/depot/cli/pkg/cmd/image" initCmd "github.com/depot/cli/pkg/cmd/init" "github.com/depot/cli/pkg/cmd/list" loginCmd "github.com/depot/cli/pkg/cmd/login" @@ -65,6 +66,7 @@ func NewCmdRoot(version, buildDate string) *cobra.Command { cmd.AddCommand(cacheCmd.NewCmdCache()) cmd.AddCommand(cargoCmd.NewCmdCargo()) cmd.AddCommand(claudeCmd.NewCmdClaude()) + cmd.AddCommand(image.NewCmdImage()) cmd.AddCommand(initCmd.NewCmdInit()) cmd.AddCommand(list.NewCmdList()) cmd.AddCommand(loginCmd.NewCmdLogin()) diff --git a/pkg/proto/depot/build/v1/buildv1connect/registry.connect.go b/pkg/proto/depot/build/v1/buildv1connect/registry.connect.go new file mode 100644 index 00000000..67f5613c --- /dev/null +++ b/pkg/proto/depot/build/v1/buildv1connect/registry.connect.go @@ -0,0 +1,132 @@ +// Code generated by protoc-gen-connect-go. DO NOT EDIT. +// +// Source: depot/build/v1/registry.proto + +package buildv1connect + +import ( + connect "connectrpc.com/connect" + context "context" + errors "errors" + v1 "github.com/depot/cli/pkg/proto/depot/build/v1" + http "net/http" + strings "strings" +) + +// This is a compile-time assertion to ensure that this generated file and the connect package are +// compatible. If you get a compiler error that this constant is not defined, this code was +// generated with a version of connect newer than the one compiled into your binary. You can fix the +// problem by either regenerating this code with an older version of connect or updating the connect +// version compiled into your binary. +const _ = connect.IsAtLeastVersion0_1_0 + +const ( + // RegistryServiceName is the fully-qualified name of the RegistryService service. + RegistryServiceName = "depot.build.v1.RegistryService" +) + +// These constants are the fully-qualified names of the RPCs defined in this package. They're +// exposed at runtime as Spec.Procedure and as the final two segments of the HTTP route. +// +// Note that these are different from the fully-qualified method names used by +// google.golang.org/protobuf/reflect/protoreflect. To convert from these constants to +// reflection-formatted method names, remove the leading slash and convert the remaining slash to a +// period. +const ( + // RegistryServiceListImagesProcedure is the fully-qualified name of the RegistryService's + // ListImages RPC. + RegistryServiceListImagesProcedure = "/depot.build.v1.RegistryService/ListImages" + // RegistryServiceDeleteImageProcedure is the fully-qualified name of the RegistryService's + // DeleteImage RPC. + RegistryServiceDeleteImageProcedure = "/depot.build.v1.RegistryService/DeleteImage" +) + +// RegistryServiceClient is a client for the depot.build.v1.RegistryService service. +type RegistryServiceClient interface { + ListImages(context.Context, *connect.Request[v1.ListImagesRequest]) (*connect.Response[v1.ListImagesResponse], error) + DeleteImage(context.Context, *connect.Request[v1.DeleteImageRequest]) (*connect.Response[v1.DeleteImageResponse], error) +} + +// NewRegistryServiceClient constructs a client for the depot.build.v1.RegistryService service. By +// default, it uses the Connect protocol with the binary Protobuf Codec, asks for gzipped responses, +// and sends uncompressed requests. To use the gRPC or gRPC-Web protocols, supply the +// connect.WithGRPC() or connect.WithGRPCWeb() options. +// +// The URL supplied here should be the base URL for the Connect or gRPC server (for example, +// http://api.acme.com or https://acme.com/grpc). +func NewRegistryServiceClient(httpClient connect.HTTPClient, baseURL string, opts ...connect.ClientOption) RegistryServiceClient { + baseURL = strings.TrimRight(baseURL, "/") + return ®istryServiceClient{ + listImages: connect.NewClient[v1.ListImagesRequest, v1.ListImagesResponse]( + httpClient, + baseURL+RegistryServiceListImagesProcedure, + opts..., + ), + deleteImage: connect.NewClient[v1.DeleteImageRequest, v1.DeleteImageResponse]( + httpClient, + baseURL+RegistryServiceDeleteImageProcedure, + opts..., + ), + } +} + +// registryServiceClient implements RegistryServiceClient. +type registryServiceClient struct { + listImages *connect.Client[v1.ListImagesRequest, v1.ListImagesResponse] + deleteImage *connect.Client[v1.DeleteImageRequest, v1.DeleteImageResponse] +} + +// ListImages calls depot.build.v1.RegistryService.ListImages. +func (c *registryServiceClient) ListImages(ctx context.Context, req *connect.Request[v1.ListImagesRequest]) (*connect.Response[v1.ListImagesResponse], error) { + return c.listImages.CallUnary(ctx, req) +} + +// DeleteImage calls depot.build.v1.RegistryService.DeleteImage. +func (c *registryServiceClient) DeleteImage(ctx context.Context, req *connect.Request[v1.DeleteImageRequest]) (*connect.Response[v1.DeleteImageResponse], error) { + return c.deleteImage.CallUnary(ctx, req) +} + +// RegistryServiceHandler is an implementation of the depot.build.v1.RegistryService service. +type RegistryServiceHandler interface { + ListImages(context.Context, *connect.Request[v1.ListImagesRequest]) (*connect.Response[v1.ListImagesResponse], error) + DeleteImage(context.Context, *connect.Request[v1.DeleteImageRequest]) (*connect.Response[v1.DeleteImageResponse], error) +} + +// NewRegistryServiceHandler builds an HTTP handler from the service implementation. It returns the +// path on which to mount the handler and the handler itself. +// +// By default, handlers support the Connect, gRPC, and gRPC-Web protocols with the binary Protobuf +// and JSON codecs. They also support gzip compression. +func NewRegistryServiceHandler(svc RegistryServiceHandler, opts ...connect.HandlerOption) (string, http.Handler) { + registryServiceListImagesHandler := connect.NewUnaryHandler( + RegistryServiceListImagesProcedure, + svc.ListImages, + opts..., + ) + registryServiceDeleteImageHandler := connect.NewUnaryHandler( + RegistryServiceDeleteImageProcedure, + svc.DeleteImage, + opts..., + ) + return "/depot.build.v1.RegistryService/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case RegistryServiceListImagesProcedure: + registryServiceListImagesHandler.ServeHTTP(w, r) + case RegistryServiceDeleteImageProcedure: + registryServiceDeleteImageHandler.ServeHTTP(w, r) + default: + http.NotFound(w, r) + } + }) +} + +// UnimplementedRegistryServiceHandler returns CodeUnimplemented from all methods. +type UnimplementedRegistryServiceHandler struct{} + +func (UnimplementedRegistryServiceHandler) ListImages(context.Context, *connect.Request[v1.ListImagesRequest]) (*connect.Response[v1.ListImagesResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("depot.build.v1.RegistryService.ListImages is not implemented")) +} + +func (UnimplementedRegistryServiceHandler) DeleteImage(context.Context, *connect.Request[v1.DeleteImageRequest]) (*connect.Response[v1.DeleteImageResponse], error) { + return nil, connect.NewError(connect.CodeUnimplemented, errors.New("depot.build.v1.RegistryService.DeleteImage is not implemented")) +} diff --git a/pkg/proto/depot/build/v1/registry.pb.go b/pkg/proto/depot/build/v1/registry.pb.go new file mode 100644 index 00000000..8cadaed4 --- /dev/null +++ b/pkg/proto/depot/build/v1/registry.pb.go @@ -0,0 +1,499 @@ +// Code generated by protoc-gen-go. DO NOT EDIT. +// versions: +// protoc-gen-go v1.31.0 +// protoc (unknown) +// source: depot/build/v1/registry.proto + +package buildv1 + +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 ListImagesRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProjectId string `protobuf:"bytes,1,opt,name=project_id,json=projectId,proto3" json:"project_id,omitempty"` + // The maximum number of results to return per page + PageSize *int32 `protobuf:"varint,2,opt,name=page_size,json=pageSize,proto3,oneof" json:"page_size,omitempty"` + // The page token indicating which page of results to return + PageToken *string `protobuf:"bytes,3,opt,name=page_token,json=pageToken,proto3,oneof" json:"page_token,omitempty"` +} + +func (x *ListImagesRequest) Reset() { + *x = ListImagesRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_depot_build_v1_registry_proto_msgTypes[0] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListImagesRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListImagesRequest) ProtoMessage() {} + +func (x *ListImagesRequest) ProtoReflect() protoreflect.Message { + mi := &file_depot_build_v1_registry_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 ListImagesRequest.ProtoReflect.Descriptor instead. +func (*ListImagesRequest) Descriptor() ([]byte, []int) { + return file_depot_build_v1_registry_proto_rawDescGZIP(), []int{0} +} + +func (x *ListImagesRequest) GetProjectId() string { + if x != nil { + return x.ProjectId + } + return "" +} + +func (x *ListImagesRequest) GetPageSize() int32 { + if x != nil && x.PageSize != nil { + return *x.PageSize + } + return 0 +} + +func (x *ListImagesRequest) GetPageToken() string { + if x != nil && x.PageToken != nil { + return *x.PageToken + } + return "" +} + +type Image struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Tag string `protobuf:"bytes,1,opt,name=tag,proto3" json:"tag,omitempty"` + Digest string `protobuf:"bytes,2,opt,name=digest,proto3" json:"digest,omitempty"` + PushedAt *timestamppb.Timestamp `protobuf:"bytes,3,opt,name=pushed_at,json=pushedAt,proto3" json:"pushed_at,omitempty"` + SizeBytes uint64 `protobuf:"varint,4,opt,name=size_bytes,json=sizeBytes,proto3" json:"size_bytes,omitempty"` +} + +func (x *Image) Reset() { + *x = Image{} + if protoimpl.UnsafeEnabled { + mi := &file_depot_build_v1_registry_proto_msgTypes[1] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *Image) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*Image) ProtoMessage() {} + +func (x *Image) ProtoReflect() protoreflect.Message { + mi := &file_depot_build_v1_registry_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 Image.ProtoReflect.Descriptor instead. +func (*Image) Descriptor() ([]byte, []int) { + return file_depot_build_v1_registry_proto_rawDescGZIP(), []int{1} +} + +func (x *Image) GetTag() string { + if x != nil { + return x.Tag + } + return "" +} + +func (x *Image) GetDigest() string { + if x != nil { + return x.Digest + } + return "" +} + +func (x *Image) GetPushedAt() *timestamppb.Timestamp { + if x != nil { + return x.PushedAt + } + return nil +} + +func (x *Image) GetSizeBytes() uint64 { + if x != nil { + return x.SizeBytes + } + return 0 +} + +type ListImagesResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + Images []*Image `protobuf:"bytes,1,rep,name=images,proto3" json:"images,omitempty"` + // The next page token, if there are more results + NextPageToken *string `protobuf:"bytes,2,opt,name=next_page_token,json=nextPageToken,proto3,oneof" json:"next_page_token,omitempty"` +} + +func (x *ListImagesResponse) Reset() { + *x = ListImagesResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_depot_build_v1_registry_proto_msgTypes[2] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *ListImagesResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*ListImagesResponse) ProtoMessage() {} + +func (x *ListImagesResponse) ProtoReflect() protoreflect.Message { + mi := &file_depot_build_v1_registry_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 ListImagesResponse.ProtoReflect.Descriptor instead. +func (*ListImagesResponse) Descriptor() ([]byte, []int) { + return file_depot_build_v1_registry_proto_rawDescGZIP(), []int{2} +} + +func (x *ListImagesResponse) GetImages() []*Image { + if x != nil { + return x.Images + } + return nil +} + +func (x *ListImagesResponse) GetNextPageToken() string { + if x != nil && x.NextPageToken != nil { + return *x.NextPageToken + } + return "" +} + +type DeleteImageRequest struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields + + ProjectId string `protobuf:"bytes,1,opt,name=project_id,json=projectId,proto3" json:"project_id,omitempty"` + ImageTags []string `protobuf:"bytes,2,rep,name=image_tags,json=imageTags,proto3" json:"image_tags,omitempty"` +} + +func (x *DeleteImageRequest) Reset() { + *x = DeleteImageRequest{} + if protoimpl.UnsafeEnabled { + mi := &file_depot_build_v1_registry_proto_msgTypes[3] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteImageRequest) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteImageRequest) ProtoMessage() {} + +func (x *DeleteImageRequest) ProtoReflect() protoreflect.Message { + mi := &file_depot_build_v1_registry_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 DeleteImageRequest.ProtoReflect.Descriptor instead. +func (*DeleteImageRequest) Descriptor() ([]byte, []int) { + return file_depot_build_v1_registry_proto_rawDescGZIP(), []int{3} +} + +func (x *DeleteImageRequest) GetProjectId() string { + if x != nil { + return x.ProjectId + } + return "" +} + +func (x *DeleteImageRequest) GetImageTags() []string { + if x != nil { + return x.ImageTags + } + return nil +} + +type DeleteImageResponse struct { + state protoimpl.MessageState + sizeCache protoimpl.SizeCache + unknownFields protoimpl.UnknownFields +} + +func (x *DeleteImageResponse) Reset() { + *x = DeleteImageResponse{} + if protoimpl.UnsafeEnabled { + mi := &file_depot_build_v1_registry_proto_msgTypes[4] + ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x)) + ms.StoreMessageInfo(mi) + } +} + +func (x *DeleteImageResponse) String() string { + return protoimpl.X.MessageStringOf(x) +} + +func (*DeleteImageResponse) ProtoMessage() {} + +func (x *DeleteImageResponse) ProtoReflect() protoreflect.Message { + mi := &file_depot_build_v1_registry_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 DeleteImageResponse.ProtoReflect.Descriptor instead. +func (*DeleteImageResponse) Descriptor() ([]byte, []int) { + return file_depot_build_v1_registry_proto_rawDescGZIP(), []int{4} +} + +var File_depot_build_v1_registry_proto protoreflect.FileDescriptor + +var file_depot_build_v1_registry_proto_rawDesc = []byte{ + 0x0a, 0x1d, 0x64, 0x65, 0x70, 0x6f, 0x74, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2f, 0x76, 0x31, + 0x2f, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, + 0x0e, 0x64, 0x65, 0x70, 0x6f, 0x74, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x76, 0x31, 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, 0x95, 0x01, 0x0a, 0x11, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x52, + 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, + 0x74, 0x5f, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x6a, + 0x65, 0x63, 0x74, 0x49, 0x64, 0x12, 0x20, 0x0a, 0x09, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, + 0x7a, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x48, 0x00, 0x52, 0x08, 0x70, 0x61, 0x67, 0x65, + 0x53, 0x69, 0x7a, 0x65, 0x88, 0x01, 0x01, 0x12, 0x22, 0x0a, 0x0a, 0x70, 0x61, 0x67, 0x65, 0x5f, + 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x48, 0x01, 0x52, 0x09, 0x70, + 0x61, 0x67, 0x65, 0x54, 0x6f, 0x6b, 0x65, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x0c, 0x0a, 0x0a, 0x5f, + 0x70, 0x61, 0x67, 0x65, 0x5f, 0x73, 0x69, 0x7a, 0x65, 0x42, 0x0d, 0x0a, 0x0b, 0x5f, 0x70, 0x61, + 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x89, 0x01, 0x0a, 0x05, 0x49, 0x6d, 0x61, + 0x67, 0x65, 0x12, 0x10, 0x0a, 0x03, 0x74, 0x61, 0x67, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x74, 0x61, 0x67, 0x12, 0x16, 0x0a, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x18, 0x02, + 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x64, 0x69, 0x67, 0x65, 0x73, 0x74, 0x12, 0x37, 0x0a, 0x09, + 0x70, 0x75, 0x73, 0x68, 0x65, 0x64, 0x5f, 0x61, 0x74, 0x18, 0x03, 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, 0x08, 0x70, 0x75, 0x73, + 0x68, 0x65, 0x64, 0x41, 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x73, 0x69, 0x7a, 0x65, 0x5f, 0x62, 0x79, + 0x74, 0x65, 0x73, 0x18, 0x04, 0x20, 0x01, 0x28, 0x04, 0x52, 0x09, 0x73, 0x69, 0x7a, 0x65, 0x42, + 0x79, 0x74, 0x65, 0x73, 0x22, 0x84, 0x01, 0x0a, 0x12, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6d, 0x61, + 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x2d, 0x0a, 0x06, 0x69, + 0x6d, 0x61, 0x67, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x64, 0x65, + 0x70, 0x6f, 0x74, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x49, 0x6d, 0x61, + 0x67, 0x65, 0x52, 0x06, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x12, 0x2b, 0x0a, 0x0f, 0x6e, 0x65, + 0x78, 0x74, 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x09, 0x48, 0x00, 0x52, 0x0d, 0x6e, 0x65, 0x78, 0x74, 0x50, 0x61, 0x67, 0x65, 0x54, + 0x6f, 0x6b, 0x65, 0x6e, 0x88, 0x01, 0x01, 0x42, 0x12, 0x0a, 0x10, 0x5f, 0x6e, 0x65, 0x78, 0x74, + 0x5f, 0x70, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x22, 0x52, 0x0a, 0x12, 0x44, + 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, + 0x74, 0x12, 0x1d, 0x0a, 0x0a, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x5f, 0x69, 0x64, 0x18, + 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x70, 0x72, 0x6f, 0x6a, 0x65, 0x63, 0x74, 0x49, 0x64, + 0x12, 0x1d, 0x0a, 0x0a, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x5f, 0x74, 0x61, 0x67, 0x73, 0x18, 0x02, + 0x20, 0x03, 0x28, 0x09, 0x52, 0x09, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x54, 0x61, 0x67, 0x73, 0x22, + 0x15, 0x0a, 0x13, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, + 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0xbe, 0x01, 0x0a, 0x0f, 0x52, 0x65, 0x67, 0x69, 0x73, + 0x74, 0x72, 0x79, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x53, 0x0a, 0x0a, 0x4c, 0x69, + 0x73, 0x74, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x12, 0x21, 0x2e, 0x64, 0x65, 0x70, 0x6f, 0x74, + 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, 0x74, 0x49, 0x6d, + 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x64, 0x65, + 0x70, 0x6f, 0x74, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x73, + 0x74, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, + 0x56, 0x0a, 0x0b, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x12, 0x22, + 0x2e, 0x64, 0x65, 0x70, 0x6f, 0x74, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x76, 0x31, 0x2e, + 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x23, 0x2e, 0x64, 0x65, 0x70, 0x6f, 0x74, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, + 0x2e, 0x76, 0x31, 0x2e, 0x44, 0x65, 0x6c, 0x65, 0x74, 0x65, 0x49, 0x6d, 0x61, 0x67, 0x65, 0x52, + 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0xb4, 0x01, 0x0a, 0x12, 0x63, 0x6f, 0x6d, 0x2e, + 0x64, 0x65, 0x70, 0x6f, 0x74, 0x2e, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x76, 0x31, 0x42, 0x0d, + 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x79, 0x50, 0x72, 0x6f, 0x74, 0x6f, 0x50, 0x01, 0x5a, + 0x35, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x64, 0x65, 0x70, 0x6f, + 0x74, 0x2f, 0x63, 0x6c, 0x69, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2f, + 0x64, 0x65, 0x70, 0x6f, 0x74, 0x2f, 0x62, 0x75, 0x69, 0x6c, 0x64, 0x2f, 0x76, 0x31, 0x3b, 0x62, + 0x75, 0x69, 0x6c, 0x64, 0x76, 0x31, 0xa2, 0x02, 0x03, 0x44, 0x42, 0x58, 0xaa, 0x02, 0x0e, 0x44, + 0x65, 0x70, 0x6f, 0x74, 0x2e, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x2e, 0x56, 0x31, 0xca, 0x02, 0x0e, + 0x44, 0x65, 0x70, 0x6f, 0x74, 0x5c, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x5c, 0x56, 0x31, 0xe2, 0x02, + 0x1a, 0x44, 0x65, 0x70, 0x6f, 0x74, 0x5c, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x5c, 0x56, 0x31, 0x5c, + 0x47, 0x50, 0x42, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0xea, 0x02, 0x10, 0x44, 0x65, + 0x70, 0x6f, 0x74, 0x3a, 0x3a, 0x42, 0x75, 0x69, 0x6c, 0x64, 0x3a, 0x3a, 0x56, 0x31, 0x62, 0x06, + 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, +} + +var ( + file_depot_build_v1_registry_proto_rawDescOnce sync.Once + file_depot_build_v1_registry_proto_rawDescData = file_depot_build_v1_registry_proto_rawDesc +) + +func file_depot_build_v1_registry_proto_rawDescGZIP() []byte { + file_depot_build_v1_registry_proto_rawDescOnce.Do(func() { + file_depot_build_v1_registry_proto_rawDescData = protoimpl.X.CompressGZIP(file_depot_build_v1_registry_proto_rawDescData) + }) + return file_depot_build_v1_registry_proto_rawDescData +} + +var file_depot_build_v1_registry_proto_msgTypes = make([]protoimpl.MessageInfo, 5) +var file_depot_build_v1_registry_proto_goTypes = []interface{}{ + (*ListImagesRequest)(nil), // 0: depot.build.v1.ListImagesRequest + (*Image)(nil), // 1: depot.build.v1.Image + (*ListImagesResponse)(nil), // 2: depot.build.v1.ListImagesResponse + (*DeleteImageRequest)(nil), // 3: depot.build.v1.DeleteImageRequest + (*DeleteImageResponse)(nil), // 4: depot.build.v1.DeleteImageResponse + (*timestamppb.Timestamp)(nil), // 5: google.protobuf.Timestamp +} +var file_depot_build_v1_registry_proto_depIdxs = []int32{ + 5, // 0: depot.build.v1.Image.pushed_at:type_name -> google.protobuf.Timestamp + 1, // 1: depot.build.v1.ListImagesResponse.images:type_name -> depot.build.v1.Image + 0, // 2: depot.build.v1.RegistryService.ListImages:input_type -> depot.build.v1.ListImagesRequest + 3, // 3: depot.build.v1.RegistryService.DeleteImage:input_type -> depot.build.v1.DeleteImageRequest + 2, // 4: depot.build.v1.RegistryService.ListImages:output_type -> depot.build.v1.ListImagesResponse + 4, // 5: depot.build.v1.RegistryService.DeleteImage:output_type -> depot.build.v1.DeleteImageResponse + 4, // [4:6] is the sub-list for method output_type + 2, // [2:4] is the sub-list for method input_type + 2, // [2:2] is the sub-list for extension type_name + 2, // [2:2] is the sub-list for extension extendee + 0, // [0:2] is the sub-list for field type_name +} + +func init() { file_depot_build_v1_registry_proto_init() } +func file_depot_build_v1_registry_proto_init() { + if File_depot_build_v1_registry_proto != nil { + return + } + if !protoimpl.UnsafeEnabled { + file_depot_build_v1_registry_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListImagesRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_depot_build_v1_registry_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*Image); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_depot_build_v1_registry_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*ListImagesResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_depot_build_v1_registry_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteImageRequest); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + file_depot_build_v1_registry_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} { + switch v := v.(*DeleteImageResponse); i { + case 0: + return &v.state + case 1: + return &v.sizeCache + case 2: + return &v.unknownFields + default: + return nil + } + } + } + file_depot_build_v1_registry_proto_msgTypes[0].OneofWrappers = []interface{}{} + file_depot_build_v1_registry_proto_msgTypes[2].OneofWrappers = []interface{}{} + type x struct{} + out := protoimpl.TypeBuilder{ + File: protoimpl.DescBuilder{ + GoPackagePath: reflect.TypeOf(x{}).PkgPath(), + RawDescriptor: file_depot_build_v1_registry_proto_rawDesc, + NumEnums: 0, + NumMessages: 5, + NumExtensions: 0, + NumServices: 1, + }, + GoTypes: file_depot_build_v1_registry_proto_goTypes, + DependencyIndexes: file_depot_build_v1_registry_proto_depIdxs, + MessageInfos: file_depot_build_v1_registry_proto_msgTypes, + }.Build() + File_depot_build_v1_registry_proto = out.File + file_depot_build_v1_registry_proto_rawDesc = nil + file_depot_build_v1_registry_proto_goTypes = nil + file_depot_build_v1_registry_proto_depIdxs = nil +} diff --git a/proto/depot/build/v1/registry.proto b/proto/depot/build/v1/registry.proto new file mode 100644 index 00000000..16c98ab8 --- /dev/null +++ b/proto/depot/build/v1/registry.proto @@ -0,0 +1,43 @@ +syntax = "proto3"; + +package depot.build.v1; + +import "google/protobuf/timestamp.proto"; + +option go_package = "depot/build/v1"; + +service RegistryService { + rpc ListImages(ListImagesRequest) returns (ListImagesResponse); + rpc DeleteImage(DeleteImageRequest) returns (DeleteImageResponse); +} + +message ListImagesRequest { + string project_id = 1; + + // The maximum number of results to return per page + optional int32 page_size = 2; + + // The page token indicating which page of results to return + optional string page_token = 3; +} + +message Image { + string tag = 1; + string digest = 2; + google.protobuf.Timestamp pushed_at = 3; + uint64 size_bytes = 4; +} + +message ListImagesResponse { + repeated Image images = 1; + + // The next page token, if there are more results + optional string next_page_token = 2; +} + +message DeleteImageRequest { + string project_id = 1; + repeated string image_tags = 2; +} + +message DeleteImageResponse {}