Skip to content

Commit 897fc80

Browse files
committed
feat: #73 support registry data prune
1 parent dae916a commit 897fc80

File tree

11 files changed

+278
-2
lines changed

11 files changed

+278
-2
lines changed

go.mod

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ require (
66
github.com/aws/aws-sdk-go-v2 v1.32.2
77
github.com/aws/aws-sdk-go-v2/credentials v1.17.41
88
github.com/aws/aws-sdk-go-v2/service/s3 v1.65.3
9+
github.com/distribution/reference v0.6.0
910
github.com/docker/docker v27.0.0+incompatible
1011
github.com/mackerelio/go-osstat v0.2.5
1112
github.com/ncruces/go-sqlite3 v0.18.4
12-
github.com/opencontainers/image-spec v1.1.0
1313
github.com/pkg/errors v0.9.1
1414
github.com/prometheus/client_golang v1.14.0
1515
github.com/prometheus/common v0.42.0
@@ -31,24 +31,29 @@ require (
3131
github.com/cespare/xxhash/v2 v2.1.2 // indirect
3232
github.com/containerd/log v0.1.0 // indirect
3333
github.com/davecgh/go-spew v1.1.1 // indirect
34-
github.com/distribution/reference v0.6.0 // indirect
34+
github.com/docker/distribution v2.8.3+incompatible // indirect
3535
github.com/docker/go-connections v0.5.0 // indirect
36+
github.com/docker/go-metrics v0.0.1 // indirect
3637
github.com/docker/go-units v0.5.0 // indirect
38+
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
3739
github.com/felixge/httpsnoop v1.0.4 // indirect
3840
github.com/go-logr/logr v1.4.1 // indirect
3941
github.com/go-logr/stdr v1.2.2 // indirect
4042
github.com/gogo/protobuf v1.3.2 // indirect
4143
github.com/golang/protobuf v1.5.2 // indirect
44+
github.com/gorilla/mux v1.8.1 // indirect
4245
github.com/kr/pretty v0.3.1 // indirect
4346
github.com/matttproud/golang_protobuf_extensions v1.0.4 // indirect
4447
github.com/moby/docker-image-spec v1.3.1 // indirect
4548
github.com/moby/term v0.5.0 // indirect
4649
github.com/morikuni/aec v1.0.0 // indirect
4750
github.com/ncruces/julianday v1.0.0 // indirect
4851
github.com/opencontainers/go-digest v1.0.0 // indirect
52+
github.com/opencontainers/image-spec v1.1.0 // indirect
4953
github.com/pmezard/go-difflib v1.0.0 // indirect
5054
github.com/prometheus/client_model v0.3.0 // indirect
5155
github.com/prometheus/procfs v0.8.0 // indirect
56+
github.com/sirupsen/logrus v1.9.3 // indirect
5257
github.com/tetratelabs/wazero v1.8.0 // indirect
5358
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.52.0 // indirect
5459
go.opentelemetry.io/otel v1.27.0 // indirect

go.sum

Lines changed: 57 additions & 0 deletions
Large diffs are not rendered by default.

internal/app/ptah-agent/images.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@ import (
66
"encoding/json"
77
"fmt"
88
"io"
9+
"slices"
910
"strings"
1011

12+
"github.com/distribution/reference"
1113
"github.com/docker/docker/api/types/mount"
1214
"github.com/pkg/errors"
1315
"github.com/ptah-sh/ptah-agent/internal/app/ptah-agent/busybox"
1416
"github.com/ptah-sh/ptah-agent/internal/app/ptah-agent/docker/config"
17+
"github.com/ptah-sh/ptah-agent/internal/app/ptah-agent/registry"
1518
t "github.com/ptah-sh/ptah-agent/internal/pkg/ptah-client"
1619
)
1720

@@ -145,3 +148,78 @@ func (e *taskExecutor) pullImage(ctx context.Context, req *t.PullImageReq) (*t.P
145148

146149
return &t.PullImageRes{Output: output}, nil
147150
}
151+
152+
func (e *taskExecutor) pruneDockerRegistry(ctx context.Context, req *t.PruneDockerRegistryReq) (*t.PruneDockerRegistryRes, error) {
153+
log := Logger(ctx)
154+
155+
var result t.PruneDockerRegistryRes
156+
157+
tagsToKeep := make(map[string][]string)
158+
for _, imageRef := range req.KeepImages {
159+
ref, err := reference.ParseNamed(imageRef)
160+
if err != nil {
161+
return nil, fmt.Errorf("prune docker registry: %w", err)
162+
}
163+
164+
repo := reference.Path(ref)
165+
166+
repoTags, ok := tagsToKeep[repo]
167+
if !ok {
168+
repoTags = make([]string, 0)
169+
}
170+
171+
taggedRef, ok := reference.TagNameOnly(ref).(reference.Tagged)
172+
if !ok {
173+
return nil, fmt.Errorf("prune docker registry: can not get tag from ref: %s", imageRef)
174+
}
175+
176+
tagsToKeep[repo] = append(repoTags, taggedRef.Tag())
177+
178+
log.Debug("parsed image ref", "ref", ref, "repo", repo, "tag", taggedRef.Tag())
179+
}
180+
181+
registry := registry.New("http://registry.ptah.local:5050")
182+
183+
catalog, err := registry.Catalog(ctx)
184+
if err != nil {
185+
return nil, fmt.Errorf("prune docker registry: %w", err)
186+
}
187+
188+
for _, repo := range catalog.Repositories {
189+
log.Debug("processing repo", "repo", repo)
190+
191+
tags, err := registry.TagsList(ctx, repo)
192+
if err != nil {
193+
return nil, fmt.Errorf("prune docker registry: %w", err)
194+
}
195+
196+
for _, tag := range tags.Tags {
197+
log.Debug("processing tag", "tag", tag)
198+
199+
if _, ok := tagsToKeep[repo]; !ok || !slices.Contains(tagsToKeep[repo], tag) {
200+
manifest, err := registry.ManifestHead(ctx, repo, tag)
201+
if err != nil {
202+
return nil, fmt.Errorf("prune docker registry: %w", err)
203+
}
204+
205+
log.Debug("deleting image", "repo", repo, "tag", tag, "digest", manifest.Digest)
206+
207+
if err := registry.ManifestDelete(ctx, repo, manifest.Digest); err != nil {
208+
return nil, fmt.Errorf("prune docker registry: %w", err)
209+
}
210+
}
211+
}
212+
}
213+
214+
// ref, err := reference.ParseNamed("ptah/test")
215+
// if err != nil {
216+
// return nil, fmt.Errorf("prune docker registry: %w", err)
217+
// }
218+
219+
// info, err := dockerRegistry.ParseRepositoryInfo(ref)
220+
// if err != nil {
221+
// return nil, fmt.Errorf("prune docker registry: %w", err)
222+
// }
223+
224+
return &result, nil
225+
}

internal/app/ptah-agent/parse_task.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,8 @@ func parseTask(taskType int, payload string) (interface{}, error) {
6464
return unmarshalTask(payload, &ptahClient.BuildImageReq{})
6565
case 26:
6666
return unmarshalTask(payload, &ptahClient.BuildImageWithNixpacksReq{})
67+
case 27:
68+
return unmarshalTask(payload, &ptahClient.PruneDockerRegistryReq{})
6769
default:
6870
return nil, fmt.Errorf("parse task: unknown task type %d", taskType)
6971
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package registry
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
)
8+
9+
type CatalogRes struct {
10+
Repositories []string `json:"repositories"`
11+
}
12+
13+
func (r *Registry) Catalog(ctx context.Context) (*CatalogRes, error) {
14+
url := fmt.Sprintf("%s/v2/_catalog", r.baseUrl)
15+
16+
resp, err := r.client.Get(url)
17+
if err != nil {
18+
return nil, fmt.Errorf("get catalog: %w", err)
19+
}
20+
21+
defer resp.Body.Close()
22+
23+
var result CatalogRes
24+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
25+
return nil, fmt.Errorf("decode catalog: %w", err)
26+
}
27+
28+
return &result, nil
29+
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package registry
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"net/http"
7+
)
8+
9+
type ManifestHeadRes struct {
10+
Digest string `json:"digest"`
11+
}
12+
13+
func (r *Registry) ManifestHead(ctx context.Context, repo, tag string) (*ManifestHeadRes, error) {
14+
url := fmt.Sprintf("%s/v2/%s/manifests/%s", r.baseUrl, repo, tag)
15+
16+
req, err := http.NewRequestWithContext(ctx, "HEAD", url, nil)
17+
if err != nil {
18+
return nil, fmt.Errorf("create manifest head request: %w", err)
19+
}
20+
21+
req.Header.Set("Accept", "application/vnd.docker.distribution.manifest.v2+json")
22+
23+
resp, err := r.client.Do(req)
24+
if err != nil {
25+
return nil, fmt.Errorf("do manifest head request: %w", err)
26+
}
27+
28+
defer resp.Body.Close()
29+
30+
return &ManifestHeadRes{
31+
Digest: resp.Header.Get("Docker-Content-Digest"),
32+
}, nil
33+
}
34+
35+
func (r *Registry) ManifestDelete(ctx context.Context, repo, digest string) error {
36+
url := fmt.Sprintf("%s/v2/%s/manifests/%s", r.baseUrl, repo, digest)
37+
38+
req, err := http.NewRequestWithContext(ctx, "DELETE", url, nil)
39+
if err != nil {
40+
return fmt.Errorf("create manifest delete request: %w", err)
41+
}
42+
43+
resp, err := r.client.Do(req)
44+
if err != nil {
45+
return fmt.Errorf("do manifest delete request: %w", err)
46+
}
47+
48+
defer resp.Body.Close()
49+
50+
return nil
51+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package registry
2+
3+
import "net/http"
4+
5+
type Registry struct {
6+
baseUrl string
7+
client *http.Client
8+
}
9+
10+
func New(baseUrl string) *Registry {
11+
return &Registry{
12+
baseUrl: baseUrl,
13+
client: &http.Client{},
14+
}
15+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package registry
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
)
8+
9+
type TagsListRes struct {
10+
Tags []string `json:"tags"`
11+
}
12+
13+
func (r *Registry) TagsList(ctx context.Context, repo string) (*TagsListRes, error) {
14+
url := fmt.Sprintf("%s/v2/%s/tags/list", r.baseUrl, repo)
15+
16+
resp, err := r.client.Get(url)
17+
if err != nil {
18+
return nil, fmt.Errorf("get tags list: %w", err)
19+
}
20+
21+
defer resp.Body.Close()
22+
23+
var result TagsListRes
24+
if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
25+
return nil, fmt.Errorf("decode tags list: %w", err)
26+
}
27+
28+
return &result, nil
29+
}

internal/app/ptah-agent/service_monitor.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ func (e *taskExecutor) monitorDaemonServiceLaunch(ctx context.Context, service *
5858

5959
successfullChecks := 0
6060

61+
// FIXME: check if the container is continiously restarting.
6162
for {
6263
select {
6364
case <-ctx.Done():

internal/app/ptah-agent/task_executor.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ func (e *taskExecutor) executeTask(ctx context.Context, anyTask interface{}) (in
7474
return e.buildImage(ctx, task)
7575
case *t.BuildImageWithNixpacksReq:
7676
return e.buildImageWithNixpacks(ctx, task)
77+
case *t.PruneDockerRegistryReq:
78+
return e.pruneDockerRegistry(ctx, task)
7779
default:
7880
return nil, fmt.Errorf("execute task: unknown task type %T", task)
7981
}

0 commit comments

Comments
 (0)