Skip to content

Commit

Permalink
Support creation of docker packages
Browse files Browse the repository at this point in the history
Issue: #2791

Co-authored-by: Georgi Sabev <[email protected]>
  • Loading branch information
danail-branekov and georgethebeatle committed Aug 24, 2023
1 parent dff8216 commit b290600
Show file tree
Hide file tree
Showing 10 changed files with 372 additions and 99 deletions.
24 changes: 22 additions & 2 deletions api/payloads/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,20 @@ type PackageCreate struct {
Type string `json:"type"`
Relationships *PackageRelationships `json:"relationships"`
Metadata Metadata `json:"metadata"`
Data *PackageData `json:"data"`
}

func (c PackageCreate) Validate() error {
return jellidation.ValidateStruct(&c,
jellidation.Field(&c.Type, validation.OneOf("bits"), jellidation.Required),
jellidation.Field(&c.Type, validation.OneOf("bits", "docker"), jellidation.Required),
jellidation.Field(&c.Relationships, jellidation.NotNil),
jellidation.Field(&c.Metadata),
jellidation.Field(&c.Data, jellidation.When(c.Type == "docker", jellidation.Required).Else(jellidation.Nil)),
)
}

func (c PackageCreate) ToMessage(record repositories.AppRecord) repositories.CreatePackageMessage {
return repositories.CreatePackageMessage{
message := repositories.CreatePackageMessage{
Type: c.Type,
AppGUID: record.GUID,
SpaceGUID: record.SpaceGUID,
Expand All @@ -33,6 +35,24 @@ func (c PackageCreate) ToMessage(record repositories.AppRecord) repositories.Cre
Labels: c.Metadata.Labels,
},
}

if c.Type == "docker" {
message.Data = &repositories.PackageData{
Image: c.Data.Image,
}
}

return message
}

type PackageData struct {
Image string `json:"image"`
}

func (d PackageData) Validate() error {
return jellidation.ValidateStruct(&d,
jellidation.Field(&d.Image, jellidation.Required),
)
}

