1// Package registry implements the container registry use case.
2package registry
3
4import (
5 "context"
6 "io"
7
8 "github.com/bnema/zerowrap"
9
10 "gordon/internal/boundaries/out"
11 "gordon/internal/domain"
12)
13
14// Service implements the RegistryService interface.
15type Service struct {
16 blobStorage out.BlobStorage
17 manifestStorage out.ManifestStorage
18 eventBus out.EventPublisher
19}
20
21// NewService creates a new registry service.
22func NewService(
23 blobStorage out.BlobStorage,
24 manifestStorage out.ManifestStorage,
25 eventBus out.EventPublisher,
26) *Service {
27 return &Service{
28 blobStorage: blobStorage,
29 manifestStorage: manifestStorage,
30 eventBus: eventBus,
31 }
32}
33
34// GetManifest retrieves a manifest by name and reference.
35func (s *Service) GetManifest(ctx context.Context, name, reference string) (*domain.Manifest, error) {
36 ctx = zerowrap.CtxWithFields(ctx, map[string]any{
37 zerowrap.FieldLayer: "usecase",
38 zerowrap.FieldUseCase: "GetManifest",
39 "name": name,
40 "reference": reference,
41 })
42 log := zerowrap.FromCtx(ctx)
43
44 data, contentType, err := s.manifestStorage.GetManifest(name, reference)
45 if err != nil {
46 return nil, log.WrapErr(err, "failed to get manifest")
47 }
48
49 return &domain.Manifest{
50 Name: name,
51 Reference: reference,
52 ContentType: contentType,
53 Data: data,
54 }, nil
55}
56
57// PutManifest stores a manifest.
58func (s *Service) PutManifest(ctx context.Context, manifest *domain.Manifest) error {
59 ctx = zerowrap.CtxWithFields(ctx, map[string]any{
60 zerowrap.FieldLayer: "usecase",
61 zerowrap.FieldUseCase: "PutManifest",
62 "name": manifest.Name,
63 "reference": manifest.Reference,
64 })
65 log := zerowrap.FromCtx(ctx)
66
67 if err := s.manifestStorage.PutManifest(manifest.Name, manifest.Reference, manifest.ContentType, manifest.Data); err != nil {
68 return log.WrapErr(err, "failed to store manifest")
69 }
70
71 // Publish image pushed event
72 if s.eventBus != nil {
73 if err := s.eventBus.Publish(domain.EventImagePushed, domain.ImagePushedPayload{
74 Name: manifest.Name,
75 Reference: manifest.Reference,
76 Manifest: manifest.Data,
77 Annotations: manifest.Annotations,
78 }); err != nil {
79 log.Warn().Err(err).Msg("failed to publish image pushed event")
80 }
81 }
82
83 log.Info().Msg("manifest stored")
84 return nil
85}
86
87// DeleteManifest removes a manifest.
88func (s *Service) DeleteManifest(ctx context.Context, name, reference string) error {
89 ctx = zerowrap.CtxWithFields(ctx, map[string]any{
90 zerowrap.FieldLayer: "usecase",
91 zerowrap.FieldUseCase: "DeleteManifest",
92 "name": name,
93 "reference": reference,
94 })
95 log := zerowrap.FromCtx(ctx)
96
97 if err := s.manifestStorage.DeleteManifest(name, reference); err != nil {
98 return log.WrapErr(err, "failed to delete manifest")
99 }
100
101 log.Info().Msg("manifest deleted")
102 return nil
103}
104
105// GetBlob retrieves a blob by digest.
106func (s *Service) GetBlob(ctx context.Context, digest string) (io.ReadCloser, error) {
107 ctx = zerowrap.CtxWithFields(ctx, map[string]any{
108 zerowrap.FieldLayer: "usecase",
109 zerowrap.FieldUseCase: "GetBlob",
110 "digest": digest,
111 })
112 log := zerowrap.FromCtx(ctx)
113
114 reader, err := s.blobStorage.GetBlob(digest)
115 if err != nil {
116 return nil, log.WrapErr(err, "failed to get blob")
117 }
118
119 return reader, nil
120}
121
122// PutBlob stores a blob with the given digest.
123func (s *Service) PutBlob(ctx context.Context, digest string, data io.Reader, size int64) error {
124 ctx = zerowrap.CtxWithFields(ctx, map[string]any{
125 zerowrap.FieldLayer: "usecase",
126 zerowrap.FieldUseCase: "PutBlob",
127 "digest": digest,
128 zerowrap.FieldSize: size,
129 })
130 log := zerowrap.FromCtx(ctx)
131
132 if err := s.blobStorage.PutBlob(digest, data, size); err != nil {
133 return log.WrapErr(err, "failed to store blob")
134 }
135
136 log.Info().Msg("blob stored")
137 return nil
138}
139
140// BlobExists checks if a blob exists.
141func (s *Service) BlobExists(_ context.Context, digest string) bool {
142 return s.blobStorage.BlobExists(digest)
143}
144
145// StartUpload starts a new blob upload.
146func (s *Service) StartUpload(ctx context.Context, name string) (string, error) {
147 ctx = zerowrap.CtxWithFields(ctx, map[string]any{
148 zerowrap.FieldLayer: "usecase",
149 zerowrap.FieldUseCase: "StartUpload",
150 "name": name,
151 })
152 log := zerowrap.FromCtx(ctx)
153
154 uuid, err := s.blobStorage.StartBlobUpload(name)
155 if err != nil {
156 return "", log.WrapErr(err, "failed to start blob upload")
157 }
158
159 log.Info().Str("uuid", uuid).Msg("blob upload started")
160 return uuid, nil
161}
162
163// FinishUpload completes a blob upload.
164func (s *Service) FinishUpload(ctx context.Context, uuid, digest string) error {
165 ctx = zerowrap.CtxWithFields(ctx, map[string]any{
166 zerowrap.FieldLayer: "usecase",
167 zerowrap.FieldUseCase: "FinishUpload",
168 "uuid": uuid,
169 "digest": digest,
170 })
171 log := zerowrap.FromCtx(ctx)
172
173 if err := s.blobStorage.FinishBlobUpload(uuid, digest); err != nil {
174 return log.WrapErr(err, "failed to finish blob upload")
175 }
176
177 log.Info().Msg("blob upload finished")
178 return nil
179}
180
181// CancelUpload cancels an in-progress upload.
182func (s *Service) CancelUpload(ctx context.Context, uuid string) error {
183 ctx = zerowrap.CtxWithFields(ctx, map[string]any{
184 zerowrap.FieldLayer: "usecase",
185 zerowrap.FieldUseCase: "CancelUpload",
186 "uuid": uuid,
187 })
188 log := zerowrap.FromCtx(ctx)
189
190 if err := s.blobStorage.CancelBlobUpload(uuid); err != nil {
191 return log.WrapErr(err, "failed to cancel blob upload")
192 }
193
194 log.Info().Msg("blob upload cancelled")
195 return nil
196}
197
198// ListTags returns all tags for a repository.
199func (s *Service) ListTags(ctx context.Context, name string) ([]string, error) {
200 ctx = zerowrap.CtxWithFields(ctx, map[string]any{
201 zerowrap.FieldLayer: "usecase",
202 zerowrap.FieldUseCase: "ListTags",
203 "name": name,
204 })
205 log := zerowrap.FromCtx(ctx)
206
207 tags, err := s.manifestStorage.ListTags(name)
208 if err != nil {
209 return nil, log.WrapErr(err, "failed to list tags")
210 }
211
212 return tags, nil
213}
214
215// ListRepositories returns all repository names.
216func (s *Service) ListRepositories(ctx context.Context) ([]string, error) {
217 ctx = zerowrap.CtxWithFields(ctx, map[string]any{
218 zerowrap.FieldLayer: "usecase",
219 zerowrap.FieldUseCase: "ListRepositories",
220 })
221 log := zerowrap.FromCtx(ctx)
222
223 repos, err := s.manifestStorage.ListRepositories()
224 if err != nil {
225 return nil, log.WrapErr(err, "failed to list repositories")
226 }
227
228 return repos, nil
229}