Skip to content

Commit

Permalink
feat(endpoint): endpoint type for distribution (#31)
Browse files Browse the repository at this point in the history
  • Loading branch information
josias-brenner authored Oct 21, 2023
1 parent cb0e13a commit 1e1e8b2
Show file tree
Hide file tree
Showing 14 changed files with 348 additions and 33 deletions.
3 changes: 2 additions & 1 deletion assets/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@
"enum": [
"generic",
"dockerhub",
"acr"
"acr",
"distribution"
]
},
"requiredHeaders": {
Expand Down
4 changes: 2 additions & 2 deletions docs/src/configuration/endpoints.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ endpoints:
## Types
Currently supported endpoint types are `acr`, `dockerhub` and `generic`. The
generic type expects the image reference in plain text. It can be used if you
Currently supported endpoint types are `acr`, `dockerhub`, `distribution` and `generic`.
The generic type expects the image reference in plain text. It can be used if you
want to dispatch events manually, perhaps via pipeline.

> **Note** If there is no type for your registry of choice, please open an issue
Expand Down
47 changes: 47 additions & 0 deletions e2e/testdata/events/osr.http
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
POST /acr HTTP/1.1
Host: localhost:8080
Content-Type: application/json
Authorization: test

{
"events": [
{
"action": "push",
"id": "069a4143-3448-4260-8af7-1c8c13f89b6e",
"request": {
"host": "test.azurecr.io",
"id": "5f3df6ec-cf20-4f15-92ee-5cd0805f20cf",
"method": "PUT",
"useragent": "docker/24.0.6 go/go1.20.7 git-commit/1a79695 kernel/5.10.0-25-amd64 os/linux arch/amd64 UpstreamClient(Docker-Client/24.0.6 \\(linux\\))"
},
"target": {
"digest": "sha256:xxxxd5c8786bb9e621a45ece0dbxxxx1cdc624ad20da9fe62e9d25490f33xxxx",
"length": 524,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"repository": "nginx",
"size": 524,
"tag": "v1"
},
"timestamp": "2023-10-11T14:45:59.730519823Z"
},
{
"action": "push",
"id": "fb92fd30-f2ee-47ac-8bb8-5cbdddb5bbbc",
"request": {
"host": "test.azurecr.io",
"id": "30fb2503-ccba-4cce-ac9d-3c5a6fec208a",
"method": "PUT",
"useragent": "docker/24.0.6 go/go1.20.7 git-commit/1a79695 kernel/5.10.0-25-amd64 os/linux arch/amd64 UpstreamClient(Docker-Client/24.0.6 \\(linux\\))"
},
"target": {
"digest": "sha256:xxxxd5c8786bb9e621a45ece0dbxxxx1cdc624ad20da9fe62e9d25490f33xxxx",
"length": 524,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"repository": "busybox",
"size": 524,
"tag": "v1"
},
"timestamp": "2023-10-11T14:45:59.730519823Z"
}
]
}
8 changes: 4 additions & 4 deletions internal/events/acr/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,16 +42,16 @@ func (ph payloadHandler) Validate(b []byte) error {
return nil
}

func (ph payloadHandler) Decode(b []byte) (events.PushData, error) {
func (ph payloadHandler) Decode(b []byte) ([]events.PushData, error) {
pl := PushPayload{}
if err := json.Unmarshal(b, &pl); err != nil {
return events.PushData{}, nil
return nil, err
}
return events.PushData{
return []events.PushData{{
Image: fmt.Sprintf("%s/%s", pl.Request.Host, pl.Target.Repository),
Tag: pl.Target.Tag,
Digest: pl.Target.Digest,
}, nil
}}, nil
}

type PushPayload struct {
Expand Down
69 changes: 69 additions & 0 deletions internal/events/distribution/schema.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
{
"$id": "kobold.sh/distribution/push",
"$comment": "Distribution Push Events must conform to this schema",
"title": "Distribution Push",
"type": "object",
"properties": {
"events": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"timestamp": {
"type": "string"
},
"action": {
"type": "string",
"const": "push"
},
"target": {
"type": "object",
"properties": {
"mediaType": {
"type": "string"
},
"size": {
"type": "integer"
},
"digest": {
"type": "string"
},
"length": {
"type": "integer"
},
"repository": {
"type": "string"
},
"tag": {
"type": "string"
}
},
"required": ["digest", "repository", "tag"]
},
"request": {
"type": "object",
"properties": {
"id": {
"type": "string"
},
"host": {
"type": "string"
},
"method": {
"type": "string"
},
"useragent": {
"type": "string"
}
},
"required": ["host"]
}
}
},
"required": ["action"]
}
}
}
105 changes: 105 additions & 0 deletions internal/events/distribution/schema_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package distribution