type PackageRelationships struct {
Expand Down
196 changes: 146 additions & 50 deletions api/payloads/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,17 @@ package payloads_test

import (
"code.cloudfoundry.org/korifi/api/payloads"
"code.cloudfoundry.org/korifi/api/repositories"
"code.cloudfoundry.org/korifi/tools"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
"github.com/onsi/gomega/gstruct"
)

var _ = Describe("PackageCreate", func() {
var (
createPayload payloads.PackageCreate
packageCreate *payloads.PackageCreate
validatorErr error
)
var createPayload payloads.PackageCreate

BeforeEach(func() {
packageCreate = new(payloads.PackageCreate)
createPayload = payloads.PackageCreate{
Type: "bits",
Relationships: &payloads.PackageRelationships{
Expand All @@ -37,76 +33,176 @@ var _ = Describe("PackageCreate", func() {
}
})

JustBeforeEach(func() {
validatorErr = validator.DecodeAndValidateJSONPayload(createJSONRequest(createPayload), packageCreate)
})

It("succeeds", func() {
Expect(validatorErr).NotTo(HaveOccurred())
Expect(packageCreate).To(gstruct.PointTo(Equal(createPayload)))
})
Describe("Validate", func() {
var (
packageCreate *payloads.PackageCreate
validatorErr error
)

When("type is empty", func() {
BeforeEach(func() {
createPayload.Type = ""
packageCreate = new(payloads.PackageCreate)
})

It("returns an appropriate error", func() {
expectUnprocessableEntityError(validatorErr, "type cannot be blank")
JustBeforeEach(func() {
validatorErr = validator.DecodeAndValidateJSONPayload(createJSONRequest(createPayload), packageCreate)
})
})

When("type is not in the allowed list", func() {
BeforeEach(func() {
createPayload.Type = "foo"
It("succeeds", func() {
Expect(validatorErr).NotTo(HaveOccurred())
Expect(packageCreate).To(gstruct.PointTo(Equal(createPayload)))
})

It("returns an appropriate error", func() {
expectUnprocessableEntityError(validatorErr, "type value must be one of: bits")
When("data is specified", func() {
BeforeEach(func() {
createPayload.Data = &payloads.PackageData{}
})

It("returns an appropriate error", func() {
expectUnprocessableEntityError(validatorErr, "data must be blank")
})
})
})

When("relationships is not set", func() {
BeforeEach(func() {
createPayload.Relationships = nil
When("type is docker", func() {
BeforeEach(func() {
createPayload.Type = "docker"
createPayload.Data = &payloads.PackageData{
Image: "some/image",
}
})

It("succeeds", func() {
Expect(validatorErr).NotTo(HaveOccurred())
Expect(packageCreate).To(gstruct.PointTo(Equal(createPayload)))
})

When("image is not specified", func() {
BeforeEach(func() {
createPayload.Data = &payloads.PackageData{}
})

It("returns an appropriate error", func() {
expectUnprocessableEntityError(validatorErr, "data.image cannot be blank")
})
})
})

It("returns an appropriate error", func() {
expectUnprocessableEntityError(validatorErr, "relationships is required")
When("type is empty", func() {
BeforeEach(func() {
createPayload.Type = ""
})

It("returns an appropriate error", func() {
expectUnprocessableEntityError(validatorErr, "type cannot be blank")
})
})
})

When("relationships.app is not set", func() {
BeforeEach(func() {
createPayload.Relationships.App = nil
When("type is not in the allowed list", func() {
BeforeEach(func() {
createPayload.Type = "foo"
})

It("returns an appropriate error", func() {
expectUnprocessableEntityError(validatorErr, "type value must be one of: bits")
})
})

When("relationships is not set", func() {
BeforeEach(func() {
createPayload.Relationships = nil
})

It("returns an appropriate error", func() {
expectUnprocessableEntityError(validatorErr, "relationships is required")
})
})

It("returns an appropriate error", func() {
expectUnprocessableEntityError(validatorErr, "app is required")
When("relationships.app is not set", func() {
BeforeEach(func() {
createPayload.Relationships.App = nil
})

It("returns an appropriate error", func() {
expectUnprocessableEntityError(validatorErr, "app is required")
})
})
})

When("relationships.app is invalid", func() {
BeforeEach(func() {
createPayload.Relationships.App.Data.GUID = ""
When("relationships.app is invalid", func() {
BeforeEach(func() {
createPayload.Relationships.App.Data.GUID = ""
})

It("returns an appropriate error", func() {
expectUnprocessableEntityError(validatorErr, "guid cannot be blank")
})
})

It("returns an appropriate error", func() {
expectUnprocessableEntityError(validatorErr, "guid cannot be blank")
When("metadata is invalid", func() {
BeforeEach(func() {
createPayload.Metadata = payloads.Metadata{
Labels: map[string]string{
"foo.cloudfoundry.org/bar": "jim",
},
}
})

It("returns an appropriate error", func() {
expectUnprocessableEntityError(validatorErr, "label/annotation key cannot use the cloudfoundry.org domain")
})
})
})

When("metadata is invalid", func() {
BeforeEach(func() {
createPayload.Metadata = payloads.Metadata{
Labels: map[string]string{
"foo.cloudfoundry.org/bar": "jim",
Describe("ToMessage", func() {
var createMessage repositories.CreatePackageMessage

JustBeforeEach(func() {
createMessage = createPayload.ToMessage(repositories.AppRecord{
GUID: "guid",
SpaceGUID: "space-guid",
})
})

It("create the message", func() {
Expect(createMessage).To(Equal(repositories.CreatePackageMessage{
Type: "bits",
AppGUID: "guid",
SpaceGUID: "space-guid",
Metadata: repositories.Metadata{
Labels: map[string]string{
"foo": "bar",
},
Annotations: map[string]string{
"example.org/jim": "hello",
},
},
}
}))
})

It("returns an appropriate error", func() {
expectUnprocessableEntityError(validatorErr, "label/annotation key cannot use the cloudfoundry.org domain")
When("package type is docker", func() {
BeforeEach(func() {
createPayload.Type = "docker"
createPayload.Data = &payloads.PackageData{
Image: "some/image",
}
})

It("create the message", func() {
Expect(createMessage).To(Equal(repositories.CreatePackageMessage{
Type: "docker",
AppGUID: "guid",
SpaceGUID: "space-guid",
Metadata: repositories.Metadata{
Labels: map[string]string{
"foo": "bar",
},
Annotations: map[string]string{
"example.org/jim": "hello",
},
},
Data: &repositories.PackageData{
Image: "some/image",
},
}))
})
})
})
})
Expand Down
7 changes: 6 additions & 1 deletion api/presenter/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ type PackageResponse struct {
UpdatedAt string `json:"updated_at"`
}

type PackageData struct{}
type PackageData struct {
Image string `json:"image,omitempty"`
}

type PackageLinks struct {
Self Link `json:"self"`
Expand Down Expand Up @@ -65,5 +67,8 @@ func ForPackage(record repositories.PackageRecord, baseURL url.URL) PackageRespo
Labels: emptyMapIfNil(record.Labels),
Annotations: emptyMapIfNil(record.Annotations),
},
Data: PackageData{
Image: record.ImageRef,
},
}
}
52 changes: 52 additions & 0 deletions api/presenter/package_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,4 +110,56 @@ var _ = Describe("Package", func() {
Expect(output).To(MatchJSONPath("$.metadata.annotations", Not(BeNil())))
})
})

When("the package type is docker", func() {
BeforeEach(func() {
record.Type = "docker"
record.ImageRef = "some/image"
record.State = "READY"
})

It("produces the expected json", func() {
Expect(output).To(MatchJSON(`{
"guid": "the-package-guid",
"type": "docker",
"data": {
"image": "some/image"
},
"state": "READY",
"created_at": "1970-01-01T00:00:01Z",
"updated_at": "1970-01-01T00:00:02Z",
"relationships": {
"app": {
"data": {
"guid": "the-app-guid"
}
}
},
"links": {
"self": {
"href": "https://api.example.org/v3/packages/the-package-guid"
},
"upload": {
"href": "https://api.example.org/v3/packages/the-package-guid/upload",
"method": "POST"
},
"download": {
"href": "https://api.example.org/v3/packages/the-package-guid/download",
"method": "GET"
},
"app": {
"href": "https://api.example.org/v3/apps/the-app-guid"
}
},
"metadata": {
"labels": {
"foo": "bar"
},
"annotations": {
"baz": "fof"
}
}
}`))
})
})
})
Loading

0 comments on commit b290600

Please sign in to comment.