import "testing"

func TestGoodSchema(t *testing.T) {
ph := NewPayloadHandler()
err := ph.Validate(goodPayload)
if err != nil {
t.Fatal(err)
}
}

func TestBadSchema(t *testing.T) {
ph := NewPayloadHandler()
err := ph.Validate(badPayload)
if err == nil {
t.Fatal("expected error but got none")
}
}

var goodPayload = []byte(`{
"events": [
{
"action": "push",
"id": "6e401fa1-c6d8-48ae-bbfe-2d06941f11b6",
"request": {
"host": "test.azurecr.io",
"id": "d89e7b46-38b0-4f71-83d3-ba6fc005c189",
"method": "PUT",
"useragent": "docker/24.0.6 go/go1.20.7 git-commit/1a79695 kernel/5.10.0-25-amd64 os/linux arch/amd64 UpstreamClient(Docker-Client/24.0.6 \\(linux\\))"
},
"target": {
"digest": "sha256:xxxxd5c8786bb9e621a45ece0dbxxxx1cdc624ad20da9fe62e9d25490f33xxxx",
"length": 524,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"repository": "nginx",
"size": 524,
"tag": "v1"
},
"timestamp": "2023-10-11T14:45:59.730519823Z"
},
{
"action": "push",
"id": "9a5f8995-dbab-4c2b-b06c-0c29c23e759e",
"request": {
"host": "test.azurecr.io",
"id": "ae93b7e8-7e96-4e21-8487-917d74224d92",
"method": "PUT",
"useragent": "docker/24.0.6 go/go1.20.7 git-commit/1a79695 kernel/5.10.0-25-amd64 os/linux arch/amd64 UpstreamClient(Docker-Client/24.0.6 \\(linux\\))"
},
"target": {
"digest": "sha256:xxxxd5c8786bb9e621a45ece0dbxxxx1cdc624ad20da9fe62e9d25490f33xxxx",
"length": 524,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"repository": "busybox",
"size": 524,
"tag": "v1"
},
"timestamp": "2023-10-11T14:45:59.730519823Z"
}
]
}`)

var badPayload = []byte(`{
"events": [
{
"action": "push",
"id": "2f21f8f8-431f-49f9-8f4e-c080094dfc71",
"request": {
"host": "test.azurecr.io",
"id": "407cb475-dadb-4bd1-995f-15eddbdc98e2",
"method": "PUT",
"useragent": "docker/24.0.6 go/go1.20.7 git-commit/1a79695 kernel/5.10.0-25-amd64 os/linux arch/amd64 UpstreamClient(Docker-Client/24.0.6 \\(linux\\))"
},
"target": {
"digest": "sha256:xxxxd5c8786bb9e621a45ece0dbxxxx1cdc624ad20da9fe62e9d25490f33xxxx",
"length": 524,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"repository": "nginx",
"size": 524,
"tag": "v1"
},
"timestamp": "2023-10-11T14:45:59.730519823Z"
},
{
"action": "delete",
"id": "71695e9f-988a-4992-a49f-c52904f7abf0",
"request": {
"host": "test.azurecr.io",
"id": "341ee113-8ea2-4138-9d0d-8e0eddadd55a",
"method": "PUT",
"useragent": "docker/24.0.6 go/go1.20.7 git-commit/1a79695 kernel/5.10.0-25-amd64 os/linux arch/amd64 UpstreamClient(Docker-Client/24.0.6 \\(linux\\))"
},
"target": {
"digest": "sha256:xxxxd5c8786bb9e621a45ece0dbxxxx1cdc624ad20da9fe62e9d25490f33xxxx",
"length": 524,
"mediaType": "application/vnd.docker.distribution.manifest.v2+json",
"repository": "busybox",
"size": 524,
"tag": "v1"
},
"timestamp": "2023-10-11T14:45:59.730519823Z"
}
]
}`)
87 changes: 87 additions & 0 deletions internal/events/distribution/types.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package distribution

import (
"context"
_ "embed"
"encoding/json"
"fmt"
"time"

"github.com/bluebrown/kobold/internal/events"
"github.com/qri-io/jsonschema"
)

var (
//go:embed schema.json
schemaBytes []byte
schema = &jsonschema.Schema{}
)

func init() {
if err := json.Unmarshal(schemaBytes, schema); err != nil {
panic("unmarshal schema: " + err.Error())
}
}

type payloadHandler struct {
schema *jsonschema.Schema
}

func NewPayloadHandler() events.PayloadHandler {
return payloadHandler{schema: schema}
}

func (ph payloadHandler) Validate(b []byte) error {
verr, err := ph.schema.ValidateBytes(context.TODO(), b)
if err != nil {
return err
}
if len(verr) > 0 {
return fmt.Errorf("invalid data")
}
return nil
}

func (ph payloadHandler) Decode(b []byte) ([]events.PushData, error) {
ple := PushPayloadEnvelope{}
if err := json.Unmarshal(b, &ple); err != nil {
return nil, err
}
pushDataSlice := make([]events.PushData, len(ple.Events))
for i, pl := range ple.Events {
pushDataSlice[i] = events.PushData{
Image: fmt.Sprintf("%s/%s", pl.Request.Host, pl.Target.Repository),
Tag: pl.Target.Tag,
Digest: pl.Target.Digest,
}
}
return pushDataSlice, nil
}

type PushPayloadEnvelope struct {
Events []PushPayload `json:"events"`
}

type PushPayload struct {
ID string `json:"id"`
Timestamp time.Time `json:"timestamp"`
Action string `json:"action"`
Target PushTarget `json:"target"`
Request PushRequest `json:"request"`
}

type PushTarget struct {
MediaType string `json:"mediaType"`
Size int `json:"size"`
Digest string `json:"digest"`
Length int `json:"length"`
Repository string `json:"repository"`
Tag string `json:"tag"`
}

type PushRequest struct {
ID string `json:"id"`
Host string `json:"host"`
Method string `json:"method"`
Useragent string `json:"useragent"`
}
10 changes: 5 additions & 5 deletions internal/events/dockerhub/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,20 @@ func (ph payloadHandler) Validate(b []byte) error {
return nil
}

func (ph payloadHandler) Decode(b []byte) (events.PushData, error) {
func (ph payloadHandler) Decode(b []byte) ([]events.PushData, error) {
pl := PushPayload{}
if err := json.Unmarshal(b, &pl); err != nil {
return events.PushData{}, err
return nil, err
}
digest, err := ph.digestFetcher.Fetch(fmt.Sprintf("index.docker.io/%s:%s", pl.Repository.RepoName, pl.PushData.Tag))
if err != nil {
return events.PushData{}, err
return nil, err
}
return events.PushData{
return []events.PushData{{
Image: "index.docker.io/" + pl.Repository.RepoName,
Tag: pl.PushData.Tag,
Digest: digest,
}, nil
}}, nil
}

type PushPayload struct {
Expand Down
2 changes: 1 addition & 1 deletion internal/events/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@ type PushData struct {

type PayloadHandler interface {
Validate([]byte) error
Decode([]byte) (PushData, error)
Decode([]byte) ([]PushData, error)
}
Loading

0 comments on commit 1e1e8b2

Please sign in to comment.